<commit_before>import * as globals from './globals'
import {createComponent, defineComponent, mountComponent} from './core/component'
import $ from 'bianco.query'
import {DOMattributesToObject} from '@riotjs/util/dom'
import {callOrAssign} from '@riotjs/util/functions'
import compose from 'cumpa'
import cssManager from './core/css-manager'
import {isFunction} from '@riotjs/util/checks'
import {panic} from '@riotjs/util/misc'

const { DOM_COMPONENT_INSTANCE_PROPERTY, COMPONENTS_IMPLEMENTATION_MAP, PLUGINS_SET } = globals

/**
 * Evaluate the component properties either from its real attributes or from its initial user properties
 * @param   {HTMLElement} element - component root
 * @param   {Object}  initialProps - initial props
 * @returns {Object} component props key value pairs
 */
function evaluateInitialProps(element, initialProps = []) {
  return {
    ...DOMattributesToObject(element),
    ...callOrAssign(initialProps)
  }
}

/**
 * Riot public api
 */

/**
 * Register a custom tag by name
 * @param   {string} name - component name
 * @param   {Object} implementation - tag implementation
 * @returns {Map} map containing all the components implementations
 */
export function register(name, {css, template, exports}) {
  if (COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component "${name}" was already registered`)

  COMPONENTS_IMPLEMENTATION_MAP.set(name, createComponent({name, css, template, exports}))

  return COMPONENTS_IMPLEMENTATION_MAP
}

/**
 * Unregister a riot web component
 * @param   {string} name - component name
 * @returns {Map} map containing all the components implementations
 */
export function unregister(name) {
  if (!COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component "${name}" was never registered`)

  COMPONENTS_IMPLEMENTATION_MAP.delete(name)
  cssManager.remove(name)

  return COMPONENTS_IMPLEMENTATION_MAP
}

/**
 * Mounting function that will work only for the components that were globally registered
 * @param   {string|HTMLElement} selector - query for the selection or a DOM element
 * @param   {Object} initialProps - the initial component properties
 * @param   {string} name - optional component name
 * @returns {Array} list of nodes upgraded
 */
export function mount(selector, initialProps, name) {
  return $(selector).map(element => mountComponent(element, evaluateInitialProps(element, initialProps), name))
}

/**
 * Sweet unmounting helper function for the DOM node mounted manually by the user
 * @param   {string|HTMLElement} selector - query for the selection or a DOM element
 * @param   {boolean|null} keepRootElement - if true keep the root element
 * @returns {Array} list of nodes unmounted
 */
export function unmount(selector, keepRootElement) {
  return $(selector).map(element => {
    if (element[DOM_COMPONENT_INSTANCE_PROPERTY]) {
      element[DOM_COMPONENT_INSTANCE_PROPERTY].unmount(keepRootElement)
    }
    return element
  })
}

/**
 * Define a riot plugin
 * @param   {Function} plugin - function that will receive all the components created
 * @returns {Set} the set containing all the plugins installed
 */
export function install(plugin) {
  if (!isFunction(plugin)) panic('Plugins must be of type function')
  if (PLUGINS_SET.has(plugin)) panic('This plugin was already install')

  PLUGINS_SET.add(plugin)

  return PLUGINS_SET
}

/**
 * Uninstall a riot plugin
 * @param   {Function} plugin - plugin previously installed
 * @returns {Set} the set containing all the plugins installed
 */
export function uninstall(plugin) {
  if (!PLUGINS_SET.has(plugin)) panic('This plugin was never installed')

  PLUGINS_SET.delete(plugin)

  return PLUGINS_SET
}

/**
 * Helpter method to create component without relying on the registered ones
 * @param   {Object} implementation - component implementation
 * @returns {Function} function that will allow you to mount a riot component on a DOM node
 */
export function component(implementation) {
  return (el, props, {slots, attributes} = {}) => compose(
    c => c.mount(el),
    c => c({props: evaluateInitialProps(el, props), slots, attributes}),
    createComponent
  )(implementation)
}

/**
 * Lift a riot component Interface into a pure riot object
 * @param   {Function} func - RiotPureComponent factory function
 * @returns {Function} the lifted original function received as argument
 */
export function pure(func) {
  if (!isFunction(func)) panic('riot.pure accepts only arguments of type "function"')
  func[globals.IS_PURE_SYMBOL] = true
  return func
}

/** @type {string} current riot version */
export const version = 'WIP'

// expose some internal stuff that might be used from external tools
export const __ = {
  cssManager,
  createComponent,
  defineComponent,
  globals
}
<commit_msg>added: the parentScope to the  mount properties<commit_after>import * as globals from './globals'
import {createComponent, defineComponent, mountComponent} from './core/component'
import $ from 'bianco.query'
import {DOMattributesToObject} from '@riotjs/util/dom'
import {callOrAssign} from '@riotjs/util/functions'
import compose from 'cumpa'
import cssManager from './core/css-manager'
import {isFunction} from '@riotjs/util/checks'
import {panic} from '@riotjs/util/misc'

const { DOM_COMPONENT_INSTANCE_PROPERTY, COMPONENTS_IMPLEMENTATION_MAP, PLUGINS_SET } = globals

/**
 * Evaluate the component properties either from its real attributes or from its initial user properties
 * @param   {HTMLElement} element - component root
 * @param   {Object}  initialProps - initial props
 * @returns {Object} component props key value pairs
 */
function evaluateInitialProps(element, initialProps = []) {
  return {
    ...DOMattributesToObject(element),
    ...callOrAssign(initialProps)
  }
}

/**
 * Riot public api
 */

/**
 * Register a custom tag by name
 * @param   {string} name - component name
 * @param   {Object} implementation - tag implementation
 * @returns {Map} map containing all the components implementations
 */
export function register(name, {css, template, exports}) {
  if (COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component "${name}" was already registered`)

  COMPONENTS_IMPLEMENTATION_MAP.set(name, createComponent({name, css, template, exports}))

  return COMPONENTS_IMPLEMENTATION_MAP
}

/**
 * Unregister a riot web component
 * @param   {string} name - component name
 * @returns {Map} map containing all the components implementations
 */
export function unregister(name) {
  if (!COMPONENTS_IMPLEMENTATION_MAP.has(name)) panic(`The component "${name}" was never registered`)

  COMPONENTS_IMPLEMENTATION_MAP.delete(name)
  cssManager.remove(name)

  return COMPONENTS_IMPLEMENTATION_MAP
}

/**
 * Mounting function that will work only for the components that were globally registered
 * @param   {string|HTMLElement} selector - query for the selection or a DOM element
 * @param   {Object} initialProps - the initial component properties
 * @param   {string} name - optional component name
 * @returns {Array} list of nodes upgraded
 */
export function mount(selector, initialProps, name) {
  return $(selector).map(element => mountComponent(element, evaluateInitialProps(element, initialProps), name))
}

/**
 * Sweet unmounting helper function for the DOM node mounted manually by the user
 * @param   {string|HTMLElement} selector - query for the selection or a DOM element
 * @param   {boolean|null} keepRootElement - if true keep the root element
 * @returns {Array} list of nodes unmounted
 */
export function unmount(selector, keepRootElement) {
  return $(selector).map(element => {
    if (element[DOM_COMPONENT_INSTANCE_PROPERTY]) {
      element[DOM_COMPONENT_INSTANCE_PROPERTY].unmount(keepRootElement)
    }
    return element
  })
}

/**
 * Define a riot plugin
 * @param   {Function} plugin - function that will receive all the components created
 * @returns {Set} the set containing all the plugins installed
 */
export function install(plugin) {
  if (!isFunction(plugin)) panic('Plugins must be of type function')
  if (PLUGINS_SET.has(plugin)) panic('This plugin was already install')

  PLUGINS_SET.add(plugin)

  return PLUGINS_SET
}

/**
 * Uninstall a riot plugin
 * @param   {Function} plugin - plugin previously installed
 * @returns {Set} the set containing all the plugins installed
 */
export function uninstall(plugin) {
  if (!PLUGINS_SET.has(plugin)) panic('This plugin was never installed')

  PLUGINS_SET.delete(plugin)

  return PLUGINS_SET
}

/**
 * Helpter method to create component without relying on the registered ones
 * @param   {Object} implementation - component implementation
 * @returns {Function} function that will allow you to mount a riot component on a DOM node
 */
export function component(implementation) {
  return (el, props, {slots, attributes, parentScope} = {}) => compose(
    c => c.mount(el, parentScope),
    c => c({props: evaluateInitialProps(el, props), slots, attributes}),
    createComponent
  )(implementation)
}

/**
 * Lift a riot component Interface into a pure riot object
 * @param   {Function} func - RiotPureComponent factory function
 * @returns {Function} the lifted original function received as argument
 */
export function pure(func) {
  if (!isFunction(func)) panic('riot.pure accepts only arguments of type "function"')
  func[globals.IS_PURE_SYMBOL] = true
  return func
}

/** @type {string} current riot version */
export const version = 'WIP'

// expose some internal stuff that might be used from external tools
export const __ = {
  cssManager,
  createComponent,
  defineComponent,
  globals
}
<|endoftext|>


<commit_before>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

define(['jquery', 'oae.core', 'jquery.spectrum'], function($, oae) {

    return function(uid, showSettings, widgetData) {

        // The widget container
        var $rootel = $('#' + uid);

        // Variable that will cache the default skin for the current tenant
        var defaultSkin = {};

        // Variable that will cache the current skin values
        var activeSkin = {};

        /**
         * Save the new skin values. The back-end requires us to send all skin values
         * that are different than the default values
         *
         * @param  {Object}      nonDefaultValues    Object of skin values that override default skin values. e.g., `{institutional-logo-url: "'/shared/oae/img/oae-logo.png'"}`
         * @param  {Function}    callback            Standard callback function
         * @param  {Object}      callback.err        Error object containing error code and error message
         */
        var saveSkin = function(nonDefaultValues, callback) {
            // Return straight away when no overrides have been provided
            if (_.keys(nonDefaultValues).length === 0) {
                return callback();
            }

            var data = {};
            // Create the JSON object to be sent to the server containing all non-default values
            $.each(nonDefaultValues, function(changedProperty, change) {
                data['oae-ui/skin/variables/' + changedProperty] = change;
            });

            var url = '/api/config';
            // Append the tenant alias when we're not on the tenant server itself
            if (widgetData.context.isTenantOnGlobalAdminServer) {
                url += '/' + widgetData.context.alias;
            }

            // Save the skin values
            $.ajax({
                'url': url,
                'type': 'POST',
                'data': data,
                'success': function() {
                    callback();
                },
                'error': function(jqXHR, textStatus) {
                    callback({'code': jqXHR.status, 'msg': jqXHR.responseText});
                }
            });
        };

        /**
         * Revert all skin values that have been reverted/changed back to the default skin value
         *
         * @param  {String[]}    revertedValues    Array of skin values to be reverted to the default
         * @param  {Function}    callback          Standard callback function
         * @param  {Object}      callback.err      Error object containing error code and error message
         */
        var revertSkin = function(revertedValues, callback) {
            // Return straight away when no reverted values have been provided
            if (revertedValues.length === 0) {
                return callback();
            }

            var data = [];
            // Create the JSON object to be sent to the server containing all values to reset
            $.each(revertedValues, function(propertyIndex, property) {
                data.push('oae-ui/skin/variables/' + property);
            });

            // When we are on the tenant server itself, we don't need
            // to add the tenant alias to the endpoint
            var url = '/api/config/clear';
            if (widgetData.context.isTenantOnGlobalAdminServer) {
                url = '/api/config/' + widgetData.context.alias + '/clear';
            }

            // Revert the skin values
            $.ajax({
                'url': url,
                'type': 'POST',
                'data': {
                    'configFields': data
                },
                'success': function() {
                    callback();
                },
                'error': function(jqXHR, textStatus) {
                    callback({'code': jqXHR.status, 'msg': jqXHR.responseText});
                }
            });
        };

        /**
         * Update active skin values to match the entered values
         */
        var refreshActiveSkin = function() {
            $('#skinning-form input').each(function(index, input) {
                activeSkin[$(input).attr('name')] = $.trim($(input).val());
            });
        };

        /**
         * Compare the selected skin values against the default skin and return the changed values only.
         * @return {Object}  changedValues                     The skinning values that have been changed or reverted
         * @return {Object}  changedValues.nonDefaultValues    The skinning values that have been changed (i.e. are different to the default skin)
         * @return {Object}  changedValues.revertedValues      The skinning values that should be reverted to the default skin values
         */
        var getSkinChanges = function() {
            var nonDefaultValues = {};
            var revertedValues = [];

            // Loop over the form input fields to see which values need to be stored or reverted
            $('#skinning-form input').each(function(index, input) {

                // Get the ID and data type of the skin element
                var name = $(input).attr('name');
                var type = $(input).attr('data-type');

                // Get the default, active, and new values
                var defaultValue = defaultSkin[name];
                var activeValue = activeSkin[name];
                var newValue = $.trim($(input).val());

                // If the field is a color, match as colors
                if (type === 'color') {
                    // If the color is different to the default skin color, its name and
                    // current value are added to the non-default list
                    if (!tinycolor.equals(newValue, defaultValue)) {
                        nonDefaultValues[name] = newValue;
                    // If the user changed a non-default color back to the default
                    // value, its name is added to the reverted list
                    } else if (!tinycolor.equals(activeValue, defaultValue)) {
                        revertedValues.push(name);
                    }
                // The only other choice is an input field, handle as string
                } else {
                    // If the value is different to the default value, its name and current
                    // value are added to the non-default list
                    if (newValue !== defaultValue) {
                        nonDefaultValues[name] = newValue;
                    // If the user changed a non-default value back to the default
                    // value, its name is added to the reverted list
                    } else if (activeValue !== defaultValue) {
                        revertedValues.push(name);
                    }
                }
            });

            // Returns the object of skin values to be saved and reverted
            return {
                'nonDefaultValues': nonDefaultValues,
                'revertedValues': revertedValues
            };
        };

        /**
         * Persist the new skin. All values that override default skin values will be saved as the
         * skin overrides. All values that have been reverted back to the default value will be reverted.
         */
        var applySkinChanges = function() {
            // Retrieve the skin changes
            var skinChanges = getSkinChanges();

            // Save the skin overrides
            saveSkin(skinChanges.nonDefaultValues, function(saveSkinErr) {
                // Revert the reverted skin values
                revertSkin(skinChanges.revertedValues, function(revertSkinErr) {
                    // Show a success/error notification and update state
                    if (saveSkinErr || revertSkinErr) {
                        oae.api.util.notification(
                            oae.api.i18n.translate('__MSG__SKIN_NOT_SAVED__', 'skinning'),
                            oae.api.i18n.translate('__MSG__SKIN_COULD_NOT_BE_SAVED__', 'skinning'),
                            'error');
                    } else {
                        refreshActiveSkin();
                        oae.api.util.notification(
                            oae.api.i18n.translate('__MSG__SKIN_SAVED__', 'skinning'),
                            oae.api.i18n.translate('__MSG__SKIN_SUCCESSFULLY_SAVED__', 'skinning'));
                    }
                });
            });

            // Return false to avoid default form submit behavior
            return false;
        };

        /**
         * Revert a skin value back to its original value as defined in the
         * base LESS file. Therefore, this will not necessarily revert the
         * value back to its previous value.
         */
        var revertSkinValue = function() {
            var $input = $('input', $(this).parents('.form-group'));
            var defaultValue = defaultSkin[$input.attr('name')];
            // If the variable is a color, we use the set method provided by jQuery spectrum
            if ($input.attr('data-type') === 'color') {
                $input.spectrum('set', defaultValue);
            } else {
                $input.val(defaultValue);
            }
        };

        /**
         * Initialize the list of available skinning variables and their values
         */
        var loadSkin = function() {
            $.ajax({
                'url': '/api/ui/skin/variables',
                'data': {
                    'tenant': widgetData.context.alias
                },
                'success': function(data) {
                    // The stored skin values for the current tenant can be found in the configuration
                    // object. This will be stored as a stringified JSON object, so we need to parse this first
                    var configuredSkin = {};
                    if (widgetData.configuration['oae-ui'].skin.variables) {
                        configuredSkin = widgetData.configuration['oae-ui'].skin.variables;
                    }

                    // For all of the values in the available skin variables, we check if the current tenant
                    // has a stored value that overrides the default value. If the tenant doesn't have a value
                    // for a variable, the default value will be used
                    $.each(data.results, function(configSectionIndex, configSection) {
                        $.each(configSection.subsections, function(configSubsectionIndex, configSubsection) {
                            $.each(configSubsection.variables, function(variableIndex, variable) {
                                variable.value = configuredSkin[variable.name] || variable.defaultValue;
                                defaultSkin[variable.name] = variable.defaultValue;
                                activeSkin[variable.name] = variable.value;
                            });
                        });
                    });

                    // Render the editable skin values
                    oae.api.util.template().render($('#skinning-template', $rootel), data, $('#skinning-container', $rootel));

                    // Initialize the jQuery.spectrum color pickers
                    $('[data-type="color"]').spectrum({
                        'preferredFormat': 'rgb',
                        'showAlpha': true,
                        'showButtons': false,
                        'showInitial': true,
                        'showInput': true
                    });
                }
            });
        };

        var uploadLogo = function(e) {
            e.preventDefault();

            // Get the extension of the selected file and match it against supported types
            var extension = $('#skinning-logo-upload')[0].files[0].name.split('.').pop();
            var validType = extension.match(/(gif|jpe?g|png)$/i);

            // If no valid image type has been submitted, show a notification
            if (!validType) {
                oae.api.util.notification(
                    oae.api.i18n.translate('__MSG__INVALID_LOGO__', 'skinning'),
                    oae.api.i18n.translate('__MSG__SELECT_A_VALID_LOGO__', 'skinning'),
                    'error'
                );
            } else {
                var formData = new FormData(this);
                formData.append('tenant', widgetData.context.alias);
                $.ajax({
                    'url': '/api/ui/skin/logo',
                    'data': formData,
                    'type': 'POST',
                    'processData': false,
                    'contentType': false,
                    'success': function(data) {
                        saveSkin({'institutional-logo-url': '\''+ data.url +'\''}, function(err) {
                            if (err) {
                                oae.api.util.notification(
                                    oae.api.i18n.translate('__MSG__SKIN_NOT_SAVED__', 'skinning'),
                                    oae.api.i18n.translate('__MSG__SKIN_COULD_NOT_BE_SAVED__', 'skinning'),
                                    'error');
                            } else {
                                oae.api.util.notification(
                                    oae.api.i18n.translate('__MSG__SKIN_SAVED__', 'skinning'),
                                    oae.api.i18n.translate('__MSG__SKIN_SUCCESSFULLY_SAVED__', 'skinning'));
                            }
                        });
                    }
                });
            }
        };

        /**
         * Add event binding for the skinning related functionality
         */
        var addBinding = function() {
            // Revert skin value
            $rootel.on('click', '.skinning-revert', revertSkinValue);
            // Change skin
            $rootel.on('submit', '#skinning-form', applySkinChanges);
            // Upload logo
            $rootel.on('submit', '#logo-upload-form', uploadLogo);
        };

        addBinding();
        loadSkin();
    };
});
<commit_msg>Refresh the page after loading new logo<commit_after>/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may
 * obtain a copy of the License at
 *
 *     http://opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

define(['jquery', 'oae.core', 'jquery.spectrum'], function($, oae) {

    return function(uid, showSettings, widgetData) {

        // The widget container
        var $rootel = $('#' + uid);

        // Variable that will cache the default skin for the current tenant
        var defaultSkin = {};

        // Variable that will cache the current skin values
        var activeSkin = {};

        /**
         * Save the new skin values. The back-end requires us to send all skin values
         * that are different than the default values
         *
         * @param  {Object}      nonDefaultValues    Object of skin values that override default skin values. e.g., `{institutional-logo-url: "'/shared/oae/img/oae-logo.png'"}`
         * @param  {Function}    callback            Standard callback function
         * @param  {Object}      callback.err        Error object containing error code and error message
         */
        var saveSkin = function(nonDefaultValues, callback) {
            // Return straight away when no overrides have been provided
            if (_.keys(nonDefaultValues).length === 0) {
                return callback();
            }

            var data = {};
            // Create the JSON object to be sent to the server containing all non-default values
            $.each(nonDefaultValues, function(changedProperty, change) {
                data['oae-ui/skin/variables/' + changedProperty] = change;
            });

            var url = '/api/config';
            // Append the tenant alias when we're not on the tenant server itself
            if (widgetData.context.isTenantOnGlobalAdminServer) {
                url += '/' + widgetData.context.alias;
            }

            // Save the skin values
            $.ajax({
                'url': url,
                'type': 'POST',
                'data': data,
                'success': function() {
                    callback();
                },
                'error': function(jqXHR, textStatus) {
                    callback({'code': jqXHR.status, 'msg': jqXHR.responseText});
                }
            });
        };

        /**
         * Revert all skin values that have been reverted/changed back to the default skin value
         *
         * @param  {String[]}    revertedValues    Array of skin values to be reverted to the default
         * @param  {Function}    callback          Standard callback function
         * @param  {Object}      callback.err      Error object containing error code and error message
         */
        var revertSkin = function(revertedValues, callback) {
            // Return straight away when no reverted values have been provided
            if (revertedValues.length === 0) {
                return callback();
            }

            var data = [];
            // Create the JSON object to be sent to the server containing all values to reset
            $.each(revertedValues, function(propertyIndex, property) {
                data.push('oae-ui/skin/variables/' + property);
            });

            // When we are on the tenant server itself, we don't need
            // to add the tenant alias to the endpoint
            var url = '/api/config/clear';
            if (widgetData.context.isTenantOnGlobalAdminServer) {
                url = '/api/config/' + widgetData.context.alias + '/clear';
            }

            // Revert the skin values
            $.ajax({
                'url': url,
                'type': 'POST',
                'data': {
                    'configFields': data
                },
                'success': function() {
                    callback();
                },
                'error': function(jqXHR, textStatus) {
                    callback({'code': jqXHR.status, 'msg': jqXHR.responseText});
                }
            });
        };

        /**
         * Update active skin values to match the entered values
         */
        var refreshActiveSkin = function() {
            $('#skinning-form input').each(function(index, input) {
                activeSkin[$(input).attr('name')] = $.trim($(input).val());
            });
        };

        /**
         * Compare the selected skin values against the default skin and return the changed values only.
         * @return {Object}  changedValues                     The skinning values that have been changed or reverted
         * @return {Object}  changedValues.nonDefaultValues    The skinning values that have been changed (i.e. are different to the default skin)
         * @return {Object}  changedValues.revertedValues      The skinning values that should be reverted to the default skin values
         */
        var getSkinChanges = function() {
            var nonDefaultValues = {};
            var revertedValues = [];

            // Loop over the form input fields to see which values need to be stored or reverted
            $('#skinning-form input').each(function(index, input) {

                // Get the ID and data type of the skin element
                var name = $(input).attr('name');
                var type = $(input).attr('data-type');

                // Get the default, active, and new values
                var defaultValue = defaultSkin[name];
                var activeValue = activeSkin[name];
                var newValue = $.trim($(input).val());

                // If the field is a color, match as colors
                if (type === 'color') {
                    // If the color is different to the default skin color, its name and
                    // current value are added to the non-default list
                    if (!tinycolor.equals(newValue, defaultValue)) {
                        nonDefaultValues[name] = newValue;
                    // If the user changed a non-default color back to the default
                    // value, its name is added to the reverted list
                    } else if (!tinycolor.equals(activeValue, defaultValue)) {
                        revertedValues.push(name);
                    }
                // The only other choice is an input field, handle as string
                } else {
                    // If the value is different to the default value, its name and current
                    // value are added to the non-default list
                    if (newValue !== defaultValue) {
                        nonDefaultValues[name] = newValue;
                    // If the user changed a non-default value back to the default
                    // value, its name is added to the reverted list
                    } else if (activeValue !== defaultValue) {
                        revertedValues.push(name);
                    }
                }
            });

            // Returns the object of skin values to be saved and reverted
            return {
                'nonDefaultValues': nonDefaultValues,
                'revertedValues': revertedValues
            };
        };

        /**
         * Persist the new skin. All values that override default skin values will be saved as the
         * skin overrides. All values that have been reverted back to the default value will be reverted.
         */
        var applySkinChanges = function() {
            // Retrieve the skin changes
            var skinChanges = getSkinChanges();

            // Save the skin overrides
            saveSkin(skinChanges.nonDefaultValues, function(saveSkinErr) {
                // Revert the reverted skin values
                revertSkin(skinChanges.revertedValues, function(revertSkinErr) {
                    // Show a success/error notification and update state
                    if (saveSkinErr || revertSkinErr) {
                        oae.api.util.notification(
                            oae.api.i18n.translate('__MSG__SKIN_NOT_SAVED__', 'skinning'),
                            oae.api.i18n.translate('__MSG__SKIN_COULD_NOT_BE_SAVED__', 'skinning'),
                            'error');
                    } else {
                        refreshActiveSkin();
                        oae.api.util.notification(
                            oae.api.i18n.translate('__MSG__SKIN_SAVED__', 'skinning'),
                            oae.api.i18n.translate('__MSG__SKIN_SUCCESSFULLY_SAVED__', 'skinning'));
                    }
                });
            });

            // Return false to avoid default form submit behavior
            return false;
        };

        /**
         * Revert a skin value back to its original value as defined in the
         * base LESS file. Therefore, this will not necessarily revert the
         * value back to its previous value.
         */
        var revertSkinValue = function() {
            var $input = $('input', $(this).parents('.form-group'));
            var defaultValue = defaultSkin[$input.attr('name')];
            // If the variable is a color, we use the set method provided by jQuery spectrum
            if ($input.attr('data-type') === 'color') {
                $input.spectrum('set', defaultValue);
            } else {
                $input.val(defaultValue);
            }
        };

        /**
         * Initialize the list of available skinning variables and their values
         */
        var loadSkin = function() {
            $.ajax({
                'url': '/api/ui/skin/variables',
                'data': {
                    'tenant': widgetData.context.alias
                },
                'success': function(data) {
                    // The stored skin values for the current tenant can be found in the configuration
                    // object. This will be stored as a stringified JSON object, so we need to parse this first
                    var configuredSkin = {};
                    if (widgetData.configuration['oae-ui'].skin.variables) {
                        configuredSkin = widgetData.configuration['oae-ui'].skin.variables;
                    }

                    // For all of the values in the available skin variables, we check if the current tenant
                    // has a stored value that overrides the default value. If the tenant doesn't have a value
                    // for a variable, the default value will be used
                    $.each(data.results, function(configSectionIndex, configSection) {
                        $.each(configSection.subsections, function(configSubsectionIndex, configSubsection) {
                            $.each(configSubsection.variables, function(variableIndex, variable) {
                                variable.value = configuredSkin[variable.name] || variable.defaultValue;
                                defaultSkin[variable.name] = variable.defaultValue;
                                activeSkin[variable.name] = variable.value;
                            });
                        });
                    });

                    // Render the editable skin values
                    oae.api.util.template().render($('#skinning-template', $rootel), data, $('#skinning-container', $rootel));

                    // Initialize the jQuery.spectrum color pickers
                    $('[data-type="color"]').spectrum({
                        'preferredFormat': 'rgb',
                        'showAlpha': true,
                        'showButtons': false,
                        'showInitial': true,
                        'showInput': true
                    });
                }
            });
        };

        var uploadLogo = function(e) {
            e.preventDefault();

            // Get the extension of the selected file and match it against supported types
            var extension = $('#skinning-logo-upload')[0].files[0].name.split('.').pop();
            var validType = extension.match(/(gif|jpe?g|png)$/i);

            // If no valid image type has been submitted, show a notification
            if (!validType) {
                oae.api.util.notification(
                    oae.api.i18n.translate('__MSG__INVALID_LOGO__', 'skinning'),
                    oae.api.i18n.translate('__MSG__SELECT_A_VALID_LOGO__', 'skinning'),
                    'error'
                );
            } else {
                var formData = new FormData(this);
                formData.append('tenant', widgetData.context.alias);
                $.ajax({
                    'url': '/api/ui/skin/logo',
                    'data': formData,
                    'type': 'POST',
                    'processData': false,
                    'contentType': false,
                    'success': function(data) {
                        saveSkin({'institutional-logo-url': '\''+ data.url +'\''}, function(err) {
                            if (err) {
                                oae.api.util.notification(
                                    oae.api.i18n.translate('__MSG__SKIN_NOT_SAVED__', 'skinning'),
                                    oae.api.i18n.translate('__MSG__SKIN_COULD_NOT_BE_SAVED__', 'skinning'),
                                    'error');
                            } else {
                                oae.api.util.notification(
                                    oae.api.i18n.translate('__MSG__SKIN_SAVED__', 'skinning'),
                                    oae.api.i18n.translate('__MSG__SKIN_SUCCESSFULLY_SAVED__', 'skinning'));
                                loadSkin();
                            }
                        });
                    }
                });
            }
        };

        /**
         * Add event binding for the skinning related functionality
         */
        var addBinding = function() {
            // Revert skin value
            $rootel.on('click', '.skinning-revert', revertSkinValue);
            // Change skin
            $rootel.on('submit', '#skinning-form', applySkinChanges);
            // Upload logo
            $rootel.on('submit', '#logo-upload-form', uploadLogo);
        };

        addBinding();
        loadSkin();
    };
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

// Release automation script inspired by
// https://github.com/geddski/grunt-release

var fs = require('fs');
var path = require('path');

var _ = require('lodash');
var S = require('string');
var shell = require('shelljs');
var Mustache = require('mustache');
var semver = require('semver');
var program = require('commander');
var Promise = require('bluebird');

// Message templates use https://github.com/janl/mustache.js
var config = {
    indentation: 2,
    releaseMessage: 'Release {{ version }}',
    backToDevMessage: 'Bump to dev version',
    bumpType: 'patch',
    files: ['package.json', 'bower.json'],
    readmeFile: 'README.md',

    // Banner to insert in beginning of distributables
    bannerFiles: ['dist/progressbar.js', 'dist/progressbar.min.js'],
    banner: '// ProgressBar.js {{ version }}\n// https://kimmobrunfeldt.github.io/progressbar.js\n// License: MIT\n\n',

    // If true, don't execute anything, just tell what would have been done
    dryRun: false,

    // If true, don't push commits/tags or release to npm
    noPush: false,
    consolePrefix: '->',
    devSuffix: '-dev'
}

var projectRoot = path.join(__dirname, '..');
process.chdir(projectRoot);

function main() {
    parseArgs();
    config = mergeArgsToDefaults(config);

    if (config.dryRun) status('Dry run\n');

    var newVersion = bumpVersion(config.files, config.bumpType);

    var banner = Mustache.render(config.banner, {
        version: newVersion
    });
    insertBanner(config.bannerFiles, banner);

    _gitBranchName()
    .then(function(stdout) {
        if (stdout.trim().toLowerCase() !== 'master') {
            throw new Error('You should be in master branch before running the script!');
        }

        return gitAdd(config.bannerFiles);
    })
    .then(function() {
        return gitAdd([config.readmeFile]);
    })
    .then(function() {
        return gitAdd(config.files);
    })
    .then(function() {
        var message = Mustache.render(config.releaseMessage, {
            version: newVersion
        });

        return gitCommit(message);
    })
    .then(function() {
        return gitTag(newVersion);
    })
    .then(function() {
        return gitPushTag(newVersion);
    })
    .then(npmPublish)
    .then(function() {
        bumpVersion(config.files, 'dev');
        return gitAdd(config.files.concat(config.readmeFile));
    })
    .then(function() {
        return gitCommit(config.backToDevMessage);
    })
    .then(function() {
        gitPush();
    })
    .then(function() {
        console.log('');
        status('Release successfully done!');
    })
    .catch(function(err) {
        console.error('\n!! Releasing failed')
        console.trace(err);
        process.exit(2);
    });
}

function parseArgs() {
    program
        .usage('bump')

    program.on('--help', function() {
        console.log('  Example usage:');
        console.log('');
        console.log('  $ ./release.js minor');
    });

    program.parse(process.argv);
}

function mergeArgsToDefaults(config) {
    if (program.args[0]) {
        config.bumpType = program.args[0];

        if (!_.contains(['major', 'minor', 'patch'], config.bumpType)) {
            console.error('Error:', config.bumpType, 'is not a valid bump type');
            process.exit(1);
        }
    }

    return config;
}

function status( /* arguments */ ) {
    var args = Array.prototype.slice.call(arguments);
    console.log(config.consolePrefix, args.join(' '));
}

function run(cmd, msg) {
    // All calls are actually synchronous but eventually some task
    // will need async stuff, so keep them promises
    return new Promise(function(resolve, reject) {
        if (msg) {
            status(msg);
        }

        if (config.dryRun) {
            return resolve();
        }

        var exec = shell.exec(cmd);
        var success = exec.code === 0;

        if (success) {
            resolve(exec.output);
        } else {
            var errMsg = 'Error executing: `' + cmd + '`\nOutput:\n' + exec.output;
            var err = new Error(errMsg);
            reject(err);
        }
    });
}

// Task functions
// All functions should return promise

// Bumps version in specified files.
// Files are assumed to contain JSON data which has "version" key following
// semantic versioning
function bumpVersion(files, bumpType) {
    status('Bump', bumpType, 'version to files:', files.join(' '));
    if (config.dryRun) return '[not available in dry run]';

    var newVersion;
    var originalVersion;
    files.forEach(function(fileName) {
        var filePath = path.join(projectRoot, fileName);

        var data = JSON.parse(fs.readFileSync(filePath));
        originalVersion = data.version;
        var currentVersion = data.version;
        if (!semver.valid(currentVersion)) {
            var msg = 'Invalid version ' + currentVersion +
                ' in file ' + fileName;;
            var err = new Error(msg);
            throw err;
        }

        if (S(currentVersion).endsWith(config.devSuffix)) {
            currentVersion = S(currentVersion).chompRight(config.devSuffix).s;
        }

        if (bumpType === 'dev') {
            newVersion = currentVersion + config.devSuffix;
        } else {
            newVersion = semver.inc(currentVersion, bumpType);
        }
        data.version = newVersion;

        var content = JSON.stringify(data, null, config.indentation);
        fs.writeFileSync(filePath, content);

        status('Bump', originalVersion, '->', newVersion, 'in',
            fileName);
    });

    bumpReadmeVersion(originalVersion, newVersion, bumpType);

    return newVersion;
}

function insertBanner(files, banner) {
    status('Insert banner to', files.length, 'files');
    if (config.dryRun) return;

    _.each(files, function(fileName) {
        var filePath = path.join(projectRoot, fileName);
        var content = fs.readFileSync(filePath);
        var newContent = banner + content;

        fs.writeFileSync(filePath, newContent);
    });
}

function bumpReadmeVersion(oldVersion, newVersion, bumpType) {
    if (bumpType === 'dev') {
        // Don't bump readme version in to dev version
        return;
    }

    var oldReleaseVersion = oldVersion;
    if (S(oldReleaseVersion).endsWith(config.devSuffix)) {
        oldReleaseVersion = S(oldReleaseVersion).chompRight(config.devSuffix).s;
    }

    status('Replace readme version', oldReleaseVersion, '->', newVersion);
    if (config.dryRun) return;

    var filePath = path.join(projectRoot, config.readmeFile);
    var content = fs.readFileSync(filePath, {encoding: 'utf-8'});

    // Update visible version
    var re = new RegExp('Version: ' + oldReleaseVersion, 'g');
    var newContent = content.replace(re, 'Version: ' + newVersion);

    // Replace link to previous stable
    re = new RegExp('tree/[0-9]\\.[0-9]\\.[0-9]');
    newContent = newContent.replace(re, 'tree/' + oldReleaseVersion);

    fs.writeFileSync(filePath, newContent);
}

function gitAdd(files) {
    var cmd = 'git add ' + files.join(' ');
    var msg = 'Staged ' + files.length + ' files';
    return run(cmd, msg);
}

function gitCommit(message) {
    var cmd = 'git commit -m "' + message + '"';
    var msg = 'Commit files'
    return run(cmd, msg);
}

function gitTag(name) {
    var cmd = 'git tag ' + name;
    var msg = 'Created a new git tag: ' + name;
    return run(cmd, msg);
}

function gitPush() {
    if (config.noPush) return;

    var cmd = 'git push';
    var msg = 'Push to remote';
    return run(cmd, msg);
}

function gitPushTag(tagName) {
    if (config.noPush) return;

    var cmd = 'git push origin ' + tagName;
    var msg = 'Push created git tag to remote'
    return run(cmd, msg);
}

function gitCheckout(branch) {
    var cmd = 'git checkout ' + branch;
    var msg = 'Checkout branch ' + branch;
    return run(cmd, msg);
}

function npmPublish() {
    if (config.noPush) return;

    var cmd = 'npm publish';
    var msg = 'Publish to npm';
    return run(cmd, msg);
}

function _gitBranchName() {
    var cmd = 'git rev-parse --abbrev-ref HEAD'
    return run(cmd, false);
}

main();
<commit_msg>Release<commit_after>#!/usr/bin/env node

// Release automation script inspired by
// https://github.com/geddski/grunt-release

var fs = require('fs');
var path = require('path');

var _ = require('lodash');
var S = require('string');
var shell = require('shelljs');
var Mustache = require('mustache');
var semver = require('semver');
var program = require('commander');
var Promise = require('bluebird');

// Message templates use https://github.com/janl/mustache.js
var config = {
    indentation: 2,
    releaseMessage: 'Release {{ version }}',
    backToDevMessage: 'Bump to dev version',
    bumpType: 'patch',
    files: ['package.json', 'bower.json'],
    readmeFile: 'README.md',

    // Banner to insert in beginning of distributables
    bannerFiles: ['dist/progressbar.js', 'dist/progressbar.min.js'],
    banner: '// ProgressBar.js {{ version }}\n// https://kimmobrunfeldt.github.io/progressbar.js\n// License: MIT\n\n',

    // If true, don't execute anything, just tell what would have been done
    dryRun: false,

    // If true, don't push commits/tags or release to npm
    noPush: false,
    consolePrefix: '->',
    devSuffix: '-dev'
}

var projectRoot = path.join(__dirname, '..');
process.chdir(projectRoot);

function main() {
    parseArgs();
    config = mergeArgsToDefaults(config);

    if (config.dryRun) status('Dry run\n');

    var newVersion = bumpVersion(config.files, config.bumpType);

    var banner = Mustache.render(config.banner, {
        version: newVersion
    });
    insertBanner(config.bannerFiles, banner);

    _gitBranchName()
    .then(function(stdout) {
        if (stdout.trim().toLowerCase() !== 'master') {
            throw new Error('You should be in master branch before running the script!');
        }

        return gitAdd(config.bannerFiles);
    })
    .then(function() {
        return gitAdd([config.readmeFile]);
    })
    .then(function() {
        return gitAdd(config.files);
    })
    .then(function() {
        var message = Mustache.render(config.releaseMessage, {
            version: newVersion
        });

        return gitCommit(message);
    })
    .then(function() {
        return gitTag(newVersion);
    })
    .then(function() {
        return gitPushTag(newVersion);
    })
    .then(npmPublish)
    .then(function() {
        bumpVersion(config.files, 'dev');
        return gitAdd(config.files.concat(config.readmeFile));
    })
    .then(function() {
        return gitCommit(config.backToDevMessage);
    })
    .then(function() {
        gitPush();
    })
    .then(function() {
        console.log('');
        status('Release successfully done!');
    })
    .catch(function(err) {
        console.error('\n!! Releasing failed')
        console.trace(err);
        process.exit(2);
    });
}

function parseArgs() {
    program
        .usage('bump')

    program.on('--help', function() {
        console.log('  Example usage:');
        console.log('');
        console.log('  $ ./release.js minor');
    });

    program.parse(process.argv);
}

function mergeArgsToDefaults(config) {
    if (program.args[0]) {
        config.bumpType = program.args[0];

        if (!_.contains(['major', 'minor', 'patch'], config.bumpType)) {
            console.error('Error:', config.bumpType, 'is not a valid bump type');
            process.exit(1);
        }
    }

    return config;
}

function status( /* arguments */ ) {
    var args = Array.prototype.slice.call(arguments);
    console.log(config.consolePrefix, args.join(' '));
}

function run(cmd, msg) {
    // All calls are actually synchronous but eventually some task
    // will need async stuff, so keep them promises
    return new Promise(function(resolve, reject) {
        if (msg) {
            status(msg);
        }

        if (config.dryRun) {
            return resolve();
        }

        var exec = shell.exec(cmd);
        var success = exec.code === 0;

        if (success) {
            resolve(exec.output);
        } else {
            var errMsg = 'Error executing: `' + cmd + '`\nOutput:\n' + exec.output;
            var err = new Error(errMsg);
            reject(err);
        }
    });
}

// Task functions
// All functions should return promise

// Bumps version in specified files.
// Files are assumed to contain JSON data which has "version" key following
// semantic versioning
function bumpVersion(files, bumpType) {
    status('Bump', bumpType, 'version to files:', files.join(' '));
    if (config.dryRun) return '[not available in dry run]';

    var newVersion;
    var originalVersion;
    files.forEach(function(fileName) {
        var filePath = path.join(projectRoot, fileName);

        var data = JSON.parse(fs.readFileSync(filePath));
        originalVersion = data.version;
        var currentVersion = data.version;
        if (!semver.valid(currentVersion)) {
            var msg = 'Invalid version ' + currentVersion +
                ' in file ' + fileName;;
            var err = new Error(msg);
            throw err;
        }

        if (S(currentVersion).endsWith(config.devSuffix)) {
            currentVersion = S(currentVersion).chompRight(config.devSuffix).s;
        }

        if (bumpType === 'dev') {
            newVersion = currentVersion + config.devSuffix;
        } else {
            newVersion = semver.inc(currentVersion, bumpType);
        }
        data.version = newVersion;

        var content = JSON.stringify(data, null, config.indentation);
        fs.writeFileSync(filePath, content);

        status('Bump', originalVersion, '->', newVersion, 'in',
            fileName);
    });

    bumpReadmeVersion(originalVersion, newVersion, bumpType);

    return newVersion;
}

function insertBanner(files, banner) {
    status('Insert banner to', files.length, 'files');
    if (config.dryRun) return;

    _.each(files, function(fileName) {
        var filePath = path.join(projectRoot, fileName);
        var content = fs.readFileSync(filePath);
        var newContent = banner + content;

        fs.writeFileSync(filePath, newContent);
    });
}

function bumpReadmeVersion(oldVersion, newVersion, bumpType) {
    if (bumpType === 'dev') {
        // Don't bump readme version in to dev version
        return;
    }

    var oldReleaseVersion = oldVersion;
    if (S(oldReleaseVersion).endsWith(config.devSuffix)) {
        oldReleaseVersion = S(oldReleaseVersion).chompRight(config.devSuffix).s;
    }

    status('Replace readme version', oldReleaseVersion, '->', newVersion);
    if (config.dryRun) return;

    var filePath = path.join(projectRoot, config.readmeFile);
    var content = fs.readFileSync(filePath, {encoding: 'utf-8'});

    // Update visible version
    var re = new RegExp('Version: ' + oldReleaseVersion, 'g');
    var newContent = content.replace(re, 'Version: ' + newVersion);

    // Replace link to previous stable
    re = new RegExp('tree/[0-9]\\.[0-9]\\.[0-9]');
    newContent = newContent.replace(re, 'tree/' + oldReleaseVersion);

    fs.writeFileSync(filePath, newContent);
}

function gitAdd(files) {
    var cmd = 'git add ' + files.join(' ');
    var msg = 'Staged ' + files.length + ' files';
    return run(cmd, msg);
}

function gitCommit(message) {
    var cmd = 'git commit -m "' + message + '"';
    var msg = 'Commit files'
    return run(cmd, msg);
}

function gitTag(name) {
    var cmd = 'git tag ' + name;
    var msg = 'Created a new git tag: ' + name;
    return run(cmd, msg);
}

function gitPush() {
    if (config.noPush) return;

    var cmd = 'git push';
    var msg = 'Push to remote';
    return run(cmd, msg);
}

function gitPushTag(tagName) {
    if (config.noPush) return;

    var cmd = 'git push origin ' + tagName;
    var msg = 'Push created git tag to remote'
    return run(cmd, msg);
}

function gitCheckout(branch) {
    var cmd = 'git checkout ' + branch;
    var msg = 'Checkout branch ' + branch;
    return run(cmd, msg);
}

function npmPublish() {
    if (config.noPush) return;

    var cmd = 'npm publish';
    var msg = 'Publish to npm';
    return run(cmd, msg);
}

function _gitBranchName() {
    var cmd = 'git rev-parse --abbrev-ref HEAD'
    return run(cmd, false);
}

main();
<|endoftext|>


<commit_before>const Plugin = require('./plugin')
const Ease = require('pixi-ease')
const exists = require('exists')

module.exports = class Snap extends Plugin
{
    /**
     * @param {Viewport} parent
     * @param {number} x
     * @param {number} y
     * @param {object} [options]
     * @param {boolean} [options.center] snap to the center of the camera instead of the top-left corner of viewport
     * @param {number} [options.friction=0.8] friction/frame to apply if decelerate is active
     * @param {number} [options.time=1000]
     * @param {string|function} [options.ease=easeInOutSine] ease function or name (see http://easings.net/ for supported names)
     * @param {boolean} [options.interrupt=true] pause snapping with any user input on the viewport
     * @param {boolean} [options.removeOnComplete=true] removes this plugin after snapping is complete
     *
     * @event snap-start(Viewport) emitted each time a snap animation starts
     * @event snap-end(Viewport) emitted each time snap reaches its target
     */
    constructor(parent, x, y, options)
    {
        super(parent)
        options = options || {}
        this.friction = options.friction || 0.8
        this.time = options.time || 1000
        this.ease = options.ease || 'easeInOutSine'
        this.targetX = x
        this.targetY = y
        this.center = options.center
        if (!this.center)
        {
            // Always target the center, and calculate the new top-left corner to target upon resizing
            this.originalTargetX = this.targetX
            this.originalTargetY = this.targetY

            this.resize()
        }
        this.originalCenter = this.parent.center
        this.interrupt = exists(options.interrupt) ? options.interrupt : true
        this.removeOnComplete = exists(options.removeOnComplete) ? options.removeOnComplete: true
    }

    reset()
    {
        this.snapping = null
    }

    resize()
    {
        if (!this.center)
        {
            this.targetX = this.parent.container.scale.x * (this.parent.worldScreenWidth / 2 - this.originalTargetX)
            this.targetY = this.parent.container.scale.y * (this.parent.worldScreenHeight / 2 - this.originalTargetY)
            this.snapping = null
            this.originalCenter = this.parent.center
        }
    }

    down()
    {
        this.snapping = null
    }

    up()
    {
        if (this.parent.input.pointers.length === 0)
        {
            const decelerate = this.parent.plugins['decelerate']
            if (decelerate && (decelerate.x || decelerate.y))
            {
                decelerate.percentChangeX = decelerate.percentChangeY = this.friction
            }
        }
    }

    update(elapsed)
    {
        if (this.paused)
        {
            return
        }
        if (this.interrupt && this.parent.input.pointers.length !== 0)
        {
            return
        }
        let center = this.parent.center
        if (!this.snapping && (center.x !== this.targetX || center.y !== this.targetY))
        {
            this.snapping = new Ease.to(this.originalCenter, { x: this.targetX, y: this.targetY }, this.time, { ease: this.ease })
            this.parent.emit('snap-start', this.parent)
        }
        else if (this.snapping)
        {
            var finished = this.snapping.update(elapsed)

            this.parent.moveCenter(this.originalCenter)

            if (finished)
            {
                if (this.removeOnComplete)
                {
                    this.parent.removePlugin('snap')
                    console.log('snap-end')
                }
                this.parent.emit('snap-end', this.parent )
                this.snapping = null
            }
        }
    }

    resume()
    {
        this.snapping = null
        super.resume()
    }
}<commit_msg>Snap calculates equivalent center for non-center snaps<commit_after>const Plugin = require('./plugin')
const Ease = require('pixi-ease')
const exists = require('exists')

module.exports = class Snap extends Plugin
{
    /**
     * @param {Viewport} parent
     * @param {number} x
     * @param {number} y
     * @param {object} [options]
     * @param {boolean} [options.center] snap to the center of the camera instead of the top-left corner of viewport
     * @param {number} [options.friction=0.8] friction/frame to apply if decelerate is active
     * @param {number} [options.time=1000]
     * @param {string|function} [options.ease=easeInOutSine] ease function or name (see http://easings.net/ for supported names)
     * @param {boolean} [options.interrupt=true] pause snapping with any user input on the viewport
     * @param {boolean} [options.removeOnComplete=true] removes this plugin after snapping is complete
     *
     * @event snap-start(Viewport) emitted each time a snap animation starts
     * @event snap-end(Viewport) emitted each time snap reaches its target
     */
    constructor(parent, x, y, options)
    {
        super(parent)
        options = options || {}
        this.friction = options.friction || 0.8
        this.time = options.time || 1000
        this.ease = options.ease || 'easeInOutSine'
        this.targetX = x
        this.targetY = y
        this.center = options.center
        if (!this.center)
        {
            // Always target the center, and calculate the new top-left corner to target upon resizing
            this.originalTargetX = this.targetX
            this.originalTargetY = this.targetY

            this.resize()
        }
        this.originalCenter = this.parent.center
        this.interrupt = exists(options.interrupt) ? options.interrupt : true
        this.removeOnComplete = exists(options.removeOnComplete) ? options.removeOnComplete: true
    }

    reset()
    {
        this.snapping = null
    }

    resize()
    {
        if (!this.center)
        {
            /* Finds target center based on the given originalTarget point (the top left corner)
             * DOES NOT WORK WHEN the snap-zoom plugin is working simultaneously because this.parent.container.scale.x and y are
             * constantly changing, therefore worldScreenWidth and worldScreenHeight are constantly changing, and the pixi-ease
             * library does not support constantly changing values
             */
            this.originalCenter = this.parent.center
            this.targetX = this.parent.worldScreenWidth / 2 + this.originalTargetX
            this.targetY = this.parent.worldScreenHeight / 2 + this.originalTargetY
        }
    }

    down()
    {
        this.snapping = null
    }

    up()
    {
        if (this.parent.input.pointers.length === 0)
        {
            const decelerate = this.parent.plugins['decelerate']
            if (decelerate && (decelerate.x || decelerate.y))
            {
                decelerate.percentChangeX = decelerate.percentChangeY = this.friction
            }
        }
    }

    update(elapsed)
    {
        if (this.paused)
        {
            return
        }
        if (this.interrupt && this.parent.input.pointers.length !== 0)
        {
            return
        }
        let center = this.parent.center
        if (!this.snapping && (center.x !== this.targetX || center.y !== this.targetY))
        {
            this.snapping = new Ease.to(this.originalCenter, { x: this.targetX, y: this.targetY }, this.time, { ease: this.ease })
            this.parent.emit('snap-start', this.parent)
        }
        else if (this.snapping)
        {
            var finished = this.snapping.update(elapsed)

            this.parent.moveCenter(this.originalCenter)

            if (finished)
            {
                if (this.removeOnComplete)
                {
                    this.parent.removePlugin('snap')
                }
                this.parent.emit('snap-end', this.parent )
                this.snapping = null
            }
        }
    }

    resume()
    {
        this.snapping = null
        super.resume()
    }
}<|endoftext|>


<commit_before>// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Merge Sort:

var merge_sort_inplace = function(ar, s, e) {
    var left, right, left_tail;
    if (s===e) { return; }
    left_tail = s+Math.floor((e-s)/2);
    merge_sort_inplace(ar, s, left_tail);
    merge_sort_inplace(ar, left_tail+1, e);
    merge(ar, s, left_tail, e);
};

var merge = function(ar, l_h, l_t, r_t) {
    var i = l_h,
        l_i=0, r_i=0, val,
        left = ar.slice(l_h, l_t+1),
        l_len = left.length,
        right = ar.slice(l_t+1, r_t+1),
        r_len = right.length;

    for (;i<=r_t;i+=1) {
        if (l_i==l_len || right[r_i]<left[l_i]) {
            val = right[r_i]; r_i+=1;
        } else {
            val = left[l_i];  l_i+=1;
        }
        ar[i] = val;
    }
};

var ar = [8,2,6,3,4,3,9,1];
merge_sort_inplace(ar, 0, ar.length-1);
console.log(ar);

var merge_sort_immutable = function(ar) {
    var left, right, len = ar.length;
    if (len===1) { return ar; }
    left = merge_sort(ar.slice(0,Math.ceil(len/2)));
    right = merge_sort(ar.slice(Math.ceil(len/2)));
    return _merge_immutable(left,right);
};

var _merge_immutable = function(l, r) {
    var l_val, r_val, val, result = [];
    if (l==null) { l = []; }
    if (r==null) { r = []; }
    while (l.length>0||r.length>0) {
        l_val = l[0] || Number.MAX_VALUE;
        r_val = r[0] || Number.MAX_VALUE;
        if (l_val<r_val) {
            result.push(l.shift());
        } else {
            result.push(r.shift());
        }
    }
    return result;
};

ar = [8,2,6,3,4,3,9,1];
console.log(merge_sort(ar));

// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Quicksort:

var quick_sort_immutable = function(ar) {
    var i=0, left=[], right=[], pivot;
    if (ar.length<2) { return ar; }
    pivot = ar.shift();
    for (;i<ar.length; i+=1) {
        if (ar[i]<pivot) {
            left.push(ar[i]);
        } else {
            right.push(ar[i]);
        }
    }
    return quick_sort_immutable(left).concat([pivot]).concat(quick_sort_immutable(right));
};

// 0 2 2 2 [ 1, 2, 3, 3, 4, 6, 9, 8 ]
// 1 3 2 1 [ 1, 2, 3, 3, 4, 6, 9, 8 ]
var quick_sort_inplace_headpivot = function(ar, s, e) {
    var temp, i=s+1,j=e-1, pivot = ar[s];
    if (e-s<2) { return; }
    while (i<j) {
        while (ar[i]<=pivot) { i+=1; }
        while (ar[j]>pivot) { j-=1; }
        if (i<j) {
            temp  = ar[j];
            ar[j] = ar[i];
            ar[i] = temp;
            i+=1;
            j-=1;
        }
    }
    ar[s] = ar[j];
    ar[j] = pivot;
    quick_sort_inplace_headpivot(ar, s, j);
    quick_sort_inplace_headpivot(ar, j+1, e);
};

var quick_sort_inplace_tailpivot = function(ar, s, e) {
    var pivot = ar[e-1], temp, i=s, j=i;
    if (e-s<2) { return; }
    for (; i<e-1; i+=1) {
        if (ar[i]<=pivot) {
            if (i!=j) {
              	temp = ar[i];
              	ar[i] = ar[j];
              	ar[j] = temp;
            }
            j+=1;
        }
    }
    ar[e-1] = ar[j];
    ar[j] = pivot;
    quick_sort_inplace_tailpivot(ar, s, j);
    quick_sort_inplace_tailpivot(ar, j+1, e);
};

ar = [8,2,6,3,4,3,9,1];
console.log(quick_sort_immutable(ar));
ar = [8,2,6,3,4,3,9,1];
quick_sort_tailpivot(ar, 0, ar.length);
console.log(ar);

// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Order Statistics:

var kth = function(ar, s, e, k) {
    var pivot = ar[e], i=s, j=i, temp;
    // base case
    if (e==s && k==s) { return ar[k]; }
    if (e==s+1 && (s==k||e==k)) { return ar[k]; }

    for (i; i<e; i+=1) {
        if (ar[i] <= pivot) {
            if (i!==j) {
              	temp = ar[i];
              	ar[i] = ar[j];
              	ar[j] = temp;
            }
            j+=1;
        }
    }
    ar[e] = ar[j];
    ar[j] = pivot;
    if (j==k) { return ar[k]; }
    if (j>k) {
        return kth(ar, s, j-1, k);
    } else {
        return kth(ar, j+1, e, k);
    }
};

ar = [8,2,6,3,4,3,9,1];
console.log(kth(ar, 0, ar.length-1, 5));

// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Question: Nearest Neighbor

var neighbor = function(ar, k) {
    var i=0, len=ar.length, neighbors=[], nbr, l_i, u_i, j;
    ar.sort(function(a,b) {return +a-(+b);});
    console.log(ar);
    for (;i<len; i+=1) {
        nbr = [];
        l_i = i-1;
        u_i = i+1;
        while(nbr.length<k) {
            if (ar[l_i] && Math.abs(ar[i]-ar[l_i])<=Math.abs(ar[i]-ar[u_i])||!ar[u_i]) { nbr.push(ar[l_i]); l_i-=1; }
            if (nbr.length>=k) break;
            if (ar[u_i] && Math.abs(ar[i]-ar[u_i])<=Math.abs(ar[i]-ar[l_i])||!ar[l_i]) { nbr.push(ar[u_i]); u_i+=1; }
        }
        neighbors.push(nbr);
    }
    return neighbors;
};

ar = [8,2,6,3,4,3,9,1];
console.log(neighbor(ar, 2));<commit_msg>Implemented counting sort<commit_after>// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Merge Sort:

var merge_sort_inplace = function(ar, s, e) {
    var left, right, left_tail;
    if (s===e) { return; }
    left_tail = s+Math.floor((e-s)/2);
    merge_sort_inplace(ar, s, left_tail);
    merge_sort_inplace(ar, left_tail+1, e);
    merge(ar, s, left_tail, e);
};

var merge = function(ar, l_h, l_t, r_t) {
    var i = l_h,
        l_i=0, r_i=0, val,
        left = ar.slice(l_h, l_t+1),
        l_len = left.length,
        right = ar.slice(l_t+1, r_t+1),
        r_len = right.length;

    for (;i<=r_t;i+=1) {
        if (l_i==l_len || right[r_i]<left[l_i]) {
            val = right[r_i]; r_i+=1;
        } else {
            val = left[l_i];  l_i+=1;
        }
        ar[i] = val;
    }
};

var ar = [8,2,6,3,4,3,9,1];
merge_sort_inplace(ar, 0, ar.length-1);
console.log(ar);

var merge_sort_immutable = function(ar) {
    var left, right, len = ar.length;
    if (len===1) { return ar; }
    left = merge_sort_immutable(ar.slice(0,Math.ceil(len/2)));
    right = merge_sort_immutable(ar.slice(Math.ceil(len/2)));
    return _merge_immutable(left,right);
};

var _merge_immutable = function(l, r) {
    var l_val, r_val, val, result = [];
    if (l==null) { l = []; }
    if (r==null) { r = []; }
    while (l.length>0||r.length>0) {
        l_val = l[0] || Number.MAX_VALUE;
        r_val = r[0] || Number.MAX_VALUE;
        if (l_val<r_val) {
            result.push(l.shift());
        } else {
            result.push(r.shift());
        }
    }
    return result;
};

ar = [8,2,6,3,4,3,9,1];
console.log(merge_sort_immutable(ar));

// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Quicksort:

var quick_sort_immutable = function(ar) {
    var i=0, left=[], right=[], pivot;
    if (ar.length<2) { return ar; }
    pivot = ar.shift();
    for (;i<ar.length; i+=1) {
        if (ar[i]<pivot) {
            left.push(ar[i]);
        } else {
            right.push(ar[i]);
        }
    }
    return quick_sort_immutable(left).concat([pivot]).concat(quick_sort_immutable(right));
};

// 0 2 2 2 [ 1, 2, 3, 3, 4, 6, 9, 8 ]
// 1 3 2 1 [ 1, 2, 3, 3, 4, 6, 9, 8 ]
var quick_sort_inplace_headpivot = function(ar, s, e) {
    var temp, i=s+1,j=e-1, pivot = ar[s];
    if (e-s<2) { return; }
    while (i<j) {
        while (ar[i]<=pivot) { i+=1; }
        while (ar[j]>pivot) { j-=1; }
        if (i<j) {
            temp  = ar[j];
            ar[j] = ar[i];
            ar[i] = temp;
            i+=1;
            j-=1;
        }
    }
    ar[s] = ar[j];
    ar[j] = pivot;
    quick_sort_inplace_headpivot(ar, s, j);
    quick_sort_inplace_headpivot(ar, j+1, e);
};

var quick_sort_inplace_tailpivot = function(ar, s, e) {
    var pivot = ar[e-1], temp, i=s, j=i;
    if (e-s<2) { return; }
    for (; i<e-1; i+=1) {
        if (ar[i]<=pivot) {
            if (i!=j) {
              	temp = ar[i];
              	ar[i] = ar[j];
              	ar[j] = temp;
            }
            j+=1;
        }
    }
    ar[e-1] = ar[j];
    ar[j] = pivot;
    quick_sort_inplace_tailpivot(ar, s, j);
    quick_sort_inplace_tailpivot(ar, j+1, e);
};

ar = [8,2,6,3,4,3,9,1];
console.log(quick_sort_immutable(ar));
ar = [8,2,6,3,4,3,9,1];
quick_sort_inplace_tailpivot(ar, 0, ar.length);
console.log(ar);

// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Order Statistics:

var kth = function(ar, s, e, k) {
    var pivot = ar[e], i=s, j=i, temp;
    // base case
    if (e==s && k==s) { return ar[k]; }
    if (e==s+1 && (s==k||e==k)) { return ar[k]; }

    for (i; i<e; i+=1) {
        if (ar[i] <= pivot) {
            if (i!==j) {
              	temp = ar[i];
              	ar[i] = ar[j];
              	ar[j] = temp;
            }
            j+=1;
        }
    }
    ar[e] = ar[j];
    ar[j] = pivot;
    if (j==k) { return ar[k]; }
    if (j>k) {
        return kth(ar, s, j-1, k);
    } else {
        return kth(ar, j+1, e, k);
    }
};

ar = [8,2,6,3,4,3,9,1];
console.log(kth(ar, 0, ar.length-1, 5));

// http://courses.csail.mit.edu/iap/interview/Hacking_a_Google_Interview_Handout_2.pdf
// Question: Nearest Neighbor

var neighbor = function(ar, k) {
    var i=0, len=ar.length, neighbors=[], nbr, l_i, u_i, j;
    ar.sort(function(a,b) {return +a-(+b);});
    console.log(ar);
    for (;i<len; i+=1) {
        nbr = [];
        l_i = i-1;
        u_i = i+1;
        while(nbr.length<k) {
            if (ar[l_i] && Math.abs(ar[i]-ar[l_i])<=Math.abs(ar[i]-ar[u_i])||!ar[u_i]) { nbr.push(ar[l_i]); l_i-=1; }
            if (nbr.length>=k) break;
            if (ar[u_i] && Math.abs(ar[i]-ar[u_i])<=Math.abs(ar[i]-ar[l_i])||!ar[l_i]) { nbr.push(ar[u_i]); u_i+=1; }
        }
        neighbors.push(nbr);
    }
    return neighbors;
};

ar = [8,2,6,3,4,3,9,1];
console.log(neighbor(ar, 2));

// Counting sort: Applicable if value's distribution is limited up to k (bucket size)

var counting_sort = function(ar) {
    var i=0, j=0,
        k=-Infinity,
        bucket = ar.reduce(function(bucket, val) {
            bucket[val] = bucket[val] || 0;
            bucket[val] += 1;
            if (val>k) { k=val; }
            return bucket;
        }, []);

    for (; i<=k; i+=1) {
        if (bucket[i] != null) {
            while (bucket[i]>0) {
                ar[j] = i;
                bucket[i] -= 1;
                j += 1;
            }
        }
    }
    return ar;
};

console.log(counting_sort([3,0,2,0,0,2,2]));

<|endoftext|>


<commit_before>
// SmoothScroll v0.9.5
// Licensed under the terms of the MIT license.

// People involved
// - Balazs Galambosi: maintainer (CHANGELOG.txt)
// - Patrick Brunner (patrickb1991@gmail.com)
// - Michael Herf: Pulse Algorithm

// Frame Variables
var frame         = false;
var noscrollframe = false;

// Scroll Variables (tweakable)
var framerate = 150; // [Hz]
var animtime  = 400; // [px]
var stepsize  = 120; // [px]

// Pulse (less tweakable)
// ratio of "tail" to "acceleration"
var pulseAlgorithm = true;
var pulseScale     = 8;
var pulseNormalize = 1;

// Keyboard Settings
var keyboardsupport = true;
var disableKeyboard = false;
var arrowscroll     = 50; // [px]

// Excluded pages
var exclude = "";

// Other Variables
var scrolls;
var deltaX = 0;
var deltaY = 0;
var direction = "down";
var initdone  = false;
var activeElement;

var key = { up: 38, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 };

/***********************************************
 * SETTINGS
 ***********************************************/

chrome.extension.connect({ name: "smoothscroll" }).
onMessage.addListener(function (settings) {

    // NOTE: + converts to {Number}
    framerate = Math.min(+settings.framerate, 250);
    animtime  = +settings.animtime;
    stepsize  = +settings.scrollsz;
    exclude   = settings.exclude;
    pulseAlgorithm  = (settings.pulseAlgorithm == "true");
    pulseScale      = +settings.pulseScale;
    keyboardsupport = (settings.keyboardsupport == "true");
    arrowscroll     = +settings.arrscroll;

    computePulseScale();

    // it seems that sometimes settings come late
    // and we need to test again for excluded pages
    initTest();

    if (keyboardsupport && !disableKeyboard) {
        addEvent("keydown", keydown);
    }

    // If extension settings were deleted somehow
    if (!framerate) {
        alert("SmoothScroll: Please restart Chrome");
    }
});

/***********************************************
 * INITIALIZE
 ***********************************************/

/**
 * Tests if smooth scrolling is allowed. Shuts down everything if not.
 */
function initTest() {

    // disable keys for google reader (spacebar conflict)
    if (document.URL.indexOf("google.com/reader/view") > -1) {
        disableKeyboard = true;
    }

    // disable everything if the page is blacklisted
    if (exclude) {
        var domains = exclude.split(/[,\n] ?/);
        for (var i = domains.length; i--;) {
            if (document.URL.indexOf(domains[i]) > -1) {
                removeEvent("mousewheel", wheel);
                disableKeyboard = true;
                break;
            }
        }
    }

    // disable keyboard support if anything above requested it
    if (disableKeyboard) {
        removeEvent("keydown", keydown);
    }
}

/**
 * Sets up scrolls array, determines if frames are involved.
 */
function init() {

    var body  = document.body;
    var docel = document.documentElement;
    var htmlHeight   = docel.offsetHeight;
    var windowHeight = window.innerHeight;

    activeElement = body;
    initTest();

    if (document.domain === "acid3.acidtests.org") return;

    // Checks if this script is running in a frame
    if (top && top != self) {
        frame = true;
        if (docel.scrollHeight <= docel.clientHeight + 10) {
            noscrollframe = true;
        }
    }

    /**
     * This fixes a bug where the areas left and right to
     * the content does not trigger the onmousewheel event
     * on some pages. e.g.: html, body { height: 100% }
     */
    else if (body.scrollHeight > windowHeight &&
            (body.offsetHeight <= windowHeight || htmlHeight <= windowHeight)) {

        var underlay = document.createElement("div");
        underlay.setAttribute( "style",
            "z-index: -1; position: absolute; top: 0; left: 0; " +
            "width: 100%; height: " + body.scrollHeight + "px;" );
        body.appendChild(underlay);

        var scrollHeight = body.scrollHeight;
        var pending = false;

        // DOMChange delayed
        var refresh = function() {
            if (!pending && scrollHeight != body.scrollHeight) {
                pending = true; // add a new pending action
                setTimeout(function(){
                    underlay.style.height = body.scrollHeight + "px";
                    scrollHeight = body.scrollHeight;
                    pending = false;
                }, 1000); // act rarely to stay fast
            }
        }

        addEvent("DOMNodeInserted", refresh);
        addEvent("DOMNodeRemoved",  refresh);
    }
    initdone = true;
}

/************************************************
 * SCROLLING
 ************************************************/

var que = [];
var pending = false;

/**
 * Pushes scroll actions to a given direction Array.
 */
function scrollArray(elem, dir, multiplyX, multiplyY, delay) {

    delay || (delay = 1000);
    directionCheck(dir);

    // contains the scroll commands [ x, y, start, last ]
    que.push([multiplyX, multiplyY, +new Date, 0]);

    // don't act if there's a pending queue
    if (pending) {
        return;
    }

    var step = function() {

        var now = +new Date;
        var addX = 0;
        var addY = 0;
        var finished;
        var x = 0, y = 1, start = 2, last = 3; // {enum}

        for (var i = 0; i < que.length; i++) {

            var item = que[i];
            var elapsed = now - item[start];

            finished = (elapsed >= animtime);

            // scroll is [0, 1]
            var scroll = (finished) ? 1 : elapsed / animtime;
            // easing
            if (pulseAlgorithm) {
                scroll = pulse(scroll);
            }

            // scale and quantize to int so our pixel difference works:
            var iscroll = Math.floor(stepsize * scroll + 0.99);
            scroll = iscroll - item[last];
            item[last] = iscroll;

            // add this to the actual scrolling
            addX += item[x] * scroll;
            addY += item[y] * scroll;

            // delete and step back
            if (finished) {
                que.splice(i, 1); i--;
            }
        }

        addX >>= 0; // toInt
        addY >>= 0; // toInt

        // scroll left
        if (multiplyX && addX) {
            var lastLeft = elem.scrollLeft;
            elem.scrollLeft += addX;

            // scroll left failed (edge)
            if (elem.scrollLeft === lastLeft) {
                multiplyX = 0;
            }
        }

        // scroll top
        if (multiplyY && addY) {
            var lastTop = elem.scrollTop;
            elem.scrollTop += addY;

            // scroll top failed (edge)
            if (elem.scrollTop === lastTop) {
                multiplyY = 0;
            }
        }

        // clean up if there's nothing left to do
        if (!multiplyX && !multiplyY) {
            que = [];
        }

        if (que.length) {
            setTimeout(step, delay / framerate + 1);
        } else {
            pending = false;
        }
    }

    // start a new que of actions
    setTimeout(step, 0);
    pending = true;
}

/***********************************************
 * EVENTS
 ***********************************************/

/**
 * Mouse wheel handler.
 * NOTE: function logic should be decomposed.
 * @param {Object} event
 */
function wheel(event) {

    if (!initdone) {
        init();
    }

    var target = event.target;

    // use default if there's no overflowing
    // element or default action is prevented
    var elem = overflowingAncestor(target);
    if (!elem || noscrollframe || event.defaultPrevented ||
        isNodeName(activeElement, "embed") ||
       (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
        return true;
    }

    deltaX = event.wheelDeltaX || 0;
    deltaY = event.wheelDeltaY || 0;

    // normalize deltas
    // synaptics seems to send 1 sometimes,
    // and 120 other times (fix)
    if (Math.abs(deltaX) > 1.2) {
        deltaX /= 120;
    }
    if (Math.abs(deltaY) > 1.2) {
        deltaY /= 120;
    }

    var dir = (deltaY > 0) ? "up" : "down";
    scrollArray(elem, dir, -deltaX, -deltaY);
    event.preventDefault();
}

/**
 * Keydown event handler.
 * @param {Object} event
 */
function keydown(event) {

    var target   = event.target;
    var modifier = event.ctrlKey || event.altKey || event.metaKey;

    // do nothing if user is editing text
    // or using a modifier key (except shift)
    if ( /input|textarea|embed/i.test(target.nodeName) ||
         target.isContentEditable ||
         event.defaultPrevented   ||
         modifier ) {
      return true;
    }
    // spacebar should trigger button press
    if (isNodeName(target, "button") &&
        event.keyCode === key.spacebar) {
      return true;
    }

    var scale, dir, shift;
    var elem = overflowingAncestor(activeElement);
    var clientHeight = elem.clientHeight;

    if (elem == document.body) {
        clientHeight = window.innerHeight;
    }

    switch (event.keyCode) {
        case key.up:
            scale = -arrowscroll;
            dir = "up";
            break;
        case key.down:
            scale = arrowscroll;
            dir = "down";
            break;
        case key.spacebar: // (+ shift)
            shift = event.shiftKey ? 1 : -1;
            scale = -shift * clientHeight * 0.9;
            dir = (shift > 0) ? "up" : "down";
            break;
        case key.pageup:
            scale = -clientHeight * 0.9;
            dir = "up";
            break;
        case key.pagedown:
            scale = clientHeight * 0.9;
            dir = "down";
            break;
        case key.home:
            scale = -elem.scrollTop;
            dir = "up";
            break;
        case key.end:
            var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
            scale = (damt > 0) ? damt+10 : 0;
            dir = "down";
            break;
        default:
            return true; // a key we don"t care about
    }
    scale /= stepsize;
    scrollArray(elem, dir, 0, scale);
    event.preventDefault();
}

/**
 * Mousedown event only for updating activeElement
 */
function mousedown(event) {
    activeElement = event.target;
}

/***********************************************
 * HELPERS
 ***********************************************/

function addEvent(type, fn, bubble) {
    window.addEventListener(type, fn, (bubble||false));
}

function removeEvent(type, fn, bubble) {
    window.removeEventListener(type, fn, (bubble||false));
}

function isNodeName(el, tag) {
    return el.nodeName.toLowerCase() === tag.toLowerCase();
}

function directionCheck(dir) {
    if (direction !== dir) {
        que = [];
        direction = dir;
    }
}

function overflowingAncestor(el) {
    var bodyScrollHeight = document.body.scrollHeight;
    do {
        if (bodyScrollHeight === el.scrollHeight) {
            return document.body;
        }
        else if (el.clientHeight + 10 < el.scrollHeight) {
            overflow = getComputedStyle(el, "").getPropertyValue("overflow");
            if (overflow === "scroll" || overflow === "auto") {
                return el;
            }
        }
    } while (el = el.parentNode);
}

/***********************************************
 * PULSE
 ***********************************************/

/**
 * Viscous fluid with a pulse for part and decay for the rest.
 * - Applies a fixed force over an interval (a damped acceleration), and
 * - Lets the exponential bleed away the velocity over a longer interval
 * - Michael Herf, http://stereopsis.com/stopping/
 */
function pulse_(x) {
    var val, start, expx;
    // test
    x = x * pulseScale;
    if (x < 1) { // acceleartion
        val = x - (1 - Math.exp(-x));
    } else {     // tail
        // the previous animation ended here:
        start = Math.exp(-1);
        // simple viscous drag
        x -= 1;
        expx = 1 - Math.exp(-x);
        val = start + (expx * (1 - start));
    }
    return val * pulseNormalize;
}

function computePulseScale() {
    pulseNormalize = 1 / pulse_(1);
}

/**
 * Viscous fluid with a pulse for part and decay for the rest
 */
function pulse(x) {
    if (x >= 1) return 1;
    if (x <= 0) return 0;

    if (pulseNormalize == 1) {
        computePulseScale();
    }
    return pulse_(x);
}

addEvent("mousedown", mousedown);
addEvent("mousewheel", wheel);
addEvent("DOMContentLoaded", init);
<commit_msg>simplified scroll last delta<commit_after>
// SmoothScroll v0.9.5
// Licensed under the terms of the MIT license.

// People involved
// - Balazs Galambosi: maintainer (CHANGELOG.txt)
// - Patrick Brunner (patrickb1991@gmail.com)
// - Michael Herf: Pulse Algorithm

// Frame Variables
var frame         = false;
var noscrollframe = false;

// Scroll Variables (tweakable)
var framerate = 150; // [Hz]
var animtime  = 400; // [px]
var stepsize  = 120; // [px]

// Pulse (less tweakable)
// ratio of "tail" to "acceleration"
var pulseAlgorithm = true;
var pulseScale     = 8;
var pulseNormalize = 1;

// Keyboard Settings
var keyboardsupport = true;
var disableKeyboard = false;
var arrowscroll     = 50; // [px]

// Excluded pages
var exclude = "";

// Other Variables
var scrolls;
var deltaX = 0;
var deltaY = 0;
var direction = "down";
var initdone  = false;
var activeElement;

var key = { up: 38, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36 };

/***********************************************
 * SETTINGS
 ***********************************************/

chrome.extension.connect({ name: "smoothscroll" }).
onMessage.addListener(function (settings) {

    // NOTE: + converts to {Number}
    framerate = Math.min(+settings.framerate, 250);
    animtime  = +settings.animtime;
    stepsize  = +settings.scrollsz;
    exclude   = settings.exclude;
    pulseAlgorithm  = (settings.pulseAlgorithm == "true");
    pulseScale      = +settings.pulseScale;
    keyboardsupport = (settings.keyboardsupport == "true");
    arrowscroll     = +settings.arrscroll;

    computePulseScale();

    // it seems that sometimes settings come late
    // and we need to test again for excluded pages
    initTest();

    if (keyboardsupport && !disableKeyboard) {
        addEvent("keydown", keydown);
    }

    // If extension settings were deleted somehow
    if (!framerate) {
        alert("SmoothScroll: Please restart Chrome");
    }
});

/***********************************************
 * INITIALIZE
 ***********************************************/

/**
 * Tests if smooth scrolling is allowed. Shuts down everything if not.
 */
function initTest() {

    // disable keys for google reader (spacebar conflict)
    if (document.URL.indexOf("google.com/reader/view") > -1) {
        disableKeyboard = true;
    }

    // disable everything if the page is blacklisted
    if (exclude) {
        var domains = exclude.split(/[,\n] ?/);
        for (var i = domains.length; i--;) {
            if (document.URL.indexOf(domains[i]) > -1) {
                removeEvent("mousewheel", wheel);
                disableKeyboard = true;
                break;
            }
        }
    }

    // disable keyboard support if anything above requested it
    if (disableKeyboard) {
        removeEvent("keydown", keydown);
    }
}

/**
 * Sets up scrolls array, determines if frames are involved.
 */
function init() {

    var body  = document.body;
    var docel = document.documentElement;
    var htmlHeight   = docel.offsetHeight;
    var windowHeight = window.innerHeight;

    activeElement = body;
    initTest();

    if (document.domain === "acid3.acidtests.org") return;

    // Checks if this script is running in a frame
    if (top && top != self) {
        frame = true;
        if (docel.scrollHeight <= docel.clientHeight + 10) {
            noscrollframe = true;
        }
    }

    /**
     * This fixes a bug where the areas left and right to
     * the content does not trigger the onmousewheel event
     * on some pages. e.g.: html, body { height: 100% }
     */
    else if (body.scrollHeight > windowHeight &&
            (body.offsetHeight <= windowHeight || htmlHeight <= windowHeight)) {

        var underlay = document.createElement("div");
        underlay.setAttribute( "style",
            "z-index: -1; position: absolute; top: 0; left: 0; " +
            "width: 100%; height: " + body.scrollHeight + "px;" );
        body.appendChild(underlay);

        var scrollHeight = body.scrollHeight;
        var pending = false;

        // DOMChange delayed
        var refresh = function() {
            if (!pending && scrollHeight != body.scrollHeight) {
                pending = true; // add a new pending action
                setTimeout(function(){
                    underlay.style.height = body.scrollHeight + "px";
                    scrollHeight = body.scrollHeight;
                    pending = false;
                }, 1000); // act rarely to stay fast
            }
        }

        addEvent("DOMNodeInserted", refresh);
        addEvent("DOMNodeRemoved",  refresh);
    }
    initdone = true;
}

/************************************************
 * SCROLLING
 ************************************************/

var que = [];
var pending = false;

/**
 * Pushes scroll actions to a given direction Array.
 */
function scrollArray(elem, dir, multiplyX, multiplyY, delay) {

    delay || (delay = 1000);
    directionCheck(dir);

    // contains the scroll commands [ x, y, start, last ]
    que.push([multiplyX, multiplyY, +new Date, 0]);

    // don't act if there's a pending queue
    if (pending) {
        return;
    }

    var step = function() {

        var now = +new Date;
        var addX = 0;
        var addY = 0;
        var finished;
        var x = 0, y = 1, start = 2, last = 3; // {enum}

        for (var i = 0; i < que.length; i++) {

            var item = que[i];
            var elapsed = now - item[start];

            finished = (elapsed >= animtime);

            // scroll is [0, 1]
            var scroll = (finished) ? 1 : elapsed / animtime;
            // easing
            if (pulseAlgorithm) {
                scroll = pulse(scroll);
            }

            // scale and quantize to int so our pixel difference works:
            scroll -= item[last];
            item[last] += scroll;

            // add this to the actual scrolling
            addX += item[x] * scroll;
            addY += item[y] * scroll;

            // delete and step back
            if (finished) {
                que.splice(i, 1); i--;
            }
        }

        addX >>= 0; // toInt
        addY >>= 0; // toInt

        // scroll left
        if (multiplyX && addX) {
            var lastLeft = elem.scrollLeft;
            elem.scrollLeft += addX;

            // scroll left failed (edge)
            if (elem.scrollLeft === lastLeft) {
                multiplyX = 0;
            }
        }

        // scroll top
        if (multiplyY && addY) {
            var lastTop = elem.scrollTop;
            elem.scrollTop += addY;

            // scroll top failed (edge)
            if (elem.scrollTop === lastTop) {
                multiplyY = 0;
            }
        }

        // clean up if there's nothing left to do
        if (!multiplyX && !multiplyY) {
            que = [];
        }

        if (que.length) {
            setTimeout(step, delay / framerate + 1);
        } else {
            pending = false;
        }
    }

    // start a new queue of actions
    setTimeout(step, 0);
    pending = true;
}

/***********************************************
 * EVENTS
 ***********************************************/

/**
 * Mouse wheel handler.
 * NOTE: function logic should be decomposed.
 * @param {Object} event
 */
function wheel(event) {

    if (!initdone) {
        init();
    }

    var target = event.target;

    // use default if there's no overflowing
    // element or default action is prevented
    var elem = overflowingAncestor(target);
    if (!elem || noscrollframe || event.defaultPrevented ||
        isNodeName(activeElement, "embed") ||
       (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
        return true;
    }

    deltaX = event.wheelDeltaX || 0;
    deltaY = event.wheelDeltaY || 0;

    // normalize deltas
    // synaptics seems to send 1 sometimes,
    // and 120 other times (fix)
    if (Math.abs(deltaX) < 1.2) {
        deltaX *= 120;
    }
    if (Math.abs(deltaY) < 1.2) {
        deltaY *= 120;
    }

    var dir = (deltaY > 0) ? "up" : "down";
    scrollArray(elem, dir, -deltaX, -deltaY);
    event.preventDefault();
}

/**
 * Keydown event handler.
 * @param {Object} event
 */
function keydown(event) {

    var target   = event.target;
    var modifier = event.ctrlKey || event.altKey || event.metaKey;

    // do nothing if user is editing text
    // or using a modifier key (except shift)
    if ( /input|textarea|embed/i.test(target.nodeName) ||
         target.isContentEditable ||
         event.defaultPrevented   ||
         modifier ) {
      return true;
    }
    // spacebar should trigger button press
    if (isNodeName(target, "button") &&
        event.keyCode === key.spacebar) {
      return true;
    }

    var scale, dir, shift;
    var elem = overflowingAncestor(activeElement);
    var clientHeight = elem.clientHeight;

    if (elem == document.body) {
        clientHeight = window.innerHeight;
    }

    switch (event.keyCode) {
        case key.up:
            scale = -arrowscroll;
            dir = "up";
            break;
        case key.down:
            scale = arrowscroll;
            dir = "down";
            break;
        case key.spacebar: // (+ shift)
            shift = event.shiftKey ? 1 : -1;
            scale = -shift * clientHeight * 0.9;
            dir = (shift > 0) ? "up" : "down";
            break;
        case key.pageup:
            scale = -clientHeight * 0.9;
            dir = "up";
            break;
        case key.pagedown:
            scale = clientHeight * 0.9;
            dir = "down";
            break;
        case key.home:
            scale = -elem.scrollTop;
            dir = "up";
            break;
        case key.end:
            var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
            scale = (damt > 0) ? damt+10 : 0;
            dir = "down";
            break;
        default:
            return true; // a key we don"t care about
    }

    scrollArray(elem, dir, 0, scale);
    event.preventDefault();
}

/**
 * Mousedown event only for updating activeElement
 */
function mousedown(event) {
    activeElement = event.target;
}

/***********************************************
 * HELPERS
 ***********************************************/

function addEvent(type, fn, bubble) {
    window.addEventListener(type, fn, (bubble||false));
}

function removeEvent(type, fn, bubble) {
    window.removeEventListener(type, fn, (bubble||false));
}

function isNodeName(el, tag) {
    return el.nodeName.toLowerCase() === tag.toLowerCase();
}

function directionCheck(dir) {
    if (direction !== dir) {
        que = [];
        direction = dir;
    }
}

function overflowingAncestor(el) {
    var bodyScrollHeight = document.body.scrollHeight;
    do {
        if (bodyScrollHeight === el.scrollHeight) {
            return document.body;
        }
        else if (el.clientHeight + 10 < el.scrollHeight) {
            overflow = getComputedStyle(el, "").getPropertyValue("overflow");
            if (overflow === "scroll" || overflow === "auto") {
                return el;
            }
        }
    } while (el = el.parentNode);
}

/***********************************************
 * PULSE
 ***********************************************/

/**
 * Viscous fluid with a pulse for part and decay for the rest.
 * - Applies a fixed force over an interval (a damped acceleration), and
 * - Lets the exponential bleed away the velocity over a longer interval
 * - Michael Herf, http://stereopsis.com/stopping/
 */
function pulse_(x) {
    var val, start, expx;
    // test
    x = x * pulseScale;
    if (x < 1) { // acceleartion
        val = x - (1 - Math.exp(-x));
    } else {     // tail
        // the previous animation ended here:
        start = Math.exp(-1);
        // simple viscous drag
        x -= 1;
        expx = 1 - Math.exp(-x);
        val = start + (expx * (1 - start));
    }
    return val * pulseNormalize;
}

function computePulseScale() {
    pulseNormalize = 1 / pulse_(1);
}

/**
 * Viscous fluid with a pulse for part and decay for the rest
 */
function pulse(x) {
    if (x >= 1) return 1;
    if (x <= 0) return 0;

    if (pulseNormalize == 1) {
        computePulseScale();
    }
    return pulse_(x);
}

addEvent("mousedown", mousedown);
addEvent("mousewheel", wheel);
addEvent("DOMContentLoaded", init);
<|endoftext|>


<commit_before><commit_msg>Updatıng Javascrıpt template<commit_after><|endoftext|>