<commit_before>/*
 * jquery-ajaxmiddleware
 * https://github.com/kentfrazier/jquery-ajaxmiddleware
 *
 * Copyright (c) 2013 Kent Frazier
 * Licensed under the MIT license.
 */

/**
 * @fileOverview
 * A jQuery module that provides hooks
 */
(function(factory, window) {
    'use strict';

    /*global jQuery: false, define: false */

    /* --- Install the module --- */
    if (typeof define === 'function' && define.amd) {
        // TODO: This is really ugly. Is there a better way to pass non-AMD
        // params?
        define('_windowGlobal', [], function() { return window; });
        define(['jquery', '_windowGlobal'], factory);
    } else {
        // relies on browser global jQuery and window
        factory(jQuery, window);
    }
}(function($, window, undefined) {
    'use strict';

    /**
     * Currently registered middleware objects.
     *
     * The key is the name of the middleware, and the value is an array
     * of middleware objects registered to that key.
     *
     * @private
     * @type {Object.<String, ajaxMiddleware[]>}
     */
    var registry = {
        ids: {},
        names: {},
        add: function(middleware) {
            var id = middleware.id;
            var name = middleware.name;

            this.ids[id] = middleware;
            this.names[name] = this.names[name] || [];
            this.names[name].push(middleware);
        }
    };

    /**
     * Generate a unique key for each middleware object.
     *
     * @private
     * @function
     * @returns {String} a new unique key
     */
    var generateID = (function(seed) {
        return function generateID() {
            var id = '' + seed;
            ++seed;
            return id;
        };
    }($.now()));

    function createPrefilter(middleware) {
        return function(options, originalOptions, jqXHR) {
            if (!middleware.isActive()) {
                return;
            }

            /* Ensure that options has a namespace for this module. */
            $.extend(true, options, {_ajaxMiddleware: {}});

            if (options._ajaxMiddleware[middleware.id]) {
                return;
            }

            /* Now we need to mark the options so we know this middleware has
             * processed it.
             */
            options._ajaxMiddleware[middleware.id] = true;

            if (middleware.options.shouldIntercept.call(middleware,
                                                        options,
                                                        originalOptions,
                                                        jqXHR)) {
                /* Returning a string from here will cause the AJAX handlers to
                 * treat it as a different dataType.  This is necessary so that
                 * the ajaxTransport handler we install below can have higher
                 * precedence than the defaults.
                 */
                return middleware.dataType;
            }
        };
    }

    function createTransport(middleware) {
        return function(options, originalOptions) {
            /* Define this in the closure scope so that the send and abort
             * functions can both access it.
             */
            var request;

            function send(headers, completeCallback) {
                var overrides = {
                    /* Convert everything to text. The original request
                     * will handle all the necessary conversion.
                     */
                    // TODO: This will break anything that doesn't use the
                    // default transport! I need to figure out how to avoid
                    // breaking things like script and jsonp!
                    // TODO: Add some unit tests to demonstrate.
                    dataType: 'text',
                    converters: {
                        '* text': window.String
                    },

                    /* Don't want AJAX events to go off for the inner call.
                     * That will happen when completeCallback is called.
                     */
                    global: false,

                    /* Pass along any request headers that were specified */
                    headers: headers,

                    /* Prevent any of the original success or error handlers
                     * from running
                     */
                    statusCode: null,
                    success: null,
                    error: null,

                    /* Completion of the underlying request will trigger our
                     * postprocessing and then call the original handlers
                     * specified by the user.
                     */
                    complete: function(jqXHR, textStatus) {
                        var completeParams = {
                            status: jqXHR.status,
                            textStatus: textStatus,
                            responses: {text: jqXHR.responseText},
                            headers: jqXHR.getAllResponseHeaders()
                        };

                        /* Now we give the middleware a chance to modify
                         * the returned values before calling the original
                         * completeCallback.
                         */
                        middleware.options.beforeComplete.call(
                            middleware,
                            completeParams,
                            options,
                            originalOptions
                        );

                        completeCallback(
                            completeParams.status,
                            completeParams.textStatus,
                            completeParams.responses,
                            completeParams.headers
                        );
                    }
                };

                /* Have to get rid of the fake dataType we added */
                options.dataTypes.shift();

                var overriddenOptions = $.extend(
                    true,
                    {},
                    options,
                    overrides
                );

                request = $.ajax(overriddenOptions);
            }

            function abort() {
                if (request) {
                    request.abort();
                }
            }

            return {
                send: send,
                abort: abort
            };
        };
    }

    /**
     * The real constructor for ajaxMiddleware.
     *
     * @constructor
     * @private
     * @param {Object} options
     *     see the module JSDoc for detail on what can be passed in.
     */
    function Middleware(options) {
        this.options = $.extend(
            true,
            {},
            ajaxMiddleware.DEFAULT_OPTIONS,
            options
        );
        this.name = this.options.name;
        this.id = generateID();

        /* For some reason, jQuery lower cases all dataTypes on ajaxPrefilters,
         * but later does not do the same when checking ajaxTransports.
         * To make it work, we need to be consistent with that and always use
         * lower case.
         */
        this.dataType = (this.name + this.id).toLowerCase();

        this.activate();
    }

    /**
     * Handles the business of installing the middleware.
     *
     * @private
     * @returns {void}
     */
    function installMiddleware(middleware) {
        /* Install this middleware in the registry so we can track it
         */
        registry.add(middleware);

        /* First we need to install a prefilter in order to give the
         * transport handler a place to hook in
         */
        $.ajaxPrefilter(
            middleware.options.dataTypes,
            createPrefilter(middleware)
        );

        /* Now we can catch the new dataType we just inserted and call
         * our hooks
         */
        $.ajaxTransport(
            middleware.dataType,
            createTransport(middleware)
        );
    }

    /**
     * The module itself.
     *
     * @constructor
     * @param {Object} options
     */
    function ajaxMiddleware(options) {
        return new Middleware(options);
    }

    /* ajaxMiddleware is the public face of the internal Middleware
     * object, and they need to evaluate to the same type (or instances of
     * either should pass the instanceof test with the other). Setting their
     * prototypes to the same object will ensure that this happens, as defined
     * by ECMA-262 sections 11.8.6 and 15.3.5.3.
     */
    ajaxMiddleware.prototype = Middleware.prototype = {

        /* ------------------------------------------------------------------ *
         *                          PUBLIC API
         * ------------------------------------------------------------------ */

        /**
         * Activate this middleware class.
         */
        activate: function() {
            this._active = true;
            if (!(this.id in registry.ids)) {
                installMiddleware(this);
            }
        },

        /**
         * Deactivate this middleware class.
         */
        deactivate: function() {
            this._active = false;
        },

        /**
         * @returns {boolean} whether the middleware is currently active
         */
        isActive: function() {
            return this._active;
        },

        /**
         * @returns {String} a simple string representation
         */
        toString: function() {
            return [
                '<ajaxMiddleware: ',
                this.dataType,
                ', ',
                this.id,
                '>'
            ].join('');
        }

    };

    /* ---------------------------------------------------------------------- *
     *                          PUBLIC MEMBERS
     * ---------------------------------------------------------------------- */
    $.extend(ajaxMiddleware, {
        /**
         * The default options for middleware. These values are all
         * overrideable. See the documentation for individual attributes to
         * understand what to pass in.
         *
         * @type {Object}
         */
        DEFAULT_OPTIONS: {
            /**
             * The namespace used for this middleware object.
             *
             * @type {String}
             */
            name: 'ajaxMiddleware',

            /**
             * The dataTypes to be handled.
             *
             * @type {String}
             */
            dataTypes: '*',

            /**
             * A predicate function to determine whether this middleware
             * should process this request.
             *
             * @param {Object} options
             *     The fully merged AJAX options for this request
             * @param {Object} originalOptions
             *     The original AJAX options specified by the user
             * @param {jqXHR} jqXHR
             *     The jQuery-wrapped XMLHttpResponse (or similar) object
             *
             * @returns {boolean} whether the request should be processed.
             */
            shouldIntercept: function() {
                // Process all requests for the dataTypes by default.
                return true;
            },

            /**
             * Hook to modify the parameters to be sent to completeCallback.
             *
             * @param {Object} completeParams
             *     Has four items, each corresponding to one of the arguments
             *     to completeCallback as defined by jQuery. It uses the names
             *     that jQuery gives them, but they are as follows:
             *         status: the HTTP status code of the response
             *         statusText: the text for the status (e.g. Internal
             *             Server Error)
             *         responses: (optional) is an object containing
             *             dataType->value mappings that contains the response
             *             in all the formats the transport could provide.
             *         headers: (optional) is a string containing all the
             *             response headers if the transport has access to
             *             them.
             * @param {Object} options
             *     The AJAX options for the intercepted call.
             * @param {Object} originalOptions
             *     The original AJAX options for the intercepted call.
             */
            beforeComplete: function() {
                /* Do nothing by default. */

                /* If we modify completeParams here, it will affect the values
                 * that are passed to the completeCallback.
                 */
            }
        },

        /**
         * @param {String} name
         *     the name of the middleware type in question, or '*' to return
         *     all registered middleware.
         * @param {boolean} [includeDeactivated=false]
         *     whether or not to include deactivated middleware in results
         * @returns {ajaxMiddleware[]}
         *     an array of all middleware registered using that name. If
         *     includeDeactivated is true, it will include the deactivated
         *     middleware as well.
         */
        getByName: function(name, includeDeactivated) {
            var results = [];
            if (name === '*') {
                for (var mwName in registry.names) {
                    if (registry.names.hasOwnProperty(mwName)) {
                        $.merge(results,
                                this.getByName(mwName, includeDeactivated));
                    }
                }
            } else {
                $.merge(results, registry.names[name] || []);
                results = $.grep(results, function(middleware) {
                    return includeDeactivated || middleware._active;
                });
            }
            return results;
        },

        _jQuery: $
    });

    /* Install for normal jQuery */
    $.ajaxMiddleware = ajaxMiddleware;

    /* Return for AMD */
    return ajaxMiddleware;
},
this));
<commit_msg>Improve nesting logic so it works in jQuery < 1.9<commit_after>/*
 * jquery-ajaxmiddleware
 * https://github.com/kentfrazier/jquery-ajaxmiddleware
 *
 * Copyright (c) 2013 Kent Frazier
 * Licensed under the MIT license.
 */

/**
 * @fileOverview
 * A jQuery module that provides hooks
 */
(function(factory, window) {
    'use strict';

    /*global jQuery: false, define: false */

    /* --- Install the module --- */
    if (typeof define === 'function' && define.amd) {
        // TODO: This is really ugly. Is there a better way to pass non-AMD
        // params?
        define('_windowGlobal', [], function() { return window; });
        define(['jquery', '_windowGlobal'], factory);
    } else {
        // relies on browser global jQuery and window
        factory(jQuery, window);
    }
}(function($, window, undefined) {
    'use strict';

    /**
     * Currently registered middleware objects.
     *
     * The key is the name of the middleware, and the value is an array
     * of middleware objects registered to that key.
     *
     * @private
     * @type {Object.<String, ajaxMiddleware[]>}
     */
    var registry = {
        ids: {},
        names: {},
        add: function(middleware) {
            var id = middleware.id;
            var name = middleware.name;

            this.ids[id] = middleware;
            this.names[name] = this.names[name] || [];
            this.names[name].push(middleware);
        }
    };

    /**
     * Generate a unique key for each middleware object.
     *
     * @private
     * @function
     * @returns {String} a new unique key
     */
    var generateID = (function(seed) {
        return function generateID() {
            var id = '' + seed;
            ++seed;
            return id;
        };
    }($.now()));

    function createPrefilter(middleware) {
        return function(options, originalOptions, jqXHR) {
            if (!middleware.isActive()) {
                return;
            }

            /* Ensure that options has a namespace for this module. */
            if (!options.hasOwnProperty('_ajaxMiddleware')) {
                options._ajaxMiddleware = {
                    /* The middleware currently processing. */
                    current: null,
                    /* Middleware that has completed processing */
                    handled: {}
                };
            }

            /* Escape if another middleware is currently processing */
            if (options._ajaxMiddleware.current !== null) {
                return;
            }

            /* Escape if this middleware has already processed the request */
            if (options._ajaxMiddleware.handled[middleware.dataType]) {
                return;
            }

            if (middleware.options.shouldIntercept.call(middleware,
                                                        options,
                                                        originalOptions,
                                                        jqXHR)) {

                /* Now this middleware needs to mark its territory so nothing
                 * else jumps in the way until its transport can handle things.
                 */
                options._ajaxMiddleware.current = middleware.dataType;

                /* Returning a string from here will cause the AJAX handlers to
                 * treat it as a different dataType.  This is necessary so that
                 * the ajaxTransport handler we install below can have higher
                 * precedence than the defaults.
                 */
                return middleware.dataType;
            }
        };
    }

    function createTransport(middleware) {
        return function(options, originalOptions) {
            /* Mark the options so we know this middleware has finished. */
            options._ajaxMiddleware.handled[middleware.dataType] = true;

            /* Clear the flag that prevents other middleware from activating. */
            options._ajaxMiddleware.current = null;

            /* Define this in the closure scope so that the send and abort
             * functions can both access it.
             */
            var request;

            function send(headers, completeCallback) {
                var overrides = {
                    /* Convert everything to text. The original request
                     * will handle all the necessary conversion.
                     */
                    // TODO: This will break anything that doesn't use the
                    // default transport! I need to figure out how to avoid
                    // breaking things like script and jsonp!
                    // TODO: Add some unit tests to demonstrate.
                    dataType: 'text',
                    converters: {
                        '* text': window.String
                    },

                    /* Don't want AJAX events to go off for the inner call.
                     * That will happen when completeCallback is called.
                     */
                    global: false,

                    /* Pass along any request headers that were specified */
                    headers: headers,

                    /* Prevent any of the original success or error handlers
                     * from running
                     */
                    statusCode: null,
                    success: null,
                    error: null,

                    /* Completion of the underlying request will trigger our
                     * postprocessing and then call the original handlers
                     * specified by the user.
                     */
                    complete: function(jqXHR, textStatus) {
                        var completeParams = {
                            status: jqXHR.status,
                            textStatus: textStatus,
                            responses: {text: jqXHR.responseText},
                            headers: jqXHR.getAllResponseHeaders()
                        };

                        /* Now we give the middleware a chance to modify
                         * the returned values before calling the original
                         * completeCallback.
                         */
                        middleware.options.beforeComplete.call(
                            middleware,
                            completeParams,
                            options,
                            originalOptions
                        );

                        completeCallback(
                            completeParams.status,
                            completeParams.textStatus,
                            completeParams.responses,
                            completeParams.headers
                        );
                    }
                };

                /* Have to get rid of the fake dataType we added */
                options.dataTypes.shift();

                var overriddenOptions = $.extend(
                    true,
                    {},
                    options,
                    overrides
                );

                request = $.ajax(overriddenOptions);
            }

            function abort() {
                if (request) {
                    request.abort();
                }
            }

            return {
                send: send,
                abort: abort
            };
        };
    }

    /**
     * The real constructor for ajaxMiddleware.
     *
     * @constructor
     * @private
     * @param {Object} options
     *     see the module JSDoc for detail on what can be passed in.
     */
    function Middleware(options) {
        this.options = $.extend(
            true,
            {},
            ajaxMiddleware.DEFAULT_OPTIONS,
            options
        );
        this.name = this.options.name;
        this.id = generateID();

        /* For some reason, jQuery lower cases all dataTypes on ajaxPrefilters,
         * but later does not do the same when checking ajaxTransports.
         * To make it work, we need to be consistent with that and always use
         * lower case.
         */
        this.dataType = (this.name + this.id).toLowerCase();

        this.activate();
    }

    /**
     * Handles the business of installing the middleware.
     *
     * @private
     * @returns {void}
     */
    function installMiddleware(middleware) {
        /* Install this middleware in the registry so we can track it
         */
        registry.add(middleware);

        /* First we need to install a prefilter in order to give the
         * transport handler a place to hook in
         */
        $.ajaxPrefilter(
            middleware.options.dataTypes,
            createPrefilter(middleware)
        );

        /* Now we can catch the new dataType we just inserted and call
         * our hooks
         */
        $.ajaxTransport(
            middleware.dataType,
            createTransport(middleware)
        );
    }

    /**
     * The module itself.
     *
     * @constructor
     * @param {Object} options
     */
    function ajaxMiddleware(options) {
        return new Middleware(options);
    }

    /* ajaxMiddleware is the public face of the internal Middleware
     * object, and they need to evaluate to the same type (or instances of
     * either should pass the instanceof test with the other). Setting their
     * prototypes to the same object will ensure that this happens, as defined
     * by ECMA-262 sections 11.8.6 and 15.3.5.3.
     */
    ajaxMiddleware.prototype = Middleware.prototype = {

        /* ------------------------------------------------------------------ *
         *                          PUBLIC API
         * ------------------------------------------------------------------ */

        /**
         * Activate this middleware class.
         */
        activate: function() {
            this._active = true;
            if (!(this.id in registry.ids)) {
                installMiddleware(this);
            }
        },

        /**
         * Deactivate this middleware class.
         */
        deactivate: function() {
            this._active = false;
        },

        /**
         * @returns {boolean} whether the middleware is currently active
         */
        isActive: function() {
            return this._active;
        },

        /**
         * @returns {String} a simple string representation
         */
        toString: function() {
            return [
                '<ajaxMiddleware: ',
                this.dataType,
                ', ',
                this.id,
                '>'
            ].join('');
        }

    };

    /* ---------------------------------------------------------------------- *
     *                          PUBLIC MEMBERS
     * ---------------------------------------------------------------------- */
    $.extend(ajaxMiddleware, {
        /**
         * The default options for middleware. These values are all
         * overrideable. See the documentation for individual attributes to
         * understand what to pass in.
         *
         * @type {Object}
         */
        DEFAULT_OPTIONS: {
            /**
             * The namespace used for this middleware object.
             *
             * @type {String}
             */
            name: 'ajaxMiddleware',

            /**
             * The dataTypes to be handled.
             *
             * @type {String}
             */
            dataTypes: '*',

            /**
             * A predicate function to determine whether this middleware
             * should process this request.
             *
             * @param {Object} options
             *     The fully merged AJAX options for this request
             * @param {Object} originalOptions
             *     The original AJAX options specified by the user
             * @param {jqXHR} jqXHR
             *     The jQuery-wrapped XMLHttpResponse (or similar) object
             *
             * @returns {boolean} whether the request should be processed.
             */
            shouldIntercept: function() {
                // Process all requests for the dataTypes by default.
                return true;
            },

            /**
             * Hook to modify the parameters to be sent to completeCallback.
             *
             * @param {Object} completeParams
             *     Has four items, each corresponding to one of the arguments
             *     to completeCallback as defined by jQuery. It uses the names
             *     that jQuery gives them, but they are as follows:
             *         status: the HTTP status code of the response
             *         statusText: the text for the status (e.g. Internal
             *             Server Error)
             *         responses: (optional) is an object containing
             *             dataType->value mappings that contains the response
             *             in all the formats the transport could provide.
             *         headers: (optional) is a string containing all the
             *             response headers if the transport has access to
             *             them.
             * @param {Object} options
             *     The AJAX options for the intercepted call.
             * @param {Object} originalOptions
             *     The original AJAX options for the intercepted call.
             */
            beforeComplete: function() {
                /* Do nothing by default. */

                /* If we modify completeParams here, it will affect the values
                 * that are passed to the completeCallback.
                 */
            }
        },

        /**
         * @param {String} name
         *     the name of the middleware type in question, or '*' to return
         *     all registered middleware.
         * @param {boolean} [includeDeactivated=false]
         *     whether or not to include deactivated middleware in results
         * @returns {ajaxMiddleware[]}
         *     an array of all middleware registered using that name. If
         *     includeDeactivated is true, it will include the deactivated
         *     middleware as well.
         */
        getByName: function(name, includeDeactivated) {
            var results = [];
            if (name === '*') {
                for (var mwName in registry.names) {
                    if (registry.names.hasOwnProperty(mwName)) {
                        $.merge(results,
                                this.getByName(mwName, includeDeactivated));
                    }
                }
            } else {
                $.merge(results, registry.names[name] || []);
                results = $.grep(results, function(middleware) {
                    return includeDeactivated || middleware._active;
                });
            }
            return results;
        },

        _jQuery: $
    });

    /* Install for normal jQuery */
    $.ajaxMiddleware = ajaxMiddleware;

    /* Return for AMD */
    return ajaxMiddleware;
},
this));
<|endoftext|>


<commit_before>define(function(require){

	var Constructor = require('../managerAPI');
	var _ = require('underscore');
	var isDev = /^(localhost|127.0.0.1)/.test(location.host) || window.DEBUG;
	var decorator = require('APIs/pi/APIdecorator');

	var messageTemplate = require('text!./messageTemplate.jst');
	var messageTemplateDebrief = require('text!./messageTemplateDebrief.jst');
	var messageTemplatePanel = require('text!./messageTemplatePanel.jst');

	/**
	 * Constructor for PIPlayer script creator
	 * @return {Object}		Script creator
	 */
	function API(){
		Constructor.call(this);
		this.settings.onPreTask = onPreTask;
	}

	decorator(API);

	// create API functions
	_.extend(API.prototype, Constructor.prototype);

	// annotate onPreTask
	onPreTask.$inject = ['currentTask', '$http','$rootScope','managerBeforeUnload','templateDefaultContext'];

	return API;

	/**
	 * Before each task,
	 * 		post to /implicit/PiManager
	 * 		in case this is a quest/pip added logging meta
	 *
	 * @param  {Object} currentTask The task Object
	 * @param  {Object} $http       The $http service
	 * @return {Promise}            Resolved when server responds
	 */
	function onPreTask(currentTask, $http, $rootScope, beforeUnload, templateDefaultContext){

		var global = $rootScope.global;
		var settings, context;
		var data = {taskName: currentTask.name || 'namelessTask', taskNumber: currentTask.$meta.number};

		// add logging meta
		if (currentTask.type == 'quest' || currentTask.type == 'pip'){
			currentTask.$script.serial = currentTask.$meta.number;
			settings = currentTask.$script.settings;
			settings.logger = settings.logger || {};
			settings.logger.meta = angular.extend(settings.logger.meta || {}, data);
		}

		// add feedback

		_.extend(templateDefaultContext,{
			showFeedback: _.bind(showFeedback,null,global),
			showPanel: showPanel
		});

		if (currentTask.type == 'message' && currentTask.piTemplate){
			context = {
				content: currentTask.$template,
				global: global,
				current: global.current,
				task: currentTask
			};

			if (currentTask.piTemplate == 'debrief'){
				currentTask.$template = _.template(messageTemplateDebrief)(context); // insert into meta template
				currentTask.$template = _.template(currentTask.$template)(context); // render secondary template with extended context
			} else {
				currentTask.$template = _.template(messageTemplate)(context); // insert into meta template
			}
		}

		// set last task flag
		if (currentTask.last){
			data.sessionStatus = "C";
			beforeUnload.deactivate();
		}

		if (currentTask.last && global.$mTurk){
			var $mTurk = global.$mTurk;
			var url = $mTurk.isProduction ?  'http://www.mturk.com/mturk/externalSubmit' : 'https://workersandbox.mturk.com/mturk/externalSubmit';
			var onPost = currentTask.post || _.noop;

			if (!_.every(['assignmentId','hitId','workerId'], function(prop){return _.has($mTurk, prop);})){
				throw new Error ('$mTurk is missing a crucial property (assignmentId,hitId,workerId)');
			}

			currentTask.post = function(){
				onPost.apply(this, arguments);
				postRedirect(url,_.omit($mTurk,'isProduction'));
			};
		}

		if (window._err && window._err.meta){
			var meta = window._err.meta;
			meta.subtaskName = currentTask.name;
			meta.subtaskURL = currentTask.scriptUrl || currentTask.templateUrl;
		}

		return isDev ? true : $http.post('/implicit/PiManager', data);
	}

	function postRedirect(path, params, method) {
	    method = method || "post"; // Set method to post by default if not specified.

	    // The rest of this code assumes you are not using a library.
	    // It can be made less wordy if you use one.
	    var form = document.createElement("form");
	    form.setAttribute("method", method);
	    form.setAttribute("action", path);

	    for(var key in params) {
	        if(params.hasOwnProperty(key)) {
	            var hiddenField = document.createElement("input");
	            hiddenField.setAttribute("type", "hidden");
	            hiddenField.setAttribute("name", key);
	            hiddenField.setAttribute("value", params[key]);

	            form.appendChild(hiddenField);
	         }
	    }

	    document.body.appendChild(form);
	    form.submit();
	}

	function showPanel(content, header, footer){
		return _.template(messageTemplatePanel, {
			content: content,
			header: header,
			footer: footer
		});
	}

	function showFeedback(global, options){
		_.isPlainObject(options) || (options = {});

		_.defaults(options,{
			pre: '
',
			post: '
',
			wrap: true,
			header: '',
			noFeedback: '
No feedback was found
',
			property: 'feedback',
			exclude: []
		});

		var feedback = _(global)
			.filter(function(task,taskName){
				if (!_.isArray(options.exclude)) { throw Error('Exclude must be an array'); }
				// make sure task is an object
				// make sure feedback is defined
				// make sure this task is not excluded
				return _.isPlainObject(task) && !_.isUndefined(task[options.property]) && (_.indexOf(options.exclude,taskName) === -1);
			})
			.mapValues(function(task){
				return options.pre + task[options.property] + options.post;
			})
			.reduce(function(result, feedback){
				return result + feedback;
			},'') || options.noFeedback;

		return options.wrap ? showPanel(feedback, options.header) : feedback;
	}
});

<commit_msg>fix(PI): fixed templating for showFeedback<commit_after>define(function(require){

	var Constructor = require('../managerAPI');
	var _ = require('underscore');
	var isDev = /^(localhost|127.0.0.1)/.test(location.host) || window.DEBUG;
	var decorator = require('APIs/pi/APIdecorator');

	var messageTemplate = require('text!./messageTemplate.jst');
	var messageTemplateDebrief = require('text!./messageTemplateDebrief.jst');
	var messageTemplatePanel = require('text!./messageTemplatePanel.jst');

	/**
	 * Constructor for PIPlayer script creator
	 * @return {Object}		Script creator
	 */
	function API(){
		Constructor.call(this);
		this.settings.onPreTask = onPreTask;
	}

	decorator(API);

	// create API functions
	_.extend(API.prototype, Constructor.prototype);

	// annotate onPreTask
	onPreTask.$inject = ['currentTask', '$http','$rootScope','managerBeforeUnload','templateDefaultContext'];

	return API;

	/**
	 * Before each task,
	 * 		post to /implicit/PiManager
	 * 		in case this is a quest/pip added logging meta
	 *
	 * @param  {Object} currentTask The task Object
	 * @param  {Object} $http       The $http service
	 * @return {Promise}            Resolved when server responds
	 */
	function onPreTask(currentTask, $http, $rootScope, beforeUnload, templateDefaultContext){

		var global = $rootScope.global;
		var settings, context = {};
		var data = {taskName: currentTask.name || 'namelessTask', taskNumber: currentTask.$meta.number};

		// add logging meta
		if (currentTask.type == 'quest' || currentTask.type == 'pip'){
			currentTask.$script.serial = currentTask.$meta.number;
			settings = currentTask.$script.settings;
			settings.logger = settings.logger || {};
			settings.logger.meta = angular.extend(settings.logger.meta || {}, data);
		}

		// add feedback

		_.extend(templateDefaultContext,{
			showFeedback: _.bind(showFeedback,null,global),
			showPanel: showPanel
		});

		if (currentTask.type == 'message' && currentTask.piTemplate){
			_.extend(context, templateDefaultContext, {
				content: currentTask.$template,
				global: global,
				current: global.current,
				task: currentTask
			});

			if (currentTask.piTemplate == 'debrief'){
				currentTask.$template = _.template(messageTemplateDebrief)(context); // insert into meta template
				currentTask.$template = _.template(currentTask.$template)(context); // render secondary template with extended context
			} else {
				currentTask.$template = _.template(messageTemplate)(context); // insert into meta template
			}
		}

		// set last task flag
		if (currentTask.last){
			data.sessionStatus = "C";
			beforeUnload.deactivate();
		}

		if (currentTask.last && global.$mTurk){
			var $mTurk = global.$mTurk;
			var url = $mTurk.isProduction ?  'http://www.mturk.com/mturk/externalSubmit' : 'https://workersandbox.mturk.com/mturk/externalSubmit';
			var onPost = currentTask.post || _.noop;

			if (!_.every(['assignmentId','hitId','workerId'], function(prop){return _.has($mTurk, prop);})){
				throw new Error ('$mTurk is missing a crucial property (assignmentId,hitId,workerId)');
			}

			currentTask.post = function(){
				onPost.apply(this, arguments);
				postRedirect(url,_.omit($mTurk,'isProduction'));
			};
		}

		if (window._err && window._err.meta){
			var meta = window._err.meta;
			meta.subtaskName = currentTask.name;
			meta.subtaskURL = currentTask.scriptUrl || currentTask.templateUrl;
		}

		return isDev ? true : $http.post('/implicit/PiManager', data);
	}

	function postRedirect(path, params, method) {
	    method = method || "post"; // Set method to post by default if not specified.

	    // The rest of this code assumes you are not using a library.
	    // It can be made less wordy if you use one.
	    var form = document.createElement("form");
	    form.setAttribute("method", method);
	    form.setAttribute("action", path);

	    for(var key in params) {
	        if(params.hasOwnProperty(key)) {
	            var hiddenField = document.createElement("input");
	            hiddenField.setAttribute("type", "hidden");
	            hiddenField.setAttribute("name", key);
	            hiddenField.setAttribute("value", params[key]);

	            form.appendChild(hiddenField);
	         }
	    }

	    document.body.appendChild(form);
	    form.submit();
	}

	function showPanel(content, header, footer){
		return _.template(messageTemplatePanel)({
			content: content,
			header: header,
			footer: footer
		});
	}

	function showFeedback(global, options){
		_.isPlainObject(options) || (options = {});

		_.defaults(options,{
			pre: '
',
			post: '
',
			wrap: true,
			header: '',
			noFeedback: '
No feedback was found
',
			property: 'feedback',
			exclude: []
		});

		var feedback = _(global)
			.filter(function(task,taskName){
				if (!_.isArray(options.exclude)) { throw Error('Exclude must be an array'); }
				// make sure task is an object
				// make sure feedback is defined
				// make sure this task is not excluded
				return _.isPlainObject(task) && !_.isUndefined(task[options.property]) && (_.indexOf(options.exclude,taskName) === -1);
			})
			.mapValues(function(task){
				return options.pre + task[options.property] + options.post;
			})
			.reduce(function(result, feedback){
				return result + feedback;
			},'') || options.noFeedback;

		return options.wrap ? showPanel(feedback,options.header,options.footer) : feedback;
	}
});

<|endoftext|>


<commit_before>/*
  Aras.HTML5 provides a HTML5 client library to build Aras Innovator Applications

  Copyright (C) 2015 Processwall Limited.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as published
  by the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see http://opensource.org/licenses/AGPL-3.0.

  Company: Processwall Limited
  Address: The Winnowing House, Mill Lane, Askham Richard, York, YO23 3NW, United Kingdom
  Tel:     +44 113 815 3440
  Email:   support@processwall.com
*/

define([
	'dojo/_base/declare',
	'dojo/_base/lang',
	'./Control'
], function(declare, lang, Control) {

	return declare('Aras.View.Property', [Control], {

		constructor: function() {

		},

		startup: function() {
			this.inherited(arguments);

		},

		OnViewModelLoaded: function() {
			this.inherited(arguments);

			this.set("readOnly", this.ViewModel.Enabled);
		}

	});
});<commit_msg>Updated Property Enabled<commit_after>/*
  Aras.HTML5 provides a HTML5 client library to build Aras Innovator Applications

  Copyright (C) 2015 Processwall Limited.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Affero General Public License as published
  by the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License
  along with this program.  If not, see http://opensource.org/licenses/AGPL-3.0.

  Company: Processwall Limited
  Address: The Winnowing House, Mill Lane, Askham Richard, York, YO23 3NW, United Kingdom
  Tel:     +44 113 815 3440
  Email:   support@processwall.com
*/

define([
	'dojo/_base/declare',
	'dojo/_base/lang',
	'./Control'
], function(declare, lang, Control) {

	return declare('Aras.View.Property', [Control], {

		constructor: function() {

		},

		startup: function() {
			this.inherited(arguments);

		},

		OnViewModelLoaded: function() {
			this.inherited(arguments);

			this.set("readOnly", !this.ViewModel.Enabled);
		}

	});
});<|endoftext|>


<commit_before>/**
 *
 * Adapted from https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/STLLoader.js
 * to use in our context.
 *
 * Only keeps the parsing part, no Loader class
 */

'use strict';

var THREE = require('three');

function isBinary(binData) {

    var expect;
    var face_size;
    var n_faces;
    var reader;
    reader = new DataView(binData);
    face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
    n_faces = reader.getUint32(80, true);
    expect = 80 + (32 / 8) + (n_faces * face_size);

    if (expect === reader.byteLength) {
        return true;
    }
    // some binary files will have different size from expected,
    // checking characters higher than ASCII to confirm is binary
    var fileLength = reader.byteLength;
    for (var index = 0; index < fileLength; index++) {
        if (reader.getUint8(index, false) > 127) {
            return true;
        }
    }

    return false;
}

function parse(data) {
    var binData = ensureBinary(data);
    return isBinary(binData) ?
           parseBinary(binData) : parseASCII(ensureString(data));
}

function parseBinary(data) {

    var reader = new DataView(data);
    var faces = reader.getUint32(80, true);

    var r;
    var g;
    var b;
    var hasColors = false;
    var colors;
    var defaultR;
    var defaultG;
    var defaultB;
    var alpha;

    // process STL header
    // check for default color in header ("COLOR=rgba" sequence).
    for (var index = 0; index < 80 - 10; index++) {
        if ((reader.getUint32(index, false) === 0x434F4C4F /*COLO*/ ) &&
            (reader.getUint8(index + 4) === 0x52 /*'R'*/ ) &&
            (reader.getUint8(index + 5) === 0x3D /*'='*/ )) {

            hasColors = true;
            colors = new Float32Array(faces * 3 * 3);

            defaultR = reader.getUint8(index + 6) / 255;
            defaultG = reader.getUint8(index + 7) / 255;
            defaultB = reader.getUint8(index + 8) / 255;
            alpha = reader.getUint8(index + 9) / 255;
        }
    }

    var dataOffset = 84;
    var faceLength = 12 * 4 + 2;

    var offset = 0;

    var geometry = new THREE.BufferGeometry();

    var vertices = new Float32Array(faces * 3 * 3);
    var normals = new Float32Array(faces * 3 * 3);

    for (var face = 0; face < faces; face++) {
        var start = dataOffset + face * faceLength;
        var normalX = reader.getFloat32(start, true);
        var normalY = reader.getFloat32(start + 4, true);
        var normalZ = reader.getFloat32(start + 8, true);

        if (hasColors) {
            var packedColor = reader.getUint16(start + 48, true);
            if ((packedColor & 0x8000) === 0) { // facet has its own unique color
                r = (packedColor & 0x1F) / 31;
                g = ((packedColor >> 5) & 0x1F) / 31;
                b = ((packedColor >> 10) & 0x1F) / 31;
            } else {
                r = defaultR;
                g = defaultG;
                b = defaultB;
            }
        }

        for (var i = 1; i <= 3; i++) {
            var vertexstart = start + i * 12;

            vertices[offset] = reader.getFloat32(vertexstart, true);
            vertices[offset + 1] = reader.getFloat32(vertexstart + 4, true);
            vertices[offset + 2] = reader.getFloat32(vertexstart + 8, true);

            normals[offset] = normalX;
            normals[offset + 1] = normalY;
            normals[offset + 2] = normalZ;

            if (hasColors) {
                colors[offset] = r;
                colors[offset + 1] = g;
                colors[offset + 2] = b;
            }

            offset += 3;
        }
    }

    geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
    geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));

    if (hasColors) {
        geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
        geometry.hasColors = true;
        geometry.alpha = alpha;
    }

    return geometry;
}

function parseASCII(data) {

    var geometry;
    var length;
    var normal;
    var patternFace;
    var patternNormal;
    var patternVertex;
    var result;
    var text;
    geometry = new THREE.Geometry();
    patternFace = /facet([\s\S]*?)endfacet/g;

    while ((result = patternFace.exec(data)) !== null) {

        text = result[0];
        patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

        while ((result = patternNormal.exec(text)) !== null) {
            normal = new THREE.Vector3(parseFloat(result[1]), parseFloat(result[3]), parseFloat(result[5]));
        }

        patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

        while ((result = patternVertex.exec(text)) !== null) {
            geometry.vertices.push(new THREE.Vector3(parseFloat(result[1]), parseFloat(result[3]), parseFloat(result[5])));
        }

        length = geometry.vertices.length;

        geometry.faces.push(new THREE.Face3(length - 3, length - 2, length - 1, normal));

    }

    geometry.computeBoundingBox();
    geometry.computeBoundingSphere();

    return geometry;

}

function ensureString(buf) {

    if (typeof buf !== "string") {
        var array_buffer = new Uint8Array(buf);
        var str = '';
        for (var i = 0; i < buf.byteLength; i++) {
            str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian
        }
        return str;
    } else {
        return buf;
    }

}

function ensureBinary(buf) {

    if (typeof buf === "string") {
        var array_buffer = new Uint8Array(buf.length);
        for (var i = 0; i < buf.length; i++) {
            array_buffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian
        }
        return array_buffer.buffer || array_buffer;
    } else {
        return buf;
    }

}

module.exports = parse;
<commit_msg>Update STL ASCII Parser<commit_after>/**
 *
 * Adapted from https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/STLLoader.js
 * to use in our context.
 *
 * Only keeps the parsing part, no Loader class
 */

'use strict';

var THREE = require('three');

function isBinary(binData) {

    var expect;
    var faceSize;
    var nFaces;
    var reader;
    reader = new DataView(binData);
    faceSize = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
    nFaces = reader.getUint32(80, true);
    expect = 80 + (32 / 8) + (nFaces * faceSize);

    if (expect === reader.byteLength) {
        return true;
    }
    // some binary files will have different size from expected,
    // checking characters higher than ASCII to confirm is binary
    var fileLength = reader.byteLength;
    for (var index = 0; index < fileLength; index++) {
        if (reader.getUint8(index, false) > 127) {
            return true;
        }
    }

    return false;
}

function parse(data) {
    console.time('STLLoader');
    var binData = ensureBinary(data);
    const geo = isBinary(binData) ?
                parseBinary(binData) : parseASCII(ensureString(data));
    console.time('STLLoader');
    return geo;
}

function parseBinary(data) {

    var reader = new DataView(data);
    var faces = reader.getUint32(80, true);

    var r;
    var g;
    var b;
    var hasColors = false;
    var colors;
    var defaultR;
    var defaultG;
    var defaultB;
    var alpha;

    // process STL header
    // check for default color in header ("COLOR=rgba" sequence).
    for (var index = 0; index < 80 - 10; index++) {
        if ((reader.getUint32(index, false) === 0x434F4C4F /*COLO*/ ) &&
            (reader.getUint8(index + 4) === 0x52 /*'R'*/ ) &&
            (reader.getUint8(index + 5) === 0x3D /*'='*/ )) {

            hasColors = true;
            colors = new Float32Array(faces * 3 * 3);

            defaultR = reader.getUint8(index + 6) / 255;
            defaultG = reader.getUint8(index + 7) / 255;
            defaultB = reader.getUint8(index + 8) / 255;
            alpha = reader.getUint8(index + 9) / 255;
        }
    }

    var dataOffset = 84;
    var faceLength = 12 * 4 + 2;

    var offset = 0;

    var geometry = new THREE.BufferGeometry();

    var vertices = new Float32Array(faces * 3 * 3);
    var normals = new Float32Array(faces * 3 * 3);

    for (var face = 0; face < faces; face++) {
        var start = dataOffset + face * faceLength;
        var normalX = reader.getFloat32(start, true);
        var normalY = reader.getFloat32(start + 4, true);
        var normalZ = reader.getFloat32(start + 8, true);

        if (hasColors) {
            var packedColor = reader.getUint16(start + 48, true);
            if ((packedColor & 0x8000) === 0) { // facet has its own unique color
                r = (packedColor & 0x1F) / 31;
                g = ((packedColor >> 5) & 0x1F) / 31;
                b = ((packedColor >> 10) & 0x1F) / 31;
            } else {
                r = defaultR;
                g = defaultG;
                b = defaultB;
            }
        }

        for (var i = 1; i <= 3; i++) {
            var vertexstart = start + i * 12;

            vertices[offset] = reader.getFloat32(vertexstart, true);
            vertices[offset + 1] = reader.getFloat32(vertexstart + 4, true);
            vertices[offset + 2] = reader.getFloat32(vertexstart + 8, true);

            normals[offset] = normalX;
            normals[offset + 1] = normalY;
            normals[offset + 2] = normalZ;

            if (hasColors) {
                colors[offset] = r;
                colors[offset + 1] = g;
                colors[offset + 2] = b;
            }

            offset += 3;
        }
    }

    geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
    geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));

    if (hasColors) {
        geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
        geometry.hasColors = true;
        geometry.alpha = alpha;
    }

    geometry.computeBoundingBox();
    geometry.computeBoundingSphere();
    return geometry;
}

function parseASCII(data) {

    let geometry,
        nx, ny, nz, vx, vy, vz,
        patternFace, patternNormal, patternVertex,
        result, text;

    const normals = [], vertices = [];

    geometry = new THREE.BufferGeometry();

    patternFace = /facet([\s\S]*?)endfacet/g;

    while ((result = patternFace.exec(data)) !== null) {

        text = result[0];
        patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

        while ((result = patternNormal.exec(text)) !== null) {
            [nx, ny, nz] = [result[1], result[3], result[5]].map(parseFloat);
        }

        patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;

        while ((result = patternVertex.exec(text)) !== null) {
            [vx, vy, vz] = [result[1], result[3], result[5]].map(parseFloat);
            vertices.push(vx, vy, vz);
            normals.push(nx, ny, nz);
        }
    }

    geometry.addAttribute(
        'position',
        new THREE.BufferAttribute(new Float32Array(vertices), 3)
    );

    geometry.addAttribute(
        'normal',
        new THREE.BufferAttribute(new Float32Array(normals), 3)
    );

    geometry.computeBoundingBox();
    geometry.computeBoundingSphere();

    return geometry;

}

function ensureString(buf) {

    if (typeof buf !== "string") {
        var arrayBuffer = new Uint8Array(buf);
        var str = '';
        for (var i = 0; i < buf.byteLength; i++) {
            str += String.fromCharCode(arrayBuffer[i]); // implicitly assumes little-endian
        }
        return str;
    } else {
        return buf;
    }

}

function ensureBinary(buf) {

    if (typeof buf === "string") {
        var arrayBuffer = new Uint8Array(buf.length);
        for (var i = 0; i < buf.length; i++) {
            arrayBuffer[i] = buf.charCodeAt(i) & 0xff; // implicitly assumes little-endian
        }
        return arrayBuffer.buffer || arrayBuffer;
    } else {
        return buf;
    }

}

module.exports = parse;
<|endoftext|>


<commit_before>module.exports = function($stateProvider) {

  $stateProvider
  .state('network', {
    url: "/n/:slug",
    parent: 'main',
    abstract: true,
    resolve: {
      network: /*@ngInject*/ function(Network, $stateParams) {
        return Network.get({id: $stateParams.slug}).$promise;
      }
    },
    views: {
      main: {
        templateUrl: '/ui/app/network.tpl.html',
        controller: function($scope, network) {
          'ngInject';
          $scope.network = network;
        }
      }
    }
  })
  .state('network.posts', {
    url: '',
    resolve: {
      firstPostQuery: /*@ngInject*/ function(network, Seed) {
        return Seed.queryForNetwork({id: network.id, limit: 10}).$promise;
      }
    },
    views: {
      tab: {
        templateUrl: '/ui/network/posts.tpl.html',
        controller: function($scope, network, firstPostQuery, PostManager, Seed) {
          'ngInject';

          var postManager = new PostManager({
            firstPage: firstPostQuery,
            scope: $scope,
            attr: 'posts',
            query: function() {
              return Seed.queryForNetwork({
                id: network.id,
                limit: 10,
                offset: $scope.posts.length
              }).$promise;
            }
          });

          postManager.setup();
        }
      }
    }
  })
  .state('network.communities', {
    url: '/communities',
    resolve: {
      communities: function(network) {
        return network.communities().$promise;
      }
    },
    views: {
      tab: {
        templateUrl: '/ui/network/communities.tpl.html',
        controller: function($scope, communities) {
          'ngInject';
          $scope.communities = communities;
        }
      }
    }
  })
  .state('network.about', {
    url: '/about',
    resolve: {

    },
    views: {
      tab: {
        templateUrl: '/ui/network/about.tpl.html',
        controller: function($scope, network) {
          'ngInject';
          $scope.network = network;
        }
      }
    }
  });

};<commit_msg>missed an ngInject<commit_after>module.exports = function($stateProvider) {

  $stateProvider
  .state('network', {
    url: "/n/:slug",
    parent: 'main',
    abstract: true,
    resolve: {
      network: /*@ngInject*/ function(Network, $stateParams) {
        return Network.get({id: $stateParams.slug}).$promise;
      }
    },
    views: {
      main: {
        templateUrl: '/ui/app/network.tpl.html',
        controller: function($scope, network) {
          'ngInject';
          $scope.network = network;
        }
      }
    }
  })
  .state('network.posts', {
    url: '',
    resolve: {
      firstPostQuery: /*@ngInject*/ function(network, Seed) {
        return Seed.queryForNetwork({id: network.id, limit: 10}).$promise;
      }
    },
    views: {
      tab: {
        templateUrl: '/ui/network/posts.tpl.html',
        controller: function($scope, network, firstPostQuery, PostManager, Seed) {
          'ngInject';

          var postManager = new PostManager({
            firstPage: firstPostQuery,
            scope: $scope,
            attr: 'posts',
            query: function() {
              return Seed.queryForNetwork({
                id: network.id,
                limit: 10,
                offset: $scope.posts.length
              }).$promise;
            }
          });

          postManager.setup();
        }
      }
    }
  })
  .state('network.communities', {
    url: '/communities',
    resolve: {
      communities: /*@ngInject*/ function(network) {
        return network.communities().$promise;
      }
    },
    views: {
      tab: {
        templateUrl: '/ui/network/communities.tpl.html',
        controller: function($scope, communities) {
          'ngInject';
          $scope.communities = communities;
        }
      }
    }
  })
  .state('network.about', {
    url: '/about',
    resolve: {

    },
    views: {
      tab: {
        templateUrl: '/ui/network/about.tpl.html',
        controller: function($scope, network) {
          'ngInject';
          $scope.network = network;
        }
      }
    }
  });

};<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
'use strict';

/*
 * In generic cameras using Chrome rescaler, all resolutions should be supported
 * up to a given one and none beyond there. Special cameras, such as digitizers,
 * might support only one resolution.
 */

/*
 * "Analyze performance for "resolution"" test uses getStats, canvas and the
 * video element to analyze the video frames from a capture device. It will
 * report number of black frames, frozen frames, tested frames and various stats
 * like average encode time and FPS. A test case will be created per mandatory
 * resolution found in the "resolutions" array.
 */

addTest(testSuiteName.CAMERA, testCaseName.CHECKRESOLUTION240, function(test) {
  var camResolutionsTest = new CamResolutionsTest(test , [[320, 240]]);
  camResolutionsTest.run();
});

addTest(testSuiteName.CAMERA, testCaseName.CHECKRESOLUTION480, function(test) {
  var camResolutionsTest = new CamResolutionsTest(test, [[640, 480]]);
  camResolutionsTest.run();
});

addTest(testSuiteName.CAMERA, testCaseName.CHECKRESOLUTION720, function(test) {
  var camResolutionsTest = new CamResolutionsTest(test, [[1280, 720]]);
  camResolutionsTest.run();
});

addTest(
  testSuiteName.CAMERA,testCaseName.CHECKSUPPORTEDRESOLUTIONS, function(test) {
    var resolutionArray = [
      [160, 120], [320, 180], [320, 240], [640, 360], [640, 480], [768, 576],
      [1024, 576], [1280, 720], [1280, 768], [1280, 800], [1920, 1080],
      [1920, 1200], [3840, 2160], [4096, 2160]
    ];
    var camResolutionsTest = new CamResolutionsTest(test, resolutionArray);
    camResolutionsTest.run();
});

function CamResolutionsTest(test, resolutions) {
  this.test = test;
  this.resolutions = resolutions;
  this.currentResolution = 0;
  this.isMuted = false;
}

CamResolutionsTest.prototype = {
  run: function() {
    this.startGetUserMedia(this.resolutions[this.currentResolution]);
  },

  startGetUserMedia: function(resolution) {
    var constraints = {
      audio: false,
      video: {width: {exact: resolution[0]},
          height: {exact: resolution[1]}}
    };
    navigator.mediaDevices.getUserMedia(constraints)
    .then(function(stream) {
      // Do not check actual video frames when more than one resolution is
      // provided.
      if (this.resolutions.length > 1) {
        this.test.reportSuccess('Supported: ' + resolution[0] + 'x' +
            resolution[1]);
        stream.getTracks().forEach(function(track) {track.stop();});
        this.maybeContinueGetUserMedia();
      } else {
        this.collectAndAnalyzeStats_(stream, resolution);
      }
    }.bind(this))
    .catch(function(error) {
      if (this.resolutions.length > 1) {
        this.test.reportInfo(resolution[0] + 'x' + resolution[1] +
            ' not supported');
        this.maybeContinueGetUserMedia();
      } else {
        this.test.reportError('getUserMedia failed with error: ' + error.name);
        this.test.done();
      }
    }.bind(this));
  },

  maybeContinueGetUserMedia: function() {
    if (this.currentResolution === this.resolutions.length) {
      this.test.done();
      return;
    }
    this.startGetUserMedia(this.resolutions[this.currentResolution++]);
  },

  collectAndAnalyzeStats_: function(stream, resolution) {
    var tracks = stream.getVideoTracks();
    if (tracks.length < 1) {
      this.test.reportError('No video track in returned stream.');
      this.finishTestOrRetrigger_();
      return;
    }

    var videoTrack = tracks[0];
    // Register events.
    videoTrack.addEventListener('ended', function() {
      this.test.reportError('Video track ended, camera stopped working');
    });
    videoTrack.addEventListener('mute', function() {
      this.test.reportError('Your camera reported itself as muted.');
      // MediaStreamTrack.muted property is not wired up in Chrome yet, checking
      // isMuted local state.
      this.isMuted = true;
    });
    videoTrack.addEventListener('unmute', function() {
      this.isMuted = false;
    });

    var video = document.createElement('video');
    video.setAttribute('autoplay', '');
    video.setAttribute('muted', '');
    video.width = resolution[0];
    video.height = resolution[1];
    attachMediaStream(video, stream);
    var frameChecker = new VideoFrameChecker(video);
    var call = new Call();
    call.pc1.addStream(stream);
    call.establishConnection();
    call.gatherStats(call.pc1,
                     this.onCallEnded_.bind(this, resolution, video,
                                            stream, frameChecker),
                     100);

    setTimeoutWithProgressBar(call.close.bind(call), 8000);
  },

  onCallEnded_: function(resolution, videoElement, stream, frameChecker,
                         stats, statsTime) {
    this.analyzeStats_(resolution, videoElement, stream, frameChecker,
                       stats, statsTime);

    frameChecker.stop();

    stream.getTracks().forEach(function(track) {
      track.stop();
    });
    this.test.done();
  },

  analyzeStats_: function(resolution, videoElement, stream,
                          frameChecker, stats, statsTime) {
    var googAvgEncodeTime = [];
    var googAvgFrameRateInput = [];
    var googAvgFrameRateSent = [];
    var statsReport = {};
    var frameStats = frameChecker.frameStats;

    for (var index = 0; index < stats.length - 1; index++) {
      if (stats[index].type === 'ssrc') {
        // Make sure to only capture stats after the encoder is setup.
        if (stats[index].stat('googFrameRateInput') > 0) {
          googAvgEncodeTime.push(
              parseInt(stats[index].stat('googAvgEncodeMs')));
          googAvgFrameRateInput.push(
              parseInt(stats[index].stat('googFrameRateInput')));
          googAvgFrameRateSent.push(
              parseInt(stats[index].stat('googFrameRateSent')));
        }
      }
    }

    statsReport.cameraName = stream.getVideoTracks()[0].label || NaN;
    statsReport.actualVideoWidth = videoElement.videoWidth;
    statsReport.actualVideoHeight = videoElement.videoHeight;
    statsReport.mandatoryWidth = resolution[0];
    statsReport.mandatoryHeight = resolution[1];
    statsReport.encodeSetupTimeMs =
        this.extractEncoderSetupTime_(stats, statsTime);
    statsReport.avgEncodeTimeMs = arrayAverage(googAvgEncodeTime);
    statsReport.minEncodeTimeMs = arrayMin(googAvgEncodeTime);
    statsReport.maxEncodeTimeMs = arrayMax(googAvgEncodeTime);
    statsReport.avgInputFps = arrayAverage(googAvgFrameRateInput);
    statsReport.minInputFps = arrayMin(googAvgFrameRateInput);
    statsReport.maxInputFps = arrayMax(googAvgFrameRateInput);
    statsReport.avgSentFps = arrayAverage(googAvgFrameRateSent);
    statsReport.minSentFps = arrayMin(googAvgFrameRateSent);
    statsReport.maxSentFps = arrayMax(googAvgFrameRateSent);
    statsReport.isMuted = this.isMuted;
    statsReport.testedFrames = frameStats.numFrames;
    statsReport.blackFrames = frameStats.numBlackFrames;
    statsReport.frozenFrames = frameStats.numFrozenFrames;

    // TODO: Add a reportInfo() function with a table format to display
    // values clearer.
    report.traceEventInstant('video-stats', statsReport);

    this.testExpectations_(statsReport);
  },

  extractEncoderSetupTime_: function(stats, statsTime) {
    for (var index = 0; index !== stats.length; index++) {
      if (stats[index].type === 'ssrc') {
        if (stats[index].stat('googFrameRateInput') > 0) {
          return JSON.stringify(statsTime[index] - statsTime[0]);
        }
      }
    }
    return NaN;
  },

  resolutionMatchesIndependentOfRotationOrCrop_: function(aWidth, aHeight,
                                                       bWidth, bHeight) {
    var minRes = Math.min(bWidth, bHeight);
    return (aWidth === bWidth && aHeight === bHeight) ||
           (aWidth === bHeight && aHeight === bWidth) ||
           (aWidth === minRes && bHeight === minRes);
  },

  testExpectations_: function(info) {
    var notAvailableStats = [];
    for (var key in info) {
      if (info.hasOwnProperty(key)) {
        if (typeof info[key] === 'number' && isNaN(info[key])) {
          notAvailableStats.push(key);
        } else {
          this.test.reportInfo(key + ': ' + info[key]);
        }
      }
    }
    if (notAvailableStats.length !== 0) {
      this.test.reportInfo('Not available: ' + notAvailableStats.join(', '));
    }

    if (isNaN(info.avgSentFps)) {
      this.test.reportInfo('Cannot verify sent FPS.');
    } else if (info.avgSentFps < 5) {
      this.test.reportError('Low average sent FPS: ' + info.avgSentFps);
    } else {
      this.test.reportSuccess('Average FPS above threshold');
    }
    if (!this.resolutionMatchesIndependentOfRotationOrCrop_(
        info.actualVideoWidth, info.actualVideoHeight, info.mandatoryWidth,
        info.mandatoryHeight)) {
      this.test.reportError('Incorrect captured resolution.');
    } else {
      this.test.reportSuccess('Captured video using expected resolution.');
    }
    if (info.testedFrames === 0) {
      this.test.reportError('Could not analyze any video frame.');
    } else {
      if (info.blackFrames > info.testedFrames / 3) {
        this.test.reportError('Camera delivering lots of black frames.');
      }
      if (info.frozenFrames > info.testedFrames / 3) {
        this.test.reportError('Camera delivering lots of frozen frames.');
      }
    }
  }
};

//TODO: Move this to a separate file.
function VideoFrameChecker(videoElement) {
  this.frameStats = {
    numFrozenFrames: 0,
    numBlackFrames: 0,
    numFrames: 0
  };

  this.running_ = true;

  this.nonBlackPixelLumaThreshold = 20;
  this.previousFrame_ = [];
  this.identicalFrameSsimThreshold = 0.985;
  this.frameComparator = new Ssim();

  this.canvas_ = document.createElement('canvas');
  this.videoElement_ = videoElement;
  this.listener_ = this.checkVideoFrame_.bind(this);
  this.videoElement_.addEventListener('play', this.listener_, false);
}

VideoFrameChecker.prototype = {
  stop: function() {
    this.videoElement_.removeEventListener('play' , this.listener_);
    this.running_ = false;
  },

  getCurrentImageData_: function() {
    this.canvas_.width = this.videoElement_.width;
    this.canvas_.height = this.videoElement_.height;

    var context = this.canvas_.getContext('2d');
    context.drawImage(this.videoElement_, 0, 0, this.canvas_.width,
                      this.canvas_.height);
    return context.getImageData(0, 0, this.canvas_.width, this.canvas_.height);
  },

  checkVideoFrame_: function() {
    if (!this.running_) {
      return;
    }
    if (this.videoElement_.ended) {
      return;
    }

    var imageData = this.getCurrentImageData_();

    if (this.isBlackFrame_(imageData.data, imageData.data.length)) {
      this.frameStats.numBlackFrames++;
    }

    if (this.frameComparator.calculate(this.previousFrame_, imageData.data) >
        this.identicalFrameSsimThreshold) {
      this.frameStats.numFrozenFrames++;
    }
    this.previousFrame_ = imageData.data;

    this.frameStats.numFrames++;
    setTimeout(this.checkVideoFrame_.bind(this), 20);
  },

  isBlackFrame_: function(data, length) {
    // TODO: Use a statistical, histogram-based detection.
    var thresh = this.nonBlackPixelLumaThreshold;
    var accuLuma = 0;
    for (var i = 4; i < length; i += 4) {
      // Use Luma as in Rec. 709: Y′709 = 0.21R + 0.72G + 0.07B;
      accuLuma += 0.21 * data[i] +  0.72 * data[i + 1] + 0.07 * data[i + 2];
      // Early termination if the average Luma so far is bright enough.
      if (accuLuma > (thresh * i / 4)) {
        return false;
      }
    }
    return true;
  }
};
<commit_msg>Fixed indent take2 (run correct linter next time)<commit_after>/*
 *  Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */
'use strict';

/*
 * In generic cameras using Chrome rescaler, all resolutions should be supported
 * up to a given one and none beyond there. Special cameras, such as digitizers,
 * might support only one resolution.
 */

/*
 * "Analyze performance for "resolution"" test uses getStats, canvas and the
 * video element to analyze the video frames from a capture device. It will
 * report number of black frames, frozen frames, tested frames and various stats
 * like average encode time and FPS. A test case will be created per mandatory
 * resolution found in the "resolutions" array.
 */

addTest(testSuiteName.CAMERA, testCaseName.CHECKRESOLUTION240, function(test) {
  var camResolutionsTest = new CamResolutionsTest(test , [[320, 240]]);
  camResolutionsTest.run();
});

addTest(testSuiteName.CAMERA, testCaseName.CHECKRESOLUTION480, function(test) {
  var camResolutionsTest = new CamResolutionsTest(test, [[640, 480]]);
  camResolutionsTest.run();
});

addTest(testSuiteName.CAMERA, testCaseName.CHECKRESOLUTION720, function(test) {
  var camResolutionsTest = new CamResolutionsTest(test, [[1280, 720]]);
  camResolutionsTest.run();
});

addTest(
  testSuiteName.CAMERA, testCaseName.CHECKSUPPORTEDRESOLUTIONS, function(test) {
  var resolutionArray = [
    [160, 120], [320, 180], [320, 240], [640, 360], [640, 480], [768, 576],
    [1024, 576], [1280, 720], [1280, 768], [1280, 800], [1920, 1080],
    [1920, 1200], [3840, 2160], [4096, 2160]
  ];
  var camResolutionsTest = new CamResolutionsTest(test, resolutionArray);
  camResolutionsTest.run();
});

function CamResolutionsTest(test, resolutions) {
  this.test = test;
  this.resolutions = resolutions;
  this.currentResolution = 0;
  this.isMuted = false;
}

CamResolutionsTest.prototype = {
  run: function() {
    this.startGetUserMedia(this.resolutions[this.currentResolution]);
  },

  startGetUserMedia: function(resolution) {
    var constraints = {
      audio: false,
      video: {width: {exact: resolution[0]},
          height: {exact: resolution[1]}}
    };
    navigator.mediaDevices.getUserMedia(constraints)
    .then(function(stream) {
      // Do not check actual video frames when more than one resolution is
      // provided.
      if (this.resolutions.length > 1) {
        this.test.reportSuccess('Supported: ' + resolution[0] + 'x' +
            resolution[1]);
        stream.getTracks().forEach(function(track) {track.stop();});
        this.maybeContinueGetUserMedia();
      } else {
        this.collectAndAnalyzeStats_(stream, resolution);
      }
    }.bind(this))
    .catch(function(error) {
      if (this.resolutions.length > 1) {
        this.test.reportInfo(resolution[0] + 'x' + resolution[1] +
            ' not supported');
        this.maybeContinueGetUserMedia();
      } else {
        this.test.reportError('getUserMedia failed with error: ' + error.name);
        this.test.done();
      }
    }.bind(this));
  },

  maybeContinueGetUserMedia: function() {
    if (this.currentResolution === this.resolutions.length) {
      this.test.done();
      return;
    }
    this.startGetUserMedia(this.resolutions[this.currentResolution++]);
  },

  collectAndAnalyzeStats_: function(stream, resolution) {
    var tracks = stream.getVideoTracks();
    if (tracks.length < 1) {
      this.test.reportError('No video track in returned stream.');
      this.finishTestOrRetrigger_();
      return;
    }

    var videoTrack = tracks[0];
    // Register events.
    videoTrack.addEventListener('ended', function() {
      this.test.reportError('Video track ended, camera stopped working');
    });
    videoTrack.addEventListener('mute', function() {
      this.test.reportError('Your camera reported itself as muted.');
      // MediaStreamTrack.muted property is not wired up in Chrome yet, checking
      // isMuted local state.
      this.isMuted = true;
    });
    videoTrack.addEventListener('unmute', function() {
      this.isMuted = false;
    });

    var video = document.createElement('video');
    video.setAttribute('autoplay', '');
    video.setAttribute('muted', '');
    video.width = resolution[0];
    video.height = resolution[1];
    attachMediaStream(video, stream);
    var frameChecker = new VideoFrameChecker(video);
    var call = new Call();
    call.pc1.addStream(stream);
    call.establishConnection();
    call.gatherStats(call.pc1,
                     this.onCallEnded_.bind(this, resolution, video,
                                            stream, frameChecker),
                     100);

    setTimeoutWithProgressBar(call.close.bind(call), 8000);
  },

  onCallEnded_: function(resolution, videoElement, stream, frameChecker,
                         stats, statsTime) {
    this.analyzeStats_(resolution, videoElement, stream, frameChecker,
                       stats, statsTime);

    frameChecker.stop();

    stream.getTracks().forEach(function(track) {
      track.stop();
    });
    this.test.done();
  },

  analyzeStats_: function(resolution, videoElement, stream,
                          frameChecker, stats, statsTime) {
    var googAvgEncodeTime = [];
    var googAvgFrameRateInput = [];
    var googAvgFrameRateSent = [];
    var statsReport = {};
    var frameStats = frameChecker.frameStats;

    for (var index = 0; index < stats.length - 1; index++) {
      if (stats[index].type === 'ssrc') {
        // Make sure to only capture stats after the encoder is setup.
        if (stats[index].stat('googFrameRateInput') > 0) {
          googAvgEncodeTime.push(
              parseInt(stats[index].stat('googAvgEncodeMs')));
          googAvgFrameRateInput.push(
              parseInt(stats[index].stat('googFrameRateInput')));
          googAvgFrameRateSent.push(
              parseInt(stats[index].stat('googFrameRateSent')));
        }
      }
    }

    statsReport.cameraName = stream.getVideoTracks()[0].label || NaN;
    statsReport.actualVideoWidth = videoElement.videoWidth;
    statsReport.actualVideoHeight = videoElement.videoHeight;
    statsReport.mandatoryWidth = resolution[0];
    statsReport.mandatoryHeight = resolution[1];
    statsReport.encodeSetupTimeMs =
        this.extractEncoderSetupTime_(stats, statsTime);
    statsReport.avgEncodeTimeMs = arrayAverage(googAvgEncodeTime);
    statsReport.minEncodeTimeMs = arrayMin(googAvgEncodeTime);
    statsReport.maxEncodeTimeMs = arrayMax(googAvgEncodeTime);
    statsReport.avgInputFps = arrayAverage(googAvgFrameRateInput);
    statsReport.minInputFps = arrayMin(googAvgFrameRateInput);
    statsReport.maxInputFps = arrayMax(googAvgFrameRateInput);
    statsReport.avgSentFps = arrayAverage(googAvgFrameRateSent);
    statsReport.minSentFps = arrayMin(googAvgFrameRateSent);
    statsReport.maxSentFps = arrayMax(googAvgFrameRateSent);
    statsReport.isMuted = this.isMuted;
    statsReport.testedFrames = frameStats.numFrames;
    statsReport.blackFrames = frameStats.numBlackFrames;
    statsReport.frozenFrames = frameStats.numFrozenFrames;

    // TODO: Add a reportInfo() function with a table format to display
    // values clearer.
    report.traceEventInstant('video-stats', statsReport);

    this.testExpectations_(statsReport);
  },

  extractEncoderSetupTime_: function(stats, statsTime) {
    for (var index = 0; index !== stats.length; index++) {
      if (stats[index].type === 'ssrc') {
        if (stats[index].stat('googFrameRateInput') > 0) {
          return JSON.stringify(statsTime[index] - statsTime[0]);
        }
      }
    }
    return NaN;
  },

  resolutionMatchesIndependentOfRotationOrCrop_: function(aWidth, aHeight,
                                                       bWidth, bHeight) {
    var minRes = Math.min(bWidth, bHeight);
    return (aWidth === bWidth && aHeight === bHeight) ||
           (aWidth === bHeight && aHeight === bWidth) ||
           (aWidth === minRes && bHeight === minRes);
  },

  testExpectations_: function(info) {
    var notAvailableStats = [];
    for (var key in info) {
      if (info.hasOwnProperty(key)) {
        if (typeof info[key] === 'number' && isNaN(info[key])) {
          notAvailableStats.push(key);
        } else {
          this.test.reportInfo(key + ': ' + info[key]);
        }
      }
    }
    if (notAvailableStats.length !== 0) {
      this.test.reportInfo('Not available: ' + notAvailableStats.join(', '));
    }

    if (isNaN(info.avgSentFps)) {
      this.test.reportInfo('Cannot verify sent FPS.');
    } else if (info.avgSentFps < 5) {
      this.test.reportError('Low average sent FPS: ' + info.avgSentFps);
    } else {
      this.test.reportSuccess('Average FPS above threshold');
    }
    if (!this.resolutionMatchesIndependentOfRotationOrCrop_(
        info.actualVideoWidth, info.actualVideoHeight, info.mandatoryWidth,
        info.mandatoryHeight)) {
      this.test.reportError('Incorrect captured resolution.');
    } else {
      this.test.reportSuccess('Captured video using expected resolution.');
    }
    if (info.testedFrames === 0) {
      this.test.reportError('Could not analyze any video frame.');
    } else {
      if (info.blackFrames > info.testedFrames / 3) {
        this.test.reportError('Camera delivering lots of black frames.');
      }
      if (info.frozenFrames > info.testedFrames / 3) {
        this.test.reportError('Camera delivering lots of frozen frames.');
      }
    }
  }
};

//TODO: Move this to a separate file.
function VideoFrameChecker(videoElement) {
  this.frameStats = {
    numFrozenFrames: 0,
    numBlackFrames: 0,
    numFrames: 0
  };

  this.running_ = true;

  this.nonBlackPixelLumaThreshold = 20;
  this.previousFrame_ = [];
  this.identicalFrameSsimThreshold = 0.985;
  this.frameComparator = new Ssim();

  this.canvas_ = document.createElement('canvas');
  this.videoElement_ = videoElement;
  this.listener_ = this.checkVideoFrame_.bind(this);
  this.videoElement_.addEventListener('play', this.listener_, false);
}

VideoFrameChecker.prototype = {
  stop: function() {
    this.videoElement_.removeEventListener('play' , this.listener_);
    this.running_ = false;
  },

  getCurrentImageData_: function() {
    this.canvas_.width = this.videoElement_.width;
    this.canvas_.height = this.videoElement_.height;

    var context = this.canvas_.getContext('2d');
    context.drawImage(this.videoElement_, 0, 0, this.canvas_.width,
                      this.canvas_.height);
    return context.getImageData(0, 0, this.canvas_.width, this.canvas_.height);
  },

  checkVideoFrame_: function() {
    if (!this.running_) {
      return;
    }
    if (this.videoElement_.ended) {
      return;
    }

    var imageData = this.getCurrentImageData_();

    if (this.isBlackFrame_(imageData.data, imageData.data.length)) {
      this.frameStats.numBlackFrames++;
    }

    if (this.frameComparator.calculate(this.previousFrame_, imageData.data) >
        this.identicalFrameSsimThreshold) {
      this.frameStats.numFrozenFrames++;
    }
    this.previousFrame_ = imageData.data;

    this.frameStats.numFrames++;
    setTimeout(this.checkVideoFrame_.bind(this), 20);
  },

  isBlackFrame_: function(data, length) {
    // TODO: Use a statistical, histogram-based detection.
    var thresh = this.nonBlackPixelLumaThreshold;
    var accuLuma = 0;
    for (var i = 4; i < length; i += 4) {
      // Use Luma as in Rec. 709: Y′709 = 0.21R + 0.72G + 0.07B;
      accuLuma += 0.21 * data[i] +  0.72 * data[i + 1] + 0.07 * data[i + 2];
      // Early termination if the average Luma so far is bright enough.
      if (accuLuma > (thresh * i / 4)) {
        return false;
      }
    }
    return true;
  }
};
<|endoftext|>


<commit_before>import React from 'react';

import DateUtil from '../utils/DateUtil';
import Util from '../utils/Util';

const SECOND = 1000;
const MINUTE = 60 * 1000;
const HOUR = 60 * 60 * 1000;
const DAY = 24 * 60 * 60 * 1000;
const METHODS_TO_BIND = [
  'updateTime'
];

class TimeAgo extends React.Component {
  constructor() {
    super(...arguments);

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });

    this.state = {};

    if (this.props.autoUpdate && this.props.time != null) {
      this.state.interval = global.setTimeout(
        this.updateTime,
        this.getUpdateInterval()
      );
    }
  }

  componentWillUnmount() {
    if (this.state.interval) {
      global.clearInterval(this.state.interval);
    }
  }

  getUpdateInterval() {
    let timeAgo = Date.now() - new Date(this.props.time);

    if (timeAgo > DAY) {
      return DAY;
    }

    if (timeAgo > HOUR) {
      return HOUR;
    }

    if (timeAgo > MINUTE) {
      return MINUTE;
    }

    return SECOND;
  }

  updateTime() {
    this.setState({
      interval: global.setTimeout(this.updateTime, this.getUpdateInterval())
    });
  }

  render() {
    let {prefix, suppressSuffix, time} = this.props;
    let relativeTime = DateUtil.msToRelativeTime(time, suppressSuffix);
    if (time == null) {
      relativeTime = 'N/A';
    }

    let prefixString;
    if (prefix) {
      prefixString = `${prefix} `;
    }

    return (
      <time
        title={DateUtil.msToUTCDate(time)}
        dateTime={DateUtil.msToUTCDate(time)}
        {...Util.omit(this.props, ['prefix', 'suppressSuffix', 'time'])}>
        {prefixString}{relativeTime}
      </time>
    );
  }
}

TimeAgo.defaultProps = {
  autoUpdate: true,
  suppressSuffix: false
};

TimeAgo.propTypes = {
  autoUpdate: React.PropTypes.bool,
  prefix: React.PropTypes.node,
  suppressSuffix: React.PropTypes.bool,
  time: React.PropTypes.oneOfType([
    React.PropTypes.object,
    React.PropTypes.number
  ])
};

module.exports = TimeAgo;
<commit_msg>More expressive definitions<commit_after>import React from 'react';

import DateUtil from '../utils/DateUtil';
import Util from '../utils/Util';

const SECOND = 1000;
const MINUTE = 60 * MINUTE;
const HOUR = 60 * HOUR;
const DAY = 24 * DAY;
const METHODS_TO_BIND = [
  'updateTime'
];

class TimeAgo extends React.Component {
  constructor() {
    super(...arguments);

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });

    this.state = {};

    if (this.props.autoUpdate && this.props.time != null) {
      this.state.interval = global.setTimeout(
        this.updateTime,
        this.getUpdateInterval()
      );
    }
  }

  componentWillUnmount() {
    if (this.state.interval) {
      global.clearInterval(this.state.interval);
    }
  }

  getUpdateInterval() {
    let timeAgo = Date.now() - new Date(this.props.time);

    if (timeAgo > DAY) {
      return DAY;
    }

    if (timeAgo > HOUR) {
      return HOUR;
    }

    if (timeAgo > MINUTE) {
      return MINUTE;
    }

    return SECOND;
  }

  updateTime() {
    this.setState({
      interval: global.setTimeout(this.updateTime, this.getUpdateInterval())
    });
  }

  render() {
    let {prefix, suppressSuffix, time} = this.props;
    let relativeTime = DateUtil.msToRelativeTime(time, suppressSuffix);
    if (time == null) {
      relativeTime = 'N/A';
    }

    let prefixString;
    if (prefix) {
      prefixString = `${prefix} `;
    }

    return (
      <time
        title={DateUtil.msToUTCDate(time)}
        dateTime={DateUtil.msToUTCDate(time)}
        {...Util.omit(this.props, ['prefix', 'suppressSuffix', 'time'])}>
        {prefixString}{relativeTime}
      </time>
    );
  }
}

TimeAgo.defaultProps = {
  autoUpdate: true,
  suppressSuffix: false
};

TimeAgo.propTypes = {
  autoUpdate: React.PropTypes.bool,
  prefix: React.PropTypes.node,
  suppressSuffix: React.PropTypes.bool,
  time: React.PropTypes.oneOfType([
    React.PropTypes.object,
    React.PropTypes.number
  ])
};

module.exports = TimeAgo;
<|endoftext|>


<commit_before>import Container from '../mixin/container';
import Togglable from '../mixin/togglable';
import Position from '../mixin/position';
import {
    append,
    attr,
    flipPosition,
    hasAttr,
    includes,
    isFocusable,
    isTouch,
    matches,
    offset,
    on,
    once,
    pointerDown,
    pointerEnter,
    pointerLeave,
    remove,
    within,
} from 'uikit-util';

export default {
    mixins: [Container, Togglable, Position],

    args: 'title',

    props: {
        delay: Number,
        title: String,
    },

    data: {
        pos: 'top',
        title: '',
        delay: 0,
        animation: ['uk-animation-scale-up'],
        duration: 100,
        cls: 'uk-active',
    },

    beforeConnect() {
        this.id = `uk-tooltip-${this._uid}`;
        this._hasTitle = hasAttr(this.$el, 'title');
        attr(this.$el, {
            title: '',
            'aria-describedby': this.id,
        });
        makeFocusable(this.$el);
    },

    disconnected() {
        this.hide();
        attr(this.$el, 'title', this._hasTitle ? this.title : null);
    },

    methods: {
        show() {
            if (this.isToggled(this.tooltip || null) || !this.title) {
                return;
            }

            this._unbind = once(
                document,
                `keydown ${pointerDown}`,
                this.hide,
                false,
                (e) =>
                    (e.type === pointerDown && !within(e.target, this.$el)) ||
                    (e.type === 'keydown' && e.keyCode === 27)
            );

            clearTimeout(this.showTimer);
            this.showTimer = setTimeout(this._show, this.delay);
        },

        async hide() {
            if (matches(this.$el, 'input:focus')) {
                return;
            }

            clearTimeout(this.showTimer);

            if (!this.isToggled(this.tooltip || null)) {
                return;
            }

            await this.toggleElement(this.tooltip, false, false);
            remove(this.tooltip);
            this.tooltip = null;
            this._unbind();
        },

        _show() {
            this.tooltip = append(
                this.container,
                `

${this.title}

`
            );

            on(this.tooltip, 'toggled', (e, toggled) => {
                if (!toggled) {
                    return;
                }

                this.positionAt(this.tooltip, this.$el);

                const [dir, align] = getAlignment(this.tooltip, this.$el, this.pos);

                this.origin =
                    this.axis === 'y'
                        ? `${flipPosition(dir)}-${align}`
                        : `${align}-${flipPosition(dir)}`;
            });

            this.toggleElement(this.tooltip, true);
        },
    },

    events: {
        focus: 'show',
        blur: 'hide',

        [`${pointerEnter} ${pointerLeave}`](e) {
            if (!isTouch(e)) {
                this[e.type === pointerEnter ? 'show' : 'hide']();
            }
        },

        // Clicking a button does not give it focus on all browsers and platforms
        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
        [pointerDown](e) {
            if (isTouch(e)) {
                this.show();
            }
        },
    },
};

function makeFocusable(el) {
    if (!isFocusable(el)) {
        attr(el, 'tabindex', '0');
    }
}

function getAlignment(el, target, [dir, align]) {
    const elOffset = offset(el);
    const targetOffset = offset(target);
    const properties = [
        ['left', 'right'],
        ['top', 'bottom'],
    ];

    for (const props of properties) {
        if (elOffset[props[0]] >= targetOffset[props[1]]) {
            dir = props[1];
            break;
        }
        if (elOffset[props[1]] <= targetOffset[props[0]]) {
            dir = props[0];
            break;
        }
    }

    const props = includes(properties[0], dir) ? properties[1] : properties[0];
    if (elOffset[props[0]] === targetOffset[props[0]]) {
        align = props[0];
    } else if (elOffset[props[1]] === targetOffset[props[1]]) {
        align = props[1];
    } else {
        align = 'center';
    }

    return [dir, align];
}
<commit_msg>fix: handle title attribute change while connected<commit_after>import Container from '../mixin/container';
import Togglable from '../mixin/togglable';
import Position from '../mixin/position';
import {
    append,
    attr,
    flipPosition,
    hasAttr,
    includes,
    isFocusable,
    isTouch,
    matches,
    offset,
    on,
    once,
    pointerDown,
    pointerEnter,
    pointerLeave,
    remove,
    within,
} from 'uikit-util';

export default {
    mixins: [Container, Togglable, Position],

    args: 'title',

    props: {
        delay: Number,
        title: String,
    },

    data: {
        pos: 'top',
        title: '',
        delay: 0,
        animation: ['uk-animation-scale-up'],
        duration: 100,
        cls: 'uk-active',
    },

    beforeConnect() {
        this.id = `uk-tooltip-${this._uid}`;
        this._hasTitle = hasAttr(this.$el, 'title');
        attr(this.$el, {
            title: '',
            'aria-describedby': this.id,
        });
        makeFocusable(this.$el);
    },

    disconnected() {
        this.hide();

        if (!attr(this.$el, 'title')) {
            attr(this.$el, 'title', this._hasTitle ? this.title : null);
        }
    },

    methods: {
        show() {
            if (this.isToggled(this.tooltip || null) || !this.title) {
                return;
            }

            this._unbind = once(
                document,
                `keydown ${pointerDown}`,
                this.hide,
                false,
                (e) =>
                    (e.type === pointerDown && !within(e.target, this.$el)) ||
                    (e.type === 'keydown' && e.keyCode === 27)
            );

            clearTimeout(this.showTimer);
            this.showTimer = setTimeout(this._show, this.delay);
        },

        async hide() {
            if (matches(this.$el, 'input:focus')) {
                return;
            }

            clearTimeout(this.showTimer);

            if (!this.isToggled(this.tooltip || null)) {
                return;
            }

            await this.toggleElement(this.tooltip, false, false);
            remove(this.tooltip);
            this.tooltip = null;
            this._unbind();
        },

        _show() {
            this.tooltip = append(
                this.container,
                `

${this.title}

`
            );

            on(this.tooltip, 'toggled', (e, toggled) => {
                if (!toggled) {
                    return;
                }

                this.positionAt(this.tooltip, this.$el);

                const [dir, align] = getAlignment(this.tooltip, this.$el, this.pos);

                this.origin =
                    this.axis === 'y'
                        ? `${flipPosition(dir)}-${align}`
                        : `${align}-${flipPosition(dir)}`;
            });

            this.toggleElement(this.tooltip, true);
        },
    },

    events: {
        focus: 'show',
        blur: 'hide',

        [`${pointerEnter} ${pointerLeave}`](e) {
            if (!isTouch(e)) {
                this[e.type === pointerEnter ? 'show' : 'hide']();
            }
        },

        // Clicking a button does not give it focus on all browsers and platforms
        // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#clicking_and_focus
        [pointerDown](e) {
            if (isTouch(e)) {
                this.show();
            }
        },
    },
};

function makeFocusable(el) {
    if (!isFocusable(el)) {
        attr(el, 'tabindex', '0');
    }
}

function getAlignment(el, target, [dir, align]) {
    const elOffset = offset(el);
    const targetOffset = offset(target);
    const properties = [
        ['left', 'right'],
        ['top', 'bottom'],
    ];

    for (const props of properties) {
        if (elOffset[props[0]] >= targetOffset[props[1]]) {
            dir = props[1];
            break;
        }
        if (elOffset[props[1]] <= targetOffset[props[0]]) {
            dir = props[0];
            break;
        }
    }

    const props = includes(properties[0], dir) ? properties[1] : properties[0];
    if (elOffset[props[0]] === targetOffset[props[0]]) {
        align = props[0];
    } else if (elOffset[props[1]] === targetOffset[props[1]]) {
        align = props[1];
    } else {
        align = 'center';
    }

    return [dir, align];
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name         SlashdotTest
// @namespace    https://github.com/skurtn/TampermonkeyScripts
// @version      1.0.2
// @description  Tinkers with Slashdot's landing page
// @author       S. Kurt Newman
// @match        https://slashdot.org/*
// @run-at       document-end
// @grant        GM_log
// @noiframes
// @updateUrl    https://raw.githubusercontent.com/skurtn/TampermonkeyScripts/master/snippets/slashdot-tests.meta.js
// @downloadUrl  https://raw.githubusercontent.com/skurtn/TampermonkeyScripts/master/snippets/slashdot-tests.js
// @require      https://code.jquery.com/jquery-3.0.0.min.js#sha256=6069398299730203aa434d1520ccf88ee8bf0aeee241aca18edbd85c78943432
// ==/UserScript==

(function() {
    'use strict';
    var version = "1.0.1";

    GM_log( "Slashdot test script started: v" + version );

    // display a list of story headlines on slashdot
    $( "span.story-title > a" ).each( function( index ) {
        GM_log( "Story: " + $(this).html() );
    });

    GM_log( "Test tampermonkey script ended" );
})();
<commit_msg>Update slashdot-tests.js<commit_after>// ==UserScript==
// @name         SlashdotTest
// @namespace    https://github.com/skurtn/TampermonkeyScripts
// @version      1.0.2
// @description  Tinkers with Slashdot's landing page
// @author       S. Kurt Newman
// @match        https://slashdot.org/*
// @run-at       document-end
// @grant        GM_log
// @noiframes
// @updateUrl    https://raw.githubusercontent.com/skurtn/TampermonkeyScripts/master/snippets/slashdot-tests.meta.js
// @require      https://code.jquery.com/jquery-3.0.0.min.js#sha256=6069398299730203aa434d1520ccf88ee8bf0aeee241aca18edbd85c78943432
// ==/UserScript==

(function() {
    'use strict';
    var version = "1.0.1";

    GM_log( "Slashdot test script started: v" + version );

    // display a list of story headlines on slashdot
    $( "span.story-title > a" ).each( function( index ) {
        GM_log( "Story: " + $(this).html() );
    });

    GM_log( "Test tampermonkey script ended" );
})();
<|endoftext|>