<commit_before>/**
 * @author TristanVALCKE / https://github.com/Itee
 */
/* global QUnit */

import { AnimationAction } from '../../../../src/animation/AnimationAction';
import { AnimationMixer } from '../../../../src/animation/AnimationMixer';
import { AnimationClip } from '../../../../src/animation/AnimationClip';
import { NumberKeyframeTrack } from '../../../../src/animation/tracks/NumberKeyframeTrack';
import { Object3D } from '../../../../src/core/Object3D';
import { LoopOnce, LoopRepeat, LoopPingPong } from '../../../../src/constants';

function createAnimation(){

	var mixer = new AnimationMixer(new Object3D());
	var track = new NumberKeyframeTrack( ".rotation[x]", [ 0, 1000 ], [ 0, 360 ] );
	var clip = new AnimationClip( "clip1", 1000, [track] );

	var animationAction = new AnimationAction( mixer, clip );
	return { mixer :mixer,track:track,clip:clip,animationAction:animationAction};

}

export default QUnit.module( 'Animation', () => {

	QUnit.module( 'AnimationAction', () => {

		// INSTANCING
		QUnit.test( "Instancing", ( assert ) => {

			var mixer = new AnimationMixer();
			var clip = new AnimationClip( "nonname", - 1, [] );

			var animationAction = new AnimationAction( mixer, clip );
			assert.ok( animationAction, "animationAction instanciated" );

		} );

		// PUBLIC STUFF
		QUnit.test( "play", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			var animationAction2 = animationAction.play();
			assert.equal( animationAction, animationAction2, "AnimationAction.play can be chained." );

			var UserException = function () {

				this.message = "AnimationMixer must activate AnimationAction on play.";

			};
			mixer._activateAction = function ( action ) {

				if ( action === animationAction ) {

					throw new UserException();

				}

			};
			assert.throws( () => {

				animationAction.play();

			}, new UserException() );

		} );

		QUnit.test( "stop", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			var animationAction2 = animationAction.stop();
			assert.equal( animationAction, animationAction2, "AnimationAction.stop can be chained." );

			var UserException = function () {

				this.message = "AnimationMixer must deactivate AnimationAction on stop.";

			};
			mixer._deactivateAction = function ( action ) {

				if ( action === animationAction ) {

					throw new UserException();

				}

			};
			assert.throws( () => {

				animationAction.stop();

			}, new UserException() );

		} );

		QUnit.test( "reset", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			var animationAction2 = animationAction.stop();
			assert.equal( animationAction, animationAction2, "AnimationAction.reset can be chained." );
			assert.equal( animationAction2.paused, false, "AnimationAction.reset() sets paused false" );
			assert.equal( animationAction2.enabled, true, "AnimationAction.reset() sets enabled true" );
			assert.equal( animationAction2.time, 0, "AnimationAction.reset() resets time." );
			assert.equal( animationAction2._loopCount, - 1, "AnimationAction.reset() resets loopcount." );
			assert.equal( animationAction2._startTime, null, "AnimationAction.reset() removes starttime." );

		} );

		QUnit.test( "isRunning", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			assert.notOk( animationAction.isRunning(), "When an animation is just made, it is not running." );
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			animationAction.stop();
			assert.notOk( animationAction.isRunning(), "When an animation is stopped, it is not running." );
			animationAction.play();
			animationAction.paused = true;
			assert.notOk( animationAction.isRunning(), "When an animation is paused, it is not running." );
			animationAction.paused = false;
			animationAction.enabled = false;
			assert.notOk( animationAction.isRunning(), "When an animation is not enabled, it is not running." );
			animationAction.enabled = true;
			assert.ok( animationAction.isRunning(), "When an animation is enabled, it is running." );

		} );

		QUnit.test( "isScheduled", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			assert.notOk( animationAction.isScheduled(), "When an animation is just made, it is not scheduled." );
			animationAction.play();
			assert.ok( animationAction.isScheduled(), "When an animation is started, it is scheduled." );
			mixer.update(1);
			assert.ok( animationAction.isScheduled(), "When an animation is updated, it is scheduled." );
			animationAction.stop();
			assert.notOk( animationAction.isScheduled(), "When an animation is stopped, it isn't scheduled anymore." );

		} );

		QUnit.test( "startAt", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.startAt(2);
			animationAction.play();
			assert.notOk( animationAction.isRunning(), "When an animation is started at a specific time, it is not running." );
			assert.ok( animationAction.isScheduled(), "When an animation is started at a specific time, it is scheduled." );
			mixer.update(1);
			assert.notOk( animationAction.isRunning(), "When an animation is started at a specific time and the interval is not passed, it is not running." );
			assert.ok( animationAction.isScheduled(), "When an animation is started at a specific time and the interval is not passed, it is scheduled." );
			mixer.update(1);
			assert.ok( animationAction.isRunning(), "When an animation is started at a specific time and the interval is passed, it is running." );
			assert.ok( animationAction.isScheduled(), "When an animation is started at a specific time and the interval is passed, it is scheduled." );
			animationAction.stop();
			assert.notOk( animationAction.isRunning(), "When an animation is stopped, it is not running." );
			assert.notOk( animationAction.isScheduled(), "When an animation is stopped, it is not scheduled." );

		} );

		QUnit.test( "setLoop LoopOnce", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.setLoop(LoopOnce);
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			mixer.update(500);
			assert.ok( animationAction.isRunning(), "When an animation is in the first loop, it is running." );
			mixer.update(500);
			assert.notOk( animationAction.isRunning(), "When an animation is ended, it is not running." );
			mixer.update(500);
			assert.notOk( animationAction.isRunning(), "When an animation is ended, it is not running." );

		} );

		QUnit.test( "setLoop LoopRepeat", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.setLoop(LoopRepeat,3);
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			mixer.update(500);
			assert.ok( animationAction.isRunning(), "When an animation is in the first loop, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in second loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in third loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.notOk( animationAction.isRunning(), "When an animation ended his third loop when in looprepeat 3 times, it is not running anymore." );
			mixer.update(1000);
			assert.notOk( animationAction.isRunning(), "When an animation ended his third loop when in looprepeat 3 times, it stays not running anymore." );

		} );

		QUnit.todo( "setEffectiveWeight", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getEffectiveWeight", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "fadeIn", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "fadeOut", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "crossFadeFrom", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "crossFadeTo", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "stopFading", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "setEffectiveTimeScale", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getEffectiveTimeScale", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "setDuration", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "syncWith", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "halt", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "warp", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "stopWarping", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getMixer", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getClip", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getRoot", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

	} );

} );
<commit_msg>Add unit test for clipTime in AnimationAtion<commit_after>/**
 * @author TristanVALCKE / https://github.com/Itee
 */
/* global QUnit */

import { AnimationAction } from '../../../../src/animation/AnimationAction';
import { AnimationMixer } from '../../../../src/animation/AnimationMixer';
import { AnimationClip } from '../../../../src/animation/AnimationClip';
import { NumberKeyframeTrack } from '../../../../src/animation/tracks/NumberKeyframeTrack';
import { Object3D } from '../../../../src/core/Object3D';
import { LoopOnce, LoopRepeat, LoopPingPong } from '../../../../src/constants';

function createAnimation(){

	var mixer = new AnimationMixer(new Object3D());
	var track = new NumberKeyframeTrack( ".rotation[x]", [ 0, 1000 ], [ 0, 360 ] );
	var clip = new AnimationClip( "clip1", 1000, [track] );

	var animationAction = new AnimationAction( mixer, clip );
	return { mixer :mixer,track:track,clip:clip,animationAction:animationAction};

}

export default QUnit.module( 'Animation', () => {

	QUnit.module( 'AnimationAction', () => {

		// INSTANCING
		QUnit.test( "Instancing", ( assert ) => {

			var mixer = new AnimationMixer();
			var clip = new AnimationClip( "nonname", - 1, [] );

			var animationAction = new AnimationAction( mixer, clip );
			assert.ok( animationAction, "animationAction instanciated" );

		} );

		// PUBLIC STUFF
		QUnit.test( "play", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			var animationAction2 = animationAction.play();
			assert.equal( animationAction, animationAction2, "AnimationAction.play can be chained." );

			var UserException = function () {

				this.message = "AnimationMixer must activate AnimationAction on play.";

			};
			mixer._activateAction = function ( action ) {

				if ( action === animationAction ) {

					throw new UserException();

				}

			};
			assert.throws( () => {

				animationAction.play();

			}, new UserException() );

		} );

		QUnit.test( "stop", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			var animationAction2 = animationAction.stop();
			assert.equal( animationAction, animationAction2, "AnimationAction.stop can be chained." );

			var UserException = function () {

				this.message = "AnimationMixer must deactivate AnimationAction on stop.";

			};
			mixer._deactivateAction = function ( action ) {

				if ( action === animationAction ) {

					throw new UserException();

				}

			};
			assert.throws( () => {

				animationAction.stop();

			}, new UserException() );

		} );

		QUnit.test( "reset", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			var animationAction2 = animationAction.stop();
			assert.equal( animationAction, animationAction2, "AnimationAction.reset can be chained." );
			assert.equal( animationAction2.paused, false, "AnimationAction.reset() sets paused false" );
			assert.equal( animationAction2.enabled, true, "AnimationAction.reset() sets enabled true" );
			assert.equal( animationAction2.time, 0, "AnimationAction.reset() resets time." );
			assert.equal( animationAction2._loopCount, - 1, "AnimationAction.reset() resets loopcount." );
			assert.equal( animationAction2._startTime, null, "AnimationAction.reset() removes starttime." );

		} );

		QUnit.test( "isRunning", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			assert.notOk( animationAction.isRunning(), "When an animation is just made, it is not running." );
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			animationAction.stop();
			assert.notOk( animationAction.isRunning(), "When an animation is stopped, it is not running." );
			animationAction.play();
			animationAction.paused = true;
			assert.notOk( animationAction.isRunning(), "When an animation is paused, it is not running." );
			animationAction.paused = false;
			animationAction.enabled = false;
			assert.notOk( animationAction.isRunning(), "When an animation is not enabled, it is not running." );
			animationAction.enabled = true;
			assert.ok( animationAction.isRunning(), "When an animation is enabled, it is running." );

		} );

		QUnit.test( "isScheduled", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			assert.notOk( animationAction.isScheduled(), "When an animation is just made, it is not scheduled." );
			animationAction.play();
			assert.ok( animationAction.isScheduled(), "When an animation is started, it is scheduled." );
			mixer.update(1);
			assert.ok( animationAction.isScheduled(), "When an animation is updated, it is scheduled." );
			animationAction.stop();
			assert.notOk( animationAction.isScheduled(), "When an animation is stopped, it isn't scheduled anymore." );

		} );

		QUnit.test( "startAt", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.startAt(2);
			animationAction.play();
			assert.notOk( animationAction.isRunning(), "When an animation is started at a specific time, it is not running." );
			assert.ok( animationAction.isScheduled(), "When an animation is started at a specific time, it is scheduled." );
			mixer.update(1);
			assert.notOk( animationAction.isRunning(), "When an animation is started at a specific time and the interval is not passed, it is not running." );
			assert.ok( animationAction.isScheduled(), "When an animation is started at a specific time and the interval is not passed, it is scheduled." );
			mixer.update(1);
			assert.ok( animationAction.isRunning(), "When an animation is started at a specific time and the interval is passed, it is running." );
			assert.ok( animationAction.isScheduled(), "When an animation is started at a specific time and the interval is passed, it is scheduled." );
			animationAction.stop();
			assert.notOk( animationAction.isRunning(), "When an animation is stopped, it is not running." );
			assert.notOk( animationAction.isScheduled(), "When an animation is stopped, it is not scheduled." );

		} );

		QUnit.test( "setLoop LoopOnce", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.setLoop(LoopOnce);
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			mixer.update(500);
			assert.ok( animationAction.isRunning(), "When an animation is in the first loop, it is running." );
			mixer.update(500);
			assert.notOk( animationAction.isRunning(), "When an animation is ended, it is not running." );
			mixer.update(500);
			assert.notOk( animationAction.isRunning(), "When an animation is ended, it is not running." );

		} );

		QUnit.test( "setLoop LoopRepeat", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.setLoop(LoopRepeat,3);
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			mixer.update(500);
			assert.ok( animationAction.isRunning(), "When an animation is in the first loop, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in second loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in third loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in fourth loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.notOk( animationAction.isRunning(), "When an animation ended his third loop when in looprepeat 3 times, it stays not running anymore." );

		} );

		QUnit.test( "setLoop LoopPingPong", ( assert ) => {

			var {mixer,animationAction} = createAnimation();
			animationAction.setLoop(LoopPingPong,3);
			animationAction.play();
			assert.ok( animationAction.isRunning(), "When an animation is started, it is running." );
			mixer.update(500);
			assert.ok( animationAction.isRunning(), "When an animation is in the first loop, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in second loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in third loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.ok( animationAction.isRunning(), "When an animation is in fourth loop when in looprepeat 3 times, it is running." );
			mixer.update(1000);
			assert.notOk( animationAction.isRunning(), "When an animation ended his third loop when in looprepeat 3 times, it stays not running anymore." );

		} );

		QUnit.todo( "setEffectiveWeight", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getEffectiveWeight", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "fadeIn", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "fadeOut", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "crossFadeFrom", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "crossFadeTo", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "stopFading", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "setEffectiveTimeScale", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getEffectiveTimeScale", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "setDuration", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "syncWith", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "halt", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "warp", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "stopWarping", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getMixer", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getClip", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

		QUnit.todo( "getRoot", ( assert ) => {

			assert.ok( false, "everything's gonna be alright" );

		} );

	} );

} );
<|endoftext|>


<commit_before>/**
 * @flow
 */
import Immutable from 'immutable';

import PromisePlaceholder from './utils/PromisePlaceholder';
import resolveMapOfPromises from './utils/resolveMapOfPromises';
import mapObject from './utils/mapObject';

import type Store from './Store';

type StoresMap = Immutable.Map<string, Store<any>>;
type OnUpdateFunc = (updateStore: StoresMap) => void;
type OnErrorFunc = (err: Error) => void;
type DispatchFunctionSettings = { initialStores: StoresMap, onUpdatedStores: OnUpdateFunc, onError: OnErrorFunc };
type FinishOnServerFunc = (
	startingPoints: {[key: string]: StartingPoint},
	actions: Array<Action>
) => Promise<{[key: string]: any}>;
type ClientDispatchResults = {
	updatedStoresPromise: Promise<StoresMap>,
	pausePoints: Immutable.Map<string, StartingPoint>,
	responsePlaceholders: Immutable.Map<string, PromisePlaceholder>
};

/**
 * A class to handle calling 'dispatch' with different actions concurrently.
 */
export default class DispatchHandler {

	_stores: StoresMap;
	_actionQueue: ?Immutable.List<{ action: Action, promisePlaceholder: PromisePlaceholder}>;
	_updateFunctions: Immutable.List<OnUpdateFunc>;
	_errorFunctions: Immutable.List<OnErrorFunc>;

	/**
	 * The constructor for the DispatchHandler.
	 *
	 * @param initialStores		{StoresMap}			The stores to start with
	 */
	constructor(initialStores: StoresMap) {
		this._stores = initialStores;
		this._actionQueue = null;

		this._updateFunctions = Immutable.List();
		this._errorFunctions = Immutable.List();
	}

	/**
	 * The create a DispatchHandler with the given initial stores.
	 *
	 * @param initialStores		{StoresMap}			The stores to start with
	 *
	 * @return					{DispatchHandler}	The new DispatchHandler
	 */
	static createDispatchHandler(initialStores: StoresMap): DispatchHandler {
		return new DispatchHandler(initialStores);
	}

	/**
	 * Add a new action to the queue of actions to be dispatched to the stores.
	 *
	 * @param action	{Action}				The action to be dispatched
	 *
	 * @return			{Promise<StoresMap>}	A promise that contains the stores after the give action was dispatched
	 */
	pushAction(action: Action): Promise<StoresMap> {
		// Enqueue action if dispatch is already being performed
		if(this._actionQueue) {
			const promisePlaceholder = new PromisePlaceholder();
			this._actionQueue = this._actionQueue.push({ action, promisePlaceholder });

			return promisePlaceholder.getPromise();
		}

		// Start dispatch
		this._actionQueue = Immutable.List();
		return this._startDispatch(this._stores, action);
	}

	/**
	 * Add a new actions to the queue of actions to be dispatched to the stores.
	 *
	 * @param actions	{Array<Action>}			The actions to be dispatched
	 *
	 * @return			{Promise<StoresMap>}	A promise that contains the stores after all the given action have
	 *											finished dispatching
	 */
	pushActions(actions: Array<Action>): Promise<StoresMap> {
		// Add each action to the queue
		const storeMapPromises = actions.map((action) => this.pushAction(action));

		// The returned promise should resolve when the last action is finished
		return storeMapPromises[storeMapPromises.length - 1];
	}

	/**
	 * Get the current stores.
	 *
	 * @return 		{StoreMap}	The current stores
	 */
	getStores(): StoresMap {
		return this._stores;
	}

	/**
	 * Add a callback for the given event.
	 *
	 * @param event	{string}	The event to subscribe to
	 * @param cb	{Function}	The function to call when the given event happens
	 */
	on(event: string, cb: Function) {
		switch(event) {
			case 'update':
				this._updateFunctions = this._updateFunctions.push(cb);
				break;
			case 'error':
				this._errorFunctions = this._errorFunctions.push(cb);
				break;
			default:
				throw new Error(`Invalid event type for DispatchHandler: ${event}`);
		}
	}

	_onUpdatedStores(updatedStores: StoresMap) {
		// Save updated stores
		this._stores = this._stores.merge(updatedStores);

		// Send to event subscribers
		this._updateFunctions.forEach((updateFunc) => {
			updateFunc(updatedStores);
		});
	}

	_onError(err: Error) {
		this._errorFunctions.forEach((errorFunc) => {
			errorFunc(err);
		});
	}

	_startDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		// Dispatch the action to each store
		let updatedStoresPromises = this._performDispatch(stores, action);

		// Report Results
		updatedStoresPromises = updatedStoresPromises.then((updatedStores) => {
			this._onUpdatedStores(updatedStores);

			return updatedStores;
		}).catch((err) => {
			this._onError(err);

			return stores;
		})

		// Return the stores after dispatch finishes
		return updatedStoresPromises.then((currentStores) => {
			if(!this._actionQueue || this._actionQueue.size === 0) {
				// Pause, to wait for next action
				this._actionQueue = null;

				return currentStores;
			}

			// Start performing the next dispatch
			const { action: nextAction, promisePlaceholder } = this._dequeueNextAction();
			this._startDispatch(currentStores, nextAction).then((nextStores) => {
				// Resolve promise that was returned from 'pushAction'
				promisePlaceholder.resolve(nextStores);
			});

			return currentStores;
		}).catch((err) => {	//NOTE, this shouldn't be need (just in case)
			this._onError(err);

			throw err;
		});
	}

	_performDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		const updatedStoresPromises = stores.map((store) => store.dispatch(action));

		return resolveMapOfPromises(updatedStoresPromises);
	}

	_dequeueNextAction(): { action: Action, promisePlaceholder: PromisePlaceholder } {
		if(!this._actionQueue) throw new Error('cannot dequeue if the queue, _actionQueue, is null');
		const actionQueue = this._actionQueue;

		const nextActionObject = actionQueue.first();
		this._actionQueue = actionQueue.shift();

		return nextActionObject;
	}
}

export class ServerDispatchHandler extends DispatchHandler {
	_onServerArg: any;

	/**
	 * The constructor for the ServerDispatchHandler.
	 *
	 * @param initialStores	{StoresMap}	The stores to start with
	 * @param onServerArg	{any}		The arg to pass to the 'onServer' argument of the updaters
	 */
	constructor(initialStores: StoresMap, onServerArg: any) {
		super(initialStores);

		this._onServerArg = onServerArg;
	}

	/**
	 * The create a ServerDispatchHandler with the given initial stores and passes the given arg to the 'onServer' arg
	 * of the updaters (3rd argument).
	 *
	 * @param initialStores	{StoresMap}			The stores to start with
	 * @param onServerArg	{any}				The arg to pass to the 'onServer' argument of the updaters
	 *
	 * @return				{DispatchHandler}	The new ServerDispatchHandler
	 */
	static createServerDispatchHandler(initialStores: StoresMap, onServerArg: any): DispatchHandler {
		return new ServerDispatchHandler(initialStores, onServerArg);
	}

	_performDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		const updatedStoresPromises = stores.map((store) => {
			return store.dispatch(action, { arg: this._onServerArg })
		});

		return resolveMapOfPromises(updatedStoresPromises);
	}
}

export class ClientDispatchHandler extends DispatchHandler {
	_finishOnServer: FinishOnServerFunc;

	/**
	 * The constructor for the ClientDispatchHandler.
	 *
	 * @param initialStores		{StoresMap}				The stores to start with
	 * @param finishOnServer	{FinishOnServerFunc}	A function that finishes a dispatching an action on the server
	 */
	constructor(initialStores: StoresMap, finishOnServer: FinishOnServerFunc) {
		super(initialStores);

		this._finishOnServer = finishOnServer;
	}

	/**
	 * The create a ServerDispatchHandler with the given initial stores and the function to finish the a dispatch in
	 * the ServerDispatcher.
	 *
	 * @param initialStores		{StoresMap}				The stores to start with
	 * @param finishOnServer	{FinishOnServerFunc}	A function that finishes a dispatching an action on the server
	 *
	 * @return					{DispatchHandler}		The new ServerDispatchHandler
	 */
	static createClientDispatchHandler(initialStores: StoresMap, finishOnServer: FinishOnServerFunc): DispatchHandler {
		return new ClientDispatchHandler(initialStores, finishOnServer);
	}

	_performDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		// Preform client side dispatch
		const clientDispatchPromise = this._performClientDispatch(stores, action);

		return clientDispatchPromise.then(({ updatedStoresPromise, pausePoints, responsePlaceholders}) => {
			// Finish on server, if any 'onServer' was called
			if(responsePlaceholders.size > 0) {
				const actionQueue = this._actionQueue? this._actionQueue: Immutable.List();
				this._actionQueue = null;

				const queuedActions = actionQueue.map(({ action }) => action).toArray();
				let queuedPromisePlaceholders = actionQueue.map(({ promisePlaceholder}) => promisePlaceholder);
				let responsePlaceholdersLeft = responsePlaceholders;

				// Send to server
				this._finishOnServer(pausePoints, [action, ...queuedActions]).then((responseStates) => {
					for(let storeName in responseStates) {
						const responseState = responseStates[storeName];

						// Return result for paused dispatches
						if(responsePlaceholdersLeft.has(storeName)) {
							const responsePlaceholder = responsePlaceholdersLeft.get(storeName);
							responsePlaceholdersLeft = responsePlaceholdersLeft.delete(storeName);

							//Give result to Stores
							responsePlaceholder.resolve(responseState);
						}
					}

					// Return result for queued actions
					if(queuedPromisePlaceholders.size > 0) {
						queuedPromisePlaceholders.forEach((queuedPromisePlaceholder) => {
							//Give result to dispatch caller
							queuedPromisePlaceholder.resolve(responseStates);
						});

						queuedPromisePlaceholders = queuedPromisePlaceholders.clear();
					}

					// Send errors if any stores are not returned
					if(responsePlaceholdersLeft.size > 0) {
						throw new Error('No state for store missing from server response');
					}
				}).catch((err) => {
					// Send error to stores
					responsePlaceholdersLeft.forEach((responsePlaceholder) => {
						responsePlaceholder.reject(err);
					});

					// Send error to dispatch callers
					queuedPromisePlaceholders.forEach((queuedPromisePlaceholder) => {
						queuedPromisePlaceholder.reject(err);
					});
				});
			}

			// Return the final state (resolves after server returns response)
			return updatedStoresPromise;
		});
	}

	_performClientDispatch(stores: StoresMap, action: Action): Promise<ClientDispatchResults> {
		let responsePlaceholders = Immutable.Map();
		let updatedStoresPromises = Immutable.Map();

		// Get pause points from each store's dispatch
		const pausePointsPromises = stores.map((store, storeName) => {
			return new Promise((resolve, reject) => {
				// Perform dispatch, track returned promise for final return from 'dispatch method'
				updatedStoresPromises = updatedStoresPromises.set(storeName,
					store.dispatch(action, {
						finishOnServer(state, index) {
							const responsePlaceholder = new PromisePlaceholder();
							responsePlaceholders = responsePlaceholders.set(storeName, responsePlaceholder);
							return responsePlaceholder.getPromise();
						},
						finishedUpdaters(didFinishOnClient) {
							// $FlowIssue - null is valid resolved value (filtered out below)
							if(didFinishOnClient) resolve(null);
						}
					})
				);
			});
		});

		// Wait for client  part of dispatch to finish
		return resolveMapOfPromises(pausePointsPromises).then((maybePausePoints) => {
			const updatedStoresPromise = resolveMapOfPromises(updatedStoresPromises);
			const pausePoints = maybePausePoints.filter((maybePausePoint) => maybePausePoint? true: false);

			return { updatedStoresPromise, pausePoints, responsePlaceholders };
		});
	}
}

export const createDispatchHandler = DispatchHandler.createDispatchHandler;
export const createServerDispatchHandler = ServerDispatchHandler.createServerDispatchHandler;
export const createClientDispatchHandler = ClientDispatchHandler.createClientDispatchHandler;
<commit_msg>Resolve Pause Points to Send to Server<commit_after>/**
 * @flow
 */
import Immutable from 'immutable';

import PromisePlaceholder from './utils/PromisePlaceholder';
import resolveMapOfPromises from './utils/resolveMapOfPromises';
import mapObject from './utils/mapObject';

import type Store from './Store';

type StoresMap = Immutable.Map<string, Store<any>>;
type OnUpdateFunc = (updateStore: StoresMap) => void;
type OnErrorFunc = (err: Error) => void;
type DispatchFunctionSettings = { initialStores: StoresMap, onUpdatedStores: OnUpdateFunc, onError: OnErrorFunc };
type FinishOnServerFunc = (
	startingPoints: {[key: string]: StartingPoint},
	actions: Array<Action>
) => Promise<{[key: string]: any}>;
type ClientDispatchResults = {
	updatedStoresPromise: Promise<StoresMap>,
	pausePoints: Immutable.Map<string, StartingPoint>,
	responsePlaceholders: Immutable.Map<string, PromisePlaceholder>
};

/**
 * A class to handle calling 'dispatch' with different actions concurrently.
 */
export default class DispatchHandler {

	_stores: StoresMap;
	_actionQueue: ?Immutable.List<{ action: Action, promisePlaceholder: PromisePlaceholder}>;
	_updateFunctions: Immutable.List<OnUpdateFunc>;
	_errorFunctions: Immutable.List<OnErrorFunc>;

	/**
	 * The constructor for the DispatchHandler.
	 *
	 * @param initialStores		{StoresMap}			The stores to start with
	 */
	constructor(initialStores: StoresMap) {
		this._stores = initialStores;
		this._actionQueue = null;

		this._updateFunctions = Immutable.List();
		this._errorFunctions = Immutable.List();
	}

	/**
	 * The create a DispatchHandler with the given initial stores.
	 *
	 * @param initialStores		{StoresMap}			The stores to start with
	 *
	 * @return					{DispatchHandler}	The new DispatchHandler
	 */
	static createDispatchHandler(initialStores: StoresMap): DispatchHandler {
		return new DispatchHandler(initialStores);
	}

	/**
	 * Add a new action to the queue of actions to be dispatched to the stores.
	 *
	 * @param action	{Action}				The action to be dispatched
	 *
	 * @return			{Promise<StoresMap>}	A promise that contains the stores after the give action was dispatched
	 */
	pushAction(action: Action): Promise<StoresMap> {
		// Enqueue action if dispatch is already being performed
		if(this._actionQueue) {
			const promisePlaceholder = new PromisePlaceholder();
			this._actionQueue = this._actionQueue.push({ action, promisePlaceholder });

			return promisePlaceholder.getPromise();
		}

		// Start dispatch
		this._actionQueue = Immutable.List();
		return this._startDispatch(this._stores, action);
	}

	/**
	 * Add a new actions to the queue of actions to be dispatched to the stores.
	 *
	 * @param actions	{Array<Action>}			The actions to be dispatched
	 *
	 * @return			{Promise<StoresMap>}	A promise that contains the stores after all the given action have
	 *											finished dispatching
	 */
	pushActions(actions: Array<Action>): Promise<StoresMap> {
		// Add each action to the queue
		const storeMapPromises = actions.map((action) => this.pushAction(action));

		// The returned promise should resolve when the last action is finished
		return storeMapPromises[storeMapPromises.length - 1];
	}

	/**
	 * Get the current stores.
	 *
	 * @return 		{StoreMap}	The current stores
	 */
	getStores(): StoresMap {
		return this._stores;
	}

	/**
	 * Add a callback for the given event.
	 *
	 * @param event	{string}	The event to subscribe to
	 * @param cb	{Function}	The function to call when the given event happens
	 */
	on(event: string, cb: Function) {
		switch(event) {
			case 'update':
				this._updateFunctions = this._updateFunctions.push(cb);
				break;
			case 'error':
				this._errorFunctions = this._errorFunctions.push(cb);
				break;
			default:
				throw new Error(`Invalid event type for DispatchHandler: ${event}`);
		}
	}

	_onUpdatedStores(updatedStores: StoresMap) {
		// Save updated stores
		this._stores = this._stores.merge(updatedStores);

		// Send to event subscribers
		this._updateFunctions.forEach((updateFunc) => {
			updateFunc(updatedStores);
		});
	}

	_onError(err: Error) {
		this._errorFunctions.forEach((errorFunc) => {
			errorFunc(err);
		});
	}

	_startDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		// Dispatch the action to each store
		let updatedStoresPromises = this._performDispatch(stores, action);

		// Report Results
		updatedStoresPromises = updatedStoresPromises.then((updatedStores) => {
			this._onUpdatedStores(updatedStores);

			return updatedStores;
		}).catch((err) => {
			this._onError(err);

			return stores;
		})

		// Return the stores after dispatch finishes
		return updatedStoresPromises.then((currentStores) => {
			if(!this._actionQueue || this._actionQueue.size === 0) {
				// Pause, to wait for next action
				this._actionQueue = null;

				return currentStores;
			}

			// Start performing the next dispatch
			const { action: nextAction, promisePlaceholder } = this._dequeueNextAction();
			this._startDispatch(currentStores, nextAction).then((nextStores) => {
				// Resolve promise that was returned from 'pushAction'
				promisePlaceholder.resolve(nextStores);
			});

			return currentStores;
		}).catch((err) => {	//NOTE, this shouldn't be need (just in case)
			this._onError(err);

			throw err;
		});
	}

	_performDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		const updatedStoresPromises = stores.map((store) => store.dispatch(action));

		return resolveMapOfPromises(updatedStoresPromises);
	}

	_dequeueNextAction(): { action: Action, promisePlaceholder: PromisePlaceholder } {
		if(!this._actionQueue) throw new Error('cannot dequeue if the queue, _actionQueue, is null');
		const actionQueue = this._actionQueue;

		const nextActionObject = actionQueue.first();
		this._actionQueue = actionQueue.shift();

		return nextActionObject;
	}
}

export class ServerDispatchHandler extends DispatchHandler {
	_onServerArg: any;

	/**
	 * The constructor for the ServerDispatchHandler.
	 *
	 * @param initialStores	{StoresMap}	The stores to start with
	 * @param onServerArg	{any}		The arg to pass to the 'onServer' argument of the updaters
	 */
	constructor(initialStores: StoresMap, onServerArg: any) {
		super(initialStores);

		this._onServerArg = onServerArg;
	}

	/**
	 * The create a ServerDispatchHandler with the given initial stores and passes the given arg to the 'onServer' arg
	 * of the updaters (3rd argument).
	 *
	 * @param initialStores	{StoresMap}			The stores to start with
	 * @param onServerArg	{any}				The arg to pass to the 'onServer' argument of the updaters
	 *
	 * @return				{DispatchHandler}	The new ServerDispatchHandler
	 */
	static createServerDispatchHandler(initialStores: StoresMap, onServerArg: any): DispatchHandler {
		return new ServerDispatchHandler(initialStores, onServerArg);
	}

	_performDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		const updatedStoresPromises = stores.map((store) => {
			return store.dispatch(action, { arg: this._onServerArg })
		});

		return resolveMapOfPromises(updatedStoresPromises);
	}
}

export class ClientDispatchHandler extends DispatchHandler {
	_finishOnServer: FinishOnServerFunc;

	/**
	 * The constructor for the ClientDispatchHandler.
	 *
	 * @param initialStores		{StoresMap}				The stores to start with
	 * @param finishOnServer	{FinishOnServerFunc}	A function that finishes a dispatching an action on the server
	 */
	constructor(initialStores: StoresMap, finishOnServer: FinishOnServerFunc) {
		super(initialStores);

		this._finishOnServer = finishOnServer;
	}

	/**
	 * The create a ServerDispatchHandler with the given initial stores and the function to finish the a dispatch in
	 * the ServerDispatcher.
	 *
	 * @param initialStores		{StoresMap}				The stores to start with
	 * @param finishOnServer	{FinishOnServerFunc}	A function that finishes a dispatching an action on the server
	 *
	 * @return					{DispatchHandler}		The new ServerDispatchHandler
	 */
	static createClientDispatchHandler(initialStores: StoresMap, finishOnServer: FinishOnServerFunc): DispatchHandler {
		return new ClientDispatchHandler(initialStores, finishOnServer);
	}

	_performDispatch(stores: StoresMap, action: Action): Promise<StoresMap> {
		// Preform client side dispatch
		const clientDispatchPromise = this._performClientDispatch(stores, action);

		return clientDispatchPromise.then(({ updatedStoresPromise, pausePoints, responsePlaceholders }) => {

			// Finish on server, if any 'onServer' was called
			if(responsePlaceholders.size > 0) {
				const actionQueue = this._actionQueue? this._actionQueue: Immutable.List();
				this._actionQueue = null;

				const queuedActions = actionQueue.map(({ action }) => action).toArray();
				let queuedPromisePlaceholders = actionQueue.map(({ promisePlaceholder}) => promisePlaceholder);
				let responsePlaceholdersLeft = responsePlaceholders;

				// Send to server
				this._finishOnServer(pausePoints, [action, ...queuedActions]).then((responseStates) => {
					for(let storeName in responseStates) {
						const responseState = responseStates[storeName];

						// Return result for paused dispatches
						if(responsePlaceholdersLeft.has(storeName)) {
							const responsePlaceholder = responsePlaceholdersLeft.get(storeName);
							responsePlaceholdersLeft = responsePlaceholdersLeft.delete(storeName);

							//Give result to Stores
							responsePlaceholder.resolve(responseState);
						}
					}

					// Return result for queued actions
					if(queuedPromisePlaceholders.size > 0) {
						queuedPromisePlaceholders.forEach((queuedPromisePlaceholder) => {
							//Give result to dispatch caller
							queuedPromisePlaceholder.resolve(responseStates);
						});

						queuedPromisePlaceholders = queuedPromisePlaceholders.clear();
					}

					// Send errors if any stores are not returned
					if(responsePlaceholdersLeft.size > 0) {
						throw new Error('No state for store missing from server response');
					}
				}).catch((err) => {
					// Send error to stores
					responsePlaceholdersLeft.forEach((responsePlaceholder) => {
						responsePlaceholder.reject(err);
					});

					// Send error to dispatch callers
					queuedPromisePlaceholders.forEach((queuedPromisePlaceholder) => {
						queuedPromisePlaceholder.reject(err);
					});
				});
			}

			// Return the final state (resolves after server returns response)
			return updatedStoresPromise;
		});
	}

	_performClientDispatch(stores: StoresMap, action: Action): Promise<ClientDispatchResults> {
		let responsePlaceholders = Immutable.Map();
		let updatedStoresPromises = Immutable.Map();

		// Get pause points from each store's dispatch
		const pausePointsPromises = stores.map((store, storeName) => {
			return new Promise((resolve, reject) => {
				// Perform dispatch, track returned promise for final return from 'dispatch method'
				updatedStoresPromises = updatedStoresPromises.set(storeName,
					store.dispatch(action, {
						finishOnServer(state, index) {
							resolve({ state, index });

							// Return value from Dispatcher.dispatch calls
							const responsePlaceholder = new PromisePlaceholder();
							responsePlaceholders = responsePlaceholders.set(storeName, responsePlaceholder);
							return responsePlaceholder.getPromise();
						},
						finishedUpdaters(didFinishOnClient) {
							// $FlowIssue - null is valid resolved value (filtered out below)
							if(didFinishOnClient) resolve(null);
						}
					})
				);
			});
		});

		// Wait for client  part of dispatch to finish
		return resolveMapOfPromises(pausePointsPromises).then((maybePausePoints) => {
			const updatedStoresPromise = resolveMapOfPromises(updatedStoresPromises);
			const pausePoints = maybePausePoints.filter((maybePausePoint) => maybePausePoint? true: false);

			return { updatedStoresPromise, pausePoints, responsePlaceholders };
		});
	}
}

export const createDispatchHandler = DispatchHandler.createDispatchHandler;
export const createServerDispatchHandler = ServerDispatchHandler.createServerDispatchHandler;
export const createClientDispatchHandler = ClientDispatchHandler.createClientDispatchHandler;
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Type for loading a playing sounds, works on multiple platforms and supports
 * embedded base64 data.  This uses Web Audio when available, primarily
 * because the webkit platforms were failing with cross-domain errors when
 * attempting to load audio data from embedded data URIs.  This was occurring
 * in mid-September 2013.  Simplification may be possible if the cross-domain
 * issue goes away at some point in the future.
 *
 * TODO: Do we want to specify just the stem of the file name and have this
 * code figure out whether to use mp3 or ogg?
 */
define( function( require ) {
  'use strict';

  // Imports
  var base64Binary = require( 'VIBE/base64Binary' );

  /**
   * @param soundName - name of this sound no path name, e.g. "ding.mp3".
   * @constructor
   */
  function Sound( soundName ) {

    var self = this;

    // Determine whether Web Audio API is supported and set up an audio context if so.
    if ( 'AudioContext' in window ) {
      this.audioContext = new AudioContext();
    }
    else if ( 'webkitAudioContext' in window ) {
      this.audioContext = new webkitAudioContext();
    }
    else {
      this.audioContext = undefined;
    }

    // Locate the sound data.
    this.sound = document.getElementById( soundName );
    if ( this.sound !== null ) {
      // Sound data is already in the DOM.  For PhET's purposes, it should be
      // encoded as base64 data.  If it's not, throw an error.
      if ( this.sound.getAttribute( 'src' ).match( /^data/ ) === null ) {
        throw new Error( "Embedded audio data must be encoded as base64." );
      }
    }
    else {
      // Set up an audio element in the DOM with a relative path.
      this.sound = document.createElement( 'audio' );
      this.sound.setAttribute( 'src', soundName );
    }

    if ( this.sound === null ) {
      // Sound not found.
      throw new Error( "The specified sound was not found: " + soundName );
    }

    // Load the sound. TODO: Consider moving this to sim preload phase.
    if ( this.audioContext ) {
      var arrayBuff;
      if ( this.sound.getAttribute( 'src' ).match( /^data:/ ) !== null ) {
        // We're working with base64 data, so we need to decode it.
        var soundData = this.sound.getAttribute( 'src' ).replace( new RegExp( '^.*,' ), '' );
        arrayBuff = base64Binary.decodeArrayBuffer( soundData );
        this.audioContext.decodeAudioData( arrayBuff,
          function( audioData ) {
            self.audioBuffer = audioData;
          },
          function() {
            console.log( "Error: Unable to decode audio data." )
          } );
      }
      else {
        // Sound is not yet in DOM, try loading via relative URL path.
        var request = new XMLHttpRequest();
        request.open( 'GET', soundName, true );
        request.responseType = 'arraybuffer';
        request.onload = function() {
          // Decode the audio data asynchronously
          self.audioContext.decodeAudioData( request.response,
            function( audioData ) {
              self.audioBuffer = audioData;
            },
            function() { console.log( "Error loading and decoding sound, sound name: " + soundName ); }
          );
        };
        request.onerror = function() {
          console.log( "Error occurred on request (delete this code when debugged)" );
        };

        request.send();
      }
    }
  }

  /**
   * Plays the sound using the Web Audio API if available or HTML5 audio if not.
   */
  Sound.prototype.play = function() {
    if ( this.audioContext ) {
      // Use the Web Audio API.
      this.soundSource = this.audioContext.createBufferSource();
      this.soundSource.buffer = this.audioBuffer;
      this.soundSource.connect( this.audioContext.destination );

      if ( 'AudioContext' in window ) {
        this.soundSource.start( 0 );
      }
      else if ( 'webkitAudioContext' in window ) {
        this.soundSource.noteOn( 0 );
      }
    }
    else {
      // Use the HTML5 API.
      alert( "About to play sound with HTML API" );
      alert( "sound = " + this.sound );
      this.sound.play();
    }
  };

  /**
   * Stop the sound if it is currently playing.
   */
  Sound.prototype.stop = function() {
    if ( this.audioContext ) {

    }
    else {
      this.sound.pause();
      this.sound.currentTime = 0;
    }
  };

  return Sound;
} );
<commit_msg>Removed erroneously committed debug statements.<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Type for loading a playing sounds, works on multiple platforms and supports
 * embedded base64 data.  This uses Web Audio when available, primarily
 * because the webkit platforms were failing with cross-domain errors when
 * attempting to load audio data from embedded data URIs.  This was occurring
 * in mid-September 2013.  Simplification may be possible if the cross-domain
 * issue goes away at some point in the future.
 *
 * TODO: Do we want to specify just the stem of the file name and have this
 * code figure out whether to use mp3 or ogg?
 */
define( function( require ) {
  'use strict';

  // Imports
  var base64Binary = require( 'VIBE/base64Binary' );

  /**
   * @param soundName - name of this sound no path name, e.g. "ding.mp3".
   * @constructor
   */
  function Sound( soundName ) {

    var self = this;

    // Determine whether Web Audio API is supported and set up an audio context if so.
    if ( 'AudioContext' in window ) {
      this.audioContext = new AudioContext();
    }
    else if ( 'webkitAudioContext' in window ) {
      this.audioContext = new webkitAudioContext();
    }
    else {
      this.audioContext = undefined;
    }

    // Locate the sound data.
    this.sound = document.getElementById( soundName );
    if ( this.sound !== null ) {
      // Sound data is already in the DOM.  For PhET's purposes, it should be
      // encoded as base64 data.  If it's not, throw an error.
      if ( this.sound.getAttribute( 'src' ).match( /^data/ ) === null ) {
        throw new Error( "Embedded audio data must be encoded as base64." );
      }
    }
    else {
      // Set up an audio element in the DOM with a relative path.
      this.sound = document.createElement( 'audio' );
      this.sound.setAttribute( 'src', soundName );
    }

    if ( this.sound === null ) {
      // Sound not found.
      throw new Error( "The specified sound was not found: " + soundName );
    }

    // Load the sound. TODO: Consider moving this to sim preload phase.
    if ( this.audioContext ) {
      var arrayBuff;
      if ( this.sound.getAttribute( 'src' ).match( /^data:/ ) !== null ) {
        // We're working with base64 data, so we need to decode it.
        var soundData = this.sound.getAttribute( 'src' ).replace( new RegExp( '^.*,' ), '' );
        arrayBuff = base64Binary.decodeArrayBuffer( soundData );
        this.audioContext.decodeAudioData( arrayBuff,
          function( audioData ) {
            self.audioBuffer = audioData;
          },
          function() {
            console.log( "Error: Unable to decode audio data." )
          } );
      }
      else {
        // Sound is not yet in DOM, try loading via relative URL path.
        var request = new XMLHttpRequest();
        request.open( 'GET', soundName, true );
        request.responseType = 'arraybuffer';
        request.onload = function() {
          // Decode the audio data asynchronously
          self.audioContext.decodeAudioData( request.response,
            function( audioData ) {
              self.audioBuffer = audioData;
            },
            function() { console.log( "Error loading and decoding sound, sound name: " + soundName ); }
          );
        };
        request.onerror = function() {
          console.log( "Error occurred on request (delete this code when debugged)" );
        };

        request.send();
      }
    }
  }

  /**
   * Plays the sound using the Web Audio API if available or HTML5 audio if not.
   */
  Sound.prototype.play = function() {
    if ( this.audioContext ) {
      // Use the Web Audio API.
      this.soundSource = this.audioContext.createBufferSource();
      this.soundSource.buffer = this.audioBuffer;
      this.soundSource.connect( this.audioContext.destination );

      if ( 'AudioContext' in window ) {
        this.soundSource.start( 0 );
      }
      else if ( 'webkitAudioContext' in window ) {
        this.soundSource.noteOn( 0 );
      }
    }
    else {
      // Use the HTML5 API.
      this.sound.play();
    }
  };

  /**
   * Stop the sound if it is currently playing.
   */
  Sound.prototype.stop = function() {
    if ( this.audioContext ) {

    }
    else {
      this.sound.pause();
      this.sound.currentTime = 0;
    }
  };

  return Sound;
} );
<|endoftext|>


<commit_before>// Constants
var IMG_URL_KEY = "imgurl=";
var SEARCH_BOX_ID = "gsfi";

// Initial Vars
var links = document.getElementsByTagName('a')
var validLinks = []

function getGifUrl(link) {
  var href = link.href + "";
  var start = href.indexOf(IMG_URL_KEY) + IMG_URL_KEY.length
  href = href.substr(start)
  var end = href.indexOf("&")
  href = href.substr(0,end)
  return href
}

// extract the relevant image and replace the source of the child image
function replaceGif(link) {
  var gifUrl = getGifUrl(link);
  link.children[0].src = gifUrl;
}

// changes all the gifs on the page
function main() {
  for(var i=0; i<links.length;i++) {
    var link = links[i];
    var href = link.href
    if (href.indexOf(IMG_URL_KEY) !== -1) {
      replaceGif(link);
    }
  }
}

main();

// eventually this should refresh properly
var searchBar = document.getElementsByClassName(SEARCH_BOX_ID)[0]
<commit_msg>fix the ajax-y images not loading properly<commit_after>// Constants
var IMG_URL_KEY = "imgurl=";
var SEARCH_BOX_ID = "gsfi";

// Initial Vars
var links = document.getElementsByTagName('a')
var processedGifs = {};

function getGifUrl(link) {
  var href = link.href + "";
  var start = href.indexOf(IMG_URL_KEY) + IMG_URL_KEY.length
  href = href.substr(start)
  var end = href.indexOf("&")
  href = href.substr(0,end)
  return decodeURIComponent(href);
}

// extract the relevant image and replace the source of the child image
function replaceGif(link) {
  var gifUrl = getGifUrl(link);
  try {
    var currentSearch = document.getElementsByClassName(SEARCH_BOX_ID)[0].value
  } catch(e) {
    var currentSearch = "";
  }
  var key = gifUrl + currentSearch
  if (processedGifs[key] == undefined) {
    link.children[0].src = gifUrl;
    processedGifs[key] = true;
  }
}

// changes all the gifs on the page
function main() {
  for(var i=0; i<links.length;i++) {
    var link = links[i];
    var href = link.href
    if (href.indexOf(IMG_URL_KEY) !== -1) {
      replaceGif(link);
    }
  }
}

main();

setInterval(main,500);
setTimeout(main,100);
<|endoftext|>


<commit_before>---
---

// Floating label headings for the contact form
$(function() {
    $("body").on("input propertychange", ".floating-label-form-group", function(e) {
        $(this).toggleClass("floating-label-form-group-with-value", !! $(e.target).val());
    }).on("focus", ".floating-label-form-group", function() {
        $(this).addClass("floating-label-form-group-with-focus");
    }).on("blur", ".floating-label-form-group", function() {
        $(this).removeClass("floating-label-form-group-with-focus");
    });
});

//jqBootstrapValidation
$(function() {
  $("input,textarea").jqBootstrapValidation({
    preventSubmit: true,
    submitError: function($form, event, errors) {
    },
    submitSuccess: function($form, event) {
    },
    filter: function() {
      return $(this).is(":visible");
    },
  });

  $("a[data-toggle=\"tab\"]").click(function(e) {
    e.preventDefault();
    $(this).tab("show");
  });
});

//Formspree
var $contactForm = $('#contactForm');
$contactForm.submit(function (e) {
  e.preventDefault();
  $.ajax({
    url: '//formspree.io/{{ site.email }}',
    method: 'POST',
    data: $(this).serialize(),
    dataType: 'json',
    beforeSend: function() {
      $('#send-button').hide();
      $('#sending').show();
    },
    success: function (data) {
      $('#sending').hide();
      $('#sent-success').show();
      $('.input-element').hide();
    },
    error: function (err) {
      $('#sending').hide();
      $('#sent-error').show();
      $('#send-button').show();
    }
  });
});<commit_msg>made contact section work with jqBootstrapValidation, Formspree and Ajax<commit_after>---
---

// Floating label headings for the contact form
$(function() {
    $("body").on("input propertychange", ".floating-label-form-group", function(e) {
        $(this).toggleClass("floating-label-form-group-with-value", !! $(e.target).val());
    }).on("focus", ".floating-label-form-group", function() {
        $(this).addClass("floating-label-form-group-with-focus");
    }).on("blur", ".floating-label-form-group", function() {
        $(this).removeClass("floating-label-form-group-with-focus");
    });
});

//jqBootstrapValidation + Formspree + Ajax
$(function() {
  $("input,textarea").jqBootstrapValidation({
    preventSubmit: true,
    submitError: function($form, event, errors) {
      console.log("submitError!")
    },
    submitSuccess: function($form, event) {
      console.log("submitSuccess!!!")
      event.preventDefault();
      $.ajax({
        url: '//formspree.io/{{ site.email }}',
        method: 'POST',
        data: $(this).serialize(),
        dataType: 'json',
        beforeSend: function() {
          $('#send-button').hide();
          $('#sending').show();
        },
        success: function (data) {
          $('#sending').hide();
          $('.input-element').fadeOut(1000, function() {
            $('#sent-success').show();
          });
       },
        error: function (err) {
          $('#sending').hide();
          $('#sent-error').show();
          $('#send-button').show();
        }
      });
    },
    filter: function() {
      return $(this).is(":visible");
    },
  });

  $("a[data-toggle=\"tab\"]").click(function(e) {
    e.preventDefault();
    $(this).tab("show");
  });
});
<|endoftext|>


<commit_before>var Tracks = (function ()
{

    var minFontSize = 0.1
    var maxFontSize = 20

    function zoom(area, direction)
    {
        // possible refinements: max + css animations
        var size = parseFloat(area.css('font-size'))
        // scale down slower toward 0
        size += direction * size / 15

        size = Math.max(size, minFontSize)
        size = Math.min(size, maxFontSize)

        area.css('font-size', size + 'px')
    }

    function zoomIn()
    {
        var area = $(this)
        zoom(area, +1)
    }

    function zoomOut()
    {
        var area = $(this)
        zoom(area, -1)
    }

    function scroll(area, direction)
    {
        var pos = area.scrollLeft()
        var len = area.width()
        area.stop().animate({scrollLeft: pos + len * direction }, '300', 'swing')
    }

	function scrollLeft()
	{
        var area = $(this)
        scroll(area, -1)
	}

	function scrollRight()
	{
        var area = $(this)
        scroll(area, +1)
	}

    function getCharSize(sequence)
    {
        var elements = sequence.children('.elements')
        var seq = $.trim(elements.text())
        elements.wrapInner('')
        var span = elements.children('span')
        var charSize = span.innerWidth() / seq.length
        span.contents().unwrap()
        return charSize
    }

	function scrollTo()
	{
        var input = $(this).closest('.input-group').find('.scroll-to-input')
        var tracks = $(this).closest('.tracks-box')
        var area = tracks.find('.scroll-area')
        var sequence = tracks.find('.sequence')
        var pos = $(input).val()

        var charSize = getCharSize(sequence)
        area.animate({scrollLeft: pos * charSize }, '300', 'swing')
	}

    function initButtons(buttons, func, context)
    {
        for(var i = 0; i < buttons.length; i++)
        {
            var button = $(buttons[i])
            if(context)
            {
                button.click($.proxy(func, context))
            }
            else
            {
                button.click(func)
            }
        }
    }

    function initFields(fields, func)
    {
        for(var i = 0; i < fields.length; i++)
        {
            $(fields[i]).keyup(function(e)
            {
                if(e.keyCode == 13)
                {
                    func.call(this)
                }
            })
        }
    }

	var publicSpace = {
		init: function(data)
		{
            var boxes = $('.tracks-box')
            for(var i = 0; i < boxes.length; i++)
            {
                var box = $(boxes[i])

                var tracks = box.find('.tracks')

                var scrollArea = tracks.find('.scroll-area')

                var buttons = box.find('.scroll-left')
                initButtons(buttons, scrollLeft, scrollArea)

                var buttons = box.find('.scroll-right')
                initButtons(buttons, scrollRight, scrollArea)

                var innerDiv = box.children('.inner')

                var buttons = box.find('.zoom-out')
                initButtons(buttons, zoomOut, innerDiv)

                var buttons = box.find('.zoom-in')
                initButtons(buttons, zoomIn, innerDiv)

                var buttons = box.find('.scroll-to')
                initButtons(buttons, scrollTo)

                var buttons = box.find('.scroll-to-input')
                initFields(buttons, scrollTo)

                var controls = box.find('.controls')
                for(var j = 0; j < controls.length; j++)
                {
                    $(controls[i]).show()
                }
            }
		}
	}

	return publicSpace
})()

Tracks.init()
<commit_msg>Fix typo in tracks JS<commit_after>var Tracks = (function ()
{

    var minFontSize = 0.1
    var maxFontSize = 20

    function zoom(area, direction)
    {
        // possible refinements: max + css animations
        var size = parseFloat(area.css('font-size'))
        // scale down slower toward 0
        size += direction * size / 15

        size = Math.max(size, minFontSize)
        size = Math.min(size, maxFontSize)

        area.css('font-size', size + 'px')
    }

    function zoomIn()
    {
        var area = $(this)
        zoom(area, +1)
    }

    function zoomOut()
    {
        var area = $(this)
        zoom(area, -1)
    }

    function scroll(area, direction)
    {
        var pos = area.scrollLeft()
        var len = area.width()
        area.stop().animate({scrollLeft: pos + len * direction }, '300', 'swing')
    }

	function scrollLeft()
	{
        var area = $(this)
        scroll(area, -1)
	}

	function scrollRight()
	{
        var area = $(this)
        scroll(area, +1)
	}

    function getCharSize(sequence)
    {
        var elements = sequence.children('.elements')
        var seq = $.trim(elements.text())
        elements.wrapInner('')
        var span = elements.children('span')
        var charSize = span.innerWidth() / seq.length
        span.contents().unwrap()
        return charSize
    }

	function scrollTo()
	{
        var input = $(this).closest('.input-group').find('.scroll-to-input')
        var tracks = $(this).closest('.tracks-box')
        var area = tracks.find('.scroll-area')
        var sequence = tracks.find('.sequence')
        var pos = $(input).val()

        var charSize = getCharSize(sequence)
        area.animate({scrollLeft: pos * charSize }, '300', 'swing')
	}

    function initButtons(buttons, func, context)
    {
        for(var i = 0; i < buttons.length; i++)
        {
            var button = $(buttons[i])
            if(context)
            {
                button.click($.proxy(func, context))
            }
            else
            {
                button.click(func)
            }
        }
    }

    function initFields(fields, func)
    {
        for(var i = 0; i < fields.length; i++)
        {
            $(fields[i]).keyup(function(e)
            {
                if(e.keyCode == 13)
                {
                    func.call(this)
                }
            })
        }
    }

	var publicSpace = {
		init: function(data)
		{
            var boxes = $('.tracks-box')
            for(var i = 0; i < boxes.length; i++)
            {
                var box = $(boxes[i])

                var tracks = box.find('.tracks')

                var scrollArea = tracks.find('.scroll-area')

                var buttons = box.find('.scroll-left')
                initButtons(buttons, scrollLeft, scrollArea)

                var buttons = box.find('.scroll-right')
                initButtons(buttons, scrollRight, scrollArea)

                var innerDiv = box.children('.inner')

                var buttons = box.find('.zoom-out')
                initButtons(buttons, zoomOut, innerDiv)

                var buttons = box.find('.zoom-in')
                initButtons(buttons, zoomIn, innerDiv)

                var buttons = box.find('.scroll-to')
                initButtons(buttons, scrollTo)

                var buttons = box.find('.scroll-to-input')
                initFields(buttons, scrollTo)

                var controls = box.find('.controls')
                for(var j = 0; j < controls.length; j++)
                {
                    $(controls[j]).show()
                }
            }
		}
	}

	return publicSpace
})()

Tracks.init()
<|endoftext|>


<commit_before>(function(module, fs, extend, moment, request, requestPromise, chai, liburl, Joi, helper, multipartParser, redirect)
{

/**  XAPI-00334, Communication 2.1.3 GET Statements
 * An LRS rejects a Statement of bad authorization (either authentication needed or failed credentials) with error code 401 Unauthorized
 */
    it('An LRS rejects a Statement of bad authorization (either authentication needed or failed credentials) with error code 401 Unauthorized (Authentication 4.0 XAPI-00334)', function(done)
    {
        var templates = [
        {
            statement: '{{statements.default}}'
        }];
        var data = helper.createFromTemplate(templates);
        data = data.statement;
        data.id = helper.generateUUID();
        var headers = helper.addAllHeaders(
        {});

        //warning: this ".\\" is super important. Node caches the modules, and the superrequest module has been modified to work correctly
        //with oauth already. We get a new verions by appending some other characters to defeat the cache.
        if(global.OAUTH)
            request = require('.\\super-request');
        else
            headers["Authorization"] = 'Basic ' + new Buffer('RobCIsNot:AUserOnThisLRS').toString('base64');
        request(helper.getEndpointAndAuth())
            .put(helper.getEndpointStatements() + '?statementId=' + data.id)
            .headers(headers)
            .json(data)
            .expect(401, done);
    });

/**  XAPI-00335, Communication 2.1.3 GET Statements
 * An LRS must support HTTP Basic Authentication
 */
 //WARNING: This might not be a great test. OAUTH will override it
    it('An LRS must support HTTP Basic Authentication(Authentication 4.0 XAPI-00335)', function(done)
    {
        var templates = [
        {
            statement: '{{statements.default}}'
        }];
        var data = helper.createFromTemplate(templates);
        data = data.statement;
        data.id = helper.generateUUID();
        var headers = helper.addAllHeaders(
        {});

        request(helper.getEndpointAndAuth())
            .put(helper.getEndpointStatements() + '?statementId=' + data.id)
            .headers(headers)
            .json(data)
            .expect(204, done);
    });

}(module, require('fs'), require('extend'), require('moment'), require('super-request'), require('supertest-as-promised'), require('chai'), require('url'), require('joi'), require('./../helper'), require('./../multipartParser'), require('./../redirect.js')));<commit_msg>chekc for malformed header<commit_after>(function(module, fs, extend, moment, request, requestPromise, chai, liburl, Joi, helper, multipartParser, redirect)
{

/**  XAPI-00334, Communication 2.1.3 GET Statements
 * An LRS rejects a Statement of bad authorization (either authentication needed or failed credentials) with error code 401 Unauthorized
 */
 describe('An LRS rejects a Statement of bad authorization (either authentication needed or failed credentials) with error code 401 Unauthorized (Authentication 4.0 XAPI-00334)',function(){

    it("fails when given a random name pass pair", function(done)
    {
        var templates = [
        {
            statement: '{{statements.default}}'
        }];
        var data = helper.createFromTemplate(templates);
        data = data.statement;
        data.id = helper.generateUUID();
        var headers = helper.addAllHeaders(
        {});

        //warning: this ".\\" is super important. Node caches the modules, and the superrequest module has been modified to work correctly
        //with oauth already. We get a new verions by appending some other characters to defeat the cache.
        if(global.OAUTH)
            request = require('.\\super-request');
        else
            headers["Authorization"] = 'Basic ' + new Buffer('RobCIsNot:AUserOnThisLRS').toString('base64');
            request(helper.getEndpointAndAuth())
            .put(helper.getEndpointStatements() + '?statementId=' + data.id)
            .headers(headers)
            .json(data)
            .expect(401, done);
    });

    it('fails with a malformed header', function(done)
    {
        var templates = [
        {
            statement: '{{statements.default}}'
        }];
        var data = helper.createFromTemplate(templates);
        data = data.statement;
        data.id = helper.generateUUID();
        var headers = helper.addAllHeaders(
        {});

        //warning: this ".\\" is super important. Node caches the modules, and the superrequest module has been modified to work correctly
        //with oauth already. We get a new verions by appending some other characters to defeat the cache.
        if(global.OAUTH)
            request = require('.\\super-request');
        else
            headers["Authorization"] = 'Basic:' + new Buffer('RobCIsNot:AUserOnThisLRS').toString('base64'); //note bad encoding here.
            request(helper.getEndpointAndAuth())
            .put(helper.getEndpointStatements() + '?statementId=' + data.id)
            .headers(headers)
            .json(data)
            .expect(401, done);
    });
})

/**  XAPI-00335, Communication 2.1.3 GET Statements
 * An LRS must support HTTP Basic Authentication
 */
 //WARNING: This might not be a great test. OAUTH will override it
    it('An LRS must support HTTP Basic Authentication(Authentication 4.0 XAPI-00335)', function(done)
    {
        var templates = [
        {
            statement: '{{statements.default}}'
        }];
        var data = helper.createFromTemplate(templates);
        data = data.statement;
        data.id = helper.generateUUID();
        var headers = helper.addAllHeaders(
        {});

        request(helper.getEndpointAndAuth())
            .put(helper.getEndpointStatements() + '?statementId=' + data.id)
            .headers(headers)
            .json(data)
            .expect(204, done);
    });

}(module, require('fs'), require('extend'), require('moment'), require('super-request'), require('supertest-as-promised'), require('chai'), require('url'), require('joi'), require('./../helper'), require('./../multipartParser'), require('./../redirect.js')));<|endoftext|>