From 0e754ada1621d2941dd21a435b7aa61d320ec9e1 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Tue, 5 May 2015 08:53:59 -0500 Subject: [PATCH] transition updates --- gulpfile.js | 112 ++++----- ionic/animations/ios-transition.js | 31 --- ionic/components/nav/nav-base.js | 216 ++++++++++++++---- .../nav/test/basic/pages/first-page.html | 2 +- .../nav/test/basic/pages/second-page.html | 2 +- .../nav/test/basic/pages/third-page.html | 2 +- ionic/ionic.js | 6 +- ionic/transitions/ios-transition.js | 57 +++++ ionic/transitions/none-transition.js | 34 +++ ionic/transitions/transition.js | 23 ++ ionic/util/util.js | 28 +-- scripts/build/update-angular.sh | 22 -- 12 files changed, 353 insertions(+), 182 deletions(-) delete mode 100644 ionic/animations/ios-transition.js create mode 100644 ionic/transitions/ios-transition.js create mode 100644 ionic/transitions/none-transition.js create mode 100644 ionic/transitions/transition.js delete mode 100755 scripts/build/update-angular.sh diff --git a/gulpfile.js b/gulpfile.js index 8e09486f09..3b55424609 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,37 +1,38 @@ -var _ = require('lodash') -var buildConfig = require('./scripts/build/config') -var SystemJsBuilder = require('systemjs-builder') -var exec = require('child_process').exec -var fs = require('fs') -var gulp = require('gulp') -var karma = require('karma').server -var path = require('path') -var VinylFile = require('vinyl') +var _ = require('lodash'); +var buildConfig = require('./scripts/build/config'); +var SystemJsBuilder = require('systemjs-builder'); +var exec = require('child_process').exec; +var fs = require('fs'); +var gulp = require('gulp'); +var karma = require('karma').server; +var path = require('path'); +var VinylFile = require('vinyl'); -var argv = require('yargs').argv -var babel = require('gulp-babel') +var argv = require('yargs').argv; +var babel = require('gulp-babel'); var cached = require('gulp-cached'); -var concat = require('gulp-concat') -var debug = require('gulp-debug') -var del = require('del') -var gulpif = require('gulp-if') -var karma = require('karma').server -var plumber = require('gulp-plumber') -var rename = require('gulp-rename') -var sass = require('gulp-sass') -var shell = require('gulp-shell') -var through2 = require('through2') -var traceur = require('gulp-traceur') +var concat = require('gulp-concat'); +var debug = require('gulp-debug'); +var del = require('del'); +var gulpif = require('gulp-if'); +var karma = require('karma').server; +var plumber = require('gulp-plumber'); +var rename = require('gulp-rename'); +var sass = require('gulp-sass'); +var shell = require('gulp-shell'); +var through2 = require('through2'); +var traceur = require('gulp-traceur'); -require('./scripts/snapshot/snapshot.task')(gulp, argv, buildConfig) + +require('./scripts/snapshot/snapshot.task')(gulp, argv, buildConfig); gulp.task('default', ['clean'], function() { - gulp.run('build') -}) + gulp.run('build'); +}); -gulp.task('build', ['e2e', 'ionic-js', 'ng2', 'sass']) +gulp.task('build', ['e2e', 'ionic-js', 'ng2', 'sass']); -gulp.task('lib', ['fonts', 'dependencies']) +gulp.task('lib', ['fonts', 'dependencies']); gulp.task('watch', ['default'], function() { gulp.watch(buildConfig.src.scss, ['sass']) @@ -43,14 +44,15 @@ gulp.task('watch', ['default'], function() { buildConfig.src.e2e, buildConfig.src.html, 'scripts/e2e/index.template.html' ), ['ionic-js']) -}) +}); gulp.task('karma', function() { return karma.start({ configFile: __dirname + '/scripts/test/karma.conf.js' }) -}) +}); + gulp.task('karma-watch', function() { return karma.start({ configFile: __dirname + '/scripts/test/karma-watch.conf.js' }) -}) +}); gulp.task('dependencies', function() { var copyFrom = buildConfig.scripts @@ -58,7 +60,7 @@ gulp.task('dependencies', function() { .filter(function(item) { return !!item; }); return gulp.src(copyFrom) .pipe(gulp.dest(buildConfig.distLib)) -}) +}); gulp.task('sass', function() { return gulp.src('ionic/ionic.scss') @@ -68,16 +70,16 @@ gulp.task('sass', function() { } })) .pipe(gulp.dest('dist/css')); -}) +}); gulp.task('fonts', function() { return gulp.src('ionic/components/icon/fonts/**/*') .pipe(gulp.dest('dist/fonts')); -}) +}); gulp.task('clean', function(done) { - del([buildConfig.dist], done) -}) + del([buildConfig.dist], done); +}); gulp.task('e2e', ['ionic-js', 'sass'], function() { var indexContents = _.template( fs.readFileSync('scripts/e2e/index.template.html') )({ @@ -89,7 +91,7 @@ gulp.task('e2e', ['ionic-js', 'sass'], function() { 'android', 'core', 'ios', - ] + ]; // Get each test folder with gulp.src return gulp.src(buildConfig.src.e2e) @@ -163,13 +165,14 @@ gulp.task('e2e', ['ionic-js', 'sass'], function() { }) } -}) +}); gulp.task('ng2-copy', function() { return gulp.src('node_modules/angular2/es6/prod/**/*.es6') .pipe(rename({ extname: '.js' })) .pipe(gulp.dest(path.join(buildConfig.distLib, 'angular2'))); }); + gulp.task('ng2', ['lib', 'ng2-copy'], function() { var builder = new SystemJsBuilder({ paths: { @@ -181,6 +184,7 @@ gulp.task('ng2', ['lib', 'ng2-copy'], function() { return builder.build('angular2/di', path.join(buildConfig.distLib, 'angular2-di.js')); }); }); + gulp.task('ng2-di', ['ng2'], function() { }); @@ -201,36 +205,4 @@ gulp.task('ionic-js', function() { } }); return builder.build('ionic/ionic', path.join(buildConfig.distLib, 'ionic2.js')); -}) - - -// Take es6 files from angular2's output, rename to js, and move to dist/lib/ -// gulp.task('ng2-rename', function(done) { -// exec('test -e node_modules/angular-master', function(err) { -// if (err) { -// console.log('You have not installed angular master.\n' + -// 'Please run ./scripts/build/update-angular.sh.\n' + -// 'Aborting.') -// return process.exit(1) -// } -// gulp.src([ -// 'node_modules/angular-master/dist/js/dev/es6/{angular2,rtts_assert}/**/*.es6' -// ]) -// .pipe(rename({ extname: '.js' })) -// .pipe(gulp.dest(buildConfig.distLib)) -// .on('end', done) -// }) -// }) - -// // We use SystemJsBuilder to build ng2 because it will properly -// gulp.task('ng2', ['ng2-rename'], function() { -// var builder = new SystemJsBuilder() -// builder.config({ -// baseURL: buildConfig.distLib, -// traceurOptions: buildConfig.traceurOptions, -// map: { -// rx: __dirname + '/node_modules/rx' -// } -// }) -// return builder.build('angular2/angular2', buildConfig.distLib + '/angular2.js') -// }) +}); diff --git a/ionic/animations/ios-transition.js b/ionic/animations/ios-transition.js deleted file mode 100644 index 261a292628..0000000000 --- a/ionic/animations/ios-transition.js +++ /dev/null @@ -1,31 +0,0 @@ -import {Animation} from '../collide/animation'; -import {addEasing} from '../collide/easing'; - - -const easing = [.36, .66, .04, 1]; -const duration = 500; - - -class IOSTransition extends Animation { - - constructor(navCtrl) { - - this.duration(duration); - - addEasing('ios', easing); - this.easing('ios'); - - - var enteringViewEle = navCtrl.enteringEle(); - - - var viewA = new Animation(); - viewA.elements( document.querySelectorAll('.square') ) - .to('translateX', translateX) - - this.addChild(row1); - } - - - -} diff --git a/ionic/components/nav/nav-base.js b/ionic/components/nav/nav-base.js index 8bc18c30c1..cc31362a1c 100644 --- a/ionic/components/nav/nav-base.js +++ b/ionic/components/nav/nav-base.js @@ -1,8 +1,19 @@ import {NgElement} from 'angular2/angular2'; import * as util from 'ionic/util'; +import {Transition} from 'ionic/ionic'; + + +const STAGED_STATE = 'staged'; +const STAGED_ENTERING_STATE = 'staged-enter'; +const STAGED_LEAVING_STATE = 'staged-leave'; +const ACTIVELY_ENTERING_STATE = 'entering'; +const ACTIVELY_LEAVING_STATE = 'leaving'; +const ACTIVE_STATE = 'active'; +const CACHED_STATE = 'cached'; + /* - * Used be tabs and nav + * Used by tabs and nav */ export class NavBase { constructor( @@ -23,47 +34,174 @@ export class NavBase { containsClass(Class) { for (let i = 0; i < this._stack.length; i++) { if (this._stack[i].Class === Class) { - return true + return true; } } - return false + return false; } set initial(Class) { if (!this.initialized) { this.initialized = true - this.push(Class, {}, { - animation: 'none' - }); + this.push(Class); } } - /** - * Push a new view into the history stack. - */ - push(Class: Function, params = {}, opts = {}) { - let pushedItem = new NavStackData(Class, params); - - this._stack.push(pushedItem); - this.navItems.push(pushedItem); - - return pushedItem.setup().then(() => { - let current = this._getPrevious(pushedItem); - current && current.leaveReverse(opts); - return pushedItem.enter(opts); - }); + getActiveItem() { + for (let i = 0, ii = this.navItems.length; i < ii; i++) { + if (this.navItems[i].state === ACTIVE_STATE) { + return this.navItems[i]; + } + } + return null; } - /** - * Pop a view off the history - */ - pop(opts = {}) { - let current = this._stack.pop() - let dest = this.last() + getStagedEnteringItem() { + for (let i = 0, ii = this.navItems.length; i < ii; i++) { + if (this.navItems[i].state === STAGED_ENTERING_STATE) { + return this.navItems[i]; + } + } + return null; + } - dest && dest.enterReverse(opts) - return current && current.leave(opts) - .then(() => this._destroy(current)) + getStagedLeavingItem() { + for (let i = 0, ii = this.navItems.length; i < ii; i++) { + if (this.navItems[i].state === STAGED_LEAVING_STATE) { + return this.navItems[i]; + } + } + return null; + } + + getLeavingItems() { + let items = []; + for (let i = 0, ii = this.navItems.length; i < ii; i++) { + if (this.navItems[i].state === ACTIVELY_LEAVING_STATE || this.navItems[i].state === STAGED_LEAVING_STATE) { + items.push(this.navItems[i]); + } + } + return items; + } + + push(Class: Function, params = {}, opts = {}) { + let resolve; + let promise = new Promise(res => { resolve = res; }); + + // default the direction to "forward" + opts.direction = opts.direction || 'forward'; + + // do not animate if this is the first in the stack + if (!this._stack.length) { + opts.animation = 'none'; + } + + // the active item is going to be the leaving one (if one exists) + let leavingItem = this.getActiveItem() || {}; + + // create a new NavStackItem + let enteringItem = new NavStackItem(Class, params); + + // set that this item is staged (it's not ready to be animated in yet) + enteringItem.state = STAGED_STATE; + + // add the item to the stack (just renders in the DOM, doesn't animate yet) + this._stack.push(enteringItem); + this.navItems.push(enteringItem); + + // start the transition + this.transition(enteringItem, leavingItem, opts).then(() => { + console.log('push completed'); + resolve(); + }); + + return promise; + } + + pop(opts = {}) { + let resolve; + let promise = new Promise(res => { resolve = res; }); + + // default the direction to "back" + opts.direction = opts.direction || 'back'; + + // remove the last item + this._stack.pop(); + + // the entering item is now the new last item + let enteringItem = this.last() + + // get the active item and set that it is staged to be leaving + // was probably the one popped from the stack + let leavingItem = this.getActiveItem() || {}; + + // start the transition + this.transition(enteringItem, leavingItem, opts).then(() => { + // transition completed, destroy the leaving item + console.log('pop completed'); + this._destroy(leavingItem); + resolve(); + }); + + return promise; + } + + transition(enteringItem, leavingItem, opts) { + let resolve; + let promise = new Promise(res => { resolve = res; }); + + // wait for the new item to complete setup + enteringItem.setup().then(() => { + + // get any items that are already staged to leave, or are actively leaving + // since a different item will be leaving, reset any actively leaving items to cached + let leavingItems = this.getLeavingItems(); + for (let i = 0; i < leavingItems.length; i++) { + leavingItems[i].state = CACHED_STATE; + } + + // set that the leaving item is stage to be leaving + leavingItem.state = STAGED_LEAVING_STATE; + + // set that the new item pushed on the stack is staged to be entering + // setting staged state is important for the transition logic to find the correct item + enteringItem.state = STAGED_ENTERING_STATE; + + // init the transition animation + let transAnimation = Transition.create(this, opts); + + // wait for the items to be fully staged + transAnimation.stage().then(() => { + + // update the state that the items are actively entering/leaving + enteringItem.state = ACTIVELY_ENTERING_STATE; + leavingItem.state = ACTIVELY_LEAVING_STATE; + + // start the transition + transAnimation.start().then(() => { + + // transition has completed, update each item's state + enteringItem.state = ACTIVE_STATE; + leavingItem.state = CACHED_STATE; + + // resolve that this push has completed + resolve(); + + // on the next frame, hide the item that's cached + util.dom.raf(() => { + // ensure it's state is still cached + if (leavingItem.state === CACHED_STATE && leavingItem.navItem) { + // CSS default is display:none, so setting to '' does the trick + leavingItem.navItem.domElement.style.display = ''; + } + }); + }); + + }); + + }); + + return promise; } last() { @@ -103,23 +241,18 @@ export class NavBase { return this.pop(opts) } - setStack(stack) { - this._stack = stack.slice() - this.navItems = stack.slice() - } - remove(index) { - const item = this._stack[index] - this._stack.splice(index, 1) - this._destroy(item) + const item = this._stack[index]; + this._stack.splice(index, 1); + this._destroy(item); } _destroy(navItem) { - util.array.remove(this.navItems, navItem) + util.array.remove(this.navItems, navItem); } - _getPrevious(item) { - return this._stack[ this._stack.indexOf(item) - 1 ] + getPrevious(item) { + return this._stack[ this._stack.indexOf(item) - 1 ]; } getToolbars(pos: String) { @@ -128,7 +261,8 @@ export class NavBase { } } -class NavStackData { + +class NavStackItem { constructor(ComponentClass, params = {}) { this.Class = ComponentClass; this.params = params; diff --git a/ionic/components/nav/test/basic/pages/first-page.html b/ionic/components/nav/test/basic/pages/first-page.html index c9e367269f..9d321a9636 100644 --- a/ionic/components/nav/test/basic/pages/first-page.html +++ b/ionic/components/nav/test/basic/pages/first-page.html @@ -1,5 +1,5 @@ -

First Page

+

First Page

diff --git a/ionic/components/nav/test/basic/pages/second-page.html b/ionic/components/nav/test/basic/pages/second-page.html index ae6ced4b2b..635944c1da 100644 --- a/ionic/components/nav/test/basic/pages/second-page.html +++ b/ionic/components/nav/test/basic/pages/second-page.html @@ -1,5 +1,5 @@ -

Second Page

+

Second Page

diff --git a/ionic/components/nav/test/basic/pages/third-page.html b/ionic/components/nav/test/basic/pages/third-page.html index 80622f9553..df974a59cf 100644 --- a/ionic/components/nav/test/basic/pages/third-page.html +++ b/ionic/components/nav/test/basic/pages/third-page.html @@ -1,5 +1,5 @@ -

Third Page

+

Third Page

diff --git a/ionic/ionic.js b/ionic/ionic.js index 6af086f261..1c72293c6f 100644 --- a/ionic/ionic.js +++ b/ionic/ionic.js @@ -33,8 +33,12 @@ export * from 'ionic/components' export * from 'ionic/platform/platform' export * from 'ionic/routing/router' export * from 'ionic/util/focus' -export * from 'ionic/collide/animation' export * from 'ionic/engine/engine' export * from 'ionic/engine/cordova/cordova' export * from 'ionic/engine/electron/electron' + +export * from 'ionic/collide/animation' +export * from 'ionic/transitions/transition' +export * from 'ionic/transitions/none-transition' +export * from 'ionic/transitions/ios-transition' diff --git a/ionic/transitions/ios-transition.js b/ionic/transitions/ios-transition.js new file mode 100644 index 0000000000..7f8fb1e623 --- /dev/null +++ b/ionic/transitions/ios-transition.js @@ -0,0 +1,57 @@ +import {Animation} from '../collide/animation'; +import {addEasing} from '../collide/easing'; +import {rafPromise} from '../util/dom' +import {Transition} from './transition' + + +const EASING_FN = [.36, .66, .04, 1]; +const DURATION = 500; + + +class IOSTransition extends Animation { + + constructor(navCtrl, opts) { + super(); + + this.duration(DURATION); + this.easing('ios'); + + // get the entering and leaving items + this.enteringItem = navCtrl.getStagedEnteringItem(); + this.leavingItem = navCtrl.getStagedLeavingItem(); + + // create animation for entering item + let enteringItemAnimation = new Animation(); + enteringItemAnimation.elements(this.enteringItem.navItem.domElement); + + // show the item + this.enteringItem.navItem.domElement.style.display = 'block'; + + if (opts.direction === 'back') { + // back direction + this.enteringItem.navItem.domElement.style.transform = 'translateX(-33%)'; + if (this.leavingItem) { + this.leavingItem.navItem.domElement.style.display = ''; + } + + } else { + // forward direction + this.enteringItem.navItem.domElement.style.transform = 'translateX(100%)'; + + } + + // entering item moves to dead center + enteringItemAnimation.to('translateX', ['0%', '100%']); + + this.addChild(enteringItemAnimation); + } + + stage() { + return rafPromise(); + } + +} + +addEasing('ios', EASING_FN); + +Transition.register('ios', IOSTransition); diff --git a/ionic/transitions/none-transition.js b/ionic/transitions/none-transition.js new file mode 100644 index 0000000000..4c85ed3597 --- /dev/null +++ b/ionic/transitions/none-transition.js @@ -0,0 +1,34 @@ +import {Transition} from './transition' + + +class NoneTransition { + + constructor(navCtrl, opts) { + + // get the entering and leaving items + let enteringItem = navCtrl.getStagedEnteringItem(); + let leavingItem = navCtrl.getStagedLeavingItem(); + + // show the entering item + enteringItem.navItem.domElement.style.display = 'block'; + enteringItem.navItem.domElement.style.transform = 'translateX(0%)'; + + // hide the leaving item + if (leavingItem && leavingItem.navItem) { + leavingItem.navItem.domElement.style.display = ''; + } + } + + stage() { + // immediately resolve + return Promise.resolve(); + } + + start() { + // immediately resolve + return Promise.resolve(); + } + +} + +Transition.register('none', NoneTransition); diff --git a/ionic/transitions/transition.js b/ionic/transitions/transition.js new file mode 100644 index 0000000000..85ed4d4269 --- /dev/null +++ b/ionic/transitions/transition.js @@ -0,0 +1,23 @@ + +let registry = {}; + +class TransitionController { + + create(navCtrl, opts = {}) { + let name = opts.animation || 'ios'; + + let TransitionClass = registry[name]; + if (!TransitionClass) { + TransitionClass = registry['none']; + } + + return new TransitionClass(navCtrl, opts); + } + + register(name, transitionClass) { + registry[name] = transitionClass; + } + +} + +export let Transition = new TransitionController(); diff --git a/ionic/util/util.js b/ionic/util/util.js index d22b990ea8..0338c49db6 100644 --- a/ionic/util/util.js +++ b/ionic/util/util.js @@ -1,4 +1,4 @@ -export function noop() {} +export function noop() {}; export function clamp(min, n, max) { return Math.max(min, Math.min(n, max)); @@ -54,27 +54,27 @@ export const isArray = Array.isArray export function pascalCaseToDashCase(str = '') { return str.charAt(0).toLowerCase() + str.substring(1).replace(/[A-Z]/g, match => { - return '-' + match.toLowerCase() - }) + return '-' + match.toLowerCase(); + }); } -let uid = 0 +let uid = 0; export function nextUid() { return ++uid; } export class Log { static log(...args) { - console.log.apply(console, args) + console.log.apply(console, args); } static info(...args) { - console.info.apply(console, args) + console.info.apply(console, args); } static warn(...args) { - console.warn.apply(console, args) + console.warn.apply(console, args); } static error(...args) { - console.error.apply(console, args) + console.error.apply(console, args); } } @@ -85,12 +85,12 @@ export let array = { } }, remove(arr, item) { - const index = arr.indexOf(item) + const index = arr.indexOf(item); if (index === -1) { - return false + return false; } - arr.splice(index, 1) - return true + arr.splice(index, 1); + return true; } } @@ -101,8 +101,8 @@ export function getQuerystring(key) { const queries = window.location.href.slice(startIndex + 1).split('&'); if (queries.length) { queries.forEach((param) => { - var split = param.split('=') - queryParams[split[0]] = split[1] + var split = param.split('='); + queryParams[split[0]] = split[1]; }); } } diff --git a/scripts/build/update-angular.sh b/scripts/build/update-angular.sh deleted file mode 100755 index 82f0c796be..0000000000 --- a/scripts/build/update-angular.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -NG_FOLDER=node_modules/angular-master - -cd $(dirname $0)/../../ - -$(test -e $NG_FOLDER) || (git clone git@github.com:angular/angular $NG_FOLDER) - -cd $NG_FOLDER -echo "Pulling from master.." -git pull origin master - -echo "Running npm install in angular2..." -npm install - -echo "Running gulp to build source..." -gulp clean -gulp build.js - -echo "--" -echo "-- DONE, gulp will work now --" -echo "--"