MOBILE-2302 mainmenu: Implement More page

main
Dani Palou 2017-12-07 13:29:42 +01:00
parent 704e3796a0
commit c950d7dd40
8 changed files with 361 additions and 4 deletions

View File

@ -853,10 +853,10 @@ export class CoreSite {
/**
* Returns the URL to the documentation of the app, based on Moodle version and current language.
*
* @param {string} [page] Docs page to go to.
* @return {Promise} Promise resolved with the Moodle docs URL.
* @param {string} [page] Docs page to go to.
* @return {Promise<string>} Promise resolved with the Moodle docs URL.
*/
getDocsUrl(page: string) : Promise<string> {
getDocsUrl(page?: string) : Promise<string> {
const release = this.infos.release ? this.infos.release : undefined;
return this.urlUtils.getDocsUrl(release, page);
}

View File

@ -14,6 +14,7 @@
import { NgModule } from '@angular/core';
import { CoreMainMenuDelegate } from './providers/delegate';
import { CoreMainMenuProvider } from './providers/mainmenu';
@NgModule({
declarations: [
@ -22,6 +23,7 @@ import { CoreMainMenuDelegate } from './providers/delegate';
],
providers: [
CoreMainMenuDelegate,
CoreMainMenuProvider
]
})
export class CoreMainMenuModule {}

View File

@ -16,6 +16,7 @@ import { Component, OnDestroy } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import { CoreEventsProvider } from '../../../../providers/events';
import { CoreSitesProvider } from '../../../../providers/sites';
import { CoreMainMenuProvider } from '../../providers/mainmenu';
import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/delegate';
/**
@ -56,7 +57,7 @@ export class CoreMainMenuPage implements OnDestroy {
}
this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => {
this.tabs = handlers.slice(0, 4); // Get first 4.
this.tabs = handlers.slice(0, CoreMainMenuProvider.NUM_MAIN_HANDLERS); // Get main handlers.
this.tabs.push(this.moreTabData); // Add "More" tab.
this.loaded = this.menuDelegate.areHandlersLoaded();
});

View File

@ -0,0 +1,52 @@
<ion-header>
<ion-navbar>
<ion-title>{{ siteInfo.sitename }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<a ion-item core-user-link>
<ion-avatar item-start>
<img [src]="siteInfo.userpictureurl" core-external-content alt="{{ 'core.pictureof' | translate:{$a: siteInfo.fullname} }}" role="presentation">
</ion-avatar>
<p>{{siteInfo.fullname}}</p>
</a>
<ion-item-divider color="light"></ion-item-divider>
<ion-item class="text-center" *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
<ion-spinner></ion-spinner>
</ion-item>
<ion-item *ngFor="let handler of handlers" class="mm-sidemenu-handler {{handler.class}}" (click)="openHandler(handler)" title="{{ handler.title | translate }}">
<ion-icon [name]="handler.icon" item-start></ion-icon>
<p>{{ handler.title | translate}}</p>
<!-- @todo: Badge. -->
<!-- <span ng-show="!loading && badge" class="badge badge-positive">{{badge}}</span>
<ion-spinner ng-if="loading" class="icon"></ion-spinner> -->
</ion-item>
<div *ngFor="let item of customItems" class="mm-sidemenu-customitem">
<a ion-item *ngIf="item.type != 'embedded'" [href]="item.url" core-link [capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'" title="{{item.label}}">
<ion-icon [name]="item.icon" item-start></ion-icon>
<p>{{item.label}}</p>
</a>
<a ion-item *ngIf="item.type == 'embedded'" (click)="openItem(item)" title="{{item.label}}">
<ion-icon [name]="item.icon" item-start></ion-icon>
<p>{{item.label}}</p>
</a>
</div>
<a *ngIf="showWeb" ion-item [href]="siteInfo.siteurl" core-link autoLogin="yes" title="{{ 'core.mainmenu.website' | translate }}">
<ion-icon name="globe" item-start></ion-icon>
<p>{{ 'core.mainmenu.website' | translate }}</p>
</a>
<a *ngIf="showHelp" ion-item [href]="docsUrl" core-link autoLogin="no" title="{{ 'core.mainmenu.help' | translate }}">
<ion-icon name="help-buoy" item-start></ion-icon>
<p>{{ 'core.mainmenu.help' | translate }}</p>
</a>
<a ion-item (click)="openSettings()" title="{{ 'core.mainmenu.appsettings' | translate }}">
<ion-icon name="cog" item-start></ion-icon>
<p>{{ 'core.mainmenu.appsettings' | translate }}</p>
</a>
<a ion-item (click)="logout()" title="{{ logoutLabel | translate }}">
<ion-icon name="log-out" item-start></ion-icon>
<p>{{ logoutLabel | translate }}</p>
</a>
</ion-list>
</ion-content>

View File

@ -0,0 +1,35 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreMainMenuMorePage } from './more';
import { CoreMainMenuModule } from '../../mainmenu.module';
import { CoreComponentsModule } from '../../../../components/components.module';
import { CoreDirectivesModule } from '../../../../directives/directives.module';
@NgModule({
declarations: [
CoreMainMenuMorePage,
],
imports: [
CoreComponentsModule,
CoreDirectivesModule,
CoreMainMenuModule,
IonicPageModule.forChild(CoreMainMenuMorePage),
TranslateModule.forChild()
],
})
export class CoreMainMenuPageModule {}

View File

@ -0,0 +1,3 @@
page-core-mainmenu-more {
}

View File

@ -0,0 +1,129 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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, OnDestroy } from '@angular/core';
import { IonicPage, NavController } from 'ionic-angular';
import { CoreEventsProvider } from '../../../../providers/events';
import { CoreSitesProvider } from '../../../../providers/sites';
import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/delegate';
import { CoreMainMenuProvider, CoreMainMenuCustomItem } from '../../providers/mainmenu';
/**
* Page that displays the list of main menu options that aren't in the tabs.
*/
@IonicPage()
@Component({
selector: 'page-core-mainmenu-more',
templateUrl: 'more.html',
})
export class CoreMainMenuMorePage implements OnDestroy {
handlers: CoreMainMenuHandlerData[];
handlersLoaded: boolean;
siteInfo: any;
logoutLabel: string;
showWeb: boolean;
showHelp: boolean;
docsUrl: string;
customItems: CoreMainMenuCustomItem[];
protected subscription;
protected langObserver;
protected updateSiteObserver;
constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider,
private navCtrl: NavController, private mainMenuProvider: CoreMainMenuProvider, eventsProvider: CoreEventsProvider) {
this.langObserver = eventsProvider.on(CoreEventsProvider.LANGUAGE_CHANGED, this.loadSiteInfo.bind(this));
this.updateSiteObserver = eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => {
if (sitesProvider.getCurrentSiteId() == data.siteId) {
this.loadSiteInfo();
}
});
this.loadSiteInfo();
}
/**
* View loaded.
*/
ionViewDidLoad() {
// Load the handlers.
this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => {
this.handlers = handlers.slice(CoreMainMenuProvider.NUM_MAIN_HANDLERS); // Remove the main handlers.
this.handlersLoaded = this.menuDelegate.areHandlersLoaded();
});
}
/**
* Page destroyed.
*/
ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
/**
* Load the site info required by the view.
*/
protected loadSiteInfo() {
const currentSite = this.sitesProvider.getCurrentSite(),
config = currentSite.getStoredConfig();
this.siteInfo = currentSite.getInfo();
this.logoutLabel = 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout': 'changesite');
this.showWeb = !currentSite.isFeatureDisabled('$mmSideMenuDelegate_website');
this.showHelp = !currentSite.isFeatureDisabled('$mmSideMenuDelegate_help');
currentSite.getDocsUrl().then((docsUrl) => {
this.docsUrl = docsUrl;
});
this.mainMenuProvider.getCustomMenuItems().then((items) => {
this.customItems = items;
});
}
/**
* Open a handler.
*
* @param {CoreMainMenuHandlerData} handler Handler to open.
*/
openHandler(handler: CoreMainMenuHandlerData) {
// @todo.
}
/**
* Open an embedded custom item.
*
* @param {CoreMainMenuCustomItem} item Item to open.
*/
openItem(item: CoreMainMenuCustomItem) {
// @todo.
}
/**
* Open settings page.
*/
openSettings() {
this.navCtrl.push('CoreSettingsListPage');
}
/**
* Logout the user.
*/
logout() {
this.sitesProvider.logout();
}
}

View File

@ -0,0 +1,135 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { CoreLangProvider } from '../../../providers/lang';
import { CoreSitesProvider } from '../../../providers/sites';
import { CoreConfigConstants } from '../../../configconstants';
export interface CoreMainMenuCustomItem {
type: string;
url: string;
label: string;
icon: string;
};
/**
* Service that provides some features regarding Main Menu.
*/
@Injectable()
export class CoreMainMenuProvider {
public static NUM_MAIN_HANDLERS = 4;
constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) {}
/**
* Get a list of custom menu items for a certain site.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<CoreMainMenuCustomItem[]>} List of custom menu items.
*/
getCustomMenuItems(siteId?: string) : Promise<CoreMainMenuCustomItem[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
let itemsString = site.getStoredConfig('tool_mobile_custommenuitems'),
items,
position = 0, // Position of each item, to keep the same order as it's configured.
map = {},
result = [];
if (!itemsString || typeof itemsString != 'string') {
// Setting not valid.
return result;
}
// Add items to the map.
items = itemsString.split(/(?:\r\n|\r|\n)/);
items.forEach((item) => {
let values = item.split('|'),
id,
label = values[0] ? values[0].trim() : values[0],
url = values[1] ? values[1].trim() : values[1],
type = values[2] ? values[2].trim() : values[2],
lang = (values[3] ? values[3].trim() : values[3]) || 'none',
icon = values[4] ? values[4].trim() : values[4];
if (!label || !url || !type) {
// Invalid item, ignore it.
return;
}
id = url + '#' + type;
if (!icon) {
// Icon not defined, use default one.
icon = type == 'embedded' ? 'qr-scanner' : 'link';
}
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;
}
return this.langProvider.getCurrentLanguage().then((currentLang) => {
const fallbackLang = CoreConfigConstants.default_lang || 'en';
// Get the right label for each entry and add it to the result.
for (let id in map) {
let entry = map[id],
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 (let lang in entry.labels) {
if (lang.indexOf('_only') == -1) {
data = entry.labels[lang];
break;
}
}
if (!data) {
// No valid label, ignore this entry.
return;
}
}
result[entry.position] = {
url: entry.url,
type: entry.type,
label: data.label,
icon: data.icon
};
}
return result;
});
});
}
}