<commit_before>/**
 * @file MenuBody component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import {findDOMNode} from 'react-dom';
import PropTypes from 'prop-types';
import _ from 'lodash';

import Paper from '../Paper';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Event from '../_vendors/Event';
import PopupCalculation from '../_vendors/PopupCalculation';

export default class MenuBody extends Component {

    static Position = {

        TOP_LEFT: 'top-left',
        TOP: 'top',
        TOP_RIGHT: 'top-right',

        BOTTOM_LEFT: 'bottom-left',
        BOTTOM: 'bottom',
        BOTTOM_RIGHT: 'bottom-right',

        LEFT_TOP: 'left-top',
        LEFT: 'left',
        LEFT_BOTTOM: 'left-bottom',

        RIGHT_TOP: 'right-top',
        RIGHT: 'right',
        RIGHT_BOTTOM: 'right-bottom'

    };

    static TriggerMode = {
        TOGGLE: 'toggle',
        OPEN: 'open'
    };

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.hasMounted = false;
        this.prepareCloseTimeout = null;
        this.requestCloseTimeout = null;

        this.state = {
            visible: false
        };

        this.triggerMouseEnterHandler = ::this.triggerMouseEnterHandler;
        this.triggerMouseLeaveHandler = ::this.triggerMouseLeaveHandler;
        this.resizeHandler = ::this.resizeHandler;
        this.debounceResizeHandle = _.debounce(::this.debounceResizeHandle, 150);
        this.initializeAnimation = ::this.initializeAnimation;
        this.animate = ::this.animate;
        this.wheelHandler = ::this.wheelHandler;

    }

    triggerMouseEnterHandler(e) {
        this.prepareCloseTimeout && clearTimeout(this.prepareCloseTimeout);
    }

    triggerMouseLeaveHandler(e) {
        this.prepareCloseTimeout && clearTimeout(this.prepareCloseTimeout);
        this.prepareCloseTimeout = setTimeout(() => {
            this.setState({
                visible: false
            }, () => {
                this.requestCloseTimeout && clearTimeout(this.requestCloseTimeout);
                this.requestCloseTimeout = setTimeout(() => {
                    const {onRequestClose} = this.props;
                    this.hasMounted && onRequestClose && onRequestClose();
                }, 250);
            });
        }, 100);
    }

    resizeHandler() {
        this.debounceResizeHandle();
    }

    debounceResizeHandle() {
        this.forceUpdate();
    }

    wheelHandler(e) {
        const {shouldPreventContainerScroll, onWheel} = this.props;
        shouldPreventContainerScroll && Event.preventContainerScroll(e);
        onWheel && onWheel(e);
    }

    initializeAnimation(callback) {
        setTimeout(() => {
            this.hasMounted && callback();
        }, 0);
    }

    animate() {
        this.setState({
            visible: true
        });
    }

    componentDidMount() {

        this.hasMounted = true;
        this.menuEl = findDOMNode(this.refs.menu);

        Event.addEvent(this.props.triggerEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.addEvent(this.props.triggerEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.addEvent(this.menuEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.addEvent(this.menuEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.addEvent(window, 'resize', this.resizeHandler);

    }

    componentWillAppear(callback) {
        this.initializeAnimation(callback);
    }

    componentDidAppear() {
        this.animate();
    }

    componentDidUpdate() {
        const {onRender} = this.props,
            {visible} = this.state;
        visible && onRender && onRender(this.menuEl, this.props.triggerEl);
    }

    componentWillUnmount() {

        this.hasMounted = false;
        this.prepareCloseTimeout && clearTimeout(this.prepareCloseTimeout);
        this.requestCloseTimeout && clearTimeout(this.requestCloseTimeout);

        Event.removeEvent(this.props.triggerEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.removeEvent(this.props.triggerEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.removeEvent(this.menuEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.removeEvent(this.menuEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.removeEvent(window, 'resize', this.resizeHandler);

    }

    render() {

        const {
                children, className, style, theme, hasTriangle, triangle, position, isAnimated, depth, triggerEl
            } = this.props,
            {visible} = this.state,

            menuClassName = (visible ? '' : ' hidden') + (hasTriangle ? ' menu-has-triangle' : '')
                + (theme ? ` theme-${theme}` : '') + (position ? ` menu-position-${position}` : '')
                + (isAnimated ? ' menu-animated' : '') + (className ? ' ' + className : '');

        return (
            <Paper ref="menu"
                   className={'menu' + menuClassName}
                   style={{...PopupCalculation.getStyle(triggerEl, this.menuEl, position), ...style}}
                   depth={depth}
                   onWheel={this.wheelHandler}>

                {
                    hasTriangle ?

                            {triangle}

                        :
                        null
                }

                    {children}

            </Paper>
        );

    }
};

MenuBody.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The popover alignment.The value can be Menu.Position.LEFT or Menu.Position.RIGHT.
     */
    position: PropTypes.oneOf(Util.enumerateValue(MenuBody.Position)),

    /**
     * If true, menu will have animation effects.
     */
    isAnimated: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * The status of menu-triangle.Can be open or toggle.
     */
    triggerMode: PropTypes.oneOf(Util.enumerateValue(MenuBody.TriggerMode)),

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    /**
     * The function of menu render.
     */
    onRender: PropTypes.func,

    /**
     * Callback function fired when the popover is requested to be closed.
     */
    onRequestClose: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

MenuBody.defaultProps = {

    className: '',
    style: null,
    theme: Theme.DEFAULT,

    triggerEl: null,
    visible: false,
    hasTriangle: true,
    triangle:

,
    position: MenuBody.Position.BOTTOM_LEFT,
    isAnimated: true,
    triggerMode: MenuBody.TriggerMode.TOGGLE,
    depth: 6,
    shouldPreventContainerScroll: true

};<commit_msg>optimize MenuBody<commit_after>/**
 * @file MenuBody component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import {findDOMNode} from 'react-dom';
import PropTypes from 'prop-types';
import _ from 'lodash';

import Paper from '../Paper';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Event from '../_vendors/Event';
import PopupCalculation from '../_vendors/PopupCalculation';
import Position from '../_statics/Position';

export default class MenuBody extends Component {

    static Position = Position;

    static TriggerMode = {
        TOGGLE: 'toggle',
        OPEN: 'open'
    };

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.hasMounted = false;
        this.prepareCloseTimeout = null;
        this.requestCloseTimeout = null;

        this.state = {
            visible: false
        };

        this.triggerMouseEnterHandler = ::this.triggerMouseEnterHandler;
        this.triggerMouseLeaveHandler = ::this.triggerMouseLeaveHandler;
        this.resizeHandler = ::this.resizeHandler;
        this.debounceResizeHandle = _.debounce(::this.debounceResizeHandle, 150);
        this.initializeAnimation = ::this.initializeAnimation;
        this.animate = ::this.animate;
        this.wheelHandler = ::this.wheelHandler;

    }

    triggerMouseEnterHandler(e) {
        this.prepareCloseTimeout && clearTimeout(this.prepareCloseTimeout);
    }

    triggerMouseLeaveHandler(e) {
        this.prepareCloseTimeout && clearTimeout(this.prepareCloseTimeout);
        this.prepareCloseTimeout = setTimeout(() => {
            this.setState({
                visible: false
            }, () => {
                this.requestCloseTimeout && clearTimeout(this.requestCloseTimeout);
                this.requestCloseTimeout = setTimeout(() => {
                    const {onRequestClose} = this.props;
                    this.hasMounted && onRequestClose && onRequestClose();
                }, 250);
            });
        }, 100);
    }

    resizeHandler() {
        this.debounceResizeHandle();
    }

    debounceResizeHandle() {
        this.forceUpdate();
    }

    wheelHandler(e) {
        const {shouldPreventContainerScroll, onWheel} = this.props;
        shouldPreventContainerScroll && Event.preventContainerScroll(e);
        onWheel && onWheel(e);
    }

    initializeAnimation(callback) {
        setTimeout(() => {
            this.hasMounted && callback();
        }, 0);
    }

    animate() {
        this.setState({
            visible: true
        });
    }

    componentDidMount() {

        this.hasMounted = true;
        this.menuEl = findDOMNode(this.refs.menu);

        Event.addEvent(this.props.triggerEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.addEvent(this.props.triggerEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.addEvent(this.menuEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.addEvent(this.menuEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.addEvent(window, 'resize', this.resizeHandler);

    }

    componentWillAppear(callback) {
        this.initializeAnimation(callback);
    }

    componentDidAppear() {
        this.animate();
    }

    componentDidUpdate() {
        const {onRender} = this.props,
            {visible} = this.state;
        visible && onRender && onRender(this.menuEl, this.props.triggerEl);
    }

    componentWillUnmount() {

        this.hasMounted = false;
        this.prepareCloseTimeout && clearTimeout(this.prepareCloseTimeout);
        this.requestCloseTimeout && clearTimeout(this.requestCloseTimeout);

        Event.removeEvent(this.props.triggerEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.removeEvent(this.props.triggerEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.removeEvent(this.menuEl, 'mouseenter', this.triggerMouseEnterHandler);
        Event.removeEvent(this.menuEl, 'mouseleave', this.triggerMouseLeaveHandler);
        Event.removeEvent(window, 'resize', this.resizeHandler);

    }

    render() {

        const {
                children, className, style, theme, hasTriangle, triangle, position, isAnimated, depth, triggerEl
            } = this.props,
            {visible} = this.state,

            menuClassName = (visible ? '' : ' hidden') + (hasTriangle ? ' menu-has-triangle' : '')
                + (theme ? ` theme-${theme}` : '') + (position ? ` menu-position-${position}` : '')
                + (isAnimated ? ' menu-animated' : '') + (className ? ' ' + className : '');

        return (
            <Paper ref="menu"
                   className={'menu' + menuClassName}
                   style={{...PopupCalculation.getStyle(triggerEl, this.menuEl, position), ...style}}
                   depth={depth}
                   onWheel={this.wheelHandler}>

                {
                    hasTriangle ?

                            {triangle}

                        :
                        null
                }

                    {children}

            </Paper>
        );

    }
};

MenuBody.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * This is the DOM element that will be used to set the position of the popover.
     */
    triggerEl: PropTypes.object,

    /**
     * If true,the popover is visible.
     */
    visible: PropTypes.bool,

    /**
     * If true,the popover will have a triangle on the top of the DOM element.
     */
    hasTriangle: PropTypes.bool,

    triangle: PropTypes.element,

    /**
     * The popover theme.Can be primary,highlight,success,warning,error.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The popover alignment.The value can be Menu.Position.LEFT or Menu.Position.RIGHT.
     */
    position: PropTypes.oneOf(Util.enumerateValue(Position)),

    /**
     * If true, menu will have animation effects.
     */
    isAnimated: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * The status of menu-triangle.Can be open or toggle.
     */
    triggerMode: PropTypes.oneOf(Util.enumerateValue(MenuBody.TriggerMode)),

    /**
     * The depth of Paper component.
     */
    depth: PropTypes.number,

    /**
     * The function of menu render.
     */
    onRender: PropTypes.func,

    /**
     * Callback function fired when the popover is requested to be closed.
     */
    onRequestClose: PropTypes.func,

    /**
     * Callback function fired when wrapper wheeled.
     */
    onWheel: PropTypes.func

};

MenuBody.defaultProps = {

    className: '',
    style: null,
    theme: Theme.DEFAULT,

    triggerEl: null,
    visible: false,
    hasTriangle: true,
    triangle:

,
    position: Position.BOTTOM_LEFT,
    isAnimated: true,
    triggerMode: MenuBody.TriggerMode.TOGGLE,
    depth: 6,
    shouldPreventContainerScroll: true

};<|endoftext|>


<commit_before>"use strict";
var AppServiceRegistration = require("matrix-appservice").AppServiceRegistration;
var bridgeLib = require("./");
var Cli = bridgeLib.Cli;
var Bridge = bridgeLib.Bridge;
var RemoteUser = bridgeLib.RemoteUser;

var bridgeInst = new Bridge({
    homeserverUrl: "http://localhost:8008",
    domain: "localhost",
    registration: "my-bridge-registration.yaml",
    userStore: "store-users.db",
    roomStore: "store-rooms.db",

    controller: {
        // optional function to automatically create users and set display names
        // and link to remote users in the store. Can also return a Promise here.
        onUserQuery: function(matrixUserQueried) {
            var remoteUsername = matrixUserQueried.localpart.replace("example_", "");
            var remoteUser = new RemoteUser(remoteUsername);
            remoteUser.set("arbitrary key", {
                "arbitrary value": "here"
            });
            console.log(
                "provisionUser user_id=%s remote_id=%s",
                matrixUserQueried.getId(), remoteUser.getId()
            );
            return {
                name: remoteUsername,
                url: null,
                user: remoteUser
            };
        },

        onAliasQuery: function(alias, aliasLocalpart) {
            return {
                creationOpts: {
                    room_alias_name: aliasLocalpart,
                    name: aliasLocalpart
                }
            };
        },

        onEvent: function(request, context) {
            console.log(JSON.stringify(request.getData()));
            console.log("Remote Sender: %s", context.senders.remote);
        }
    }
});

bridgeInst.on("run", function(port, config) {
    console.log("Bridge now running on port %s", port);
    // You can do some custom bridge logic here with the parsed and validated
    // config object.
})

var c = new Cli({
    // Required. the thing which will be called with run(port,config)
    bridge: bridgeInst,

    // this little example has no config file, so disable it from the CLI.
    enableConfig: false,

    // Optional: where --generate-registration will dump to
    registrationPath: "my-bridge-registration.yaml",

    // Optional: default port, CLI can override with --port
    port: 8155,

    // Required if enableRegistration is true.
    // make the registration file. Callback provided so you can do DB hits.
    generateRegistration: function(callback) {
        var reg = new AppServiceRegistration("http://localhost:8008");
        reg.setHomeserverToken(AppServiceRegistration.generateToken());
        reg.setAppServiceToken(AppServiceRegistration.generateToken());
        reg.setSenderLocalpart("bridge-example");
        reg.addRegexPattern("users", "@example_.*", true);
        console.log("Generating registration to 'my-bridge-registration.yaml'");
        callback(reg);
    }
});

c.run(); // check system args
<commit_msg>Remove stale example<commit_after><|endoftext|>


<commit_before>var template = function(user) {
  if (typeof user === "string") {
    user = {
      name: user
    };
  }
  var username = user.name || "";
  var role = user.role || "";

  return $("
")
    .append("" +
      username +
      "")
    .append("" + role + "")
    .append("<button type='button' class='delete-user icon-x'>Remove User " + username + "</button>")
    .append("<input type='hidden' name='member' value='" + username + "' />").html();

};

var getTeamUsers = function(orgName, teamName) {
  var url = "/org/" + orgName + "/team/" + teamName + "/user";

  var opts = {
    method: "GET",
    url: url,
    json: true,
    data: {}
  };

  if (window && window.crumb) {
    opts.data.crumb = window.crumb;
    opts.headers = {
      'x-csrf-token': window.crumb
    };
  }

  return $.ajax(opts);
};

var AddUserForm = function(form) {
  this.$el = form;
  this.selectMenu = this.$el.find("[name=teams]");
  this.membersList = $(".members-list");
  this.memberCount = 0;

  var action = this.$el.attr("action");
  var actionArr = action.split("/");

  if (actionArr.length === 5) {
    this.orgScope = actionArr[2];
    this.teamName = actionArr[4];
  }

  this.bearer = $("[data-user-name]").data("user-name");
};

AddUserForm.prototype.removeUser = function(username) {
  $("[data-name=" + username + "]").remove();
  this.memberCount--;
  this.updateMemberCount();
};

AddUserForm.prototype.removeAll = function() {
  this.membersList.empty();
  this.memberCount = 0;
  this.updateMemberCount();
};

AddUserForm.prototype.updateMemberCount = function() {
  $(".add-count .add-number").text(this.memberCount);
  if (this.memberCount === 1) {
    $(".add-count .unit").text("User");
  } else {
    $(".add-count .unit").text("Users");
  }
};

AddUserForm.prototype.addUsers = function(users) {
  var list = users.map(function(user) {
    return template(user);
  }).join("");
  this.membersList.append(list);
  this.memberCount = users.length;
  this.updateMemberCount();
};

AddUserForm.prototype.notify = function(notification) {
  $(".notice").remove();
  var html = "
" + notification + "
";
  $(".container.content").prepend(html);
};

module.exports = function() {
  $(function() {
    var form = $("#add-user-to-team-form");
    var auf = new AddUserForm(form);

    var addBtn = $("#add-member-to-list");

    addBtn.on("click", function() {
      var group = $(this).closest("fieldset");
      var username = group.find("[name=member]").val();
      var role = group.find("input[type=radio]:checked");
      auf.addUsers([
        {
          name: username,
          role: role
        }
      ]);
      group.find("[name=member]").val("");
    });

    auf.selectMenu.on("change", function() {
      return getTeamUsers(auf.orgScope, this.options[this.selectedIndex].value)
        .done(function(users) {
          auf.removeAll();
          return auf.addUsers(users);
        })
        .fail(function(response) {
          console.log(response);
        });
    });

  });
};
<commit_msg>Fix templating<commit_after>var template = function(user) {
  if (typeof user === "string") {
    user = {
      name: user
    };
  }
  var username = user.name || "";
  var role = user.role || "";

  return "
" +
    "" +
    username +
    "" +
    "" + role + "" +
    "<button type='button' class='delete-user icon-x'>" +
    "Remove User " +
    username +
    "</button>" +
    "<input type='hidden' name='member' value='" +
    username + "' />";

};

var getTeamUsers = function(orgName, teamName) {
  var url = "/org/" + orgName + "/team/" + teamName + "/user";

  var opts = {
    method: "GET",
    url: url,
    json: true,
    data: {}
  };

  if (window && window.crumb) {
    opts.data.crumb = window.crumb;
    opts.headers = {
      'x-csrf-token': window.crumb
    };
  }

  return $.ajax(opts);
};

var AddUserForm = function(form) {
  this.$el = form;
  this.selectMenu = this.$el.find("[name=teams]");
  this.membersList = $(".members-list");
  this.memberCount = 0;

  var action = this.$el.attr("action");
  var actionArr = action.split("/");

  if (actionArr.length === 5) {
    this.orgScope = actionArr[2];
    this.teamName = actionArr[4];
  }

  this.bearer = $("[data-user-name]").data("user-name");
};

AddUserForm.prototype.removeUser = function(username) {
  $("[data-name=" + username + "]").remove();
  this.memberCount--;
  this.updateMemberCount();
};

AddUserForm.prototype.removeAll = function() {
  this.membersList.empty();
  this.memberCount = 0;
  this.updateMemberCount();
};

AddUserForm.prototype.updateMemberCount = function() {
  $(".add-count .add-number").text(this.memberCount);
  if (this.memberCount === 1) {
    $(".add-count .unit").text("User");
  } else {
    $(".add-count .unit").text("Users");
  }
};

AddUserForm.prototype.addUsers = function(users) {
  var list = users.map(function(user) {
    return template(user);
  }).join("");
  this.membersList.append(list);
  this.memberCount = users.length;
  this.updateMemberCount();
};

AddUserForm.prototype.notify = function(notification) {
  $(".notice").remove();
  var html = "
" + notification + "
";
  $(".container.content").prepend(html);
};

module.exports = function() {
  $(function() {
    var form = $("#add-user-to-team-form");
    var auf = new AddUserForm(form);

    var addBtn = $("#add-member-to-list");

    addBtn.on("click", function() {
      var group = $(this).closest("fieldset");
      var username = group.find("[name=member]").val();
      var role = group.find("input[type=radio]:checked");
      auf.addUsers([
        {
          name: username,
          role: role
        }
      ]);
      group.find("[name=member]").val("");
    });

    auf.selectMenu.on("change", function() {
      return getTeamUsers(auf.orgScope, this.options[this.selectedIndex].value)
        .done(function(users) {
          auf.removeAll();
          return auf.addUsers(users);
        })
        .fail(function(response) {
          console.log(response);
        });
    });

  });
};
<|endoftext|>


<commit_before>define(function(require) {
    'use strict';

    var $ = require('jquery');
    var _ = require('underscore');
    var tools = {};
    var iOS = /(iPad|iPhone)/.test(navigator.userAgent);
    var edge = /(Edge\/)/.test(navigator.userAgent);

    /**
     * @export oroui/js/tools
     * @name   oroui.tools
     */
    _.extend(tools, {
        /** @type {boolean} */
        debug: false,
        /**
         * Pack object to string
         *
         * Object {foo: 'x', 'bar': 'y'} will be converted to string "foo=x&bar=y".
         *
         * @param {Object} object
         * @return {String}
         */
        packToQueryString: function(object) {
            return $.param(object);
        },

        /**
         * Pack object to string with query parameters sorted
         *
         * Object {foo: 'x', 'bar': 'y'} will be converted to string "foo=x&bar=y".
         *
         * @param {Object} object
         * @return {String}
         */
        packToQuerySortedString: function(object) {
            var self = this;
            var result = [];
            var add = function(key, value) {
                result[result.length] = encodeURIComponent(key) + '=' +
                    encodeURIComponent(value === null ? '' : value);
            };

            var buildParams = function(pref, obj) {
                if (self.isArrayLikeObject(obj)) {
                    obj = _.toArray(obj);
                }

                if (_.isArray(obj)) {
                    obj.sort();
                    _.each(obj, function(value, key) {
                        buildParams(pref + '[' + (typeof value === 'object' ? key : '') + ']', value);
                    });
                } else if (typeof obj === 'object') {
                    var keys = _.keys(obj);
                    keys.sort();
                    for (var i = 0; i < keys.length; i++) {
                        var name = keys[i];
                        buildParams(pref + '[' + name + ']', obj[name]);
                    }
                } else {
                    add(pref, obj);
                }
            };

            var keys = _.keys(object);
            keys.sort();
            for (var i = 0; i < keys.length; i++) {
                var prefix = keys[i];
                buildParams(prefix, object[prefix]);
            }
            return result.join('&');
        },

        /**
         * Unpack string to object. Reverse from packToQueryString.
         *
         * @param {String} query
         * @return {Object}
         */
        unpackFromQueryString: function(query) {
            if (query.charAt(0) === '?') {
                query = query.slice(1);
            }
            var setValue = function(root, path, value) {
                if (path.length > 1) {
                    var dir = path.shift();
                    if (typeof root[dir] === 'undefined') {
                        root[dir] = path[0] === '' ? [] : {};
                    }
                    setValue(root[dir], path, value);
                } else {
                    if (root instanceof Array) {
                        root.push(value);
                    } else {
                        root[path] = value;
                    }
                }
            };
            var nvp = query.split('&');
            var data = {};
            for (var i = 0; i < nvp.length; i++) {
                var pair = nvp[i].split('=');
                if (pair.length < 2) {
                    continue;
                }
                var name = this.decodeUriComponent(pair[0]);
                var value = this.decodeUriComponent(pair[1]);

                var path = name.match(/(^[^\[]+)(\[.*\]$)?/);
                var first = path[1];
                if (path[2]) {
                    // case of 'array[level1]' || 'array[level1][level2]'
                    path = path[2].match(/(?=\[(.*)\]$)/)[1].split('][');
                } else {
                    // case of 'name'
                    path = [];
                }
                path.unshift(first);

                setValue(data, path, value);
            }
            return data;
        },

        /**
         * Decode URL encoded component
         *
         * @param {String} string
         * @return {String}
         * @protected
         */
        decodeUriComponent: function(string) {
            var result = string.replace(/\+/g, '%20');
            result = decodeURIComponent(result);
            return result;
        },

        /**
         * Invert object keys.
         *
         * Example of usage:
         *
         * oro.app.invertKeys({foo: 'x', bar: 'y'}, {foo: 'f', bar: 'b'})
         * will return {f: 'x', b: 'y'}
         *
         * @param {Object} object
         * @param {Object} keys
         * @return {Object}
         */
        invertKeys: function(object, keys) {
            var result = _.extend({}, object);
            for (var key in keys) {
                if (!keys.hasOwnProperty(key)) {
                    continue;
                }
                var baseKey = key;
                var mirrorKey = keys[key];

                if (baseKey in result) {
                    result[mirrorKey] = result[baseKey];
                    delete result[baseKey];
                }
            }
            return result;
        },

        /**
         * Loosely compare two values
         *
         * @param {*} value1
         * @param {*} value2
         * @return {Boolean} TRUE if values are equal, otherwise - FALSE
         */
        isEqualsLoosely: function(value1, value2) {
            if (!_.isObject(value1)) {
                if (_.isNumber(value1) || _.isNumber(value2)) {
                    var toNumber = function(v) {
                        if (_.isString(v) && v === '') {
                            return NaN;
                        }
                        return Number(v);
                    };
                    return (toNumber(value1) === toNumber(value2));
                }
                return ((value1 || '') === (value2 || ''));
            } else if (_.isObject(value1)) {
                var valueKeys = _.keys(value1);

                if (_.isObject(value2)) {
                    valueKeys = _.unique(valueKeys.concat(_.keys(value2)));
                    for (var index in valueKeys) {
                        if (!valueKeys.hasOwnProperty(index)) {
                            continue;
                        }
                        var key = valueKeys[index];
                        if (!_.has(value2, key) || !this.isEqualsLoosely(value1[key], value2[key])) {
                            return false;
                        }
                    }
                    return true;
                }
                return false;
            } else {
                return value1 == value2; // eslint-disable-line eqeqeq
            }
        },

        /**
         * Deep clone a value
         *
         * @param {*} value
         * @return {*}
         */
        deepClone: function(value) {
            return $.extend(true, {}, value);
        },

        /**
         * Are we currently on mobile
         */
        isMobile: function() {
            return _.isMobile();
        },

        /**
         * Are we currently on desktop
         */
        isDesktop: function() {
            return _.isDesktop();
        },

        /**
         * Are we have touch screen
         */
        isTouchDevice: function() {
            return ('ontouchstart' in window && window.ontouchstart) ||
                ('DocumentTouch' in window && document instanceof window.DocumentTouch);
        },

        /**
         * Are we currently on iOS device
         */
        isIOS: function() {
            return iOS;
        },

        /**
         * Are we currently on EDGE browser
         */
        isEDGE: function() {
            return edge;
        },

        /**
         * Loads dynamic list of modules and execute callback function with passed modules
         *
         * @param {Object.<string, string>|Array.<string>|string} modules
         *  - Object: where keys are formal module names and values are actual
         *  - Array: module names,
         *  - string: single module name
         * @param {function(Object)=} callback
         * @param {Object=} context
         * @return {JQueryPromise}
         */
        loadModules: function(modules, callback, context) {
            var requirements;
            var processModules;

            if (_.isObject(modules) && !_.isArray(modules)) {
                // if modules is an object of {formal_name: module_name}
                requirements = _.values(modules);
                processModules = function(loadedModules) {
                    // maps loaded modules into original object
                    _.each(modules, _.partial(function(map, value, key) {
                        modules[key] = map[value];
                    }, _.object(requirements, loadedModules)));
                    return [modules];
                };
            } else {
                // if modules is an array of module_names or single module_name
                requirements = !_.isArray(modules) ? [modules] : modules;
                processModules = function(loadedModules) {
                    return loadedModules;
                };
            }

            var deferred = $.Deferred();
            require(requirements, _.partial(function(processor) {
                var modules = processor(_.rest(arguments));
                if (callback) {
                    callback.apply(context || null, modules);
                }
                deferred.resolve.apply(deferred, modules);
            }, processModules), function(e) {
                deferred.reject(e);
            });
            return deferred.promise();
        },

        /**
         * Loads single module through requireJS and returns promise
         *
         * @deprecated
         * @param {string} module name
         * @return {JQueryPromise}
         */
        loadModule: function(module) {
            return tools.loadModules(module);
        },

        /**
         * Loads single module through requireJS and replaces the property
         *
         * @param {Object} container where to replace property
         * @param {string} moduleProperty name to replace module ref to concrete realization
         * @return {JQueryPromise}
         */
        loadModuleAndReplace: function(container, moduleProperty) {
            if (_.isFunction(container[moduleProperty])) {
                var deferred = $.Deferred();
                deferred.resolve(container[moduleProperty]);
                return deferred.promise();
            }
            return this.loadModules(container[moduleProperty]).then(function(realization) {
                container[moduleProperty] = realization;
                return realization;
            });
        },

        /**
         * Check if current page is an error page (404, 503, 504, etc.)
         * @returns {boolean}
         */
        isErrorPage: function() {
            return Boolean($('meta[name=error]').length);
        },

        /**
         * Creates safe regexp expression from string
         *
         * @param {string} str
         * @param {string} flags
         */
        safeRegExp: function(str, flags) {
            var expression;
            str = str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
            expression = new RegExp('(' + str + ')', flags);
            return expression;
        },

        /**
         * Generates Version 4 random UUIDs (https://en.wikipedia.org/wiki/Universally_unique_identifier)
         * @return {string}
         */
        createRandomUUID: function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random() * 16 | 0;
                var v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        },

        /**
         * Checks input values and if it isn't an array wraps it
         *
         * @returns {Array}
         */
        ensureArray: function(value) {
            return _.isArray(value) ? value : [value];
        },

        /**
         * Check is this object can be represented as an array
         *
         * @param {Object} object
         * @returns {boolean}
         */
        isArrayLikeObject: function(object) {
            if (typeof object !== 'object') {
                return false;
            }
            var keys = _.keys(object);
            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];
                if (String(key) !== String(i)) {
                    return false;
                }
            }
            return true;
        },

        /**
         * Adds css rules to the first style sheet
         *
         * @param {string} selector
         * @param {string} styles
         */
        addCSSRule: function(selector, styles) {
            var styleSheet = document.styleSheets[0];
            styleSheet.insertRule(selector + '{' + styles + '}', styleSheet.cssRules.length);
        },

        /**
         * @param {Object} event
         */
        isTargetBlankEvent: function(event) {
            var mouseMiddleButton = 2;
            return event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ||
                event.which === mouseMiddleButton;
        },

        /**
         * Gets an XPath for an element which describes its hierarchical location.
         *
         * @param {HTMLElement} element
         * @returns {string|null}
         */
        getElementXPath: function(element) {
            var paths = [];
            var tagName = element.nodeName.toLowerCase();
            if (element && element.id) {
                return '//' + tagName + '[@id="' + element.id + '"]';
            } else {
                // Use nodeName (instead of localName) so namespace prefix is included (if any).
                for (; element && element.nodeType === 1; element = element.parentNode) {
                    var index = 0;
                    tagName = element.nodeName.toLowerCase();
                    // EXTRA TEST FOR ELEMENT.ID
                    if (element && element.id) {
                        paths.splice(0, 0, '/' + tagName + '[@id="' + element.id + '"]');
                        break;
                    }

                    for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
                        // Ignore document type declaration.
                        if (sibling.nodeType === Node.DOCUMENT_TYPE_NODE) {
                            continue;
                        }
                        if (sibling.nodeName === element.nodeName) {
                            ++index;
                        }
                    }

                    var pathIndex = '[' + (index + 1) + ']';
                    var classAttr = element.className ? '[@class="' + element.className + '"]' : '';
                    paths.splice(0, 0, tagName + pathIndex + classAttr);
                }

                return paths.length ? '/' + paths.join('/') : null;
            }
        }
    });

    return tools;
});
<commit_msg>BB-16485: Product quantity is not updated by enter in IE11 (#23192)<commit_after>define(function(require) {
    'use strict';

    var $ = require('jquery');
    var _ = require('underscore');
    var tools = {};
    var iOS = /(iPad|iPhone)/.test(navigator.userAgent);
    var edge = /(Edge\/)/.test(navigator.userAgent);
    var ie11 = /(Trident\/)/.test(navigator.userAgent);

    /**
     * @export oroui/js/tools
     * @name   oroui.tools
     */
    _.extend(tools, {
        /** @type {boolean} */
        debug: false,
        /**
         * Pack object to string
         *
         * Object {foo: 'x', 'bar': 'y'} will be converted to string "foo=x&bar=y".
         *
         * @param {Object} object
         * @return {String}
         */
        packToQueryString: function(object) {
            return $.param(object);
        },

        /**
         * Pack object to string with query parameters sorted
         *
         * Object {foo: 'x', 'bar': 'y'} will be converted to string "foo=x&bar=y".
         *
         * @param {Object} object
         * @return {String}
         */
        packToQuerySortedString: function(object) {
            var self = this;
            var result = [];
            var add = function(key, value) {
                result[result.length] = encodeURIComponent(key) + '=' +
                    encodeURIComponent(value === null ? '' : value);
            };

            var buildParams = function(pref, obj) {
                if (self.isArrayLikeObject(obj)) {
                    obj = _.toArray(obj);
                }

                if (_.isArray(obj)) {
                    obj.sort();
                    _.each(obj, function(value, key) {
                        buildParams(pref + '[' + (typeof value === 'object' ? key : '') + ']', value);
                    });
                } else if (typeof obj === 'object') {
                    var keys = _.keys(obj);
                    keys.sort();
                    for (var i = 0; i < keys.length; i++) {
                        var name = keys[i];
                        buildParams(pref + '[' + name + ']', obj[name]);
                    }
                } else {
                    add(pref, obj);
                }
            };

            var keys = _.keys(object);
            keys.sort();
            for (var i = 0; i < keys.length; i++) {
                var prefix = keys[i];
                buildParams(prefix, object[prefix]);
            }
            return result.join('&');
        },

        /**
         * Unpack string to object. Reverse from packToQueryString.
         *
         * @param {String} query
         * @return {Object}
         */
        unpackFromQueryString: function(query) {
            if (query.charAt(0) === '?') {
                query = query.slice(1);
            }
            var setValue = function(root, path, value) {
                if (path.length > 1) {
                    var dir = path.shift();
                    if (typeof root[dir] === 'undefined') {
                        root[dir] = path[0] === '' ? [] : {};
                    }
                    setValue(root[dir], path, value);
                } else {
                    if (root instanceof Array) {
                        root.push(value);
                    } else {
                        root[path] = value;
                    }
                }
            };
            var nvp = query.split('&');
            var data = {};
            for (var i = 0; i < nvp.length; i++) {
                var pair = nvp[i].split('=');
                if (pair.length < 2) {
                    continue;
                }
                var name = this.decodeUriComponent(pair[0]);
                var value = this.decodeUriComponent(pair[1]);

                var path = name.match(/(^[^\[]+)(\[.*\]$)?/);
                var first = path[1];
                if (path[2]) {
                    // case of 'array[level1]' || 'array[level1][level2]'
                    path = path[2].match(/(?=\[(.*)\]$)/)[1].split('][');
                } else {
                    // case of 'name'
                    path = [];
                }
                path.unshift(first);

                setValue(data, path, value);
            }
            return data;
        },

        /**
         * Decode URL encoded component
         *
         * @param {String} string
         * @return {String}
         * @protected
         */
        decodeUriComponent: function(string) {
            var result = string.replace(/\+/g, '%20');
            result = decodeURIComponent(result);
            return result;
        },

        /**
         * Invert object keys.
         *
         * Example of usage:
         *
         * oro.app.invertKeys({foo: 'x', bar: 'y'}, {foo: 'f', bar: 'b'})
         * will return {f: 'x', b: 'y'}
         *
         * @param {Object} object
         * @param {Object} keys
         * @return {Object}
         */
        invertKeys: function(object, keys) {
            var result = _.extend({}, object);
            for (var key in keys) {
                if (!keys.hasOwnProperty(key)) {
                    continue;
                }
                var baseKey = key;
                var mirrorKey = keys[key];

                if (baseKey in result) {
                    result[mirrorKey] = result[baseKey];
                    delete result[baseKey];
                }
            }
            return result;
        },

        /**
         * Loosely compare two values
         *
         * @param {*} value1
         * @param {*} value2
         * @return {Boolean} TRUE if values are equal, otherwise - FALSE
         */
        isEqualsLoosely: function(value1, value2) {
            if (!_.isObject(value1)) {
                if (_.isNumber(value1) || _.isNumber(value2)) {
                    var toNumber = function(v) {
                        if (_.isString(v) && v === '') {
                            return NaN;
                        }
                        return Number(v);
                    };
                    return (toNumber(value1) === toNumber(value2));
                }
                return ((value1 || '') === (value2 || ''));
            } else if (_.isObject(value1)) {
                var valueKeys = _.keys(value1);

                if (_.isObject(value2)) {
                    valueKeys = _.unique(valueKeys.concat(_.keys(value2)));
                    for (var index in valueKeys) {
                        if (!valueKeys.hasOwnProperty(index)) {
                            continue;
                        }
                        var key = valueKeys[index];
                        if (!_.has(value2, key) || !this.isEqualsLoosely(value1[key], value2[key])) {
                            return false;
                        }
                    }
                    return true;
                }
                return false;
            } else {
                return value1 == value2; // eslint-disable-line eqeqeq
            }
        },

        /**
         * Deep clone a value
         *
         * @param {*} value
         * @return {*}
         */
        deepClone: function(value) {
            return $.extend(true, {}, value);
        },

        /**
         * Are we currently on mobile
         */
        isMobile: function() {
            return _.isMobile();
        },

        /**
         * Are we currently on desktop
         */
        isDesktop: function() {
            return _.isDesktop();
        },

        /**
         * Are we have touch screen
         */
        isTouchDevice: function() {
            return ('ontouchstart' in window && window.ontouchstart) ||
                ('DocumentTouch' in window && document instanceof window.DocumentTouch);
        },

        /**
         * Are we currently on iOS device
         */
        isIOS: function() {
            return iOS;
        },

        /**
         * Are we currently on IE 11 browser
         */
        isIE11: function() {
            return ie11;
        },

        /**
         * Are we currently on EDGE browser
         */
        isEDGE: function() {
            return edge;
        },

        /**
         * Loads dynamic list of modules and execute callback function with passed modules
         *
         * @param {Object.<string, string>|Array.<string>|string} modules
         *  - Object: where keys are formal module names and values are actual
         *  - Array: module names,
         *  - string: single module name
         * @param {function(Object)=} callback
         * @param {Object=} context
         * @return {JQueryPromise}
         */
        loadModules: function(modules, callback, context) {
            var requirements;
            var processModules;

            if (_.isObject(modules) && !_.isArray(modules)) {
                // if modules is an object of {formal_name: module_name}
                requirements = _.values(modules);
                processModules = function(loadedModules) {
                    // maps loaded modules into original object
                    _.each(modules, _.partial(function(map, value, key) {
                        modules[key] = map[value];
                    }, _.object(requirements, loadedModules)));
                    return [modules];
                };
            } else {
                // if modules is an array of module_names or single module_name
                requirements = !_.isArray(modules) ? [modules] : modules;
                processModules = function(loadedModules) {
                    return loadedModules;
                };
            }

            var deferred = $.Deferred();
            require(requirements, _.partial(function(processor) {
                var modules = processor(_.rest(arguments));
                if (callback) {
                    callback.apply(context || null, modules);
                }
                deferred.resolve.apply(deferred, modules);
            }, processModules), function(e) {
                deferred.reject(e);
            });
            return deferred.promise();
        },

        /**
         * Loads single module through requireJS and returns promise
         *
         * @deprecated
         * @param {string} module name
         * @return {JQueryPromise}
         */
        loadModule: function(module) {
            return tools.loadModules(module);
        },

        /**
         * Loads single module through requireJS and replaces the property
         *
         * @param {Object} container where to replace property
         * @param {string} moduleProperty name to replace module ref to concrete realization
         * @return {JQueryPromise}
         */
        loadModuleAndReplace: function(container, moduleProperty) {
            if (_.isFunction(container[moduleProperty])) {
                var deferred = $.Deferred();
                deferred.resolve(container[moduleProperty]);
                return deferred.promise();
            }
            return this.loadModules(container[moduleProperty]).then(function(realization) {
                container[moduleProperty] = realization;
                return realization;
            });
        },

        /**
         * Check if current page is an error page (404, 503, 504, etc.)
         * @returns {boolean}
         */
        isErrorPage: function() {
            return Boolean($('meta[name=error]').length);
        },

        /**
         * Creates safe regexp expression from string
         *
         * @param {string} str
         * @param {string} flags
         */
        safeRegExp: function(str, flags) {
            var expression;
            str = str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
            expression = new RegExp('(' + str + ')', flags);
            return expression;
        },

        /**
         * Generates Version 4 random UUIDs (https://en.wikipedia.org/wiki/Universally_unique_identifier)
         * @return {string}
         */
        createRandomUUID: function() {
            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
                var r = Math.random() * 16 | 0;
                var v = c === 'x' ? r : (r & 0x3 | 0x8);
                return v.toString(16);
            });
        },

        /**
         * Checks input values and if it isn't an array wraps it
         *
         * @returns {Array}
         */
        ensureArray: function(value) {
            return _.isArray(value) ? value : [value];
        },

        /**
         * Check is this object can be represented as an array
         *
         * @param {Object} object
         * @returns {boolean}
         */
        isArrayLikeObject: function(object) {
            if (typeof object !== 'object') {
                return false;
            }
            var keys = _.keys(object);
            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];
                if (String(key) !== String(i)) {
                    return false;
                }
            }
            return true;
        },

        /**
         * Adds css rules to the first style sheet
         *
         * @param {string} selector
         * @param {string} styles
         */
        addCSSRule: function(selector, styles) {
            var styleSheet = document.styleSheets[0];
            styleSheet.insertRule(selector + '{' + styles + '}', styleSheet.cssRules.length);
        },

        /**
         * @param {Object} event
         */
        isTargetBlankEvent: function(event) {
            var mouseMiddleButton = 2;
            return event.shiftKey || event.altKey || event.ctrlKey || event.metaKey ||
                event.which === mouseMiddleButton;
        },

        /**
         * Gets an XPath for an element which describes its hierarchical location.
         *
         * @param {HTMLElement} element
         * @returns {string|null}
         */
        getElementXPath: function(element) {
            var paths = [];
            var tagName = element.nodeName.toLowerCase();
            if (element && element.id) {
                return '//' + tagName + '[@id="' + element.id + '"]';
            } else {
                // Use nodeName (instead of localName) so namespace prefix is included (if any).
                for (; element && element.nodeType === 1; element = element.parentNode) {
                    var index = 0;
                    tagName = element.nodeName.toLowerCase();
                    // EXTRA TEST FOR ELEMENT.ID
                    if (element && element.id) {
                        paths.splice(0, 0, '/' + tagName + '[@id="' + element.id + '"]');
                        break;
                    }

                    for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
                        // Ignore document type declaration.
                        if (sibling.nodeType === Node.DOCUMENT_TYPE_NODE) {
                            continue;
                        }
                        if (sibling.nodeName === element.nodeName) {
                            ++index;
                        }
                    }

                    var pathIndex = '[' + (index + 1) + ']';
                    var classAttr = element.className ? '[@class="' + element.className + '"]' : '';
                    paths.splice(0, 0, tagName + pathIndex + classAttr);
                }

                return paths.length ? '/' + paths.join('/') : null;
            }
        }
    });

    return tools;
});
<|endoftext|>


<commit_before>(function() {

  // LocalSolves manages solves which the user does offline. It uses
  // LocalPuzzles to get the solves of interest.
  //
  // The puzzles argument must be a LocalPuzzles which has already been loaded
  // with the user's current puzzles.
  //
  // This is an EventEmitter which will emit the following events:
  // - delete: either the deleteSolve or the moveSolve method was used.
  // - modify: the modifySolve method was used.
  // - modifyUnindexed: modifySolveById was used and the ID was not in a cursor.
  // - move: the moveSolve method was used.
  // - loadingStats
  // - computedStats
  function LocalSolves(puzzles) {
    window.app.EventEmitter.call(this);
    this._puzzles = puzzles;
    this._stats = null;
    this._averages = null;
    this._cursors = [];
    this._fillInMissingFields();
    this._computeStats();
  }

  LocalSolves.prototype = Object.create(window.app.EventEmitter.prototype);

  // addSolve pushes a solve to the end of the list.
  LocalSolves.prototype.addSolve = function(solve) {
    var solves = this.getSolves();
    solve.id = window.app.generateId();
    solves.push(solve);

    // All the cursors at the end of the data get the extra solve.
    for (var i = 0, len = this._cursors.length; i < len; ++i) {
      var cursor = this._cursors[i];
      if (cursor._start+cursor._length === this.getSolves().length-1) {
        ++cursor._length;
      }
    }

    recomputeLastPBsAndPWs(solves, solves.length-1);
    if (this._averages !== null) {
      this._averages.pushSolve(solve);
    }
    this._emitStats();
  };

  // createCursor generates a ticket for getting a LocalCursor.
  LocalSolves.prototype.createCursor = function(start, length, cb) {
    return new window.app.LocalCursorTicket(cb, this, start, length);
  };

  // cursorClosed is called by a LocalCursor when it is closed by a consumer.
  LocalSolves.prototype.cursorClosed = function(cursor) {
    var idx = this._cursors.indexOf(cursor);
    if (idx < 0) {
      throw new Error('cursor was not registered');
    }
    this._cursors.splice(idx, 1);
  };

  // cursorCreated is called by each cursor to register itself with the
  // LocalSolves.
  LocalSolves.prototype.cursorCreated = function(cursor) {
    this._cursors.push(cursor);
  };

  // deleteSolve deletes a solve at a given index.
  LocalSolves.prototype.deleteSolve = function(index) {
    var solves = this.getSolves();
    var id = solves[index].id;
    solves.splice(index, 1);

    for (var i = 0, len = this._cursors.length; i < len; ++i) {
      var cursor = this._cursors[i];
      if (index < cursor._start) {
        --cursor._start;
      } else if (index < cursor._start+cursor._length) {
        --cursor._length;
      }
    }

    recomputeLastPBsAndPWs(solves, index);
    this._resetStats();
    this.emit('delete', id, index);
  };

  // getLatestSolve returns the latest solve or null if no solves exist.
  LocalSolves.prototype.getLatestSolve = function() {
    var solves = this.getSolves();
    if (solves.length === 0) {
      return null;
    }
    return solves[solves.length - 1];
  };

  // getSolves returns the current array of solves.
  LocalSolves.prototype.getSolves = function() {
    return this._puzzles.getActivePuzzle().solves;
  };

  // getStats returns the current stats or null if no stats are cached.
  LocalSolves.prototype.getStats = function() {
    return this._stats;
  };

  // modifySolve changes the attributes of a given solve.
  LocalSolves.prototype.modifySolve = function(index, attrs) {
    this._modifySolveNoEmit(index, attrs);
    this.emit('modify', this.getSolves()[index].id, attrs, index);
  };

  // modifySolveById changes the attributes of a solve by looking up its ID.
  // This will emit 'modifyUnindexed' instead of 'modify' if the solve is not
  // within any cursor.
  LocalSolves.prototype.modifySolveById = function(id, attrs) {
    var index = -1;
    var solves = this.getSolves();
    for (var i = 0, len = solves.length; i < len; ++i) {
      if (solves[i].id === id) {
        index = i;
        break;
      }
    }
    if (index < 0) {
      return;
    }
    this._modifySolveNoEmit(index, attrs);

    var containedByCursor = false;
    for (var i = 0, len = this._cursors.length; i < len; ++i) {
      var cursor = this._cursors[i];
      if (index < cursor._start+cursor._length && index >= cursor._start) {
        containedByCursor = true;
        break;
      }
    }

    if (containedByCursor) {
      this.emit('modify', id, attrs, index);
    } else {
      this.emit('modifyUnindexed', id, attrs);
    }
  };

  // moveSolve moves a solve to a different puzzle.
  LocalSolves.prototype.moveSolve = function(index, puzzleId) {
    var puzzle = this._puzzles.findById(puzzleId);
    if (puzzle === null) {
      return;
    }
    var solve = this.getSolves()[index];
    this.deleteSolve(index);
    insertSolveUsingTimestamp(solve, puzzle.solves);
    this.emit('move', solve.id, puzzle);
  };

  // reset should be called whenever the solves were changed in a way that
  // cannot be easily broken down into deletions, additions and modifications.
  LocalSolves.prototype.reset = function() {
    while (this._cursors.length > 0) {
      this._cursors[this._cursors.length-1].close();
    }
    this._resetStats();
  };

  // _computeStats generates this._stats.
  LocalSolves.prototype._computeStats = function() {
    if (this._averages === null) {
      this._averages = new window.app.OfflineAverages();
      var solves = this.getSolves();
      for (var i = 0, len = solves.length; i < len; ++i) {
        this._averages.pushSolve(solves[i]);
      }
    }
    this._stats = this._averages.stats();
  };

  // _emitStats uses this._averages to emit new stats asynchronously.
  // It will create this._averages if it is null.
  LocalSolves.prototype._emitStats = function() {
    this._stats = null;
    this.emit('loadingStats');
    setTimeout(function() {
      if (this._stats === null) {
        this._computeStats();
        this.emit('computedStats', this._stats);
      }
    }.bind(this), 1);
  };

  // _fillInMissingFields fills in missing fields for all solve objects on all
  // puzzles.
  LocalSolves.prototype._fillInMissingFields = function() {
    var puzzles = this._puzzles.getPuzzles();
    for (var i = 0, len = puzzles.length; i < len; ++i) {
      var puzzle = puzzles[i];
      var solves = puzzle.solves;
      recomputeLastPBsAndPWs(solves, 0);
      for (var j = 0, len1 = solves.length; j < len1; ++j) {
        var solve = solves[j];
        if (!solve.scrambler) {
          solve.scrambler = puzzle.scrambler;
          solve.scrambleType = puzzle.scrambleType;
        }
      }
    }
  };

  // _modifySolveNoEmit modifies a solve without emitting a modify event.
  LocalSolves.prototype._modifySolveNoEmit = function(index, attrs) {
    var solve = this.getSolves()[index];
    if ('undefined' === typeof solve) {
      throw new Error('no solve at index: ' + index);
    }

    var newSolve = copySolve(solve);
    this.getSolves()[index] = newSolve;

    var keys = Object.keys(attrs);
    for (var i = 0, len = keys.length; i < len; ++i) {
      var key = keys[i];
      newSolve[key] = attrs[key];
    }

    // TODO: do not recompute this stuff if neither the time nor the penalties
    // were modified.
    recomputeLastPBsAndPWs(this.getSolves(), index+1);
    this._resetStats();
  };

  // _resetStats deletes all pre-existing knowledge about the stats and
  // recomputes them asynchronously.
  LocalSolves.prototype._resetStats = function() {
    this._averages = null;
    this._emitStats();
  };

  function copySolve(solve) {
    var newObject = {};
    var keys = Object.keys(solve);
    for (var i = 0, len = keys.length; i < len; ++i) {
      var key = keys[i];
      newObject[key] = solve[key];
    }
    return newObject;
  }

  function insertSolveUsingTimestamp(solve, solves) {
    // TODO: use a binary search here.
    for (var i = solves.length-1; i >= 0; --i) {
      if (solves[i].date <= solve.date) {
        solves.splice(i+1, 0, solve);
        recomputeLastPBsAndPWs(solves, i+1);
        return;
      }
    }
    solves.unshift(solve);
    recomputeLastPBsAndPWs(solves, 0);
  }

  function recomputeLastPBsAndPWs(solves, startIndex) {
    recomputeLastPBs(solves, startIndex);
    recomputeLastPWs(solves, startIndex);
  }

  function recomputeLastPBs(solves, startIndex) {
    if (startIndex >= solves.length) {
      return;
    }

    var lastPB = -1;

    if (startIndex > 0) {
      var previousSolve = solves[startIndex - 1];
      if (previousSolve.dnf) {
        lastPB = previousSolve.lastPB;
      } else if (previousSolve.lastPB === -1) {
        lastPB = window.app.solveTime(previousSolve);
      } else {
        lastPB = Math.min(window.app.solveTime(previousSolve),
          previousSolve.lastPB);
      }
    }

    for (var i = startIndex, len = solves.length; i < len; ++i) {
      var solve = solves[i];
      solve.lastPB = lastPB;
      if (!solve.dnf) {
        if (lastPB < 0) {
          lastPB = window.app.solveTime(solve);
        } else {
          lastPB = Math.min(lastPB, window.app.solveTime(solve));
        }
      }
    }
  };

  function recomputeLastPWs(solves, startIndex) {
    if (startIndex >= solves.length) {
      return;
    }

    var lastPW = -1;

    if (startIndex > 0) {
      var previousSolve = solves[startIndex - 1];
      if (previousSolve.dnf) {
        lastPW = previousSolve.lastPW;
      } else if (previousSolve.lastPW === -1) {
        lastPW = window.app.solveTime(previousSolve);
      } else {
        lastPW = Math.max(window.app.solveTime(previousSolve),
          previousSolve.lastPW);
      }
    }

    for (var i = startIndex, len = solves.length; i < len; ++i) {
      var solve = solves[i];
      solve.lastPW = lastPW;
      if (!solve.dnf) {
        if (lastPW < 0) {
          lastPW = window.app.solveTime(solve);
        } else {
          lastPW = Math.max(lastPW, window.app.solveTime(solve));
        }
      }
    }
  };

  window.app.LocalSolves = LocalSolves;

})();
<commit_msg>use copySolve that I already wrote!<commit_after>(function() {

  // LocalSolves manages solves which the user does offline. It uses
  // LocalPuzzles to get the solves of interest.
  //
  // The puzzles argument must be a LocalPuzzles which has already been loaded
  // with the user's current puzzles.
  //
  // This is an EventEmitter which will emit the following events:
  // - delete: either the deleteSolve or the moveSolve method was used.
  // - modify: the modifySolve method was used.
  // - modifyUnindexed: modifySolveById was used and the ID was not in a cursor.
  // - move: the moveSolve method was used.
  // - loadingStats
  // - computedStats
  function LocalSolves(puzzles) {
    window.app.EventEmitter.call(this);
    this._puzzles = puzzles;
    this._stats = null;
    this._averages = null;
    this._cursors = [];
    this._fillInMissingFields();
    this._computeStats();
  }

  LocalSolves.prototype = Object.create(window.app.EventEmitter.prototype);

  // addSolve pushes a solve to the end of the list.
  LocalSolves.prototype.addSolve = function(solve) {
    var solves = this.getSolves();
    solve.id = window.app.generateId();
    solves.push(solve);

    // All the cursors at the end of the data get the extra solve.
    for (var i = 0, len = this._cursors.length; i < len; ++i) {
      var cursor = this._cursors[i];
      if (cursor._start+cursor._length === this.getSolves().length-1) {
        ++cursor._length;
      }
    }

    recomputeLastPBsAndPWs(solves, solves.length-1);
    if (this._averages !== null) {
      this._averages.pushSolve(solve);
    }
    this._emitStats();
  };

  // createCursor generates a ticket for getting a LocalCursor.
  LocalSolves.prototype.createCursor = function(start, length, cb) {
    return new window.app.LocalCursorTicket(cb, this, start, length);
  };

  // cursorClosed is called by a LocalCursor when it is closed by a consumer.
  LocalSolves.prototype.cursorClosed = function(cursor) {
    var idx = this._cursors.indexOf(cursor);
    if (idx < 0) {
      throw new Error('cursor was not registered');
    }
    this._cursors.splice(idx, 1);
  };

  // cursorCreated is called by each cursor to register itself with the
  // LocalSolves.
  LocalSolves.prototype.cursorCreated = function(cursor) {
    this._cursors.push(cursor);
  };

  // deleteSolve deletes a solve at a given index.
  LocalSolves.prototype.deleteSolve = function(index) {
    var solves = this.getSolves();
    var id = solves[index].id;
    solves.splice(index, 1);

    for (var i = 0, len = this._cursors.length; i < len; ++i) {
      var cursor = this._cursors[i];
      if (index < cursor._start) {
        --cursor._start;
      } else if (index < cursor._start+cursor._length) {
        --cursor._length;
      }
    }

    recomputeLastPBsAndPWs(solves, index);
    this._resetStats();
    this.emit('delete', id, index);
  };

  // getLatestSolve returns the latest solve or null if no solves exist.
  LocalSolves.prototype.getLatestSolve = function() {
    var solves = this.getSolves();
    if (solves.length === 0) {
      return null;
    }
    return solves[solves.length - 1];
  };

  // getSolves returns the current array of solves.
  LocalSolves.prototype.getSolves = function() {
    return this._puzzles.getActivePuzzle().solves;
  };

  // getStats returns the current stats or null if no stats are cached.
  LocalSolves.prototype.getStats = function() {
    return this._stats;
  };

  // modifySolve changes the attributes of a given solve.
  LocalSolves.prototype.modifySolve = function(index, attrs) {
    this._modifySolveNoEmit(index, attrs);
    this.emit('modify', this.getSolves()[index].id, attrs, index);
  };

  // modifySolveById changes the attributes of a solve by looking up its ID.
  // This will emit 'modifyUnindexed' instead of 'modify' if the solve is not
  // within any cursor.
  LocalSolves.prototype.modifySolveById = function(id, attrs) {
    var index = -1;
    var solves = this.getSolves();
    for (var i = 0, len = solves.length; i < len; ++i) {
      if (solves[i].id === id) {
        index = i;
        break;
      }
    }
    if (index < 0) {
      return;
    }
    this._modifySolveNoEmit(index, attrs);

    var containedByCursor = false;
    for (var i = 0, len = this._cursors.length; i < len; ++i) {
      var cursor = this._cursors[i];
      if (index < cursor._start+cursor._length && index >= cursor._start) {
        containedByCursor = true;
        break;
      }
    }

    if (containedByCursor) {
      this.emit('modify', id, attrs, index);
    } else {
      this.emit('modifyUnindexed', id, attrs);
    }
  };

  // moveSolve moves a solve to a different puzzle.
  LocalSolves.prototype.moveSolve = function(index, puzzleId) {
    var puzzle = this._puzzles.findById(puzzleId);
    if (puzzle === null) {
      return;
    }
    var solve = this.getSolves()[index];
    this.deleteSolve(index);
    insertSolveUsingTimestamp(solve, puzzle.solves);
    this.emit('move', solve.id, puzzle);
  };

  // reset should be called whenever the solves were changed in a way that
  // cannot be easily broken down into deletions, additions and modifications.
  LocalSolves.prototype.reset = function() {
    while (this._cursors.length > 0) {
      this._cursors[this._cursors.length-1].close();
    }
    this._resetStats();
  };

  // _computeStats generates this._stats.
  LocalSolves.prototype._computeStats = function() {
    if (this._averages === null) {
      this._averages = new window.app.OfflineAverages();
      var solves = this.getSolves();
      for (var i = 0, len = solves.length; i < len; ++i) {
        this._averages.pushSolve(solves[i]);
      }
    }
    this._stats = this._averages.stats();
  };

  // _emitStats uses this._averages to emit new stats asynchronously.
  // It will create this._averages if it is null.
  LocalSolves.prototype._emitStats = function() {
    this._stats = null;
    this.emit('loadingStats');
    setTimeout(function() {
      if (this._stats === null) {
        this._computeStats();
        this.emit('computedStats', this._stats);
      }
    }.bind(this), 1);
  };

  // _fillInMissingFields fills in missing fields for all solve objects on all
  // puzzles.
  LocalSolves.prototype._fillInMissingFields = function() {
    var puzzles = this._puzzles.getPuzzles();
    for (var i = 0, len = puzzles.length; i < len; ++i) {
      var puzzle = puzzles[i];
      var solves = puzzle.solves;
      recomputeLastPBsAndPWs(solves, 0);
      for (var j = 0, len1 = solves.length; j < len1; ++j) {
        var solve = solves[j];
        if (!solve.scrambler) {
          solve.scrambler = puzzle.scrambler;
          solve.scrambleType = puzzle.scrambleType;
        }
      }
    }
  };

  // _modifySolveNoEmit modifies a solve without emitting a modify event.
  LocalSolves.prototype._modifySolveNoEmit = function(index, attrs) {
    var solve = this.getSolves()[index];
    if ('undefined' === typeof solve) {
      throw new Error('no solve at index: ' + index);
    }

    var newSolve = window.app.copySolve(solve);
    this.getSolves()[index] = newSolve;

    var keys = Object.keys(attrs);
    for (var i = 0, len = keys.length; i < len; ++i) {
      var key = keys[i];
      newSolve[key] = attrs[key];
    }

    // TODO: do not recompute this stuff if neither the time nor the penalties
    // were modified.
    recomputeLastPBsAndPWs(this.getSolves(), index+1);
    this._resetStats();
  };

  // _resetStats deletes all pre-existing knowledge about the stats and
  // recomputes them asynchronously.
  LocalSolves.prototype._resetStats = function() {
    this._averages = null;
    this._emitStats();
  };

  function insertSolveUsingTimestamp(solve, solves) {
    // TODO: use a binary search here.
    for (var i = solves.length-1; i >= 0; --i) {
      if (solves[i].date <= solve.date) {
        solves.splice(i+1, 0, solve);
        recomputeLastPBsAndPWs(solves, i+1);
        return;
      }
    }
    solves.unshift(solve);
    recomputeLastPBsAndPWs(solves, 0);
  }

  function recomputeLastPBsAndPWs(solves, startIndex) {
    recomputeLastPBs(solves, startIndex);
    recomputeLastPWs(solves, startIndex);
  }

  function recomputeLastPBs(solves, startIndex) {
    if (startIndex >= solves.length) {
      return;
    }

    var lastPB = -1;

    if (startIndex > 0) {
      var previousSolve = solves[startIndex - 1];
      if (previousSolve.dnf) {
        lastPB = previousSolve.lastPB;
      } else if (previousSolve.lastPB === -1) {
        lastPB = window.app.solveTime(previousSolve);
      } else {
        lastPB = Math.min(window.app.solveTime(previousSolve),
          previousSolve.lastPB);
      }
    }

    for (var i = startIndex, len = solves.length; i < len; ++i) {
      var solve = solves[i];
      solve.lastPB = lastPB;
      if (!solve.dnf) {
        if (lastPB < 0) {
          lastPB = window.app.solveTime(solve);
        } else {
          lastPB = Math.min(lastPB, window.app.solveTime(solve));
        }
      }
    }
  };

  function recomputeLastPWs(solves, startIndex) {
    if (startIndex >= solves.length) {
      return;
    }

    var lastPW = -1;

    if (startIndex > 0) {
      var previousSolve = solves[startIndex - 1];
      if (previousSolve.dnf) {
        lastPW = previousSolve.lastPW;
      } else if (previousSolve.lastPW === -1) {
        lastPW = window.app.solveTime(previousSolve);
      } else {
        lastPW = Math.max(window.app.solveTime(previousSolve),
          previousSolve.lastPW);
      }
    }

    for (var i = startIndex, len = solves.length; i < len; ++i) {
      var solve = solves[i];
      solve.lastPW = lastPW;
      if (!solve.dnf) {
        if (lastPW < 0) {
          lastPW = window.app.solveTime(solve);
        } else {
          lastPW = Math.max(lastPW, window.app.solveTime(solve));
        }
      }
    }
  };

  window.app.LocalSolves = LocalSolves;

})();
<|endoftext|>


<commit_before>//
var status = require('./status.js')
  , os = require('os')
  , console = status.console()

var total = status.addItem('total', {
  max: 100
})

var err_count = status.addItem("ERR", {
  label: 'errors'
})

var pizza = status.addItem('pizza', {
  label: 'pizza eaten',
  max: 12,
  count: 4,
  precision: 0,
  custom: function () {
    return `${this.count} slices`;
  }
})

status.pattern = '{timestamp} | {total.spinner} {total.percentage}';

console.log("Starting timer for status bar example...")

var it = 500
var times = 0
var runner = function() {
	times++
  if (times % 3 === 0)
    total.inc(5)
  err_count.inc()
  if (times % 2 === 0)
    pizza.inc()
	if(times % 10 === 0) {
		console.log("Logging something arbirtrary", total.count, err_count.count)
	}
  if(times < 15) {
    setTimeout(runner, it)
  } else {
    status.stop()
  }
}

status.start({
  invert: false,
  pattern: '{uptime.green} | {total.label} {spinner.cyan} {total.percentage}  |  {pizza.green.bar} {pizza.custom.magenta}'
})
runner()
<commit_msg>i removed label support<commit_after>//
var status = require('./status.js')
  , os = require('os')
  , console = status.console()

var total = status.addItem('total', {
  max: 100
})

var err_count = status.addItem("ERR", {
  label: 'errors'
})

var pizza = status.addItem('pizza', {
  label: 'pizza eaten',
  max: 12,
  count: 4,
  precision: 0,
  custom: function () {
    return `${this.count} slices`;
  }
})

status.pattern = '{timestamp} | {total.spinner} {total.percentage}';

console.log("Starting timer for status bar example...")

var it = 500
var times = 0
var runner = function() {
	times++
  if (times % 3 === 0)
    total.inc(5)
  err_count.inc()
  if (times % 2 === 0)
    pizza.inc()
	if(times % 10 === 0) {
		console.log("Logging something arbirtrary", total.count, err_count.count)
	}
  if(times < 15) {
    setTimeout(runner, it)
  } else {
    status.stop()
  }
}

status.start({
  invert: false,
  pattern: '{uptime.green} | Total: {spinner.cyan} {total.percentage}  |  {pizza.green.bar} {pizza.custom.magenta}'
})
runner()
<|endoftext|>