<commit_before>/*
 * Copyright https://github.com/divio/django-cms
 */

// #############################################################################
// NAMESPACES
/**
 * @module CMS
 */
var CMS = window.CMS || {};

// #############################################################################
// MODAL
(function ($) {
    'use strict';

    // shorthand for jQuery(document).ready();
    $(function () {

        /**
         * JSTree plugin used to synchronise the column width depending on the
         * screen size. Hides rows from right to left.
         */
        $.jstree.plugins.gridResize = function (options, parent) {
            var that = this;
            // this is how we register event handlers on jstree plugins
            this.bind = function () {
                parent.bind.call(this);
                // store elements after jstree is loaded and trigger initial states
                this.element.on('ready.jstree', function () {
                    that.ui = {
                        window: $(window),
                        cols: $('.jstree-grid-column'),
                        container: $('.jstree-grid-wrapper'),
                        inner: $('.jstree-grid-midwrapper')
                    };
                    that.timeout = 100;
                    that.snapshot = [];

                    // bind resize event and trigger
                    that.ui.window.on('resize.jstree',
                        CMS.API.Helpers.throttle(synchronise, that.timeout))
                        .trigger('resize.jstree');
                });
                // reload snapshot when nodes are updated
                this.element.on('redraw.jstree after_open.jstree after_close.jstree dnd_stop.vakata', function () {
                    that.snapshot = [];
                });
            };

            function synchronise() {
                var containerWidth = that.ui.container.outerWidth(true);
                var wrapperWidth = that.ui.inner.outerWidth(true);
                // we do not now the smallest size possible at this stage,
                // the "pages" section is automatically adapted to 100% to fill
                // the screen. In order to get the correct breakpoints, we need
                // to make a snapshot at the lowest point
                if (!that.snapshot.length && (containerWidth < wrapperWidth)) {
                    // store the current breakpoints
                    that.snapshot = createSnapshot();
                }
                // only recalculate once the snapshot is available to save memory
                if (that.snapshot.length) {
                    var index = that.snapshot.length;
                    // loops from most the most right to the most left column
                    // without incorporating the very first column
                    for (var i = 1; i < that.snapshot.length; i++) {
                        var calc = 0;
                        var condition1;
                        var condition2;
                        var idx = that.snapshot.length - i;

                        for (var x = 1; x < i; x++) {
                            calc = calc + that.snapshot.array[that.snapshot.length - x] || 0;
                        }

                        condition1 = containerWidth < (that.snapshot.width - calc);
                        condition2 = index <= (idx + 1);

                        if (condition1 && condition2) {
                            that.ui.cols.eq(idx).addClass('hidden');
                            index = idx;
                        } else {
                            that.ui.cols.eq(idx).removeClass('hidden');
                        }
                    }
                }
            }

            function createSnapshot() {
                var array = [];
                // we need to get the real size of all visible columns added
                that.ui.cols.each(function () {
                    array.push($(this).outerWidth(true));
                });
                return {
                    array: array,
                    length: array.length,
                    width: array.reduce(function (pv, cv) {
                        return pv + cv;
                    }, 0)
                };
            }
        };

        // TODO implement success feedback when moving a tree item (that.options.lang.success)
        // TODO implement error handling when tree couldnt be moved (that.options.lang.error)
        // TODO make sure static path is not hard coded
        // TODO implement dialog for copy permissions
        /* TODO avialable ajax requests:
        *  'cms/page/' + pageId + '/change-navigation/?language=' + language (used for filtering)
        *  > loaded for first initialization
        *  > need to pass "data.fitlered = 1"
        *  > might need to consider site: { 1: 1 }
        *  'cms/page/' + pageId + '/' + language + '/descendants/'
        *  > loading decendents of a closed item
        *  'cms/page/' + item_id + '/dialog/copy/'
        *  > triggers the permission conform dialog
        *  > copy an item into new ancestor with
        *  > { position: position, target: target_id, site: site }
        *  'cms/page/' + item_id + '/copy-page/
        *  > same as above but triggers the actual move
        *  'cms/page/' + item_id + '/move-page/
        *  > { position: position, target: target_id, site: site }
        */

        /**
         * The pagetree is loaded via `/admin/cms/page` and has a custom admin
         * templates stored within `templates/admin/cms/page/tree`.
         *
         * @class PageTree
         * @namespace CMS
         * @uses CMS.API.Helpers
         */
        CMS.PageTree = new CMS.Class({

            implement: [CMS.API.Helpers],

            initialize: function initialize(options) {
                // options are loaded from the pagetree html node
                this.options = $('.js-cms-pagetree').data('json');
                this.options = $.extend(true, {}, this.options, options);

                // elements
                this._setupUI();

                // states and events
                this.click = 'click.cms.pagetree';

                // make sure that ajax request send the csrf token
                this.csrf(this.options.csrf);

                // setup functionality
                this._setup();
                this._events();
                this._setFilter();
                this._setTooltips();

                // make sure ajax post requests are working
                this._setAjaxPost('.js-cms-tree-item-menu a');
                this._setAjaxPost('.js-cms-tree-lang-trigger');
            },

            /**
             * Stores all jQuery references within `this.ui`.
             *
             * @method _setupUI
             * @private
             */
            _setupUI: function _setupUI() {
                var pagetree = $('.cms-pagetree-container');
                this.ui = {
                    container: pagetree,
                    document: $(document),
                    tree: pagetree.find('.js-cms-pagetree')
                };
            },

            /**
             * Setting up the jstree and the related columns.
             *
             * @method _setup
             * @private
             */
            _setup: function _setup() {
                var that = this;
                var columns = [];
                var obj = {
                    language: this.options.lang.code,
                    openNodes: []
                };

                // setup column headings
                $.each(this.options.columns, function (index, obj) {
                    if (obj.key === '') {
                        // the first row is already populated, to avoid overwrites
                        // just leave the "key" param empty
                        columns.push({
                            header: obj.title,
                            width: obj.width || '1%'
                        });
                    } else {
                        columns.push({
                            header: obj.title,
                            value: function (node) {
                                // it needs to have the "colde" format and not "col-de"
                                // as jstree will convert "col-de" to "colDe"
                                return node.data['col' + obj.key];
                            },
                            width: obj.width || '1%'
                        });
                    }
                });

                // bind options to the jstree instance
                this.ui.tree.jstree({
                    core: {
                        // disable open/close animations
                        animation: 0,
                        // core setting to allow actions
                        check_callback: true,
                        // https://www.jstree.com/api/#/?f=$.jstree.defaults.core.data
                        data: {
                            url: 'get-tree/',
                            data: function (node) {
                                // '#' is rendered if its the root node, there we only
                                // care about `obj.openNodes`, in the following case
                                // we are requesting a specific node
                                if (node.id !== '#') {
                                    obj.pageId = that._setNode(node.data.id);
                                }

                                // we need to store the opened items inside the localstorage
                                // as we have to load the pagetree with the previous opened
                                // state
                                obj.openNodes = that._getNodes();

                                return obj;
                            }
                        },
                        // strings used within jstree that are called using `get_string`
                        strings: {
                            'Loading ...': 'Loading ...',
                            'New node': 'New node',
                            'nodes': 'nodes'
                        },
                        error: function (error) {
                            that.showError(error.reason);
                        }
                        // TODO need to add theme capabilities
                    },
                    // activate drag and drop plugin
                    plugins : ['dnd', 'search', 'grid', 'gridResize'],
                    // https://github.com/deitch/jstree-grid
                    grid: {
                        // columns are provided from base.html options
                        width: '100%',
                        columns: columns
                    }
                });
            },

            _events: function () {
                var that = this;

                this.ui.tree.on('after_close.jstree', function (e, el) {
                    that._removeNode(el.node.data.id);
                });
                this.ui.tree.on('after_open.jstree', function (e, el) {
                    that._setNode(el.node.data.id);
                });
            },

            /**
             * Stores a node in local storage.
             *
             * @method _setNode
             * @private
             * @param {String} id to be stored
             * @return {String} id that has been stored
             */
            _setNode: function _setNode(id) {
                var number = id.toString();
                var storage = this._getNodes();
                // store value only if it isn't there yet
                if (storage.indexOf(number) === -1) {
                    storage.push(number);
                }

                localStorage.setItem('cms_test_storage', storage);

                console.log(storage);
                return number;
            },

            /**
             * Removes a node in local storage.
             *
             * @method _setNode
             * @private
             * @param {String} id to be stored
             * @return {String} id that has been removed
             */
            _removeNode: function (id) {
                var number = id.toString();
                var storage = this._getNodes();
                // remove given id from storage
                storage = storage.splice(storage.indexOf(number), 1);

                localStorage.setItem('cms_test_storage', storage);

                console.log(storage);
                return number;
            },

            /**
             * Retreives a list of nodes from local storage.
             *
             * @method _getNodes
             * @private
             * @return {Array} list of ids
             */
            _getNodes: function _getNodes() {
                var storage = localStorage.getItem('cms_test_storage');

                console.log(storage);
                return (storage) ? storage.split(',') : [];
            },

            /**
             * Handles filter button display (Filter: Off).
             *
             * @method _setFilter
             * @private
             */
            _setFilter: function _setFilter() {
                var that = this;
                var trigger = $('.js-cms-tree-filter-trigger');
                var container = $('.js-cms-tree-filter-container');

                trigger.on(this.click, function (e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();

                    container.toggleClass('hidden');

                    that.ui.document.one(that.click, function () {
                        container.addClass('hidden');
                    });
                });

                container.on(that.click, function (e) {
                    e.stopImmediatePropagation();
                });
            },

            /**
             * Sets up general tooltips that can have a list of links or content.
             *
             * @method _setTooltips
             * @private
             */
            _setTooltips: function _setTooltips() {
                var that = this;
                var triggerCls = '.js-cms-tree-tooltip-trigger';
                var containerCls = '.js-cms-tree-tooltip-container';
                var triggers;
                var containers;

                // attach event to the trigger
                this.ui.container.on(this.click, triggerCls, function (e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();

                    triggers = $(triggerCls);
                    containers = $(containerCls);

                    containers.removeClass('cms-tree-tooltip-container-open')
                        .eq(triggers.index(this))
                        .addClass('cms-tree-tooltip-container-open');

                    that.ui.document.one(that.click, function () {
                        containers.removeClass('cms-tree-tooltip-container-open');
                    });
                });

                // stop propagnation on the element
                this.ui.container.on(this.click, containerCls, function (e) {
                    e.stopImmediatePropagation();
                });
            },

            /**
             * Triggers the links `href` as ajax post request.
             *
             * @method _setAjaxPost
             * @private
             * @param {jQuery} trigger jQuery link target
             */
            _setAjaxPost: function _setAjaxPost(trigger) {
                var that = this;

                this.ui.container.on(this.click, trigger, function (e) {
                    e.preventDefault();
                    $.post($(this).attr('href')).done(function () {
                        window.location.reload();
                    }).error(function (error) {
                        that.showError(error.statusText);
                    });
                });
            },

            /**
             * Displays an error within the django UI.
             *
             * @method showError
             * @param {String} message string message to display
             */
            showError: function showError(message) {
                var messages = $('.messagelist');
                var breadcrumb = $('.breadcrumbs');
                var tpl = '

{msg}

';
                var msg = tpl.replace('{msg}', message);

                messages.length ? messages.replaceWith(msg) : breadcrumb.after(msg);
            }

        });

        // autoload the pagetree
        new CMS.PageTree();

    });

})(CMS.$);
<commit_msg>add current progress<commit_after>/*
 * Copyright https://github.com/divio/django-cms
 */

// #############################################################################
// NAMESPACES
/**
 * @module CMS
 */
var CMS = window.CMS || {};

// #############################################################################
// MODAL
(function ($) {
    'use strict';

    // shorthand for jQuery(document).ready();
    $(function () {

        /**
         * JSTree plugin used to synchronise the column width depending on the
         * screen size. Hides rows from right to left.
         */
        $.jstree.plugins.gridResize = function (options, parent) {
            var that = this;
            // this is how we register event handlers on jstree plugins
            this.bind = function () {
                parent.bind.call(this);
                // store elements after jstree is loaded and trigger initial states
                this.element.on('ready.jstree', function () {
                    that.ui = {
                        window: $(window),
                        cols: $('.jstree-grid-column'),
                        container: $('.jstree-grid-wrapper'),
                        inner: $('.jstree-grid-midwrapper')
                    };
                    that.timeout = 100;
                    that.snapshot = [];

                    // bind resize event and trigger
                    that.ui.window.on('resize.jstree',
                        CMS.API.Helpers.throttle(synchronise, that.timeout))
                        .trigger('resize.jstree');
                });
                // reload snapshot when nodes are updated
                this.element.on('redraw.jstree after_open.jstree after_close.jstree dnd_stop.vakata', function () {
                    that.snapshot = [];
                });
            };

            function synchronise() {
                var containerWidth = that.ui.container.outerWidth(true);
                var wrapperWidth = that.ui.inner.outerWidth(true);
                // we do not now the smallest size possible at this stage,
                // the "pages" section is automatically adapted to 100% to fill
                // the screen. In order to get the correct breakpoints, we need
                // to make a snapshot at the lowest point
                if (!that.snapshot.length && (containerWidth < wrapperWidth)) {
                    // store the current breakpoints
                    that.snapshot = createSnapshot();
                }
                // only recalculate once the snapshot is available to save memory
                if (that.snapshot.length) {
                    var index = that.snapshot.length;
                    // loops from most the most right to the most left column
                    // without incorporating the very first column
                    for (var i = 1; i < that.snapshot.length; i++) {
                        var calc = 0;
                        var condition1;
                        var condition2;
                        var idx = that.snapshot.length - i;

                        for (var x = 1; x < i; x++) {
                            calc = calc + that.snapshot.array[that.snapshot.length - x] || 0;
                        }

                        condition1 = containerWidth < (that.snapshot.width - calc);
                        condition2 = index <= (idx + 1);

                        if (condition1 && condition2) {
                            that.ui.cols.eq(idx).addClass('hidden');
                            index = idx;
                        } else {
                            that.ui.cols.eq(idx).removeClass('hidden');
                        }
                    }
                }
            }

            function createSnapshot() {
                var array = [];
                // we need to get the real size of all visible columns added
                that.ui.cols.each(function () {
                    array.push($(this).outerWidth(true));
                });
                return {
                    array: array,
                    length: array.length,
                    width: array.reduce(function (pv, cv) {
                        return pv + cv;
                    }, 0)
                };
            }
        };

        // TODO implement success feedback when moving a tree item (that.options.lang.success)
        // TODO implement error handling when tree couldnt be moved (that.options.lang.error)
        // TODO make sure static path is not hard coded
        // TODO implement dialog for copy permissions
        /* TODO avialable ajax requests:
        *  'cms/page/' + pageId + '/change-navigation/?language=' + language (used for filtering)
        *  > loaded for first initialization
        *  > need to pass "data.fitlered = 1"
        *  > might need to consider site: { 1: 1 }
        *  'cms/page/' + pageId + '/' + language + '/descendants/'
        *  > loading decendents of a closed item
        *  'cms/page/' + item_id + '/dialog/copy/'
        *  > triggers the permission conform dialog
        *  > copy an item into new ancestor with
        *  > { position: position, target: target_id, site: site }
        *  'cms/page/' + item_id + '/copy-page/
        *  > same as above but triggers the actual move
        *  'cms/page/' + item_id + '/move-page/
        *  > { position: position, target: target_id, site: site }
        */

        /**
         * The pagetree is loaded via `/admin/cms/page` and has a custom admin
         * templates stored within `templates/admin/cms/page/tree`.
         *
         * @class PageTree
         * @namespace CMS
         * @uses CMS.API.Helpers
         */
        CMS.PageTree = new CMS.Class({

            implement: [CMS.API.Helpers],

            initialize: function initialize(options) {
                // options are loaded from the pagetree html node
                this.options = $('.js-cms-pagetree').data('json');
                this.options = $.extend(true, {}, this.options, options);

                // elements
                this._setupUI();

                // states and events
                this.click = 'click.cms.pagetree';

                // make sure that ajax request send the csrf token
                this.csrf(this.options.csrf);

                // setup functionality
                this._setup();
                this._events();
                this._setFilter();
                this._setTooltips();

                // make sure ajax post requests are working
                this._setAjaxPost('.js-cms-tree-item-menu a');
                this._setAjaxPost('.js-cms-tree-lang-trigger');
            },

            /**
             * Stores all jQuery references within `this.ui`.
             *
             * @method _setupUI
             * @private
             */
            _setupUI: function _setupUI() {
                var pagetree = $('.cms-pagetree-container');
                this.ui = {
                    container: pagetree,
                    document: $(document),
                    tree: pagetree.find('.js-cms-pagetree')
                };
            },

            /**
             * Setting up the jstree and the related columns.
             *
             * @method _setup
             * @private
             */
            _setup: function _setup() {
                var that = this;
                var columns = [];
                var obj = {
                    language: this.options.lang.code,
                    openNodes: []
                };

                // setup column headings
                $.each(this.options.columns, function (index, obj) {
                    if (obj.key === '') {
                        // the first row is already populated, to avoid overwrites
                        // just leave the "key" param empty
                        columns.push({
                            header: obj.title,
                            width: obj.width || '1%'
                        });
                    } else {
                        columns.push({
                            header: obj.title,
                            value: function (node) {
                                // it needs to have the "colde" format and not "col-de"
                                // as jstree will convert "col-de" to "colDe"
                                return node.data['col' + obj.key];
                            },
                            width: obj.width || '1%'
                        });
                    }
                });

                // bind options to the jstree instance
                this.ui.tree.jstree({
                    core: {
                        // disable open/close animations
                        animation: 0,
                        // core setting to allow actions
                        check_callback: true,
                        // https://www.jstree.com/api/#/?f=$.jstree.defaults.core.data
                        data: {
                            url: 'get-tree/',
                            data: function (node) {
                                // '#' is rendered if its the root node, there we only
                                // care about `obj.openNodes`, in the following case
                                // we are requesting a specific node
                                if (node.id !== '#') {
                                    obj.pageId = that._setNode(node.data.id);
                                }

                                // we need to store the opened items inside the localstorage
                                // as we have to load the pagetree with the previous opened
                                // state
                                obj.openNodes = that._getNodes();

                                return obj;
                            }
                        },
                        // strings used within jstree that are called using `get_string`
                        strings: {
                            'Loading ...': 'Loading ...',
                            'New node': 'New node',
                            'nodes': 'nodes'
                        },
                        error: function (error) {
                            that.showError(error.reason);
                        }
                        // TODO need to add theme capabilities
                    },
                    // activate drag and drop plugin
                    plugins : ['dnd', 'search', 'grid', 'gridResize'],
                    // https://github.com/deitch/jstree-grid
                    grid: {
                        // columns are provided from base.html options
                        width: '100%',
                        columns: columns
                    }
                });
            },

            _events: function () {
                var that = this;

                this.ui.tree.on('after_close.jstree', function (e, el) {
                    that._removeNode(el.node.data.id);
                });
                this.ui.tree.on('after_open.jstree', function (e, el) {
                    that._setNode(el.node.data.id);
                });
            },

            /**
             * Retreives a list of nodes from local storage.
             *
             * @method _getNodes
             * @private
             * @return {Array} list of ids
             */
            _getNodes: function _getNodes() {
                var storage = localStorage.getItem('cms_test_storage');

                return (storage) ? storage.split(',') : [];
            },

            /**
             * Stores a node in local storage.
             *
             * @method _setNode
             * @private
             * @param {String} id to be stored
             * @return {String} id that has been stored
             */
            _setNode: function _setNode(id) {
                var number = id.toString();
                var storage = this._getNodes();
                // store value only if it isn't there yet
                if (storage.indexOf(number) === -1) {
                    storage.push(number);
                }

                localStorage.setItem('cms_test_storage', storage);

                return number;
            },

            /**
             * Removes a node in local storage.
             *
             * @method _setNode
             * @private
             * @param {String} id to be stored
             * @return {String} id that has been removed
             */
            _removeNode: function (id) {
                var number = id.toString();
                var storage = this._getNodes();
                var index = storage.indexOf(number);

                // remove given id from storage
                if (index !== -1) {
                    storage.splice(index, 1);
                }

                localStorage.setItem('cms_test_storage', storage);

                return number;
            },

            /**
             * Handles filter button display (Filter: Off).
             *
             * @method _setFilter
             * @private
             */
            _setFilter: function _setFilter() {
                var that = this;
                var trigger = $('.js-cms-tree-filter-trigger');
                var container = $('.js-cms-tree-filter-container');

                trigger.on(this.click, function (e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();

                    container.toggleClass('hidden');

                    that.ui.document.one(that.click, function () {
                        container.addClass('hidden');
                    });
                });

                container.on(that.click, function (e) {
                    e.stopImmediatePropagation();
                });
            },

            /**
             * Sets up general tooltips that can have a list of links or content.
             *
             * @method _setTooltips
             * @private
             */
            _setTooltips: function _setTooltips() {
                var that = this;
                var triggerCls = '.js-cms-tree-tooltip-trigger';
                var containerCls = '.js-cms-tree-tooltip-container';
                var triggers;
                var containers;

                // attach event to the trigger
                this.ui.container.on(this.click, triggerCls, function (e) {
                    e.preventDefault();
                    e.stopImmediatePropagation();

                    triggers = $(triggerCls);
                    containers = $(containerCls);

                    containers.removeClass('cms-tree-tooltip-container-open')
                        .eq(triggers.index(this))
                        .addClass('cms-tree-tooltip-container-open');

                    that.ui.document.one(that.click, function () {
                        containers.removeClass('cms-tree-tooltip-container-open');
                    });
                });

                // stop propagnation on the element
                this.ui.container.on(this.click, containerCls, function (e) {
                    e.stopImmediatePropagation();
                });
            },

            /**
             * Triggers the links `href` as ajax post request.
             *
             * @method _setAjaxPost
             * @private
             * @param {jQuery} trigger jQuery link target
             */
            _setAjaxPost: function _setAjaxPost(trigger) {
                var that = this;

                this.ui.container.on(this.click, trigger, function (e) {
                    e.preventDefault();
                    $.post($(this).attr('href')).done(function () {
                        window.location.reload();
                    }).error(function (error) {
                        that.showError(error.statusText);
                    });
                });
            },

            /**
             * Displays an error within the django UI.
             *
             * @method showError
             * @param {String} message string message to display
             */
            showError: function showError(message) {
                var messages = $('.messagelist');
                var breadcrumb = $('.breadcrumbs');
                var tpl = '

{msg}

';
                var msg = tpl.replace('{msg}', message);

                messages.length ? messages.replaceWith(msg) : breadcrumb.after(msg);
            }

        });

        // autoload the pagetree
        new CMS.PageTree();

    });

})(CMS.$);
<|endoftext|>


<commit_before>/* global assert, process, setup, suite, test, AFRAME */

var helpers = require('../helpers');
var entityFactory = helpers.entityFactory;
var coord = AFRAME.utils.coordinates.parse;

suite('stretchable', function () {
  setup(function (done) {
    var el = this.el = entityFactory();
    this.hand1 = helpers
      .controllerFactory({ 'hand-controls': 'right' }, true);
    this.hand2 = helpers
      .controllerFactory({ 'hand-controls': 'left' }, true);
    el.setAttribute('stretchable', '');
    el.sceneEl.addEventListener('loaded', evt => {
      this.comp = el.components.stretchable;
      done();
    });
  });
  test('component attaches without errors', function () {
    assert.isOk(this.el.components.stretchable.data.usePhysics);
  });
  test('component removes without errors', function (done) {
    var el = this.el;
    el.removeComponent('stretchable');
    process.nextTick(function () {
      assert.notOk(el.components.stretchable);
      done();
    });
  });
  test('stretchers captured and state added/removed', function () {
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    assert.isOk(this.el.is('stretched'));
    assert.sameMembers(this.comp.stretchers, [this.hand1, this.hand2]);
    this.comp.end({detail: {hand: this.hand1}});
    assert.notOk(this.el.is('stretched'));
  });
  test('reject duplicate stretchers', function () {
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand1 } });
    assert.equal(this.comp.stretchers.length, 1);
    assert.isFalse(this.el.is('stretched'));
  });
  test('scale updates during stretch', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const posStub2 = this.sinon.stub(this.hand2, 'getAttribute');
    var lastScale;
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    posStub2.withArgs('position').returns(coord('-1 -1 -1'));
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), coord('1 1 1'));
    this.comp.tick();
    assert.notDeepEqual(this.el.getAttribute('scale'), coord('1 1 1'));
    assert.isAbove(this.el.getAttribute('scale').x, 1);
    lastScale = this.el.getAttribute('scale');
    this.comp.end({ detail: { hand: this.hand1 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), lastScale);
  });
  test('scale updates are invertable', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const posStub2 = this.sinon.stub(this.hand2, 'getAttribute');
    var lastScale;
    this.el.setAttribute('stretchable', {invert: true});
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    posStub2.withArgs('position').returns(coord('-1 -1 -1'));
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), coord('1 1 1'));
    this.comp.tick();
    assert.isBelow(this.el.getAttribute('scale').x, 1);
    lastScale = this.el.getAttribute('scale');
    this.comp.end({ detail: { hand: this.hand1 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), lastScale);
  });
});

suite('stretchable-physics', function () {
  setup(function (done) {
    var el = this.el = entityFactory({}, true);
    this.hand1 = helpers
      .controllerFactory({ 'hand-controls': 'right' }, true);
    this.hand2 = helpers
      .controllerFactory({ 'hand-controls': 'left' }, true);
    el.setAttribute('stretchable', '');
    el.setAttribute('geometry', 'primitive: box');
    el.setAttribute('dynamic-body', '');
    el.addEventListener('body-loaded', evt => {
      this.comp = el.components.stretchable;
      done();
    });
  });
  test('box bodies update with scaling', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const scale = new window.CANNON.Vec3();
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    this.comp.tick();
    assert.deepEqual(this.el.body.shapes[0].halfExtents,
                     scale.set(0.5, 0.5, 0.5));
    this.comp.tick();
    assert.notDeepEqual(this.el.body.shapes[0].halfExtents,
                        scale.set(0.5, 0.5, 0.5));
  });
  test('box bodies do not update when usePhysics = never', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const scale = new window.CANNON.Vec3();
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    this.el.setAttribute('stretchable', 'usePhysics: never');
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    assert.ok(this.el.is('stretched'));
    this.comp.tick();
    assert.deepEqual(this.el.body.shapes[0].halfExtents,
                     scale.set(0.5, 0.5, 0.5));
    this.comp.tick();
    assert.deepEqual(this.el.body.shapes[0].halfExtents,
                        scale.set(0.5, 0.5, 0.5));
  });
});
<commit_msg>stretchable button mapping tdd<commit_after>/* global assert, process, setup, suite, test, AFRAME */

var helpers = require('../helpers');
var entityFactory = helpers.entityFactory;
var coord = AFRAME.utils.coordinates.parse;

suite('stretchable', function () {
  setup(function (done) {
    var el = this.el = entityFactory();
    this.hand1 = helpers
      .controllerFactory({ 'hand-controls': 'right' }, true);
    this.hand2 = helpers
      .controllerFactory({ 'hand-controls': 'left' }, true);
    el.setAttribute('stretchable', '');
    el.sceneEl.addEventListener('loaded', evt => {
      this.comp = el.components.stretchable;
      done();
    });
  });
  test('component attaches without errors', function () {
    assert.isOk(this.el.components.stretchable.data.usePhysics);
  });
  test('component removes without errors', function (done) {
    var el = this.el;
    el.removeComponent('stretchable');
    process.nextTick(function () {
      assert.notOk(el.components.stretchable);
      done();
    });
  });
  test('stretchers captured and state added/removed', function () {
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    assert.isOk(this.el.is('stretched'));
    assert.sameMembers(this.comp.stretchers, [this.hand1, this.hand2]);
    this.comp.end({detail: {hand: this.hand1}});
    assert.notOk(this.el.is('stretched'));
  });
  test('reject duplicate stretchers', function () {
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand1 } });
    assert.equal(this.comp.stretchers.length, 1);
    assert.isFalse(this.el.is('stretched'));
  });
  test('scale updates during stretch', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const posStub2 = this.sinon.stub(this.hand2, 'getAttribute');
    var lastScale;
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    posStub2.withArgs('position').returns(coord('-1 -1 -1'));
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), coord('1 1 1'));
    this.comp.tick();
    assert.notDeepEqual(this.el.getAttribute('scale'), coord('1 1 1'));
    assert.isAbove(this.el.getAttribute('scale').x, 1);
    lastScale = this.el.getAttribute('scale');
    this.comp.end({ detail: { hand: this.hand1 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), lastScale);
  });
  test('scale updates are invertable', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const posStub2 = this.sinon.stub(this.hand2, 'getAttribute');
    var lastScale;
    this.el.setAttribute('stretchable', {invert: true});
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    posStub2.withArgs('position').returns(coord('-1 -1 -1'));
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), coord('1 1 1'));
    this.comp.tick();
    assert.isBelow(this.el.getAttribute('scale').x, 1);
    lastScale = this.el.getAttribute('scale');
    this.comp.end({ detail: { hand: this.hand1 } });
    this.comp.tick();
    assert.deepEqual(this.el.getAttribute('scale'), lastScale);
  });
});

suite('stretchable-physics', function () {
  setup(function (done) {
    var el = this.el = entityFactory({}, true);
    this.hand1 = helpers
      .controllerFactory({ 'hand-controls': 'right' }, true);
    this.hand2 = helpers
      .controllerFactory({ 'hand-controls': 'left' }, true);
    el.setAttribute('stretchable', '');
    el.setAttribute('geometry', 'primitive: box');
    el.setAttribute('dynamic-body', '');
    el.addEventListener('body-loaded', evt => {
      this.comp = el.components.stretchable;
      done();
    });
  });
  test('box bodies update with scaling', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const scale = new window.CANNON.Vec3();
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    this.comp.tick();
    assert.deepEqual(this.el.body.shapes[0].halfExtents,
                     scale.set(0.5, 0.5, 0.5));
    this.comp.tick();
    assert.notDeepEqual(this.el.body.shapes[0].halfExtents,
                        scale.set(0.5, 0.5, 0.5));
  });
  test('box bodies do not update when usePhysics = never', function () {
    const posStub1 = this.sinon.stub(this.hand1, 'getAttribute');
    const scale = new window.CANNON.Vec3();
    posStub1.withArgs('position')
      .onFirstCall().returns(coord('0 0 0'))
      .onSecondCall().returns(coord('1 1 1'))
      .onThirdCall().returns(coord('2 2 2'));
    this.el.setAttribute('stretchable', 'usePhysics: never');
    this.comp.start({ detail: { hand: this.hand1 } });
    this.comp.start({ detail: { hand: this.hand2 } });
    assert.ok(this.el.is('stretched'));
    this.comp.tick();
    assert.deepEqual(this.el.body.shapes[0].halfExtents,
                     scale.set(0.5, 0.5, 0.5));
    this.comp.tick();
    assert.deepEqual(this.el.body.shapes[0].halfExtents,
                        scale.set(0.5, 0.5, 0.5));
  });
});
suite('stretchable button mapping', function () {
  setup(function (done) {
    var el = this.el = entityFactory();
    this.hand = helpers.controllerFactory({'super-hands': ''});
    el.setAttribute('stretchable',
        'startButtons: triggerdown; endButtons: triggerup');
    el.addEventListener('loaded', () => {
      this.comp = el.components.stretchable;
      done();
    });
  });
  test('responds to correct buttons', function () {
    const dtl = {hand: this.hand, buttonEvent: {type: 'gripdown'}};
    // reject wrong button start
    assert.isOk(helpers.emitCancelable(this.el, this.comp.STRETCH_EVENT, dtl));
    assert.notStrictEqual(this.comp.stretchers[0], this.hand);
    // accept correct button start
    dtl.buttonEvent.type = 'triggerdown';
    assert.isNotOk(helpers.emitCancelable(this.el, this.comp.STRETCH_EVENT, dtl));
    assert.strictEqual(this.comp.stretchers[0], this.hand);
    // reject wrong button end
    assert.isOk(helpers.emitCancelable(this.el, this.comp.UNSTRETCH_EVENT, dtl));
    assert.strictEqual(this.comp.stretchers[0], this.hand);
    // accpect correct button end
    dtl.buttonEvent.type = 'triggerup';
    assert.isNotOk(helpers.emitCancelable(this.el, this.comp.UNSTRETCH_EVENT, dtl));
    assert.notStrictEqual(this.comp.stretchers[0], this.hand);
  });
});
<|endoftext|>


<commit_before>'use strict';
var Point = require('../prefabs/point');

function GameData(object) {
  this.startPoint = object.startPoint;
  this.endPoint = object.endPoint;
  this.points = object.points;
  this.wrongAnswers = 0;
  this.selectedAvatarKey = 'avatar_1';
}
GameData.prototype = {
  markPointAs: function(oldState, newState) {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i].state === oldState) {
        this.points[i].state = newState;
        break;
      }
    }
  },
  getTaskForCurrentPoint: function() {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i].state === Point.STATES.CURRENT) {
        var randomTaskIndex = Math.floor(Math.random() * this.points[i].tasks.length);
        console.log('Random index between 0 and ' + this.points[i].tasks.length + ' is ' + randomTaskIndex);
        return this.points[i].tasks[randomTaskIndex];
      }
    }
  },
  getBackgroundForCurrentPoint: function() {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i].state === Point.STATES.CURRENT) {
        return this.points[i].image;
      }
    }
  },
  isEndReachable: function() {
    for (var i = 0; i < this.points.length; i++) {
      var unvisitedPointsExist = this.points[i].state === Point.STATES.UNVISITED || this.points[i].state === Point.STATES.NEXT;
      if (unvisitedPointsExist) {
        return false;
      }
    }
    return true;
  },
  saveToLocalStorage: function() {
    window.localStorage.setItem('lukuinto-2015', JSON.stringify(this));
  }
};

module.exports = GameData;
<commit_msg>Load number of wrongs answers and used avatar information from local storage, if available.<commit_after>'use strict';
var Point = require('../prefabs/point');

function GameData(object) {
  this.startPoint = object.startPoint;
  this.endPoint = object.endPoint;
  this.points = object.points;

  if (object.wrongAnswers !== undefined && object.selectedAvatarKey !== undefined) {
    this.wrongAnswers = object.wrongAnswers;
    this.selectedAvatarKey = object.selectedAvatarKey;
  } else {
    this.wrongAnswers = 0;
    this.selectedAvatarKey = 'avatar_1';
  }
}
GameData.prototype = {
  markPointAs: function(oldState, newState) {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i].state === oldState) {
        this.points[i].state = newState;
        break;
      }
    }
  },
  getTaskForCurrentPoint: function() {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i].state === Point.STATES.CURRENT) {
        var randomTaskIndex = Math.floor(Math.random() * this.points[i].tasks.length);
        console.log('Random index between 0 and ' + this.points[i].tasks.length + ' is ' + randomTaskIndex);
        return this.points[i].tasks[randomTaskIndex];
      }
    }
  },
  getBackgroundForCurrentPoint: function() {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i].state === Point.STATES.CURRENT) {
        return this.points[i].image;
      }
    }
  },
  isEndReachable: function() {
    for (var i = 0; i < this.points.length; i++) {
      var unvisitedPointsExist = this.points[i].state === Point.STATES.UNVISITED || this.points[i].state === Point.STATES.NEXT;
      if (unvisitedPointsExist) {
        return false;
      }
    }
    return true;
  },
  saveToLocalStorage: function() {
    window.localStorage.setItem('lukuinto-2015', JSON.stringify(this));
  }
};

module.exports = GameData;
<|endoftext|>


<commit_before>// @flow
import React, {Component} from 'react'
import {Box, Button, Icon, Input, PopupDialog, Text} from '../../../common-adapters/index'
import {globalColors, globalMargins, globalStyles} from '../../../styles'

import type {Props} from './'

type State = {
  index: number,
  title: string,
}

class RenderAttachmentInput extends Component<void, Props, State> {
  state: State

  constructor (props: Props) {
    super(props)
    this.state = {
      index: 0,
      title: props.inputs.length > 0 && props.inputs[0].title || '',
    }
  }

  _onSelect = () => {
    const close = this.state.index === this.props.inputs.length - 1
    this.props.onSelect(this.props.inputs[this.state.index], this.state.title, close)
    if (!close) {
      const nextIndex = this.state.index + 1
      this.setState({
        index: nextIndex,
        title: this.props.inputs[nextIndex].title,
      })
    }
  }

  _updateTitle = (title) => {
    this.setState({title})
  }

  render () {
    const count = this.props.inputs.length
    const currentTitle = this.props.inputs[this.state.index].title
    return (
      <PopupDialog onClose={this.props.onClose}>
        <Box style={{...globalStyles.flexBoxColumn, alignItems: 'center', flex: 1, justifyContent: 'center', marginBottom: 80, marginLeft: 80, marginRight: 80, marginTop: 90}}>
          <Icon type='icon-file-uploading-48' />
          {count > 0 && <Text type='BodySmall' style={{color: globalColors.black_40, marginTop: 5}}>{currentTitle} ({this.state.index + 1} of {count})</Text>}
          <Input style={{marginTop: 70, width: 460}} autoFocus={true} floatingHintTextOverride='Title' value={this.state.title} onEnterKeyDown={this._onSelect} onChangeText={this._updateTitle} />
          <Box style={{...globalStyles.flexBoxRow, marginTop: 100}}>
            <Button type='Secondary' onClick={this.props.onClose} label='Cancel' />
            <Button type='Primary' style={{marginLeft: globalMargins.tiny}} onClick={this._onSelect} label='Send' />
          </Box>
        </Box>
      </PopupDialog>
    )
  }
}

export default RenderAttachmentInput
<commit_msg>Temporary mobile attachment input fix (#6708)<commit_after>// @flow
import React, {Component} from 'react'
import {Box, Button, Icon, Input, PopupDialog, Text} from '../../../common-adapters/index'
import {globalColors, globalMargins, globalStyles} from '../../../styles'
import {isMobile} from '../../../constants/platform'

import type {Props} from './'

type State = {
  index: number,
  title: string,
}

class RenderAttachmentInput extends Component<void, Props, State> {
  state: State

  constructor (props: Props) {
    super(props)
    this.state = {
      index: 0,
      title: props.inputs.length > 0 && props.inputs[0].title || '',
    }
  }

  _onSelect = () => {
    const close = this.state.index === this.props.inputs.length - 1
    this.props.onSelect(this.props.inputs[this.state.index], this.state.title, close)
    if (!close) {
      const nextIndex = this.state.index + 1
      this.setState({
        index: nextIndex,
        title: this.props.inputs[nextIndex].title,
      })
    }
  }

  _updateTitle = (title) => {
    this.setState({title})
  }

  render () {
    const count = this.props.inputs.length
    const currentTitle = this.props.inputs[this.state.index].title

    return (
      <PopupDialog onClose={this.props.onClose}>
        <Box style={isMobile ? stylesMobile : stylesDesktop}>
          <Icon type='icon-file-uploading-48' />
          {count > 0 && <Text type='BodySmall' style={{color: globalColors.black_40, marginTop: 5}}>{currentTitle} ({this.state.index + 1} of {count})</Text>}
          <Input style={isMobile ? stylesInputMobile : stylesInputDesktop} autoFocus={true} floatingHintTextOverride='Title' value={this.state.title} onEnterKeyDown={this._onSelect} onChangeText={this._updateTitle} />
          <Box style={isMobile ? stylesButtonGroupMobile : stylesButtonGroupDesktop}>
            <Button type='Secondary' onClick={this.props.onClose} label='Cancel' />
            <Button type='Primary' style={{marginLeft: globalMargins.tiny}} onClick={this._onSelect} label='Send' />
          </Box>
        </Box>
      </PopupDialog>
    )
  }
}

const stylesDesktop = {
  ...globalStyles.flexBoxColumn,
  alignItems: 'center',
  flex: 1,
  justifyContent: 'center',
  marginBottom: 80,
  marginLeft: 80,
  marginRight: 80,
  marginTop: 90,
}

const stylesInputDesktop = {
  marginTop: 70,
  width: 460,
}

const stylesButtonGroupDesktop = {
  ...globalStyles.flexBoxRow,
  marginTop: 100,
}

const stylesMobile = {
  ...globalStyles.flexBoxColumn,
  alignItems: 'center',
  flex: 1,
  justifyContent: 'flex-start',
  marginTop: 40,
}

const stylesInputMobile = {
  marginTop: 40,
  minWidth: 320,
  paddingLeft: 20,
  paddingRight: 20,
}

const stylesButtonGroupMobile = {
  ...globalStyles.flexBoxRow,
  marginTop: 40,
}

export default RenderAttachmentInput
<|endoftext|>


<commit_before>export const linear = t => t

export const easeInQuad = t => t * t
export const easeOutQuad = t => t * (2 - t)
export const easeInOutQuad = t => t < 0.5
  ? 2 * t * t
  : -1 + (4 - 2 * t) * t

export const easeInCubic = t => t ** 3
export const easeOutCubic = t => 1 + (t - 1) ** 3
export const easeInOutCubic = t => t < 0.5
  ? 4 * t ** 3
  : 1 + (t - 1) * (2 * t - 2) ** 2

export const easeInQuart = t => t ** 4
export const easeOutQuart = t => 1 - (t - 1) ** 4
export const easeInOutQuart = t => t < 0.5
  ? 8 * t ** 4
  : 1 - 8 * (t - 1) ** 4

export const easeInQuint = t => t ** 5
export const easeOutQuint = t => 1 + (t - 1) ** 5
export const easeInOutQuint = t => t < 0.5
  ? 16 * t ** 5
  : 1 + 16 * (t - 1) ** 5

export const easeInCirc = t => -1 * Math.sqrt(1 - t ** 2) + 1
export const easeOutCirc = t => Math.sqrt(-1 * (t - 2) * t)
export const easeInOutCirc = t => t < 0.5
  ? 0.5 * (1 - Math.sqrt(1 - 4 * t * t))
  : 0.5 * (1 + Math.sqrt(-3 + 8 * t - 4 * t * t))

const dampener = t => Math.E ** (-6.3 * t)
const bounce = t => Math.cos(5 * t)
export const overshoot = t => -1 * dampener(t) * bounce(t) + 1

/* -- Material Design curves -- */

/**
 * Faster ease in, slower ease out
 */
export const standard = t => t < 0.4031
  ? 12 * t ** 4
  : 1 / 1290 * (11 * Math.sqrt(-40000 * t * t + 80000 * t - 23359) - 129)

export const decelerate = easeOutCubic
export const accelerate = easeInCubic
export const sharp = easeInOutQuad
<commit_msg>perf: Small improvement to easings<commit_after>export const linear = t => t

export const easeInQuad = t => t * t
export const easeOutQuad = t => t * (2 - t)
export const easeInOutQuad = t => t < 0.5
  ? 2 * t * t
  : -1 + (4 - 2 * t) * t

export const easeInCubic = t => t ** 3
export const easeOutCubic = t => 1 + (t - 1) ** 3
export const easeInOutCubic = t => t < 0.5
  ? 4 * t ** 3
  : 1 + (t - 1) * (2 * t - 2) ** 2

export const easeInQuart = t => t ** 4
export const easeOutQuart = t => 1 - (t - 1) ** 4
export const easeInOutQuart = t => t < 0.5
  ? 8 * t ** 4
  : 1 - 8 * (t - 1) ** 4

export const easeInQuint = t => t ** 5
export const easeOutQuint = t => 1 + (t - 1) ** 5
export const easeInOutQuint = t => t < 0.5
  ? 16 * t ** 5
  : 1 + 16 * (t - 1) ** 5

export const easeInCirc = t => -1 * Math.sqrt(1 - t ** 2) + 1
export const easeOutCirc = t => Math.sqrt(-1 * (t - 2) * t)
export const easeInOutCirc = t => t < 0.5
  ? 0.5 * (1 - Math.sqrt(1 - 4 * t * t))
  : 0.5 * (1 + Math.sqrt(-3 + 8 * t - 4 * t * t))

export const overshoot = t => -1 * (Math.E ** (-6.3 * t)) * (Math.cos(5 * t)) + 1

/* -- Material Design curves -- */

/**
 * Faster ease in, slower ease out
 */
export const standard = t => t < 0.4031
  ? 12 * t ** 4
  : 1 / 1290 * (11 * Math.sqrt(-40000 * t * t + 80000 * t - 23359) - 129)

export const decelerate = easeOutCubic
export const accelerate = easeInCubic
export const sharp = easeInOutQuad
<|endoftext|>


<commit_before>define( 'argumenta/widgets/Proposition',
[
    "require-jquery",
    "argumenta/widgets/Base",
    "argumenta/widgets/AddTag",
    "argumenta/widgets/Tags",
    "text!./Proposition/template.html.mustache"
],
function( $, Base, AddTag, Tags, Template ) {

    var Proposition = Base.module( {

        moduleID: 'Proposition',
        template: Template,

        defaults: {
            index: 'P'
        },

        init: function( options ) {
            this.initProposition( options );
        },

        prototype: {

            // Gets the object type.
            getType: function() {
                return 'proposition';
            },

            // Gets the object sha1.
            getSha1: function() {
                return this.options.sha1;
            },

            // Inits this proposition widget.
            initProposition: function( options ) {
                var self = this;

                // Initial state.
                self.addTagVisible = false;
                self.tagsVisible = false;

                // Elements.
                self.main = self.element.children('.proposition-main');
                self.addTagButton = self.main.find('.add-tag-button');

                // Toggle details on click.
                self.element.on('click', function(event) {

                    // Don't trigger background elements.
                    var closestWidget = $(event.target).closest('div[class*="widget"]');
                    if ( !closestWidget.is(self.element) ) {
                        event.stopPropagation();
                        return true;
                    }

                    // Don't expand on mouse release after dragging.
                    if (self.element.hasClass('ui-state-just-dragged')) {
                        return;
                    }

                    // Don't interfere with links.
                    if ($(event.target).is('a')) {
                        return true;
                    }

                    self.toggleDetails();
                });

                self._bindAddTagButton();
                self._makeDraggable();
            },

            // Toggles details for this proposition.
            toggleDetails: function() {
                var self = this;
                self._toggleTags();
            },

            // Toggles tags for this proposition.
            _toggleTags: function() {
                var self = this;

                if ( typeof self.tags === "undefined" ) {
                    self._initTags();
                }
                else {
                    self.tags.element.toggle( 300 );
                    self.tagsVisible = !self.tagsVisible;
                }
            },

            // Toggles AddTag widget.
            toggleAddTag: function() {
                var self = this;

                // Initialize if necessary.
                if ( typeof self.addTag === "undefined" ) {
                    self._initAddTag();
                    self.addTag.element.hide();
                }

                // Fade in or out.
                self.addTag.element.toggle( 300 );
                self.addTagVisible = !self.addTagVisible;
            },

            // Inits the AddTag widget.
            _initAddTag: function() {
                var self = this;

                // Create a new widget targeting this proposition.
                var addTag = new AddTag( {
                    target_type: 'proposition',
                    target_sha1: self.getSha1()
                } );

                // Append the widget element.
                self.element.children('.add-tag-widget-container')
                            .append( addTag.element );

                // Save a reference to the nested widget.
                self.addTag = addTag;
            },

            // Inits any tags for this proposition.
            _initTags: function() {
                var self = this;
                var tagsContainer = self.element
                    .children('.proposition-details')
                    .children('.tags-widget-container');

                // Hide the container.
                tagsContainer.hide();

                // Create a new Tags widget targeting this proposition.
                var tags = new Tags( {
                    target_type: self.getType(),
                    target_sha1: self.getSha1(),
                    onLoad: function( tagsWidget ) {
                        // Append the element and reveal the container.
                        tagsContainer.append( tagsWidget.element );
                        tagsContainer.show( 300 );
                        self.tagsVisible = true;
                    }
                } );

                // Save a reference to the nested widget
                self.tags = tags;
            },

            // Binds AddTag button behavior.
            _bindAddTagButton: function() {
                var self = this;
                var link = $('.add-tag-link', self.element).first();

                link.on( 'click', function(event) {
                    self.toggleAddTag();
                    link.blur();    // Defocus.
                    return false;   // Prevent default and stop propagation.
                } );
            },

            // Makes this widget draggable.
            _makeDraggable: function() {
                var self = this;

                this.element.draggable( {
                    helper: 'clone',
                    appendTo: $('.drag-container')[0] || $('body'),
                    revert: true,
                    cancel: 'span, input',
                    handle: self.element.children(".proposition-main"),
                    cursor: 'move',

                    start: function(event, ui) {

                        // Prevent other actions on drag.
                        ui.helper.addClass('ui-state-just-dragged drag');

                        // Fix clone size.
                        ui.helper.css( {
                            'height': self.element.outerHeight() + 'px',
                            'width': self.element.outerWidth() + 'px'
                        } );

                        // Hide original.
                        self.element.css('visibility', "hidden");
                    },

                    stop: function(event, ui) {
                        // Prevent other actions on drag, then cleanup.
                        var removeDrag = function() {
                          ui.helper.removeClass('ui-state-just-dragged')
                        }
                        setTimeout(removeDrag, 300);

                        // Reveal original.
                        self.element.css('visibility', "");
                    }
                } );
            },

            // Renders UI, extending Base behavior.
            _renderUI: function() {
                var self = this;

                self._super('_renderUI');

                var textElem = self.element.find('.text').first();
                var textHtml = textElem.html();
                var newHtml  = textHtml.replace(/\r?\n|\r/g, '
');

                textElem.html( newHtml );

                return;
            },

            // Gets view options for template, extending Base behavior.
            _getViewOptions: function() {
                var self = this;
                var role_desc;

                switch ( self.options.index ) {
                    case 'P': role_desc = '';              break;
                    case 'C': role_desc = ' (Conclusion)'; break;
                    default : role_desc = ' (Premise)';    break;
                }

                var viewOptions = {
                    partial_sha1: self.options.sha1.substr(0, 20),
                    proposition_desc: 'Proposition' + role_desc,
                    details_desc: 'Proposition details\r\nSHA1: ' + self.options.sha1
                }
                return $.extend( viewOptions, self.options );
            }
        }
    } );

    return Proposition;
} );
<commit_msg>Make proposition dragging easier<commit_after>define( 'argumenta/widgets/Proposition',
[
    "require-jquery",
    "argumenta/widgets/Base",
    "argumenta/widgets/AddTag",
    "argumenta/widgets/Tags",
    "text!./Proposition/template.html.mustache"
],
function( $, Base, AddTag, Tags, Template ) {

    var Proposition = Base.module( {

        moduleID: 'Proposition',
        template: Template,

        defaults: {
            index: 'P'
        },

        init: function( options ) {
            this.initProposition( options );
        },

        prototype: {

            // Gets the object type.
            getType: function() {
                return 'proposition';
            },

            // Gets the object sha1.
            getSha1: function() {
                return this.options.sha1;
            },

            // Inits this proposition widget.
            initProposition: function( options ) {
                var self = this;

                // Initial state.
                self.addTagVisible = false;
                self.tagsVisible = false;

                // Elements.
                self.main = self.element.children('.proposition-main');
                self.addTagButton = self.main.find('.add-tag-button');

                // Toggle details on click.
                self.element.on('click', function(event) {

                    // Don't trigger background elements.
                    var closestWidget = $(event.target).closest('div[class*="widget"]');
                    if ( !closestWidget.is(self.element) ) {
                        event.stopPropagation();
                        return true;
                    }

                    // Don't expand on mouse release after dragging.
                    if (self.element.hasClass('ui-state-just-dragged')) {
                        return;
                    }

                    // Don't interfere with links.
                    if ($(event.target).is('a')) {
                        return true;
                    }

                    self.toggleDetails();
                });

                self._bindAddTagButton();
                self._makeDraggable();
            },

            // Toggles details for this proposition.
            toggleDetails: function() {
                var self = this;
                self._toggleTags();
            },

            // Toggles tags for this proposition.
            _toggleTags: function() {
                var self = this;

                if ( typeof self.tags === "undefined" ) {
                    self._initTags();
                }
                else {
                    self.tags.element.toggle( 300 );
                    self.tagsVisible = !self.tagsVisible;
                }
            },

            // Toggles AddTag widget.
            toggleAddTag: function() {
                var self = this;

                // Initialize if necessary.
                if ( typeof self.addTag === "undefined" ) {
                    self._initAddTag();
                    self.addTag.element.hide();
                }

                // Fade in or out.
                self.addTag.element.toggle( 300 );
                self.addTagVisible = !self.addTagVisible;
            },

            // Inits the AddTag widget.
            _initAddTag: function() {
                var self = this;

                // Create a new widget targeting this proposition.
                var addTag = new AddTag( {
                    target_type: 'proposition',
                    target_sha1: self.getSha1()
                } );

                // Append the widget element.
                self.element.children('.add-tag-widget-container')
                            .append( addTag.element );

                // Save a reference to the nested widget.
                self.addTag = addTag;
            },

            // Inits any tags for this proposition.
            _initTags: function() {
                var self = this;
                var tagsContainer = self.element
                    .children('.proposition-details')
                    .children('.tags-widget-container');

                // Hide the container.
                tagsContainer.hide();

                // Create a new Tags widget targeting this proposition.
                var tags = new Tags( {
                    target_type: self.getType(),
                    target_sha1: self.getSha1(),
                    onLoad: function( tagsWidget ) {
                        // Append the element and reveal the container.
                        tagsContainer.append( tagsWidget.element );
                        tagsContainer.show( 300 );
                        self.tagsVisible = true;
                    }
                } );

                // Save a reference to the nested widget
                self.tags = tags;
            },

            // Binds AddTag button behavior.
            _bindAddTagButton: function() {
                var self = this;
                var link = $('.add-tag-link', self.element).first();

                link.on( 'click', function(event) {
                    self.toggleAddTag();
                    link.blur();    // Defocus.
                    return false;   // Prevent default and stop propagation.
                } );
            },

            // Makes this widget draggable.
            _makeDraggable: function() {
                var self = this;

                this.element.draggable( {
                    helper: 'clone',
                    appendTo: $('.drag-container')[0] || $('body'),
                    revert: true,
                    cancel: 'input',
                    handle: self.element.children(".proposition-main"),
                    cursor: 'move',

                    start: function(event, ui) {

                        // Prevent other actions on drag.
                        ui.helper.addClass('ui-state-just-dragged drag');

                        // Fix clone size.
                        ui.helper.css( {
                            'height': self.element.outerHeight() + 'px',
                            'width': self.element.outerWidth() + 'px'
                        } );

                        // Hide original.
                        self.element.css('visibility', "hidden");
                    },

                    stop: function(event, ui) {
                        // Prevent other actions on drag, then cleanup.
                        var removeDrag = function() {
                          ui.helper.removeClass('ui-state-just-dragged')
                        }
                        setTimeout(removeDrag, 300);

                        // Reveal original.
                        self.element.css('visibility', "");
                    }
                } );
            },

            // Renders UI, extending Base behavior.
            _renderUI: function() {
                var self = this;

                self._super('_renderUI');

                var textElem = self.element.find('.text').first();
                var textHtml = textElem.html();
                var newHtml  = textHtml.replace(/\r?\n|\r/g, '
');

                textElem.html( newHtml );

                return;
            },

            // Gets view options for template, extending Base behavior.
            _getViewOptions: function() {
                var self = this;
                var role_desc;

                switch ( self.options.index ) {
                    case 'P': role_desc = '';              break;
                    case 'C': role_desc = ' (Conclusion)'; break;
                    default : role_desc = ' (Premise)';    break;
                }

                var viewOptions = {
                    partial_sha1: self.options.sha1.substr(0, 20),
                    proposition_desc: 'Proposition' + role_desc,
                    details_desc: 'Proposition details\r\nSHA1: ' + self.options.sha1
                }
                return $.extend( viewOptions, self.options );
            }
        }
    } );

    return Proposition;
} );
<|endoftext|>


<commit_before>
module.exports = lifecycle;

var log = require("../utils/log"),
  exec = require("../utils/exec"),
  npm = require("../../npm"),
  path = require("path");

function lifecycle (pkg, stage, cb) {
  while (pkg && pkg._data) pkg = pkg._data;
  if (!pkg) return cb(new Error("Invalid package data"));
  if (!pkg.scripts || !(stage in pkg.scripts)) return cb();

  // run package lifecycle scripts in the package root, or the nearest parent.
  var d = path.join(npm.dir, pkg.name, pkg.version, "package");
  while (d) {
    try {
      process.chdir(d);
      break;
    } catch (ex) {
      d = path.dirname(d);
    }
  }
  log(pkg._id + " " + stage, "lifecycle");

  // set the env variables, then run the script as a child process.
  // NOTE: The env vars won't work until node supports env hashes for child procs
  var env = makeEnv(pkg);
  env.npm_lifecycle_event = stage;

  // support stuff like "make install" or "node test/all.js"
  var args = pkg.scripts[stage].split(/\s/),
    script = args.shift();
  exec(script, args, env, cb);
}

function makeEnv (data, prefix, env) {
  if (data._lifecycleEnv) return data._lifecycleEnv;
  prefix = prefix || "npm_package_";
  if (!env) {
    env = {};
    for (var i in process.env) env[i] = process.env[i];
  } else {
    data._lifecycleEnv = env;
  }

  for (var i in data) if (i.charAt(0) !== "_") {
    if (data[i] && typeof(data[i]) === "object") {
      makeEnv(data[i], prefix+i+"_", env);
    } else {
      env[prefix+i] = String(data[i]);
    }
  }
  return env;
}
<commit_msg>Don't put the _lifecycleEnv stuff directly on objects as regular properties, because it leads to strange errors.<commit_after>
module.exports = lifecycle

var log = require("../utils/log")
  , exec = require("../utils/exec")
  , npm = require("../../npm")
  , path = require("path")

function lifecycle (pkg, stage, cb) {
  while (pkg && pkg._data) pkg = pkg._data
  if (!pkg) return cb(new Error("Invalid package data"))
  if (!pkg.scripts || !(stage in pkg.scripts)) return cb()

  // run package lifecycle scripts in the package root, or the nearest parent.
  var d = path.join(npm.dir, pkg.name, pkg.version, "package")
  while (d) {
    try {
      process.chdir(d)
      break
    } catch (ex) {
      d = path.dirname(d)
    }
  }
  log(pkg._id + " " + stage, "lifecycle")

  // set the env variables, then run the script as a child process.
  // NOTE: The env vars won't work until node supports env hashes for child procs
  var env = makeEnv(pkg)
  env.npm_lifecycle_event = stage

  // support stuff like "make install" or "node test/all.js"
  var args = pkg.scripts[stage].split(/\s/)
    , script = args.shift()
  exec(script, args, env, function (er, ok) {
    log([er, ok], "lifecycle "+stage)
    cb(er, ok)
  })
}

function makeEnv (data, prefix, env) {
  if (data._lifecycleEnv) return data._lifecycleEnv
  prefix = prefix || "npm_package_"
  if (!env) {
    env = {}
    for (var i in process.env) env[i] = process.env[i]
  } else {
    Object.defineProperty(data, "_lifecycleEnv", {value:env})
  }

  for (var i in data) if (i.charAt(0) !== "_") {
    if (data[i] && typeof(data[i]) === "object") {
      makeEnv(data[i], prefix+i+"_", env)
    } else {
      env[prefix+i] = String(data[i])
    }
  }
  return env
}
<|endoftext|>


<commit_before><commit_msg>Build after version change<commit_after><|endoftext|>


<commit_before>JSM.Touch = function ()
{
	this.down = false;
	this.prevX = 0;
	this.prevY = 0;
	this.currX = 0;
	this.currY = 0;
	this.diffX = 0;
	this.diffY = 0;
};

JSM.Touch.prototype.Start = function (event, div)
{
	if (event.touches.length === 0) {
		return;
	}
	var touch = event.touches[0];

	this.down = true;
	this.SetCurrent (touch, div);
	this.prevX = this.currX;
	this.prevY = this.currY;
};

JSM.Touch.prototype.Move = function (event, div)
{
	if (event.touches.length === 0) {
		return;
	}
	var touch = event.touches[0];

	this.SetCurrent (touch, div);
	this.diffX = this.currX - this.prevX;
	this.diffY = this.currY - this.prevY;
	this.prevX = this.currX;
	this.prevY = this.currY;
};

JSM.Touch.prototype.End = function (event, div)
{
	if (event.touches.length === 0) {
		return;
	}
	var touch = event.touches[0];

	this.down = false;
	this.SetCurrent (touch, div);
};

JSM.Touch.prototype.SetCurrent = function (touch, div)
{
	this.currX = touch.pageX;
	this.currY = touch.pageY;
	if (div !== undefined) {
		this.currX = touch.pageX - div.offsetLeft;
		this.currY = touch.pageY - div.offsetTop;
	}
};
<commit_msg>Correct offset handling in Touch, too.<commit_after>JSM.Touch = function ()
{
	this.down = false;
	this.prevX = 0;
	this.prevY = 0;
	this.currX = 0;
	this.currY = 0;
	this.diffX = 0;
	this.diffY = 0;
};

JSM.Touch.prototype.Start = function (event, div)
{
	if (event.touches.length === 0) {
		return;
	}
	var touch = event.touches[0];

	this.down = true;
	this.SetCurrent (touch, div);
	this.prevX = this.currX;
	this.prevY = this.currY;
};

JSM.Touch.prototype.Move = function (event, div)
{
	if (event.touches.length === 0) {
		return;
	}
	var touch = event.touches[0];

	this.SetCurrent (touch, div);
	this.diffX = this.currX - this.prevX;
	this.diffY = this.currY - this.prevY;
	this.prevX = this.currX;
	this.prevY = this.currY;
};

JSM.Touch.prototype.End = function (event, div)
{
	if (event.touches.length === 0) {
		return;
	}
	var touch = event.touches[0];

	this.down = false;
	this.SetCurrent (touch, div);
};

JSM.Touch.prototype.SetCurrent = function (touch, div)
{
	this.currX = touch.pageX;
	this.currY = touch.pageY;
	if (div !== undefined && div.offsetLeft !== undefined && div.offsetTop !== undefined) {
		this.currX = touch.pageX - div.offsetLeft;
		this.currY = touch.pageY - div.offsetTop;
	}
};
<|endoftext|>


<commit_before>/* globals alert */

import fetchAction from '@conveyal/woonerf/fetch'
import debounce from 'debounce'
import qs from 'qs'
import {push} from 'react-router-redux'
import {createAction} from 'redux-actions'

import {network} from '../utils/messages'

/**
 * Handle fetching errors.  Redirect to login if any response is a 401.
 *
 * @param  {String} alertMsg  The message to display in an alert
 * @param  {Mixed} err        the error from woonerf fetchAction
 * @param  {Mixed} res        the res from woonerf fetchAction
 * @return {Mixed}            return varies
 */
function fetchErrorHandler (alertMsg, err, res) {
  if (err.status === 401) {
    return push('/login')
  } else {
    debounce(() => {
      alert(alertMsg)
      // TODO: replace w/ modal?  alert halts js thread
      // so other failures will still get funnelled here
      // once execution resumes
    }, 5000, true)
  }
}

/**
 * Make generic model actions that communicate with a rest server
 *
 * @param  {Object} cfg An object with the following parameters:
 * - commands: An object with the keys being commands and the corresponding cfg of each command
 * -- redirectionStrategy: The redirection strategy to use upon success.
 *  Can be one of following: 'toEntity', 'toParent', 'toHome', undefined (no redirection)
 *  Defaults are as follows:
 *    Collection GET, GET: null
 *    Collection POST, PUT: toEntity
 *    DELETE: toParent
 * - parentKey: Key in model of parent's id
 *   (must be provided if using toParent redirection strategy)
 * - parentName: Name of app route to parent view
 *   (must be provided if using toParent redirection strategy)
 * - pluralName: Plural name of the model
 * - singularName: Singular name of the model
 * @return {Object}     The actions
 */
export default function makeGenericModelActions (cfg) {
  const {commands, parentKey, parentName, pluralName, singularName} = cfg
  const actions = {}

  // make local set actions
  const addLocally = createAction(`add ${singularName}`)
  const deleteLocally = createAction(`delete ${singularName}`)
  const setLocally = createAction(`set ${singularName}`)
  const setAllLocally = createAction(`set ${pluralName}`)

  const baseEndpoint = `/api/${singularName}`

  const redirectionStrategies = {
    'toEntity': (entity) => {
      return push(`/${singularName}/${entity._id}`)
    },
    'toHome': () => {
      return push('/')
    },
    'toParent': (entity) => {
      return push(`/${parentName}/${entity[parentKey]}`)
    }
  }

  /**
   * Do a redirect if needed
   *
   * @param  {Object} cfg Paremters as follows:
   *   - actions: The actions to append to
   *   - endpointCfg: The endpoint config
   *   - defaultStrategy: The default redire strategy to use (default: undefined)
   *   - redirectArgs: Arguments to provide to the redirectionStrategy
   */
  const doRedirectIfNecessary = (cfg) => {
    const redirectionStrategy = (cfg.endpointCfg.redirectionStrategy !== undefined
      ? cfg.endpointCfg.redirectionStrategy
      : cfg.defaultStrategy)

    if (redirectionStrategy) {
      const redirect = redirectionStrategies[redirectionStrategy](cfg.redirectArgs)
      if (redirect) {
        cfg.actions.push(redirect)
      }
    }
  }

  if (commands['Collection GET']) {
    actions.loadMany = (queryParams) => {
      // only include filteredKeys in querystring
      let queryString = ''
      if (queryParams) {
        queryString = qs.stringify(queryParams)
        if (queryString.length !== 0) {
          queryString = '?' + queryString
        }
      }

      // make request
      return fetchAction({
        next: (err, res) => {
          if (err) {
            return fetchErrorHandler(network.fetchingError, err, res)
          } else {
            return setAllLocally(res.value)
          }
        },
        url: baseEndpoint + queryString
      })
    }
  }

  if (commands['Collection POST']) {
    const endpointCfg = commands['Collection POST']
    actions.create = (newEntities) => {
      let body = newEntities
      if (!Array.isArray(body)) {
        body = [body]
      }
      return fetchAction({
        next: (err, res) => {
          if (err) {
            return fetchErrorHandler(network.savingError, err, res)
          } else {
            const createdEntities = res.value
            const actions = createdEntities.map((createdEntity) => addLocally(createdEntity))
            doRedirectIfNecessary({
              actions,
              endpointCfg,
              defaultStrategy: 'toEntity',
              redirectArgs: createdEntities[0]
            })
            return actions
          }
        },
        options: {
          body,
          method: 'POST'
        },
        url: baseEndpoint
      })
    }
  }

  if (commands['DELETE']) {
    const endpointCfg = commands['DELETE']
    actions.delete = (entity) => {
      return fetchAction({
        next: (err, res) => {
          if (err) {
            return fetchErrorHandler(network.savingError, err, res)
          } else {
            const actions = []
            doRedirectIfNecessary({
              actions,
              endpointCfg,
              defaultStrategy: 'toParent',
              redirectArgs: entity
            })
            actions.push(deleteLocally(entity))
            return actions
          }
        },
        options: {
          method: 'DELETE'
        },
        url: `${baseEndpoint}/${entity._id}`
      })
    }
  }

  if (commands['GET']) {
    actions.loadOne = (id) => fetchAction({
      next: (err, res) => {
        if (err) {
          return fetchErrorHandler(network.fetchingError, err, res)
        } else {
          return setLocally(res.value)
        }
      },
      url: `${baseEndpoint}/${id}`
    })
  }

  if (commands['PUT']) {
    const endpointCfg = commands['PUT']
    actions.update = (entity) => fetchAction({
      next: (err, res) => {
        if (err) {
          return fetchErrorHandler(network.savingError, err, res)
        } else {
          const updatedEntity = res.value
          const actions = [
            setLocally(updatedEntity)
          ]
          doRedirectIfNecessary({
            actions,
            endpointCfg,
            defaultStrategy: 'toEntity',
            redirectArgs: updatedEntity
          })
          return actions
        }
      },
      options: {
        body: entity,
        method: 'PUT'
      },
      url: `${baseEndpoint}/${entity._id}`
    })
  }

  return actions
}
<commit_msg>refactor(network): make debounce work correctly<commit_after>/* globals alert */

import fetchAction from '@conveyal/woonerf/fetch'
import debounce from 'debounce'
import qs from 'qs'
import {push} from 'react-router-redux'
import {createAction} from 'redux-actions'

import {network} from '../utils/messages'

/**
 * Handle fetching errors.  Redirect to login if any response is a 401.
 *
 * @param  {String} alertMsg  The message to display in an alert
 * @param  {Mixed} err        the error from woonerf fetchAction
 * @param  {Mixed} res        the res from woonerf fetchAction
 * @return {Mixed}            return varies
 */
function fetchErrorHandler (alertMsg, err, res) {
  if (err.status === 401) {
    return push('/login')
  } else {
    debouncedErrorDisplay(alertMsg)
  }
}

const debouncedErrorDisplay = debounce((alertMsg) => {
  alert(alertMsg)
  // TODO: replace w/ modal?  alert halts js thread
  // so other failures will still get funnelled here
  // once execution resumes
}, 5000, true)

/**
 * Make generic model actions that communicate with a rest server
 *
 * @param  {Object} cfg An object with the following parameters:
 * - commands: An object with the keys being commands and the corresponding cfg of each command
 * -- redirectionStrategy: The redirection strategy to use upon success.
 *  Can be one of following: 'toEntity', 'toParent', 'toHome', undefined (no redirection)
 *  Defaults are as follows:
 *    Collection GET, GET: null
 *    Collection POST, PUT: toEntity
 *    DELETE: toParent
 * - parentKey: Key in model of parent's id
 *   (must be provided if using toParent redirection strategy)
 * - parentName: Name of app route to parent view
 *   (must be provided if using toParent redirection strategy)
 * - pluralName: Plural name of the model
 * - singularName: Singular name of the model
 * @return {Object}     The actions
 */
export default function makeGenericModelActions (cfg) {
  const {commands, parentKey, parentName, pluralName, singularName} = cfg
  const actions = {}

  // make local set actions
  const addLocally = createAction(`add ${singularName}`)
  const deleteLocally = createAction(`delete ${singularName}`)
  const setLocally = createAction(`set ${singularName}`)
  const setAllLocally = createAction(`set ${pluralName}`)

  const baseEndpoint = `/api/${singularName}`

  const redirectionStrategies = {
    'toEntity': (entity) => {
      return push(`/${singularName}/${entity._id}`)
    },
    'toHome': () => {
      return push('/')
    },
    'toParent': (entity) => {
      return push(`/${parentName}/${entity[parentKey]}`)
    }
  }

  /**
   * Do a redirect if needed
   *
   * @param  {Object} cfg Paremters as follows:
   *   - actions: The actions to append to
   *   - endpointCfg: The endpoint config
   *   - defaultStrategy: The default redire strategy to use (default: undefined)
   *   - redirectArgs: Arguments to provide to the redirectionStrategy
   */
  const doRedirectIfNecessary = (cfg) => {
    const redirectionStrategy = (cfg.endpointCfg.redirectionStrategy !== undefined
      ? cfg.endpointCfg.redirectionStrategy
      : cfg.defaultStrategy)

    if (redirectionStrategy) {
      const redirect = redirectionStrategies[redirectionStrategy](cfg.redirectArgs)
      if (redirect) {
        cfg.actions.push(redirect)
      }
    }
  }

  if (commands['Collection GET']) {
    actions.loadMany = (queryParams) => {
      // only include filteredKeys in querystring
      let queryString = ''
      if (queryParams) {
        queryString = qs.stringify(queryParams)
        if (queryString.length !== 0) {
          queryString = '?' + queryString
        }
      }

      // make request
      return fetchAction({
        next: (err, res) => {
          if (err) {
            return fetchErrorHandler(network.fetchingError, err, res)
          } else {
            return setAllLocally(res.value)
          }
        },
        url: baseEndpoint + queryString
      })
    }
  }

  if (commands['Collection POST']) {
    const endpointCfg = commands['Collection POST']
    actions.create = (newEntities) => {
      let body = newEntities
      if (!Array.isArray(body)) {
        body = [body]
      }
      return fetchAction({
        next: (err, res) => {
          if (err) {
            return fetchErrorHandler(network.savingError, err, res)
          } else {
            const createdEntities = res.value
            const actions = createdEntities.map((createdEntity) => addLocally(createdEntity))
            doRedirectIfNecessary({
              actions,
              endpointCfg,
              defaultStrategy: 'toEntity',
              redirectArgs: createdEntities[0]
            })
            return actions
          }
        },
        options: {
          body,
          method: 'POST'
        },
        url: baseEndpoint
      })
    }
  }

  if (commands['DELETE']) {
    const endpointCfg = commands['DELETE']
    actions.delete = (entity) => {
      return fetchAction({
        next: (err, res) => {
          if (err) {
            return fetchErrorHandler(network.savingError, err, res)
          } else {
            const actions = []
            doRedirectIfNecessary({
              actions,
              endpointCfg,
              defaultStrategy: 'toParent',
              redirectArgs: entity
            })
            actions.push(deleteLocally(entity))
            return actions
          }
        },
        options: {
          method: 'DELETE'
        },
        url: `${baseEndpoint}/${entity._id}`
      })
    }
  }

  if (commands['GET']) {
    actions.loadOne = (id) => fetchAction({
      next: (err, res) => {
        if (err) {
          return fetchErrorHandler(network.fetchingError, err, res)
        } else {
          return setLocally(res.value)
        }
      },
      url: `${baseEndpoint}/${id}`
    })
  }

  if (commands['PUT']) {
    const endpointCfg = commands['PUT']
    actions.update = (entity) => fetchAction({
      next: (err, res) => {
        if (err) {
          return fetchErrorHandler(network.savingError, err, res)
        } else {
          const updatedEntity = res.value
          const actions = [
            setLocally(updatedEntity)
          ]
          doRedirectIfNecessary({
            actions,
            endpointCfg,
            defaultStrategy: 'toEntity',
            redirectArgs: updatedEntity
          })
          return actions
        }
      },
      options: {
        body: entity,
        method: 'PUT'
      },
      url: `${baseEndpoint}/${entity._id}`
    })
  }

  return actions
}
<|endoftext|>


<commit_before>/**
 * GNP
 * Copyright (C) 2014 DeathsbreedGames
 * License: GNU Affero GPLv3
 *
 */
var DeathsbreedGames = DeathsbreedGames || {};

DeathsbreedGames.Game = function() {};

function degToRad(deg) {
	return deg * (Math.PI / 180);
}

DeathsbreedGames.Game.prototype = {
	create:function() {
		// Setup basic game stuff
		this.game.world.setBounds(0, 0, 480, 320);
		this.background = this.game.add.sprite(0, 0, 'bg');

		// Create players and player variables
		this.player1 = this.game.add.sprite(20, 120, 'paddle');
		this.player2 = this.game.add.sprite(446, 120, 'paddle');
		this.player1Score = 0;
		this.player2Score = 0;
		this.playerSpeed = 250;

		// Allow players to collide with the edge of the world
		this.game.physics.arcade.enable(this.player1);
		this.game.physics.arcade.enable(this.player2);
		this.player1.body.collideWorldBounds = true;
		this.player2.body.collideWorldBounds = true;

		// Setup the ball
		this.ball = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'ball');
		this.game.physics.arcade.enable(this.ball);
		this.ballSpeed = 300;
		this.ball.body.velocity.x = Math.cos(degToRad(45)) * this.ballSpeed;
		this.ball.body.velocity.y = Math.sin(degToRad(45)) * this.ballSpeed;
	},
	update:function() {
		// Check for player 1 input
		if(this.game.input.keyboard.isDown(Phaser.Keyboard.W)) {
			this.player1.body.velocity.y = -this.playerSpeed;
		} else if(this.game.input.keyboard.isDown(Phaser.Keyboard.S)) {
			this.player1.body.velocity.y = this.playerSpeed;
		} else {
			this.player1.body.velocity.y = 0;
		}

		// Check for player 2 input
		if(this.game.input.keyboard.isDown(Phaser.Keyboard.UP)) {
			this.player2.body.velocity.y = -this.playerSpeed;
		} else if(this.game.input.keyboard.isDown(Phaser.Keyboard.DOWN)) {
			this.player2.body.velocity.y = this.playerSpeed;
		} else {
			this.player2.body.velocity.y = 0;
		}
	}
};
<commit_msg>Simplified getting velocities for the ball.<commit_after>/**
 * GNP
 * Copyright (C) 2014 DeathsbreedGames
 * License: GNU Affero GPLv3
 *
 */
var DeathsbreedGames = DeathsbreedGames || {};

DeathsbreedGames.Game = function() {};

// Variables and functions for calculating ball x and y velocities
var ballSpeed = 300;
function getVel(deg) {
	var x = Math.cos(deg * (Math.PI / 180)) * ballSpeed;
	var y = Math.sin(deg * (Math.PI / 180)) * ballSpeed;
	return new Phaser.Point(x, y);
}

DeathsbreedGames.Game.prototype = {
	create:function() {
		// Setup basic game stuff
		this.game.world.setBounds(0, 0, 480, 320);
		this.background = this.game.add.sprite(0, 0, 'bg');

		// Create players and player variables
		this.player1 = this.game.add.sprite(20, 120, 'paddle');
		this.player2 = this.game.add.sprite(446, 120, 'paddle');
		this.player1Score = 0;
		this.player2Score = 0;
		this.playerSpeed = 250;

		// Allow players to collide with the edge of the world
		this.game.physics.arcade.enable(this.player1);
		this.game.physics.arcade.enable(this.player2);
		this.player1.body.collideWorldBounds = true;
		this.player2.body.collideWorldBounds = true;

		// Setup the ball
		this.ball = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'ball');
		this.game.physics.arcade.enable(this.ball);
		this.ball.body.velocity = getVel(45);
	},
	update:function() {
		// Check for player 1 input
		if(this.game.input.keyboard.isDown(Phaser.Keyboard.W)) {
			this.player1.body.velocity.y = -this.playerSpeed;
		} else if(this.game.input.keyboard.isDown(Phaser.Keyboard.S)) {
			this.player1.body.velocity.y = this.playerSpeed;
		} else {
			this.player1.body.velocity.y = 0;
		}

		// Check for player 2 input
		if(this.game.input.keyboard.isDown(Phaser.Keyboard.UP)) {
			this.player2.body.velocity.y = -this.playerSpeed;
		} else if(this.game.input.keyboard.isDown(Phaser.Keyboard.DOWN)) {
			this.player2.body.velocity.y = this.playerSpeed;
		} else {
			this.player2.body.velocity.y = 0;
		}
	}
};
<|endoftext|>