diff --git a/src/classes/site.ts b/src/classes/site.ts index 9a2673b75..901e9d7e7 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -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} Promise resolved with the Moodle docs URL. */ - getDocsUrl(page: string) : Promise { + getDocsUrl(page?: string) : Promise { const release = this.infos.release ? this.infos.release : undefined; return this.urlUtils.getDocsUrl(release, page); } diff --git a/src/core/mainmenu/mainmenu.module.ts b/src/core/mainmenu/mainmenu.module.ts index 7cee1594c..0a49f84b8 100644 --- a/src/core/mainmenu/mainmenu.module.ts +++ b/src/core/mainmenu/mainmenu.module.ts @@ -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 {} diff --git a/src/core/mainmenu/pages/menu/menu.ts b/src/core/mainmenu/pages/menu/menu.ts index 8ed726590..e9bab31e5 100644 --- a/src/core/mainmenu/pages/menu/menu.ts +++ b/src/core/mainmenu/pages/menu/menu.ts @@ -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(); }); diff --git a/src/core/mainmenu/pages/more/more.html b/src/core/mainmenu/pages/more/more.html new file mode 100644 index 000000000..d1c02dc11 --- /dev/null +++ b/src/core/mainmenu/pages/more/more.html @@ -0,0 +1,52 @@ + + + {{ siteInfo.sitename }} + + + + + + + {{ 'core.pictureof' | translate:{$a: siteInfo.fullname} }} + +

{{siteInfo.fullname}}

+
+ + + + + + +

{{ handler.title | translate}}

+ + +
+ + + +

{{ 'core.mainmenu.website' | translate }}

+
+ + +

{{ 'core.mainmenu.help' | translate }}

+
+ + +

{{ 'core.mainmenu.appsettings' | translate }}

+
+ + +

{{ logoutLabel | translate }}

+
+
+
diff --git a/src/core/mainmenu/pages/more/more.module.ts b/src/core/mainmenu/pages/more/more.module.ts new file mode 100644 index 000000000..dd338a3ea --- /dev/null +++ b/src/core/mainmenu/pages/more/more.module.ts @@ -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 {} diff --git a/src/core/mainmenu/pages/more/more.scss b/src/core/mainmenu/pages/more/more.scss new file mode 100644 index 000000000..ac9a4b40b --- /dev/null +++ b/src/core/mainmenu/pages/more/more.scss @@ -0,0 +1,3 @@ +page-core-mainmenu-more { + +} diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts new file mode 100644 index 000000000..7f5b190cc --- /dev/null +++ b/src/core/mainmenu/pages/more/more.ts @@ -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(); + } +} diff --git a/src/core/mainmenu/providers/mainmenu.ts b/src/core/mainmenu/providers/mainmenu.ts new file mode 100644 index 000000000..148e2473f --- /dev/null +++ b/src/core/mainmenu/providers/mainmenu.ts @@ -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} List of custom menu items. + */ + getCustomMenuItems(siteId?: string) : Promise { + 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; + }); + }); + } +}