Files
2018-12-19 03:15:19 +01:00

174 lines
5.2 KiB
TypeScript

import { ComponentRef, NgZone } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { RouterDirection } from '@ionic/core';
import { bindLifecycleEvents } from '../../providers/angular-delegate';
import { NavController } from '../../providers/nav-controller';
import { RouteView, computeStackId, destroyView, getUrl, insertView, isTabSwitch, toSegments } from './stack-utils';
export class StackController {
private viewsSnapshot: RouteView[] = [];
private views: RouteView[] = [];
private runningTransition?: Promise<boolean>;
private skipTransition = false;
private tabsPrefix: string[] | undefined;
private activeView: RouteView | undefined;
private nextId = 0;
constructor(
tabsPrefix: string | undefined,
private containerEl: HTMLIonRouterOutletElement,
private router: Router,
private navCtrl: NavController,
private zone: NgZone,
) {
this.tabsPrefix = tabsPrefix !== undefined ? toSegments(tabsPrefix) : undefined;
}
createView(ref: ComponentRef<any>, activatedRoute: ActivatedRoute): RouteView {
const url = getUrl(this.router, activatedRoute);
const element = (ref && ref.location && ref.location.nativeElement) as HTMLElement;
const unlistenEvents = bindLifecycleEvents(ref.instance, element);
return {
id: this.nextId++,
stackId: computeStackId(this.tabsPrefix, url),
unlistenEvents,
element,
ref,
url,
};
}
getExistingView(activatedRoute: ActivatedRoute): RouteView | undefined {
const activatedUrlKey = getUrl(this.router, activatedRoute);
return this.views.find(vw => vw.url === activatedUrlKey);
}
async setActive(enteringView: RouteView) {
let { direction, animation } = this.navCtrl.consumeTransition();
const leavingView = this.activeView;
if (isTabSwitch(enteringView, leavingView)) {
direction = 'back';
animation = undefined;
}
this.insertView(enteringView, direction);
await this.transition(enteringView, leavingView, animation, this.canGoBack(1), false);
this.cleanup();
}
canGoBack(deep: number, stackId = this.getActiveStackId()): boolean {
return this.getStack(stackId).length > deep;
}
pop(deep: number, stackId = this.getActiveStackId()) {
this.zone.run(() => {
const views = this.getStack(stackId);
const view = views[views.length - deep - 1];
this.navCtrl.navigateBack(view.url);
});
}
startBackTransition(stackId = this.getActiveStackId()) {
const views = this.getStack(stackId);
this.transition(
views[views.length - 2], // entering view
views[views.length - 1], // leaving view
'back',
true,
true
);
}
endBackTransition(shouldComplete: boolean) {
if (shouldComplete) {
this.skipTransition = true;
this.pop(1);
}
}
getLastUrl(stackId?: string) {
const views = this.getStack(stackId);
return views.length > 0 ? views[views.length - 1] : undefined;
}
getActiveStackId(): string | undefined {
return this.activeView ? this.activeView.stackId : undefined;
}
destroy() {
this.containerEl = undefined!;
this.views.forEach(destroyView);
this.activeView = undefined;
this.views = [];
}
private getStack(stackId: string | undefined) {
return this.views.filter(v => v.stackId === stackId);
}
private insertView(enteringView: RouteView, direction: RouterDirection) {
this.activeView = enteringView;
this.views = insertView(this.views, enteringView, direction);
}
private cleanup() {
const activeRoute = this.activeView;
const views = this.views;
this.viewsSnapshot
.filter(view => !views.includes(view))
.forEach(view => destroyView(view));
views.forEach(view => {
if (view !== activeRoute) {
const element = view.element;
element.setAttribute('aria-hidden', 'true');
element.classList.add('ion-page-hidden');
}
});
this.viewsSnapshot = views.slice();
}
private async transition(
enteringView: RouteView | undefined,
leavingView: RouteView | undefined,
direction: 'forward' | 'back' | undefined,
showGoBack: boolean,
progressAnimation: boolean
) {
if (this.runningTransition !== undefined) {
await this.runningTransition;
this.runningTransition = undefined;
}
if (this.skipTransition) {
this.skipTransition = false;
return;
}
// TODO
// if (enteringView) {
// enteringView.ref.changeDetectorRef.reattach();
// enteringView.ref.changeDetectorRef.markForCheck();
// }
const enteringEl = enteringView ? enteringView.element : undefined;
const leavingEl = leavingView ? leavingView.element : undefined;
const containerEl = this.containerEl;
if (enteringEl && enteringEl !== leavingEl) {
enteringEl.classList.add('ion-page', 'ion-page-invisible');
if (enteringEl.parentElement !== containerEl) {
containerEl.appendChild(enteringEl);
}
await containerEl.componentOnReady();
this.runningTransition = containerEl.commit(enteringEl, leavingEl, {
deepWait: true,
duration: direction === undefined ? 0 : undefined,
direction,
showGoBack,
progressAnimation
});
await this.runningTransition;
}
}
}