Explorar el Código

componente de animación de carga de datos personalizado basado en eventos

robert2206 hace 8 años
padre
commit
017208767e

+ 28 - 3
src/app/app.component.ts

@@ -1,7 +1,9 @@
 import { Component, ViewChild } from '@angular/core';
 import { Component, ViewChild } from '@angular/core';
-import { Nav, Platform } from 'ionic-angular';
+import { Nav, Platform, IonicApp, Events } from 'ionic-angular';
 import { StatusBar } from 'ionic-native';
 import { StatusBar } from 'ionic-native';
 
 
+import { OLoader } from "../components/oloader/oloader";
+
 import { LoginPage } from '../pages/login/login';
 import { LoginPage } from '../pages/login/login';
 import { HomePage } from '../pages/home/home';
 import { HomePage } from '../pages/home/home';
 
 
@@ -12,7 +14,6 @@ import { LeadListPage } from '../pages/lead-list/lead-list';
 import { OpportunityListPage } from '../pages/opportunity-list/opportunity-list';
 import { OpportunityListPage } from '../pages/opportunity-list/opportunity-list';
 import { CallListPage } from '../pages/call-list/call-list';
 import { CallListPage } from '../pages/call-list/call-list';
 
 
-
 import { ProjectListPage } from '../pages/project-list/project-list';
 import { ProjectListPage } from '../pages/project-list/project-list';
 import { TaskListPage } from '../pages/task-list/task-list';
 import { TaskListPage } from '../pages/task-list/task-list';
 
 
@@ -20,17 +21,23 @@ import { ToolsPage } from '../pages/tools/tools';
 import { SettingsPage } from '../pages/settings/settings';
 import { SettingsPage } from '../pages/settings/settings';
 import { AboutPage } from '../pages/about/about';
 import { AboutPage } from '../pages/about/about';
 
 
+import { Slots } from "../utils/slots";
+
 @Component({
 @Component({
     templateUrl: 'app.html'
     templateUrl: 'app.html'
 })
 })
 export class OdooMobileApp {
 export class OdooMobileApp {
     @ViewChild(Nav) nav: Nav;
     @ViewChild(Nav) nav: Nav;
+    @ViewChild(OLoader) loader: OLoader;
 
 
     rootPage: any = LoginPage;
     rootPage: any = LoginPage;
     homePage: any = HomePage;
     homePage: any = HomePage;
     entries: Array<{ visible: boolean, title: string, pages: Array<{ visible: boolean, title: string, icon: string, component: any }> }>;
     entries: Array<{ visible: boolean, title: string, pages: Array<{ visible: boolean, title: string, icon: string, component: any }> }>;
 
 
-    constructor(public platform: Platform) {
+    constructor(
+        public platform: Platform,
+        public events: Events
+    ) {
         this.initializeApp();
         this.initializeApp();
 
 
         this.entries = [
         this.entries = [
@@ -125,6 +132,24 @@ export class OdooMobileApp {
         this.platform.ready().then(() => {
         this.platform.ready().then(() => {
             // StatusBar.styleDefault();
             // StatusBar.styleDefault();
             StatusBar.backgroundColorByHexString("#364499");
             StatusBar.backgroundColorByHexString("#364499");
+            this.subscribeEvents();
+        });
+    }
+
+    /**
+     *
+     */
+    subscribeEvents(): void {
+        this.events.subscribe(Slots.APP_LOADING, () => {
+            this.loader.show();
+        });
+
+        this.events.subscribe(Slots.APP_LOADED, () => { 
+            this.loader.hide();
+        });
+
+        this.events.subscribe(Slots.APP_ERROR, () => { 
+            
         });
         });
     }
     }
 
 

+ 2 - 0
src/app/app.html

@@ -26,3 +26,5 @@
 </ion-menu>
 </ion-menu>
 
 
 <ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
 <ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
+
+<oloader></oloader>

+ 12 - 3
src/app/app.module.ts

@@ -1,11 +1,12 @@
-import { NgModule } from '@angular/core';
-import { IonicApp, IonicModule } from 'ionic-angular';
+import { NgModule, ErrorHandler } from '@angular/core';
+import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
 import { OdooMobileApp } from './app.component';
 import { OdooMobileApp } from './app.component';
 
 
 import { LoginPage } from '../pages/login/login';
 import { LoginPage } from '../pages/login/login';
 import { HallPage } from '../pages/hall/hall';
 import { HallPage } from '../pages/hall/hall';
 import { HomePage } from '../pages/home/home';
 import { HomePage } from '../pages/home/home';
 
 
+import { OLoader } from "../components/oloader/oloader";
 import { OListHeader } from "../components/olist-header/olist-header";
 import { OListHeader } from "../components/olist-header/olist-header";
 import { OListFooter } from "../components/olist-footer/olist-footer";
 import { OListFooter } from "../components/olist-footer/olist-footer";
 
 
@@ -50,6 +51,7 @@ import { DoubleTapDirective } from "../directives/double-tap-directive";
 @NgModule({
 @NgModule({
     declarations: [
     declarations: [
         OdooMobileApp,
         OdooMobileApp,
+        OLoader,
         // Pages
         // Pages
         LoginPage,
         LoginPage,
         HallPage,
         HallPage,
@@ -88,6 +90,7 @@ import { DoubleTapDirective } from "../directives/double-tap-directive";
     bootstrap: [IonicApp],
     bootstrap: [IonicApp],
     entryComponents: [
     entryComponents: [
         OdooMobileApp,
         OdooMobileApp,
+        OLoader,
         // Pages
         // Pages
         LoginPage,
         LoginPage,
         HallPage,
         HallPage,
@@ -119,7 +122,13 @@ import { DoubleTapDirective } from "../directives/double-tap-directive";
         ProductAttributeValueSyncProvider,
         ProductAttributeValueSyncProvider,
         NetworkProvider,
         NetworkProvider,
         PreferencesProvider,
         PreferencesProvider,
-        ToolsProvider
+        ToolsProvider,
+        [
+            {
+                provide: ErrorHandler,
+                useClass: IonicErrorHandler
+            }
+        ]
     ]
     ]
 })
 })
 export class AppModule {}
 export class AppModule {}

+ 3 - 0
src/components/oloader/oloader.html

@@ -0,0 +1,3 @@
+<ion-backdrop></ion-backdrop>
+<ion-spinner name="{{ type }}"></ion-spinner>
+<p>{{ text }}</p>

+ 35 - 0
src/components/oloader/oloader.scss

@@ -0,0 +1,35 @@
+oloader {
+    z-index: -1;
+    transition: z-index 0.2s step-end;
+
+    width: 100%;
+    height: 100%;
+    position: absolute;
+
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+
+    ion-backdrop {
+        opacity: 0.26;
+    }
+
+    ion-spinner * {
+         width: 32px;
+         height: 32px;
+         stroke: #3f50b4 !important;
+         fill: #3f50b4 !important;
+    }
+
+    p {
+        margin-top: 25px;
+        font-size: 8pt;
+        color: #3f50b4;
+    }
+
+    &.oloader-show {
+        z-index: 10000;
+        transition: z-index 0.2s step-end;
+    }
+}

+ 41 - 0
src/components/oloader/oloader.ts

@@ -0,0 +1,41 @@
+import { Component, Input, ElementRef, Renderer } from "@angular/core";
+
+@Component({
+    selector: "oloader",
+    templateUrl: "oloader.html"
+})
+export class OLoader {
+
+    private static readonly BUBBLES = "bubbles";
+    private static readonly CRESCENT = "crescent";
+    private static readonly CIRCLES = "circles";
+    private static readonly DOTS = "dots";
+
+    @Input()
+    type: string;
+
+    @Input()
+    text: string;
+
+    constructor(
+        public el: ElementRef,
+        public renderer: Renderer
+    ) {
+        this.type = OLoader.BUBBLES;
+        this.text = "Cargando, espere...";
+    }
+
+    /**
+     *
+     */
+    show(): void {
+        this.renderer.setElementClass(this.el.nativeElement, "oloader-show", true);
+    }
+
+    /**
+     *
+     */
+    hide(): void {
+        this.renderer.setElementClass(this.el.nativeElement, "oloader-show", false);
+    }
+}

+ 10 - 7
src/defaults/default-listable.ts

@@ -4,6 +4,7 @@ import filter from 'async/filter';
 
 
 export abstract class DefaultListable<T> implements IListable<T> {
 export abstract class DefaultListable<T> implements IListable<T> {
 
 
+    private type: T;
     private _search: boolean;
     private _search: boolean;
     private _elements: Array<T>;
     private _elements: Array<T>;
     private _findedElements: Array<T>;
     private _findedElements: Array<T>;
@@ -17,6 +18,8 @@ export abstract class DefaultListable<T> implements IListable<T> {
         this._elements = [];
         this._elements = [];
         this._findedElements = [];
         this._findedElements = [];
         this._visibleElements = [];
         this._visibleElements = [];
+        console.log(this.type);
+        
     }
     }
 
 
     /**
     /**
@@ -72,19 +75,19 @@ export abstract class DefaultListable<T> implements IListable<T> {
     /**
     /**
      *
      *
      */
      */
-    add(T: any): void {
-        this.elements.push(T);
+    add(obj: T): void {
+        this.elements.push(obj);
 
 
         if (this.elements.length < this._visibleRange[1]) {
         if (this.elements.length < this._visibleRange[1]) {
-            this._visibleElements.push(T);
+            this._visibleElements.push(obj);
         }    
         }    
     }
     }
 
 
     /**
     /**
      *
      *
      */
      */
-    remove(T: any): void {
-        let index = this.elements.indexOf(T);
+    remove(obj: T): void {
+        let index = this.elements.indexOf(obj);
 
 
         if (this.isSearchMode()) {
         if (this.isSearchMode()) {
             this.findedElements.splice(index, 1);
             this.findedElements.splice(index, 1);
@@ -98,8 +101,8 @@ export abstract class DefaultListable<T> implements IListable<T> {
     /**
     /**
      *
      *
      */
      */
-    get(T: any): T {
-        let index = this.elements.indexOf(T);
+    get(obj: T): T {
+        let index = this.elements.indexOf(obj);
         return this._elements[index];
         return this._elements[index];
     }
     }
 
 

+ 2 - 3
src/directives/double-tap-directive.ts

@@ -30,11 +30,10 @@ export class DoubleTapDirective implements OnInit, OnDestroy {
             }
             }
 
 
             let delay = e.timeStamp - this.lastTimeStamp;
             let delay = e.timeStamp - this.lastTimeStamp;
-            
+            this.lastTimeStamp = 0;
+
             if (delay <= 500) {
             if (delay <= 500) {
                 this.doubleTap.emit(e);
                 this.doubleTap.emit(e);
-            } else {
-                this.lastTimeStamp = 0;
             }
             }
         });
         });
     }   
     }   

+ 6 - 0
src/models/base.model.ts

@@ -1,5 +1,9 @@
+import * as Moment from "moment";
+
 export class BaseModel {
 export class BaseModel {
 
 
+    private static readonly DEFAULT_DATE_FORMAT = "DD/MM/YYYY d:mm:ss"
+
     public id: string;
     public id: string;
     public remote_id: number;
     public remote_id: number;
     public doc_state: string;
     public doc_state: string;
@@ -10,5 +14,7 @@ export class BaseModel {
         this.id = null;
         this.id = null;
         this.remote_id = 0;
         this.remote_id = 0;
         this.doc_state = "created";
         this.doc_state = "created";
+        this.create_date = !this.create_date ? Moment.utc().format(BaseModel.DEFAULT_DATE_FORMAT) : this.create_date;
+        this.write_date = Moment.utc().format(BaseModel.DEFAULT_DATE_FORMAT);
     }
     }
 }
 }

+ 10 - 5
src/pages/budget-details/budget-details.ts

@@ -1,8 +1,10 @@
 import { Component } from '@angular/core';
 import { Component } from '@angular/core';
-import { NavController } from 'ionic-angular';
+import { NavController, Events } from 'ionic-angular';
+import { Slots } from "../../utils/slots";
 import { DataProvider } from "../../providers/data-provider";
 import { DataProvider } from "../../providers/data-provider";
 import { SaleOrder } from "../../models/sale.order";
 import { SaleOrder } from "../../models/sale.order";
 
 
+
 @Component({
 @Component({
     selector: 'page-budget-details',
     selector: 'page-budget-details',
     templateUrl: 'budget-details.html',
     templateUrl: 'budget-details.html',
@@ -12,6 +14,7 @@ export class BudgetDetailsPage {
 
 
     constructor(
     constructor(
         public navCtrl: NavController,
         public navCtrl: NavController,
+        public events: Events,
         public saleOrder: SaleOrder,
         public saleOrder: SaleOrder,
         public db: DataProvider
         public db: DataProvider
     ) { }
     ) { }
@@ -24,10 +27,12 @@ export class BudgetDetailsPage {
     }
     }
 
 
     save(): void {
     save(): void {
-        this.db.save(DataProvider.DOCS.SALE_ORDER, this.saleOrder).then(saleOrder => { 
-            console.log(saleOrder);
+        this.events.publish(Slots.ITEM_SAVED, this.saleOrder);
+        this.navCtrl.pop(this);
+
+        // this.db.save(DataProvider.DOCS.SALE_ORDER, this.saleOrder).then(saleOrder => { 
             
             
-            this.navCtrl.pop(this);
-        }).catch(e => console.log(e));
+        //     this.navCtrl.pop(this);
+        // }).catch(e => console.log(e));
     }
     }
 }
 }

+ 13 - 12
src/pages/budget-list/budget-list.html

@@ -1,19 +1,20 @@
 <olist-header title="Presupuestos" (toggle)="toggleSearch()" (search)="search($event)"></olist-header>
 <olist-header title="Presupuestos" (toggle)="toggleSearch()" (search)="search($event)"></olist-header>
 
 
-<ion-content>
+<ion-content class="has-header">
 
 
-    <ion-card *ngFor="let sale of visibleElements; trackBy:trackByElements">
+    <ion-list [virtualScroll]="visibleElements" approxItemHeight="30px">
+        <ion-card *virtualItem="let item" (doubleTap)="showOptions($event, item)">
+            <ion-item>
+                <h2>{{ item.name }}</h2>
 
 
-        <ion-item>
-            <h2>{{ sale.name }}</h2>
+                <p>
+                    <strong>Fecha:</strong>
+                    {{ item.create_date }}
+                </p>
+            </ion-item>
+        </ion-card>
+    </ion-list>
 
 
-            <button ion-button primary clear item-right (click)="showOptions($event, item)">
-                <ion-icon name="more"></ion-icon>
-            </button>
-        </ion-item>
-
-    </ion-card>
-
-    <olist-footer [hasElements]="hasVisibleElements()" (create)="goToPage(null)" (more)="seek($event)"></olist-footer>
 </ion-content>
 </ion-content>
 
 
+<olist-footer [hasElements]="hasVisibleElements()" (create)="goToPage(null)" (more)="seek($event)"></olist-footer>

+ 52 - 21
src/pages/budget-list/budget-list.ts

@@ -1,11 +1,14 @@
 import { Component } from '@angular/core';
 import { Component } from '@angular/core';
-import { NavController, LoadingController, ToastController } from 'ionic-angular';
+import { NavController, LoadingController, ToastController, ActionSheetController, Events } from 'ionic-angular';
+import { Slots } from "../../utils/slots";
 import { INavigable } from "../../interfaces/navigable-interface";
 import { INavigable } from "../../interfaces/navigable-interface";
 import { DataProvider } from "../../providers/data-provider";
 import { DataProvider } from "../../providers/data-provider";
 import { DefaultListable } from "../../defaults/default-listable";
 import { DefaultListable } from "../../defaults/default-listable";
 import { SaleOrder } from "../../models/sale.order";
 import { SaleOrder } from "../../models/sale.order";
 import { BudgetDetailsPage } from "../budget-details/budget-details";
 import { BudgetDetailsPage } from "../budget-details/budget-details";
 
 
+import { OLoader } from "../../components/oloader/oloder";
+
 @Component({
 @Component({
     selector: 'page-budget-list',
     selector: 'page-budget-list',
     templateUrl: 'budget-list.html'
     templateUrl: 'budget-list.html'
@@ -16,23 +19,36 @@ export class BudgetListPage extends DefaultListable<SaleOrder> implements INavig
     public navCtrl: NavController,
     public navCtrl: NavController,
     public loadingCtrl: LoadingController,
     public loadingCtrl: LoadingController,
     public toastCtrl: ToastController,
     public toastCtrl: ToastController,
+    public actionSheetCtrl: ActionSheetController,
+    public events: Events,
     public db: DataProvider
     public db: DataProvider
-  ) { 
-      super(loadingCtrl);
-  }
-
+    ) { 
+        super(loadingCtrl);
+    }
+    
+    /**
+     *
+     */
     ionViewDidLoad() {
     ionViewDidLoad() {
         this.initialize();
         this.initialize();
+
+        this.events.subscribe(Slots.ITEM_SAVED, data => {
+            this.add(data[0]);
+        });
+    }
+
+    /**
+     *
+     */
+    ionViewDidLeave() {
+        this.events.unsubscribe(Slots.ITEM_SAVED);
     }
     }
 
 
     /**
     /**
     *
     *
     */
     */
     initialize(): void {
     initialize(): void {
-        let loader = this.loadingCtrl.create({
-            content: "Cargando presupuestos, espere..."
-        });
-        loader.present();
+        this.events.publish(Slots.APP_LOADING);
 
 
         this.db.getAll(DataProvider.DOCS.SALE_ORDER).then(orders => {
         this.db.getAll(DataProvider.DOCS.SALE_ORDER).then(orders => {
             this.elements = orders.filter(item => {
             this.elements = orders.filter(item => {
@@ -40,14 +56,11 @@ export class BudgetListPage extends DefaultListable<SaleOrder> implements INavig
                 return true;
                 return true;
             });
             });
 
 
-            console.log(this.elements);
-            
-
-            loader.dismiss();
+            this.events.publish(Slots.APP_LOADED);
         }, e => { 
         }, e => { 
             console.log(e);
             console.log(e);
 
 
-            loader.dismiss();
+            this.events.publish(Slots.APP_LOADED);
 
 
             this.toastCtrl.create({
             this.toastCtrl.create({
                 message: "No se ha podido cargar los presupuestos",
                 message: "No se ha podido cargar los presupuestos",
@@ -66,12 +79,30 @@ export class BudgetListPage extends DefaultListable<SaleOrder> implements INavig
     /**
     /**
      *
      *
      */
      */
-    showOptions(): void {
-        
-    }
-
-    test() {
-        console.log("ok");
-        
+    showOptions(e, item): void {
+        this.actionSheetCtrl.create({
+            title: "Opciones",
+            buttons: [
+                {
+                    text: "Abrir",
+                    icon: "open",
+                    handler: () => console.log("open")
+                    
+                },
+                {
+                    text: "Eliminar",
+                    icon: "trash",
+                    role: "destructive",
+                    handler: () => console.log("Delete")
+                    
+                },
+                {
+                    text: "Cancelar",
+                    role: "cancel",
+                    handler: () => console.log("Cancel")
+                    
+                }
+            ]
+        }).present();
     }
     }
 }
 }

+ 8 - 0
src/theme/odoo.scss

@@ -52,3 +52,11 @@ ion-card ion-row ion-col button ion-icon {
     font-size: 15pt;
     font-size: 15pt;
     color: #d3d3d3;
     color: #d3d3d3;
 }
 }
+
+.has-header {
+    margin-top: 56px;
+}
+
+// .action-sheet-cancel ion-icon, .action-sheet-destructive ion-icon {
+//     color: #757575;
+// }

+ 13 - 0
src/utils/slots.ts

@@ -0,0 +1,13 @@
+export abstract class Slots {
+    // App events
+    public static readonly APP_LOADING = "app:loading";
+    public static readonly APP_LOADED = "app:loaded";
+    public static readonly APP_ERROR = "app:error";
+
+    // List events
+    public static readonly ITEM_CREATE = "item:create";
+    public static readonly ITEM_SAVED = "item:saved";
+    public static readonly ITEM_MODIFIED = "item:modified";
+    public static readonly ITEM_DELETED = "item:deleted";
+    
+}

+ 1 - 0
typings.json

@@ -1,5 +1,6 @@
 {
 {
     "globalDependencies": {
     "globalDependencies": {
+        "moment": "registry:dt/moment#2.11.1+20161010105546",
         "pouchdb": "registry:dt/pouchdb#5.4.4+20160724064500",
         "pouchdb": "registry:dt/pouchdb#5.4.4+20160724064500",
         "pouchdb-adapter-websql": "registry:dt/pouchdb-adapter-websql#5.4.4+20160724064500",
         "pouchdb-adapter-websql": "registry:dt/pouchdb-adapter-websql#5.4.4+20160724064500",
         "pouchdb-core": "registry:dt/pouchdb-core#5.4.4+20160919190957"
         "pouchdb-core": "registry:dt/pouchdb-core#5.4.4+20160919190957"

+ 692 - 0
typings/globals/moment/index.d.ts

@@ -0,0 +1,692 @@
+// Generated by typings
+// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/637e7d6df755e785387d5269cb9287cdc51b8cb7/moment/moment.d.ts
+declare namespace moment {
+
+    type MomentComparable = Moment | string | number | Date | number[];
+
+    interface MomentDateObject {
+        years?: number;
+        /* One digit */
+        months?: number;
+        /* Day of the month */
+        date?: number;
+        hours?: number;
+        minutes?: number;
+        seconds?: number;
+        milliseconds?: number;
+    }
+
+    interface MomentInput {
+        /** Year */
+        years?: number;
+        /** Year */
+        year?: number;
+        /** Year */
+        y?: number;
+
+        /** Month */
+        months?: number;
+        /** Month */
+        month?: number;
+        /** Month */
+        M?: number;
+
+        /** Week */
+        weeks?: number;
+        /** Week */
+        week?: number;
+        /** Week */
+        w?: number;
+
+        /** Day/Date */
+        days?: number;
+        /** Day/Date */
+        day?: number;
+        /** Day/Date */
+        date?: number;
+        /** Day/Date */
+        d?: number;
+
+        /** Hour */
+        hours?: number;
+        /** Hour */
+        hour?: number;
+        /** Hour */
+        h?: number;
+
+        /** Minute */
+        minutes?: number;
+        /** Minute */
+        minute?: number;
+        /** Minute */
+        m?: number;
+
+        /** Second */
+        seconds?: number;
+        /** Second */
+        second?: number;
+        /** Second */
+        s?: number;
+
+        /** Millisecond */
+        milliseconds?: number;
+        /** Millisecond */
+        millisecond?: number;
+        /** Millisecond */
+        ms?: number;
+    }
+
+    interface Duration {
+        humanize(withSuffix?: boolean): string;
+
+        as(units: string): number;
+
+        milliseconds(): number;
+        asMilliseconds(): number;
+
+        seconds(): number;
+        asSeconds(): number;
+
+        minutes(): number;
+        asMinutes(): number;
+
+        hours(): number;
+        asHours(): number;
+
+        days(): number;
+        asDays(): number;
+
+        weeks(): number;
+        asWeeks(): number;
+
+        months(): number;
+        asMonths(): number;
+
+        years(): number;
+        asYears(): number;
+
+        add(n: number, p: string): Duration;
+        add(n: number): Duration;
+        add(d: Duration): Duration;
+
+        subtract(n: number, p: string): Duration;
+        subtract(n: number): Duration;
+        subtract(d: Duration): Duration;
+
+        toISOString(): string;
+        toJSON(): string;
+    }
+
+    interface MomentLocale {
+        ordinal(n: number): string;
+    }
+
+    interface MomentCreationData {
+        input?: string;
+        format?: string;
+        locale: MomentLocale;
+        isUTC: boolean;
+        strict?: boolean;
+    }
+
+    interface Moment {
+        format(format: string): string;
+        format(): string;
+
+        fromNow(withoutSuffix?: boolean): string;
+
+        startOf(unitOfTime: string): Moment;
+        endOf(unitOfTime: string): Moment;
+
+        /**
+         * Mutates the original moment by adding time. (deprecated in 2.8.0)
+         *
+         * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc)
+         * @param amount the amount you want to add
+         */
+        add(unitOfTime: string, amount: number): Moment;
+        /**
+         * Mutates the original moment by adding time.
+         *
+         * @param amount the amount you want to add
+         * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc)
+         */
+        add(amount: number, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by adding time. Note that the order of arguments can be flipped.
+         *
+         * @param amount the amount you want to add
+         * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc)
+         */
+        add(amount: string, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by adding time.
+         *
+         * @param objectLiteral an object literal that describes multiple time units {days:7,months:1}
+         */
+        add(objectLiteral: MomentInput): Moment;
+        /**
+         * Mutates the original moment by adding time.
+         *
+         * @param duration a length of time
+         */
+        add(duration: Duration): Moment;
+
+        /**
+         * Mutates the original moment by subtracting time. (deprecated in 2.8.0)
+         *
+         * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc)
+         * @param amount the amount you want to subtract
+         */
+        subtract(unitOfTime: string, amount: number): Moment;
+        /**
+         * Mutates the original moment by subtracting time.
+         *
+         * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc)
+         * @param amount the amount you want to subtract
+         */
+        subtract(amount: number, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by subtracting time. Note that the order of arguments can be flipped.
+         *
+         * @param amount the amount you want to add
+         * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc)
+         */
+        subtract(amount: string, unitOfTime: string): Moment;
+        /**
+         * Mutates the original moment by subtracting time.
+         *
+         * @param objectLiteral an object literal that describes multiple time units {days:7,months:1}
+         */
+        subtract(objectLiteral: MomentInput): Moment;
+        /**
+         * Mutates the original moment by subtracting time.
+         *
+         * @param duration a length of time
+         */
+        subtract(duration: Duration): Moment;
+
+        calendar(): string;
+        calendar(start: Moment): string;
+        calendar(start: Moment, formats: MomentCalendar): string;
+
+        clone(): Moment;
+
+        /**
+         * @return Unix timestamp, or milliseconds since the epoch.
+         */
+        valueOf(): number;
+
+        local(): Moment; // current date/time in local mode
+
+        utc(): Moment; // current date/time in UTC mode
+
+        isValid(): boolean;
+        invalidAt(): number;
+
+        year(y: number): Moment;
+        year(): number;
+        quarter(): number;
+        quarter(q: number): Moment;
+        month(M: number): Moment;
+        month(M: string): Moment;
+        month(): number;
+        day(d: number): Moment;
+        day(d: string): Moment;
+        day(): number;
+        date(d: number): Moment;
+        date(): number;
+        hour(h: number): Moment;
+        hour(): number;
+        hours(h: number): Moment;
+        hours(): number;
+        minute(m: number): Moment;
+        minute(): number;
+        minutes(m: number): Moment;
+        minutes(): number;
+        second(s: number): Moment;
+        second(): number;
+        seconds(s: number): Moment;
+        seconds(): number;
+        millisecond(ms: number): Moment;
+        millisecond(): number;
+        milliseconds(ms: number): Moment;
+        milliseconds(): number;
+        weekday(): number;
+        weekday(d: number): Moment;
+        isoWeekday(): number;
+        isoWeekday(d: number): Moment;
+        weekYear(): number;
+        weekYear(d: number): Moment;
+        isoWeekYear(): number;
+        isoWeekYear(d: number): Moment;
+        week(): number;
+        week(d: number): Moment;
+        weeks(): number;
+        weeks(d: number): Moment;
+        isoWeek(): number;
+        isoWeek(d: number): Moment;
+        isoWeeks(): number;
+        isoWeeks(d: number): Moment;
+        weeksInYear(): number;
+        isoWeeksInYear(): number;
+        dayOfYear(): number;
+        dayOfYear(d: number): Moment;
+
+        from(f: MomentComparable, suffix?: boolean): string;
+        to(f: MomentComparable, suffix?: boolean): string;
+        toNow(withoutPrefix?: boolean): string;
+
+        diff(b: MomentComparable): number;
+        diff(b: MomentComparable, unitOfTime: string): number;
+        diff(b: MomentComparable, unitOfTime: string, round: boolean): number;
+
+        toArray(): number[];
+        toDate(): Date;
+        toISOString(): string;
+        toJSON(): string;
+        unix(): number;
+
+        isLeapYear(): boolean;
+        zone(): number;
+        zone(b: number): Moment;
+        zone(b: string): Moment;
+        utcOffset(): number;
+        utcOffset(b: number): Moment;
+        utcOffset(b: string): Moment;
+        daysInMonth(): number;
+        isDST(): boolean;
+
+        isBefore(): boolean;
+        isBefore(b: MomentComparable, granularity?: string): boolean;
+
+        isAfter(): boolean;
+        isAfter(b: MomentComparable, granularity?: string): boolean;
+
+        isSame(b: MomentComparable, granularity?: string): boolean;
+        isBetween(a: MomentComparable, b: MomentComparable, granularity?: string, inclusivity?: string): boolean;
+
+        /**
+         * @since 2.10.7+
+         */
+        isSameOrBefore(b: MomentComparable, granularity?: string): boolean;
+        isSameOrAfter(b: MomentComparable, granularity?: string): boolean;
+
+        /**
+         * @deprecated since version 2.8.0
+         */
+        lang(language: string): Moment;
+        lang(reset: boolean): Moment;
+        lang(): MomentLanguage;
+
+        locale(language: string): Moment;
+        locale(reset: boolean): Moment;
+        locale(): string;
+
+        /**
+         * @since 2.12.0+
+         */
+        locales() : string[];
+        localeData(language: string): Moment;
+        localeData(reset: boolean): Moment;
+        localeData(): MomentLanguageData;
+
+        /**
+         * @deprecated since version 2.7.0
+         */
+        max(date: Moment | string | number | Date | any[]): Moment;
+        max(date: string, format: string): Moment;
+
+        /**
+         * @deprecated since version 2.7.0
+         */
+        min(date: Moment | string | number | Date | any[]): Moment;
+        min(date: string, format: string): Moment;
+
+        get(unit: string): number;
+        set(unit: string, value: number): Moment;
+        set(objectLiteral: MomentInput): Moment;
+
+        /**
+         * This returns an object containing year, month, day-of-month, hour, minute, seconds, milliseconds.
+         * @since 2.10.5+
+         */
+        toObject(): MomentDateObject;
+
+        /**
+         * @since 2.10.7+
+         */
+        creationData(): MomentCreationData;
+    }
+
+    type formatFunction = () => string;
+
+    interface MomentCalendar {
+        lastDay?: string | formatFunction;
+        sameDay?: string | formatFunction;
+        nextDay?: string | formatFunction;
+        lastWeek?: string | formatFunction;
+        nextWeek?: string | formatFunction;
+        sameElse?: string | formatFunction;
+    }
+
+    interface BaseMomentLanguage {
+        months?: any;
+        monthsShort?: any;
+        weekdays?: any;
+        weekdaysShort?: any;
+        weekdaysMin?: any;
+        relativeTime?: MomentRelativeTime;
+        meridiem?: (hour: number, minute: number, isLowercase: boolean) => string;
+        calendar?: MomentCalendar;
+        ordinal?: (num: number) => string;
+        week?: MomentLanguageWeek;
+    }
+
+    interface MomentLanguage extends BaseMomentLanguage {
+        longDateFormat?: MomentLongDateFormat;
+    }
+
+    interface MomentLanguageWeek {
+        dow?: number;
+        doy?: number;
+    }
+
+    interface MomentLanguageData {
+        /**
+         * Get the full localized month name of a moment object
+         * @param  {Moment} aMoment a moment object
+         * @return {string}         full month name
+         */
+        months(aMoment: Moment): string;
+
+        /**
+         * Get the short localized month name of a moment object
+         * @param  {Moment} aMoment a moment object
+         * @return {string}         short month name
+         */
+        monthsShort(aMoment: Moment): string;
+
+        /**
+         * Parses a month name and returns the month id (0-11)
+         * @param  {string} longOrShortMonthString string of month to parse
+         * @return {number}                        month id (0 to 11) of input
+         */
+        monthsParse(longOrShortMonthString: string): number;
+
+        /**
+         * Gets the full weekday name of a moment object (eg. Monday)
+         * @param  {Moment} aMoment a moment object
+         * @return {string}         full weekday name
+         */
+        weekdays(aMoment: Moment): string;
+
+        /**
+         * Gets the short weekday name of a moment object (eg. Mon)
+         * @param  {Moment} aMoment a moment object
+         * @return {string}         short weekday name
+         */
+        weekdaysShort(aMoment: Moment): string;
+
+        /**
+         * Gets the min weekday name of a moment object (eg. Mo)
+         * @param  {Moment} aMoment a moment object
+         * @return {string}         min weekday name
+         */
+        weekdaysMin(aMoment: Moment): string;
+
+        /**
+         * Parses a weekday name and returns the weekday id (0-6)
+         * @param  {string} longOrShortMonthString string of weekday to parse
+         * @return {number}                        weekday id (0 to 6) of input
+         */
+        weekdaysParse(longOrShortMonthString: string): number;
+
+        /**
+         * Returns the full format of abbreviated date-time formats
+         * @param  {string} dateFormat date-time format such as LT, L, LL and so on
+         * @return {string}            full date format string
+         */
+        longDateFormat(dateFormat: string): string;
+
+        /**
+         * Returns whether a string represents PM
+         * @param  {string}  amPmString date string to check
+         * @return {boolean}            true if string represents PM
+         */
+        isPM(amPmString: string): boolean;
+
+        /**
+         * Returns am/pm string for particular time-of-day in upper/lower case
+         * @param  {number}  hour        hour
+         * @param  {number}  minute      minute
+         * @param  {boolean} isLowercase whether to return in lowercase
+         * @return {string}              'am' or 'pm'
+         */
+        meridiem(hour: number, minute: number, isLowercase: boolean): string;
+
+        /**
+         * Returns a format that would be used for calendar representation.
+         * @param  {string} key     one of 'sameDay', 'nextDay', 'lastDay', 'nextWeek', 'prevWeek', 'sameElse'
+         * @param  {Moment} aMoment a moment object
+         * @return {string}         date format string
+         */
+        calendar(key: string, aMoment: Moment): string;
+
+        /**
+         * Returns relative time string (eg. a year ago)
+         * @param  {number}  number        the relative number
+         * @param  {boolean} withoutSuffix whether to drop the suffix
+         * @param  {string}  key           one of 's', 'm', 'mm', 'h', 'hh', 'd', 'dd', 'M', 'MM', 'y', 'yy'. Single letter when number is 1.
+         * @param  {boolean} isFuture      whether this represents a future date
+         * @return {string}                humanized representation of relative time
+         */
+        relativeTime(number: number, withoutSuffix: boolean, key: string, isFuture: boolean): string;
+
+        /**
+         * Converts relative time string to past or future string depending on difference
+         * @param  {number} diff    positive or negative number
+         * @param  {string} relTime relative time string
+         * @return {string}         humanized representation of relative time
+         */
+        pastFuture(diff: number, relTime: string): string;
+
+        /**
+         * Convert number to ordinal string 1 -> 1st
+         * @param  {number} number the number
+         * @return {string}        ordinal string
+         */
+        ordinal(number: number): string;
+
+        /**
+         * Called before parsing every input string
+         */
+        preparse(str: string): string;
+
+        /**
+         * Called after formatting on every string
+         */
+        postformat(str: string): string;
+
+        /**
+         * Returns week-of-year of a moment object
+         * @param  {Moment} aMoment a moment object
+         * @return {number}         number of the week
+         */
+        week(aMoment: Moment): number;
+
+        /**
+         * Returns a translation of 'Invalid date'
+         * @return {string} translation of 'Invalid date'
+         */
+        invalidDate(): string;
+
+        /**
+         * Returns the first day of the week (0-6, Sunday to Saturday)
+         * @return {number} first day of the week
+         */
+        firstDayOfWeek(): number;
+
+        /**
+         * This and the first day of week are used to determine which is
+         * the first week of the year. dow == 1 and doy == 4 means week starts
+         * Monday and first week that has Thursday is the first week of the
+         * year (but doy is NOT simply Thursday).
+         * @return {number} number between 0-15
+         */
+        firstDayOfYear(): number;
+    }
+
+    interface MomentLongDateFormat {
+        L: string;
+        LL: string;
+        LLL: string;
+        LLLL: string;
+        LT: string;
+        LTS: string;
+        l?: string;
+        ll?: string;
+        lll?: string;
+        llll?: string;
+        lt?: string;
+        lts?: string;
+    }
+
+    interface MomentRelativeTime {
+        future: any;
+        past: any;
+        s: any;
+        m: any;
+        mm: any;
+        h: any;
+        hh: any;
+        d: any;
+        dd: any;
+        M: any;
+        MM: any;
+        y: any;
+        yy: any;
+    }
+
+    interface MomentBuiltinFormat {
+        __momentBuiltinFormatBrand: any;
+    }
+
+    type MomentFormatSpecification = string | MomentBuiltinFormat | (string | MomentBuiltinFormat)[];
+
+    interface MomentStatic {
+        version: string;
+        fn: Moment;
+
+        (): Moment;
+        (date: number): Moment;
+        (date: number[]): Moment;
+        (date: string, format?: MomentFormatSpecification, strict?: boolean): Moment;
+        (date: string, format?: MomentFormatSpecification, language?: string, strict?: boolean): Moment;
+        (date: Date): Moment;
+        (date: Moment): Moment;
+        (date: Object): Moment;
+
+        utc(): Moment;
+        utc(date: number): Moment;
+        utc(date: number[]): Moment;
+        utc(date: string, format?: string, strict?: boolean): Moment;
+        utc(date: string, format?: string, language?: string, strict?: boolean): Moment;
+        utc(date: string, formats: string[], strict?: boolean): Moment;
+        utc(date: string, formats: string[], language?: string, strict?: boolean): Moment;
+        utc(date: Date): Moment;
+        utc(date: Moment): Moment;
+        utc(date: Object): Moment;
+
+        unix(timestamp: number): Moment;
+
+        invalid(parsingFlags?: Object): Moment;
+        isMoment(): boolean;
+        isMoment(m: any): m is Moment;
+        isDate(m: any): m is Date;
+        isDuration(): boolean;
+        isDuration(d: any): d is Duration;
+
+        /**
+         * @deprecated since version 2.8.0
+         */
+        lang(language?: string): string;
+        lang(language?: string, definition?: MomentLanguage): string;
+
+        locale(language?: string): string;
+        locale(language?: string[]): string;
+        locale(language?: string, definition?: MomentLanguage): string;
+
+        localeData(language?: string): MomentLanguageData;
+
+        longDateFormat: any;
+        relativeTime: any;
+        meridiem: (hour: number, minute: number, isLowercase: boolean) => string;
+        calendar: any;
+        ordinal: (num: number) => string;
+
+        duration(milliseconds: Number): Duration;
+        duration(num: Number, unitOfTime: string): Duration;
+        duration(input: MomentInput): Duration;
+        duration(object: any): Duration;
+        duration(): Duration;
+
+        parseZone(date: string): Moment;
+
+        months(): string[];
+        months(index: number): string;
+        months(format: string): string[];
+        months(format: string, index: number): string;
+        monthsShort(): string[];
+        monthsShort(index: number): string;
+        monthsShort(format: string): string[];
+        monthsShort(format: string, index: number): string;
+
+        weekdays(): string[];
+        weekdays(index: number): string;
+        weekdays(format: string): string[];
+        weekdays(format: string, index: number): string;
+        weekdaysShort(): string[];
+        weekdaysShort(index: number): string;
+        weekdaysShort(format: string): string[];
+        weekdaysShort(format: string, index: number): string;
+        weekdaysMin(): string[];
+        weekdaysMin(index: number): string;
+        weekdaysMin(format: string): string[];
+        weekdaysMin(format: string, index: number): string;
+
+        min(...moments: Moment[]): Moment;
+        min(moments: Moment[]): Moment;
+        max(...moments: Moment[]): Moment;
+        max(moments: Moment[]): Moment;
+
+        normalizeUnits(unit: string): string;
+        relativeTimeThreshold(threshold: string): number | boolean;
+        relativeTimeThreshold(threshold: string, limit: number): boolean;
+
+        /**
+         * @since 2.10.7+
+         */
+        now(): number;
+
+        /**
+         * Constant used to enable explicit ISO_8601 format parsing.
+         */
+        ISO_8601: MomentBuiltinFormat;
+
+        defaultFormat: string;
+    }
+
+}
+
+declare module 'moment' {
+    var moment: moment.MomentStatic;
+    export = moment;
+}
+
+declare module 'moment/moment' {
+    var moment: moment.MomentStatic;
+    export = moment;
+}
+
+declare var moment: moment.MomentStatic;

+ 8 - 0
typings/globals/moment/typings.json

@@ -0,0 +1,8 @@
+{
+  "resolution": "main",
+  "tree": {
+    "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/637e7d6df755e785387d5269cb9287cdc51b8cb7/moment/moment.d.ts",
+    "raw": "registry:dt/moment#2.11.1+20161010105546",
+    "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/637e7d6df755e785387d5269cb9287cdc51b8cb7/moment/moment.d.ts"
+  }
+}

+ 1 - 0
typings/index.d.ts

@@ -1,3 +1,4 @@
+/// <reference path="globals/moment/index.d.ts" />
 /// <reference path="globals/pouchdb-core/index.d.ts" />
 /// <reference path="globals/pouchdb-core/index.d.ts" />
 /// <reference path="globals/pouchdb/index.d.ts" />
 /// <reference path="globals/pouchdb/index.d.ts" />
 /// <reference path="globals/reflect-metadata/index.d.ts" />
 /// <reference path="globals/reflect-metadata/index.d.ts" />