<commit_before>
(function ($) {
  "use strict";
  if (typeof JSAV === "undefined") {
    return;
  }

  /*****************************************************************************
   * Add the Array Tree constructor to the public facing JSAV interface.
   ****************************************************************************/
  JSAV.ext.ds.arraytree = function (options) {
    /**
     * Add attributes to options:
     * - Set visibility by default to true.
     * - Set autoresize by default to true.
     *
     * NOTE: These properties will be overridden if the provided 'options'
     * redefine them.
     */
    var ex_options = $.extend(true, {visible: true, autoresize: true}, options);
    // Create new Array Tree object.
    return new ArrayTree(this, ex_options);
  };

  /*****************************************************************************
   * Implement Array Tree data structure.
   ****************************************************************************/

  var ArrayTree = function (jsav, options) {
    this.init(jsav, options);
  };
  JSAV.utils.extend(ArrayTree, JSAV._types.ds.Tree);

  // Get Array Tree prototype
  var arrayTreeProto = ArrayTree.prototype;

  /**
   * Initialize the Array Tree. Creates an empty root node.
   * @param jsav      The JSAV object for this Array Tree.
   * @param options   Options to be passed to the Array Tree structure.
   */
  arrayTreeProto.init = function (jsav, options) {
    this._layoutDone = false; // Set layout as done.
    this.jsav = jsav; // Set the JSAV object for this tree.
    this.options = options; // Set the options for the tree

    /**
     * Generate the element where this tree is going to be placed. The element
     * can either come from the options, which means that it already exists.
     * Otherwise it will be generated now by creating an empty div.
     */
    var el = this.options.element || $("
");
    el.addClass("jsavtree jsavcommontree jsavarraytree");
    // Add all options to the tree element.
    for (var key in this.options) {
      // Get the value for the key.
      var val = this.options[key];
      /**
       * Check if the value is valid:
       * - The options object has this key
       * - The value is a string
       * - The value is a number
       * - The value is a boolean
       */
      if (this.options.hasOwnProperty(key) && typeof(val) === "string" ||
        typeof(val) === "number" || typeof(val) === "boolean") {
        // Add the property to the element as a data attribute.
        el.attr("data-" + key, val);
      }
    }

    /**
     * Add the this tree's element to the DOM only if the element was not
     * specified in the options (because that means it already exists).
     */
    if (!this.options.element) {
      $(this.jsav.canvas).append(el);
    }
    // Set the element for this tree.
    this.element = el;
    // Add auto-resize class if options has property.
    if (this.options.autoresize) {
      this.element.addClass("jsavautoresize");
    }

    // Handles top, left, right, bottom options and positions the given element accordingly
    JSAV.utils._helpers.handlePosition(this);
    // Create an empty node and set it as the root
    this.rootnode = this.newNode(null, null);
    // TODO: Why does the child role need to be declared as root?
    this.rootnode.element.attr("data-child-role", "root");
    // TODO: What is the purpose of the IDs?
    this.element.attr({"data-root": this.rootnode.id(), "id": this.id()});

    // Shows or hides the data structure based on options.visible
    // This is placed after the root node code, since the roots show function
    // will be called if the tree is visible
    JSAV.utils._helpers.handleVisibility(this, this.options);
  };

  /**
    Creates a new Array Tree Node and sets it as the root node. If the newRoot
    parameter is not specified then, the current root node is returned.
    */
  arrayTreeProto.root = function (newRoot, options) {
    var opts = $.extend({hide: true}, options);
    if (typeof newRoot === "undefined") {
      return this.rootnode;
    } else if (newRoot instanceof ArrayTreeNode) {
      var oldroot = this.rootnode;
      this._setrootnode(newRoot, options);
      this.rootnode.edgeToParent(null);
      if (opts.hide && oldroot) { oldroot.hide(); }
    } else {
      if (this.rootnode) {
        this.rootnode.value(newRoot, options);
      } else {
        this._setrootnode(this.newNode(newRoot, null, options), options);
      }
    }
    return this.rootnode;
  };

  /**
   * Creates a new node in the Array Tree.
   * @param value     The value of Array Tree Node. Array of values.
   * @param parent    The Array Tree Node parent of the new Array Tree Node.
   * @param options   Options to be passed to the Array Tree Node.
   * @returns {ArrayTreeNode} The newly create Array Tree Node object.
   */
  arrayTreeProto.newNode = function (value, parent, options) {
    return new ArrayTreeNode(this, value, parent, options);
  };

  arrayTreeProto.moveValue = function (fromNode, fromIndex, toNode, toIndex) {
    // Test if fromNode is of type Array Tree Node
    if (fromNode instanceof ArrayTreeNode) {
      fromNode = fromNode.node_array;
    }
    // Test if toNode is of type Array Tree Node
    if (toNode instanceof ArrayTreeNode) {
      toNode = toNode.node_array;
    }
    this.jsav.effects.moveValue(fromNode, fromIndex, toNode, toIndex);
  };

  // events to register as functions on array
  var events = ["click", "dblclick", "mousedown", "mousemove", "mouseup",
                "mouseenter", "mouseleave"];
  // returns a function for the passed eventType that binds a passed
  // function to that eventType for indices in the array
  var eventhandler = function (eventType) {
    return function (data, handler) {
      // store reference to this, needed when executing the handler
      var self = this;
      // bind a jQuery event handler, limit to .jsavindex
      this.element.on(eventType, ".jsavindex", function (e) {
        // get the node of the clicked element
        var $curr = $(this),
            elem = $curr.data("node"); // get the JSAV node object
        while (!elem) {
          $curr = $curr.parent();
          elem = $curr.data("node");
        }
        // get the index of the clicked element
        var index = elem.node_array.element.find(".jsavindex").index(this);
        // log the event
        self.jsav.logEvent({type: "jsav-arraytree-" + eventType, objid: elem.id(), index: index});
        if ($.isFunction(data)) { // if no custom data..
          // ..bind this to the array and call handler
          // with params array index and the event
          data.call(elem, index, e);
        } else if ($.isFunction(handler)) { // if custom data is passed
          // ..bind this to the array and call handler
          var params = $.isArray(data) ? data.slice(0) : [data]; // get a cloned array or data as array
          params.unshift(index); // add index to first parameter
          params.push(e); // jQuery event as the last
          handler.apply(self, params); // apply the function
        }
      });
      return this;
    };
  };
  // create the event binding functions and add to array prototype
  for (var i = events.length; i--;) {
    arrayTreeProto[events[i]] = eventhandler(events[i]);
  }

  /*****************************************************************************
   * Implement Array Tree Node for the Array Tree data structure.
   ****************************************************************************/

  var ArrayTreeNode = function (container, value, parent, options) {
    this.init(container, value, parent, options);
  };
  JSAV.utils.extend(ArrayTreeNode, JSAV._types.ds.TreeNode);

  // Get Array Tree Node prototype.
  var arrayTreeNodeProto = ArrayTreeNode.prototype;

  /**
   * Initializes a new Array Tree Node.
   * @param container The container where this Array Tree Node will be placed
   * @param value     An array of values for the Array Tree Node.
   * @param parent    The Array Tree Node parent for this Node.
   * @param options   Options to be passed to the Array Tree Node.
   */
  arrayTreeNodeProto.init = function (container, value, parent, options) {
    // Set global variables for array tree node.
    this.jsav = container.jsav;
    this.container = container;
    this.parentnode = parent;

    // Merge options from parent to the provided ones.
    var parent_options = parent ? parent.options: {};
    this.options = $.extend(true, {visible: true}, parent_options, options);

    if (value === null) { // Set default value if none was provided.
      value = [""];
    }

    // Generate element where the Array Tree Node will be placed in.
    this.element = this.options.nodeelement ||
      $("

");

    // Create array for the Array Tree Node and added to the node element.
    this.arrayelement = $(this.element).find("ol");
    var array_options = $.extend(
      {element: this.arrayelement}, this.options);
    this.node_array = new this.jsav.ds.array(value, array_options);

    // Set classes for Array Tree Node element.
    this.element.addClass("jsavnode jsavtreenode jsavarraytreenode");
    // Set ID.
    this.element.attr({"id": this.id()});
    // Save this node in the DOM (used by the click handler)
    this.element.data("node", this);

    // Get/Set parent ID
    if (parent) {
      this.element.attr("data-parent", parent.id());
    }
    if (this.options.autoResize) {
      this.element.addClass("jsavautoresize");
    }

    // Add the Array Tree Node element to the Array Tree container.
    this.container.element.append(this.element);
    this.node_array.layout();

    // Shows or hides the data structure based on options.visible
    // This is placed after the root node code, since the roots show function
    // will be called if the tree is visible
    JSAV.utils._helpers.handleVisibility(this, this.options);

    // Draw edge from this Array Tree Node to parent Node.
    if (parent) {
      // Draw edge from the parent Array Tree Node to this node.
      this._edgetoparent = new ArrayTreeEdge(this.jsav, this, parent);
      // Draw edge label if necessary.
      if (this.options.edgeLabel) {
        this._edgetoparent.label(this.options.edgeLabel);
      }
    }

    // Initialized Array Tree Node children array.
    this.childnodes = [];
  };

  /**
    Child helper function that had to be reimplemented because it has a
    reference to the ArrayTreeNode object.
    */
  var setchildhelper = function (self, pos, node, options) {
    var oldval = self.childnodes[pos],
      opts = $.extend({hide: true}, options);
    if (oldval) {
      if (opts.hide) { oldval.hide(); }
      oldval.parent(null);
    }
    if (node) {
      var newchildnodes = self.childnodes.slice(0);
      newchildnodes[pos] = node;
      if (node.parent() && node.parent() !== self) {
        node.remove({hide: false});
      }
      node.parent(self);
      self._setchildnodes(newchildnodes, opts);
    } else {
      self._setchildnodes($.map(self.childnodes, function (item, index) {
        if (index !== pos) { return item; }
        else { return null; }
      }), opts);
    }
    return self;
  };

  // Create a new array tree node, or gets the child at position 'pos'.
  arrayTreeNodeProto.child = function (pos, node, options) {
    // If no node value was given return existing node.
    if (typeof node === "undefined") {
      return this.childnodes[pos];
    } else {
      if (node !== null && !(node instanceof ArrayTreeNode)) {
        node = this.container.newNode(node, this, options);
      }
      return setchildhelper(this, pos, node, options);
    }
  };

  /**
   *  Gets or sets the values of the array tree node.
   *  - If index is an array, all the values in index are set in their appropriate
   *  index.
   *  - If index is a number, the newValue is set at the specified index.
   *  - If no parameter is passed the values of the array tree node are returned.
   */
  arrayTreeNodeProto.value = function (index, newValue) {
    if (typeof(index) === "undefined") {
      // return a copy of all values in the array
      return this.node_array._values.slice(0);
    } else if ($.isArray(index)) {
      // replace all values in the array with the new array
      // TODO: What if the new array (index) is smaller than node_array???
      // Looks like when the new array (index) is smaller the remaining elements
      // are left untouched. Whereas if it is bigger, the array grows. Does it
      // make sense to leave it as it is?
      for (var i = 0; i < index.length; i ++) {
        this.node_array.value(i, index[i]);
      }
      this.node_array.layout();
      // ...
    } else {
      // call the array's value method
      var result = this.node_array.value(index, newValue);
      this.node_array.layout();
      return result;
    }

    return this;
  };

  // ---------------------------------------------------------------------------
  // Add interface for array methods
  // ---------------------------------------------------------------------------

  arrayTreeNodeProto.isHighlight = function (index, options) {
    this.node_array.isHighlight(index, options);
  };

  arrayTreeNodeProto.highlight = function (indices, options) {
    this.node_array.highlight(indices, options);
  };

  arrayTreeNodeProto.unhighlight = function (indices, options) {
    this.node_array.unhighlight(indices, options);
  };

  arrayTreeNodeProto.css = function (indices, cssprop, options) {
    this.node_array.css(indices, cssprop, options);
  };

  arrayTreeNodeProto.index = function (index) {
    this.node_array.index(index);
  };

  arrayTreeNodeProto.swap = function (index1, index2, options) {
    this.node_array.swap(index1, index2, options);
  };

  // arrayTreeNodeProto. = function() {
  //   this.node_array.();
  // };

  /*****************************************************************************
   * Implement Array Tree Node for the Array Tree data structure.
   ****************************************************************************/

  var ArrayTreeEdge = function (jsav, start, end, options) {
    JSAV._types.ds.Edge.call(this, jsav, start, end, options);
  };
  JSAV.utils.extend(ArrayTreeEdge, JSAV._types.ds.Edge);

  // Get Array Tree Edge prototype
  var edgeProto = ArrayTreeEdge.prototype;

  edgeProto.layout = function (options) {
    var sElem = this.start().element,
        eElem = this.end().element,
        start = (options && options.start) ? options.start : this.start().position(),
        end = (options && options.end) ? options.end : this.end().position(),
        eWidth = eElem.outerWidth(),
        eHeight = eElem.outerHeight(),
        size = this.end().node_array.size(),
        pos = this.end().childnodes.indexOf(this.start()),
        fromPoint = [end.left + pos * eWidth / size, end.top + eHeight];
    var opts = {
      start: end,
      end: start,
      fromPoint: fromPoint
    };
    JSAV._types.ds.Edge.prototype.layout.call(this, opts);
  };

}(jQuery));
<commit_msg>Fixed small edge issue with the array tree.<commit_after>
(function ($) {
  "use strict";
  if (typeof JSAV === "undefined") {
    return;
  }

  /*****************************************************************************
   * Add the Array Tree constructor to the public facing JSAV interface.
   ****************************************************************************/
  JSAV.ext.ds.arraytree = function (options) {
    /**
     * Add attributes to options:
     * - Set visibility by default to true.
     * - Set autoresize by default to true.
     *
     * NOTE: These properties will be overridden if the provided 'options'
     * redefine them.
     */
    var ex_options = $.extend(true, {visible: true, autoresize: true}, options);
    // Create new Array Tree object.
    return new ArrayTree(this, ex_options);
  };

  /*****************************************************************************
   * Implement Array Tree data structure.
   ****************************************************************************/

  var ArrayTree = function (jsav, options) {
    this.init(jsav, options);
  };
  JSAV.utils.extend(ArrayTree, JSAV._types.ds.Tree);

  // Get Array Tree prototype
  var arrayTreeProto = ArrayTree.prototype;

  /**
   * Initialize the Array Tree. Creates an empty root node.
   * @param jsav      The JSAV object for this Array Tree.
   * @param options   Options to be passed to the Array Tree structure.
   */
  arrayTreeProto.init = function (jsav, options) {
    this._layoutDone = false; // Set layout as done.
    this.jsav = jsav; // Set the JSAV object for this tree.
    this.options = options; // Set the options for the tree

    /**
     * Generate the element where this tree is going to be placed. The element
     * can either come from the options, which means that it already exists.
     * Otherwise it will be generated now by creating an empty div.
     */
    var el = this.options.element || $("
");
    el.addClass("jsavtree jsavcommontree jsavarraytree");
    // Add all options to the tree element.
    for (var key in this.options) {
      // Get the value for the key.
      var val = this.options[key];
      /**
       * Check if the value is valid:
       * - The options object has this key
       * - The value is a string
       * - The value is a number
       * - The value is a boolean
       */
      if (this.options.hasOwnProperty(key) && typeof(val) === "string" ||
        typeof(val) === "number" || typeof(val) === "boolean") {
        // Add the property to the element as a data attribute.
        el.attr("data-" + key, val);
      }
    }

    /**
     * Add the this tree's element to the DOM only if the element was not
     * specified in the options (because that means it already exists).
     */
    if (!this.options.element) {
      $(this.jsav.canvas).append(el);
    }
    // Set the element for this tree.
    this.element = el;
    // Add auto-resize class if options has property.
    if (this.options.autoresize) {
      this.element.addClass("jsavautoresize");
    }

    // Handles top, left, right, bottom options and positions the given element accordingly
    JSAV.utils._helpers.handlePosition(this);
    // Create an empty node and set it as the root
    this.rootnode = this.newNode(null, null);
    // TODO: Why does the child role need to be declared as root?
    this.rootnode.element.attr("data-child-role", "root");
    // TODO: What is the purpose of the IDs?
    this.element.attr({"data-root": this.rootnode.id(), "id": this.id()});

    // Shows or hides the data structure based on options.visible
    // This is placed after the root node code, since the roots show function
    // will be called if the tree is visible
    JSAV.utils._helpers.handleVisibility(this, this.options);
  };

  /**
    Creates a new Array Tree Node and sets it as the root node. If the newRoot
    parameter is not specified then, the current root node is returned.
    */
  arrayTreeProto.root = function (newRoot, options) {
    var opts = $.extend({hide: true}, options);
    if (typeof newRoot === "undefined") {
      return this.rootnode;
    } else if (newRoot instanceof ArrayTreeNode) {
      var oldroot = this.rootnode;
      this._setrootnode(newRoot, options);
      this.rootnode.edgeToParent(null);
      if (opts.hide && oldroot) { oldroot.hide(); }
    } else {
      if (this.rootnode) {
        this.rootnode.value(newRoot, options);
      } else {
        this._setrootnode(this.newNode(newRoot, null, options), options);
      }
    }
    return this.rootnode;
  };

  /**
   * Creates a new node in the Array Tree.
   * @param value     The value of Array Tree Node. Array of values.
   * @param parent    The Array Tree Node parent of the new Array Tree Node.
   * @param options   Options to be passed to the Array Tree Node.
   * @returns {ArrayTreeNode} The newly create Array Tree Node object.
   */
  arrayTreeProto.newNode = function (value, parent, options) {
    return new ArrayTreeNode(this, value, parent, options);
  };

  arrayTreeProto.moveValue = function (fromNode, fromIndex, toNode, toIndex) {
    // Test if fromNode is of type Array Tree Node
    if (fromNode instanceof ArrayTreeNode) {
      fromNode = fromNode.node_array;
    }
    // Test if toNode is of type Array Tree Node
    if (toNode instanceof ArrayTreeNode) {
      toNode = toNode.node_array;
    }
    this.jsav.effects.moveValue(fromNode, fromIndex, toNode, toIndex);
  };

  // events to register as functions on array
  var events = ["click", "dblclick", "mousedown", "mousemove", "mouseup",
                "mouseenter", "mouseleave"];
  // returns a function for the passed eventType that binds a passed
  // function to that eventType for indices in the array
  var eventhandler = function (eventType) {
    return function (data, handler) {
      // store reference to this, needed when executing the handler
      var self = this;
      // bind a jQuery event handler, limit to .jsavindex
      this.element.on(eventType, ".jsavindex", function (e) {
        // get the node of the clicked element
        var $curr = $(this),
            elem = $curr.data("node"); // get the JSAV node object
        while (!elem) {
          $curr = $curr.parent();
          elem = $curr.data("node");
        }
        // get the index of the clicked element
        var index = elem.node_array.element.find(".jsavindex").index(this);
        // log the event
        self.jsav.logEvent({type: "jsav-arraytree-" + eventType, objid: elem.id(), index: index});
        if ($.isFunction(data)) { // if no custom data..
          // ..bind this to the array and call handler
          // with params array index and the event
          data.call(elem, index, e);
        } else if ($.isFunction(handler)) { // if custom data is passed
          // ..bind this to the array and call handler
          var params = $.isArray(data) ? data.slice(0) : [data]; // get a cloned array or data as array
          params.unshift(index); // add index to first parameter
          params.push(e); // jQuery event as the last
          handler.apply(self, params); // apply the function
        }
      });
      return this;
    };
  };
  // create the event binding functions and add to array prototype
  for (var i = events.length; i--;) {
    arrayTreeProto[events[i]] = eventhandler(events[i]);
  }

  /*****************************************************************************
   * Implement Array Tree Node for the Array Tree data structure.
   ****************************************************************************/

  var ArrayTreeNode = function (container, value, parent, options) {
    this.init(container, value, parent, options);
  };
  JSAV.utils.extend(ArrayTreeNode, JSAV._types.ds.TreeNode);

  // Get Array Tree Node prototype.
  var arrayTreeNodeProto = ArrayTreeNode.prototype;

  /**
   * Initializes a new Array Tree Node.
   * @param container The container where this Array Tree Node will be placed
   * @param value     An array of values for the Array Tree Node.
   * @param parent    The Array Tree Node parent for this Node.
   * @param options   Options to be passed to the Array Tree Node.
   */
  arrayTreeNodeProto.init = function (container, value, parent, options) {
    // Set global variables for array tree node.
    this.jsav = container.jsav;
    this.container = container;
    this.parentnode = parent;

    // Merge options from parent to the provided ones.
    var parent_options = parent ? parent.options: {};
    this.options = $.extend(true, {visible: true}, parent_options, options);

    if (value === null) { // Set default value if none was provided.
      value = [""];
    }

    // Generate element where the Array Tree Node will be placed in.
    this.element = this.options.nodeelement ||
      $("

");

    // Create array for the Array Tree Node and added to the node element.
    this.arrayelement = $(this.element).find("ol");
    var array_options = $.extend(
      {element: this.arrayelement}, this.options);
    this.node_array = new this.jsav.ds.array(value, array_options);

    // Set classes for Array Tree Node element.
    this.element.addClass("jsavnode jsavtreenode jsavarraytreenode");
    // Set ID.
    this.element.attr({"id": this.id()});
    // Save this node in the DOM (used by the click handler)
    this.element.data("node", this);

    // Get/Set parent ID
    if (parent) {
      this.element.attr("data-parent", parent.id());
    }
    if (this.options.autoResize) {
      this.element.addClass("jsavautoresize");
    }

    // Add the Array Tree Node element to the Array Tree container.
    this.container.element.append(this.element);
    this.node_array.layout();

    // Shows or hides the data structure based on options.visible
    // This is placed after the root node code, since the roots show function
    // will be called if the tree is visible
    JSAV.utils._helpers.handleVisibility(this, this.options);

    // Draw edge from this Array Tree Node to parent Node.
    if (parent) {
      // Draw edge from the parent Array Tree Node to this node.
      this._edgetoparent = new ArrayTreeEdge(this.jsav, this, parent);
      // Draw edge label if necessary.
      if (this.options.edgeLabel) {
        this._edgetoparent.label(this.options.edgeLabel);
      }
    }

    // Initialized Array Tree Node children array.
    this.childnodes = [];
  };

  /**
    Child helper function that had to be reimplemented because it has a
    reference to the ArrayTreeNode object.
    */
  var setchildhelper = function (self, pos, node, options) {
    var oldval = self.childnodes[pos],
      opts = $.extend({hide: true}, options);
    if (oldval) {
      if (opts.hide) { oldval.hide(); }
      oldval.parent(null);
    }
    if (node) {
      var newchildnodes = self.childnodes.slice(0);
      newchildnodes[pos] = node;
      if (node.parent() && node.parent() !== self) {
        node.remove({hide: false});
      }
      node.parent(self);
      self._setchildnodes(newchildnodes, opts);
    } else {
      self._setchildnodes($.map(self.childnodes, function (item, index) {
        if (index !== pos) { return item; }
        else { return null; }
      }), opts);
    }
    return self;
  };

  // Create a new array tree node, or gets the child at position 'pos'.
  arrayTreeNodeProto.child = function (pos, node, options) {
    // If no node value was given return existing node.
    if (typeof node === "undefined") {
      return this.childnodes[pos];
    } else {
      if (node !== null && !(node instanceof ArrayTreeNode)) {
        node = this.container.newNode(node, this, options);
      }
      return setchildhelper(this, pos, node, options);
    }
  };

  /**
   *  Gets or sets the values of the array tree node.
   *  - If index is an array, all the values in index are set in their appropriate
   *  index.
   *  - If index is a number, the newValue is set at the specified index.
   *  - If no parameter is passed the values of the array tree node are returned.
   */
  arrayTreeNodeProto.value = function (index, newValue) {
    if (typeof(index) === "undefined") {
      // return a copy of all values in the array
      return this.node_array._values.slice(0);
    } else if ($.isArray(index)) {
      // replace all values in the array with the new array
      // TODO: What if the new array (index) is smaller than node_array???
      // Looks like when the new array (index) is smaller the remaining elements
      // are left untouched. Whereas if it is bigger, the array grows. Does it
      // make sense to leave it as it is?
      for (var i = 0; i < index.length; i ++) {
        this.node_array.value(i, index[i]);
      }
      this.node_array.layout();
      // ...
    } else {
      // call the array's value method
      var result = this.node_array.value(index, newValue);
      this.node_array.layout();
      return result;
    }

    return this;
  };

  arrayTreeNodeProto.parent = function (newParent, options) {
    if (typeof newParent === "undefined") {
      return this.parentnode;
    } else {
      if (!this._edgetoparent) {
        this._setEdgeToParent(new ArrayTreeEdge(this.jsav, this, newParent, options));
      }
      this._setparent(newParent, options);

      // if both this node and parent are visible but the edge is not, show it
      if (this.isVisible() && newParent && newParent.isVisible() && !this._edgetoparent.isVisible()) {
        this._edgetoparent.show();
      } else if ((!this.isVisible() || !newParent || !newParent.isVisible()) && this._edgetoparent.isVisible()) {
        // if either this node or new parent are invisible but the edge is not,
        // and hide option is either not set ot it's true -> hide the edge to parent
        if (!options || typeof options.hide === "undefined" || options.hide) {
          this._edgetoparent.hide();
        }
      }
      return this;
    }
  };

  // ---------------------------------------------------------------------------
  // Add interface for array methods
  // ---------------------------------------------------------------------------

  arrayTreeNodeProto.isHighlight = function (index, options) {
    this.node_array.isHighlight(index, options);
  };

  arrayTreeNodeProto.highlight = function (indices, options) {
    this.node_array.highlight(indices, options);
  };

  arrayTreeNodeProto.unhighlight = function (indices, options) {
    this.node_array.unhighlight(indices, options);
  };

  arrayTreeNodeProto.css = function (indices, cssprop, options) {
    this.node_array.css(indices, cssprop, options);
  };

  arrayTreeNodeProto.index = function (index) {
    this.node_array.index(index);
  };

  arrayTreeNodeProto.swap = function (index1, index2, options) {
    this.node_array.swap(index1, index2, options);
  };

  // arrayTreeNodeProto. = function() {
  //   this.node_array.();
  // };

  /*****************************************************************************
   * Implement Array Tree Node for the Array Tree data structure.
   ****************************************************************************/

  var ArrayTreeEdge = function (jsav, start, end, options) {
    JSAV._types.ds.Edge.call(this, jsav, start, end, options);
  };
  JSAV.utils.extend(ArrayTreeEdge, JSAV._types.ds.Edge);

  // Get Array Tree Edge prototype
  var edgeProto = ArrayTreeEdge.prototype;

  edgeProto.layout = function (options) {
    var sElem = this.start().element,
        eElem = this.end().element,
        start = (options && options.start) ? options.start : this.start().position(),
        end = (options && options.end) ? options.end : this.end().position(),
        eWidth = eElem.outerWidth(),
        eHeight = eElem.outerHeight(),
        size = this.end().node_array.size(),
        pos = this.end().childnodes.indexOf(this.start()),
        fromPoint = [end.left + pos * eWidth / size, end.top + eHeight];
    var opts = {
      start: end,
      end: start,
      fromPoint: fromPoint
    };
    JSAV._types.ds.Edge.prototype.layout.call(this, opts);
  };

}(jQuery));
<|endoftext|>


<commit_before>// n.b. .layoutPositions() handles all these options for you

const defaults = Object.freeze({
  // animation
  animate: undefined, // whether or not to animate the layout
  animationDuration: undefined, // duration of animation in ms, if enabled
  animationEasing: undefined, // easing of animation, if enabled

  // viewport
  pan: undefined, // pan the graph to the provided position, given as { x, y }
  zoom: undefined, // zoom level as a positive number to set after animation
  fit: undefined, // fit the viewport to the repositioned nodes, overrides pan and zoom

  // modifications
  padding: undefined, // padding around layout
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  spacingFactor: undefined, // a positive value which adjusts spacing between nodes (>1 means greater than usual spacing)

  // layout event callbacks
  ready: function(){}, // on layoutready
  stop: function(){} // on layoutstop
});

class Layout {
  constructor( options ){
    this.options = options;
  }

  run(){
    let layout = this;
    let options = this.options;
    let cy = options.cy;
    let eles = options.eles;
    let nodes = eles.nodes();

    // example positioning algorithm
    let getRandomPos = function( ele, i ){
      return {
        x: Math.round( Math.random() * 100 ),
        y: Math.round( Math.random() * 100 )
      };
    };

    // TODO replace this with your own positioning algorithm
    let getNodePos = getRandomPos;

    // .layoutPositions() automatically handles the layout busywork for you
    nodes.layoutPositions( layout, options, getNodePos );
  }
}

module.exports = Layout;
<commit_msg>Fixes mssing default options assignment #12<commit_after>// n.b. .layoutPositions() handles all these options for you

const assign = require('../assign');

const defaults = Object.freeze({
  // animation
  animate: undefined, // whether or not to animate the layout
  animationDuration: undefined, // duration of animation in ms, if enabled
  animationEasing: undefined, // easing of animation, if enabled

  // viewport
  pan: undefined, // pan the graph to the provided position, given as { x, y }
  zoom: undefined, // zoom level as a positive number to set after animation
  fit: undefined, // fit the viewport to the repositioned nodes, overrides pan and zoom

  // modifications
  padding: undefined, // padding around layout
  boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
  spacingFactor: undefined, // a positive value which adjusts spacing between nodes (>1 means greater than usual spacing)

  // layout event callbacks
  ready: function(){}, // on layoutready
  stop: function(){} // on layoutstop
});

class Layout {
  constructor( options ){
    this.options = assign( {}, defaults, options );
  }

  run(){
    let layout = this;
    let options = this.options;
    let cy = options.cy;
    let eles = options.eles;
    let nodes = eles.nodes();

    // example positioning algorithm
    let getRandomPos = function( ele, i ){
      return {
        x: Math.round( Math.random() * 100 ),
        y: Math.round( Math.random() * 100 )
      };
    };

    // TODO replace this with your own positioning algorithm
    let getNodePos = getRandomPos;

    // .layoutPositions() automatically handles the layout busywork for you
    nodes.layoutPositions( layout, options, getNodePos );
  }
}

module.exports = Layout;
<|endoftext|>


<commit_before>import Vue from 'vue'

import SizeMixin from '../../mixins/size.js'

const
  radius = 50,
  diameter = 2 * radius,
  circumference = diameter * Math.PI,
  strokeDashArray = Math.round(circumference * 1000) / 1000

export default Vue.extend({
  name: 'QCircularProgress',

  mixins: [ SizeMixin ],

  props: {
    value: {
      type: Number,
      default: 0
    },

    min: {
      type: Number,
      default: 0
    },
    max: {
      type: Number,
      default: 100
    },

    color: String,
    centerColor: String,
    trackColor: String,

    fontSize: String,

    // ratio
    thickness: {
      type: Number,
      default: 0.2,
      validator: v => v >= 0 && v <= 1
    },

    angle: {
      type: Number,
      default: 0
    },

    indeterminate: Boolean,
    showValue: Boolean,
    reverse: Boolean,

    instantFeedback: Boolean // used by QKnob, private
  },

  computed: {
    svgStyle () {
      return { transform: `rotate3d(0, 0, 1, ${this.angle - 90}deg)` }
    },

    circleStyle () {
      if (this.instantFeedback !== true && this.indeterminate !== true) {
        return { transition: 'stroke-dashoffset 0.6s ease 0s, stroke 0.6s ease' }
      }
    },

    dir () {
      return (this.$q.lang.rtl === true ? -1 : 1) * (this.reverse ? -1 : 1)
    },

    viewBox () {
      return diameter / (1 - this.thickness / 2)
    },

    viewBoxAttr () {
      return `${this.viewBox / 2} ${this.viewBox / 2} ${this.viewBox} ${this.viewBox}`
    },

    strokeDashOffset () {
      const progress = 1 - (this.value - this.min) / (this.max - this.min)
      return (this.dir * progress) * circumference
    },

    strokeWidth () {
      return this.thickness / 2 * this.viewBox
    }
  },

  methods: {
    __getCircle (h, { thickness, offset, color, cls }) {
      return h('circle', {
        staticClass: 'q-circular-progress__' + cls,
        class: color !== void 0 ? `text-${color}` : null,
        style: this.circleStyle,
        attrs: {
          fill: 'transparent',
          stroke: 'currentColor',
          'stroke-width': thickness,
          'stroke-dasharray': strokeDashArray,
          'stroke-dashoffset': offset,
          cx: this.viewBox,
          cy: this.viewBox,
          r: radius
        }
      })
    }
  },

  render (h) {
    return h('div', {
      staticClass: 'q-circular-progress',
      'class': `q-circular-progress--${this.indeterminate === true ? 'in' : ''}determinate`,
      style: this.sizeStyle,
      on: this.$listeners,
      attrs: {
        'role': 'progressbar',
        'aria-valuemin': this.min,
        'aria-valuemax': this.max,
        'aria-valuenow': this.indeterminate !== true ? this.value : null
      }
    }, [
      h('svg', {
        staticClass: 'q-circular-progress__svg',
        style: this.svgStyle,
        attrs: {
          focusable: 'false' /* needed for IE11 */,
          viewBox: this.viewBoxAttr
        }
      }, [
        this.centerColor !== void 0 && this.centerColor !== 'transparent' ? h('circle', {
          staticClass: 'q-circular-progress__center',
          class: `text-${this.centerColor}`,
          attrs: {
            fill: 'currentColor',
            r: radius - this.strokeWidth / 2,
            cx: this.viewBox,
            cy: this.viewBox
          }
        }) : null,

        this.trackColor !== void 0 && this.trackColor !== 'transparent' ? this.__getCircle(h, {
          cls: 'track',
          thickness: this.strokeWidth,
          offset: 0,
          color: this.trackColor
        }) : null,

        this.__getCircle(h, {
          cls: 'circle',
          thickness: this.strokeWidth,
          offset: this.strokeDashOffset,
          color: this.color
        })
      ]),

      this.showValue === true
        ? h('div', {
          staticClass: 'q-circular-progress__text absolute-full row flex-center content-center',
          style: { fontSize: this.fontSize }
        }, this.$scopedSlots.default !== void 0 ? this.$scopedSlots.default() : [ h('div', [ this.value ]) ])
        : null
    ])
  }
})
<commit_msg>chore(ui): small tweak to QCircularProgress<commit_after>import Vue from 'vue'

import SizeMixin from '../../mixins/size.js'

const
  radius = 50,
  diameter = 2 * radius,
  circumference = diameter * Math.PI,
  strokeDashArray = Math.round(circumference * 1000) / 1000

export default Vue.extend({
  name: 'QCircularProgress',

  mixins: [ SizeMixin ],

  props: {
    value: {
      type: Number,
      default: 0
    },

    min: {
      type: Number,
      default: 0
    },
    max: {
      type: Number,
      default: 100
    },

    color: String,
    centerColor: String,
    trackColor: String,

    fontSize: String,

    // ratio
    thickness: {
      type: Number,
      default: 0.2,
      validator: v => v >= 0 && v <= 1
    },

    angle: {
      type: Number,
      default: 0
    },

    indeterminate: Boolean,
    showValue: Boolean,
    reverse: Boolean,

    instantFeedback: Boolean // used by QKnob, private
  },

  computed: {
    svgStyle () {
      return { transform: `rotate3d(0, 0, 1, ${this.angle - 90}deg)` }
    },

    circleStyle () {
      if (this.instantFeedback !== true && this.indeterminate !== true) {
        return { transition: 'stroke-dashoffset 0.6s ease 0s, stroke 0.6s ease' }
      }
    },

    dir () {
      return (this.$q.lang.rtl === true ? -1 : 1) * (this.reverse ? -1 : 1)
    },

    viewBox () {
      return diameter / (1 - this.thickness / 2)
    },

    viewBoxAttr () {
      return `${this.viewBox / 2} ${this.viewBox / 2} ${this.viewBox} ${this.viewBox}`
    },

    strokeDashOffset () {
      const progress = 1 - (this.value - this.min) / (this.max - this.min)
      return (this.dir * progress) * circumference
    },

    strokeWidth () {
      return this.thickness / 2 * this.viewBox
    }
  },

  methods: {
    __getCircle (h, { thickness, offset, color, cls }) {
      return h('circle', {
        staticClass: 'q-circular-progress__' + cls,
        class: color !== void 0 ? `text-${color}` : null,
        style: this.circleStyle,
        attrs: {
          fill: 'transparent',
          stroke: 'currentColor',
          'stroke-width': thickness,
          'stroke-dasharray': strokeDashArray,
          'stroke-dashoffset': offset,
          cx: this.viewBox,
          cy: this.viewBox,
          r: radius
        }
      })
    }
  },

  render (h) {
    return h('div', {
      staticClass: 'q-circular-progress',
      class: `q-circular-progress--${this.indeterminate === true ? 'in' : ''}determinate`,
      style: this.sizeStyle,
      on: this.$listeners,
      attrs: {
        'role': 'progressbar',
        'aria-valuemin': this.min,
        'aria-valuemax': this.max,
        'aria-valuenow': this.indeterminate !== true ? this.value : null
      }
    }, [
      h('svg', {
        staticClass: 'q-circular-progress__svg',
        style: this.svgStyle,
        attrs: {
          focusable: 'false' /* needed for IE11 */,
          viewBox: this.viewBoxAttr
        }
      }, [
        this.centerColor !== void 0 && this.centerColor !== 'transparent' ? h('circle', {
          staticClass: 'q-circular-progress__center',
          class: `text-${this.centerColor}`,
          attrs: {
            fill: 'currentColor',
            r: radius - this.strokeWidth / 2,
            cx: this.viewBox,
            cy: this.viewBox
          }
        }) : null,

        this.trackColor !== void 0 && this.trackColor !== 'transparent' ? this.__getCircle(h, {
          cls: 'track',
          thickness: this.strokeWidth,
          offset: 0,
          color: this.trackColor
        }) : null,

        this.__getCircle(h, {
          cls: 'circle',
          thickness: this.strokeWidth,
          offset: this.strokeDashOffset,
          color: this.color
        })
      ]),

      this.showValue === true
        ? h('div', {
          staticClass: 'q-circular-progress__text absolute-full row flex-center content-center',
          style: { fontSize: this.fontSize }
        }, this.$scopedSlots.default !== void 0 ? this.$scopedSlots.default() : [ h('div', [ this.value ]) ])
        : null
    ])
  }
})
<|endoftext|>


<commit_before>function range(start, end, step) {
    var arr = [];
    var mystep = arguments.length == 3 ? step : 1;
    var val = start;
    while (true) {
        arr.push(val);
        val += mystep;
        if (start < end && val > end || start > end && val < end)
            break;
    }
    return arr;
}

function sum(arr) {
    var res = 0;
    console.log(arr);
    for (var i = 0; i < arr.length; ++i)
        res += arr[i];
    return res;
}
<commit_msg>Add Chapter 04, exercise 1<commit_after>function range(start, end, step = start < end ? 1 : -1) {
    arr = [];
    if (step > 0) {
        for (n = start; n <= end; n += step) {
            arr.push(n);
        }
    } else if (step < 0) {
        for (n = start; n >= end; n += step) {
            arr.push(n);
            }
    }
    return arr;
}

function sum(arr) {
    s = 0;
    for (let num of arr) {
        s += num;
    }
    return s;
}
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import {
  bemHelper,
  prepareTemplateProps,
  getContainerNode
} from '../../lib/utils.js';
import cx from 'classnames';
import find from 'lodash/find';
import includes from 'lodash/includes';
import autoHideContainerHOC from '../../decorators/autoHideContainer.js';
import headerFooterHOC from '../../decorators/headerFooter.js';
import defaultTemplates from './defaultTemplates.js';
import RefinementListComponent from '../../components/RefinementList/RefinementList.js';

const bem = bemHelper('ais-refinement-list');

/**
 * Instantiate a list of refinements based on a facet
 * @function numericRefinementList
 * @param  {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
 * @param  {string} options.attributeName Name of the attribute for filtering
 * @param  {Object[]} options.options List of all the options
 * @param  {string} options.options[].name Name of the option
 * @param  {number} [options.options[].start] Low bound of the option (>=)
 * @param  {number} [options.options[].end] High bound of the option (<=)
 * @param  {Object} [options.templates] Templates to use for the widget
 * @param  {string|Function} [options.templates.header] Header template
 * @param  {string|Function} [options.templates.item] Item template, provided with `name`, `isRefined`, `url` data properties
 * @param  {string|Function} [options.templates.footer] Footer template
 * @param  {Function} [options.transformData.item] Function to change the object passed to the `item` template
 * @param  {boolean} [options.autoHideContainer=true] Hide the container when no results match
 * @param  {Object} [options.cssClasses] CSS classes to add to the wrapping elements
 * @param  {string|string[]} [options.cssClasses.root] CSS class to add to the root element
 * @param  {string|string[]} [options.cssClasses.header] CSS class to add to the header element
 * @param  {string|string[]} [options.cssClasses.body] CSS class to add to the body element
 * @param  {string|string[]} [options.cssClasses.footer] CSS class to add to the footer element
 * @param  {string|string[]} [options.cssClasses.list] CSS class to add to the list element
 * @param  {string|string[]} [options.cssClasses.label] CSS class to add to each link element
 * @param  {string|string[]} [options.cssClasses.item] CSS class to add to each item element
 * @param  {string|string[]} [options.cssClasses.radio] CSS class to add to each radio element (when using the default template)
 * @param  {string|string[]} [options.cssClasses.active] CSS class to add to each active element
 * @param  {object|boolean} [options.collapsible=false] Hide the widget body and footer when clicking on header
 * @param  {boolean} [options.collapsible.collapsed] Initial collapsed state of a collapsible widget
 * @return {Object}
 */
const usage = `Usage:
numericRefinementList({
  container,
  attributeName,
  options,
  [ cssClasses.{root,header,body,footer,list,item,active,label,checkbox,count} ],
  [ templates.{header,item,footer} ],
  [ transformData.{item} ],
  [ autoHideContainer ],
  [ collapsible=false ]
})`;
function numericRefinementList({
    container,
    attributeName,
    options,
    cssClasses: userCssClasses = {},
    templates = defaultTemplates,
    collapsible = false,
    transformData,
    autoHideContainer = true
  }) {
  if (!container || !attributeName || !options) {
    throw new Error(usage);
  }

  const containerNode = getContainerNode(container);
  let RefinementList = headerFooterHOC(RefinementListComponent);
  if (autoHideContainer === true) {
    RefinementList = autoHideContainerHOC(RefinementList);
  }

  const cssClasses = {
    root: cx(bem(null), userCssClasses.root),
    header: cx(bem('header'), userCssClasses.header),
    body: cx(bem('body'), userCssClasses.body),
    footer: cx(bem('footer'), userCssClasses.footer),
    list: cx(bem('list'), userCssClasses.list),
    item: cx(bem('item'), userCssClasses.item),
    label: cx(bem('label'), userCssClasses.label),
    radio: cx(bem('radio'), userCssClasses.radio),
    active: cx(bem('item', 'active'), userCssClasses.active)
  };

  return {
    init({templatesConfig, helper}) {
      this._templateProps = prepareTemplateProps({
        transformData,
        defaultTemplates,
        templatesConfig,
        templates
      });

      this._toggleRefinement = facetValue => {
        const refinedState = refine(helper.state, attributeName, options, facetValue);
        helper.setState(refinedState).search();
      };
    },
    render({results, state, createURL}) {
      const facetValues = options.map(facetValue =>
        ({
          ...facetValue,
          isRefined: isRefined(state, attributeName, facetValue),
          attributeName
        })
      );

      // Bind createURL to this specific attribute
      function _createURL(facetValue) {
        return createURL(refine(state, attributeName, options, facetValue));
      }

      ReactDOM.render(
        <RefinementList
          collapsible={collapsible}
          createURL={_createURL}
          cssClasses={cssClasses}
          facetValues={facetValues}
          shouldAutoHideContainer={results.nbHits === 0}
          templateProps={this._templateProps}
          toggleRefinement={this._toggleRefinement}
        />,
        containerNode
      );
    }
  };
}

function isRefined(state, attributeName, option) {
  const currentRefinements = state.getNumericRefinements(attributeName);

  if (option.start !== undefined && option.end !== undefined) {
    if (option.start === option.end) {
      return hasNumericRefinement(currentRefinements, '=', option.start);
    }
  }

  if (option.start !== undefined) {
    return hasNumericRefinement(currentRefinements, '>=', option.start);
  }

  if (option.end !== undefined) {
    return hasNumericRefinement(currentRefinements, '<=', option.end);
  }

  if (option.start === undefined && option.end === undefined) {
    return Object.keys(currentRefinements).length === 0;
  }

  return undefined;
}

function refine(state, attributeName, options, facetValue) {
  let resolvedState = state;

  const refinedOption = find(options, {name: facetValue});

  const currentRefinements = resolvedState.getNumericRefinements(attributeName);

  if (refinedOption.start === undefined && refinedOption.end === undefined) {
    return resolvedState.clearRefinements(attributeName);
  }

  if (!isRefined(resolvedState, attributeName, refinedOption)) {
    resolvedState = resolvedState.clearRefinements(attributeName);
  }

  if (refinedOption.start !== undefined && refinedOption.end !== undefined) {
    if (refinedOption.start > refinedOption.end) {
      throw new Error('option.start should be > to option.end');
    }

    if (refinedOption.start === refinedOption.end) {
      if (hasNumericRefinement(currentRefinements, '=', refinedOption.start)) {
        resolvedState = resolvedState.removeNumericRefinement(attributeName, '=', refinedOption.start);
      } else {
        resolvedState = resolvedState.addNumericRefinement(attributeName, '=', refinedOption.start);
      }
      return resolvedState;
    }
  }

  if (refinedOption.start !== undefined) {
    if (hasNumericRefinement(currentRefinements, '>=', refinedOption.start)) {
      resolvedState = resolvedState.removeNumericRefinement(attributeName, '>=', refinedOption.start);
    } else {
      resolvedState = resolvedState.addNumericRefinement(attributeName, '>=', refinedOption.start);
    }
  }

  if (refinedOption.end !== undefined) {
    if (hasNumericRefinement(currentRefinements, '<=', refinedOption.end)) {
      resolvedState = resolvedState.removeNumericRefinement(attributeName, '<=', refinedOption.end);
    } else {
      resolvedState = resolvedState.addNumericRefinement(attributeName, '<=', refinedOption.end);
    }
  }

  return resolvedState;
}

function hasNumericRefinement(currentRefinements, operator, value) {
  const hasOperatorRefinements = currentRefinements[operator] !== undefined;
  const includesValue = includes(currentRefinements[operator], value);

  return hasOperatorRefinements && includesValue;
}

export default numericRefinementList;
<commit_msg>chore(numericRefinementList): checkbox => radio<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import {
  bemHelper,
  prepareTemplateProps,
  getContainerNode
} from '../../lib/utils.js';
import cx from 'classnames';
import find from 'lodash/find';
import includes from 'lodash/includes';
import autoHideContainerHOC from '../../decorators/autoHideContainer.js';
import headerFooterHOC from '../../decorators/headerFooter.js';
import defaultTemplates from './defaultTemplates.js';
import RefinementListComponent from '../../components/RefinementList/RefinementList.js';

const bem = bemHelper('ais-refinement-list');

/**
 * Instantiate a list of refinements based on a facet
 * @function numericRefinementList
 * @param  {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
 * @param  {string} options.attributeName Name of the attribute for filtering
 * @param  {Object[]} options.options List of all the options
 * @param  {string} options.options[].name Name of the option
 * @param  {number} [options.options[].start] Low bound of the option (>=)
 * @param  {number} [options.options[].end] High bound of the option (<=)
 * @param  {Object} [options.templates] Templates to use for the widget
 * @param  {string|Function} [options.templates.header] Header template
 * @param  {string|Function} [options.templates.item] Item template, provided with `name`, `isRefined`, `url` data properties
 * @param  {string|Function} [options.templates.footer] Footer template
 * @param  {Function} [options.transformData.item] Function to change the object passed to the `item` template
 * @param  {boolean} [options.autoHideContainer=true] Hide the container when no results match
 * @param  {Object} [options.cssClasses] CSS classes to add to the wrapping elements
 * @param  {string|string[]} [options.cssClasses.root] CSS class to add to the root element
 * @param  {string|string[]} [options.cssClasses.header] CSS class to add to the header element
 * @param  {string|string[]} [options.cssClasses.body] CSS class to add to the body element
 * @param  {string|string[]} [options.cssClasses.footer] CSS class to add to the footer element
 * @param  {string|string[]} [options.cssClasses.list] CSS class to add to the list element
 * @param  {string|string[]} [options.cssClasses.label] CSS class to add to each link element
 * @param  {string|string[]} [options.cssClasses.item] CSS class to add to each item element
 * @param  {string|string[]} [options.cssClasses.radio] CSS class to add to each radio element (when using the default template)
 * @param  {string|string[]} [options.cssClasses.active] CSS class to add to each active element
 * @param  {object|boolean} [options.collapsible=false] Hide the widget body and footer when clicking on header
 * @param  {boolean} [options.collapsible.collapsed] Initial collapsed state of a collapsible widget
 * @return {Object}
 */
const usage = `Usage:
numericRefinementList({
  container,
  attributeName,
  options,
  [ cssClasses.{root,header,body,footer,list,item,active,label,radio,count} ],
  [ templates.{header,item,footer} ],
  [ transformData.{item} ],
  [ autoHideContainer ],
  [ collapsible=false ]
})`;
function numericRefinementList({
    container,
    attributeName,
    options,
    cssClasses: userCssClasses = {},
    templates = defaultTemplates,
    collapsible = false,
    transformData,
    autoHideContainer = true
  }) {
  if (!container || !attributeName || !options) {
    throw new Error(usage);
  }

  const containerNode = getContainerNode(container);
  let RefinementList = headerFooterHOC(RefinementListComponent);
  if (autoHideContainer === true) {
    RefinementList = autoHideContainerHOC(RefinementList);
  }

  const cssClasses = {
    root: cx(bem(null), userCssClasses.root),
    header: cx(bem('header'), userCssClasses.header),
    body: cx(bem('body'), userCssClasses.body),
    footer: cx(bem('footer'), userCssClasses.footer),
    list: cx(bem('list'), userCssClasses.list),
    item: cx(bem('item'), userCssClasses.item),
    label: cx(bem('label'), userCssClasses.label),
    radio: cx(bem('radio'), userCssClasses.radio),
    active: cx(bem('item', 'active'), userCssClasses.active)
  };

  return {
    init({templatesConfig, helper}) {
      this._templateProps = prepareTemplateProps({
        transformData,
        defaultTemplates,
        templatesConfig,
        templates
      });

      this._toggleRefinement = facetValue => {
        const refinedState = refine(helper.state, attributeName, options, facetValue);
        helper.setState(refinedState).search();
      };
    },
    render({results, state, createURL}) {
      const facetValues = options.map(facetValue =>
        ({
          ...facetValue,
          isRefined: isRefined(state, attributeName, facetValue),
          attributeName
        })
      );

      // Bind createURL to this specific attribute
      function _createURL(facetValue) {
        return createURL(refine(state, attributeName, options, facetValue));
      }

      ReactDOM.render(
        <RefinementList
          collapsible={collapsible}
          createURL={_createURL}
          cssClasses={cssClasses}
          facetValues={facetValues}
          shouldAutoHideContainer={results.nbHits === 0}
          templateProps={this._templateProps}
          toggleRefinement={this._toggleRefinement}
        />,
        containerNode
      );
    }
  };
}

function isRefined(state, attributeName, option) {
  const currentRefinements = state.getNumericRefinements(attributeName);

  if (option.start !== undefined && option.end !== undefined) {
    if (option.start === option.end) {
      return hasNumericRefinement(currentRefinements, '=', option.start);
    }
  }

  if (option.start !== undefined) {
    return hasNumericRefinement(currentRefinements, '>=', option.start);
  }

  if (option.end !== undefined) {
    return hasNumericRefinement(currentRefinements, '<=', option.end);
  }

  if (option.start === undefined && option.end === undefined) {
    return Object.keys(currentRefinements).length === 0;
  }

  return undefined;
}

function refine(state, attributeName, options, facetValue) {
  let resolvedState = state;

  const refinedOption = find(options, {name: facetValue});

  const currentRefinements = resolvedState.getNumericRefinements(attributeName);

  if (refinedOption.start === undefined && refinedOption.end === undefined) {
    return resolvedState.clearRefinements(attributeName);
  }

  if (!isRefined(resolvedState, attributeName, refinedOption)) {
    resolvedState = resolvedState.clearRefinements(attributeName);
  }

  if (refinedOption.start !== undefined && refinedOption.end !== undefined) {
    if (refinedOption.start > refinedOption.end) {
      throw new Error('option.start should be > to option.end');
    }

    if (refinedOption.start === refinedOption.end) {
      if (hasNumericRefinement(currentRefinements, '=', refinedOption.start)) {
        resolvedState = resolvedState.removeNumericRefinement(attributeName, '=', refinedOption.start);
      } else {
        resolvedState = resolvedState.addNumericRefinement(attributeName, '=', refinedOption.start);
      }
      return resolvedState;
    }
  }

  if (refinedOption.start !== undefined) {
    if (hasNumericRefinement(currentRefinements, '>=', refinedOption.start)) {
      resolvedState = resolvedState.removeNumericRefinement(attributeName, '>=', refinedOption.start);
    } else {
      resolvedState = resolvedState.addNumericRefinement(attributeName, '>=', refinedOption.start);
    }
  }

  if (refinedOption.end !== undefined) {
    if (hasNumericRefinement(currentRefinements, '<=', refinedOption.end)) {
      resolvedState = resolvedState.removeNumericRefinement(attributeName, '<=', refinedOption.end);
    } else {
      resolvedState = resolvedState.addNumericRefinement(attributeName, '<=', refinedOption.end);
    }
  }

  return resolvedState;
}

function hasNumericRefinement(currentRefinements, operator, value) {
  const hasOperatorRefinements = currentRefinements[operator] !== undefined;
  const includesValue = includes(currentRefinements[operator], value);

  return hasOperatorRefinements && includesValue;
}

export default numericRefinementList;
<|endoftext|>


<commit_before>/**
 * Parsed resources (snippets, abbreviations, variables, etc.) for Emmet.
 * Contains convenient method to get access for snippets with respect of
 * inheritance. Also provides ability to store data in different vocabularies
 * ('system' and 'user') for fast and safe resource update
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 *
 * XXX This module is over-complicated, should provide better implementation
 *
 * @param {Function} require
 * @param {Underscore} _
 */
emmet.define('resources', function(require, _) {
	var VOC_SYSTEM = 'system';
	var VOC_USER = 'user';

	/** Regular expression for XML tag matching */
	var reTag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/;

	var systemSettings = {};
	var userSettings = {};

	/** @type HandlerList List of registered abbreviation resolvers */
	var resolvers = require('handlerList').create();

	/**
	 * Check if specified resource is parsed by Emmet
	 * @param {Object} obj
	 * @return {Boolean}
	 */
	function isParsed(obj) {
		return obj && obj.__emmet_parsed__;
	}

	/**
	 * Marks object as parsed by Emmet
	 * @param {Object}
	 */
	function setParsed(obj) {
		obj.__emmet_parsed__ = true;
	}

	/**
	 * Returns resource vocabulary by its name
	 * @param {String} name Vocabulary name ('system' or 'user')
	 */
	function getVocabulary(name) {
		return name == VOC_SYSTEM ? systemSettings : userSettings;
	}

	/**
	 * Helper function that transforms string into hash
	 * @return {Object}
	 */
	function stringToHash(str){
		var obj = {}, items = str.split(",");
		for ( var i = 0; i < items.length; i++ )
			obj[ items[i] ] = true;
		return obj;
	}

	/**
	 * Creates resource inheritance chain for lookups
	 * @param {String} vocabulary Resource vocabulary
	 * @param {String} syntax Syntax name
	 * @param {String} name Resource name
	 * @return {Array}
	 */
	function createResourceChain(vocabulary, syntax, name) {
		var voc = getVocabulary(vocabulary),
			result = [],
			resource = null;

		if (voc && syntax in voc) {
			resource = voc[syntax];
			if (name in resource)
				result.push(resource[name]);
		}

		// get inheritance definition
		// in case of user-defined vocabulary, resource dependency
		// may be defined in system vocabulary only, so we have to correctly
		// handle this case
		var chain_source = null;
		if (resource && 'extends' in resource)
			chain_source = resource;
		else if (vocabulary == VOC_USER && syntax in systemSettings
			&& 'extends' in systemSettings[syntax] )
			chain_source = systemSettings[syntax];

		if (chain_source) {
			if (!isParsed(chain_source['extends'])) {
				var ar = chain_source['extends'].split(',');
				var utils = require('utils');
				for (var i = 0; i < ar.length; i++)
					ar[i] = utils.trim(ar[i]);
				chain_source['extends'] = ar;
				setParsed(chain_source['extends']);
			}

			// find resource in ancestors
			for (var i = 0; i < chain_source['extends'].length; i++) {
				var type = chain_source['extends'][i];
				if (voc[type] && voc[type][name])
					result.push(voc[type][name]);
			}
		}

		return result;
	}

	/**
	 * Get resource collection from settings vocbulary for specified syntax.
	 * It follows inheritance chain if resource wasn't directly found in
	 * syntax settings
	 * @param {String} vocabulary Resource vocabulary
	 * @param {String} syntax Syntax name
	 * @param {String} name Resource name
	 */
	function getSubset(vocabulary, syntax, name) {
		var chain = createResourceChain(vocabulary, syntax, name);
		return chain[0];
	}

	/**
	 * Normalizes caret plceholder in passed text: replaces | character with
	 * default caret placeholder
	 * @param {String} text
	 * @returns {String}
	 */
	function normalizeCaretPlaceholder(text) {
		var utils = require('utils');
		return utils.replaceUnescapedSymbol(text, '|', utils.getCaretPlaceholder());
	}

	/**
	 * Returns parsed item located in specified vocabulary by its syntax and
	 * name
	 * @param {String} vocabulary Resource vocabulary
	 * @param {String} syntax Syntax name
	 * @param {String} name Resource name ('abbreviation', 'snippet')
	 * @param {String} item Abbreviation or snippet name
	 * @return {Object}
	 */
	function getParsedItem(vocabulary, syntax, name, item) {
		var chain = createResourceChain(vocabulary, syntax, name);
		var result = null, res;
		var elements = require('elements');

		for (var i = 0, il = chain.length; i < il; i++) {
			res = chain[i];
			if (item in res) {
				if (!isParsed(res[item])) {
					var value = normalizeCaretPlaceholder(res[item]);
					switch(name) {
						case 'abbreviations':
							res[item] = parseAbbreviation(item, value);
							res[item].__ref = value;
							break;
						case 'snippets':
							res[item] = elements.create('snippet', value);
							break;
					}

					setParsed(res[item]);
				}

				result = res[item];
				break;
			}
		}

		return result;
	}

	/**
	 * Parses single abbreviation
	 * @param {String} key Abbreviation name
	 * @param {String} value Abbreviation value
	 * @return {Object}
	 */
	function parseAbbreviation(key, value) {
		key = require('utils').trim(key);
		var elements = require('elements');
		var m;
		if (m = reTag.exec(value)) {
			return elements.create('element', m[1], m[2], m[4] == '/');
		} else {
			// assume it's reference to another abbreviation
			return elements.create('reference', value);
		}
	}

	return {
		/**
		 * Sets new unparsed data for specified settings vocabulary
		 * @param {Object} data
		 * @param {String} type Vocabulary type ('system' or 'user')
		 * @memberOf resources
		 */
		setVocabulary: function(data, type) {
			if (type == VOC_SYSTEM)
				systemSettings = data;
			else
				userSettings = data;
		},

		/**
		 * Get data from specified vocabulary. Can contain parsed entities
		 * @param {String} name Vocabulary type ('system' or 'user')
		 * @return {Object}
		 */
		getVocabulary: getVocabulary,

		/**
		 * Returns resource value from data set with respect of inheritance
		 * @param {String} syntax Resource syntax (html, css, ...)
		 * @param {String} name Resource name ('snippets' or 'abbreviation')
		 * @param {String} item Resource item name
		 * @return {Object}
		 */
		getResource: function(syntax, name, item) {
			return getParsedItem(VOC_USER, syntax, name, item)
				|| getParsedItem(VOC_SYSTEM, syntax, name, item);
		},

		/**
		 * Returns abbreviation value from data set
		 * @param {String} type Resource type (html, css, ...)
		 * @param {String} name Abbreviation name
		 * @return {Object}
		 */
		getAbbreviation: function(type, name) {
			name = name || '';
			return this.getResource(type, 'abbreviations', name)
				|| this.getResource(type, 'abbreviations', name.replace(/\-/g, ':'));
		},

		/**
		 * Returns snippet value from data set
		 * @param {String} type Resource type (html, css, ...)
		 * @param {String} name Snippet name
		 * @return {Object}
		 */
		getSnippet: function(type, name) {
			name = name || '';
			return this.getResource(type, 'snippets', name)
				|| this.getResource(type, 'snippets', name.replace(/\-/g, ':'));
		},

		/**
		 * Returns resource (abbreviation, snippet, etc.) matched for passed
		 * abbreviation
		 * @param {TreeNode} node
		 * @param {String} syntax
		 * @returns {Object}
		 */
		getMatchedResource: function(node, syntax) {
			return resolvers.exec(null, _.toArray(arguments))
				|| this.getAbbreviation(syntax, node.name())
				|| this.getSnippet(syntax, node.name());
		},

		/**
		 * Returns variable value
		 * @return {String}
		 */
		getVariable: function(name) {
			return getSubset(VOC_USER, 'variables', name)
				|| getSubset(VOC_SYSTEM, 'variables', name);
		},

		/**
		 * Store runtime variable in user storage
		 * @param {String} name Variable name
		 * @param {String} value Variable value
		 */
		setVariable: function(name, value){
			var voc = getVocabulary('user') || {};
			if (!('variables' in voc))
				voc.variables = {};

			voc.variables[name] = value;
			this.setVocabulary(voc, 'user');
		},

		/**
		 * Returns resource subset from settings vocabulary
		 * @param {String} syntax Syntax name
		 * @param {String} name Resource name
		 * @return {Object}
		 */
		getSubset: function(syntax, name) {
			return getSubset(VOC_USER, syntax, name)
				|| getSubset(VOC_SYSTEM, syntax, name);
		},

		/**
		 * Check if there are resources for specified syntax
		 * @param {String} syntax
		 * @return {Boolean}
		 */
		hasSyntax: function(syntax) {
			return syntax in getVocabulary(VOC_USER)
				|| syntax in getVocabulary(VOC_SYSTEM);
		},

		/**
		 * Registers new abbreviation resolver.
		 * @param {Function} fn Abbreviation resolver which will receive
		 * abbreviation as first argument and should return parsed abbreviation
		 * object if abbreviation has handled successfully, null
		 * otherwise
		 * @param {Object} options Options list as described in
		 * {@link HandlerList#add()} method
		 */
		addResolver: function(fn, options) {
			resolvers.add(fn, options);
		},

		removeResolver: function(fn) {
			resolvers.remove(fn);
		}
	};
});<commit_msg>Working on new resource manager<commit_after>/**
 * Parsed resources (snippets, abbreviations, variables, etc.) for Emmet.
 * Contains convenient method to get access for snippets with respect of
 * inheritance. Also provides ability to store data in different vocabularies
 * ('system' and 'user') for fast and safe resource update
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 *
 * XXX This module is over-complicated, should provide better implementation
 *
 * @param {Function} require
 * @param {Underscore} _
 */
emmet.define('resources', function(require, _) {
	var VOC_SYSTEM = 'system';
	var VOC_USER = 'user';

	/** Regular expression for XML tag matching */
	var reTag = /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/;

	var systemSettings = {};
	var userSettings = {};

	/** @type HandlerList List of registered abbreviation resolvers */
	var resolvers = require('handlerList').create();

	/**
	 * Check if specified resource is parsed by Emmet
	 * @param {Object} obj
	 * @return {Boolean}
	 */
	function isParsed(obj) {
		return obj && obj.__emmet_parsed__;
	}

	/**
	 * Marks object as parsed by Emmet
	 * @param {Object}
	 */
	function setParsed(obj) {
		obj.__emmet_parsed__ = true;
	}

	/**
	 * Returns resource vocabulary by its name
	 * @param {String} name Vocabulary name ('system' or 'user')
	 */
	function getVocabulary(name) {
		return name == VOC_SYSTEM ? systemSettings : userSettings;
	}

	/**
	 * Helper function that transforms string into hash
	 * @return {Object}
	 */
	function stringToHash(str){
		var obj = {}, items = str.split(",");
		for ( var i = 0; i < items.length; i++ )
			obj[ items[i] ] = true;
		return obj;
	}

	/**
	 * Creates resource inheritance chain for lookups
	 * @param {String} vocabulary Resource vocabulary
	 * @param {String} syntax Syntax name
	 * @param {String} name Resource name
	 * @return {Array}
	 */
	function createResourceChain(vocabulary, syntax, name) {
		var voc = getVocabulary(vocabulary),
			result = [],
			resource = null;

		if (voc && syntax in voc) {
			resource = voc[syntax];
			if (name in resource)
				result.push(resource[name]);
		}

		// get inheritance definition
		// in case of user-defined vocabulary, resource dependency
		// may be defined in system vocabulary only, so we have to correctly
		// handle this case
		var chain_source = null;
		if (resource && 'extends' in resource)
			chain_source = resource;
		else if (vocabulary == VOC_USER && syntax in systemSettings
			&& 'extends' in systemSettings[syntax] )
			chain_source = systemSettings[syntax];

		if (chain_source) {
			if (!isParsed(chain_source['extends'])) {
				var ar = chain_source['extends'].split(',');
				var utils = require('utils');
				for (var i = 0; i < ar.length; i++)
					ar[i] = utils.trim(ar[i]);
				chain_source['extends'] = ar;
				setParsed(chain_source['extends']);
			}

			// find resource in ancestors
			for (var i = 0; i < chain_source['extends'].length; i++) {
				var type = chain_source['extends'][i];
				if (voc[type] && voc[type][name])
					result.push(voc[type][name]);
			}
		}

		return result;
	}

	/**
	 * Get resource collection from settings vocbulary for specified syntax.
	 * It follows inheritance chain if resource wasn't directly found in
	 * syntax settings
	 * @param {String} vocabulary Resource vocabulary
	 * @param {String} syntax Syntax name
	 * @param {String} name Resource name
	 */
	function getSubset(vocabulary, syntax, name) {
		var chain = createResourceChain(vocabulary, syntax, name);
		return chain[0];
	}

	/**
	 * Normalizes caret plceholder in passed text: replaces | character with
	 * default caret placeholder
	 * @param {String} text
	 * @returns {String}
	 */
	function normalizeCaretPlaceholder(text) {
		var utils = require('utils');
		return utils.replaceUnescapedSymbol(text, '|', utils.getCaretPlaceholder());
	}

	/**
	 * Returns parsed item located in specified vocabulary by its syntax and
	 * name
	 * @param {String} vocabulary Resource vocabulary
	 * @param {String} syntax Syntax name
	 * @param {String} name Resource name ('abbreviation', 'snippet')
	 * @param {String} item Abbreviation or snippet name
	 * @return {Object}
	 */
	function getParsedItem(vocabulary, syntax, name, item) {
		var chain = createResourceChain(vocabulary, syntax, name);
		var result = null, res;
		var elements = require('elements');

		for (var i = 0, il = chain.length; i < il; i++) {
			res = chain[i];
			if (item in res) {
				if (!isParsed(res[item])) {
					var value = normalizeCaretPlaceholder(res[item]);
					switch(name) {
						case 'abbreviations':
							res[item] = parseAbbreviation(item, value);
							res[item].__ref = value;
							break;
						case 'snippets':
							res[item] = elements.create('snippet', value);
							break;
					}

					setParsed(res[item]);
				}

				result = res[item];
				break;
			}
		}

		return result;
	}

	function parseItem(name, value, type) {
		var value = normalizeCaretPlaceholder(value);

		if (type == 'snippets') {
			return elements.create('snippet', value);
		}

		if (type == 'abbreviations') {
			return parseAbbreviation(name, value);
		}
	}

	/**
	 * Parses single abbreviation
	 * @param {String} key Abbreviation name
	 * @param {String} value Abbreviation value
	 * @return {Object}
	 */
	function parseAbbreviation(key, value) {
		key = require('utils').trim(key);
		var elements = require('elements');
		var m;
		if (m = reTag.exec(value)) {
			return elements.create('element', m[1], m[2], m[4] == '/');
		} else {
			// assume it's reference to another abbreviation
			return elements.create('reference', value);
		}
	}

	return {
		/**
		 * Sets new unparsed data for specified settings vocabulary
		 * @param {Object} data
		 * @param {String} type Vocabulary type ('system' or 'user')
		 * @memberOf resources
		 */
		setVocabulary: function(data, type) {
			if (type == VOC_SYSTEM)
				systemSettings = data;
			else
				userSettings = data;
		},

		/**
		 * Get data from specified vocabulary. Can contain parsed entities
		 * @param {String} name Vocabulary type ('system' or 'user')
		 * @return {Object}
		 */
		getVocabulary: getVocabulary,

		/**
		 * Returns resource value from data set with respect of inheritance
		 * @param {String} syntax Resource syntax (html, css, ...)
		 * @param {String} name Resource name ('snippets' or 'abbreviation')
		 * @param {String} item Resource item name
		 * @return {Object}
		 */
		getResource: function(syntax, name, item) {
			return getParsedItem(VOC_USER, syntax, name, item)
				|| getParsedItem(VOC_SYSTEM, syntax, name, item);
		},

		/**
		 * Returns abbreviation value from data set
		 * @param {String} type Resource type (html, css, ...)
		 * @param {String} name Abbreviation name
		 * @return {Object}
		 */
		getAbbreviation: function(type, name) {
			name = name || '';
			return this.getResource(type, 'abbreviations', name)
				|| this.getResource(type, 'abbreviations', name.replace(/\-/g, ':'));
		},

		/**
		 * Returns snippet value from data set
		 * @param {String} type Resource type (html, css, ...)
		 * @param {String} name Snippet name
		 * @return {Object}
		 */
		getSnippet: function(type, name) {
			name = name || '';
			return this.getResource(type, 'snippets', name)
				|| this.getResource(type, 'snippets', name.replace(/\-/g, ':'));
		},

		/**
		 * Returns resource (abbreviation, snippet, etc.) matched for passed
		 * abbreviation
		 * @param {TreeNode} node
		 * @param {String} syntax
		 * @returns {Object}
		 */
		getMatchedResource: function(node, syntax) {
			return resolvers.exec(null, _.toArray(arguments))
				|| this.getAbbreviation(syntax, node.name())
				|| this.getSnippet(syntax, node.name());
		},

		/**
		 * Returns variable value
		 * @return {String}
		 */
		getVariable: function(name) {
			return getSubset(VOC_USER, 'variables', name)
				|| getSubset(VOC_SYSTEM, 'variables', name);
		},

		/**
		 * Store runtime variable in user storage
		 * @param {String} name Variable name
		 * @param {String} value Variable value
		 */
		setVariable: function(name, value){
			var voc = getVocabulary('user') || {};
			if (!('variables' in voc))
				voc.variables = {};

			voc.variables[name] = value;
			this.setVocabulary(voc, 'user');
		},

		/**
		 * Returns resource subset from settings vocabulary
		 * @param {String} syntax Syntax name
		 * @param {String} name Resource name
		 * @return {Object}
		 */
		getSubset: function(syntax, name) {
			return getSubset(VOC_USER, syntax, name)
				|| getSubset(VOC_SYSTEM, syntax, name);
		},

		/**
		 * Check if there are resources for specified syntax
		 * @param {String} syntax
		 * @return {Boolean}
		 */
		hasSyntax: function(syntax) {
			return syntax in getVocabulary(VOC_USER)
				|| syntax in getVocabulary(VOC_SYSTEM);
		},

		/**
		 * Registers new abbreviation resolver.
		 * @param {Function} fn Abbreviation resolver which will receive
		 * abbreviation as first argument and should return parsed abbreviation
		 * object if abbreviation has handled successfully, null
		 * otherwise
		 * @param {Object} options Options list as described in
		 * {@link HandlerList#add()} method
		 */
		addResolver: function(fn, options) {
			resolvers.add(fn, options);
		},

		removeResolver: function(fn) {
			resolvers.remove(fn);
		},

		findItem: function(syntax, name, memo) {
			memo = memo || [];

			var data = require('utils').deepMerge({}, systemSettings[syntax], userSettings[syntax]);
			console.log('searching in', data);

			var matchedItem = null;
			_.find(['snippets', 'abbreviations'], function(sectionName) {
				if (sectionName in data && name in data[sectionName]) {
					return matchedItem = parseItem(name, data[sectionName][name], sectionName);
				}
			});

			memo.push(syntax);

			if (!matchedItem && data['extends'] && !_.include(memo, data['extends'])) {
				// try to find item in parent syntax section
				return this.findItem(data['extends'], name, memo);
			}

			return matchedItem;
		}

	};
});<|endoftext|>


<commit_before>import Logs from './logs';

export default class Buffers {
  constructor(rootChannelName) {
    this._buffers = [{name: rootChannelName, current: true, logs: new Logs()}];
  }

  _bufferOps(target) {
    return {
      send: function (nick, text) {
        target.logs.append(nick, text);
        return this;
      }.bind(this)
    }
  }

  to(name) {
    let target = this._buffers.filter(c => (c.name === name))[0];
    if (!target) {
      target = {
        name,
        current: false,
      };
      this._buffers.push(target);
    }

    return this._bufferOps(target);
  }

  current() {
    return this._buffers.filter(c => c.current)[0];
  }

  map(func) {
    return this._buffers.map(func);
  }
}
<commit_msg>Implement Buf.<commit_after>import Logs from './logs';

class Buf { // Buffer exists as a default class
  constructor(name) {
    this.name = name;
    this.logs = new Logs();
    this._current = false;
  }

  current(set) {
    if (typeof set === 'undefined') {
      return this._current;
    } else {
      this._current = set;
    }
  }
}

export default class Buffers {
  constructor(rootBufName) {
    let rootBuf = new Buf(rootBufName);
    rootBuf.current(true);
    this._buffers = [rootBuf];
  }

  _bufferOps(target) {
    return {
      send: function (nick, text) {
        target.logs.append(nick, text);
        return this;
      }.bind(this)
    }
  }

  to(name) {
    let target = this._buffers.filter(c => (c.name === name))[0];
    if (!target) {
      target = new Buf(name);
      this._buffers.push(target);
    }

    return this._bufferOps(target);
  }

  setCurrent(bufName) {
    this._buffers = this._buffers.map(function (buffer) {
      if (buffer.name === bufName) {
        buffer.current(true);
      } else {
        buffer.current(false);
      }
    });
  }

  current() {
    return this._buffers.filter(c => c.current())[0];
  }

  map(func) {
    return this._buffers.map(func);
  }
}
<|endoftext|>


<commit_before>/* -*- js-indent-level: 8 -*-

/*
*	Pong
* 	@author Tapani Jamsa
*	@author Erno Kuusela
*	@author Toni Alatalo
*	Date: 2013
*/

// "use strict";
/* jshint -W097, -W040 */
/* global THREE, THREEx, Ammo, window, Director, DirectorScreens, PlayerArea */
// MAIN

// standard global variables

// var container, scene, renderer, camera;
var sceneGen;

var orbitControls, touchController, stats, gui;
// var SCREEN_WIDTH, SCREEN_HEIGHT, VIEW_ANGLE, ASPECT, NEAR, FAR;
var keyboard = new THREEx.KeyboardState();
var clock = new THREE.Clock();

var playerAmount = 3;
var clientPlayerAmount = 0;
var oldPlayerAmount = playerAmount;

var playerAreas = [];
var collidableMeshList = [];

var playerAreaWidth = 100; // TODO duplicated in playerArea

var borderMaterial;
var gameDirector;
// var ball;

init();
update();

// FUNCTIONS

function init() {
	// SCENE
	sceneGen = new SceneGenerator();

	// DAT GUI
	container = document.getElementById('ThreeJS');
	gui = new dat.GUI();
	/* using "this" here works only by accident? needs non-strict mode */
	gui.add(this, 'playerAmount').min(2).max(100).step(1).listen();
	gui.close();
	gui.domElement.style.position = 'absolute';
	gui.domElement.style.right = '0px';
	// gui.domElement.style.zIndex = 100;
	container.appendChild(gui.domElement);

	// RENDERER
	if (Detector.webgl)
		renderer = new THREE.WebGLRenderer({
			antialias: true
		});
	else
		renderer = new THREE.CanvasRenderer();
	renderer.setSize(window.innerWidth, window.innerHeight);

	container.appendChild(renderer.domElement);

	// EVENTS
	THREEx.WindowResize(renderer, sceneGen.camera);
	THREEx.FullScreen.bindKey({
		charCode: 'm'.charCodeAt(0)
	});

	// CONTROLS
	orbitControls = new THREE.OrbitControls(sceneGen.camera, renderer.domElement);
	orbitControls.userZoom = false;

	// TOUCH
	touchController = new TouchInputController();

	// STATS
	stats = new Stats();
	stats.domElement.style.position = 'absolute';
	stats.domElement.style.bottom = '0px';
	stats.domElement.style.zIndex = 100;
	container.appendChild(stats.domElement);

	gui.add(sceneGen.ball, 'speed').min(0.1).max(400).step(0.1).listen();

	gameDirector = new Director();
	//initNet(clientUpdate);
}

function deleteScene() {
	collidableMeshList = [];

	for (var i = 0; i < playerAreas.length; i++) {
		// ammo.js
		sceneGen.btWorld.removeRigidBody(playerAreas[i].borderBottom.collider);
		sceneGen.btWorld.removeRigidBody(playerAreas[i].borderLeft.collider);
		sceneGen.btWorld.removeRigidBody(playerAreas[i].borderTop.collider);
		sceneGen.btWorld.removeRigidBody(playerAreas[i].racketMesh.collider);

		sceneGen.scene.remove(playerAreas[i].group);
	}

	playerAreas = [];
}

function generateScene() {
	// delete previous scene
	deleteScene();

	playerAmount = Math.round(playerAmount);

	sceneGen.ball.lastCollider = null;

	// ammo.js . Reset positions
	transform = sceneGen.ball.collider.getCenterOfMassTransform();
	transform.setOrigin(new Ammo.btVector3(0, 0, 0));
	sceneGen.ball.collider.setCenterOfMassTransform(transform);

	// Angle in radians
	var radians = Math.PI * 2 / playerAmount;

	var radius = playerAmount; // * radiusTweak;
	var pivotPoint = 9; // 10 Push player area forward by width of the area

	// Two player tweak to remove gaps between player areas
	if (playerAmount === 2) {
		pivotPoint -= 5; // 4.5 , 5,5
	} else if (playerAmount === 3) {
		pivotPoint -= 0.5; //-0,5
	}

	// Calculate player area offset
	var tHypotenuse = radius;
	var tAngle = radians / 2;
	var tAdjacent = Math.cos(tAngle) * tHypotenuse;
	var playerAreaOffset = radius - tAdjacent;

	radius += radius - playerAreaOffset;

	for (var i = 0; i < playerAmount; i++) {
		radians = Math.PI * 2 / playerAmount * (i + 1);
		// var degree = 360 - (radians * (180 / Math.PI));

		var x = Math.cos(radians) * radius * pivotPoint;
		var z = Math.sin(radians) * radius * -1 * pivotPoint;

		var pa = new PlayerArea(sceneGen.btWorld, new THREE.Vector3(x, 0, z), radians, i, touchController);

		playerAreas.push(pa);

		collidableMeshList.push(pa.borderBottom);
		collidableMeshList.push(pa.borderLeft);
		collidableMeshList.push(pa.borderTop);
		collidableMeshList.push(pa.racketMesh);

		sceneGen.scene.add(pa.group);
	}

	// update the camera
	var gameAreaDiameter = (radius * 25 + (playerAreaWidth * 2)); // TODO 25 the magic number
	// console.log("gameAreaDiameter: " + gameAreaDiameter);

	var SCREEN_WIDTH = window.innerWidth;
	var SCREEN_HEIGHT = window.innerHeight;
	var ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT;

	if (SCREEN_HEIGHT < SCREEN_WIDTH) {
		sceneGen.camera.left = ASPECT * -gameAreaDiameter / 2;
		sceneGen.camera.right = ASPECT * gameAreaDiameter / 2;
		sceneGen.camera.top = gameAreaDiameter / 2;
		sceneGen.camera.bottom = -gameAreaDiameter / 2;
	} else {
		sceneGen.camera.left = -gameAreaDiameter / 2;
		sceneGen.camera.right = gameAreaDiameter / 2;
		sceneGen.camera.top = gameAreaDiameter / 2 / ASPECT;
		sceneGen.camera.bottom = -gameAreaDiameter / 2 / ASPECT;
	}

	sceneGen.camera.updateProjectionMatrix();

	// Players info
	refreshPlayersInfo();

	oldPlayerAmount = playerAmount;
}

function refreshPlayersInfo() {
	$("#playersInfo").empty();
	for (var i = 0; i < playerAmount; i++) {

		$("#playersInfo").append("Player" + (i + 1) + "");
		switch (playerAreas[i].player.balls) {
			case 0:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
			case 1:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
			case 2:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
			case 3:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
		}
	}
}

function update() {
	var delta = clock.getDelta(); // seconds

	orbitControls.update();
	stats.update();

	if (netRole === 'client') {
		clientUpdate();
	} else if (netRole === 'server') {
		serverUpdate(delta);
	} else if (netRole === null) {
		offlineUpdate(delta);
	}

	// // Refresh the scene if the player amount has changed
	// var delta = clock.getDelta(); // seconds.

	// if (playerAmount !== oldPlayerAmount) {
	// 	generateScene();
	// }

	// orbitControls.update();
	// sceneGen.camera.up = new THREE.Vector3(0, 1, 0);
	// stats.update();

	// if (netRole === 'client')
	// 	return;

	// // online game mode
	// if (netRole !== null) {
	// 	var racketPositions = [];
	// 	for (var i = 0; i < playerAreas.length; i++) {
	// 		playerAreas[i].serverUpdate(delta, clientKeysPressed ? clientKeysPressed : [], clientTouch ? clientTouch : 0, clientID, i);
	// 		racketPositions.push(playerAreas[i].racketMesh.position);
	// 	}
	// } else {
	// 	// offline game mode
	// 	for (var i = 0; i < playerAreas.length; i++) {
	// 		playerAreas[i].offlineUpdate(collidableMeshList, delta);
	// 	}
	// }

	// // Ammo.js btWorld update
	// sceneGen.btWorldUpdate();

	// sceneGen.ball.update(collidableMeshList, delta);

	// // online game mode
	// if (netRole !== null) {
	// 	serverNetUpdate(racketPositions, sceneGen.ball.sphereMesh.position, delta, playerAmount);
	// }

	requestAnimationFrame(update);
	render();
}

// Update the scene etc.

function clientUpdate() {
	sceneUpdate();
}

function serverUpdate(delta) {

	var racketPositions = [];
	for (var i = 0; i < playerAreas.length; i++) {
		playerAreas[i].serverUpdate(delta, clientKeysPressed ? clientKeysPressed : [], clientTouch ? clientTouch : 0, clientID, i);
		racketPositions.push(playerAreas[i].racketMesh.position);
	}

	sceneUpdate();
	sceneGen.btWorldUpdate(delta);

	serverNetUpdate(racketPositions, sceneGen.ball.sphereMesh.position, delta, playerAmount);
}

function offlineUpdate(delta) {
	for (var i = 0; i < playerAreas.length; i++) {
		playerAreas[i].offlineUpdate(collidableMeshList, delta);
	}

	sceneUpdate();
	sceneGen.btWorldUpdate(delta);
}

function sceneUpdate() {
	if (playerAmount !== oldPlayerAmount) {
		generateScene();
	}
	// sceneGen.camera.up = new THREE.Vector3(0, 1, 0); // What is this?
}

// Callback from the server

function updateClient(msg) {
	// called in client mode (when we're just showing what server tells us).
	if (msg.dt === undefined || msg.ballpos === undefined || msg.racketspos === undefined)
		throw "update msg: missing properties";
	sceneGen.ball.sphereMesh.position = msg.ballpos;
	// ball.update(collidableMeshList, msg.dt);
	for (var i = 0; i < playerAreas.length; i++) {
		playerAreas[i].clientUpdate(msg);
		if (clientPlayerAmount !== playerAmount && playerAreas[i].player.id === ThisPeerID) {
			var worldPos = new THREE.Vector3();
			worldPos.getPositionFromMatrix(playerAreas[i].borderLeft.matrixWorld);
			sceneGen.camera.position.x = worldPos.x;
			sceneGen.camera.position.z = worldPos.z;
			sceneGen.camera.lookAt(playerAreas[i].group.position);

			clientPlayerAmount = playerAmount; // Helps to prevent unnecessary camera position modification
		}
	}

	var inputs = {
		keyboard: readKeyboard(),
		touch: touchController.deltaPosition.x * touchController.swipeSpeed,
	};

	return inputs;
}

function render() {
	renderer.render(sceneGen.scene, sceneGen.camera);
}

function showHelp() {
	gameDirector.setScreen(DirectorScreens.controls);
}

function getRandomColor() {
	return '#' + '000000'.concat(Math.floor(Math.random() * 16777215).toString(16)).substr(-6);
}

function readKeyboard() {
	var pressed = [];

	function checkPressed(keyname) {
		if (keyboard.pressed(keyname))
			pressed.push(keyname);
	}

	checkPressed("left");
	checkPressed("right");
	checkPressed("a");
	checkPressed("d");
	// checkPressed("o");
	// checkPressed("p");

	return pressed;
}<commit_msg>forgot something<commit_after>/* -*- js-indent-level: 8 -*-

/*
*	Pong
* 	@author Tapani Jamsa
*	@author Erno Kuusela
*	@author Toni Alatalo
*	Date: 2013
*/

// "use strict";
/* jshint -W097, -W040 */
/* global THREE, THREEx, Ammo, window, Director, DirectorScreens, PlayerArea */
// MAIN

// standard global variables

// var container, scene, renderer, camera;
var sceneGen;

var orbitControls, touchController, stats, gui;
// var SCREEN_WIDTH, SCREEN_HEIGHT, VIEW_ANGLE, ASPECT, NEAR, FAR;
var keyboard = new THREEx.KeyboardState();
var clock = new THREE.Clock();

var playerAmount = 3;
var clientPlayerAmount = 0;
var oldPlayerAmount = playerAmount;

var playerAreas = [];
var collidableMeshList = [];

var playerAreaWidth = 100; // TODO duplicated in playerArea

var borderMaterial;
var gameDirector;
// var ball;

init();
update();

// FUNCTIONS

function init() {
	// SCENE
	sceneGen = new SceneGenerator();

	// DAT GUI
	container = document.getElementById('ThreeJS');
	gui = new dat.GUI();
	/* using "this" here works only by accident? needs non-strict mode */
	gui.add(this, 'playerAmount').min(2).max(100).step(1).listen();
	gui.close();
	gui.domElement.style.position = 'absolute';
	gui.domElement.style.right = '0px';
	// gui.domElement.style.zIndex = 100;
	container.appendChild(gui.domElement);

	// RENDERER
	if (Detector.webgl)
		renderer = new THREE.WebGLRenderer({
			antialias: true
		});
	else
		renderer = new THREE.CanvasRenderer();
	renderer.setSize(window.innerWidth, window.innerHeight);

	container.appendChild(renderer.domElement);

	// EVENTS
	THREEx.WindowResize(renderer, sceneGen.camera);
	THREEx.FullScreen.bindKey({
		charCode: 'm'.charCodeAt(0)
	});

	// CONTROLS
	orbitControls = new THREE.OrbitControls(sceneGen.camera, renderer.domElement);
	orbitControls.userZoom = false;

	// TOUCH
	touchController = new TouchInputController();

	// STATS
	stats = new Stats();
	stats.domElement.style.position = 'absolute';
	stats.domElement.style.bottom = '0px';
	stats.domElement.style.zIndex = 100;
	container.appendChild(stats.domElement);

	gui.add(sceneGen.ball, 'speed').min(0.1).max(400).step(0.1).listen();

	gameDirector = new Director();
	//initNet(clientUpdate);
}

function deleteScene() {
	collidableMeshList = [];

	for (var i = 0; i < playerAreas.length; i++) {
		// ammo.js
		sceneGen.btWorld.removeRigidBody(playerAreas[i].borderBottom.collider);
		sceneGen.btWorld.removeRigidBody(playerAreas[i].borderLeft.collider);
		sceneGen.btWorld.removeRigidBody(playerAreas[i].borderTop.collider);
		sceneGen.btWorld.removeRigidBody(playerAreas[i].racketMesh.collider);

		sceneGen.scene.remove(playerAreas[i].group);
	}

	playerAreas = [];
}

function generateScene() {
	// delete previous scene
	deleteScene();

	playerAmount = Math.round(playerAmount);

	sceneGen.ball.lastCollider = null;

	// ammo.js . Reset positions
	transform = sceneGen.ball.collider.getCenterOfMassTransform();
	transform.setOrigin(new Ammo.btVector3(0, 0, 0));
	sceneGen.ball.collider.setCenterOfMassTransform(transform);

	// Angle in radians
	var radians = Math.PI * 2 / playerAmount;

	var radius = playerAmount; // * radiusTweak;
	var pivotPoint = 9; // 10 Push player area forward by width of the area

	// Two player tweak to remove gaps between player areas
	if (playerAmount === 2) {
		pivotPoint -= 5; // 4.5 , 5,5
	} else if (playerAmount === 3) {
		pivotPoint -= 0.5; //-0,5
	}

	// Calculate player area offset
	var tHypotenuse = radius;
	var tAngle = radians / 2;
	var tAdjacent = Math.cos(tAngle) * tHypotenuse;
	var playerAreaOffset = radius - tAdjacent;

	radius += radius - playerAreaOffset;

	for (var i = 0; i < playerAmount; i++) {
		radians = Math.PI * 2 / playerAmount * (i + 1);
		// var degree = 360 - (radians * (180 / Math.PI));

		var x = Math.cos(radians) * radius * pivotPoint;
		var z = Math.sin(radians) * radius * -1 * pivotPoint;

		var pa = new PlayerArea(sceneGen.btWorld, new THREE.Vector3(x, 0, z), radians, i, touchController);

		playerAreas.push(pa);

		collidableMeshList.push(pa.borderBottom);
		collidableMeshList.push(pa.borderLeft);
		collidableMeshList.push(pa.borderTop);
		collidableMeshList.push(pa.racketMesh);

		sceneGen.scene.add(pa.group);
	}

	// update the camera
	var gameAreaDiameter = (radius * 25 + (playerAreaWidth * 2)); // TODO 25 the magic number
	// console.log("gameAreaDiameter: " + gameAreaDiameter);

	var SCREEN_WIDTH = window.innerWidth;
	var SCREEN_HEIGHT = window.innerHeight;
	var ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT;

	if (SCREEN_HEIGHT < SCREEN_WIDTH) {
		sceneGen.camera.left = ASPECT * -gameAreaDiameter / 2;
		sceneGen.camera.right = ASPECT * gameAreaDiameter / 2;
		sceneGen.camera.top = gameAreaDiameter / 2;
		sceneGen.camera.bottom = -gameAreaDiameter / 2;
	} else {
		sceneGen.camera.left = -gameAreaDiameter / 2;
		sceneGen.camera.right = gameAreaDiameter / 2;
		sceneGen.camera.top = gameAreaDiameter / 2 / ASPECT;
		sceneGen.camera.bottom = -gameAreaDiameter / 2 / ASPECT;
	}

	sceneGen.camera.updateProjectionMatrix();

	// Players info
	refreshPlayersInfo();

	oldPlayerAmount = playerAmount;
}

function refreshPlayersInfo() {
	$("#playersInfo").empty();
	for (var i = 0; i < playerAmount; i++) {

		$("#playersInfo").append("Player" + (i + 1) + "");
		switch (playerAreas[i].player.balls) {
			case 0:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
			case 1:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
			case 2:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
			case 3:
				$("#playersInfo").append(" ");
				$("#playersInfo").append(" ");
				$("#playersInfo").append("
");
				break;
		}
	}
}

function update() {
	var delta = clock.getDelta(); // seconds

	orbitControls.update();
	stats.update();

	if (netRole === 'client') {
		clientUpdate();
	} else if (netRole === 'server') {
		serverUpdate(delta);
	} else if (netRole === null) {
		offlineUpdate(delta);
	}

	requestAnimationFrame(update);
	render();
}

// Update the scene etc.
function clientUpdate() {
	sceneUpdate();
}

function serverUpdate(delta) {
	var racketPositions = [];
	for (var i = 0; i < playerAreas.length; i++) {
		playerAreas[i].serverUpdate(delta, clientKeysPressed ? clientKeysPressed : [], clientTouch ? clientTouch : 0, clientID, i);
		racketPositions.push(playerAreas[i].racketMesh.position);
	}

	sceneUpdate();
	sceneGen.btWorldUpdate(delta);

	serverNetUpdate(racketPositions, sceneGen.ball.sphereMesh.position, delta, playerAmount);
}

function offlineUpdate(delta) {
	for (var i = 0; i < playerAreas.length; i++) {
		playerAreas[i].offlineUpdate(collidableMeshList, delta);
	}

	sceneUpdate();
	sceneGen.btWorldUpdate(delta);
}

function sceneUpdate() {
	if (playerAmount !== oldPlayerAmount) {
		generateScene();
	}
	// sceneGen.camera.up = new THREE.Vector3(0, 1, 0); // What is this?
}

// Callback from the server

function updateClient(msg) {
	// called in client mode (when we're just showing what server tells us).
	if (msg.dt === undefined || msg.ballpos === undefined || msg.racketspos === undefined)
		throw "update msg: missing properties";
	sceneGen.ball.sphereMesh.position = msg.ballpos;
	// ball.update(collidableMeshList, msg.dt);
	for (var i = 0; i < playerAreas.length; i++) {
		playerAreas[i].clientUpdate(msg);
		if (clientPlayerAmount !== playerAmount && playerAreas[i].player.id === ThisPeerID) {
			var worldPos = new THREE.Vector3();
			worldPos.getPositionFromMatrix(playerAreas[i].borderLeft.matrixWorld);
			sceneGen.camera.position.x = worldPos.x;
			sceneGen.camera.position.z = worldPos.z;
			sceneGen.camera.lookAt(playerAreas[i].group.position);

			clientPlayerAmount = playerAmount; // Helps to prevent unnecessary camera position modification
		}
	}

	var inputs = {
		keyboard: readKeyboard(),
		touch: touchController.deltaPosition.x * touchController.swipeSpeed,
	};

	return inputs;
}

function render() {
	renderer.render(sceneGen.scene, sceneGen.camera);
}

function showHelp() {
	gameDirector.setScreen(DirectorScreens.controls);
}

function getRandomColor() {
	return '#' + '000000'.concat(Math.floor(Math.random() * 16777215).toString(16)).substr(-6);
}

function readKeyboard() {
	var pressed = [];

	function checkPressed(keyname) {
		if (keyboard.pressed(keyname))
			pressed.push(keyname);
	}

	checkPressed("left");
	checkPressed("right");
	checkPressed("a");
	checkPressed("d");
	// checkPressed("o");
	// checkPressed("p");

	return pressed;
}<|endoftext|>