diff --git a/src/components/ion-tabs/core-ion-tabs.html b/src/components/ion-tabs/core-ion-tabs.html index e255871b6..123d340c1 100644 --- a/src/components/ion-tabs/core-ion-tabs.html +++ b/src/components/ion-tabs/core-ion-tabs.html @@ -1,5 +1,5 @@
- +
@@ -7,7 +7,7 @@
-
+
diff --git a/src/components/ion-tabs/ion-tabs.scss b/src/components/ion-tabs/ion-tabs.scss index 22cf41ef9..a4efdc295 100644 --- a/src/components/ion-tabs/ion-tabs.scss +++ b/src/components/ion-tabs/ion-tabs.scss @@ -1,3 +1,5 @@ +$core-sidetab-size: 60px !default; + ion-app.app-root core-ion-tabs { .tabbar { z-index: 101; // For some reason, the regular z-index isn't enough with our tabs, use a higher one. @@ -21,6 +23,31 @@ ion-app.app-root core-ion-tabs { .tab-badge.badge { background-color: $ion-tabs-badge-color; } + + &[tabsplacement="side"] { + .tabbar { + @include float(start); + width: $core-sidetab-size; + height: 100%; + flex-direction: column; + .tab-button { + width: 100%; + .tab-badge.badge { + @include position(calc(50% - 30px), 2px, null, null); + } + } + } + + .tabcontent { + width: calc(100% - #{($core-sidetab-size)}); + position: absolute; + @include position(0, 0, 0, 0); + core-ion-tab { + @include position(0, 0, 0, $core-sidetab-size); + position: relative; + } + } + } } ion-app.app-root.ios core-ion-tabs .core-ion-tabs-loading { diff --git a/src/core/mainmenu/pages/menu/menu.html b/src/core/mainmenu/pages/menu/menu.html index 9faebad6f..9ee9cf395 100644 --- a/src/core/mainmenu/pages/menu/menu.html +++ b/src/core/mainmenu/pages/menu/menu.html @@ -1,5 +1,5 @@ - + - + diff --git a/src/core/mainmenu/pages/menu/menu.ts b/src/core/mainmenu/pages/menu/menu.ts index ae3f68830..358b840bc 100644 --- a/src/core/mainmenu/pages/menu/menu.ts +++ b/src/core/mainmenu/pages/menu/menu.ts @@ -30,10 +30,12 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../provid }) export class CoreMainMenuPage implements OnDestroy { tabs: CoreMainMenuHandlerToDisplay[] = []; + allHandlers: CoreMainMenuHandlerToDisplay[]; loaded = false; redirectPage: string; redirectParams: any; showTabs = false; + tabsPlacement = 'bottom'; protected subscription; protected redirectObs: any; @@ -42,7 +44,8 @@ export class CoreMainMenuPage implements OnDestroy { @ViewChild('mainTabs') mainTabs: CoreIonTabsComponent; constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider, navParams: NavParams, - private navCtrl: NavController, private eventsProvider: CoreEventsProvider, private cdr: ChangeDetectorRef) { + private navCtrl: NavController, private eventsProvider: CoreEventsProvider, private cdr: ChangeDetectorRef, + private mainMenuProvider: CoreMainMenuProvider) { // Check if the menu was loaded with a redirect. const redirectPage = navParams.get('redirectPage'); @@ -78,10 +81,34 @@ export class CoreMainMenuPage implements OnDestroy { this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { // Remove the handlers that should only appear in the More menu. - handlers = handlers.filter((handler) => { + this.allHandlers = handlers.filter((handler) => { return !handler.onlyInMore; }); - handlers = handlers.slice(0, CoreMainMenuProvider.NUM_MAIN_HANDLERS); // Get main handlers. + + 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)); + } + + /** + * Init handlers on change (size or handlers). + */ + initHandlers(): void { + if (this.allHandlers) { + this.tabsPlacement = this.mainMenuProvider.getTabPlacement(this.navCtrl); + + const handlers = this.allHandlers.slice(0, this.mainMenuProvider.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 = []; @@ -94,9 +121,32 @@ export class CoreMainMenuPage implements OnDestroy { return 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) => { + return tab.page == selectedTab.root && tab.icon == selectedTab.tabIcon; + }); + + if (oldTab) { + // Check if the selected handler is visible. + const isVisible = newTabs.some((newTab) => { + return 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. @@ -105,17 +155,7 @@ export class CoreMainMenuPage implements OnDestroy { }); this.loaded = this.menuDelegate.areHandlersLoaded(); - - 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; - } - }); - } - }); + } } /** @@ -153,5 +193,6 @@ export class CoreMainMenuPage implements OnDestroy { ngOnDestroy(): void { this.subscription && this.subscription.unsubscribe(); this.redirectObs && this.redirectObs.off(); + window.removeEventListener('resize', this.initHandlers.bind(this)); } } diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index 9027f7012..03fc83452 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -29,6 +29,7 @@ import { CoreMainMenuProvider, CoreMainMenuCustomItem } from '../../providers/ma }) export class CoreMainMenuMorePage implements OnDestroy { handlers: CoreMainMenuHandlerData[]; + allHandlers: CoreMainMenuHandlerData[]; handlersLoaded: boolean; siteInfo: any; siteName: string; @@ -58,32 +59,44 @@ export class CoreMainMenuMorePage implements OnDestroy { ionViewDidLoad(): void { // Load the handlers. this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { - // Calculate the main handlers to not display them in this view. - const mainHandlers = handlers.filter((handler) => { - return !handler.onlyInMore; - }).slice(0, CoreMainMenuProvider.NUM_MAIN_HANDLERS); + this.allHandlers = handlers; - // Get only the handlers that don't appear in the main view. - this.handlers = []; - handlers.forEach((handler) => { - if (mainHandlers.indexOf(handler) == -1) { - this.handlers.push(handler); - } - }); - - this.handlersLoaded = this.menuDelegate.areHandlersLoaded(); + this.initHandlers(); }); + + window.addEventListener('resize', this.initHandlers.bind(this)); } /** * Page destroyed. */ ngOnDestroy(): void { + window.removeEventListener('resize', this.initHandlers.bind(this)); + if (this.subscription) { this.subscription.unsubscribe(); } } + /** + * Init handlers on change (size or handlers). + */ + initHandlers(): void { + if (this.allHandlers) { + // Calculate the main handlers not to display them in this view. + const mainHandlers = this.allHandlers.filter((handler) => { + return !handler.onlyInMore; + }).slice(0, this.mainMenuProvider.getNumItems()); + + // Get only the handlers that don't appear in the main view. + this.handlers = this.allHandlers.filter((handler) => { + return mainHandlers.indexOf(handler) == -1; + }); + + this.handlersLoaded = this.menuDelegate.areHandlersLoaded(); + } + } + /** * Load the site info required by the view. */ diff --git a/src/core/mainmenu/providers/delegate.ts b/src/core/mainmenu/providers/delegate.ts index 9f7ea3bdf..a32d6e040 100644 --- a/src/core/mainmenu/providers/delegate.ts +++ b/src/core/mainmenu/providers/delegate.ts @@ -111,6 +111,12 @@ export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData { * @type {number} */ priority?: number; + + /** + * Hide tab. Used then resizing. + * @type {[type]} + */ + hide?: boolean; } /** diff --git a/src/core/mainmenu/providers/mainmenu.ts b/src/core/mainmenu/providers/mainmenu.ts index ac38ef92b..5f8fe59ed 100644 --- a/src/core/mainmenu/providers/mainmenu.ts +++ b/src/core/mainmenu/providers/mainmenu.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { NavController } from 'ionic-angular'; import { CoreLangProvider } from '@providers/lang'; import { CoreSitesProvider } from '@providers/sites'; import { CoreConfigConstants } from '../../../configconstants'; @@ -52,8 +53,12 @@ export interface CoreMainMenuCustomItem { @Injectable() export class CoreMainMenuProvider { static NUM_MAIN_HANDLERS = 4; + static ITEM_MIN_WIDTH = 72; // Min with of every item, based on 5 items on a 360 pixel wide screen. + protected tablet = false; - constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) { } + constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) { + this.tablet = window && window.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576; + } /** * Get a list of custom menu items for a certain site. @@ -158,4 +163,62 @@ export class CoreMainMenuProvider { }); }); } + + /** + * Get the number of items to be shown on the main menu bar. + * + * @return {number} Number of items depending on the device width. + */ + getNumItems(): number { + if (!this.isResponsiveMainMenuItemsDisabledInCurrentSite() && window && window.innerWidth) { + let numElements; + + 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. + * + * @param {NavController} navCtrl NavController to resize the content. + * @return {string} Tabs placement including side value. + */ + getTabPlacement(navCtrl: NavController): string { + const tablet = window && window.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576; + + if (tablet != this.tablet) { + this.tablet = tablet; + + // Resize so content margins can be updated. + setTimeout(() => { + navCtrl.resize(); + }, 500); + } + + return tablet ? 'side' : 'bottom'; + } + + /** + * Check if responsive main menu items is disabled in the current site. + * + * @return {boolean} Whether it's disabled. + */ + protected isResponsiveMainMenuItemsDisabledInCurrentSite(): boolean { + const site = this.sitesProvider.getCurrentSite(); + + return site && site.isFeatureDisabled('NoDelegate_ResponsiveMainMenuItems'); + } }