From 11385ea7f178bf3a8c53adbca3b5268d376ecbcb Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Mon, 28 Aug 2017 13:25:43 -0500 Subject: [PATCH] wip nav --- .../core/src/components/app/app-constants.ts | 4 + .../src/components/app/app-interfaces.d.ts | 5 + packages/core/src/components/app/app.tsx | 130 +++++++++++++++++- packages/core/src/components/nav/nav.tsx | 17 ++- .../overlay-portal/overlay-portal.tsx | 45 ++++++ packages/core/src/components/util/util.tsx | 15 ++ .../core/src/navigation/nav-interfaces.d.ts | 42 ++++-- packages/core/src/utils/helpers.ts | 2 +- packages/core/stencil.config.js | 2 +- 9 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 packages/core/src/components/app/app-constants.ts create mode 100644 packages/core/src/components/app/app-interfaces.d.ts create mode 100644 packages/core/src/components/overlay-portal/overlay-portal.tsx create mode 100644 packages/core/src/components/util/util.tsx diff --git a/packages/core/src/components/app/app-constants.ts b/packages/core/src/components/app/app-constants.ts new file mode 100644 index 0000000000..362d60658b --- /dev/null +++ b/packages/core/src/components/app/app-constants.ts @@ -0,0 +1,4 @@ +export const PORTAL_DEFAULT = 'general'; +export const PORTAL_LOADING = 'loading'; +export const PORTAL_MODAL = 'modal'; +export const PORTAL_TOAST = 'toast'; diff --git a/packages/core/src/components/app/app-interfaces.d.ts b/packages/core/src/components/app/app-interfaces.d.ts new file mode 100644 index 0000000000..8a19f6587d --- /dev/null +++ b/packages/core/src/components/app/app-interfaces.d.ts @@ -0,0 +1,5 @@ +import { Config } from '../..'; +export interface App { + element?: HTMLElement; + config?: Config; +} \ No newline at end of file diff --git a/packages/core/src/components/app/app.tsx b/packages/core/src/components/app/app.tsx index 327349bd05..895c6b7fa2 100644 --- a/packages/core/src/components/app/app.tsx +++ b/packages/core/src/components/app/app.tsx @@ -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(); +const portals = new Map(); @Component({ tag: 'ion-app', @@ -12,8 +25,119 @@ import { Component } from '@stencil/core'; 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() { - return ; + return ([ + , + , + , + , + , + ]); } } + + +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 { + // 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'); + }); +} + + + + + + diff --git a/packages/core/src/components/nav/nav.tsx b/packages/core/src/components/nav/nav.tsx index d000680ad3..4d8478a780 100644 --- a/packages/core/src/components/nav/nav.tsx +++ b/packages/core/src/components/nav/nav.tsx @@ -34,8 +34,8 @@ export class IonNav implements Nav { init(this); } - ionViewDidLoad() { - ionViewDidLoadImpl(this); + componentDidLoad() { + 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); if (nav.root) { nav.setRoot(nav.root); @@ -205,11 +205,14 @@ export function getNavController(nav: Nav): Promise { return isReady(nav.navController as any as HTMLElement); } -export function navInitializedImpl(nav: Nav, event: CustomEvent) { - if (nav.element !== event.target) { - console.log('nav.id is parent of: ', (event as any).detail.id); +export function navInitializedImpl(potentialParent: Nav, event: CustomEvent) { + if (potentialParent.element !== event.target) { // 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 event.stopPropagation(); } diff --git a/packages/core/src/components/overlay-portal/overlay-portal.tsx b/packages/core/src/components/overlay-portal/overlay-portal.tsx new file mode 100644 index 0000000000..54717e9717 --- /dev/null +++ b/packages/core/src/components/overlay-portal/overlay-portal.tsx @@ -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 ; + } +} + +export function componentWillLoadImpl(overlayPortal: OverlayPortal) { + overlayPortal.registerPortal.emit(overlayPortal); +} \ No newline at end of file diff --git a/packages/core/src/components/util/util.tsx b/packages/core/src/components/util/util.tsx new file mode 100644 index 0000000000..1a155bd7a8 --- /dev/null +++ b/packages/core/src/components/util/util.tsx @@ -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; + } + } + + +} \ No newline at end of file diff --git a/packages/core/src/navigation/nav-interfaces.d.ts b/packages/core/src/navigation/nav-interfaces.d.ts index 1cef2bd32c..ef8d6b57ba 100644 --- a/packages/core/src/navigation/nav-interfaces.d.ts +++ b/packages/core/src/navigation/nav-interfaces.d.ts @@ -12,6 +12,16 @@ export interface FrameworkDelegate { removeViewFromDom(navController: Nav, leavingView: ViewController): Promise; } +export interface NavContainer { + id?: number; + name?: string; + parent?: Nav; + getActiveChildNavs?(): NavContainer[]; + getAllChildNavs?(): NavContainer[]; + getType?(): string; + getSecondaryIdentifier?(): string; +} + export interface Nav { id?: number; element?: HTMLElement; @@ -32,19 +42,24 @@ export interface Nav { mode?: string; // public methods - getActive(): ViewController; - getPrevious(view?: ViewController): ViewController; - getViews(): ViewController[]; - push(component: any, data?: any, opts?: NavOptions): Promise; - pop(opts?: NavOptions): Promise; - setRoot(component: any, data?: any, opts?: NavOptions): Promise; - insert(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise; - insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions): Promise; - popToRoot(opts?: NavOptions): Promise; - popTo(indexOrViewCtrl: any, opts?: NavOptions): Promise; - remove(startIndex: number, removeCount?: number, opts?: NavOptions): Promise; - removeView(viewController: ViewController, opts?: NavOptions): Promise; - setPages(componentDataPairs: ComponentDataPair[], opts? : NavOptions): Promise; + getActive?(): ViewController; + getPrevious?(view?: ViewController): ViewController; + getViews?(): ViewController[]; + push?(component: any, data?: any, opts?: NavOptions): Promise; + pop?(opts?: NavOptions): Promise; + setRoot?(component: any, data?: any, opts?: NavOptions): Promise; + insert?(insertIndex: number, page: any, params?: any, opts?: NavOptions): Promise; + insertPages?(insertIndex: number, insertPages: any[], opts?: NavOptions): Promise; + popToRoot?(opts?: NavOptions): Promise; + popTo?(indexOrViewCtrl: any, opts?: NavOptions): Promise; + remove?(startIndex: number, removeCount?: number, opts?: NavOptions): Promise; + removeView?(viewController: ViewController, opts?: NavOptions): Promise; + setPages?(componentDataPairs: ComponentDataPair[], opts? : NavOptions): Promise; +} + +export interface OverlayPortal extends Nav { + type?: string; + registerPortal?: EventEmitter; } export interface NavController { @@ -63,6 +78,7 @@ export interface NavController { animationCtrl?: AnimationController; } + export interface ViewController { id: string; component: any; diff --git a/packages/core/src/utils/helpers.ts b/packages/core/src/utils/helpers.ts index c8ffc11952..ef3fce1add 100644 --- a/packages/core/src/utils/helpers.ts +++ b/packages/core/src/utils/helpers.ts @@ -160,7 +160,7 @@ export function swipeShouldReset(isResetDirection: boolean, isMovingFast: boolea return (!isMovingFast && isOnResetZone) || (isResetDirection && isMovingFast); } -export function isReady(element: HTMLElement) { +export function isReady(element: Element): Promise { return new Promise((resolve) => { (element as StencilElement).componentOnReady((elm: HTMLElement) => { resolve(elm); diff --git a/packages/core/stencil.config.js b/packages/core/stencil.config.js index b9c63c9378..a5067078b9 100644 --- a/packages/core/stencil.config.js +++ b/packages/core/stencil.config.js @@ -5,7 +5,7 @@ exports.config = { generateCollection: true, bundles: [ { 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-alert', 'ion-alert-controller'] }, { components: ['ion-avatar', 'ion-badge', 'ion-thumbnail'] },