This commit is contained in:
Dan Bucholtz
2017-08-28 13:25:43 -05:00
parent e0a29db3bb
commit 11385ea7f1
9 changed files with 237 additions and 25 deletions

View File

@ -0,0 +1,4 @@
export const PORTAL_DEFAULT = 'general';
export const PORTAL_LOADING = 'loading';
export const PORTAL_MODAL = 'modal';
export const PORTAL_TOAST = 'toast';

View File

@ -0,0 +1,5 @@
import { Config } from '../..';
export interface App {
element?: HTMLElement;
config?: Config;
}

View File

@ -1,5 +1,18 @@
import { Component } from '@stencil/core'; import { Element, Component, Listen, Prop } from '@stencil/core';
import { Nav, NavContainer, OverlayPortal } from '../../navigation/nav-interfaces';
import { Config } from '../..';
import { App } from './app-interfaces';
import { isReady } from '../../utils/helpers';
import {
PORTAL_DEFAULT,
PORTAL_LOADING,
PORTAL_MODAL,
PORTAL_TOAST
} from './app-constants';
const rootNavs = new Map<number, Nav>();
const portals = new Map<string, OverlayPortal>();
@Component({ @Component({
tag: 'ion-app', tag: 'ion-app',
@ -12,8 +25,119 @@ import { Component } from '@stencil/core';
theme: 'app' theme: 'app'
} }
}) })
export class App { export class IonApp implements App {
@Element() element: HTMLElement;
@Prop({ context: 'config' }) config: Config;
@Listen('body:navInit')
registerRootNav(event: CustomEvent) {
rootNavs.set((event.detail as Nav).id, (event.detail as Nav));
}
@Listen('body:registerPortal')
registerPortal(event: CustomEvent) {
portals.set((event.detail as OverlayPortal).type, (event.detail as OverlayPortal));
}
componentWillLoad() {
componentDidLoadImpl(this);
}
getActiveNavs(rootNavId?: number): Nav[] {
const portal = portals.get(PORTAL_MODAL);
if (portal && portal.views && portal.views.length) {
return findTopNavs(portal);
}
if (!rootNavs.size) {
return [];
}
if (rootNavId) {
return findTopNavs(rootNavs.get(rootNavId));
}
if (rootNavs.size === 1) {
return findTopNavs(rootNavs.values().next().value);
}
// fallback to just using all root navs
let activeNavs: Nav[] = [];
rootNavs.forEach(nav => {
activeNavs = activeNavs.concat(findTopNavs(nav));
});
return activeNavs;
}
getNavByIdOrName(nameOrId: number | string) {
const navs = Array.from(rootNavs.values());
for (const navContainer of navs) {
const match = getNavByIdOrNameImpl(navContainer, nameOrId);
if (match) {
return match;
}
}
return null;
}
render() { render() {
return <slot></slot>; return ([
<slot></slot>,
<ion-overlay-portal type={PORTAL_MODAL}></ion-overlay-portal>,
<ion-overlay-portal type={PORTAL_DEFAULT}></ion-overlay-portal>,
<ion-overlay-portal type={PORTAL_LOADING}></ion-overlay-portal>,
<ion-overlay-portal type={PORTAL_TOAST}></ion-overlay-portal>,
]);
} }
} }
export function findTopNavs(nav: NavContainer): NavContainer[] {
let containers: NavContainer[] = [];
const childNavs = nav.getActiveChildNavs();
if (!childNavs || !childNavs.length) {
containers.push(nav);
} else {
childNavs.forEach(childNav => {
const topNavs = findTopNavs(childNav);
containers = containers.concat(topNavs);
});
}
return containers;
}
export function getNavByIdOrNameImpl(nav: NavContainer, id: number | string): NavContainer {
if (nav.id === id || nav.name === id) {
return nav;
}
for (const child of nav.getAllChildNavs()) {
const tmp = getNavByIdOrNameImpl(child, id);
if (tmp) {
return tmp;
}
}
return null;
}
export function componentDidLoadImpl(app: App) {
app.element.classList.add(app.config.get('mode'));
// TODO add platform classes
if (app.config.getBoolean('hoverCSS', true)) {
app.element.classList.add('enable-hover');
}
// TODO fire platform ready
}
export function handleBackButtonClick(): Promise<any> {
// if there is a menu controller dom element, hydrate it, otherwise move on
// TODO ensure ion-menu-controller is the name
const menuControllerElement = document.querySelector('ion-menu-controller'); // TODO - use menu controller types
const promise = menuControllerElement ? isReady(menuControllerElement) : Promise.resolve();
return promise.then(() => {
// TODO check if the menu is open, close it if so
console.log('todo');
});
}

View File

@ -34,8 +34,8 @@ export class IonNav implements Nav {
init(this); init(this);
} }
ionViewDidLoad() { componentDidLoad() {
ionViewDidLoadImpl(this); componentDidLoadImpl(this);
} }
@ -129,7 +129,7 @@ export class IonNav implements Nav {
} }
} }
export function ionViewDidLoadImpl(nav: Nav) { export function componentDidLoadImpl(nav: Nav) {
nav.navInit.emit(nav); nav.navInit.emit(nav);
if (nav.root) { if (nav.root) {
nav.setRoot(nav.root); nav.setRoot(nav.root);
@ -205,11 +205,14 @@ export function getNavController(nav: Nav): Promise<any> {
return isReady(nav.navController as any as HTMLElement); return isReady(nav.navController as any as HTMLElement);
} }
export function navInitializedImpl(nav: Nav, event: CustomEvent) { export function navInitializedImpl(potentialParent: Nav, event: CustomEvent) {
if (nav.element !== event.target) { if (potentialParent.element !== event.target) {
console.log('nav.id is parent of: ', (event as any).detail.id);
// set the parent on the child nav that dispatched the event // set the parent on the child nav that dispatched the event
(event.detail as Nav).parent = nav; (event.detail as Nav).parent = potentialParent;
if (!potentialParent.childNavs) {
potentialParent.childNavs = [];
}
potentialParent.childNavs.push((event.detail as Nav));
// kill the event so it doesn't propagate further // kill the event so it doesn't propagate further
event.stopPropagation(); event.stopPropagation();
} }

View File

@ -0,0 +1,45 @@
import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core';
import { Nav, NavContainer, OverlayPortal } from '../../navigation/nav-interfaces';
@Component({
tag: 'ion-overlay-portal'
})
export class IonOverlayPortal implements NavContainer, OverlayPortal {
id: number;
name: string;
parent: Nav;
@Element() element: HTMLElement;
@Prop() type: string;
@Event() registerPortal: EventEmitter;
getActiveChildNavs(): NavContainer[] {
throw new Error("Method not implemented.");
}
getAllChildNavs?(): NavContainer[] {
throw new Error("Method not implemented.");
}
getType(): string {
return 'portal';
}
getSecondaryIdentifier(): string {
return null;
}
componentWillLoad() {
componentWillLoadImpl(this);
}
render() {
return <slot></slot>;
}
}
export function componentWillLoadImpl(overlayPortal: OverlayPortal) {
overlayPortal.registerPortal.emit(overlayPortal);
}

View File

@ -0,0 +1,15 @@
import { Component } from '@stencil/core';
@Component({
tag: 'ion-utils'
})
export class IonUtils {
setTitle(newTitle: string): void {
if (document.title !== newTitle) {
document.title = newTitle;
}
}
}

View File

@ -12,6 +12,16 @@ export interface FrameworkDelegate {
removeViewFromDom(navController: Nav, leavingView: ViewController): Promise<any>; removeViewFromDom(navController: Nav, leavingView: ViewController): Promise<any>;
} }
export interface NavContainer {
id?: number;
name?: string;
parent?: Nav;
getActiveChildNavs?(): NavContainer[];
getAllChildNavs?(): NavContainer[];
getType?(): string;
getSecondaryIdentifier?(): string;
}
export interface Nav { export interface Nav {
id?: number; id?: number;
element?: HTMLElement; element?: HTMLElement;
@ -32,19 +42,24 @@ export interface Nav {
mode?: string; mode?: string;
// public methods // public methods
getActive(): ViewController; getActive?(): ViewController;
getPrevious(view?: ViewController): ViewController; getPrevious?(view?: ViewController): ViewController;
getViews(): ViewController[]; getViews?(): ViewController[];
push(component: any, data?: any, opts?: NavOptions): Promise<any>; push?(component: any, data?: any, opts?: NavOptions): Promise<any>;
pop(opts?: NavOptions): Promise<any>; pop?(opts?: NavOptions): Promise<any>;
setRoot(component: any, data?: any, opts?: NavOptions): Promise<any>; setRoot?(component: any, data?: any, opts?: NavOptions): Promise<any>;
insert(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise<any>; insert?(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise<any>;
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions): Promise<any>; insertPages?(insertIndex: number, insertPages: any[], opts?: NavOptions): Promise<any>;
popToRoot(opts?: NavOptions): Promise<any>; popToRoot?(opts?: NavOptions): Promise<any>;
popTo(indexOrViewCtrl: any, opts?: NavOptions): Promise<any>; popTo?(indexOrViewCtrl: any, opts?: NavOptions): Promise<any>;
remove(startIndex: number, removeCount?: number, opts?: NavOptions): Promise<any>; remove?(startIndex: number, removeCount?: number, opts?: NavOptions): Promise<any>;
removeView(viewController: ViewController, opts?: NavOptions): Promise<any>; removeView?(viewController: ViewController, opts?: NavOptions): Promise<any>;
setPages(componentDataPairs: ComponentDataPair[], opts? : NavOptions): Promise<any>; setPages?(componentDataPairs: ComponentDataPair[], opts? : NavOptions): Promise<any>;
}
export interface OverlayPortal extends Nav {
type?: string;
registerPortal?: EventEmitter;
} }
export interface NavController { export interface NavController {
@ -63,6 +78,7 @@ export interface NavController {
animationCtrl?: AnimationController; animationCtrl?: AnimationController;
} }
export interface ViewController { export interface ViewController {
id: string; id: string;
component: any; component: any;

View File

@ -160,7 +160,7 @@ export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolea
return (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast); return (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast);
} }
export function isReady(element: HTMLElement) { export function isReady(element: Element): Promise<any> {
return new Promise((resolve) => { return new Promise((resolve) => {
(element as StencilElement).componentOnReady((elm: HTMLElement) => { (element as StencilElement).componentOnReady((elm: HTMLElement) => {
resolve(elm); resolve(elm);

View File

@ -5,7 +5,7 @@ exports.config = {
generateCollection: true, generateCollection: true,
bundles: [ bundles: [
{ components: ['ion-animation-controller'] }, { components: ['ion-animation-controller'] },
{ components: ['ion-app', 'ion-content', 'ion-fixed', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-page', 'ion-title', 'ion-toolbar'] }, { components: ['ion-app', 'ion-content', 'ion-fixed', 'ion-footer', 'ion-header', 'ion-navbar', 'ion-overlay-portal', 'ion-page', 'ion-title', 'ion-toolbar'] },
{ components: ['ion-action-sheet', 'ion-action-sheet-controller'] }, { components: ['ion-action-sheet', 'ion-action-sheet-controller'] },
{ components: ['ion-alert', 'ion-alert-controller'] }, { components: ['ion-alert', 'ion-alert-controller'] },
{ components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] }, { components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] },