<commit_before>
  basis.require('basis.event');
  basis.require('basis.dom');
  basis.require('basis.dom.event');
  basis.require('basis.cssom');
  basis.require('basis.layout');
  basis.require('basis.l10n');
  basis.require('basis.ui');

 /**
  * @see ./demo/defile/popup.html
  * @namespace basis.ui.popup
  */

  var namespace = this.path;

  //
  // import names
  //

  var Class = basis.Class;
  var DOM = basis.dom;
  var Event = basis.dom.event;
  var cssom = basis.cssom;
  var layout = basis.layout;

  var getter = basis.getter;
  var arrayFrom = basis.array.from;
  var createEvent = basis.event.create;

  var UINode = basis.ui.Node;

  //
  // definitions
  //

  var templates = basis.template.define(namespace, {
    Popup: resource('templates/popup/Popup.tmpl'),
    Balloon: resource('templates/popup/Balloon.tmpl'),
    popupManager: resource('templates/popup/popupManager.tmpl')
  });

  //
  // main part
  //

  var LEFT = 'LEFT';
  var RIGHT = 'RIGHT';
  var TOP = 'TOP';
  var BOTTOM = 'BOTTOM';
  var CENTER = 'CENTER';

  var ORIENTATION = {
    VERTICAL: 'V',
    HORIZONTAL: 'H'
  };

  var ROTATE_MATRIX = String('LTRTRBLBLCCTRCCBCCCCCCCC');

  var LETTER_TO_SIDE = {
    L: LEFT,
    R: RIGHT,
    T: TOP,
    B: BOTTOM,
    C: CENTER
  };

  var FLIP = {
    LEFT: RIGHT,
    RIGHT: LEFT,
    TOP: BOTTOM,
    BOTTOM: TOP,
    CENTER: CENTER
  };

 /**
  * @class
  */
  var Popup = Class(UINode, {
    className: namespace + '.Popup',

    template: templates.Popup,
    binding: {
      visible: {
        events: 'show hide',
        getter: function(node){
          return node.visible ? 'visible' : 'hidden';
        }
      },
      orientation: {
        events: 'layoutChanged',
        getter: function(node){
          return (node.orientation + '-' + node.dir.qw().slice(2, 4).join('-')).toLowerCase();
        }
      }
    },

    action: {
      hide: function(){
        this.hide();
      }
    },

    emit_beforeShow: createEvent('beforeShow'),
    emit_show: createEvent('show'),
    emit_hide: createEvent('hide'),
    emit_realign: createEvent('realign'),
    emit_layoutChanged: createEvent('layoutChanged', 'oldOrientation', 'oldDir'),

    visible: false,
    autorotate: false,

    dir: '',
    defaultDir: [RIGHT, BOTTOM, RIGHT, TOP].join(' '),
    orientation: ORIENTATION.VERTICAL,

    hideOnAnyClick: true,
    hideOnKey: false,
    ignoreClickFor: null,

    init: function(){
      UINode.prototype.init.call(this);

      this.ignoreClickFor = arrayFrom(this.ignoreClickFor);

      if (this.dir)
        this.defaultDir = this.dir.toUpperCase();

      this.setLayout(this.defaultDir, this.orientation);
    },
    templateSync: function(noRecreate){
      UINode.prototype.templateSync.call(this, noRecreate);

      if (this.element)
        this.realign();
    },
    setLayout: function(dir, orientation, noRealign){
      var oldDir = this.dir;
      var oldOrientation = this.orientation;

      if (typeof dir == 'string')
        this.dir = dir.toUpperCase();

      if (typeof orientation == 'string')
        this.orientation = orientation;

      if (oldDir != this.dir || oldOrientation != this.orientation)
      {
        this.emit_layoutChanged(oldOrientation, oldDir);
        if (!noRealign)
          this.realign();
      }
    },
    flip: function(orientation){
      var dir = this.dir.qw();
      var v = orientation == ORIENTATION.VERTICAL;

      dir[0 + v] = FLIP[dir[0 + v]];
      dir[2 + v] = FLIP[dir[2 + v]];

      this.setLayout(dir.join(' '));
    },
    rotate: function(offset){
      var dir = this.dir.qw();
      offset = ((offset % 4) + 4) % 4;

      var result = [];

      if (!offset)
        return dir;

      // point
      var a = dir[0].charAt(0);
      var b = dir[1].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;

      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      // direction
      var a = dir[2].charAt(0);
      var b = dir[3].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      offset = (a != 'C' && b != 'C') && ((dir[0] == dir[2]) != (dir[1] == dir[3])) ? -offset + 4 : offset;
      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;

      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      return result;
    },
    isFitToViewport: function(dir){
      if (this.visible && this.relElement)
      {
        var box = new layout.Box(this.relElement, false, this.element.offsetParent);
        var viewport = new layout.Viewport(this.element.offsetParent);
        var width  = this.element.offsetWidth;
        var height = this.element.offsetHeight;

        dir = String(dir || this.dir).toUpperCase().qw();

        var pointX = dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()];
        var pointY = dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()];

        if (
            (dir[2] != LEFT) * (pointX < (width >> (dir[2] == CENTER)))
            ||
            (dir[2] != RIGHT) * ((viewport.width - pointX) < (width >> (dir[2] == CENTER)))
           )
          return false;

        if (
            (dir[3] != TOP) * (pointY < (height >> (dir[3] == CENTER)))
            ||
            (dir[3] != BOTTOM) * ((viewport.height - pointY) < (height >> (dir[3] == CENTER)))
           )
          return false;

        return {
          x: pointX,
          y: pointY
        };
      }
    },
    setZIndex: function(zIndex){
      this.zIndex = isNaN(zIndex) ? 'auto' : zIndex;
      cssom.setStyle(this.element, {
        'z-index': zIndex
      });
    },
    realign: function(){
      this.setZIndex(this.zIndex);

      if (this.visible && this.relElement)
      {
        var dir = this.dir.qw();
        var point;
        var rotateOffset = 0;
        var curDir = dir;
        var dirH = dir[2];
        var dirV = dir[3];
        var maxRotate = typeof this.autorotate == 'number' || !this.autorotate.length ? 3 : this.autorotate.length;

        while (this.autorotate && rotateOffset <= maxRotate)
        {
          if (point = this.isFitToViewport(curDir.join(' ')))
          {
            dirH = curDir[2];
            dirV = curDir[3];
            this.setLayout(curDir.join(' '), null, true);
            break;
          }

          if (rotateOffset == maxRotate)
            break;

          if (Array.isArray(this.autorotate))
          {
            var rotate = this.autorotate[rotateOffset++];

            if (typeof rotate == 'string')
              curDir = rotate.toUpperCase().split(' ');
            else
              curDir = this.rotate(rotate);
          }
          else
            curDir = this.rotate(++rotateOffset * this.autorotate);
        }

        if (!point)
        {
          var box = new layout.Box(this.relElement, false, this.element.offsetParent);

          point = {
            x: dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()],
            y: dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()]
          };
        }

        var offsetParentBox = new layout.Box(this.element.offsetParent);
        var style = {
          left: 'auto',
          right: 'auto',
          top: 'auto',
          bottom: 'auto'
        };

        switch (dirH)
        {
          case LEFT:
            style.left = point.x + 'px';
            break;
          case CENTER:
            style.left = Math.round(point.x - this.element.offsetWidth/2) + 'px';
            break;
          case RIGHT:
            style.right = (offsetParentBox.width - point.x) + 'px';
            break;
        }

        switch (dirV)
        {
          case TOP:
            style.top = point.y + 'px';
            break;
          case CENTER:
            style.top = Math.round(point.y - this.element.offsetHeight/2) + 'px';
            break;
          case BOTTOM:
            style.bottom = (offsetParentBox.height - point.y) + 'px';
            break;
        }

        cssom.setStyle(this.element, style);

        this.emit_realign();
      }
    },
    show: function(relElement, dir, orientation){
      // assign new offset element
      this.relElement = DOM.get(relElement) || this.relElement;

      // set up direction and orientation
      this.setLayout(dir || this.defaultDir, orientation);

      // if not visible yet, make popup visible
      if (!this.visible)
      {
        // error on relElement no assigned
        if (!this.relElement)
        {
          ;;;basis.dev.warn('Popup#show(): relElement missed');
          return;
        }

        // make element invisible & insert element into DOM
        cssom.visibility(this.element, false);

        popupManager.appendChild(this);

        // dispatch `beforeShow` event, there we can fill popup with content
        this.emit_beforeShow();

        // set visible flag
        this.visible = true;

        // realign position and make it visible
        this.realign();

        cssom.visibility(this.element, true);

        // dispatch `show` event, there we can set focus for elements etc.
        this.emit_show();
      }
      else
        this.realign();
    },
    hide: function(){
      if (this.visible)
      {
        // set visible flag
        this.visible = false;

        if (this.parentNode)
          popupManager.removeChild(this);

        // dispatch event
        this.emit_hide();
      }
    },
    hideAll: function(){
      popupManager.clear();
    },
    destroy: function(){
      this.hide();

      UINode.prototype.destroy.call(this);
    }
  });

 /**
  * @class
  */
  var Balloon = Class(Popup, {
    className: namespace + '.Balloon',

    template: templates.Balloon
  });

  //
  //  Popup manager
  //

  // NOTE: popupManager adds global event handlers dynamically because click event
  // which makes popup visible can also hide it (as click outside of popup).

  var popupManager = new UINode({
    template: templates.popupManager,

    selection: true,

    emit_childNodesModified: function(delta){
      if (delta.deleted)
        for (var i = delta.deleted.length - 1, item; item = delta.deleted[i]; i--)
          item.hide();

      if (delta.inserted && !delta.deleted && this.childNodes.length == delta.inserted.length)
      {
        Event.addGlobalHandler('click', this.hideByClick, this);
        Event.addGlobalHandler('keydown', this.hideByKey, this);
        Event.addGlobalHandler('scroll', this.hideByScroll, this);
        Event.addHandler(window, 'resize', this.realignAll, this);
      }

      if (this.lastChild)
        this.lastChild.select();
      else
      {
        Event.removeGlobalHandler('click', this.hideByClick, this);
        Event.removeGlobalHandler('keydown', this.hideByKey, this);
        Event.removeGlobalHandler('scroll', this.hideByScroll, this);
        Event.removeHandler(window, 'resize', this.realignAll, this);
      }

      UINode.prototype.emit_childNodesModified.call(this, delta);
    },

    insertBefore: function(newChild, refChild){
      if (UINode.prototype.insertBefore.call(this,newChild, refChild))
        newChild.setZIndex(basis.ui.window ? basis.ui.window.getWindowTopZIndex() : 2001);
    },
    removeChild: function(popup){
      if (popup)
      {
        if (popup.hideOnAnyClick && popup.nextSibling)
          this.removeChild(popup.nextSibling);

        UINode.prototype.removeChild.call(this, popup);
      }
    },
    realignAll: function(){
      for (var popup = this.firstChild; popup; popup = popup.nextSibling)
        popup.realign();
    },
    clear: function(){
      if (this.firstChild)
        this.removeChild(this.firstChild);
    },
    hideByClick: function(event){
      var sender = Event.sender(event);
      var ancestorAxis;

      var popups = this.childNodes.filter(getter('hideOnAnyClick')).reverse();

      for (var i = 0, popup; popup = popups[i]; i++)
      {
        if (!ancestorAxis)
          ancestorAxis = DOM.axis(sender, DOM.AXIS_ANCESTOR_OR_SELF);

        if (ancestorAxis.has(popup.element) || ancestorAxis.some(Array.prototype.has, popup.ignoreClickFor))
        {
          this.removeChild(popups[i - 1]);
          return;
        }
      }

      this.removeChild(popups.pop());
      //this.clear();
    },
    hideByKey: function(event){
      var key = Event.key(event);
      var popup = this.lastChild;
      if (popup && popup.hideOnKey)
      {
        var result = false;

        if (typeof popup.hideOnKey == 'function')
          result = popup.hideOnKey(key);
        else
          if (Array.isArray(popup.hideOnKey))
            result = popup.hideOnKey.has(key);

        if (result)
          popup.hide();
      }
    },
    hideByScroll: function(event){
      var sender = Event.sender(event);

      if (DOM.parentOf(sender, this.element))
        return;

      var popup = this.lastChild;
      while (popup)
      {
        var next = popup.previousSibling;
        if (popup.relElement && popup.offsetParent !== sender && DOM.parentOf(sender, popup.relElement))
          popup.hide();
        popup = next;
      }
    }
  });

  basis.ready(function(){
    DOM.insert(document.body, popupManager.element, DOM.INSERT_BEGIN);
    popupManager.realignAll();
  });

  //
  // export names
  //

  module.exports = {
    // const
    ORIENTATION: ORIENTATION,
    DIR: {
      LEFT: LEFT,
      RIGHT: RIGHT,
      TOP: TOP,
      BOTTOM: BOTTOM,
      CENTER: CENTER
    },

    // classes
    Popup: Popup,
    Balloon: Balloon
  };
<commit_msg>make hide on scroll optional<commit_after>
  basis.require('basis.event');
  basis.require('basis.dom');
  basis.require('basis.dom.event');
  basis.require('basis.cssom');
  basis.require('basis.layout');
  basis.require('basis.l10n');
  basis.require('basis.ui');

 /**
  * @see ./demo/defile/popup.html
  * @namespace basis.ui.popup
  */

  var namespace = this.path;

  //
  // import names
  //

  var Class = basis.Class;
  var DOM = basis.dom;
  var Event = basis.dom.event;
  var cssom = basis.cssom;
  var layout = basis.layout;

  var getter = basis.getter;
  var arrayFrom = basis.array.from;
  var createEvent = basis.event.create;

  var UINode = basis.ui.Node;

  //
  // definitions
  //

  var templates = basis.template.define(namespace, {
    Popup: resource('templates/popup/Popup.tmpl'),
    Balloon: resource('templates/popup/Balloon.tmpl'),
    popupManager: resource('templates/popup/popupManager.tmpl')
  });

  //
  // main part
  //

  var LEFT = 'LEFT';
  var RIGHT = 'RIGHT';
  var TOP = 'TOP';
  var BOTTOM = 'BOTTOM';
  var CENTER = 'CENTER';

  var ORIENTATION = {
    VERTICAL: 'V',
    HORIZONTAL: 'H'
  };

  var ROTATE_MATRIX = String('LTRTRBLBLCCTRCCBCCCCCCCC');

  var LETTER_TO_SIDE = {
    L: LEFT,
    R: RIGHT,
    T: TOP,
    B: BOTTOM,
    C: CENTER
  };

  var FLIP = {
    LEFT: RIGHT,
    RIGHT: LEFT,
    TOP: BOTTOM,
    BOTTOM: TOP,
    CENTER: CENTER
  };

 /**
  * @class
  */
  var Popup = Class(UINode, {
    className: namespace + '.Popup',

    template: templates.Popup,
    binding: {
      visible: {
        events: 'show hide',
        getter: function(node){
          return node.visible ? 'visible' : 'hidden';
        }
      },
      orientation: {
        events: 'layoutChanged',
        getter: function(node){
          return (node.orientation + '-' + node.dir.qw().slice(2, 4).join('-')).toLowerCase();
        }
      }
    },

    action: {
      hide: function(){
        this.hide();
      }
    },

    emit_beforeShow: createEvent('beforeShow'),
    emit_show: createEvent('show'),
    emit_hide: createEvent('hide'),
    emit_realign: createEvent('realign'),
    emit_layoutChanged: createEvent('layoutChanged', 'oldOrientation', 'oldDir'),

    visible: false,
    autorotate: false,

    dir: '',
    defaultDir: [RIGHT, BOTTOM, RIGHT, TOP].join(' '),
    orientation: ORIENTATION.VERTICAL,

    hideOnAnyClick: true,
    hideOnKey: false,
    hideOnScroll: false,
    ignoreClickFor: null,

    init: function(){
      UINode.prototype.init.call(this);

      this.ignoreClickFor = arrayFrom(this.ignoreClickFor);

      if (this.dir)
        this.defaultDir = this.dir.toUpperCase();

      this.setLayout(this.defaultDir, this.orientation);
    },
    templateSync: function(noRecreate){
      UINode.prototype.templateSync.call(this, noRecreate);

      if (this.element)
        this.realign();
    },
    setLayout: function(dir, orientation, noRealign){
      var oldDir = this.dir;
      var oldOrientation = this.orientation;

      if (typeof dir == 'string')
        this.dir = dir.toUpperCase();

      if (typeof orientation == 'string')
        this.orientation = orientation;

      if (oldDir != this.dir || oldOrientation != this.orientation)
      {
        this.emit_layoutChanged(oldOrientation, oldDir);
        if (!noRealign)
          this.realign();
      }
    },
    flip: function(orientation){
      var dir = this.dir.qw();
      var v = orientation == ORIENTATION.VERTICAL;

      dir[0 + v] = FLIP[dir[0 + v]];
      dir[2 + v] = FLIP[dir[2 + v]];

      this.setLayout(dir.join(' '));
    },
    rotate: function(offset){
      var dir = this.dir.qw();
      offset = ((offset % 4) + 4) % 4;

      var result = [];

      if (!offset)
        return dir;

      // point
      var a = dir[0].charAt(0);
      var b = dir[1].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;

      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      // direction
      var a = dir[2].charAt(0);
      var b = dir[3].charAt(0);
      var idx = ROTATE_MATRIX.indexOf(a + b) >> 1;

      offset = (a != 'C' && b != 'C') && ((dir[0] == dir[2]) != (dir[1] == dir[3])) ? -offset + 4 : offset;
      var index = ((idx & 0xFC) + (((idx & 0x03) + offset) & 0x03)) << 1;

      result.push(
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index)],
        LETTER_TO_SIDE[ROTATE_MATRIX.charAt(index + 1)]
      );

      return result;
    },
    isFitToViewport: function(dir){
      if (this.visible && this.relElement)
      {
        var box = new layout.Box(this.relElement, false, this.element.offsetParent);
        var viewport = new layout.Viewport(this.element.offsetParent);
        var width  = this.element.offsetWidth;
        var height = this.element.offsetHeight;

        dir = String(dir || this.dir).toUpperCase().qw();

        var pointX = dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()];
        var pointY = dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()];

        if (
            (dir[2] != LEFT) * (pointX < (width >> (dir[2] == CENTER)))
            ||
            (dir[2] != RIGHT) * ((viewport.width - pointX) < (width >> (dir[2] == CENTER)))
           )
          return false;

        if (
            (dir[3] != TOP) * (pointY < (height >> (dir[3] == CENTER)))
            ||
            (dir[3] != BOTTOM) * ((viewport.height - pointY) < (height >> (dir[3] == CENTER)))
           )
          return false;

        return {
          x: pointX,
          y: pointY
        };
      }
    },
    setZIndex: function(zIndex){
      this.zIndex = isNaN(zIndex) ? 'auto' : zIndex;
      cssom.setStyle(this.element, {
        'z-index': zIndex
      });
    },
    realign: function(){
      this.setZIndex(this.zIndex);

      if (this.visible && this.relElement)
      {
        var dir = this.dir.qw();
        var point;
        var rotateOffset = 0;
        var curDir = dir;
        var dirH = dir[2];
        var dirV = dir[3];
        var maxRotate = typeof this.autorotate == 'number' || !this.autorotate.length ? 3 : this.autorotate.length;

        while (this.autorotate && rotateOffset <= maxRotate)
        {
          if (point = this.isFitToViewport(curDir.join(' ')))
          {
            dirH = curDir[2];
            dirV = curDir[3];
            this.setLayout(curDir.join(' '), null, true);
            break;
          }

          if (rotateOffset == maxRotate)
            break;

          if (Array.isArray(this.autorotate))
          {
            var rotate = this.autorotate[rotateOffset++];

            if (typeof rotate == 'string')
              curDir = rotate.toUpperCase().split(' ');
            else
              curDir = this.rotate(rotate);
          }
          else
            curDir = this.rotate(++rotateOffset * this.autorotate);
        }

        if (!point)
        {
          var box = new layout.Box(this.relElement, false, this.element.offsetParent);

          point = {
            x: dir[0] == CENTER ? box.left + (box.width >> 1) : box[dir[0].toLowerCase()],
            y: dir[1] == CENTER ? box.top + (box.height >> 1) : box[dir[1].toLowerCase()]
          };
        }

        var offsetParentBox = new layout.Box(this.element.offsetParent);
        var style = {
          left: 'auto',
          right: 'auto',
          top: 'auto',
          bottom: 'auto'
        };

        switch (dirH)
        {
          case LEFT:
            style.left = point.x + 'px';
            break;
          case CENTER:
            style.left = Math.round(point.x - this.element.offsetWidth/2) + 'px';
            break;
          case RIGHT:
            style.right = (offsetParentBox.width - point.x) + 'px';
            break;
        }

        switch (dirV)
        {
          case TOP:
            style.top = point.y + 'px';
            break;
          case CENTER:
            style.top = Math.round(point.y - this.element.offsetHeight/2) + 'px';
            break;
          case BOTTOM:
            style.bottom = (offsetParentBox.height - point.y) + 'px';
            break;
        }

        cssom.setStyle(this.element, style);

        this.emit_realign();
      }
    },
    show: function(relElement, dir, orientation){
      // assign new offset element
      this.relElement = DOM.get(relElement) || this.relElement;

      // set up direction and orientation
      this.setLayout(dir || this.defaultDir, orientation);

      // if not visible yet, make popup visible
      if (!this.visible)
      {
        // error on relElement no assigned
        if (!this.relElement)
        {
          ;;;basis.dev.warn('Popup#show(): relElement missed');
          return;
        }

        // make element invisible & insert element into DOM
        cssom.visibility(this.element, false);

        popupManager.appendChild(this);

        // dispatch `beforeShow` event, there we can fill popup with content
        this.emit_beforeShow();

        // set visible flag
        this.visible = true;

        // realign position and make it visible
        this.realign();

        cssom.visibility(this.element, true);

        // dispatch `show` event, there we can set focus for elements etc.
        this.emit_show();
      }
      else
        this.realign();
    },
    hide: function(){
      if (this.visible)
      {
        // set visible flag
        this.visible = false;

        if (this.parentNode)
          popupManager.removeChild(this);

        // dispatch event
        this.emit_hide();
      }
    },
    hideAll: function(){
      popupManager.clear();
    },
    destroy: function(){
      this.hide();

      UINode.prototype.destroy.call(this);
    }
  });

 /**
  * @class
  */
  var Balloon = Class(Popup, {
    className: namespace + '.Balloon',

    template: templates.Balloon
  });

  //
  //  Popup manager
  //

  // NOTE: popupManager adds global event handlers dynamically because click event
  // which makes popup visible can also hide it (as click outside of popup).

  var popupManager = new UINode({
    template: templates.popupManager,

    selection: true,

    emit_childNodesModified: function(delta){
      if (delta.deleted)
        for (var i = delta.deleted.length - 1, item; item = delta.deleted[i]; i--)
          item.hide();

      if (delta.inserted && !delta.deleted && this.childNodes.length == delta.inserted.length)
      {
        Event.addGlobalHandler('click', this.hideByClick, this);
        Event.addGlobalHandler('keydown', this.hideByKey, this);
        Event.addGlobalHandler('scroll', this.hideByScroll, this);
        Event.addHandler(window, 'resize', this.realignAll, this);
      }

      if (this.lastChild)
        this.lastChild.select();
      else
      {
        Event.removeGlobalHandler('click', this.hideByClick, this);
        Event.removeGlobalHandler('keydown', this.hideByKey, this);
        Event.removeGlobalHandler('scroll', this.hideByScroll, this);
        Event.removeHandler(window, 'resize', this.realignAll, this);
      }

      UINode.prototype.emit_childNodesModified.call(this, delta);
    },

    insertBefore: function(newChild, refChild){
      if (UINode.prototype.insertBefore.call(this,newChild, refChild))
        newChild.setZIndex(basis.ui.window ? basis.ui.window.getWindowTopZIndex() : 2001);
    },
    removeChild: function(popup){
      if (popup)
      {
        if (popup.hideOnAnyClick && popup.nextSibling)
          this.removeChild(popup.nextSibling);

        UINode.prototype.removeChild.call(this, popup);
      }
    },
    realignAll: function(){
      for (var popup = this.firstChild; popup; popup = popup.nextSibling)
        popup.realign();
    },
    clear: function(){
      if (this.firstChild)
        this.removeChild(this.firstChild);
    },
    hideByClick: function(event){
      var sender = Event.sender(event);
      var ancestorAxis;

      var popups = this.childNodes.filter(getter('hideOnAnyClick')).reverse();

      for (var i = 0, popup; popup = popups[i]; i++)
      {
        if (!ancestorAxis)
          ancestorAxis = DOM.axis(sender, DOM.AXIS_ANCESTOR_OR_SELF);

        if (ancestorAxis.has(popup.element) || ancestorAxis.some(Array.prototype.has, popup.ignoreClickFor))
        {
          this.removeChild(popups[i - 1]);
          return;
        }
      }

      this.removeChild(popups.pop());
      //this.clear();
    },
    hideByKey: function(event){
      var key = Event.key(event);
      var popup = this.lastChild;
      if (popup && popup.hideOnKey)
      {
        var result = false;

        if (typeof popup.hideOnKey == 'function')
          result = popup.hideOnKey(key);
        else
          if (Array.isArray(popup.hideOnKey))
            result = popup.hideOnKey.has(key);

        if (result)
          popup.hide();
      }
    },
    hideByScroll: function(event){
      var sender = Event.sender(event);

      if (DOM.parentOf(sender, this.element))
        return;

      var popup = this.lastChild;
      while (popup)
      {
        var next = popup.previousSibling;
        if (popup.hideOnScroll && popup.relElement && popup.offsetParent !== sender && DOM.parentOf(sender, popup.relElement))
          popup.hide();
        popup = next;
      }
    }
  });

  basis.ready(function(){
    DOM.insert(document.body, popupManager.element, DOM.INSERT_BEGIN);
    popupManager.realignAll();
  });

  //
  // export names
  //

  module.exports = {
    // const
    ORIENTATION: ORIENTATION,
    DIR: {
      LEFT: LEFT,
      RIGHT: RIGHT,
      TOP: TOP,
      BOTTOM: BOTTOM,
      CENTER: CENTER
    },

    // classes
    Popup: Popup,
    Balloon: Balloon
  };
<|endoftext|>


<commit_before>"use strict"

module.exports = ({
  scale,
  threshold,
  cursorX,
  cursorY,
  viewportWidth,
  viewportHeight,
}) => {
  const autoScroll = {x: 0, y: 0}
  const autoScrollRight = viewportWidth - threshold * scale
  const autoScrollDown = viewportHeight - threshold * scale

  if (cursorX < threshold) {
    autoScroll.x--
  }

  if (cursorX >= autoScrollRight) {
    autoScroll.x++
  }

  if (cursorY < threshold) {
    autoScroll.y--
  }

  if (cursorY >= autoScrollDown) {
    autoScroll.y++
  }

  console.log('autoScroll', autoScroll)
  return autoScroll
}<commit_msg>remove debugging<commit_after>"use strict"

module.exports = ({
  scale,
  threshold,
  cursorX,
  cursorY,
  viewportWidth,
  viewportHeight,
}) => {
  const autoScroll = {x: 0, y: 0}
  const autoScrollRight = viewportWidth - threshold * scale
  const autoScrollDown = viewportHeight - threshold * scale

  if (cursorX < threshold) {
    autoScroll.x--
  }

  if (cursorX >= autoScrollRight) {
    autoScroll.x++
  }

  if (cursorY < threshold) {
    autoScroll.y--
  }

  if (cursorY >= autoScrollDown) {
    autoScroll.y++
  }

  return autoScroll
}<|endoftext|>


<commit_before>var levelCounter = 1;

World = require('./public/javascripts/fp/framework/world.js').World;
Level = require('./public/javascripts/fp/framework/level.js').Level;
require('./public/javascripts/fp/framework/agent.js');
require('./public/javascripts/fp/framework/resource.js');
require('./public/javascripts/fp/framework/tile.js');

var Profile = require('./public/javascripts/fp/fp-models/profile.js').Profile;

var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;
var username, password;

FPProvider = function(name, host, port, username, password, callback){
  this.db = new Db(name, new Server(host, port, {auto_reconnect: true}, {}));
  this.db.open(function(err, db){
      // Authenticate
      if (username && password) {
          db.authenticate(username, password, function(error, res) {
            if( error ) callback(error);
            else callback(null, res);
        });
      }
  });
};
FPProvider.prototype.levels = [];

FPProvider.prototype.getCollection= function(callback) {
    this.db.collection('levels', function(error, level_collection) {
      if( error ) callback(error);
      else callback(null, level_collection);
    });
};

FPProvider.prototype.findAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.find().toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findAllByUser = function(user, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
//        level_collection.find({user_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(user._id)}).toArray(function(error, results) {
        level_collection.find({user_id: user._id}).toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.findOne({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(error, result) {
          if( error ) callback(error)
          else callback(null, result)
        });
      }
    });};

FPProvider.prototype.deleteById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
          level_collection.remove({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(err, r) {
            if( error ) callback(error)
            else callback(null, r)
          });
      }
    });};

FPProvider.prototype.updateLevel = function(level, callback) {
    this.getCollection(function(error, level_collection) {
        if( error ) callback(error);
        else {
			console.log("level id: " + level._id)
            if (level._id && typeof(level._id) === 'string')
                level._id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level._id)
	        if (level.user_id && typeof(level.user_id) === 'string')
	                level.user_id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level.user_id)
            level_collection.save(level, {safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};
FPProvider.prototype.loadLevels = function(levels, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        if( typeof(levels.length)=="undefined")
          levels = [levels];

        for( var i =0;i< levels.length;i++ ) {
          level = levels[i];
//          article.created_at = new Date();
//          if( article.comments === undefined ) article.comments = [];
//          for(var j =0;j< article.comments.length; j++) {
//            article.comments[j].created_at = new Date();
//          }
        }

        level_collection.remove({}, function(err, r) {
            level_collection.insert(levels, function() {
              callback(error, levels);
            });
        });
      }
    });
};

FPProvider.prototype.findHighScores = function(callback) {
    this.getCollection(function(error, user_collection) {
      if( error ) callback(error)
      else {
          // TODO: MongoDB order by / limit
          user_collection.find().toArray(function(error, results) {
            if( error ) callback(error)
            else {
                callback(null, results)
            }
          });
      }
    });
};

FPProvider.prototype.deleteAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.remove()
                .toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

/* Lets bootstrap with dummy data */
//new LevelProvider().save([
//    new Level(1),
//    new Level(2),
//    new Level(3)
//], function(error, levels){
//    console.log(error);
//});

FPProvider.prototype.getUserCollection= function(callback) {
    this.db.collection('users', function(error, user_collection) {
      if( error ) callback(error);
      else callback(null, user_collection);
    });
};

FPProvider.prototype.getUserById = function(user_id, callback) {
    this.getUserCollection(function(error, user_collection) {
      if( error ) callback(error);
      else {
          user_collection.findOne({id: user_id}, function(error, result) {
            if( error ) callback(error)
            else if (!result) {
                user_collection.findOne({_id: user_collection.db.bson_serializer.ObjectID.createFromHexString(user_id)}, function(error, result2) {
                    if( error ) callback(error)
                    else callback(null, result2)
                });
            }
            else callback(null, result)
          });
      }
    });
};

FPProvider.prototype.updateUser = function(user, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.save(user,{safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};

FPProvider.prototype.saveUser = function(user, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: user.email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else {
                // Make sure the user has a profile and a nickname
                if (user.profile == undefined) {
                    user.profile = new Profile();
                    user.profile.nickname = user.profile.nickname || user.nickname || user.email;
                }
                user_collection.save(user, function(e, u) {
                  callback(e, u);
                });
              }
            });
        }
    });
};

FPProvider.prototype.saveUsers = function(users, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.insert(users, function(e, u) {
              callback(e, u);
            });
        }
    });
};

FPProvider.prototype.authenticateUser = function(email, password, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else if (result == undefined) {
                // Make sure the user has a profile and a nickname
                  callback("Could not authenticate - no user found with email of " + email)
              }
              else {
                  if (result.password == password) {
                      callback(null, result);
                      return;
                  }
                  else {
                      callback("Could not authenticate - no user found with email of " + email)
                  }
              }
            });
        }
    });
};

exports.FPProvider = FPProvider;<commit_msg>Fixes to level tiles<commit_after>var levelCounter = 1;

World = require('./public/javascripts/fp/framework/world.js').World;
Level = require('./public/javascripts/fp/framework/level.js').Level;
require('./public/javascripts/fp/framework/agent.js');
require('./public/javascripts/fp/framework/resource.js');
require('./public/javascripts/fp/framework/tile.js');

var Profile = require('./public/javascripts/fp/fp-models/profile.js').Profile;

var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;
var username, password;

FPProvider = function(name, host, port, username, password, callback){
  this.db = new Db(name, new Server(host, port, {auto_reconnect: true}, {}));
  this.db.open(function(err, db){
      if (err) {
          console.log(err)
          callback(err);
      }

      // Authenticate
      if (username && password) {
          db.authenticate(username, password, function(error, res) {
            if( error ) callback(error);
            else callback(null, res);
        });
      }
  });
};
FPProvider.prototype.levels = [];

FPProvider.prototype.getCollection= function(callback) {
    this.db.collection('levels', function(error, level_collection) {
      if( error ) callback(error);
      else callback(null, level_collection);
    });
};

FPProvider.prototype.findAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.find().toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findAllByUser = function(user, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
//        level_collection.find({user_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(user._id)}).toArray(function(error, results) {
        level_collection.find({user_id: user._id}).toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.findOne({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(error, result) {
          if( error ) callback(error)
          else callback(null, result)
        });
      }
    });};

FPProvider.prototype.deleteById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
          level_collection.remove({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(err, r) {
            if( error ) callback(error)
            else callback(null, r)
          });
      }
    });};

FPProvider.prototype.updateLevel = function(level, callback) {
    this.getCollection(function(error, level_collection) {
        if( error ) callback(error);
        else {
			console.log("level id: " + level._id)
            if (level._id && typeof(level._id) === 'string')
                level._id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level._id)
	        if (level.user_id && typeof(level.user_id) === 'string')
	                level.user_id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level.user_id)
            level_collection.save(level, {safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};
FPProvider.prototype.loadLevels = function(levels, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        if( typeof(levels.length)=="undefined")
          levels = [levels];

        for( var i =0;i< levels.length;i++ ) {
          level = levels[i];
//          article.created_at = new Date();
//          if( article.comments === undefined ) article.comments = [];
//          for(var j =0;j< article.comments.length; j++) {
//            article.comments[j].created_at = new Date();
//          }
        }

        level_collection.remove({}, function(err, r) {
            level_collection.insert(levels, function() {
              callback(error, levels);
            });
        });
      }
    });
};

FPProvider.prototype.findHighScores = function(callback) {
    this.getCollection(function(error, user_collection) {
      if( error ) callback(error)
      else {
          // TODO: MongoDB order by / limit
          user_collection.find().toArray(function(error, results) {
            if( error ) callback(error)
            else {
                callback(null, results)
            }
          });
      }
    });
};

FPProvider.prototype.deleteAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.remove()
                .toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

/* Lets bootstrap with dummy data */
//new LevelProvider().save([
//    new Level(1),
//    new Level(2),
//    new Level(3)
//], function(error, levels){
//    console.log(error);
//});

FPProvider.prototype.getUserCollection= function(callback) {
    this.db.collection('users', function(error, user_collection) {
      if( error ) callback(error);
      else callback(null, user_collection);
    });
};

FPProvider.prototype.getUserById = function(user_id, callback) {
    this.getUserCollection(function(error, user_collection) {
      if( error ) callback(error);
      else {
          user_collection.findOne({id: user_id}, function(error, result) {
            if( error ) callback(error)
            else if (!result) {
                user_collection.findOne({_id: user_collection.db.bson_serializer.ObjectID.createFromHexString(user_id)}, function(error, result2) {
                    if( error ) callback(error)
                    else callback(null, result2)
                });
            }
            else callback(null, result)
          });
      }
    });
};

FPProvider.prototype.updateUser = function(user, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.save(user,{safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};

FPProvider.prototype.saveUser = function(user, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: user.email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else {
                // Make sure the user has a profile and a nickname
                if (user.profile == undefined) {
                    user.profile = new Profile();
                    user.profile.nickname = user.profile.nickname || user.nickname || user.email;
                }
                user_collection.save(user, function(e, u) {
                  callback(e, u);
                });
              }
            });
        }
    });
};

FPProvider.prototype.saveUsers = function(users, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.insert(users, function(e, u) {
              callback(e, u);
            });
        }
    });
};

FPProvider.prototype.authenticateUser = function(email, password, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else if (result == undefined) {
                // Make sure the user has a profile and a nickname
                  callback("Could not authenticate - no user found with email of " + email)
              }
              else {
                  if (result.password == password) {
                      callback(null, result);
                      return;
                  }
                  else {
                      callback("Could not authenticate - no user found with email of " + email)
                  }
              }
            });
        }
    });
};

exports.FPProvider = FPProvider;<|endoftext|>


<commit_before>var levelCounter = 1;

World = require('./public/javascripts/fp/core/world.js').World;
Level = require('./public/javascripts/fp/core/level.js').Level;
require('./public/javascripts/fp/core/agent/agent.js');
require('./public/javascripts/fp/core/resource.js');
require('./public/javascripts/fp/core/tile.js');

var Profile = require('./public/javascripts/fp/profile/profile.js').Profile;

var Db = require('mongodb').Db,
    connect = require('mongodb').connect;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;
//var mongo = require('mongoskin');
var username, password;

//FPProvider = function(name, host, port, u, p, callback){
FPProvider = function(url, callback){
//  this.db = new Db(name, new Server(host, parseInt(port), {auto_reconnect: true}, {}));
//    username = u, password = p;
    var that = this;
    this.db = connect(url, {noOpen: true}, function(err, db) {
//    this.db = mongo.db(url);
//    this.db.open(function(err, db){
        this.db = db;
        that.db = db;
      if (err) {
          console.log(err)
          callback(err);
      }
       else {
          callback(null, db);
      }

      // Authenticate
        /*
      if (username && password) {
          db.authenticate(username, password, function(error, res) {
            if( error ) callback(error);
            else callback(null, res);
        });
      }
      */
  });
};
FPProvider.prototype.levels = [];

FPProvider.prototype.getCollection= function(callback) {
    this.db.collection('levels', function(error, level_collection) {
      if( error ) callback(error);
      else callback(null, level_collection);
    });
};

FPProvider.prototype.findAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.find().toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findAllByUser = function(user, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
//        level_collection.find({user_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(user._id)}).toArray(function(error, results) {
        level_collection.find({user_id: user._id}).toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.findOne({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(error, result) {
          if( error ) callback(error)
          else callback(null, result)
        });
      }
    });};

FPProvider.prototype.deleteById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
          level_collection.remove({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(err, r) {
            if( error ) callback(error)
            else callback(null, r)
          });
      }
    });};

FPProvider.prototype.updateLevel = function(level, callback) {
    this.getCollection(function(error, level_collection) {
        if( error ) callback(error);
        else {
			console.log("level id: " + level._id)
            if (level._id && typeof(level._id) === 'string')
                level._id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level._id)
	        if (level.user_id && typeof(level.user_id) === 'string')
	                level.user_id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level.user_id)
            level_collection.save(level, {safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};
FPProvider.prototype.loadLevels = function(levels, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        if( typeof(levels.length)=="undefined")
          levels = [levels];

        for( var i =0;i< levels.length;i++ ) {
          level = levels[i];
//          article.created_at = new Date();
//          if( article.comments === undefined ) article.comments = [];
//          for(var j =0;j< article.comments.length; j++) {
//            article.comments[j].created_at = new Date();
//          }
        }

        level_collection.remove({}, function(err, r) {
            level_collection.insert(levels, function() {
              callback(error, levels);
            });
        });
      }
    });
};

FPProvider.prototype.findHighScores = function(callback) {
    this.getCollection(function(error, user_collection) {
      if( error ) callback(error)
      else {
          // TODO: MongoDB order by / limit
          user_collection.find().toArray(function(error, results) {
            if( error ) callback(error)
            else {
                callback(null, results)
            }
          });
      }
    });
};

FPProvider.prototype.deleteAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.remove()
                .toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

/* Lets bootstrap with dummy data */
//new LevelProvider().save([
//    new Level(1),
//    new Level(2),
//    new Level(3)
//], function(error, levels){
//    console.log(error);
//});

FPProvider.prototype.getUserCollection= function(callback) {
    this.db.collection('users', function(error, user_collection) {
      if( error ) callback(error);
      else callback(null, user_collection);
    });
};

FPProvider.prototype.getUserById = function(user_id, callback) {
    this.getUserCollection(function(error, user_collection) {
      if( error ) callback(error);
      else {
          user_collection.findOne({id: user_id}, function(error, result) {
            if( error ) callback(error)
            else if (!result) {
                user_collection.findOne({_id: user_collection.db.bson_serializer.ObjectID.createFromHexString(user_id)}, function(error, result2) {
                    if( error ) callback(error)
                    else callback(null, result2)
                });
            }
            else callback(null, result)
          });
      }
    });
};

FPProvider.prototype.updateUser = function(user, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.save(user,{safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};

FPProvider.prototype.saveUser = function(user, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: user.email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else {
                // Make sure the user has a profile and a nickname
                if (user.profile == undefined) {
                    user.profile = new Profile();
                    user.profile.nickname = user.profile.nickname || user.nickname || user.email;
                }
                user_collection.save(user, function(e, u) {
                  callback(e, u);
                });
              }
            });
        }
    });
};

FPProvider.prototype.saveUsers = function(users, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.insert(users, function(e, u) {
              callback(e, u);
            });
        }
    });
};

FPProvider.prototype.authenticateUser = function(email, password, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else if (result == undefined) {
                // Make sure the user has a profile and a nickname
                  callback("Could not authenticate - no user found with email of " + email)
              }
              else {
                  if (result.password == password) {
                      callback(null, result);
                      return;
                  }
                  else {
                      callback("Could not authenticate - no user found with email of " + email)
                  }
              }
            });
        }
    });
};

exports.FPProvider = FPProvider;<commit_msg>heroku fix<commit_after>var levelCounter = 1;

World = require('./public/javascripts/fp/core/world.js').World;
Level = require('./public/javascripts/fp/core/level.js').Level;
require('./public/javascripts/fp/core/agent/agent.js');
require('./public/javascripts/fp/core/resource.js');
require('./public/javascripts/fp/core/tile.js');

var Profile = require('./public/javascripts/fp/profile/profile.js').Profile;

var Db = require('mongodb').Db,
    connect = require('mongodb').connect;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;
//var mongo = require('mongoskin');
var username, password;

//FPProvider = function(name, host, port, u, p, callback){
FPProvider = function(url, callback){
//  this.db = new Db(name, new Server(host, parseInt(port), {auto_reconnect: true}, {}));
//    username = u, password = p;
    var that = this;
    connect(url, function(err, db) {
//    this.db = mongo.db(url);
//    this.db.open(function(err, db){
        this.db = db;
        that.db = db;
      if (err) {
          console.log(err)
          callback(err);
      }
       else {
          callback(null, db);
      }

      // Authenticate
        /*
      if (username && password) {
          db.authenticate(username, password, function(error, res) {
            if( error ) callback(error);
            else callback(null, res);
        });
      }
      */
  });
};
FPProvider.prototype.levels = [];

FPProvider.prototype.getCollection= function(callback) {
    this.db.collection('levels', function(error, level_collection) {
      if( error ) callback(error);
      else callback(null, level_collection);
    });
};

FPProvider.prototype.findAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.find().toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findAllByUser = function(user, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
//        level_collection.find({user_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(user._id)}).toArray(function(error, results) {
        level_collection.find({user_id: user._id}).toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

FPProvider.prototype.findById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.findOne({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(error, result) {
          if( error ) callback(error)
          else callback(null, result)
        });
      }
    });};

FPProvider.prototype.deleteById = function(id, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
          level_collection.remove({_id: level_collection.db.bson_serializer.ObjectID.createFromHexString(id)}, function(err, r) {
            if( error ) callback(error)
            else callback(null, r)
          });
      }
    });};

FPProvider.prototype.updateLevel = function(level, callback) {
    this.getCollection(function(error, level_collection) {
        if( error ) callback(error);
        else {
			console.log("level id: " + level._id)
            if (level._id && typeof(level._id) === 'string')
                level._id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level._id)
	        if (level.user_id && typeof(level.user_id) === 'string')
	                level.user_id = level_collection.db.bson_serializer.ObjectID.createFromHexString(level.user_id)
            level_collection.save(level, {safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};
FPProvider.prototype.loadLevels = function(levels, callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        if( typeof(levels.length)=="undefined")
          levels = [levels];

        for( var i =0;i< levels.length;i++ ) {
          level = levels[i];
//          article.created_at = new Date();
//          if( article.comments === undefined ) article.comments = [];
//          for(var j =0;j< article.comments.length; j++) {
//            article.comments[j].created_at = new Date();
//          }
        }

        level_collection.remove({}, function(err, r) {
            level_collection.insert(levels, function() {
              callback(error, levels);
            });
        });
      }
    });
};

FPProvider.prototype.findHighScores = function(callback) {
    this.getCollection(function(error, user_collection) {
      if( error ) callback(error)
      else {
          // TODO: MongoDB order by / limit
          user_collection.find().toArray(function(error, results) {
            if( error ) callback(error)
            else {
                callback(null, results)
            }
          });
      }
    });
};

FPProvider.prototype.deleteAll = function(callback) {
    this.getCollection(function(error, level_collection) {
      if( error ) callback(error)
      else {
        level_collection.remove()
                .toArray(function(error, results) {
          if( error ) callback(error)
          else callback(null, results)
        });
      }
    });};

/* Lets bootstrap with dummy data */
//new LevelProvider().save([
//    new Level(1),
//    new Level(2),
//    new Level(3)
//], function(error, levels){
//    console.log(error);
//});

FPProvider.prototype.getUserCollection= function(callback) {
    this.db.collection('users', function(error, user_collection) {
      if( error ) callback(error);
      else callback(null, user_collection);
    });
};

FPProvider.prototype.getUserById = function(user_id, callback) {
    this.getUserCollection(function(error, user_collection) {
      if( error ) callback(error);
      else {
          user_collection.findOne({id: user_id}, function(error, result) {
            if( error ) callback(error)
            else if (!result) {
                user_collection.findOne({_id: user_collection.db.bson_serializer.ObjectID.createFromHexString(user_id)}, function(error, result2) {
                    if( error ) callback(error)
                    else callback(null, result2)
                });
            }
            else callback(null, result)
          });
      }
    });
};

FPProvider.prototype.updateUser = function(user, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.save(user,{safe:true}, function(error, result) {
                callback(error, result);
            });
        }
    });
};

FPProvider.prototype.saveUser = function(user, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: user.email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else {
                // Make sure the user has a profile and a nickname
                if (user.profile == undefined) {
                    user.profile = new Profile();
                    user.profile.nickname = user.profile.nickname || user.nickname || user.email;
                }
                user_collection.save(user, function(e, u) {
                  callback(e, u);
                });
              }
            });
        }
    });
};

FPProvider.prototype.saveUsers = function(users, callback) {
//    console.log(sourceUser)
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.insert(users, function(e, u) {
              callback(e, u);
            });
        }
    });
};

FPProvider.prototype.authenticateUser = function(email, password, callback) {
    this.getUserCollection(function(error, user_collection) {
        if( error ) callback(error);
        else {
            user_collection.findOne({email: email}, function(error, result) {
              if( error ) callback(error)
              // If user is not found, save it here
              else if (result == undefined) {
                // Make sure the user has a profile and a nickname
                  callback("Could not authenticate - no user found with email of " + email)
              }
              else {
                  if (result.password == password) {
                      callback(null, result);
                      return;
                  }
                  else {
                      callback("Could not authenticate - no user found with email of " + email)
                  }
              }
            });
        }
    });
};

exports.FPProvider = FPProvider;<|endoftext|>


<commit_before>import { createCellDragBehaviour } from './cell-drag-behaviour'
import { dispatch as createDispatch } from 'd3-dispatch'
import { modulo, someResult as some } from '@zambezi/fun'
import { rebind, keyCodeHandler, appendIfMissing } from '@zambezi/d3-utils'
import { reduce, indexBy, find, findIndex, debounce, uniqueId } from 'underscore'
import { select, event } from 'd3-selection'
import { unwrap } from '@zambezi/grid'

import './cell-selection.css'

export function createCellSelection () {
  const dispatch = createDispatch(
    'cell-selected-change',
    'cell-selected-update',
    'cell-active-action',
    'cell-active-change',
    'cell-active-update',
    'cell-active-paste'
  )

  const clipboardKeydown = uniqueId('keydown.clipboard.')
  const clipboardKeyup = uniqueId('keyup.clipboard.')
  const cellDragBehaviour = createCellDragBehaviour()
  const api = rebind()
            .from(dispatch, 'on')
            .from(cellDragBehaviour, 'ignoreSelector')
            .from(cellDragBehaviour, 'debugIgnoreSelector')
  const defaultSelectionKey = d => d
  const clipboardProxy = appendIfMissing('textarea.zambezi-grid-clipboard-proxy')

  let selected = []
  let selectedCandidates
  let activeCandidate
  let selectedRowsByColumnId = {}
  let acceptPasteFrom = []
  let active
  let selectable = true
  let trackPaste = true
  let typeToActivate = true
  let lastOverCell
  let rowSelectionKey = defaultSelectionKey
  let rowUpdateNeeded = true
  let clip
  let isIEPasting

  function cellSelection (s) {
    s.each(cellSelectionEach)
  }

  cellSelection.selected = function (value) {
    if (!arguments.length) return selected
    selected = value
    selectedCandidates = value
    return cellSelection
  }

  cellSelection.trackPaste = function (value) {
    if (!arguments.length) return trackPaste
    trackPaste = value
    return cellSelection
  }

  cellSelection.acceptPasteFrom = function (value) {
    if (!arguments.length) return acceptPasteFrom
    acceptPasteFrom = value
    return cellSelection
  }

  cellSelection.typeToActivate = function (value) {
    if (!arguments.length) return typeToActivate
    typeToActivate = value
    return cellSelection
  }

  cellSelection.selectable = function (value) {
    if (!arguments.length) return selectable
    selectable = value
    return cellSelection
  }

  cellSelection.active = function (value) {
    if (!arguments.length) return active
    active = value
    activeCandidate = value
    return cellSelection
  }

  cellSelection.rowChangedKey = function (targetRow) {
    const unwrappedRow = unwrap(targetRow)
    const selectedColumnIds = []

    let key = ''

    selected.forEach(addSelected)

    function addSelected ({row, column}) {
      if (unwrappedRow !== row) return
      selectedColumnIds.push(column.id)
    }

    key = selectedColumnIds.join('↑')

    if (active && rowSelectionKey(unwrappedRow) === rowSelectionKey(active.row)) {
      key += ('☆' + active.column.id)
    }
    return key
  }

  cellSelection.rowSelectionKey = function (value) {
    if (!arguments.length) return rowSelectionKey
    rowSelectionKey = value
    return cellSelection
  }

  return api(cellSelection)

  function cellSelectionEach (bundle) {
    const target = select(this)
            .on('data-dirty.cell-selection', () => (rowUpdateNeeded = true))
    const { columns } = bundle
    const columnById = indexBy(columns, 'id')
    const newRowByOldRow = new Map()

    setupDragEvents()
    setupPasteEvents()
    setupKeyboardNavEvents()

    if (rowUpdateNeeded) updateRowsFromKeys()
    if (selectedCandidates) updateSelectedFromCandidates()
    if (activeCandidate) updateActiveFromCandidate()

    bundle.dispatcher
        .on('cell-update.cell-selection', onCellUpdate)

    function moveHorizontal (step) {
      if (!active) return

      const { column, row } = active
      const currentColumnIndex = columns.indexOf(column)
      const newColumn = columns[modulo(currentColumnIndex + step, columns.length)]

      setActive({ row, column: newColumn })
      target.dispatch('redraw', { bubbles: true })
    }

    function moveVertical (step, rows) {
      if (!active) return

      const { column, row } = active
      const currentRowIndex = findIndex(rows, r => rowSelectionKey(unwrap(r)) === rowSelectionKey(row))
      const newRow = rows[modulo(currentRowIndex + step, rows.length)]

      setActive({ row: newRow, column })
      target.dispatch('redraw', { bubbles: true })
    }

    function updateRowsFromKeys () {
      rowUpdateNeeded = false
      if (rowSelectionKey === defaultSelectionKey) return

      selectedCandidates = (selected || [])
          .reduce(updateRowFromSelectionKey, [])
          .concat(selectedCandidates || [])

      if (active) activeCandidate = active

      function updateRowFromSelectionKey (acc, {column, row}) {
        const newRow = findNewRowForOld(row)
        if (newRow) acc.push({column, row: newRow})
        return acc
      }
    }

    function findNewRowForOld (row) {
      const rowSeen = newRowByOldRow.has(row)
      let newRow = newRowByOldRow.get(row)
      if (rowSeen && !newRow) return null
      if (!newRow) {
        newRow = find(bundle, r => rowSelectionKey(r) === rowSelectionKey(row))
        newRowByOldRow.set(row, newRow)
      }
      return newRow
    }

    function setActiveIfNone () {
      if (active) return
      if (!bundle.length) return
      if (!columns.length) return
      setActive({ row: bundle[0], column: columns[0] })
      select(this).dispatch('redraw', { bubbles: true })
    }

    function updateActiveFromCandidate () {
      active = candidateToSelection(activeCandidate)
      activeCandidate = null
      dispatch.call('cell-active-update', this, active)
    }

    function updateSelectedFromCandidates () {
      selectedRowsByColumnId = selectedCandidates.reduce(toRealSelection, {})
      selected = compileSelected()
      selectedCandidates = null
      dispatch.call('cell-selected-update', this, selected, active)
    }

    function toRealSelection (acc, candidate) {
      const selection = candidateToSelection(candidate)

      if (!selection) {
        console.warn("Couldn't find cell for", candidate)
        return acc
      }

      const columnId = selection.column.id
      const set = acc[columnId] || new Set()

      set.add(selection.row)
      acc[columnId] = set
      return acc
    }

    function candidateToSelection ({ row, column }) {
      const columnFound = typeof column === 'string' ? columnById[column] : column
      const rowFound = typeof row === 'number' ? bundle[row] : findNewRowForOld(row)

      if (!columnFound || !rowFound) return undefined
      return { row: rowFound, column: columnFound }
    }

    function compileSelected () {
      return reduce(selectedRowsByColumnId, toCells, [])
    }

    function setupKeyboardNavEvents () {
      target.attr('tabindex', '0')
          .on('focus', debounce(setActiveIfNone, 200))
          .on(
            'keydown.keyboard-cell-selection'
          , some(
              keyCodeHandler(() => dispatch.call('cell-active-action', this, active), 13) // enter
            , keyCodeHandler(d => moveVertical(-1, d.rows), 38) // up
            , keyCodeHandler(d => moveVertical(1, d.rows), 40)  // down
            , keyCodeHandler(() => moveHorizontal(-1), 37)      // left
            , keyCodeHandler(() => moveHorizontal(1), 39)       // right
            , keyCodeHandler(onTab, 9)
            , activateFromInput
            )
          )
    }

    function setupPasteEvents () {
      select(document)
          .on(clipboardKeydown, trackPaste ? onClipMaybe : null)
          .on(clipboardKeyup, trackPaste ? onClipAfter : null)
    }

    function setupDragEvents () {
      target.call(
        cellDragBehaviour
            .on('dragstart.select', d => mouseSelection(d))
            .on('dragstart.active', setActive)
            .on('dragstart.redraw', () => target.dispatch('redraw', { bubbles: true }))

            .on('dragend.set-active', activateLastCell)
            .on('dragend.redraw', () => target.dispatch('redraw', { bubbles: true }))

            .on('dragover.select', d => mouseSelection(d, true))
            .on('dragover.cache', d => (lastOverCell = d))
            .on('dragover.redraw', () => target.dispatch('redraw', { bubbles: true }))
      )
    }

    function activateLastCell () {
      if (!lastOverCell) return
      setActive(lastOverCell)
      lastOverCell = null
    }

    function onClipMaybe () {
      const targetNode = target.node()
      const allAcceptedNodes = (acceptPasteFrom || []).concat(targetNode)
      const { ctrlKey } = event

      if (!allAcceptedNodes.some(n => n.contains(document.activeElement))) return
      if (!ctrlKey) return

      clip = select(document.body)
            .select(clipboardProxy)
              .on('paste.cell-selection', onPaste)

      clip.node().focus()
      clip.node().select()

      function onPaste () {
        const clipboardData = event.clipboardData

        if (!clipboardData) {
          isIEPasting = true
          return
        }

        const text = clipboardData.getData('Text')
        dispatch.call('cell-active-paste', targetNode, active, text)
      }
    }

    function onClipAfter () {
      const targetNode = target.node()
      const { key } = event

      if (key !== 'Control') return

      if (isIEPasting) {
        dispatch.call('cell-active-paste', targetNode, active, clip.property('value'))
        isIEPasting = false
      }

      if (clip) {
        clip.remove()
        clip = null
        targetNode.focus()
      }
    }

    function activateFromInput () {
      const { key, ctrlKey, altKey, metaKey } = event

      if (!typeToActivate) return
      if (ctrlKey || altKey || metaKey) return
      if (!active) return
      if (isASpecialKey(key)) return

      dispatch.call('cell-active-action', target.node(), active, key)
    }

    function onTab () {
      moveHorizontal(event.shiftKey ? -1 : 1)
      event.preventDefault()
    }

    function mouseSelection (d, forceAppend) {
      const column = d.column
      const columnId = column.id
      const set = selectedRowsByColumnId[columnId]
      const row = unwrap(d.row)
      const { shiftKey, ctrlKey } = event.sourceEvent ? event.sourceEvent : event
      const cell = { row, column }
      const isAlreadySelected = set && set.has(row)
      const hasActive = !!active

      if (selectable) {
        switch (true) {
          case ctrlKey && shiftKey && hasActive:
            addRangeToSelection(active, cell)
            break

          case (forceAppend || shiftKey) && hasActive:
            setSelectionToRange(active, cell)
            break

          case ctrlKey && isAlreadySelected:
            removeFromSelected(cell)
            break

          case ctrlKey:
            addToSelected(cell)
            break

          default:
            selectOnly(cell)
        }

        selected = compileSelected()
        dispatch.call('cell-selected-change', this, selected, active)
      }
    }

    function setActive (cell) {
      if (!cell) {
        active = null
        return
      }

      const { row, column } = cell
      active = { row: unwrap(row), column }
      dispatch.call('cell-active-change', this, active)
    }

    function setSelectionToRange (a, b) {
      selectedRowsByColumnId = {}
      rangeFrom(a, b).forEach(addToSelected)
    }

    function addRangeToSelection (a, b) {
      rangeFrom(a, b).forEach(addToSelected)
    }

    function rangeFrom (a, b) {
      const columnRange = rangeFromUnorderedIndices(
              columns,
              columns.indexOf(a.column),
              columns.indexOf(b.column)
            )
      const rowRange = rangeFromUnorderedIndices(
              bundle.rows,
              findIndex(bundle.rows, r => unwrap(r) === a.row),
              findIndex(bundle.rows, r => unwrap(r) === b.row)
            ).map(unwrap)

      return rowRange.reduce(allCellsInColumns, [])

      function allCellsInColumns (acc, row) {
        return acc.concat(columnRange.map(column => ({ column, row })))
      }
    }

    function selectOnly (target) {
      selectedRowsByColumnId = {}
      addToSelected(target)
    }

    function rangeFromUnorderedIndices (array, i1, i2) {
      return array.slice(
        Math.min(i1, i2)
      , Math.max(i1, i2) + 1
      )
    }

    function removeFromSelected ({ row, column }) {
      const columnId = column.id
      const set = selectedRowsByColumnId[columnId]

      if (set) set.delete(row)
    }

    function addToSelected ({ row, column }) {
      const columnId = column.id
      const set = selectedRowsByColumnId[columnId] || new Set()

      set.add(row)
      selectedRowsByColumnId[columnId] = set
    }

    function toCells (acc, set, columnId) {
      const column = columnById[columnId]
      return acc.concat(Array.from(set.values()).map(row => ({ row, column })))
    }
  }

  function onCellUpdate () {
    select(this)
        .classed('is-selected', isCellSelected)
        .classed('is-active', isCellActive)
  }

  function isCellSelected (d) {
    const rowSetForColumn = selectedRowsByColumnId[d.column.id]
    return rowSetForColumn && rowSetForColumn.has(unwrap(d.row))
  }

  function isCellActive (d) {
    if (!active) return false
    return areSameCell(d, active)
  }

  function areSameCell (a, b) {
    return a && b && rowSelectionKey(unwrap(a.row)) === rowSelectionKey(unwrap(b.row)) && a.column.id === b.column.id
  }
}

function isASpecialKey (key) {
  return key && key.length > 1
}
<commit_msg>Don't use d3 drag when cells are not selectable<commit_after>import { createCellDragBehaviour } from './cell-drag-behaviour'
import { dispatch as createDispatch } from 'd3-dispatch'
import { modulo, someResult as some } from '@zambezi/fun'
import { rebind, keyCodeHandler, appendIfMissing } from '@zambezi/d3-utils'
import { reduce, indexBy, find, findIndex, debounce, uniqueId } from 'underscore'
import { select, event } from 'd3-selection'
import { unwrap } from '@zambezi/grid'

import './cell-selection.css'

export function createCellSelection () {
  const dispatch = createDispatch(
    'cell-selected-change',
    'cell-selected-update',
    'cell-active-action',
    'cell-active-change',
    'cell-active-update',
    'cell-active-paste'
  )

  const clipboardKeydown = uniqueId('keydown.clipboard.')
  const clipboardKeyup = uniqueId('keyup.clipboard.')
  const cellDragBehaviour = createCellDragBehaviour()
  const api = rebind()
            .from(dispatch, 'on')
            .from(cellDragBehaviour, 'ignoreSelector')
            .from(cellDragBehaviour, 'debugIgnoreSelector')
  const defaultSelectionKey = d => d
  const clipboardProxy = appendIfMissing('textarea.zambezi-grid-clipboard-proxy')

  let selected = []
  let selectedCandidates
  let activeCandidate
  let selectedRowsByColumnId = {}
  let acceptPasteFrom = []
  let active
  let selectable = true
  let trackPaste = true
  let typeToActivate = true
  let lastOverCell
  let rowSelectionKey = defaultSelectionKey
  let rowUpdateNeeded = true
  let clip
  let isIEPasting

  function cellSelection (s) {
    s.each(cellSelectionEach)
  }

  cellSelection.selected = function (value) {
    if (!arguments.length) return selected
    selected = value
    selectedCandidates = value
    return cellSelection
  }

  cellSelection.trackPaste = function (value) {
    if (!arguments.length) return trackPaste
    trackPaste = value
    return cellSelection
  }

  cellSelection.acceptPasteFrom = function (value) {
    if (!arguments.length) return acceptPasteFrom
    acceptPasteFrom = value
    return cellSelection
  }

  cellSelection.typeToActivate = function (value) {
    if (!arguments.length) return typeToActivate
    typeToActivate = value
    return cellSelection
  }

  cellSelection.selectable = function (value) {
    if (!arguments.length) return selectable
    selectable = value
    return cellSelection
  }

  cellSelection.active = function (value) {
    if (!arguments.length) return active
    active = value
    activeCandidate = value
    return cellSelection
  }

  cellSelection.rowChangedKey = function (targetRow) {
    const unwrappedRow = unwrap(targetRow)
    const selectedColumnIds = []

    let key = ''

    selected.forEach(addSelected)

    function addSelected ({row, column}) {
      if (unwrappedRow !== row) return
      selectedColumnIds.push(column.id)
    }

    key = selectedColumnIds.join('↑')

    if (active && rowSelectionKey(unwrappedRow) === rowSelectionKey(active.row)) {
      key += ('☆' + active.column.id)
    }
    return key
  }

  cellSelection.rowSelectionKey = function (value) {
    if (!arguments.length) return rowSelectionKey
    rowSelectionKey = value
    return cellSelection
  }

  return api(cellSelection)

  function cellSelectionEach (bundle) {
    const target = select(this)
            .on('data-dirty.cell-selection', () => (rowUpdateNeeded = true))
    const { columns } = bundle
    const columnById = indexBy(columns, 'id')
    const newRowByOldRow = new Map()

    if (selectable) setupDragEvents()
    else setupManualActiveSelection()

    setupPasteEvents()
    setupKeyboardNavEvents()

    if (rowUpdateNeeded) updateRowsFromKeys()
    if (selectedCandidates) updateSelectedFromCandidates()
    if (activeCandidate) updateActiveFromCandidate()

    bundle.dispatcher
        .on('cell-update.cell-selection', onCellUpdate)

    function moveHorizontal (step) {
      if (!active) return

      const { column, row } = active
      const currentColumnIndex = columns.indexOf(column)
      const newColumn = columns[modulo(currentColumnIndex + step, columns.length)]

      setActive({ row, column: newColumn })
      target.dispatch('redraw', { bubbles: true })
    }

    function moveVertical (step, rows) {
      if (!active) return

      const { column, row } = active
      const currentRowIndex = findIndex(rows, r => rowSelectionKey(unwrap(r)) === rowSelectionKey(row))
      const newRow = rows[modulo(currentRowIndex + step, rows.length)]

      setActive({ row: newRow, column })
      target.dispatch('redraw', { bubbles: true })
    }

    function updateRowsFromKeys () {
      rowUpdateNeeded = false
      if (rowSelectionKey === defaultSelectionKey) return

      selectedCandidates = (selected || [])
          .reduce(updateRowFromSelectionKey, [])
          .concat(selectedCandidates || [])

      if (active) activeCandidate = active

      function updateRowFromSelectionKey (acc, {column, row}) {
        const newRow = findNewRowForOld(row)
        if (newRow) acc.push({column, row: newRow})
        return acc
      }
    }

    function findNewRowForOld (row) {
      const rowSeen = newRowByOldRow.has(row)
      let newRow = newRowByOldRow.get(row)
      if (rowSeen && !newRow) return null
      if (!newRow) {
        newRow = find(bundle, r => rowSelectionKey(r) === rowSelectionKey(row))
        newRowByOldRow.set(row, newRow)
      }
      return newRow
    }

    function setActiveIfNone () {
      if (active) return
      if (!bundle.length) return
      if (!columns.length) return
      setActive({ row: bundle[0], column: columns[0] })
      select(this).dispatch('redraw', { bubbles: true })
    }

    function updateActiveFromCandidate () {
      active = candidateToSelection(activeCandidate)
      activeCandidate = null
      dispatch.call('cell-active-update', this, active)
    }

    function updateSelectedFromCandidates () {
      selectedRowsByColumnId = selectedCandidates.reduce(toRealSelection, {})
      selected = compileSelected()
      selectedCandidates = null
      dispatch.call('cell-selected-update', this, selected, active)
    }

    function toRealSelection (acc, candidate) {
      const selection = candidateToSelection(candidate)

      if (!selection) {
        console.warn("Couldn't find cell for", candidate)
        return acc
      }

      const columnId = selection.column.id
      const set = acc[columnId] || new Set()

      set.add(selection.row)
      acc[columnId] = set
      return acc
    }

    function candidateToSelection ({ row, column }) {
      const columnFound = typeof column === 'string' ? columnById[column] : column
      const rowFound = typeof row === 'number' ? bundle[row] : findNewRowForOld(row)

      if (!columnFound || !rowFound) return undefined
      return { row: rowFound, column: columnFound }
    }

    function compileSelected () {
      return reduce(selectedRowsByColumnId, toCells, [])
    }

    function setupKeyboardNavEvents () {
      target.attr('tabindex', '0')
          .on('focus', debounce(setActiveIfNone, 200))
          .on(
            'keydown.keyboard-cell-selection'
          , some(
              keyCodeHandler(() => dispatch.call('cell-active-action', this, active), 13) // enter
            , keyCodeHandler(d => moveVertical(-1, d.rows), 38) // up
            , keyCodeHandler(d => moveVertical(1, d.rows), 40)  // down
            , keyCodeHandler(() => moveHorizontal(-1), 37)      // left
            , keyCodeHandler(() => moveHorizontal(1), 39)       // right
            , keyCodeHandler(onTab, 9)
            , activateFromInput
            )
          )
    }

    function setupPasteEvents () {
      select(document)
          .on(clipboardKeydown, trackPaste ? onClipMaybe : null)
          .on(clipboardKeyup, trackPaste ? onClipAfter : null)
    }

    function setupDragEvents () {
      target.call(
        cellDragBehaviour
            .on('dragstart.select', d => mouseSelection(d))
            .on('dragstart.active', setActive)
            .on('dragstart.redraw', () => target.dispatch('redraw', { bubbles: true }))

            .on('dragend.set-active', activateLastCell)
            .on('dragend.redraw', () => target.dispatch('redraw', { bubbles: true }))

            .on('dragover.select', d => mouseSelection(d, true))
            .on('dragover.cache', d => (lastOverCell = d))
            .on('dragover.redraw', () => target.dispatch('redraw', { bubbles: true }))
      )
    }

    function setupManualActiveSelection () {
      bundle.dispatcher.on('cell-update.manual-set-active', onCellUpdate)

      function onCellUpdate () {
        select(this).on('click.manual-set-active', )
      }

      function onCellClick ({ column, row }) {
        setActive({ column, row: unwrap(row) })
        select(this).dispatch('redraw', { bubbles: true })
      }
    }

    function activateLastCell () {
      if (!lastOverCell) return
      setActive(lastOverCell)
      lastOverCell = null
    }

    function onClipMaybe () {
      const targetNode = target.node()
      const allAcceptedNodes = (acceptPasteFrom || []).concat(targetNode)
      const { ctrlKey } = event

      if (!allAcceptedNodes.some(n => n.contains(document.activeElement))) return
      if (!ctrlKey) return

      clip = select(document.body)
            .select(clipboardProxy)
              .on('paste.cell-selection', onPaste)

      clip.node().focus()
      clip.node().select()

      function onPaste () {
        const clipboardData = event.clipboardData

        if (!clipboardData) {
          isIEPasting = true
          return
        }

        const text = clipboardData.getData('Text')
        dispatch.call('cell-active-paste', targetNode, active, text)
      }
    }

    function onClipAfter () {
      const targetNode = target.node()
      const { key } = event

      if (key !== 'Control') return

      if (isIEPasting) {
        dispatch.call('cell-active-paste', targetNode, active, clip.property('value'))
        isIEPasting = false
      }

      if (clip) {
        clip.remove()
        clip = null
        targetNode.focus()
      }
    }

    function activateFromInput () {
      const { key, ctrlKey, altKey, metaKey } = event

      if (!typeToActivate) return
      if (ctrlKey || altKey || metaKey) return
      if (!active) return
      if (isASpecialKey(key)) return

      dispatch.call('cell-active-action', target.node(), active, key)
    }

    function onTab () {
      moveHorizontal(event.shiftKey ? -1 : 1)
      event.preventDefault()
    }

    function mouseSelection (d, forceAppend) {
      const column = d.column
      const columnId = column.id
      const set = selectedRowsByColumnId[columnId]
      const row = unwrap(d.row)
      const { shiftKey, ctrlKey } = event.sourceEvent ? event.sourceEvent : event
      const cell = { row, column }
      const isAlreadySelected = set && set.has(row)
      const hasActive = !!active

      if (selectable) {
        switch (true) {
          case ctrlKey && shiftKey && hasActive:
            addRangeToSelection(active, cell)
            break

          case (forceAppend || shiftKey) && hasActive:
            setSelectionToRange(active, cell)
            break

          case ctrlKey && isAlreadySelected:
            removeFromSelected(cell)
            break

          case ctrlKey:
            addToSelected(cell)
            break

          default:
            selectOnly(cell)
        }

        selected = compileSelected()
        dispatch.call('cell-selected-change', this, selected, active)
      }
    }

    function setActive (cell) {
      if (!cell) {
        active = null
        return
      }

      const { row, column } = cell
      active = { row: unwrap(row), column }
      dispatch.call('cell-active-change', this, active)
    }

    function setSelectionToRange (a, b) {
      selectedRowsByColumnId = {}
      rangeFrom(a, b).forEach(addToSelected)
    }

    function addRangeToSelection (a, b) {
      rangeFrom(a, b).forEach(addToSelected)
    }

    function rangeFrom (a, b) {
      const columnRange = rangeFromUnorderedIndices(
              columns,
              columns.indexOf(a.column),
              columns.indexOf(b.column)
            )
      const rowRange = rangeFromUnorderedIndices(
              bundle.rows,
              findIndex(bundle.rows, r => unwrap(r) === a.row),
              findIndex(bundle.rows, r => unwrap(r) === b.row)
            ).map(unwrap)

      return rowRange.reduce(allCellsInColumns, [])

      function allCellsInColumns (acc, row) {
        return acc.concat(columnRange.map(column => ({ column, row })))
      }
    }

    function selectOnly (target) {
      selectedRowsByColumnId = {}
      addToSelected(target)
    }

    function rangeFromUnorderedIndices (array, i1, i2) {
      return array.slice(
        Math.min(i1, i2)
      , Math.max(i1, i2) + 1
      )
    }

    function removeFromSelected ({ row, column }) {
      const columnId = column.id
      const set = selectedRowsByColumnId[columnId]

      if (set) set.delete(row)
    }

    function addToSelected ({ row, column }) {
      const columnId = column.id
      const set = selectedRowsByColumnId[columnId] || new Set()

      set.add(row)
      selectedRowsByColumnId[columnId] = set
    }

    function toCells (acc, set, columnId) {
      const column = columnById[columnId]
      return acc.concat(Array.from(set.values()).map(row => ({ row, column })))
    }
  }

  function onCellUpdate () {
    select(this)
        .classed('is-selected', isCellSelected)
        .classed('is-active', isCellActive)
  }

  function isCellSelected (d) {
    const rowSetForColumn = selectedRowsByColumnId[d.column.id]
    return rowSetForColumn && rowSetForColumn.has(unwrap(d.row))
  }

  function isCellActive (d) {
    if (!active) return false
    return areSameCell(d, active)
  }

  function areSameCell (a, b) {
    return a && b && rowSelectionKey(unwrap(a.row)) === rowSelectionKey(unwrap(b.row)) && a.column.id === b.column.id
  }
}

function isASpecialKey (key) {
  return key && key.length > 1
}
<|endoftext|>


<commit_before>'use strict'

module.exports = Format

Format.defaults = {}
Format.defaults['A4'] = new Format('A4', '210mm', '297mm')

if (window.env === 'development') {
  Format.defaults['dev-small'] = new Format('dev-small', '150mm', '100mm')
}

function Format (name, width, height) {
  this.name = name
  this.width = width
  this.height = height
}

}
<commit_msg>implement Format.prototype.applyToPlugin()<commit_after>'use strict'

var uiUtils = require('../utils/ui')

module.exports = Format

Format.prototype.applyToPlugin = applyToPlugin

Format.defaults = {}
Format.defaults['A4'] = new Format('A4', '210mm', '297mm')

if (window.env === 'development') {
  Format.defaults['dev-small'] = new Format('dev-small', '150mm', '100mm')
}

function Format (name, width, height) {
  this.name = name
  this.width = width
  this.height = height
}

function applyToPlugin (plugin) {
  if (plugin.documentBody) {
    var body = plugin.documentBody

    applyToStackedLayout()
  }

  function applyToStackedLayout () {
    var bodyHeight = uiUtils.getElementHeight(body)
    var rules = {
      height: bodyHeight,
      minHeight: bodyHeight,
      maxHeight: bodyHeight
    }

    plugin.stackedLayout.iframe.css(rules)
    // console.log(body, bodyHeight, plugin.stackedLayout.iframe, uiUtils.getElementHeight(plugin.stackedLayout.iframe[0]))
  }

}
<|endoftext|>