commit
29860227f9
|
@ -15,7 +15,8 @@
|
|||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicModule, Platform } from 'ionic-angular';
|
||||
import { IonicApp, IonicModule, Platform, Content, ScrollEvent } from 'ionic-angular';
|
||||
import { assert } from 'ionic-angular/util/util';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
|
||||
|
@ -246,5 +247,170 @@ export class AppModule {
|
|||
|
||||
// Execute the init processes.
|
||||
initDelegate.executeInitProcesses();
|
||||
|
||||
// Decorate ion-content.
|
||||
this.decorateIonContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate ion-content to make our ion-tabs work.
|
||||
* https://github.com/ionic-team/ionic/issues/14483
|
||||
*/
|
||||
protected decorateIonContent(): void {
|
||||
|
||||
const parsePxUnit = (val: string): number => {
|
||||
return (val.indexOf('px') > 0) ? parseInt(val, 10) : 0;
|
||||
};
|
||||
|
||||
// We need to convert the prototype to any because _readDimensions is private.
|
||||
// tslint:disable: typedef
|
||||
(<any> Content.prototype)._readDimensions = function() {
|
||||
const cachePaddingTop = this._pTop;
|
||||
const cachePaddingRight = this._pRight;
|
||||
const cachePaddingBottom = this._pBottom;
|
||||
const cachePaddingLeft = this._pLeft;
|
||||
const cacheHeaderHeight = this._hdrHeight;
|
||||
const cacheFooterHeight = this._ftrHeight;
|
||||
const cacheTabsPlacement = this._tabsPlacement;
|
||||
let tabsTop = 0;
|
||||
let scrollEvent: ScrollEvent;
|
||||
this._pTop = 0;
|
||||
this._pRight = 0;
|
||||
this._pBottom = 0;
|
||||
this._pLeft = 0;
|
||||
this._hdrHeight = 0;
|
||||
this._ftrHeight = 0;
|
||||
this._tabsPlacement = null;
|
||||
this._tTop = 0;
|
||||
this._fTop = 0;
|
||||
this._fBottom = 0;
|
||||
|
||||
// In certain cases this._scroll is undefined, if that is the case then we should just return.
|
||||
if (!this._scroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollEvent = this._scroll.ev;
|
||||
|
||||
let ele: HTMLElement = this.getNativeElement();
|
||||
if (!ele) {
|
||||
assert(false, 'ele should be valid');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let computedStyle: any;
|
||||
let tagName: string;
|
||||
const parentEle: HTMLElement = ele.parentElement;
|
||||
const children = parentEle.children;
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
ele = <HTMLElement> children[i];
|
||||
tagName = ele.tagName;
|
||||
if (tagName === 'ION-CONTENT') {
|
||||
scrollEvent.contentElement = ele;
|
||||
|
||||
if (this._fullscreen) {
|
||||
// ******** DOM READ ****************
|
||||
computedStyle = getComputedStyle(ele);
|
||||
this._pTop = parsePxUnit(computedStyle.paddingTop);
|
||||
this._pBottom = parsePxUnit(computedStyle.paddingBottom);
|
||||
this._pRight = parsePxUnit(computedStyle.paddingRight);
|
||||
this._pLeft = parsePxUnit(computedStyle.paddingLeft);
|
||||
}
|
||||
|
||||
} else if (tagName === 'ION-HEADER') {
|
||||
scrollEvent.headerElement = ele;
|
||||
|
||||
// ******** DOM READ ****************
|
||||
this._hdrHeight = ele.clientHeight;
|
||||
|
||||
} else if (tagName === 'ION-FOOTER') {
|
||||
scrollEvent.footerElement = ele;
|
||||
|
||||
// ******** DOM READ ****************
|
||||
this._ftrHeight = ele.clientHeight;
|
||||
this._footerEle = ele;
|
||||
}
|
||||
}
|
||||
|
||||
ele = parentEle;
|
||||
let tabbarEle: HTMLElement;
|
||||
|
||||
while (ele && ele.tagName !== 'ION-MODAL' && !ele.classList.contains('tab-subpage')) {
|
||||
|
||||
if (ele.tagName.indexOf('ION-TABS') != -1) {
|
||||
tabbarEle = <HTMLElement> ele.firstElementChild;
|
||||
// ******** DOM READ ****************
|
||||
this._tabbarHeight = tabbarEle.clientHeight;
|
||||
|
||||
if (this._tabsPlacement === null) {
|
||||
// This is the first tabbar found, remember its position.
|
||||
this._tabsPlacement = ele.getAttribute('tabsplacement');
|
||||
}
|
||||
}
|
||||
|
||||
ele = ele.parentElement;
|
||||
}
|
||||
|
||||
// Tabs top
|
||||
if (this._tabs && this._tabsPlacement === 'top') {
|
||||
this._tTop = this._hdrHeight;
|
||||
tabsTop = this._tabs._top;
|
||||
}
|
||||
|
||||
// Toolbar height
|
||||
this._cTop = this._hdrHeight;
|
||||
this._cBottom = this._ftrHeight;
|
||||
|
||||
// Tabs height
|
||||
if (this._tabsPlacement === 'top') {
|
||||
this._cTop += this._tabbarHeight;
|
||||
|
||||
} else if (this._tabsPlacement === 'bottom') {
|
||||
this._cBottom += this._tabbarHeight;
|
||||
}
|
||||
|
||||
// Refresher uses a border which should be hidden unless pulled
|
||||
if (this._hasRefresher) {
|
||||
this._cTop -= 1;
|
||||
}
|
||||
|
||||
// Fixed content shouldn't include content padding
|
||||
this._fTop = this._cTop;
|
||||
this._fBottom = this._cBottom;
|
||||
|
||||
// Handle fullscreen viewport (padding vs margin)
|
||||
if (this._fullscreen) {
|
||||
this._cTop += this._pTop;
|
||||
this._cBottom += this._pBottom;
|
||||
}
|
||||
|
||||
// ******** DOM READ ****************
|
||||
const contentDimensions = this.getContentDimensions();
|
||||
scrollEvent.scrollHeight = contentDimensions.scrollHeight;
|
||||
scrollEvent.scrollWidth = contentDimensions.scrollWidth;
|
||||
scrollEvent.contentHeight = contentDimensions.contentHeight;
|
||||
scrollEvent.contentWidth = contentDimensions.contentWidth;
|
||||
scrollEvent.contentTop = contentDimensions.contentTop;
|
||||
scrollEvent.contentBottom = contentDimensions.contentBottom;
|
||||
|
||||
this._dirty = (
|
||||
cachePaddingTop !== this._pTop ||
|
||||
cachePaddingBottom !== this._pBottom ||
|
||||
cachePaddingLeft !== this._pLeft ||
|
||||
cachePaddingRight !== this._pRight ||
|
||||
cacheHeaderHeight !== this._hdrHeight ||
|
||||
cacheFooterHeight !== this._ftrHeight ||
|
||||
cacheTabsPlacement !== this._tabsPlacement ||
|
||||
tabsTop !== this._tTop ||
|
||||
this._cTop !== this.contentTop ||
|
||||
this._cBottom !== this.contentBottom
|
||||
);
|
||||
|
||||
this._scroll.init(this.getScrollElement(), this._cTop, this._cBottom);
|
||||
|
||||
// Initial imgs refresh.
|
||||
this.imgsUpdate();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -661,3 +661,8 @@ canvas[core-chart] {
|
|||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
// For some reason, in iOS the pages don't inherit the background-color from ion-app, set it explicitly.
|
||||
.ion-page {
|
||||
background-color: $background-color;
|
||||
}
|
|
@ -45,6 +45,8 @@ import { CoreRecaptchaComponent } from './recaptcha/recaptcha';
|
|||
import { CoreRecaptchaModalComponent } from './recaptcha/recaptchamodal';
|
||||
import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar';
|
||||
import { CoreAttachmentsComponent } from './attachments/attachments';
|
||||
import { CoreIonTabsComponent } from './ion-tabs/ion-tabs';
|
||||
import { CoreIonTabComponent } from './ion-tabs/ion-tab';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -75,7 +77,9 @@ import { CoreAttachmentsComponent } from './attachments/attachments';
|
|||
CoreRecaptchaComponent,
|
||||
CoreRecaptchaModalComponent,
|
||||
CoreNavigationBarComponent,
|
||||
CoreAttachmentsComponent
|
||||
CoreAttachmentsComponent,
|
||||
CoreIonTabsComponent,
|
||||
CoreIonTabComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreContextMenuPopoverComponent,
|
||||
|
@ -113,7 +117,9 @@ import { CoreAttachmentsComponent } from './attachments/attachments';
|
|||
CoreTimerComponent,
|
||||
CoreRecaptchaComponent,
|
||||
CoreNavigationBarComponent,
|
||||
CoreAttachmentsComponent
|
||||
CoreAttachmentsComponent,
|
||||
CoreIonTabsComponent,
|
||||
CoreIonTabComponent
|
||||
]
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// (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, Optional, ElementRef, NgZone, Renderer, ComponentFactoryResolver, ChangeDetectorRef, ErrorHandler, OnInit,
|
||||
OnDestroy, ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { Tab, App, Config, Platform, GestureController, DeepLinker, DomController } from 'ionic-angular';
|
||||
import { TransitionController } from 'ionic-angular/transitions/transition-controller';
|
||||
import { CoreIonTabsComponent } from './ion-tabs';
|
||||
|
||||
/**
|
||||
* Equivalent to ion-tab, but to be used inside core-ion-tabs.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-ion-tab',
|
||||
template: '<div #viewport></div><div class="nav-decor"></div>',
|
||||
host: {
|
||||
'[attr.id]': '_tabId',
|
||||
'[attr.aria-labelledby]': '_btnId',
|
||||
'role': 'tabpanel'
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class CoreIonTabComponent extends Tab implements OnInit, OnDestroy {
|
||||
|
||||
constructor(parent: CoreIonTabsComponent, app: App, config: Config, plt: Platform, elementRef: ElementRef, zone: NgZone,
|
||||
renderer: Renderer, cfr: ComponentFactoryResolver, _cd: ChangeDetectorRef, gestureCtrl: GestureController,
|
||||
transCtrl: TransitionController, @Optional() linker: DeepLinker, _dom: DomController, errHandler: ErrorHandler) {
|
||||
super(parent, app, config, plt, elementRef, zone, renderer, cfr, _cd, gestureCtrl, transCtrl, linker, _dom, errHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
|
||||
this.parent.add(this, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy();
|
||||
|
||||
this.parent.remove(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<div class="tabbar" role="tablist" #tabbar>
|
||||
<a [hidden]="_loaded === false" *ngFor="let t of _tabs" [tab]="t" class="tab-button" role="tab" href="#" (ionSelect)="select(t)"></a>
|
||||
<div class="tab-highlight"></div>
|
||||
<div *ngIf="_loaded === false" class="core-ion-tabs-loading">
|
||||
<span class="core-ion-tabs-loading-spinner">
|
||||
<ion-spinner></ion-spinner>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div #originalTabs>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
<div #portal tab-portal></div>
|
|
@ -0,0 +1,93 @@
|
|||
core-ion-tabs {
|
||||
.tabbar {
|
||||
z-index: 101; // For some reason, the regular z-index isn't enough with our tabs, use a higher one.
|
||||
|
||||
.core-ion-tabs-loading {
|
||||
width: 100%;
|
||||
display: table;
|
||||
|
||||
.core-ion-tabs-loading-spinner {
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
.spinner circle, .spinner line {
|
||||
stroke: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.ios core-ion-tabs .core-ion-tabs-loading {
|
||||
min-height: $tabs-ios-tab-min-height;
|
||||
}
|
||||
|
||||
.md core-ion-tabs .core-ion-tabs-loading {
|
||||
min-height: $tabs-md-tab-min-height;
|
||||
}
|
||||
|
||||
.wp core-ion-tabs .core-ion-tabs-loading {
|
||||
min-height: $tabs-wp-tab-min-height;
|
||||
}
|
||||
|
||||
// Copy some styles from ion-tabs and ion-tab.
|
||||
core-ion-tabs, core-ion-tab {
|
||||
@include position(0, null, null, 0);
|
||||
|
||||
position: absolute;
|
||||
z-index: $z-index-page-container;
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
core-ion-tab {
|
||||
display: none;
|
||||
}
|
||||
|
||||
core-ion-tab.show-tab {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@mixin core-ion-tabs-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $cordova-statusbar-padding, $modal-max-width, $style-title: false) {
|
||||
|
||||
core-ion-tab > .ion-page,
|
||||
core-ion-tab > .ion-page > ion-header,
|
||||
core-ion-tabs > .ion-page.tab-subpage > ion-header {
|
||||
@include toolbar-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $cordova-statusbar-padding);
|
||||
|
||||
// If we should style the title elements in the toolbar
|
||||
@if ($style-title) {
|
||||
@include toolbar-title-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $cordova-statusbar-padding);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $modal-max-width) {
|
||||
.modal-wrapper > .ion-page > ion-header {
|
||||
@include toolbar-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $cordova-statusbar-padding);
|
||||
|
||||
// If we should style the title elements in the toolbar
|
||||
@if ($style-title) {
|
||||
@include toolbar-title-statusbar-padding($toolbar-height, $toolbar-padding, $content-padding, $cordova-statusbar-padding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ios {
|
||||
@include core-ion-tabs-statusbar-padding($toolbar-ios-height, $toolbar-ios-padding, $content-ios-padding, $cordova-ios-statusbar-padding, $cordova-ios-statusbar-padding-modal-max-width, true);
|
||||
}
|
||||
|
||||
.md {
|
||||
@include core-ion-tabs-statusbar-padding($toolbar-md-height, $toolbar-md-padding, $content-md-padding, $cordova-md-statusbar-padding, $cordova-md-statusbar-padding-modal-max-width);
|
||||
}
|
||||
|
||||
.wp {
|
||||
@include core-ion-tabs-statusbar-padding($toolbar-wp-height, $toolbar-wp-padding, $content-wp-padding, $cordova-wp-statusbar-padding, $cordova-wp-statusbar-padding-modal-max-width);
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
// (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, Optional, ElementRef, Renderer, ViewEncapsulation, forwardRef, ViewChild, Input } from '@angular/core';
|
||||
import { Tabs, NavController, ViewController, App, Config, Platform, DeepLinker, Keyboard, RootNode } from 'ionic-angular';
|
||||
import { CoreIonTabComponent } from './ion-tab';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Equivalent to ion-tabs. It has 2 improvements:
|
||||
* - If a core-ion-tab is added or removed, it will be reflected in the tab bar in the right position.
|
||||
* - It supports a loaded input to tell when are the tabs ready.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-ion-tabs',
|
||||
templateUrl: 'ion-tabs.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [{provide: RootNode, useExisting: forwardRef(() => CoreIonTabsComponent) }]
|
||||
})
|
||||
export class CoreIonTabsComponent extends Tabs {
|
||||
|
||||
/**
|
||||
* Whether the tabs have been loaded. If defined, tabs won't be initialized until it's set to true.
|
||||
*/
|
||||
@Input() set loaded(val: boolean) {
|
||||
this._loaded = this.utils.isTrueOrOne(val);
|
||||
|
||||
if (this.viewInit && !this.initialized) {
|
||||
// Use a setTimeout to make sure the tabs have been loaded.
|
||||
setTimeout(() => {
|
||||
this.initTabs();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Input() selectedDisabled: boolean; // Whether the initial tab selected can be a disabled tab.
|
||||
|
||||
@ViewChild('originalTabs') originalTabsRef: ElementRef;
|
||||
|
||||
_loaded: boolean; // Whether tabs have been loaded.
|
||||
|
||||
/**
|
||||
* List of tabs that haven't been initialized yet. This is required because IonTab calls add() on the constructor,
|
||||
* but we need it to be called in OnInit to be able to determine the tab position.
|
||||
* @type {CoreIonTabComponent[]}
|
||||
*/
|
||||
protected tabsNotInit: CoreIonTabComponent[] = [];
|
||||
|
||||
protected tabsIds: string[] = []; // An array to keep the order of tab IDs when they're sorted.
|
||||
protected tabsNotInitIds: string[] = []; // An array to keep the order of tab IDs for non-init tabs.
|
||||
protected viewInit = false; // Whether the view has been initialized.
|
||||
protected initialized = false; // Whether tabs have been initialized.
|
||||
|
||||
constructor(protected utils: CoreUtilsProvider, @Optional() parent: NavController, @Optional() viewCtrl: ViewController,
|
||||
_app: App, config: Config, elementRef: ElementRef, _plt: Platform, renderer: Renderer, _linker: DeepLinker,
|
||||
keyboard?: Keyboard) {
|
||||
super(parent, viewCtrl, _app, config, elementRef, _plt, renderer, _linker, keyboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* View has been initialized.
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.viewInit = true;
|
||||
|
||||
super.ngAfterViewInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new tab if it isn't already in the list of tabs.
|
||||
*
|
||||
* @param {CoreIonTabComponent} tab The tab to add.
|
||||
* @param {boolean} [isInit] Whether the tab has been initialized.
|
||||
* @return {string} The tab ID.
|
||||
*/
|
||||
add(tab: CoreIonTabComponent, isInit?: boolean): string {
|
||||
// Check if tab is already in the list of initialized tabs.
|
||||
let position = this._tabs.indexOf(tab);
|
||||
|
||||
if (position != -1) {
|
||||
return this.tabsIds[position];
|
||||
}
|
||||
|
||||
// Now check if the tab is in the not init list.
|
||||
position = this.tabsNotInit.indexOf(tab);
|
||||
if (position != -1) {
|
||||
if (!isInit) {
|
||||
return this.tabsNotInitIds[position];
|
||||
}
|
||||
|
||||
// The tab wasn't initialized but now it is. Move it from one array to the other.
|
||||
const tabId = this.tabsNotInitIds[position];
|
||||
this.tabsNotInit.splice(position, 1);
|
||||
this.tabsNotInitIds.splice(position, 1);
|
||||
|
||||
this._tabs.push(tab);
|
||||
this.tabsIds.push(tabId);
|
||||
|
||||
this.sortTabs();
|
||||
|
||||
return tabId;
|
||||
}
|
||||
|
||||
// Tab is new. In this case isInit should always be false, but check it just in case.
|
||||
const id = this.id + '-' + (++this._ids);
|
||||
|
||||
if (isInit) {
|
||||
this._tabs.push(tab);
|
||||
this.tabsIds.push(id);
|
||||
|
||||
this.sortTabs();
|
||||
} else {
|
||||
this.tabsNotInit.push(tab);
|
||||
this.tabsNotInitIds.push(id);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the tabs.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
initTabs(): Promise<any> {
|
||||
if (!this.initialized && (this._loaded || typeof this._loaded == 'undefined')) {
|
||||
this.initialized = true;
|
||||
|
||||
return super.initTabs().then(() => {
|
||||
// Tabs initialized. Force select the tab if it's not enabled.
|
||||
if (this.selectedDisabled && typeof this.selectedIndex != 'undefined') {
|
||||
const tab = this.getByIndex(this.selectedIndex);
|
||||
|
||||
if (tab && (!tab.enabled || !tab.show)) {
|
||||
this.select(tab);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Tabs not loaded yet. Set the tab bar position so the tab bar is shown, it'll have a spinner.
|
||||
this.setTabbarPosition(-1, 0);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a tab from the list of tabs.
|
||||
*
|
||||
* @param {CoreIonTabComponent} tab The tab to remove.
|
||||
*/
|
||||
remove(tab: CoreIonTabComponent): void {
|
||||
// First search in the list of initialized tabs.
|
||||
let index = this._tabs.indexOf(tab);
|
||||
|
||||
if (index != -1) {
|
||||
this._tabs.splice(index, 1);
|
||||
this.tabsIds.splice(index, 1);
|
||||
} else {
|
||||
// Not found, search in the list of non-init tabs.
|
||||
index = this.tabsNotInit.indexOf(tab);
|
||||
|
||||
if (index != -1) {
|
||||
this.tabsNotInit.splice(index, 1);
|
||||
this.tabsNotInitIds.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the tabs, keeping the same order as in the original list.
|
||||
*/
|
||||
sortTabs(): void {
|
||||
if (this.originalTabsRef) {
|
||||
const newTabs = [],
|
||||
newTabsIds = [],
|
||||
originalTabsEl = this.originalTabsRef.nativeElement;
|
||||
|
||||
this._tabs.forEach((tab, index) => {
|
||||
const originalIndex = Array.prototype.indexOf.call(originalTabsEl.children, tab.getNativeElement());
|
||||
if (originalIndex != -1) {
|
||||
newTabs[originalIndex] = tab;
|
||||
newTabsIds[originalIndex] = this.tabsIds[index];
|
||||
}
|
||||
});
|
||||
|
||||
// Remove undefined values. It can happen if the view has some tabs that were destroyed but weren't removed yet.
|
||||
this._tabs = newTabs.filter((tab) => {
|
||||
return typeof tab != 'undefined';
|
||||
});
|
||||
this.tabsIds = newTabsIds.filter((id) => {
|
||||
return typeof id != 'undefined';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -347,13 +347,13 @@ export class CoreCourseSectionPage implements OnDestroy {
|
|||
* User entered the page.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
this.formatComponent.ionViewDidEnter();
|
||||
this.formatComponent && this.formatComponent.ionViewDidEnter();
|
||||
}
|
||||
|
||||
/**
|
||||
* User left the page.
|
||||
*/
|
||||
ionViewDidLeave(): void {
|
||||
this.formatComponent.ionViewDidLeave();
|
||||
this.formatComponent && this.formatComponent.ionViewDidLeave();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ page-core-login-init {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.spinner circle {
|
||||
.spinner circle, .spinner line {
|
||||
stroke: $core-init-screen-spinner-color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
@ -81,7 +82,8 @@ export class CoreLoginHelperProvider {
|
|||
private wsProvider: CoreWSProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
|
||||
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider,
|
||||
private urlUtils: CoreUrlUtilsProvider, private configProvider: CoreConfigProvider, private platform: Platform,
|
||||
private initDelegate: CoreInitDelegate, private sitePluginsProvider: CoreSitePluginsProvider) {
|
||||
private initDelegate: CoreInitDelegate, private sitePluginsProvider: CoreSitePluginsProvider,
|
||||
private location: Location) {
|
||||
this.logger = logger.getInstance('CoreLoginHelper');
|
||||
}
|
||||
|
||||
|
@ -408,6 +410,10 @@ export class CoreLoginHelperProvider {
|
|||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
goToSiteInitialPage(): Promise<any> {
|
||||
// Due to DeepLinker, we need to remove the path from the URL before going to main menu.
|
||||
// IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
|
||||
this.location.replaceState('');
|
||||
|
||||
return this.appProvider.getRootNavController().setRoot('CoreMainMenuPage');
|
||||
}
|
||||
|
||||
|
@ -597,6 +603,10 @@ export class CoreLoginHelperProvider {
|
|||
* @param {any} params Params to pass to the page.
|
||||
*/
|
||||
protected loadPageInMainMenu(page: string, params: any): void {
|
||||
// Due to DeepLinker, we need to remove the path from the URL before going to main menu.
|
||||
// IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL.
|
||||
this.location.replaceState('');
|
||||
|
||||
this.appProvider.getRootNavController().setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params });
|
||||
}
|
||||
|
||||
|
@ -866,7 +876,15 @@ export class CoreLoginHelperProvider {
|
|||
} else {
|
||||
const info = currentSite.getInfo();
|
||||
if (typeof info != 'undefined' && typeof info.username != 'undefined') {
|
||||
this.appProvider.getRootNavController().setRoot('CoreLoginReconnectPage', {
|
||||
const rootNavCtrl = this.appProvider.getRootNavController(),
|
||||
activePage = rootNavCtrl.getActive();
|
||||
|
||||
// If current page is already reconnect, stop.
|
||||
if (activePage && activePage.component && activePage.component.name == 'CoreLoginReconnectPage') {
|
||||
return;
|
||||
}
|
||||
|
||||
rootNavCtrl.setRoot('CoreLoginReconnectPage', {
|
||||
infoSiteUrl: info.siteurl,
|
||||
siteUrl: result.siteUrl,
|
||||
siteId: siteId,
|
||||
|
@ -914,7 +932,15 @@ export class CoreLoginHelperProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
this.appProvider.getRootNavController().setRoot('CoreLoginSitePolicyPage', { siteId: siteId });
|
||||
const rootNavCtrl = this.appProvider.getRootNavController(),
|
||||
activePage = rootNavCtrl.getActive();
|
||||
|
||||
// If current page is already site policy, stop.
|
||||
if (activePage && activePage.component && activePage.component.name == 'CoreLoginSitePolicyPage') {
|
||||
return;
|
||||
}
|
||||
|
||||
rootNavCtrl.setRoot('CoreLoginSitePolicyPage', { siteId: siteId });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<ion-tabs *ngIf="loaded" #mainTabs [selectedIndex]="initialTab" tabsPlacement="bottom" tabsLayout="title-hide">
|
||||
<ion-tab [enabled]="false" [show]="false" [root]="redirectPage" [rootParams]="redirectParams"></ion-tab>
|
||||
<ion-tab *ngFor="let tab of tabs" [root]="tab.page" [rootParams]="tab.pageParams" [tabTitle]="tab.title | translate" [tabIcon]="tab.icon" [tabBadge]="tab.badge" class="{{tab.class}}"></ion-tab>
|
||||
</ion-tabs>
|
||||
<core-ion-tabs #mainTabs [hidden]="!showTabs" [loaded]="loaded" [selectedIndex]="initialTab" [selectedDisabled]="!!redirectPage" tabsPlacement="bottom" tabsLayout="title-hide">
|
||||
<core-ion-tab [enabled]="false" [show]="false" [root]="redirectPage" [rootParams]="redirectParams"></core-ion-tab>
|
||||
<core-ion-tab *ngFor="let tab of tabs" [root]="tab.page" [rootParams]="tab.pageParams" [tabTitle]="tab.title | translate" [tabIcon]="tab.icon" [tabBadge]="tab.badge" class="{{tab.class}}"></core-ion-tab>
|
||||
<core-ion-tab root="CoreMainMenuMorePage" [tabTitle]="'core.more' | translate" tabIcon="more"></core-ion-tab>
|
||||
</core-ion-tabs>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreMainMenuPage } from './menu';
|
||||
|
||||
@NgModule({
|
||||
|
@ -22,6 +23,7 @@ import { CoreMainMenuPage } from './menu';
|
|||
CoreMainMenuPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
IonicPageModule.forChild(CoreMainMenuPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams, Tabs } from 'ionic-angular';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreMainMenuProvider } from '../../providers/mainmenu';
|
||||
import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/delegate';
|
||||
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../providers/delegate';
|
||||
|
||||
/**
|
||||
* Page that displays the main menu of the app.
|
||||
|
@ -27,41 +27,14 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/d
|
|||
templateUrl: 'menu.html',
|
||||
})
|
||||
export class CoreMainMenuPage implements OnDestroy {
|
||||
// Use a setter to wait for ion-tabs to be loaded because it's inside a ngIf.
|
||||
@ViewChild('mainTabs') set mainTabs(ionTabs: Tabs) {
|
||||
if (ionTabs && this.redirectPage && !this.redirectPageLoaded) {
|
||||
// Tabs ready and there is a redirect page set. Load it.
|
||||
this.redirectPageLoaded = true;
|
||||
|
||||
// Check if the page is the root page of any of the tabs.
|
||||
let indexToSelect = 0;
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].page == this.redirectPage) {
|
||||
indexToSelect = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Use a setTimeout, otherwise loading the first tab opens a new state for some reason.
|
||||
setTimeout(() => {
|
||||
ionTabs.select(indexToSelect);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tabs: CoreMainMenuHandlerData[] = [];
|
||||
loaded: boolean;
|
||||
tabs: CoreMainMenuHandlerToDisplay[] = [];
|
||||
loaded = false;
|
||||
redirectPage: string;
|
||||
redirectParams: any;
|
||||
initialTab: number;
|
||||
showTabs = false;
|
||||
|
||||
protected subscription;
|
||||
protected moreTabData = {
|
||||
page: 'CoreMainMenuMorePage',
|
||||
title: 'core.more',
|
||||
icon: 'more'
|
||||
};
|
||||
protected moreTabAdded = false;
|
||||
protected redirectPageLoaded = false;
|
||||
|
||||
constructor(private menuDelegate: CoreMainMenuDelegate, private sitesProvider: CoreSitesProvider, navParams: NavParams,
|
||||
|
@ -80,42 +53,58 @@ export class CoreMainMenuPage implements OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
this.showTabs = true;
|
||||
|
||||
const site = this.sitesProvider.getCurrentSite(),
|
||||
displaySiteHome = site.getInfo() && site.getInfo().userhomepage === 0;
|
||||
|
||||
this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => {
|
||||
handlers = handlers.slice(0, CoreMainMenuProvider.NUM_MAIN_HANDLERS); // Get main handlers.
|
||||
|
||||
// Check if handlers are already in tabs. Add the ones that aren't.
|
||||
// @todo: https://github.com/ionic-team/ionic/issues/13633
|
||||
// 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 = [];
|
||||
|
||||
for (let i = 0; i < handlers.length; i++) {
|
||||
const handler = handlers[i],
|
||||
shouldSelect = (displaySiteHome && handler.name == 'CoreSiteHome') ||
|
||||
(!displaySiteHome && handler.name == 'CoreCourses');
|
||||
let found = false;
|
||||
const handler = handlers[i];
|
||||
|
||||
for (let j = 0; j < this.tabs.length; j++) {
|
||||
const tab = this.tabs[j];
|
||||
if (tab.title == handler.title && tab.icon == handler.icon) {
|
||||
found = true;
|
||||
if (shouldSelect) {
|
||||
this.initialTab = j;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Check if the handler is already in the tabs list. If so, use it.
|
||||
const tab = this.tabs.find((tab) => {
|
||||
return tab.title == handler.title && tab.icon == handler.icon;
|
||||
});
|
||||
|
||||
if (!found) {
|
||||
this.tabs.push(handler);
|
||||
if (shouldSelect) {
|
||||
this.initialTab = this.tabs.length;
|
||||
}
|
||||
}
|
||||
newTabs.push(tab || handler);
|
||||
}
|
||||
|
||||
if (!this.moreTabAdded) {
|
||||
this.moreTabAdded = true;
|
||||
this.tabs.push(this.moreTabData); // Add "More" tab.
|
||||
this.tabs = newTabs;
|
||||
|
||||
// Sort them by priority so new handlers are in the right position.
|
||||
this.tabs.sort((a, b) => {
|
||||
return b.priority - a.priority;
|
||||
});
|
||||
|
||||
if (typeof this.initialTab == 'undefined' && !this.loaded) {
|
||||
// Calculate the tab to load.
|
||||
if (this.redirectPage) {
|
||||
// Check if the redirect page is the root page of any of the tabs.
|
||||
this.initialTab = 0;
|
||||
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].page == this.redirectPage) {
|
||||
this.initialTab = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// By default, course overview will be loaded (3.3+). Check if we need to select Site Home or My Courses.
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
const handler = handlers[i];
|
||||
if ((displaySiteHome && handler.name == 'CoreSiteHome') ||
|
||||
(!displaySiteHome && handler.name == 'CoreCourses')) {
|
||||
this.initialTab = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.loaded = this.menuDelegate.areHandlersLoaded();
|
||||
|
|
|
@ -99,6 +99,12 @@ export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData {
|
|||
* @type {string}
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* Priority of the handler.
|
||||
* @type {number}
|
||||
*/
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,7 +174,9 @@ export class CoreMainMenuDelegate extends CoreDelegate {
|
|||
|
||||
// Return only the display data.
|
||||
const displayData = handlersData.map((item) => {
|
||||
// Move the name and the priority to the display data.
|
||||
item.data.name = item.name;
|
||||
item.data.priority = item.priority;
|
||||
|
||||
return item.data;
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue