diff --git a/app/coffeescripts/bundles/assignment_moderation.coffee b/app/coffeescripts/bundles/assignment_moderation.coffee index 5bdcef263d8..b44f40de326 100644 --- a/app/coffeescripts/bundles/assignment_moderation.coffee +++ b/app/coffeescripts/bundles/assignment_moderation.coffee @@ -2,10 +2,21 @@ require [ 'jquery' 'react' 'jsx/assignments/ModerationApp' -], ($, React, ModerationApp) -> + 'jsx/assignments/store/configureStore' +], ($, React, ModerationApp, configureStore) -> - React.render(ModerationApp({ - student_submissions_url: ENV.URLS.student_submissions_url - publish_grades_url: ENV.URLS.publish_grades_url + store = configureStore({ + moderationStage: [], + students: [], + urls: window.ENV.URLS + flashMessage: { + time: Date.now(), + message: '', + error: false + }, + assignment: { + published: window.ENV.GRADES_PUBLISHED + } }) - , $('#assignment_moderation')[0]) + + React.render(ModerationApp(store: store), $('#assignment_moderation')[0]) diff --git a/app/controllers/assignments_controller.rb b/app/controllers/assignments_controller.rb index 15750feeb7c..8285261cb90 100644 --- a/app/controllers/assignments_controller.rb +++ b/app/controllers/assignments_controller.rb @@ -184,8 +184,10 @@ class AssignmentsController < ApplicationController js_env({ :URLS => { :student_submissions_url => polymorphic_url([:api_v1, @context, @assignment, :submissions]) + "?include[]=user_summary&include[]=provisional_grades", - :publish_grades_url => api_v1_publish_provisional_grades_url({course_id: @context.id, assignment_id: @assignment.id}) + :publish_grades_url => api_v1_publish_provisional_grades_url({course_id: @context.id, assignment_id: @assignment.id}), + :list_gradeable_students => api_v1_course_assignment_gradeable_students_url({course_id: @context.id, assignment_id: @assignment.id}) + "?include[]=provisional_grades&per_page=50" }}) + js_env(:GRADES_PUBLISHED => @assignment.grades_published?) respond_to do |format| format.html { render } diff --git a/app/jsx/assignments/FlashMessageHolder.jsx b/app/jsx/assignments/FlashMessageHolder.jsx new file mode 100644 index 00000000000..75787ef928f --- /dev/null +++ b/app/jsx/assignments/FlashMessageHolder.jsx @@ -0,0 +1,43 @@ +/** @jsx React.DOM */ + +define([ + 'jquery', + 'react', + 'i18n!moderated_grading', + 'compiled/jquery.rails_flash_notifications' +], function ($, React, I18n) { + + var FlashMessageHolder = React.createClass({ + displayName: 'FlashMessageHolder', + + getInitialState () { + return this.props.store.getState().flashMessage; + }, + + componentDidMount () { + this.props.store.subscribe(this.handleChange); + }, + + handleChange () { + this.setState(this.props.store.getState().flashMessage); + }, + + shouldComponentUpdate (nextProps, nextState) { + return nextState.time > this.state.time; + }, + + componentWillUpdate (nextProps, nextState) { + if (nextState.error) { + $.flashError(nextState.message); + } else { + $.flashMessage(nextState.message); + } + }, + + render () { + return null; + } + }); + + return FlashMessageHolder; +}); diff --git a/app/jsx/assignments/Header.jsx b/app/jsx/assignments/Header.jsx index bd10a7a37f1..4329481e3f8 100644 --- a/app/jsx/assignments/Header.jsx +++ b/app/jsx/assignments/Header.jsx @@ -12,11 +12,23 @@ define([ actions: React.PropTypes.object.isRequired }, + getInitialState () { + return this.props.store.getState().assignment; + }, + + componentDidMount () { + this.props.store.subscribe(this.handleChange); + }, + + handleChange () { + this.setState(this.props.store.getState().assignment); + }, + handlePublishClick () { // Make a better looking confirm one day var confirmMessage = I18n.t('Are you sure you want to do this? It cannot be undone and will override existing grades in the gradebook.') if (window.confirm(confirmMessage)) { - this.props.actions.publishGrades(); + this.props.store.dispatch(this.props.actions.publishGrades()); } }, @@ -32,6 +44,7 @@ define([ type='button' className='ModeratedGrading__Header-PublishBtn Button Button--primary' onClick={this.handlePublishClick} + disabled={this.state.published} > {I18n.t('Publish')} diff --git a/app/jsx/assignments/ModeratedStudentList.jsx b/app/jsx/assignments/ModeratedStudentList.jsx index 85dce249147..fbd31d959eb 100644 --- a/app/jsx/assignments/ModeratedStudentList.jsx +++ b/app/jsx/assignments/ModeratedStudentList.jsx @@ -9,31 +9,21 @@ define([ var MARK_THREE = 2; return React.createClass({ - getInitialState () { - return ( - {submissions: []} - ); - }, - componentDidMount () { - this.props.store.addChangeListener(this.handleStoreChange); - }, - handleStoreChange () { - this.setState({submissions: this.props.store.submissions}); - }, - displayName: 'ModeratedStudentList', - selectCheckbox (submission, event) { - // this.actions.updateSubmission(submission); + + propTypes: { + students: React.PropTypes.arrayOf(React.PropTypes.object).isRequired }, + renderSubmissionMark (submission, mark_number) { - if(submission.provisional_grades[mark_number]){ - return( + if (submission.provisional_grades && submission.provisional_grades[mark_number]) { + return (
- + {submission.provisional_grades[mark_number].score}
); - }else{ - return( + } else { + return (
Speed Grader
@@ -41,14 +31,14 @@ define([ } }, renderFinalGrade (submission) { - if (submission.grade){ - return( + if (submission.grade) { + return ( {submission.score} ); - }else{ - return( + } else { + return ( - @@ -56,16 +46,16 @@ define([ } }, render () { - return( + return ( ); diff --git a/app/jsx/assignments/ModerationApp.jsx b/app/jsx/assignments/ModerationApp.jsx index 7bd0eaf1007..078570da559 100644 --- a/app/jsx/assignments/ModerationApp.jsx +++ b/app/jsx/assignments/ModerationApp.jsx @@ -5,36 +5,39 @@ define([ 'i18n!moderated_grading', './ModeratedStudentList', './Header', - './stores/ModerationStore', + './FlashMessageHolder', './actions/ModerationActions' -], function (React, I18n, ModeratedStudentList, Header, Store, Actions) { +], function (React, I18n, ModeratedStudentList, Header, FlashMessageHolder, Actions) { return React.createClass({ displayName: 'ModerationApp', propTypes: { - student_submissions_url: React.PropTypes.string.isRequired, - publish_grades_url: React.PropTypes.string.isRequired + store: React.PropTypes.object.isRequired + }, + + getInitialState () { + return this.props.store.getState(); }, componentDidMount () { - this.actions.loadInitialSubmissions(this.props.student_submissions_url); + this.props.store.subscribe(this.handleChange); + this.props.store.dispatch(Actions.apiGetStudents()); }, - componentWillMount () { - this.store = new Store(); - this.actions = new Actions(this.store, { - publish_grades_url: this.props.publish_grades_url - }); + handleChange () { + this.setState(this.props.store.getState()); }, render () { return (
+

{I18n.t('Moderate %{assignment_name}', {assignment_name: 'TODO!!!!!!!!'})}

-
- +
+
+ ); } }); diff --git a/app/jsx/assignments/actions/ModerationActions.jsx b/app/jsx/assignments/actions/ModerationActions.jsx index bf4d18ae084..17cbe5c1e64 100644 --- a/app/jsx/assignments/actions/ModerationActions.jsx +++ b/app/jsx/assignments/actions/ModerationActions.jsx @@ -1,51 +1,90 @@ /** @jsx React.DOM */ define([ - 'jquery', - 'i18n!moderated_grading', 'axios', - 'compiled/jquery.rails_flash_notifications' -], function ($, I18n, axios) { - class ModerationActions { - constructor (store, opts) { - this.store = store; - if (opts && opts.publish_grades_url) { - this.publish_grades_url = opts.publish_grades_url; - } - } + 'i18n!moderated_grading' +], function (axios, I18n) { - updateSubmission (submission) { - // Update the submission and then update the store - } + var ModerationActions = { - loadInitialSubmissions (submissions_url) { - axios.get(submissions_url) - .then(function (response) { - this.store.addSubmissions(response.data); - }.bind(this)) - .catch(function (response) { - console.log('finished'); - }); - } + // Define 'constants' for types + SELECT_STUDENT: 'SELECT_STUDENT', + UNSELECT_STUDENT: 'UNSELECT_STUDENT', + SELECT_ALL_STUDENTS: 'SELECT_ALL_STUDENTS', + UNSELECT_ALL_STUDENTS: 'UNSELECT_ALL_STUDENTS', + SELECT_MARK: 'SELECT_MARK', + UPDATE_MODERATION_SET: 'UPDATE_MODERATION_SET', + PUBLISHED_GRADES: 'PUBLISHED_GRADES', + PUBLISHED_GRADES_FAILED: 'PUBLISHED_GRADES_FAILED', + GOT_STUDENTS: 'GOT_STUDENTS', - publishGrades () { - var axiosPostOptions = { - xsrfCookieName: '_csrf_token', - xsrfHeaderName: 'X-CSRF-Token' + selectStudent (studentId) { + return { + type: this.SELECT_STUDENT, + payload: { studentId } + }; + }, + + gotStudents (students) { + return { + type: this.GOT_STUDENTS, + payload: { students } + }; + }, + + publishedGrades (message) { + return { + type: this.PUBLISHED_GRADES, + payload: { + message, + time: Date.now() + } + }; + }, + + publishGradesFailed (message) { + return { + type: this.PUBLISHED_GRADES_FAILED, + payload: { + message, + time: Date.now() + }, + error: true + }; + }, + + publishGrades (ajaxLib) { + return (dispatch, getState) => { + var endpoint = getState().urls.publish_grades_url; + ajaxLib = ajaxLib || axios; + ajaxLib.post(endpoint) + .then((response) => { + dispatch(this.publishedGrades(I18n.t('Success! Grades were published to the grade book.'))); + }) + .catch((response) => { + if (response.status === 400) { + dispatch(this.publishGradesFailed(I18n.t('Assignment grades have already been published.'))); + } else { + dispatch(this.publishGradesFailed(I18n.t('An error occurred publishing grades.'))); + } + }); + }; + }, + + apiGetStudents (ajaxLib) { + return (dispatch, getState) => { + var endpoint = getState().urls.list_gradeable_students; + ajaxLib = ajaxLib || axios; + ajaxLib.get(endpoint) + .then((response) => { + dispatch(this.gotStudents(response.data)); + }) + .catch((response) => { + throw new Error(response); + }); }; - axios.post(this.publish_grades_url, {}, axiosPostOptions) - .then((response) => { - $.flashMessage(I18n.t('Success! Grades were published to the grade book.')); - }) - .catch((response) => { - if ((response.status === 400) && (response.data.message)){ - $.flashError(response.data.message); - } else { - $.flashError(I18n.t('Error! A problem happened publishing grades.')); - } - }); } - } + }; return ModerationActions; }); diff --git a/app/jsx/assignments/reducers/rootReducer.jsx b/app/jsx/assignments/reducers/rootReducer.jsx new file mode 100644 index 00000000000..3604fcfbd22 --- /dev/null +++ b/app/jsx/assignments/reducers/rootReducer.jsx @@ -0,0 +1,84 @@ +/** @jsx React.DOM */ + +define([ + 'underscore', + 'redux', + '../actions/ModerationActions' +], function (_, Redux, ModerationActions) { + + var { combineReducers } = Redux; + + var studentHandlers = {}; + + studentHandlers[ModerationActions.GOT_STUDENTS] = (state, action) => { + return state.concat(action.payload.students); + }; + + var flashHandlers = {}; + + flashHandlers[ModerationActions.PUBLISHED_GRADES] = (state, action) => { + // Don't mutate the existing state. + var newState = _.extend({}, state); + newState.time = action.payload.time; + newState.message = action.payload.message; + newState.error = false; + return newState; + }; + + flashHandlers[ModerationActions.PUBLISHED_GRADES_FAILED] = (state, action) => { + // Don't mutate the existing state. + var newState = _.extend({}, state); + newState.time = action.payload.time; + newState.message = action.payload.message; + newState.error = true; + return newState; + }; + + function urls (state, action) { + return state || {}; + } + + function students (state, action) { + state = state || []; + var handler = studentHandlers[action.type]; + if (handler) return handler(state, action); + return state; + } + + function addUserToModeration (state, action) { + // Don't mutate the existing state. + var newState = _.extend({}, state); + var { type, payload } = action; + if (type === ModerationActions.SELECT_STUDENT) { + newState.moderationStage.push(payload.studentId); + return newState; + } + return {moderationStage: []}; + } + + function flashMessage (state, action) { + state = state || {}; + var handler = flashHandlers[action.type]; + if (handler) return handler(state, action); + return state; + } + + function assignment (state, action) { + state = state || {}; + if (action.type === ModerationActions.PUBLISHED_GRADES) { + // Don't mutate the existing state. + var newState = _.extend({}, state); + newState.published = true; + return newState; + } + return state; + } + + return combineReducers({ + students, + urls, + flashMessage, + assignment + }); + +}); diff --git a/app/jsx/assignments/store/configureStore.jsx b/app/jsx/assignments/store/configureStore.jsx new file mode 100644 index 00000000000..d6d59515a06 --- /dev/null +++ b/app/jsx/assignments/store/configureStore.jsx @@ -0,0 +1,18 @@ +/** @jsx React.DOM */ + +define([ + 'redux', + 'redux-thunk', + '../reducers/rootReducer' +], function (Redux, ReduxThunk, rootReducer) { + + var { createStore, applyMiddleware } = Redux; + + var createStoreWithMiddleware = applyMiddleware( + ReduxThunk + )(createStore); + + return function configureStore (initialState) { + return createStoreWithMiddleware(rootReducer, initialState); + }; +}); diff --git a/app/jsx/assignments/stores/ModerationStore.jsx b/app/jsx/assignments/stores/ModerationStore.jsx deleted file mode 100644 index c23c44714a7..00000000000 --- a/app/jsx/assignments/stores/ModerationStore.jsx +++ /dev/null @@ -1,48 +0,0 @@ -/** @jsx React.DOM */ - -define(['underscore', 'Backbone'], function(_){ - - class ModerationStore { - constructor () { - this.events = _.extend({}, Backbone.Events); - this.submissions = []; - } - - /** - * Adds a handler to be fired when the change event occurs. - * @param {Function} handler Function to be called when the event is fired - */ - addChangeListener (handler) { - this.events.on('change', handler); - } - - /** - * Removes a handler from the store. - * @param {Function} handler Function to be called when the event is fired - */ - removeChangeListener (handler) { - this.events.off('change', handler); - } - - /** - * Add submissions to the store - * - * @param {Array} submission An array of submission objects - */ - addSubmissions (submissions) { - this.submissions = this._mergeArraysById(this.submissions, submissions); - this.events.trigger('change'); - } - - _mergeArraysById (arrayOne, arrayTwo) { - return _.map(arrayTwo, (item) => { - var foundItem = _.find(arrayOne, (arrayOneItem) => { - return arrayOneItem.id === item.id; - }); - return _.extend(item, foundItem); - }); - } - } - - return ModerationStore; -}); diff --git a/bower.json b/bower.json index 8eb190d2f60..454e2f9e633 100644 --- a/bower.json +++ b/bower.json @@ -26,9 +26,9 @@ "react-select-box": "https://github.com/instructure-react/react-select-box.git#b1ddd39223d48793fbe3dc4e87aca00d57197b5f", "react-tray": "~0.1.2", "classnames": "~2.1.2", - "moment": "~2.10.3", - "reflux": "~0.2.7", "moment": "~2.10.6", - "axios": "~0.5.4" + "reflux": "~0.2.7", + "axios": "~0.5.4", + "when": "~3.7.3" } } diff --git a/public/javascripts/bower/when/.bower.json b/public/javascripts/bower/when/.bower.json new file mode 100644 index 00000000000..4e798f56c6c --- /dev/null +++ b/public/javascripts/bower/when/.bower.json @@ -0,0 +1,46 @@ +{ + "name": "when", + "version": "3.7.3", + "main": "when.js", + "moduleType": [ + "amd", + "node" + ], + "description": "A lightweight Promises/A+ and when() implementation, plus other async goodies.", + "keywords": [ + "Promises/A+", + "promises-aplus", + "promise", + "promises", + "deferred", + "deferreds", + "when", + "async", + "asynchronous", + "cujo" + ], + "homepage": "https://github.com/cujojs/when", + "authors": [ + "Brian Cavalier " + ], + "license": "MIT", + "ignore": [ + "**/.*", + "**/*.md", + "docs", + "benchmark", + "node_modules", + "bower_components", + "test", + "build" + ], + "_release": "3.7.3", + "_resolution": { + "type": "version", + "tag": "3.7.3", + "commit": "d748316b0a1ef9f323edd5c9eac899f221c7fa13" + }, + "_source": "git://github.com/cujojs/when.git", + "_target": "~3.7.3", + "_originalSource": "when" +} \ No newline at end of file diff --git a/public/javascripts/bower/when/LICENSE.txt b/public/javascripts/bower/when/LICENSE.txt new file mode 100644 index 00000000000..09a1063e406 --- /dev/null +++ b/public/javascripts/bower/when/LICENSE.txt @@ -0,0 +1,24 @@ +Open Source Initiative OSI - The MIT License + +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2011 Brian Cavalier + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/public/javascripts/bower/when/bower.json b/public/javascripts/bower/when/bower.json new file mode 100644 index 00000000000..40156b75fae --- /dev/null +++ b/public/javascripts/bower/when/bower.json @@ -0,0 +1,23 @@ +{ + "name": "when", + "version": "3.7.3", + "main": "when.js", + "moduleType": ["amd", "node"], + "description": "A lightweight Promises/A+ and when() implementation, plus other async goodies.", + "keywords": ["Promises/A+", "promises-aplus", "promise", "promises", "deferred", "deferreds", "when", "async", "asynchronous", "cujo"], + "homepage": "https://github.com/cujojs/when", + "authors": [ + "Brian Cavalier " + ], + "license": "MIT", + "ignore": [ + "**/.*", + "**/*.md", + "docs", + "benchmark", + "node_modules", + "bower_components", + "test", + "build" + ] +} diff --git a/public/javascripts/bower/when/callbacks.js b/public/javascripts/bower/when/callbacks.js new file mode 100644 index 00000000000..00e414b5156 --- /dev/null +++ b/public/javascripts/bower/when/callbacks.js @@ -0,0 +1,262 @@ +/** @license MIT License (c) copyright 2013-2014 original author or authors */ + +/** + * Collection of helper functions for interacting with 'traditional', + * callback-taking functions using a promise interface. + * + * @author Renato Zannon + * @contributor Brian Cavalier + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + var Promise = when.Promise; + var _liftAll = require('./lib/liftAll'); + var slice = Array.prototype.slice; + + var makeApply = require('./lib/apply'); + var _apply = makeApply(Promise, dispatch); + + return { + lift: lift, + liftAll: liftAll, + apply: apply, + call: call, + promisify: promisify + }; + + /** + * Takes a `traditional` callback-taking function and returns a promise for its + * result, accepting an optional array of arguments (that might be values or + * promises). It assumes that the function takes its callback and errback as + * the last two arguments. The resolution of the promise depends on whether the + * function will call its callback or its errback. + * + * @example + * var domIsLoaded = callbacks.apply($); + * domIsLoaded.then(function() { + * doMyDomStuff(); + * }); + * + * @example + * function existingAjaxyFunction(url, callback, errback) { + * // Complex logic you'd rather not change + * } + * + * var promise = callbacks.apply(existingAjaxyFunction, ["/movies.json"]); + * + * promise.then(function(movies) { + * // Work with movies + * }, function(reason) { + * // Handle error + * }); + * + * @param {function} asyncFunction function to be called + * @param {Array} [extraAsyncArgs] array of arguments to asyncFunction + * @returns {Promise} promise for the callback value of asyncFunction + */ + function apply(asyncFunction, extraAsyncArgs) { + return _apply(asyncFunction, this, extraAsyncArgs || []); + } + + /** + * Apply helper that allows specifying thisArg + * @private + */ + function dispatch(f, thisArg, args, h) { + args.push(alwaysUnary(h.resolve, h), alwaysUnary(h.reject, h)); + tryCatchResolve(f, thisArg, args, h); + } + + function tryCatchResolve(f, thisArg, args, resolver) { + try { + f.apply(thisArg, args); + } catch(e) { + resolver.reject(e); + } + } + + /** + * Works as `callbacks.apply` does, with the difference that the arguments to + * the function are passed individually, instead of as an array. + * + * @example + * function sumInFiveSeconds(a, b, callback) { + * setTimeout(function() { + * callback(a + b); + * }, 5000); + * } + * + * var sumPromise = callbacks.call(sumInFiveSeconds, 5, 10); + * + * // Logs '15' 5 seconds later + * sumPromise.then(console.log); + * + * @param {function} asyncFunction function to be called + * @param {...*} args arguments that will be forwarded to the function + * @returns {Promise} promise for the callback value of asyncFunction + */ + function call(asyncFunction/*, arg1, arg2...*/) { + return _apply(asyncFunction, this, slice.call(arguments, 1)); + } + + /** + * Takes a 'traditional' callback/errback-taking function and returns a function + * that returns a promise instead. The resolution/rejection of the promise + * depends on whether the original function will call its callback or its + * errback. + * + * If additional arguments are passed to the `lift` call, they will be prepended + * on the calls to the original function, much like `Function.prototype.bind`. + * + * The resulting function is also "promise-aware", in the sense that, if given + * promises as arguments, it will wait for their resolution before executing. + * + * @example + * function traditionalAjax(method, url, callback, errback) { + * var xhr = new XMLHttpRequest(); + * xhr.open(method, url); + * + * xhr.onload = callback; + * xhr.onerror = errback; + * + * xhr.send(); + * } + * + * var promiseAjax = callbacks.lift(traditionalAjax); + * promiseAjax("GET", "/movies.json").then(console.log, console.error); + * + * var promiseAjaxGet = callbacks.lift(traditionalAjax, "GET"); + * promiseAjaxGet("/movies.json").then(console.log, console.error); + * + * @param {Function} f traditional async function to be decorated + * @param {...*} [args] arguments to be prepended for the new function @deprecated + * @returns {Function} a promise-returning function + */ + function lift(f/*, args...*/) { + var args = arguments.length > 1 ? slice.call(arguments, 1) : []; + return function() { + return _apply(f, this, args.concat(slice.call(arguments))); + }; + } + + /** + * Lift all the functions/methods on src + * @param {object|function} src source whose functions will be lifted + * @param {function?} combine optional function for customizing the lifting + * process. It is passed dst, the lifted function, and the property name of + * the original function on src. + * @param {(object|function)?} dst option destination host onto which to place lifted + * functions. If not provided, liftAll returns a new object. + * @returns {*} If dst is provided, returns dst with lifted functions as + * properties. If dst not provided, returns a new object with lifted functions. + */ + function liftAll(src, combine, dst) { + return _liftAll(lift, combine, dst, src); + } + + /** + * `promisify` is a version of `lift` that allows fine-grained control over the + * arguments that passed to the underlying function. It is intended to handle + * functions that don't follow the common callback and errback positions. + * + * The control is done by passing an object whose 'callback' and/or 'errback' + * keys, whose values are the corresponding 0-based indexes of the arguments on + * the function. Negative values are interpreted as being relative to the end + * of the arguments array. + * + * If arguments are given on the call to the 'promisified' function, they are + * intermingled with the callback and errback. If a promise is given among them, + * the execution of the function will only occur after its resolution. + * + * @example + * var delay = callbacks.promisify(setTimeout, { + * callback: 0 + * }); + * + * delay(100).then(function() { + * console.log("This happens 100ms afterwards"); + * }); + * + * @example + * function callbackAsLast(errback, followsStandards, callback) { + * if(followsStandards) { + * callback("well done!"); + * } else { + * errback("some programmers just want to watch the world burn"); + * } + * } + * + * var promisified = callbacks.promisify(callbackAsLast, { + * callback: -1, + * errback: 0, + * }); + * + * promisified(true).then(console.log, console.error); + * promisified(false).then(console.log, console.error); + * + * @param {Function} asyncFunction traditional function to be decorated + * @param {object} positions + * @param {number} [positions.callback] index at which asyncFunction expects to + * receive a success callback + * @param {number} [positions.errback] index at which asyncFunction expects to + * receive an error callback + * @returns {function} promisified function that accepts + * + * @deprecated + */ + function promisify(asyncFunction, positions) { + + return function() { + var thisArg = this; + return Promise.all(arguments).then(function(args) { + var p = Promise._defer(); + + var callbackPos, errbackPos; + + if(typeof positions.callback === 'number') { + callbackPos = normalizePosition(args, positions.callback); + } + + if(typeof positions.errback === 'number') { + errbackPos = normalizePosition(args, positions.errback); + } + + if(errbackPos < callbackPos) { + insertCallback(args, errbackPos, p._handler.reject, p._handler); + insertCallback(args, callbackPos, p._handler.resolve, p._handler); + } else { + insertCallback(args, callbackPos, p._handler.resolve, p._handler); + insertCallback(args, errbackPos, p._handler.reject, p._handler); + } + + asyncFunction.apply(thisArg, args); + + return p; + }); + }; + } + + function normalizePosition(args, pos) { + return pos < 0 ? (args.length + pos + 2) : pos; + } + + function insertCallback(args, pos, callback, thisArg) { + if(typeof pos === 'number') { + args.splice(pos, 0, alwaysUnary(callback, thisArg)); + } + } + + function alwaysUnary(fn, thisArg) { + return function() { + if (arguments.length > 1) { + fn.call(thisArg, slice.call(arguments)); + } else { + fn.apply(thisArg, arguments); + } + }; + } +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); diff --git a/public/javascripts/bower/when/cancelable.js b/public/javascripts/bower/when/cancelable.js new file mode 100644 index 00000000000..7a4d08f66b0 --- /dev/null +++ b/public/javascripts/bower/when/cancelable.js @@ -0,0 +1,54 @@ +/** @license MIT License (c) copyright B Cavalier & J Hann */ + +/** + * cancelable.js + * @deprecated + * + * Decorator that makes a deferred "cancelable". It adds a cancel() method that + * will call a special cancel handler function and then reject the deferred. The + * cancel handler can be used to do resource cleanup, or anything else that should + * be done before any other rejection handlers are executed. + * + * Usage: + * + * var cancelableDeferred = cancelable(when.defer(), myCancelHandler); + * + * @author brian@hovercraftstudios.com + */ + +(function(define) { +define(function() { + + /** + * Makes deferred cancelable, adding a cancel() method. + * @deprecated + * + * @param deferred {Deferred} the {@link Deferred} to make cancelable + * @param canceler {Function} cancel handler function to execute when this deferred + * is canceled. This is guaranteed to run before all other rejection handlers. + * The canceler will NOT be executed if the deferred is rejected in the standard + * way, i.e. deferred.reject(). It ONLY executes if the deferred is canceled, + * i.e. deferred.cancel() + * + * @returns deferred, with an added cancel() method. + */ + return function(deferred, canceler) { + // Add a cancel method to the deferred to reject the delegate + // with the special canceled indicator. + deferred.cancel = function() { + try { + deferred.reject(canceler(deferred)); + } catch(e) { + deferred.reject(e); + } + + return deferred.promise; + }; + + return deferred; + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(); }); + + diff --git a/public/javascripts/bower/when/delay.js b/public/javascripts/bower/when/delay.js new file mode 100644 index 00000000000..20d77bf8e66 --- /dev/null +++ b/public/javascripts/bower/when/delay.js @@ -0,0 +1,27 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * delay.js + * + * Helper that returns a promise that resolves after a delay. + * + * @author Brian Cavalier + * @author John Hann + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + + /** + * @deprecated Use when(value).delay(ms) + */ + return function delay(msec, value) { + return when(value).delay(msec); + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + diff --git a/public/javascripts/bower/when/es6-shim/Promise.browserify-es6.js b/public/javascripts/bower/when/es6-shim/Promise.browserify-es6.js new file mode 100644 index 00000000000..751208fe968 --- /dev/null +++ b/public/javascripts/bower/when/es6-shim/Promise.browserify-es6.js @@ -0,0 +1,13 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +/** + * ES6 global Promise shim + */ +var unhandledRejections = require('../lib/decorators/unhandledRejection'); +var PromiseConstructor = unhandledRejections(require('../lib/Promise')); + +module.exports = typeof global != 'undefined' ? (global.Promise = PromiseConstructor) + : typeof self != 'undefined' ? (self.Promise = PromiseConstructor) + : PromiseConstructor; diff --git a/public/javascripts/bower/when/es6-shim/Promise.js b/public/javascripts/bower/when/es6-shim/Promise.js new file mode 100644 index 00000000000..e97f1aa693b --- /dev/null +++ b/public/javascripts/bower/when/es6-shim/Promise.js @@ -0,0 +1,1270 @@ +!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.Promise=e():"undefined"!=typeof global?global.Promise=e():"undefined"!=typeof self&&(self.Promise=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0) { + reported.splice(i, 1); + logInfo('Handled previous rejection [' + r.id + '] ' + format.formatObject(r.value)); + } + } + + function enqueue(f, x) { + tasks.push(f, x); + if(running === null) { + running = setTimer(flush, 0); + } + } + + function flush() { + running = null; + while(tasks.length > 0) { + tasks.shift()(tasks.shift()); + } + } + + return Promise; + }; + + function throwit(e) { + throw e; + } + + function noop() {} + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); + +},{"../env":5,"../format":6}],5:[function(require,module,exports){ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +/*global process,document,setTimeout,clearTimeout,MutationObserver,WebKitMutationObserver*/ +(function(define) { 'use strict'; +define(function(require) { + /*jshint maxcomplexity:6*/ + + // Sniff "best" async scheduling option + // Prefer process.nextTick or MutationObserver, then check for + // setTimeout, and finally vertx, since its the only env that doesn't + // have setTimeout + + var MutationObs; + var capturedSetTimeout = typeof setTimeout !== 'undefined' && setTimeout; + + // Default env + var setTimer = function(f, ms) { return setTimeout(f, ms); }; + var clearTimer = function(t) { return clearTimeout(t); }; + var asap = function (f) { return capturedSetTimeout(f, 0); }; + + // Detect specific env + if (isNode()) { // Node + asap = function (f) { return process.nextTick(f); }; + + } else if (MutationObs = hasMutationObserver()) { // Modern browser + asap = initMutationObserver(MutationObs); + + } else if (!capturedSetTimeout) { // vert.x + var vertxRequire = require; + var vertx = vertxRequire('vertx'); + setTimer = function (f, ms) { return vertx.setTimer(ms, f); }; + clearTimer = vertx.cancelTimer; + asap = vertx.runOnLoop || vertx.runOnContext; + } + + return { + setTimer: setTimer, + clearTimer: clearTimer, + asap: asap + }; + + function isNode () { + return typeof process !== 'undefined' && + Object.prototype.toString.call(process) === '[object process]'; + } + + function hasMutationObserver () { + return (typeof MutationObserver === 'function' && MutationObserver) || + (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver); + } + + function initMutationObserver(MutationObserver) { + var scheduled; + var node = document.createTextNode(''); + var o = new MutationObserver(run); + o.observe(node, { characterData: true }); + + function run() { + var f = scheduled; + scheduled = void 0; + f(); + } + + var i = 0; + return function (f) { + scheduled = f; + node.data = (i ^= 1); + }; + } +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); + +},{}],6:[function(require,module,exports){ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return { + formatError: formatError, + formatObject: formatObject, + tryStringify: tryStringify + }; + + /** + * Format an error into a string. If e is an Error and has a stack property, + * it's returned. Otherwise, e is formatted using formatObject, with a + * warning added about e not being a proper Error. + * @param {*} e + * @returns {String} formatted string, suitable for output to developers + */ + function formatError(e) { + var s = typeof e === 'object' && e !== null && e.stack ? e.stack : formatObject(e); + return e instanceof Error ? s : s + ' (WARNING: non-Error used)'; + } + + /** + * Format an object, detecting "plain" objects and running them through + * JSON.stringify if possible. + * @param {Object} o + * @returns {string} + */ + function formatObject(o) { + var s = String(o); + if(s === '[object Object]' && typeof JSON !== 'undefined') { + s = tryStringify(o, s); + } + return s; + } + + /** + * Try to return the result of JSON.stringify(x). If that fails, return + * defaultValue + * @param {*} x + * @param {*} defaultValue + * @returns {String|*} JSON.stringify(x) or defaultValue + */ + function tryStringify(x, defaultValue) { + try { + return JSON.stringify(x); + } catch(e) { + return defaultValue; + } + } + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); + +},{}],7:[function(require,module,exports){ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function makePromise(environment) { + + var tasks = environment.scheduler; + var emitRejection = initEmitRejection(); + + var objectCreate = Object.create || + function(proto) { + function Child() {} + Child.prototype = proto; + return new Child(); + }; + + /** + * Create a promise whose fate is determined by resolver + * @constructor + * @returns {Promise} promise + * @name Promise + */ + function Promise(resolver, handler) { + this._handler = resolver === Handler ? handler : init(resolver); + } + + /** + * Run the supplied resolver + * @param resolver + * @returns {Pending} + */ + function init(resolver) { + var handler = new Pending(); + + try { + resolver(promiseResolve, promiseReject, promiseNotify); + } catch (e) { + promiseReject(e); + } + + return handler; + + /** + * Transition from pre-resolution state to post-resolution state, notifying + * all listeners of the ultimate fulfillment or rejection + * @param {*} x resolution value + */ + function promiseResolve (x) { + handler.resolve(x); + } + /** + * Reject this promise with reason, which will be used verbatim + * @param {Error|*} reason rejection reason, strongly suggested + * to be an Error type + */ + function promiseReject (reason) { + handler.reject(reason); + } + + /** + * @deprecated + * Issue a progress event, notifying all progress listeners + * @param {*} x progress event payload to pass to all listeners + */ + function promiseNotify (x) { + handler.notify(x); + } + } + + // Creation + + Promise.resolve = resolve; + Promise.reject = reject; + Promise.never = never; + + Promise._defer = defer; + Promise._handler = getHandler; + + /** + * Returns a trusted promise. If x is already a trusted promise, it is + * returned, otherwise returns a new trusted Promise which follows x. + * @param {*} x + * @return {Promise} promise + */ + function resolve(x) { + return isPromise(x) ? x + : new Promise(Handler, new Async(getHandler(x))); + } + + /** + * Return a reject promise with x as its reason (x is used verbatim) + * @param {*} x + * @returns {Promise} rejected promise + */ + function reject(x) { + return new Promise(Handler, new Async(new Rejected(x))); + } + + /** + * Return a promise that remains pending forever + * @returns {Promise} forever-pending promise. + */ + function never() { + return foreverPendingPromise; // Should be frozen + } + + /** + * Creates an internal {promise, resolver} pair + * @private + * @returns {Promise} + */ + function defer() { + return new Promise(Handler, new Pending()); + } + + // Transformation and flow control + + /** + * Transform this promise's fulfillment value, returning a new Promise + * for the transformed result. If the promise cannot be fulfilled, onRejected + * is called with the reason. onProgress *may* be called with updates toward + * this promise's fulfillment. + * @param {function=} onFulfilled fulfillment handler + * @param {function=} onRejected rejection handler + * @param {function=} onProgress @deprecated progress handler + * @return {Promise} new promise + */ + Promise.prototype.then = function(onFulfilled, onRejected, onProgress) { + var parent = this._handler; + var state = parent.join().state(); + + if ((typeof onFulfilled !== 'function' && state > 0) || + (typeof onRejected !== 'function' && state < 0)) { + // Short circuit: value will not change, simply share handler + return new this.constructor(Handler, parent); + } + + var p = this._beget(); + var child = p._handler; + + parent.chain(child, parent.receiver, onFulfilled, onRejected, onProgress); + + return p; + }; + + /** + * If this promise cannot be fulfilled due to an error, call onRejected to + * handle the error. Shortcut for .then(undefined, onRejected) + * @param {function?} onRejected + * @return {Promise} + */ + Promise.prototype['catch'] = function(onRejected) { + return this.then(void 0, onRejected); + }; + + /** + * Creates a new, pending promise of the same type as this promise + * @private + * @returns {Promise} + */ + Promise.prototype._beget = function() { + return begetFrom(this._handler, this.constructor); + }; + + function begetFrom(parent, Promise) { + var child = new Pending(parent.receiver, parent.join().context); + return new Promise(Handler, child); + } + + // Array combinators + + Promise.all = all; + Promise.race = race; + Promise._traverse = traverse; + + /** + * Return a promise that will fulfill when all promises in the + * input array have fulfilled, or will reject when one of the + * promises rejects. + * @param {array} promises array of promises + * @returns {Promise} promise for array of fulfillment values + */ + function all(promises) { + return traverseWith(snd, null, promises); + } + + /** + * Array> -> Promise> + * @private + * @param {function} f function to apply to each promise's value + * @param {Array} promises array of promises + * @returns {Promise} promise for transformed values + */ + function traverse(f, promises) { + return traverseWith(tryCatch2, f, promises); + } + + function traverseWith(tryMap, f, promises) { + var handler = typeof f === 'function' ? mapAt : settleAt; + + var resolver = new Pending(); + var pending = promises.length >>> 0; + var results = new Array(pending); + + for (var i = 0, x; i < promises.length && !resolver.resolved; ++i) { + x = promises[i]; + + if (x === void 0 && !(i in promises)) { + --pending; + continue; + } + + traverseAt(promises, handler, i, x, resolver); + } + + if(pending === 0) { + resolver.become(new Fulfilled(results)); + } + + return new Promise(Handler, resolver); + + function mapAt(i, x, resolver) { + if(!resolver.resolved) { + traverseAt(promises, settleAt, i, tryMap(f, x, i), resolver); + } + } + + function settleAt(i, x, resolver) { + results[i] = x; + if(--pending === 0) { + resolver.become(new Fulfilled(results)); + } + } + } + + function traverseAt(promises, handler, i, x, resolver) { + if (maybeThenable(x)) { + var h = getHandlerMaybeThenable(x); + var s = h.state(); + + if (s === 0) { + h.fold(handler, i, void 0, resolver); + } else if (s > 0) { + handler(i, h.value, resolver); + } else { + resolver.become(h); + visitRemaining(promises, i+1, h); + } + } else { + handler(i, x, resolver); + } + } + + Promise._visitRemaining = visitRemaining; + function visitRemaining(promises, start, handler) { + for(var i=start; i 1 ? slice.call(arguments, 1) : []; + return function() { + return _apply(f, this, args.concat(slice.call(arguments))); + }; + } + + /** + * Lift all the functions/methods on src + * @param {object|function} src source whose functions will be lifted + * @param {function?} combine optional function for customizing the lifting + * process. It is passed dst, the lifted function, and the property name of + * the original function on src. + * @param {(object|function)?} dst option destination host onto which to place lifted + * functions. If not provided, liftAll returns a new object. + * @returns {*} If dst is provided, returns dst with lifted functions as + * properties. If dst not provided, returns a new object with lifted functions. + */ + function liftAll(src, combine, dst) { + return _liftAll(lift, combine, dst, src); + } + + /** + * Composes multiple functions by piping their return values. It is + * transparent to whether the functions return 'regular' values or promises: + * the piped argument is always a resolved value. If one of the functions + * throws or returns a rejected promise, the composed promise will be also + * rejected. + * + * The arguments (or promises to arguments) given to the returned function (if + * any), are passed directly to the first function on the 'pipeline'. + * @param {Function} f the function to which the arguments will be passed + * @param {...Function} [funcs] functions that will be composed, in order + * @returns {Function} a promise-returning composition of the functions + */ + function compose(f /*, funcs... */) { + var funcs = slice.call(arguments, 1); + + return function() { + var thisArg = this; + var args = slice.call(arguments); + var firstPromise = attempt.apply(thisArg, [f].concat(args)); + + return when.reduce(funcs, function(arg, func) { + return func.call(thisArg, arg); + }, firstPromise); + }; + } +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + diff --git a/public/javascripts/bower/when/generator.js b/public/javascripts/bower/when/generator.js new file mode 100644 index 00000000000..e0381f06e08 --- /dev/null +++ b/public/javascripts/bower/when/generator.js @@ -0,0 +1,105 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var when = require('./when'); + var slice = Array.prototype.slice; + var Promise = when.Promise; + var reject = Promise.reject; + + /** + * Lift a generator to create a function that can suspend and + * resume using the `yield` keyword to await promises. + * @param {function} generator + * @return {function} + */ + function lift(generator) { + return function() { + return run(generator, this, arguments); + }; + } + + /** + * Immediately call a generator as a promise-aware coroutine + * that can suspend and resume using the `yield` keyword to + * await promises. Additional arguments after the first will + * be passed through to the generator. + * @param {function} generator + * @returns {Promise} promise for the ultimate value returned + * from the generator. + */ + function call(generator /*x, y, z...*/) { + /*jshint validthis:true*/ + return run(generator, this, slice.call(arguments, 1)); + } + + /** + * Immediately apply a generator, with the supplied args array, + * as a promise-aware coroutine that can suspend and resume + * using the `yield` keyword to await promises. + * @param {function} generator + * @param {Array} args arguments with which to initialize the generator + * @returns {Promise} promise for the ultimate value returned + * from the generator. + */ + function apply(generator, args) { + /*jshint validthis:true*/ + return run(generator, this, args || []); + } + + /** + * Helper to initiate the provided generator as a coroutine + * @returns {*} + */ + function run(generator, thisArg, args) { + return runNext(void 0, generator.apply(thisArg, args)); + } + + function runNext(x, iterator) { + try { + return handle(iterator.next(x), iterator); + } catch(e) { + return reject(e); + } + } + + function next(x) { + /*jshint validthis:true*/ + return runNext(x, this); + } + + function error(e) { + /*jshint validthis:true*/ + try { + return handle(this.throw(e), this); + } catch(e) { + return reject(e); + } + } + + function handle(result, iterator) { + if(result.done) { + return result.value; + } + + var h = Promise._handler(result.value); + if(h.state() > 0) { + return runNext(h.value, iterator); + } + + var p = Promise._defer(); + h.chain(p._handler, iterator, next, error); + return p; + } + + return { + lift: lift, + call: call, + apply: apply + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/guard.js b/public/javascripts/bower/when/guard.js new file mode 100644 index 00000000000..38924cae06c --- /dev/null +++ b/public/javascripts/bower/when/guard.js @@ -0,0 +1,72 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * Generalized promise concurrency guard + * Adapted from original concept by Sakari Jokinen (Rocket Pack, Ltd.) + * + * @author Brian Cavalier + * @author John Hann + * @contributor Sakari Jokinen + */ +(function(define) { +define(function(require) { + + var when = require('./when'); + var slice = Array.prototype.slice; + + guard.n = n; + + return guard; + + /** + * Creates a guarded version of f that can only be entered when the supplied + * condition allows. + * @param {function} condition represents a critical section that may only + * be entered when allowed by the condition + * @param {function} f function to guard + * @returns {function} guarded version of f + */ + function guard(condition, f) { + return function() { + var args = slice.call(arguments); + + return when(condition()).withThis(this).then(function(exit) { + return when(f.apply(this, args))['finally'](exit); + }); + }; + } + + /** + * Creates a condition that allows only n simultaneous executions + * of a guarded function + * @param {number} allowed number of allowed simultaneous executions + * @returns {function} condition function which returns a promise that + * fulfills when the critical section may be entered. The fulfillment + * value is a function ("notifyExit") that must be called when the critical + * section has been exited. + */ + function n(allowed) { + var count = 0; + var waiting = []; + + return function enter() { + return when.promise(function(resolve) { + if(count < allowed) { + resolve(exit); + } else { + waiting.push(resolve); + } + count += 1; + }); + }; + + function exit() { + count = Math.max(count - 1, 0); + if(waiting.length > 0) { + waiting.shift()(exit); + } + } + } + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/keys.js b/public/javascripts/bower/when/keys.js new file mode 100644 index 00000000000..9bca604024b --- /dev/null +++ b/public/javascripts/bower/when/keys.js @@ -0,0 +1,80 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * Licensed under the MIT License at: + * http://www.opensource.org/licenses/mit-license.php + * + * @author Brian Cavalier + * @author John Hann + */ +(function(define) { 'use strict'; +define(function(require) { + + var when = require('./when'); + var Promise = when.Promise; + var toPromise = when.resolve; + + return { + all: when.lift(all), + map: map + }; + + /** + * Resolve all the key-value pairs in the supplied object or promise + * for an object. + * @param {Promise|object} object or promise for object whose key-value pairs + * will be resolved + * @returns {Promise} promise for an object with the fully resolved key-value pairs + */ + function all(object) { + var p = Promise._defer(); + var resolver = Promise._handler(p); + + var results = {}; + var keys = Object.keys(object); + var pending = keys.length; + + for(var i=0, k; i>>0; + + var pending = l; + var errors = []; + + for (var h, x, i = 0; i < l; ++i) { + x = promises[i]; + if(x === void 0 && !(i in promises)) { + --pending; + continue; + } + + h = Promise._handler(x); + if(h.state() > 0) { + resolver.become(h); + Promise._visitRemaining(promises, i, h); + break; + } else { + h.visit(resolver, handleFulfill, handleReject); + } + } + + if(pending === 0) { + resolver.reject(new RangeError('any(): array must not be empty')); + } + + return p; + + function handleFulfill(x) { + /*jshint validthis:true*/ + errors = null; + this.resolve(x); // this === resolver + } + + function handleReject(e) { + /*jshint validthis:true*/ + if(this.resolved) { // this === resolver + return; + } + + errors.push(e); + if(--pending === 0) { + this.reject(errors); + } + } + } + + /** + * N-winner competitive race + * Return a promise that will fulfill when n input promises have + * fulfilled, or will reject when it becomes impossible for n + * input promises to fulfill (ie when promises.length - n + 1 + * have rejected) + * @param {array} promises + * @param {number} n + * @returns {Promise} promise for the earliest n fulfillment values + * + * @deprecated + */ + function some(promises, n) { + /*jshint maxcomplexity:7*/ + var p = Promise._defer(); + var resolver = p._handler; + + var results = []; + var errors = []; + + var l = promises.length>>>0; + var nFulfill = 0; + var nReject; + var x, i; // reused in both for() loops + + // First pass: count actual array items + for(i=0; i nFulfill) { + resolver.reject(new RangeError('some(): array must contain at least ' + + n + ' item(s), but had ' + nFulfill)); + } else if(nFulfill === 0) { + resolver.resolve(results); + } + + // Second pass: observe each array item, make progress toward goals + for(i=0; i 2 ? ar.call(promises, liftCombine(f), arguments[2]) + : ar.call(promises, liftCombine(f)); + } + + /** + * Traditional reduce function, similar to `Array.prototype.reduceRight()`, but + * input may contain promises and/or values, and reduceFunc + * may return either a value or a promise, *and* initialValue may + * be a promise for the starting value. + * @param {Array|Promise} promises array or promise for an array of anything, + * may contain a mix of promises and values. + * @param {function(accumulated:*, x:*, index:Number):*} f reduce function + * @returns {Promise} that will resolve to the final reduced value + */ + function reduceRight(promises, f /*, initialValue */) { + return arguments.length > 2 ? arr.call(promises, liftCombine(f), arguments[2]) + : arr.call(promises, liftCombine(f)); + } + + function liftCombine(f) { + return function(z, x, i) { + return applyFold(f, void 0, [z,x,i]); + }; + } + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/lib/decorators/flow.js b/public/javascripts/bower/when/lib/decorators/flow.js new file mode 100644 index 00000000000..635e4f49376 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/flow.js @@ -0,0 +1,160 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function flow(Promise) { + + var resolve = Promise.resolve; + var reject = Promise.reject; + var origCatch = Promise.prototype['catch']; + + /** + * Handle the ultimate fulfillment value or rejection reason, and assume + * responsibility for all errors. If an error propagates out of result + * or handleFatalError, it will be rethrown to the host, resulting in a + * loud stack track on most platforms and a crash on some. + * @param {function?} onResult + * @param {function?} onError + * @returns {undefined} + */ + Promise.prototype.done = function(onResult, onError) { + this._handler.visit(this._handler.receiver, onResult, onError); + }; + + /** + * Add Error-type and predicate matching to catch. Examples: + * promise.catch(TypeError, handleTypeError) + * .catch(predicate, handleMatchedErrors) + * .catch(handleRemainingErrors) + * @param onRejected + * @returns {*} + */ + Promise.prototype['catch'] = Promise.prototype.otherwise = function(onRejected) { + if (arguments.length < 2) { + return origCatch.call(this, onRejected); + } + + if(typeof onRejected !== 'function') { + return this.ensure(rejectInvalidPredicate); + } + + return origCatch.call(this, createCatchFilter(arguments[1], onRejected)); + }; + + /** + * Wraps the provided catch handler, so that it will only be called + * if the predicate evaluates truthy + * @param {?function} handler + * @param {function} predicate + * @returns {function} conditional catch handler + */ + function createCatchFilter(handler, predicate) { + return function(e) { + return evaluatePredicate(e, predicate) + ? handler.call(this, e) + : reject(e); + }; + } + + /** + * Ensures that onFulfilledOrRejected will be called regardless of whether + * this promise is fulfilled or rejected. onFulfilledOrRejected WILL NOT + * receive the promises' value or reason. Any returned value will be disregarded. + * onFulfilledOrRejected may throw or return a rejected promise to signal + * an additional error. + * @param {function} handler handler to be called regardless of + * fulfillment or rejection + * @returns {Promise} + */ + Promise.prototype['finally'] = Promise.prototype.ensure = function(handler) { + if(typeof handler !== 'function') { + return this; + } + + return this.then(function(x) { + return runSideEffect(handler, this, identity, x); + }, function(e) { + return runSideEffect(handler, this, reject, e); + }); + }; + + function runSideEffect (handler, thisArg, propagate, value) { + var result = handler.call(thisArg); + return maybeThenable(result) + ? propagateValue(result, propagate, value) + : propagate(value); + } + + function propagateValue (result, propagate, x) { + return resolve(result).then(function () { + return propagate(x); + }); + } + + /** + * Recover from a failure by returning a defaultValue. If defaultValue + * is a promise, it's fulfillment value will be used. If defaultValue is + * a promise that rejects, the returned promise will reject with the + * same reason. + * @param {*} defaultValue + * @returns {Promise} new promise + */ + Promise.prototype['else'] = Promise.prototype.orElse = function(defaultValue) { + return this.then(void 0, function() { + return defaultValue; + }); + }; + + /** + * Shortcut for .then(function() { return value; }) + * @param {*} value + * @return {Promise} a promise that: + * - is fulfilled if value is not a promise, or + * - if value is a promise, will fulfill with its value, or reject + * with its reason. + */ + Promise.prototype['yield'] = function(value) { + return this.then(function() { + return value; + }); + }; + + /** + * Runs a side effect when this promise fulfills, without changing the + * fulfillment value. + * @param {function} onFulfilledSideEffect + * @returns {Promise} + */ + Promise.prototype.tap = function(onFulfilledSideEffect) { + return this.then(onFulfilledSideEffect)['yield'](this); + }; + + return Promise; + }; + + function rejectInvalidPredicate() { + throw new TypeError('catch predicate must be a function'); + } + + function evaluatePredicate(e, predicate) { + return isError(predicate) ? e instanceof predicate : predicate(e); + } + + function isError(predicate) { + return predicate === Error + || (predicate != null && predicate.prototype instanceof Error); + } + + function maybeThenable(x) { + return (typeof x === 'object' || typeof x === 'function') && x !== null; + } + + function identity(x) { + return x; + } + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/lib/decorators/fold.js b/public/javascripts/bower/when/lib/decorators/fold.js new file mode 100644 index 00000000000..c7f027aa4d5 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/fold.js @@ -0,0 +1,27 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ +/** @author Jeff Escalante */ + +(function(define) { 'use strict'; +define(function() { + + return function fold(Promise) { + + Promise.prototype.fold = function(f, z) { + var promise = this._beget(); + + this._handler.fold(function(z, x, to) { + Promise._handler(z).fold(function(x, z, to) { + to.resolve(f.call(this, z, x)); + }, x, this, to); + }, z, promise._handler.receiver, promise._handler); + + return promise; + }; + + return Promise; + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/lib/decorators/inspect.js b/public/javascripts/bower/when/lib/decorators/inspect.js new file mode 100644 index 00000000000..95b87157dd6 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/inspect.js @@ -0,0 +1,20 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var inspect = require('../state').inspect; + + return function inspection(Promise) { + + Promise.prototype.inspect = function() { + return inspect(Promise._handler(this)); + }; + + return Promise; + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/lib/decorators/iterate.js b/public/javascripts/bower/when/lib/decorators/iterate.js new file mode 100644 index 00000000000..3ea70b3c157 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/iterate.js @@ -0,0 +1,65 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function generate(Promise) { + + var resolve = Promise.resolve; + + Promise.iterate = iterate; + Promise.unfold = unfold; + + return Promise; + + /** + * @deprecated Use github.com/cujojs/most streams and most.iterate + * Generate a (potentially infinite) stream of promised values: + * x, f(x), f(f(x)), etc. until condition(x) returns true + * @param {function} f function to generate a new x from the previous x + * @param {function} condition function that, given the current x, returns + * truthy when the iterate should stop + * @param {function} handler function to handle the value produced by f + * @param {*|Promise} x starting value, may be a promise + * @return {Promise} the result of the last call to f before + * condition returns true + */ + function iterate(f, condition, handler, x) { + return unfold(function(x) { + return [x, f(x)]; + }, condition, handler, x); + } + + /** + * @deprecated Use github.com/cujojs/most streams and most.unfold + * Generate a (potentially infinite) stream of promised values + * by applying handler(generator(seed)) iteratively until + * condition(seed) returns true. + * @param {function} unspool function that generates a [value, newSeed] + * given a seed. + * @param {function} condition function that, given the current seed, returns + * truthy when the unfold should stop + * @param {function} handler function to handle the value produced by unspool + * @param x {*|Promise} starting value, may be a promise + * @return {Promise} the result of the last value produced by unspool before + * condition returns true + */ + function unfold(unspool, condition, handler, x) { + return resolve(x).then(function(seed) { + return resolve(condition(seed)).then(function(done) { + return done ? seed : resolve(unspool(seed)).spread(next); + }); + }); + + function next(item, newSeed) { + return resolve(handler(item)).then(function() { + return unfold(unspool, condition, handler, newSeed); + }); + } + } + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/lib/decorators/progress.js b/public/javascripts/bower/when/lib/decorators/progress.js new file mode 100644 index 00000000000..d45e3d0d58f --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/progress.js @@ -0,0 +1,24 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function progress(Promise) { + + /** + * @deprecated + * Register a progress handler for this promise + * @param {function} onProgress + * @returns {Promise} + */ + Promise.prototype.progress = function(onProgress) { + return this.then(void 0, void 0, onProgress); + }; + + return Promise; + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/lib/decorators/timed.js b/public/javascripts/bower/when/lib/decorators/timed.js new file mode 100644 index 00000000000..cd82ed436c8 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/timed.js @@ -0,0 +1,78 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var env = require('../env'); + var TimeoutError = require('../TimeoutError'); + + function setTimeout(f, ms, x, y) { + return env.setTimer(function() { + f(x, y, ms); + }, ms); + } + + return function timed(Promise) { + /** + * Return a new promise whose fulfillment value is revealed only + * after ms milliseconds + * @param {number} ms milliseconds + * @returns {Promise} + */ + Promise.prototype.delay = function(ms) { + var p = this._beget(); + this._handler.fold(handleDelay, ms, void 0, p._handler); + return p; + }; + + function handleDelay(ms, x, h) { + setTimeout(resolveDelay, ms, x, h); + } + + function resolveDelay(x, h) { + h.resolve(x); + } + + /** + * Return a new promise that rejects after ms milliseconds unless + * this promise fulfills earlier, in which case the returned promise + * fulfills with the same value. + * @param {number} ms milliseconds + * @param {Error|*=} reason optional rejection reason to use, defaults + * to a TimeoutError if not provided + * @returns {Promise} + */ + Promise.prototype.timeout = function(ms, reason) { + var p = this._beget(); + var h = p._handler; + + var t = setTimeout(onTimeout, ms, reason, p._handler); + + this._handler.visit(h, + function onFulfill(x) { + env.clearTimer(t); + this.resolve(x); // this = h + }, + function onReject(x) { + env.clearTimer(t); + this.reject(x); // this = h + }, + h.notify); + + return p; + }; + + function onTimeout(reason, h, ms) { + var e = typeof reason === 'undefined' + ? new TimeoutError('timed out after ' + ms + 'ms') + : reason; + h.reject(e); + } + + return Promise; + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/lib/decorators/unhandledRejection.js b/public/javascripts/bower/when/lib/decorators/unhandledRejection.js new file mode 100644 index 00000000000..fb966f76274 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/unhandledRejection.js @@ -0,0 +1,86 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var setTimer = require('../env').setTimer; + var format = require('../format'); + + return function unhandledRejection(Promise) { + + var logError = noop; + var logInfo = noop; + var localConsole; + + if(typeof console !== 'undefined') { + // Alias console to prevent things like uglify's drop_console option from + // removing console.log/error. Unhandled rejections fall into the same + // category as uncaught exceptions, and build tools shouldn't silence them. + localConsole = console; + logError = typeof localConsole.error !== 'undefined' + ? function (e) { localConsole.error(e); } + : function (e) { localConsole.log(e); }; + + logInfo = typeof localConsole.info !== 'undefined' + ? function (e) { localConsole.info(e); } + : function (e) { localConsole.log(e); }; + } + + Promise.onPotentiallyUnhandledRejection = function(rejection) { + enqueue(report, rejection); + }; + + Promise.onPotentiallyUnhandledRejectionHandled = function(rejection) { + enqueue(unreport, rejection); + }; + + Promise.onFatalRejection = function(rejection) { + enqueue(throwit, rejection.value); + }; + + var tasks = []; + var reported = []; + var running = null; + + function report(r) { + if(!r.handled) { + reported.push(r); + logError('Potentially unhandled rejection [' + r.id + '] ' + format.formatError(r.value)); + } + } + + function unreport(r) { + var i = reported.indexOf(r); + if(i >= 0) { + reported.splice(i, 1); + logInfo('Handled previous rejection [' + r.id + '] ' + format.formatObject(r.value)); + } + } + + function enqueue(f, x) { + tasks.push(f, x); + if(running === null) { + running = setTimer(flush, 0); + } + } + + function flush() { + running = null; + while(tasks.length > 0) { + tasks.shift()(tasks.shift()); + } + } + + return Promise; + }; + + function throwit(e) { + throw e; + } + + function noop() {} + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/lib/decorators/with.js b/public/javascripts/bower/when/lib/decorators/with.js new file mode 100644 index 00000000000..a0f05f0b081 --- /dev/null +++ b/public/javascripts/bower/when/lib/decorators/with.js @@ -0,0 +1,38 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function addWith(Promise) { + /** + * Returns a promise whose handlers will be called with `this` set to + * the supplied receiver. Subsequent promises derived from the + * returned promise will also have their handlers called with receiver + * as `this`. Calling `with` with undefined or no arguments will return + * a promise whose handlers will again be called in the usual Promises/A+ + * way (no `this`) thus safely undoing any previous `with` in the + * promise chain. + * + * WARNING: Promises returned from `with`/`withThis` are NOT Promises/A+ + * compliant, specifically violating 2.2.5 (http://promisesaplus.com/#point-41) + * + * @param {object} receiver `this` value for all handlers attached to + * the returned promise. + * @returns {Promise} + */ + Promise.prototype['with'] = Promise.prototype.withThis = function(receiver) { + var p = this._beget(); + var child = p._handler; + child.receiver = receiver; + this._handler.chain(child, receiver); + return p; + }; + + return Promise; + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); + diff --git a/public/javascripts/bower/when/lib/env.js b/public/javascripts/bower/when/lib/env.js new file mode 100644 index 00000000000..b5e1af7f84e --- /dev/null +++ b/public/javascripts/bower/when/lib/env.js @@ -0,0 +1,73 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +/*global process,document,setTimeout,clearTimeout,MutationObserver,WebKitMutationObserver*/ +(function(define) { 'use strict'; +define(function(require) { + /*jshint maxcomplexity:6*/ + + // Sniff "best" async scheduling option + // Prefer process.nextTick or MutationObserver, then check for + // setTimeout, and finally vertx, since its the only env that doesn't + // have setTimeout + + var MutationObs; + var capturedSetTimeout = typeof setTimeout !== 'undefined' && setTimeout; + + // Default env + var setTimer = function(f, ms) { return setTimeout(f, ms); }; + var clearTimer = function(t) { return clearTimeout(t); }; + var asap = function (f) { return capturedSetTimeout(f, 0); }; + + // Detect specific env + if (isNode()) { // Node + asap = function (f) { return process.nextTick(f); }; + + } else if (MutationObs = hasMutationObserver()) { // Modern browser + asap = initMutationObserver(MutationObs); + + } else if (!capturedSetTimeout) { // vert.x + var vertxRequire = require; + var vertx = vertxRequire('vertx'); + setTimer = function (f, ms) { return vertx.setTimer(ms, f); }; + clearTimer = vertx.cancelTimer; + asap = vertx.runOnLoop || vertx.runOnContext; + } + + return { + setTimer: setTimer, + clearTimer: clearTimer, + asap: asap + }; + + function isNode () { + return typeof process !== 'undefined' && + Object.prototype.toString.call(process) === '[object process]'; + } + + function hasMutationObserver () { + return (typeof MutationObserver === 'function' && MutationObserver) || + (typeof WebKitMutationObserver === 'function' && WebKitMutationObserver); + } + + function initMutationObserver(MutationObserver) { + var scheduled; + var node = document.createTextNode(''); + var o = new MutationObserver(run); + o.observe(node, { characterData: true }); + + function run() { + var f = scheduled; + scheduled = void 0; + f(); + } + + var i = 0; + return function (f) { + scheduled = f; + node.data = (i ^= 1); + }; + } +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/lib/format.js b/public/javascripts/bower/when/lib/format.js new file mode 100644 index 00000000000..6f93cd46418 --- /dev/null +++ b/public/javascripts/bower/when/lib/format.js @@ -0,0 +1,56 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return { + formatError: formatError, + formatObject: formatObject, + tryStringify: tryStringify + }; + + /** + * Format an error into a string. If e is an Error and has a stack property, + * it's returned. Otherwise, e is formatted using formatObject, with a + * warning added about e not being a proper Error. + * @param {*} e + * @returns {String} formatted string, suitable for output to developers + */ + function formatError(e) { + var s = typeof e === 'object' && e !== null && e.stack ? e.stack : formatObject(e); + return e instanceof Error ? s : s + ' (WARNING: non-Error used)'; + } + + /** + * Format an object, detecting "plain" objects and running them through + * JSON.stringify if possible. + * @param {Object} o + * @returns {string} + */ + function formatObject(o) { + var s = String(o); + if(s === '[object Object]' && typeof JSON !== 'undefined') { + s = tryStringify(o, s); + } + return s; + } + + /** + * Try to return the result of JSON.stringify(x). If that fails, return + * defaultValue + * @param {*} x + * @param {*} defaultValue + * @returns {String|*} JSON.stringify(x) or defaultValue + */ + function tryStringify(x, defaultValue) { + try { + return JSON.stringify(x); + } catch(e) { + return defaultValue; + } + } + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/lib/liftAll.js b/public/javascripts/bower/when/lib/liftAll.js new file mode 100644 index 00000000000..af4dd4f7a2a --- /dev/null +++ b/public/javascripts/bower/when/lib/liftAll.js @@ -0,0 +1,28 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function liftAll(liftOne, combine, dst, src) { + if(typeof combine === 'undefined') { + combine = defaultCombine; + } + + return Object.keys(src).reduce(function(dst, key) { + var f = src[key]; + return typeof f === 'function' ? combine(dst, liftOne(f), key) : dst; + }, typeof dst === 'undefined' ? defaultDst(src) : dst); + }; + + function defaultCombine(o, f, k) { + o[k] = f; + return o; + } + + function defaultDst(src) { + return typeof src === 'function' ? src.bind() : Object.create(src); + } +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/lib/makePromise.js b/public/javascripts/bower/when/lib/makePromise.js new file mode 100644 index 00000000000..76f25973729 --- /dev/null +++ b/public/javascripts/bower/when/lib/makePromise.js @@ -0,0 +1,927 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + return function makePromise(environment) { + + var tasks = environment.scheduler; + var emitRejection = initEmitRejection(); + + var objectCreate = Object.create || + function(proto) { + function Child() {} + Child.prototype = proto; + return new Child(); + }; + + /** + * Create a promise whose fate is determined by resolver + * @constructor + * @returns {Promise} promise + * @name Promise + */ + function Promise(resolver, handler) { + this._handler = resolver === Handler ? handler : init(resolver); + } + + /** + * Run the supplied resolver + * @param resolver + * @returns {Pending} + */ + function init(resolver) { + var handler = new Pending(); + + try { + resolver(promiseResolve, promiseReject, promiseNotify); + } catch (e) { + promiseReject(e); + } + + return handler; + + /** + * Transition from pre-resolution state to post-resolution state, notifying + * all listeners of the ultimate fulfillment or rejection + * @param {*} x resolution value + */ + function promiseResolve (x) { + handler.resolve(x); + } + /** + * Reject this promise with reason, which will be used verbatim + * @param {Error|*} reason rejection reason, strongly suggested + * to be an Error type + */ + function promiseReject (reason) { + handler.reject(reason); + } + + /** + * @deprecated + * Issue a progress event, notifying all progress listeners + * @param {*} x progress event payload to pass to all listeners + */ + function promiseNotify (x) { + handler.notify(x); + } + } + + // Creation + + Promise.resolve = resolve; + Promise.reject = reject; + Promise.never = never; + + Promise._defer = defer; + Promise._handler = getHandler; + + /** + * Returns a trusted promise. If x is already a trusted promise, it is + * returned, otherwise returns a new trusted Promise which follows x. + * @param {*} x + * @return {Promise} promise + */ + function resolve(x) { + return isPromise(x) ? x + : new Promise(Handler, new Async(getHandler(x))); + } + + /** + * Return a reject promise with x as its reason (x is used verbatim) + * @param {*} x + * @returns {Promise} rejected promise + */ + function reject(x) { + return new Promise(Handler, new Async(new Rejected(x))); + } + + /** + * Return a promise that remains pending forever + * @returns {Promise} forever-pending promise. + */ + function never() { + return foreverPendingPromise; // Should be frozen + } + + /** + * Creates an internal {promise, resolver} pair + * @private + * @returns {Promise} + */ + function defer() { + return new Promise(Handler, new Pending()); + } + + // Transformation and flow control + + /** + * Transform this promise's fulfillment value, returning a new Promise + * for the transformed result. If the promise cannot be fulfilled, onRejected + * is called with the reason. onProgress *may* be called with updates toward + * this promise's fulfillment. + * @param {function=} onFulfilled fulfillment handler + * @param {function=} onRejected rejection handler + * @param {function=} onProgress @deprecated progress handler + * @return {Promise} new promise + */ + Promise.prototype.then = function(onFulfilled, onRejected, onProgress) { + var parent = this._handler; + var state = parent.join().state(); + + if ((typeof onFulfilled !== 'function' && state > 0) || + (typeof onRejected !== 'function' && state < 0)) { + // Short circuit: value will not change, simply share handler + return new this.constructor(Handler, parent); + } + + var p = this._beget(); + var child = p._handler; + + parent.chain(child, parent.receiver, onFulfilled, onRejected, onProgress); + + return p; + }; + + /** + * If this promise cannot be fulfilled due to an error, call onRejected to + * handle the error. Shortcut for .then(undefined, onRejected) + * @param {function?} onRejected + * @return {Promise} + */ + Promise.prototype['catch'] = function(onRejected) { + return this.then(void 0, onRejected); + }; + + /** + * Creates a new, pending promise of the same type as this promise + * @private + * @returns {Promise} + */ + Promise.prototype._beget = function() { + return begetFrom(this._handler, this.constructor); + }; + + function begetFrom(parent, Promise) { + var child = new Pending(parent.receiver, parent.join().context); + return new Promise(Handler, child); + } + + // Array combinators + + Promise.all = all; + Promise.race = race; + Promise._traverse = traverse; + + /** + * Return a promise that will fulfill when all promises in the + * input array have fulfilled, or will reject when one of the + * promises rejects. + * @param {array} promises array of promises + * @returns {Promise} promise for array of fulfillment values + */ + function all(promises) { + return traverseWith(snd, null, promises); + } + + /** + * Array> -> Promise> + * @private + * @param {function} f function to apply to each promise's value + * @param {Array} promises array of promises + * @returns {Promise} promise for transformed values + */ + function traverse(f, promises) { + return traverseWith(tryCatch2, f, promises); + } + + function traverseWith(tryMap, f, promises) { + var handler = typeof f === 'function' ? mapAt : settleAt; + + var resolver = new Pending(); + var pending = promises.length >>> 0; + var results = new Array(pending); + + for (var i = 0, x; i < promises.length && !resolver.resolved; ++i) { + x = promises[i]; + + if (x === void 0 && !(i in promises)) { + --pending; + continue; + } + + traverseAt(promises, handler, i, x, resolver); + } + + if(pending === 0) { + resolver.become(new Fulfilled(results)); + } + + return new Promise(Handler, resolver); + + function mapAt(i, x, resolver) { + if(!resolver.resolved) { + traverseAt(promises, settleAt, i, tryMap(f, x, i), resolver); + } + } + + function settleAt(i, x, resolver) { + results[i] = x; + if(--pending === 0) { + resolver.become(new Fulfilled(results)); + } + } + } + + function traverseAt(promises, handler, i, x, resolver) { + if (maybeThenable(x)) { + var h = getHandlerMaybeThenable(x); + var s = h.state(); + + if (s === 0) { + h.fold(handler, i, void 0, resolver); + } else if (s > 0) { + handler(i, h.value, resolver); + } else { + resolver.become(h); + visitRemaining(promises, i+1, h); + } + } else { + handler(i, x, resolver); + } + } + + Promise._visitRemaining = visitRemaining; + function visitRemaining(promises, start, handler) { + for(var i=start; i 0 ? toFulfilledState(handler.value) + : toRejectedState(handler.value); + } + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/monitor.js b/public/javascripts/bower/when/monitor.js new file mode 100644 index 00000000000..89790882c9a --- /dev/null +++ b/public/javascripts/bower/when/monitor.js @@ -0,0 +1,17 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var PromiseMonitor = require('./monitor/PromiseMonitor'); + var ConsoleReporter = require('./monitor/ConsoleReporter'); + + var promiseMonitor = new PromiseMonitor(new ConsoleReporter()); + + return function(Promise) { + return promiseMonitor.monitor(Promise); + }; +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/monitor/ConsoleReporter.js b/public/javascripts/bower/when/monitor/ConsoleReporter.js new file mode 100644 index 00000000000..2b0c9741187 --- /dev/null +++ b/public/javascripts/bower/when/monitor/ConsoleReporter.js @@ -0,0 +1,106 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var error = require('./error'); + var unhandledRejectionsMsg = '[promises] Unhandled rejections: '; + var allHandledMsg = '[promises] All previously unhandled rejections have now been handled'; + + function ConsoleReporter() { + this._previouslyReported = false; + } + + ConsoleReporter.prototype = initDefaultLogging(); + + ConsoleReporter.prototype.log = function(traces) { + if(traces.length === 0) { + if(this._previouslyReported) { + this._previouslyReported = false; + this.msg(allHandledMsg); + } + return; + } + + this._previouslyReported = true; + this.groupStart(unhandledRejectionsMsg + traces.length); + try { + this._log(traces); + } finally { + this.groupEnd(); + } + }; + + ConsoleReporter.prototype._log = function(traces) { + for(var i=0; i= 0; --i) { + t = this._traces[i]; + if(t.handler === handler) { + break; + } + } + + if(i >= 0) { + t.extraContext = extraContext; + } else { + this._traces.push({ + handler: handler, + extraContext: extraContext + }); + } + + this.logTraces(); + }; + + PromiseMonitor.prototype.removeTrace = function(/*handler*/) { + this.logTraces(); + }; + + PromiseMonitor.prototype.fatal = function(handler, extraContext) { + var err = new Error(); + err.stack = this._createLongTrace(handler.value, handler.context, extraContext).join('\n'); + setTimer(function() { + throw err; + }, 0); + }; + + PromiseMonitor.prototype.logTraces = function() { + if(!this._traceTask) { + this._traceTask = setTimer(this._doLogTraces, this.logDelay); + } + }; + + PromiseMonitor.prototype._logTraces = function() { + this._traceTask = void 0; + this._traces = this._traces.filter(filterHandled); + this._reporter.log(this.formatTraces(this._traces)); + }; + + + PromiseMonitor.prototype.formatTraces = function(traces) { + return traces.map(function(t) { + return this._createLongTrace(t.handler.value, t.handler.context, t.extraContext); + }, this); + }; + + PromiseMonitor.prototype._createLongTrace = function(e, context, extraContext) { + var trace = error.parse(e) || [String(e) + ' (WARNING: non-Error used)']; + trace = filterFrames(this.stackFilter, trace, 0); + this._appendContext(trace, context); + this._appendContext(trace, extraContext); + return this.filterDuplicateFrames ? this._removeDuplicates(trace) : trace; + }; + + PromiseMonitor.prototype._removeDuplicates = function(trace) { + var seen = {}; + var sep = this.stackJumpSeparator; + var count = 0; + return trace.reduceRight(function(deduped, line, i) { + if(i === 0) { + deduped.unshift(line); + } else if(line === sep) { + if(count > 0) { + deduped.unshift(line); + count = 0; + } + } else if(!seen[line]) { + seen[line] = true; + deduped.unshift(line); + ++count; + } + return deduped; + }, []); + }; + + PromiseMonitor.prototype._appendContext = function(trace, context) { + trace.push.apply(trace, this._createTrace(context)); + }; + + PromiseMonitor.prototype._createTrace = function(traceChain) { + var trace = []; + var stack; + + while(traceChain) { + stack = error.parse(traceChain); + + if (stack) { + stack = filterFrames(this.stackFilter, stack); + appendStack(trace, stack, this.stackJumpSeparator); + } + + traceChain = traceChain.parent; + } + + return trace; + }; + + function appendStack(trace, stack, separator) { + if (stack.length > 1) { + stack[0] = separator; + trace.push.apply(trace, stack); + } + } + + function filterFrames(stackFilter, stack) { + return stack.filter(function(frame) { + return !stackFilter.test(frame); + }); + } + + function filterHandled(t) { + return !t.handler.handled; + } + + return PromiseMonitor; +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/monitor/console.js b/public/javascripts/bower/when/monitor/console.js new file mode 100644 index 00000000000..931caa641ba --- /dev/null +++ b/public/javascripts/bower/when/monitor/console.js @@ -0,0 +1,14 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function(require) { + + var monitor = require('../monitor'); + var Promise = require('../when').Promise; + + return monitor(Promise); + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/monitor/error.js b/public/javascripts/bower/when/monitor/error.js new file mode 100644 index 00000000000..63825e2c4fd --- /dev/null +++ b/public/javascripts/bower/when/monitor/error.js @@ -0,0 +1,86 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ +/** @author Brian Cavalier */ +/** @author John Hann */ + +(function(define) { 'use strict'; +define(function() { + + var parse, captureStack, format; + + if(Error.captureStackTrace) { + // Use Error.captureStackTrace if available + parse = function(e) { + return e && e.stack && e.stack.split('\n'); + }; + + format = formatAsString; + captureStack = Error.captureStackTrace; + + } else { + // Otherwise, do minimal feature detection to determine + // how to capture and format reasonable stacks. + parse = function(e) { + var stack = e && e.stack && e.stack.split('\n'); + if(stack && e.message) { + stack.unshift(e.message); + } + return stack; + }; + + (function() { + var e = new Error(); + if(typeof e.stack !== 'string') { + format = formatAsString; + captureStack = captureSpiderMonkeyStack; + } else { + format = formatAsErrorWithStack; + captureStack = useStackDirectly; + } + }()); + } + + function captureSpiderMonkeyStack(host) { + try { + throw new Error(); + } catch(err) { + host.stack = err.stack; + } + } + + function useStackDirectly(host) { + host.stack = new Error().stack; + } + + function formatAsString(longTrace) { + return join(longTrace); + } + + function formatAsErrorWithStack(longTrace) { + var e = new Error(); + e.stack = formatAsString(longTrace); + return e; + } + + // About 5-10x faster than String.prototype.join o_O + function join(a) { + var sep = false; + var s = ''; + for(var i=0; i< a.length; ++i) { + if(sep) { + s += '\n' + a[i]; + } else { + s+= a[i]; + sep = true; + } + } + return s; + } + + return { + parse: parse, + format: format, + captureStack: captureStack + }; + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); })); diff --git a/public/javascripts/bower/when/node.js b/public/javascripts/bower/when/node.js new file mode 100644 index 00000000000..61d9453ff76 --- /dev/null +++ b/public/javascripts/bower/when/node.js @@ -0,0 +1,282 @@ +/** @license MIT License (c) copyright 2013 original author or authors */ + +/** + * Collection of helpers for interfacing with node-style asynchronous functions + * using promises. + * + * @author Brian Cavalier + * @contributor Renato Zannon + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + var _liftAll = require('./lib/liftAll'); + var setTimer = require('./lib/env').setTimer; + var slice = Array.prototype.slice; + + var _apply = require('./lib/apply')(when.Promise, dispatch); + + return { + lift: lift, + liftAll: liftAll, + apply: apply, + call: call, + createCallback: createCallback, + bindCallback: bindCallback, + liftCallback: liftCallback + }; + + /** + * Takes a node-style async function and calls it immediately (with an optional + * array of arguments or promises for arguments). It returns a promise whose + * resolution depends on whether the async functions calls its callback with the + * conventional error argument or not. + * + * With this it becomes possible to leverage existing APIs while still reaping + * the benefits of promises. + * + * @example + * function onlySmallNumbers(n, callback) { + * if(n < 10) { + * callback(null, n + 10); + * } else { + * callback(new Error("Calculation failed")); + * } + * } + * + * var nodefn = require("when/node/function"); + * + * // Logs '15' + * nodefn.apply(onlySmallNumbers, [5]).then(console.log, console.error); + * + * // Logs 'Calculation failed' + * nodefn.apply(onlySmallNumbers, [15]).then(console.log, console.error); + * + * @param {function} f node-style function that will be called + * @param {Array} [args] array of arguments to func + * @returns {Promise} promise for the value func passes to its callback + */ + function apply(f, args) { + return _apply(f, this, args || []); + } + + function dispatch(f, thisArg, args, h) { + var cb = createCallback(h); + try { + switch(args.length) { + case 2: f.call(thisArg, args[0], args[1], cb); break; + case 1: f.call(thisArg, args[0], cb); break; + case 0: f.call(thisArg, cb); break; + default: + args.push(cb); + f.apply(thisArg, args); + } + } catch(e) { + h.reject(e); + } + } + + /** + * Has the same behavior that {@link apply} has, with the difference that the + * arguments to the function are provided individually, while {@link apply} accepts + * a single array. + * + * @example + * function sumSmallNumbers(x, y, callback) { + * var result = x + y; + * if(result < 10) { + * callback(null, result); + * } else { + * callback(new Error("Calculation failed")); + * } + * } + * + * // Logs '5' + * nodefn.call(sumSmallNumbers, 2, 3).then(console.log, console.error); + * + * // Logs 'Calculation failed' + * nodefn.call(sumSmallNumbers, 5, 10).then(console.log, console.error); + * + * @param {function} f node-style function that will be called + * @param {...*} [args] arguments that will be forwarded to the function + * @returns {Promise} promise for the value func passes to its callback + */ + function call(f /*, args... */) { + return _apply(f, this, slice.call(arguments, 1)); + } + + /** + * Takes a node-style function and returns new function that wraps the + * original and, instead of taking a callback, returns a promise. Also, it + * knows how to handle promises given as arguments, waiting for their + * resolution before executing. + * + * Upon execution, the orginal function is executed as well. If it passes + * a truthy value as the first argument to the callback, it will be + * interpreted as an error condition, and the promise will be rejected + * with it. Otherwise, the call is considered a resolution, and the promise + * is resolved with the callback's second argument. + * + * @example + * var fs = require("fs"), nodefn = require("when/node/function"); + * + * var promiseRead = nodefn.lift(fs.readFile); + * + * // The promise is resolved with the contents of the file if everything + * // goes ok + * promiseRead('exists.txt').then(console.log, console.error); + * + * // And will be rejected if something doesn't work out + * // (e.g. the files does not exist) + * promiseRead('doesnt_exist.txt').then(console.log, console.error); + * + * + * @param {Function} f node-style function to be lifted + * @param {...*} [args] arguments to be prepended for the new function @deprecated + * @returns {Function} a promise-returning function + */ + function lift(f /*, args... */) { + var args1 = arguments.length > 1 ? slice.call(arguments, 1) : []; + return function() { + // TODO: Simplify once partialing has been removed + var l = args1.length; + var al = arguments.length; + var args = new Array(al + l); + var i; + for(i=0; i 2) { + resolver.resolve(slice.call(arguments, 1)); + } else { + resolver.resolve(value); + } + }; + } + + /** + * Attaches a node-style callback to a promise, ensuring the callback is + * called for either fulfillment or rejection. Returns a promise with the same + * state as the passed-in promise. + * + * @example + * var deferred = when.defer(); + * + * function callback(err, value) { + * // Handle err or use value + * } + * + * bindCallback(deferred.promise, callback); + * + * deferred.resolve('interesting value'); + * + * @param {Promise} promise The promise to be attached to. + * @param {Function} callback The node-style callback to attach. + * @returns {Promise} A promise with the same state as the passed-in promise. + */ + function bindCallback(promise, callback) { + promise = when(promise); + + if (callback) { + promise.then(success, wrapped); + } + + return promise; + + function success(value) { + wrapped(null, value); + } + + function wrapped(err, value) { + setTimer(function () { + callback(err, value); + }, 0); + } + } + + /** + * Takes a node-style callback and returns new function that accepts a + * promise, calling the original callback when the promise is either + * fulfilled or rejected with the appropriate arguments. + * + * @example + * var deferred = when.defer(); + * + * function callback(err, value) { + * // Handle err or use value + * } + * + * var wrapped = liftCallback(callback); + * + * // `wrapped` can now be passed around at will + * wrapped(deferred.promise); + * + * deferred.resolve('interesting value'); + * + * @param {Function} callback The node-style callback to wrap. + * @returns {Function} The lifted, promise-accepting function. + */ + function liftCallback(callback) { + return function(promise) { + return bindCallback(promise, callback); + }; + } +}); + +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + + diff --git a/public/javascripts/bower/when/node/function.js b/public/javascripts/bower/when/node/function.js new file mode 100644 index 00000000000..6736f1a9ca5 --- /dev/null +++ b/public/javascripts/bower/when/node/function.js @@ -0,0 +1,13 @@ +/** @license MIT License (c) copyright 2013 original author or authors */ + +/** + * @author Brian Cavalier + */ +(function(define) { 'use strict'; +define(function(require) { + + // DEPRECATED: Use when/node instead + return require('../node'); + +}); +}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); diff --git a/public/javascripts/bower/when/package.json b/public/javascripts/bower/when/package.json new file mode 100644 index 00000000000..7c19f9cc549 --- /dev/null +++ b/public/javascripts/bower/when/package.json @@ -0,0 +1,87 @@ +{ + "name": "when", + "version": "3.7.3", + "description": "A lightweight Promises/A+ and when() implementation, plus other async goodies.", + "keywords": [ + "cujo", + "Promises/A+", + "promises-aplus", + "promise", + "promises", + "deferred", + "deferreds", + "when", + "async", + "asynchronous", + "ender" + ], + "homepage": "http://cujojs.com", + "licenses": [ + { + "type": "MIT", + "url": "http://www.opensource.org/licenses/mit-license.php" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/cujojs/when" + }, + "bugs": "https://github.com/cujojs/when/issues", + "maintainers": [ + { + "name": "Brian Cavalier", + "web": "http://hovercraftstudios.com" + }, + { + "name": "John Hann", + "web": "http://unscriptable.com" + } + ], + "contributors": [ + { + "name": "Brian Cavalier", + "web": "http://hovercraftstudios.com" + }, + { + "name": "John Hann", + "web": "http://unscriptable.com" + }, + { + "name": "Scott Andrews" + } + ], + "devDependencies": { + "promises-aplus-tests": "~2", + "benchmark": "~1", + "microtime": "~0", + "browserify": "~2", + "buster": "~0.7", + "jshint": "~2", + "rest": "1.1.x", + "optimist": "~0.6", + "sauce-connect-launcher": "~0.4", + "wd": "~0.2", + "json5": "~0.2", + "poly": "git://github.com/cujojs/poly#0.6.0" + }, + "main": "when", + "ender": { "files": ["*.js", "lib/*.js", "node/*.js", "unfold/*.js", "monitor/*.js"] }, + "browser": { + "vertx": false + }, + "directories": { + "test": "test" + }, + "scripts": { + "test": "jshint . && buster-test -e node && promises-aplus-tests test/promises-aplus-adapter.js", + "build-browser-test": "browserify ./node_modules/poly/es5.js -o test/browser/es5.js && browserify ./test/*-test.js ./test/**/*-test.js -o test/browser/tests.js -x buster ", + "browser-test": "npm run build-browser-test && buster-static -e browser -p 8080", + "ci": "npm test && node test/sauce.js", + "tunnel": "node test/sauce.js -m", + "start": "buster-static -e browser", + "benchmark": "node benchmark/promise && node benchmark/map", + "browserify-es6": "browserify -s Promise es6-shim/Promise.browserify-es6.js --no-detect-globals -o es6-shim/Promise.js", + "browserify": "browserify -s when build/when.browserify.js --no-detect-globals -o build/when.js", + "browserify-debug": "browserify -s when build/when.browserify-debug.js --no-detect-globals -o build/when.js" + } +} diff --git a/public/javascripts/bower/when/parallel.js b/public/javascripts/bower/when/parallel.js new file mode 100644 index 00000000000..6616a82beb7 --- /dev/null +++ b/public/javascripts/bower/when/parallel.js @@ -0,0 +1,39 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * parallel.js + * + * Run a set of task functions in parallel. All tasks will + * receive the same args + * + * @author Brian Cavalier + * @author John Hann + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + var all = when.Promise.all; + var slice = Array.prototype.slice; + + /** + * Run array of tasks in parallel + * @param tasks {Array|Promise} array or promiseForArray of task functions + * @param [args] {*} arguments to be passed to all tasks + * @return {Promise} promise for array containing the + * result of each task in the array position corresponding + * to position of the task in the tasks array + */ + return function parallel(tasks /*, args... */) { + return all(slice.call(arguments, 1)).then(function(args) { + return when.map(tasks, function(task) { + return task.apply(void 0, args); + }); + }); + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + diff --git a/public/javascripts/bower/when/pipeline.js b/public/javascripts/bower/when/pipeline.js new file mode 100644 index 00000000000..e5c0bd3b981 --- /dev/null +++ b/public/javascripts/bower/when/pipeline.js @@ -0,0 +1,50 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * pipeline.js + * + * Run a set of task functions in sequence, passing the result + * of the previous as an argument to the next. Like a shell + * pipeline, e.g. `cat file.txt | grep 'foo' | sed -e 's/foo/bar/g' + * + * @author Brian Cavalier + * @author John Hann + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + var all = when.Promise.all; + var slice = Array.prototype.slice; + + /** + * Run array of tasks in a pipeline where the next + * tasks receives the result of the previous. The first task + * will receive the initialArgs as its argument list. + * @param tasks {Array|Promise} array or promise for array of task functions + * @param [initialArgs...] {*} arguments to be passed to the first task + * @return {Promise} promise for return value of the final task + */ + return function pipeline(tasks /* initialArgs... */) { + // Self-optimizing function to run first task with multiple + // args using apply, but subsequence tasks via direct invocation + var runTask = function(args, task) { + runTask = function(arg, task) { + return task(arg); + }; + + return task.apply(null, args); + }; + + return all(slice.call(arguments, 1)).then(function(args) { + return when.reduce(tasks, function(arg, task) { + return runTask(arg, task); + }, args); + }); + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + diff --git a/public/javascripts/bower/when/poll.js b/public/javascripts/bower/when/poll.js new file mode 100644 index 00000000000..10a7d4131fe --- /dev/null +++ b/public/javascripts/bower/when/poll.js @@ -0,0 +1,114 @@ +/** @license MIT License (c) copyright 2012-2013 original author or authors */ + +/** + * poll.js + * + * Helper that polls until cancelled or for a condition to become true. + * + * @author Scott Andrews + */ + +(function (define) { 'use strict'; +define(function(require) { + + var when = require('./when'); + var attempt = when['try']; + var cancelable = require('./cancelable'); + + /** + * Periodically execute the task function on the msec delay. The result of + * the task may be verified by watching for a condition to become true. The + * returned deferred is cancellable if the polling needs to be cancelled + * externally before reaching a resolved state. + * + * The next vote is scheduled after the results of the current vote are + * verified and rejected. + * + * Polling may be terminated by the verifier returning a truthy value, + * invoking cancel() on the returned promise, or the task function returning + * a rejected promise. + * + * Usage: + * + * var count = 0; + * function doSomething() { return count++ } + * + * // poll until cancelled + * var p = poll(doSomething, 1000); + * ... + * p.cancel(); + * + * // poll until condition is met + * poll(doSomething, 1000, function(result) { return result > 10 }) + * .then(function(result) { assert result == 10 }); + * + * // delay first vote + * poll(doSomething, 1000, anyFunc, true); + * + * @param task {Function} function that is executed after every timeout + * @param interval {number|Function} timeout in milliseconds + * @param [verifier] {Function} function to evaluate the result of the vote. + * May return a {Promise} or a {Boolean}. Rejecting the promise or a + * falsey value will schedule the next vote. + * @param [delayInitialTask] {boolean} if truthy, the first vote is scheduled + * instead of immediate + * + * @returns {Promise} + */ + return function poll(task, interval, verifier, delayInitialTask) { + var deferred, canceled, reject; + + canceled = false; + deferred = cancelable(when.defer(), function () { canceled = true; }); + reject = deferred.reject; + + verifier = verifier || function () { return false; }; + + if (typeof interval !== 'function') { + interval = (function (interval) { + return function () { return when().delay(interval); }; + })(interval); + } + + function certify(result) { + deferred.resolve(result); + } + + function schedule(result) { + attempt(interval).then(vote, reject); + if (result !== void 0) { + deferred.notify(result); + } + } + + function vote() { + if (canceled) { return; } + when(task(), + function (result) { + when(verifier(result), + function (verification) { + return verification ? certify(result) : schedule(result); + }, + function () { schedule(result); } + ); + }, + reject + ); + } + + if (delayInitialTask) { + schedule(); + } else { + // if task() is blocking, vote will also block + vote(); + } + + // make the promise cancelable + deferred.promise = Object.create(deferred.promise); + deferred.promise.cancel = deferred.cancel; + + return deferred.promise; + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); diff --git a/public/javascripts/bower/when/sequence.js b/public/javascripts/bower/when/sequence.js new file mode 100644 index 00000000000..5f0bc4f85e6 --- /dev/null +++ b/public/javascripts/bower/when/sequence.js @@ -0,0 +1,46 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * sequence.js + * + * Run a set of task functions in sequence. All tasks will + * receive the same args. + * + * @author Brian Cavalier + * @author John Hann + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + var all = when.Promise.all; + var slice = Array.prototype.slice; + + /** + * Run array of tasks in sequence with no overlap + * @param tasks {Array|Promise} array or promiseForArray of task functions + * @param [args] {*} arguments to be passed to all tasks + * @return {Promise} promise for an array containing + * the result of each task in the array position corresponding + * to position of the task in the tasks array + */ + return function sequence(tasks /*, args... */) { + var results = []; + + return all(slice.call(arguments, 1)).then(function(args) { + return when.reduce(tasks, function(results, task) { + return when(task.apply(void 0, args), addResult); + }, results); + }); + + function addResult(result) { + results.push(result); + return results; + } + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + diff --git a/public/javascripts/bower/when/timeout.js b/public/javascripts/bower/when/timeout.js new file mode 100644 index 00000000000..ed4aeb9f40c --- /dev/null +++ b/public/javascripts/bower/when/timeout.js @@ -0,0 +1,27 @@ +/** @license MIT License (c) copyright 2011-2013 original author or authors */ + +/** + * timeout.js + * + * Helper that returns a promise that rejects after a specified timeout, + * if not explicitly resolved or rejected before that. + * + * @author Brian Cavalier + * @author John Hann + */ + +(function(define) { +define(function(require) { + + var when = require('./when'); + + /** + * @deprecated Use when(trigger).timeout(ms) + */ + return function timeout(msec, trigger) { + return when(trigger).timeout(msec); + }; +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + + diff --git a/public/javascripts/bower/when/unfold.js b/public/javascripts/bower/when/unfold.js new file mode 100644 index 00000000000..807bcfca675 --- /dev/null +++ b/public/javascripts/bower/when/unfold.js @@ -0,0 +1,17 @@ +/** @license MIT License (c) copyright B Cavalier & J Hann */ + +/** + * unfold + * @author: brian@hovercraftstudios.com + */ +(function(define) { +define(function(require) { + + /** + * @deprecated Use when.unfold + */ + return require('./when').unfold; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); } ); + diff --git a/public/javascripts/bower/when/unfold/list.js b/public/javascripts/bower/when/unfold/list.js new file mode 100644 index 00000000000..529c2c93d57 --- /dev/null +++ b/public/javascripts/bower/when/unfold/list.js @@ -0,0 +1,32 @@ +/** @license MIT License (c) copyright B Cavalier & J Hann */ + +(function(define) { +define(function(require) { + + var unfold = require('../when').unfold; + + /** + * @deprecated + * Given a seed and generator, produces an Array. Effectively the + * dual (opposite) of when.reduce() + * @param {function} generator function that generates a value (or promise + * for a value) to be placed in the resulting array + * @param {function} condition given a seed, must return truthy if the unfold + * should continue, or falsey if it should terminate + * @param {*|Promise} seed any value or promise + * @return {Promise} resulting array + */ + return function list(generator, condition, seed) { + var result = []; + + return unfold(generator, condition, append, seed)['yield'](result); + + function append(value, newSeed) { + result.push(value); + return newSeed; + } + }; + +}); +})(typeof define === 'function' && define.amd ? define : function (factory) { module.exports = factory(require); }); + diff --git a/public/javascripts/bower/when/when.js b/public/javascripts/bower/when/when.js new file mode 100644 index 00000000000..65f8e43057c --- /dev/null +++ b/public/javascripts/bower/when/when.js @@ -0,0 +1,228 @@ +/** @license MIT License (c) copyright 2010-2014 original author or authors */ + +/** + * Promises/A+ and when() implementation + * when is part of the cujoJS family of libraries (http://cujojs.com/) + * @author Brian Cavalier + * @author John Hann + */ +(function(define) { 'use strict'; +define(function (require) { + + var timed = require('./lib/decorators/timed'); + var array = require('./lib/decorators/array'); + var flow = require('./lib/decorators/flow'); + var fold = require('./lib/decorators/fold'); + var inspect = require('./lib/decorators/inspect'); + var generate = require('./lib/decorators/iterate'); + var progress = require('./lib/decorators/progress'); + var withThis = require('./lib/decorators/with'); + var unhandledRejection = require('./lib/decorators/unhandledRejection'); + var TimeoutError = require('./lib/TimeoutError'); + + var Promise = [array, flow, fold, generate, progress, + inspect, withThis, timed, unhandledRejection] + .reduce(function(Promise, feature) { + return feature(Promise); + }, require('./lib/Promise')); + + var apply = require('./lib/apply')(Promise); + + // Public API + + when.promise = promise; // Create a pending promise + when.resolve = Promise.resolve; // Create a resolved promise + when.reject = Promise.reject; // Create a rejected promise + + when.lift = lift; // lift a function to return promises + when['try'] = attempt; // call a function and return a promise + when.attempt = attempt; // alias for when.try + + when.iterate = Promise.iterate; // DEPRECATED (use cujojs/most streams) Generate a stream of promises + when.unfold = Promise.unfold; // DEPRECATED (use cujojs/most streams) Generate a stream of promises + + when.join = join; // Join 2 or more promises + + when.all = all; // Resolve a list of promises + when.settle = settle; // Settle a list of promises + + when.any = lift(Promise.any); // One-winner race + when.some = lift(Promise.some); // Multi-winner race + when.race = lift(Promise.race); // First-to-settle race + + when.map = map; // Array.map() for promises + when.filter = filter; // Array.filter() for promises + when.reduce = lift(Promise.reduce); // Array.reduce() for promises + when.reduceRight = lift(Promise.reduceRight); // Array.reduceRight() for promises + + when.isPromiseLike = isPromiseLike; // Is something promise-like, aka thenable + + when.Promise = Promise; // Promise constructor + when.defer = defer; // Create a {promise, resolve, reject} tuple + + // Error types + + when.TimeoutError = TimeoutError; + + /** + * Get a trusted promise for x, or by transforming x with onFulfilled + * + * @param {*} x + * @param {function?} onFulfilled callback to be called when x is + * successfully fulfilled. If promiseOrValue is an immediate value, callback + * will be invoked immediately. + * @param {function?} onRejected callback to be called when x is + * rejected. + * @param {function?} onProgress callback to be called when progress updates + * are issued for x. @deprecated + * @returns {Promise} a new promise that will fulfill with the return + * value of callback or errback or the completion value of promiseOrValue if + * callback and/or errback is not supplied. + */ + function when(x, onFulfilled, onRejected, onProgress) { + var p = Promise.resolve(x); + if (arguments.length < 2) { + return p; + } + + return p.then(onFulfilled, onRejected, onProgress); + } + + /** + * Creates a new promise whose fate is determined by resolver. + * @param {function} resolver function(resolve, reject, notify) + * @returns {Promise} promise whose fate is determine by resolver + */ + function promise(resolver) { + return new Promise(resolver); + } + + /** + * Lift the supplied function, creating a version of f that returns + * promises, and accepts promises as arguments. + * @param {function} f + * @returns {Function} version of f that returns promises + */ + function lift(f) { + return function() { + for(var i=0, l=arguments.length, a=new Array(l); i - - TestUtils = React.addons.TestUtils - - module 'ModeratedStudentList', - test "renders mark 1", -> - store = new Store() - score = 10 - moderatedStudentList = TestUtils.renderIntoDocument(ModeratedStudentList(store: store)) - store.addSubmissions([{id: 1, user: {display_name: 'steve'}, provisional_grades: [{score:score}]}]) - firstMark = TestUtils.scryRenderedDOMComponentsWithClass(moderatedStudentList, 'AssignmentList__Mark')[0].getDOMNode().textContent - equal firstMark, score, "renders the first mark" - React.unmountComponentAtNode(moderatedStudentList.getDOMNode().parentNode) - - diff --git a/spec/coffeescripts/jsx/assignments/actions/ModerationActionsSpec.coffee b/spec/coffeescripts/jsx/assignments/actions/ModerationActionsSpec.coffee index c9083051187..2a408295ccf 100644 --- a/spec/coffeescripts/jsx/assignments/actions/ModerationActionsSpec.coffee +++ b/spec/coffeescripts/jsx/assignments/actions/ModerationActionsSpec.coffee @@ -1,29 +1,157 @@ define [ - "jsx/assignments/actions/ModerationActions" -], (ModerationActions) -> - module "ModerationActions", - test "sets this.store in the constructor", -> - some_store = {data: true} - actions = new ModerationActions(some_store) - ok actions.store.data, "sets the store to true" + "when" + "jsx/assignments/actions/ModerationActions", +], (whenJS, ModerationActions) -> - #module "ModerationActions#loadInitalSubmissions", - #setup: -> - #@server = sinon.fakeServer.create() - #teardown: -> - #@server.restore() + module "ModerationActions - Action Creators", - #test "fetches data from the submissions_url and sets store.addSubmissions with it", -> - #the_data = {} - #actions = new ModerationActions(some_store) - #submission_url = "/something" - #actions.loadInitialSubmissions(submission_url) - #expected = {some_thing: 'here'} - #@server.respond 'get', submission_url, [ - #200 - #'Content-Type': 'application/json' - #JSON.stringify expected - #] + test "creates the SELECT_STUDENT action", -> + action = ModerationActions.selectStudent(1) + expected = + type: ModerationActions.SELECT_STUDENT + payload: + studentId: 1 + + deepEqual action, expected, "creates the action successfully" + + test "creates the GOT_STUDENTS action", -> + action = ModerationActions.gotStudents([1, 2, 3]) + expected = + type: ModerationActions.GOT_STUDENTS + payload: + students: [1, 2, 3] + + deepEqual action, expected, "creates the action successfully" + + test "creates the PUBLISHED_GRADES action", -> + action = ModerationActions.publishedGrades('test') + expected = + type: ModerationActions.PUBLISHED_GRADES + payload: + message: 'test' + time: Date.now() + + equal action.type, expected.type, "type matches" + equal action.payload.message, expected.payload.message, "message matches" + ok expected.payload.time - action.payload.time < 5, "time within 5 seconds" + + test "creates the PUBLISHED_GRADES_FAILED action", -> + action = ModerationActions.publishGradesFailed('test') + expected = + type: ModerationActions.PUBLISHED_GRADES_FAILED + payload: + message: 'test' + time: Date.now() + error: true + + equal action.type, expected.type, "type matches" + equal action.payload.message, expected.payload.message, "message matches" + ok action.error, "error flag is set" + ok expected.payload.time - action.payload.time < 5, "time within 5 seconds" - #equal the_data, expected, "gets data from the url" + module "ModerationActions#apiGetStudents", + + setup: -> + @client = { + get: -> + dfd = whenJS.defer() + setTimeout -> + dfd.resolve('test') + , 100 + dfd.promise() + } + + test "returns a function", -> + ok typeof ModerationActions.apiGetStudents() == 'function' + + asyncTest "dispatches gotStudents action", -> + + getState = -> + urls: + list_gradeable_students: 'some_url' + students: [] + + fakeResponse = {data: ['test']} + + gotStudentsAction = + type: ModerationActions.GOT_STUDENTS + payload: + students: ['test'] + + sinon.stub(@client, 'get').returns(whenJS(fakeResponse)) + ModerationActions.apiGetStudents(@client)((action) -> + deepEqual action, gotStudentsAction + start() + , getState) + + module "ModerationActions#publishGrades", + setup: -> + @client = { + post: -> + dfd = whenJS.defer() + setTimeout -> + dfd.resolve('test') + , 100 + dfd.promise() + } + + test "returns a function", -> + ok typeof ModerationActions.publishGrades() == 'function' + + asyncTest "dispatches publishGrades action on success", -> + getState = -> + urls: + publish_grades_url: 'some_url' + fakeResponse = {status: 200} + + publishGradesAction = + type: ModerationActions.PUBLISHED_GRADES + payload: + message: 'Success! Grades were published to the grade book.' + + sinon.stub(@client, 'post').returns(whenJS(fakeResponse)) + ModerationActions.publishGrades(@client)((action) -> + equal action.type, publishGradesAction.type, 'type matches' + equal action.payload.message, publishGradesAction.payload.message, 'has proper message' + start() + , getState) + + asyncTest "dispatches publishGradesFailed action with already published message on 400 failure", -> + getState = -> + urls: + publish_grades_url: 'some_url' + fakeResponse = + status: 400 + + publishGradesAction = + type: ModerationActions.PUBLISHED_GRADES_FAILED + payload: + message: 'Assignment grades have already been published.' + + sinon.stub(@client, 'post').returns(whenJS.reject(fakeResponse)) + ModerationActions.publishGrades(@client)((action) -> + equal action.type, publishGradesAction.type, 'type matches' + equal action.payload.message, publishGradesAction.payload.message, 'has proper message' + start() + , getState) + + asyncTest "dispatches publishGradesFailed action with generic error message on non-400 error", -> + getState = -> + urls: + publish_grades_url: 'some_url' + fakeResponse = + status: 500 + + publishGradesAction = + type: ModerationActions.PUBLISHED_GRADES_FAILED + payload: + message: 'An error occurred publishing grades.' + + sinon.stub(@client, 'post').returns(whenJS.reject(fakeResponse)) + ModerationActions.publishGrades(@client)((action) -> + equal action.type, publishGradesAction.type, 'type matches' + equal action.payload.message, publishGradesAction.payload.message, 'has proper message' + start() + , getState) + diff --git a/spec/coffeescripts/jsx/assignments/reducers/rootReducerSpec.coffee b/spec/coffeescripts/jsx/assignments/reducers/rootReducerSpec.coffee new file mode 100644 index 00000000000..029008133b9 --- /dev/null +++ b/spec/coffeescripts/jsx/assignments/reducers/rootReducerSpec.coffee @@ -0,0 +1,75 @@ +define [ + "jsx/assignments/reducers/rootReducer" +], (rootReducer) -> + + module "students reducer", + + test "concatenates students handling GOT_STUDENTS", -> + initialState = + students: ['one', 'two'] + gotStudentsAction = + type: 'GOT_STUDENTS' + payload: + students: ['three', 'four'] + newState = rootReducer(initialState, gotStudentsAction) + expected = ['one', 'two', 'three', 'four'] + deepEqual newState.students, expected, 'successfully concatenates' + + module "urls reducer", + + test "passes through whatever the current state is", -> + initialState = + urls: + test_url: 'test' + someRandomAction = + type: 'Random' + newState = rootReducer(initialState, someRandomAction) + deepEqual newState.urls, initialState.urls, 'passes through unchanged' + + module "assignments reducer", + + test "sets to published on PUBLISHED_GRADES", -> + initialState = + assignments: + published: false + publishedGradesAction = + type: 'PUBLISHED_GRADES' + payload: + time: Date.now() + message: 'test' + newState = rootReducer(initialState, publishedGradesAction) + ok newState.assignment.published, 'successfully sets to publish' + + module "flashMessage reducer", + + test "sets success message on PUBLISHED_GRADES", -> + initialState = + flashMessage: {} + publishedGradesAction = + type: 'PUBLISHED_GRADES' + payload: + time: 123 + message: 'test success' + newState = rootReducer(initialState, publishedGradesAction) + expected = + time: 123 + message: 'test success' + error: false + deepEqual newState.flashMessage, expected, 'updates state' + + test "sets failure message on PUBLISHED_GRADES_FAILED", -> + initialState = + flashMessage: {} + publishedGradesAction = + type: 'PUBLISHED_GRADES_FAILED' + payload: + time: 123 + message: 'failed to publish' + error: true + newState = rootReducer(initialState, publishedGradesAction) + expected = + time: 123 + message: 'failed to publish' + error: true + deepEqual newState.flashMessage, expected, 'updates state' + diff --git a/spec/coffeescripts/jsx/assignments/stores/ModerationStoreSpec.coffee b/spec/coffeescripts/jsx/assignments/stores/ModerationStoreSpec.coffee deleted file mode 100644 index 77dbbf5be5f..00000000000 --- a/spec/coffeescripts/jsx/assignments/stores/ModerationStoreSpec.coffee +++ /dev/null @@ -1,27 +0,0 @@ -define ['jsx/assignments/stores/ModerationStore'], (ModerationStore) -> - - module 'ModerationStore', - - test 'constructor', -> - store = new ModerationStore() - ok store, "constructs properly" - equal store.submissions.length, 0, 'student list is initally empty' - - test 'adds multiple submissions to the store', -> - store = new ModerationStore() - store.addSubmissions([{id: 1}, {id: 2}]) - equal store.submissions.length, 2, 'store length is two' - - test 'doesn\'t add duplicates to the store', -> - store = new ModerationStore() - store.addSubmissions([{id: 1}]) - store.addSubmissions([{id: 1}]) - equal store.submissions.length, 1, 'store length is one' - - test 'triggers change when adding submissions', -> - store = new ModerationStore() - called = false - store.addChangeListener () -> - called = true - store.addSubmissions([{id: 1}]) - ok called, 'change listener handler was called'