<commit_before>

/**
 * @param {!Element} container
 * @constructor
 */
var BabyStats = function(container) {
  var urlRE = new RegExp('^/baby/([-0-9a-f]{36})$');
  var match = window.location.pathname.match(urlRE);
  if (!match) {
    window.location.pathname = '/baby/' + Cosmopolite.uuid();
    return;
  }
  var id = match[1];

  this.container_ = container;

  this.tileScaleHeight_ = 1;
  this.tileScaleWidth_ = 1;

  this.tiles_ = [
    ['asleep', 'Asleep'],
    ['awake', 'Awake'],
    ['diaper_feces', 'Diaper change\n(feces)'],
    ['diaper_urine', 'Diaper change\n(urine only)'],
    ['feeding_breast', 'Feeding\n(breast)'],
    ['feeding_bottle_milk', 'Feeding\n(bottled breast milk)'],
    ['feeding_formula', 'Feeding\n(formula)'],
  ];

  this.intervals_ = {};

  this.cosmo_ = new Cosmopolite();
  hogfather.PublicChat.Join(this.cosmo_, id).then(this.onChatReady_.bind(this));
};

/**
 * @param {hogfather.PublicChat} chat
 * @private
 */
BabyStats.prototype.onChatReady_ = function(chat) {
  this.chat_ = chat;

  this.buildCells_();
  this.buildStylesheet_();
  this.buildLayout_();

  window.addEventListener('resize', this.rebuildIfNeeded_.bind(this));

  var grid = this.calculateGrid_();
  this.gridWidthCells_ = grid.gridWidthCells;
  this.gridHeightCells_ = grid.gridHeightCells;
  this.buildGrid_();

  var messages = this.chat_.getMessages();
  messages.forEach(this.handleMessage_.bind(this));
  this.chat_.addEventListener('message', this.onMessage_.bind(this));
};

/**
 * @param {Event} e
 * @private
 */
BabyStats.prototype.onMessage_ = function(e) {
  this.handleMessage_(e.detail);
};

/**
 * @param {Cosmopolite.typeMessage} message
 * @private
 */
BabyStats.prototype.handleMessage_ = function(message) {
  switch (message.message.type) {
    case 'child_name_change':
      if (e.detail.sender != this.cosmo_.currentProfile()) {
        this.childName_.value = message.message.child_name;
        this.checkOverlay_();
      }
      break;
    default:
      console.log('Unknown message type:', message);
      break;
  }
};

/**
 * Add a CSS class to a node if it doesn't already have it.
 * @param {!Node} node Node object to add class to
 * @param {!string} className Name of class to add
 * @private
 */
BabyStats.prototype.addCSSClass_ = function(node, className) {
  var classes = node.className.split(' ').filter(function(className) {
    return className;
  });
  if (classes.indexOf(className) != -1) {
    // Already has class.
    return;
  }
  classes.push(className);
  node.className = classes.join(' ');
};

/**
 * Check if we need to rebuild the grid layout because of optimal layout
 * changes.
 * @param {Event} e
 * @private
 */
BabyStats.prototype.rebuildIfNeeded_ = function(e) {
  var grid = this.calculateGrid_();
  if (this.gridWidthCells_ != grid.gridWidthCells ||
      this.gridHeightCells_ != grid.gridHeightCells) {
    this.gridWidthCells_ = grid.gridWidthCells;
    this.gridHeightCells_ = grid.gridHeightCells;
    this.buildGrid_();
  }
};

/**
 * Construct our stylesheet and insert it into the DOM.
 * @private
 */
BabyStats.prototype.buildStylesheet_ = function() {
  // http://www.colourlovers.com/palette/848743/(%E2%97%95_%E2%80%9D_%E2%97%95)
  var style = document.createElement('style');
  document.head.appendChild(style);

  style.sheet.insertRule('.babyStatsChildName, .babyStatsYourName {}', 0);
  var inputs = style.sheet.cssRules[0];
  inputs.style.display = 'block';
  inputs.style.height = '32px';
  inputs.style.width = '100%';
  inputs.style.border = 'none';
  inputs.style.borderRadius = 0;
  inputs.style.padding = '4px';
  inputs.style.backgroundColor = 'rgb(189,21,80)';
  inputs.style.color = 'rgb(248,202,0)';
  inputs.style.fontSize = '28px';
  inputs.style.textAlign = 'center';

  style.sheet.insertRule(
      '.babyStatsChildName:focus, ' +
      '.babyStatsYourName:focus {}', 0);
  var focus = style.sheet.cssRules[0];
  focus.style.outline = 'none';

  style.sheet.insertRule('babyStatsGridOverlay {}', 0);
  var gridOverlay = style.sheet.cssRules[0];
  gridOverlay.style.display = 'flex';
  gridOverlay.style.position = 'absolute';
  gridOverlay.style.top = '80px';
  gridOverlay.style.left = 0;
  gridOverlay.style.bottom = 0;
  gridOverlay.style.right = 0;
  gridOverlay.style.alignItems = 'center';
  gridOverlay.style.justifyContent = 'center';
  gridOverlay.style.backgroundColor = 'white';
  gridOverlay.style.color = 'rgb(189,21,80)';
  gridOverlay.style.textShadow = '0 0 2px rgb(248,202,0)';
  gridOverlay.style.fontSize = '6vmin';
  gridOverlay.style.fontWeight = 'bold';
  gridOverlay.style.transition = '0.4s';

  style.sheet.insertRule('babyStatsGridContainer {}', 0);
  var gridContainer = style.sheet.cssRules[0];
  gridContainer.style.position = 'absolute';
  gridContainer.style.top = '80px';
  gridContainer.style.left = 0;
  gridContainer.style.bottom = 0;
  gridContainer.style.right = 0;

  style.sheet.insertRule('babyStatsRow {}', 0);
  this.rowRule_ = style.sheet.cssRules[0];
  this.rowRule_.style.display = 'block';
  this.rowRule_.style.textAlign = 'center';

  style.sheet.insertRule('babyStatsCell {}', 0);
  this.cellRule_ = style.sheet.cssRules[0];
  this.cellRule_.style.display = 'inline-block';
  this.cellRule_.style.position = 'relative';
  this.cellRule_.style.height = '100%';
  this.cellRule_.style.webkitUserSelect = 'none';
  this.cellRule_.style.mozUserSelect = 'none';
  this.cellRule_.style.userSelect = 'none';
  this.cellRule_.style.cursor = 'default';

  style.sheet.insertRule('babyStatsCellContents {}', 0);
  var contents = style.sheet.cssRules[0];
  contents.style.display = 'flex';
  contents.style.position = 'absolute';
  contents.style.alignItems = 'center';
  contents.style.justifyContent = 'center';
  contents.style.margin = '5px';
  contents.style.padding = '5px';
  contents.style.height = 'calc(100% - 20px)';
  contents.style.width = 'calc(100% - 20px)';
  contents.style.fontSize = '6vmin';
  contents.style.fontWeight = 'bold';
  contents.style.whiteSpace = 'pre-line';
  contents.style.backgroundColor = 'rgb(73,10,61)';
  contents.style.color = 'rgb(233,127,2)';
  contents.style.borderRadius = '15px';

  style.sheet.insertRule('babyStatsCellOverlay {}', 0);
  var contents = style.sheet.cssRules[0];
  contents.style.display = 'flex';
  contents.style.position = 'absolute';
  contents.style.alignItems = 'center';
  contents.style.justifyContent = 'center';
  contents.style.margin = '5px';
  contents.style.height = 'calc(100% - 10px)';
  contents.style.width = 'calc(100% - 10px)';
  contents.style.fontSize = '20vmin';
  contents.style.fontWeight = 'bold';
  contents.style.backgroundColor = 'rgb(255,255,255)';
  contents.style.color = 'rgb(189,21,80)';
  contents.style.borderRadius = '15px';
  contents.style.opacity = 0.0;
  contents.style.transition = '0.4s';

  style.sheet.insertRule('.babyStatsContainer {}', 0);
  var containerRule = style.sheet.cssRules[0];
  containerRule.style.backgroundColor = 'white';

  this.addCSSClass_(this.container_, 'babyStatsContainer');
};

/**
 * Construct babyStateCell elements for insertion into the DOM.
 * @private
 */
BabyStats.prototype.buildCells_ = function() {
  this.cells_ = [];
  this.tiles_.forEach(function(tiles) {
    var cell = document.createElement('babyStatsCell');
    this.cells_.push(cell);

    var contents = document.createElement('babyStatsCellContents');
    contents.textContent = tiles[1];
    cell.appendChild(contents);

    var overlay = document.createElement('babyStatsCellOverlay');
    cell.appendChild(overlay);

    cell.addEventListener('click', this.onClick_.bind(this, tiles[0], overlay));
  }, this);
};

/**
 * Handle a click event on a button.
 * @param {string} eventName short name of event to send
 * @param {Element} overlay element to make visible with countdown timer
 * @private
 */
BabyStats.prototype.onClick_ = function(eventName, overlay) {
  if (this.intervals_[eventName]) {
    window.clearInterval(this.intervals_[eventName]);
    delete this.intervals_[eventName];
    overlay.style.opacity = 0.0;
    return;
  }
  var timer = 5;
  overlay.textContent = timer;
  overlay.style.opacity = 0.5;
  this.intervals_[eventName] = window.setInterval(function() {
    timer--;
    switch (timer) {
      case 0:
        this.chat_.sendMessage({
          type: eventName,
        });
        overlay.textContent = '✓';
        break;

      case -1:
        break;

      case -2:
        window.clearInterval(this.intervals_[eventName]);
        delete this.intervals_[eventName];
        overlay.style.opacity = 0.0;
        break;

      default:
        overlay.textContent = timer;
        break;
    }
  }.bind(this), 1000);
};

/**
 * Calculate optimal grid sizing.
 * This pile of magic math calculates the optimal grid width and height to
 * maximize the size of all buttons while preserving their aspect ratio.
 * @return {{
 *   gridWidthCells: number,
 *   gridHeightCells: number,
 *   cellWidthPx: number,
 *   cellHeightPx: number
 * }}
 * @private
 */
BabyStats.prototype.calculateGrid_ = function() {
  var containerWidth = this.gridContainer_.offsetWidth;
  var containerHeight = this.gridContainer_.offsetHeight;
  var numTiles = this.tiles_.length;

  var heightFactor = containerHeight / this.tileScaleHeight_;
  var widthFactor = containerWidth / this.tileScaleWidth_;

  var scaleFactor = heightFactor / widthFactor;

  var gridHeight = Math.sqrt(scaleFactor * numTiles);
  var gridWidth = Math.sqrt(numTiles / scaleFactor);

  var gridOptions = [
    [Math.ceil(gridWidth), Math.floor(gridHeight)],
    [Math.floor(gridWidth), Math.ceil(gridHeight)],
    [Math.ceil(gridWidth), Math.ceil(gridHeight)],
  ];

  // Check all possible options.
  // We are optimizing for several dimensions (decreasing priority):
  // 1) Be able to fit all the tiles.
  // 2) Maximum scale for an image in each cell.
  // 3) Minimize number of cells.
  var minCells = Number.MAX_VALUE;
  var maxScale = 0.0;
  var chosenHeight, chosenWidth;
  gridOptions.forEach(function(gridOption) {
    var numCells = gridOption[0] * gridOption[1];
    if (numCells < numTiles) {
      // Can't fit all the tiles in (we've rounded down too far).
      return;
    }
    var widthScale = (containerWidth / gridOption[0]) / this.tileScaleWidth_;
    var heightScale = (containerHeight / gridOption[1]) / this.tileScaleHeight_;
    var scale;
    if (widthScale < heightScale) {
      scale = widthScale;
    } else {
      scale = heightScale;
    }
    if (scale < maxScale) {
      // This would make cells smaller than another viable solution.
      return;
    }
    if (scale == maxScale && numCells > minCells) {
      // Same cell size as another viable solution, but ours has more cells.
      return;
    }
    chosenWidth = gridOption[0];
    chosenHeight = gridOption[1];
    minCells = numCells;
    maxScale = scale;
  }, this);

  return /** @struct */ {
    gridWidthCells: chosenWidth,
    gridHeightCells: chosenHeight,
    cellWidthPx: this.tileScaleWidth_ * maxScale,
    cellHeightPx: this.tileScaleHeight_ * maxScale,
  };
};

/**
 * Construct the outer DOM layout.
 * @private
 */
BabyStats.prototype.buildLayout_ = function() {
  this.childName_ = document.createElement('input');
  this.addCSSClass_(this.childName_, 'babyStatsChildName');
  this.childName_.placeholder = 'Child name';
  this.childName_.addEventListener('input', this.checkOverlay_.bind(this));
  this.childName_.addEventListener('input', this.onChildNameChange_.bind(this));
  this.container_.appendChild(this.childName_);

  this.yourName_ = document.createElement('input');
  this.addCSSClass_(this.yourName_, 'babyStatsYourName');
  this.yourName_.placeholder = 'Your name';
  this.yourName_.value = localStorage.getItem('babyStats:yourName') || '';
  this.yourName_.addEventListener('input', this.checkOverlay_.bind(this));
  this.yourName_.addEventListener('input', this.onYourNameChange_.bind(this));
  this.container_.appendChild(this.yourName_);

  this.gridContainer_ = document.createElement('babyStatsGridContainer');
  this.container_.appendChild(this.gridContainer_);

  this.gridOverlay_ = document.createElement('babyStatsGridOverlay');
  this.container_.appendChild(this.gridOverlay_);

  this.checkOverlay_();
};

/**
 * Make the grid overlay visible/hidden based on input field status.
 * @private
 */
BabyStats.prototype.checkOverlay_ = function() {
  var message = '';
  if (!this.childName_.value) {
    message = 'Please enter child name above';
  } else if (!this.yourName_.value) {
    message = 'Please enter your name above';
  }

  if (message) {
    this.gridOverlay_.style.visibility = 'visible';
    this.gridOverlay_.style.opacity = 0.7;
    this.gridOverlay_.textContent = message;
  } else {
    this.gridOverlay_.style.visibility = 'hidden';
    this.gridOverlay_.style.opacity = 0.0;
  }
};

/**
 * @private
 */
BabyStats.prototype.onChildNameChange_ = function() {
  this.chat_.sendMessage({
    type: 'child_name_change',
    child_name: this.childName_.value,
  });
};

/**
 * Store your name value locally.
 * @private
 */
BabyStats.prototype.onYourNameChange_ = function() {
  localStorage.setItem('babyStats:yourName', this.yourName_.value);
};

/**
 * Construct the grid objects in the DOM.
 * @private
 */
BabyStats.prototype.buildGrid_ = function() {
  this.gridContainer_.innerHTML = '';

  this.rowRule_.style.height = 100 / this.gridHeightCells_ + '%';
  this.cellRule_.style.width = 100 / this.gridWidthCells_ + '%';

  var i = 0;
  for (var y = 0; y < this.gridHeightCells_; y++) {
    var row = document.createElement('babyStatsRow');
    for (var x = 0; x < this.gridWidthCells_; x++) {
      if (i < this.cells_.length) {
        var cell = this.cells_[i];
        row.appendChild(cell);
        i++;
      }
    }
    this.gridContainer_.appendChild(row);
  }
};
<commit_msg>Allow us to see our own messages, but ignore name changes.<commit_after>

/**
 * @param {!Element} container
 * @constructor
 */
var BabyStats = function(container) {
  var urlRE = new RegExp('^/baby/([-0-9a-f]{36})$');
  var match = window.location.pathname.match(urlRE);
  if (!match) {
    window.location.pathname = '/baby/' + Cosmopolite.uuid();
    return;
  }
  var id = match[1];

  this.container_ = container;

  this.tileScaleHeight_ = 1;
  this.tileScaleWidth_ = 1;

  this.tiles_ = [
    ['asleep', 'Asleep'],
    ['awake', 'Awake'],
    ['diaper_feces', 'Diaper change\n(feces)'],
    ['diaper_urine', 'Diaper change\n(urine only)'],
    ['feeding_breast', 'Feeding\n(breast)'],
    ['feeding_bottle_milk', 'Feeding\n(bottled breast milk)'],
    ['feeding_formula', 'Feeding\n(formula)'],
  ];

  this.intervals_ = {};

  this.cosmo_ = new Cosmopolite();
  hogfather.PublicChat.Join(this.cosmo_, id).then(this.onChatReady_.bind(this));
};

/**
 * @param {hogfather.PublicChat} chat
 * @private
 */
BabyStats.prototype.onChatReady_ = function(chat) {
  this.chat_ = chat;

  this.buildCells_();
  this.buildStylesheet_();
  this.buildLayout_();

  window.addEventListener('resize', this.rebuildIfNeeded_.bind(this));

  var grid = this.calculateGrid_();
  this.gridWidthCells_ = grid.gridWidthCells;
  this.gridHeightCells_ = grid.gridHeightCells;
  this.buildGrid_();

  var messages = this.chat_.getMessages();
  messages.forEach(this.handleMessage_.bind(this, false));
  this.chat_.addEventListener('message', this.onMessage_.bind(this));
};

/**
 * @param {Event} e
 * @private
 */
BabyStats.prototype.onMessage_ = function(e) {
  this.handleMessage_(true, e.detail);
};

/**
 * @param {Boolean} isEvent
 * @param {Cosmopolite.typeMessage} message
 * @private
 */
BabyStats.prototype.handleMessage_ = function(isEvent, message) {
  switch (message.message.type) {
    case 'child_name_change':
      if (!isEvent || message.sender != this.cosmo_.currentProfile()) {
        this.childName_.value = message.message.child_name;
        this.checkOverlay_();
      }
      break;
    default:
      console.log('Unknown message type:', message);
      break;
  }
};

/**
 * Add a CSS class to a node if it doesn't already have it.
 * @param {!Node} node Node object to add class to
 * @param {!string} className Name of class to add
 * @private
 */
BabyStats.prototype.addCSSClass_ = function(node, className) {
  var classes = node.className.split(' ').filter(function(className) {
    return className;
  });
  if (classes.indexOf(className) != -1) {
    // Already has class.
    return;
  }
  classes.push(className);
  node.className = classes.join(' ');
};

/**
 * Check if we need to rebuild the grid layout because of optimal layout
 * changes.
 * @param {Event} e
 * @private
 */
BabyStats.prototype.rebuildIfNeeded_ = function(e) {
  var grid = this.calculateGrid_();
  if (this.gridWidthCells_ != grid.gridWidthCells ||
      this.gridHeightCells_ != grid.gridHeightCells) {
    this.gridWidthCells_ = grid.gridWidthCells;
    this.gridHeightCells_ = grid.gridHeightCells;
    this.buildGrid_();
  }
};

/**
 * Construct our stylesheet and insert it into the DOM.
 * @private
 */
BabyStats.prototype.buildStylesheet_ = function() {
  // http://www.colourlovers.com/palette/848743/(%E2%97%95_%E2%80%9D_%E2%97%95)
  var style = document.createElement('style');
  document.head.appendChild(style);

  style.sheet.insertRule('.babyStatsChildName, .babyStatsYourName {}', 0);
  var inputs = style.sheet.cssRules[0];
  inputs.style.display = 'block';
  inputs.style.height = '32px';
  inputs.style.width = '100%';
  inputs.style.border = 'none';
  inputs.style.borderRadius = 0;
  inputs.style.padding = '4px';
  inputs.style.backgroundColor = 'rgb(189,21,80)';
  inputs.style.color = 'rgb(248,202,0)';
  inputs.style.fontSize = '28px';
  inputs.style.textAlign = 'center';

  style.sheet.insertRule(
      '.babyStatsChildName:focus, ' +
      '.babyStatsYourName:focus {}', 0);
  var focus = style.sheet.cssRules[0];
  focus.style.outline = 'none';

  style.sheet.insertRule('babyStatsGridOverlay {}', 0);
  var gridOverlay = style.sheet.cssRules[0];
  gridOverlay.style.display = 'flex';
  gridOverlay.style.position = 'absolute';
  gridOverlay.style.top = '80px';
  gridOverlay.style.left = 0;
  gridOverlay.style.bottom = 0;
  gridOverlay.style.right = 0;
  gridOverlay.style.alignItems = 'center';
  gridOverlay.style.justifyContent = 'center';
  gridOverlay.style.backgroundColor = 'white';
  gridOverlay.style.color = 'rgb(189,21,80)';
  gridOverlay.style.textShadow = '0 0 2px rgb(248,202,0)';
  gridOverlay.style.fontSize = '6vmin';
  gridOverlay.style.fontWeight = 'bold';
  gridOverlay.style.transition = '0.4s';

  style.sheet.insertRule('babyStatsGridContainer {}', 0);
  var gridContainer = style.sheet.cssRules[0];
  gridContainer.style.position = 'absolute';
  gridContainer.style.top = '80px';
  gridContainer.style.left = 0;
  gridContainer.style.bottom = 0;
  gridContainer.style.right = 0;

  style.sheet.insertRule('babyStatsRow {}', 0);
  this.rowRule_ = style.sheet.cssRules[0];
  this.rowRule_.style.display = 'block';
  this.rowRule_.style.textAlign = 'center';

  style.sheet.insertRule('babyStatsCell {}', 0);
  this.cellRule_ = style.sheet.cssRules[0];
  this.cellRule_.style.display = 'inline-block';
  this.cellRule_.style.position = 'relative';
  this.cellRule_.style.height = '100%';
  this.cellRule_.style.webkitUserSelect = 'none';
  this.cellRule_.style.mozUserSelect = 'none';
  this.cellRule_.style.userSelect = 'none';
  this.cellRule_.style.cursor = 'default';

  style.sheet.insertRule('babyStatsCellContents {}', 0);
  var contents = style.sheet.cssRules[0];
  contents.style.display = 'flex';
  contents.style.position = 'absolute';
  contents.style.alignItems = 'center';
  contents.style.justifyContent = 'center';
  contents.style.margin = '5px';
  contents.style.padding = '5px';
  contents.style.height = 'calc(100% - 20px)';
  contents.style.width = 'calc(100% - 20px)';
  contents.style.fontSize = '6vmin';
  contents.style.fontWeight = 'bold';
  contents.style.whiteSpace = 'pre-line';
  contents.style.backgroundColor = 'rgb(73,10,61)';
  contents.style.color = 'rgb(233,127,2)';
  contents.style.borderRadius = '15px';

  style.sheet.insertRule('babyStatsCellOverlay {}', 0);
  var contents = style.sheet.cssRules[0];
  contents.style.display = 'flex';
  contents.style.position = 'absolute';
  contents.style.alignItems = 'center';
  contents.style.justifyContent = 'center';
  contents.style.margin = '5px';
  contents.style.height = 'calc(100% - 10px)';
  contents.style.width = 'calc(100% - 10px)';
  contents.style.fontSize = '20vmin';
  contents.style.fontWeight = 'bold';
  contents.style.backgroundColor = 'rgb(255,255,255)';
  contents.style.color = 'rgb(189,21,80)';
  contents.style.borderRadius = '15px';
  contents.style.opacity = 0.0;
  contents.style.transition = '0.4s';

  style.sheet.insertRule('.babyStatsContainer {}', 0);
  var containerRule = style.sheet.cssRules[0];
  containerRule.style.backgroundColor = 'white';

  this.addCSSClass_(this.container_, 'babyStatsContainer');
};

/**
 * Construct babyStateCell elements for insertion into the DOM.
 * @private
 */
BabyStats.prototype.buildCells_ = function() {
  this.cells_ = [];
  this.tiles_.forEach(function(tiles) {
    var cell = document.createElement('babyStatsCell');
    this.cells_.push(cell);

    var contents = document.createElement('babyStatsCellContents');
    contents.textContent = tiles[1];
    cell.appendChild(contents);

    var overlay = document.createElement('babyStatsCellOverlay');
    cell.appendChild(overlay);

    cell.addEventListener('click', this.onClick_.bind(this, tiles[0], overlay));
  }, this);
};

/**
 * Handle a click event on a button.
 * @param {string} eventName short name of event to send
 * @param {Element} overlay element to make visible with countdown timer
 * @private
 */
BabyStats.prototype.onClick_ = function(eventName, overlay) {
  if (this.intervals_[eventName]) {
    window.clearInterval(this.intervals_[eventName]);
    delete this.intervals_[eventName];
    overlay.style.opacity = 0.0;
    return;
  }
  var timer = 5;
  overlay.textContent = timer;
  overlay.style.opacity = 0.5;
  this.intervals_[eventName] = window.setInterval(function() {
    timer--;
    switch (timer) {
      case 0:
        this.chat_.sendMessage({
          type: eventName,
        });
        overlay.textContent = '✓';
        break;

      case -1:
        break;

      case -2:
        window.clearInterval(this.intervals_[eventName]);
        delete this.intervals_[eventName];
        overlay.style.opacity = 0.0;
        break;

      default:
        overlay.textContent = timer;
        break;
    }
  }.bind(this), 1000);
};

/**
 * Calculate optimal grid sizing.
 * This pile of magic math calculates the optimal grid width and height to
 * maximize the size of all buttons while preserving their aspect ratio.
 * @return {{
 *   gridWidthCells: number,
 *   gridHeightCells: number,
 *   cellWidthPx: number,
 *   cellHeightPx: number
 * }}
 * @private
 */
BabyStats.prototype.calculateGrid_ = function() {
  var containerWidth = this.gridContainer_.offsetWidth;
  var containerHeight = this.gridContainer_.offsetHeight;
  var numTiles = this.tiles_.length;

  var heightFactor = containerHeight / this.tileScaleHeight_;
  var widthFactor = containerWidth / this.tileScaleWidth_;

  var scaleFactor = heightFactor / widthFactor;

  var gridHeight = Math.sqrt(scaleFactor * numTiles);
  var gridWidth = Math.sqrt(numTiles / scaleFactor);

  var gridOptions = [
    [Math.ceil(gridWidth), Math.floor(gridHeight)],
    [Math.floor(gridWidth), Math.ceil(gridHeight)],
    [Math.ceil(gridWidth), Math.ceil(gridHeight)],
  ];

  // Check all possible options.
  // We are optimizing for several dimensions (decreasing priority):
  // 1) Be able to fit all the tiles.
  // 2) Maximum scale for an image in each cell.
  // 3) Minimize number of cells.
  var minCells = Number.MAX_VALUE;
  var maxScale = 0.0;
  var chosenHeight, chosenWidth;
  gridOptions.forEach(function(gridOption) {
    var numCells = gridOption[0] * gridOption[1];
    if (numCells < numTiles) {
      // Can't fit all the tiles in (we've rounded down too far).
      return;
    }
    var widthScale = (containerWidth / gridOption[0]) / this.tileScaleWidth_;
    var heightScale = (containerHeight / gridOption[1]) / this.tileScaleHeight_;
    var scale;
    if (widthScale < heightScale) {
      scale = widthScale;
    } else {
      scale = heightScale;
    }
    if (scale < maxScale) {
      // This would make cells smaller than another viable solution.
      return;
    }
    if (scale == maxScale && numCells > minCells) {
      // Same cell size as another viable solution, but ours has more cells.
      return;
    }
    chosenWidth = gridOption[0];
    chosenHeight = gridOption[1];
    minCells = numCells;
    maxScale = scale;
  }, this);

  return /** @struct */ {
    gridWidthCells: chosenWidth,
    gridHeightCells: chosenHeight,
    cellWidthPx: this.tileScaleWidth_ * maxScale,
    cellHeightPx: this.tileScaleHeight_ * maxScale,
  };
};

/**
 * Construct the outer DOM layout.
 * @private
 */
BabyStats.prototype.buildLayout_ = function() {
  this.childName_ = document.createElement('input');
  this.addCSSClass_(this.childName_, 'babyStatsChildName');
  this.childName_.placeholder = 'Child name';
  this.childName_.addEventListener('input', this.checkOverlay_.bind(this));
  this.childName_.addEventListener('input', this.onChildNameChange_.bind(this));
  this.container_.appendChild(this.childName_);

  this.yourName_ = document.createElement('input');
  this.addCSSClass_(this.yourName_, 'babyStatsYourName');
  this.yourName_.placeholder = 'Your name';
  this.yourName_.value = localStorage.getItem('babyStats:yourName') || '';
  this.yourName_.addEventListener('input', this.checkOverlay_.bind(this));
  this.yourName_.addEventListener('input', this.onYourNameChange_.bind(this));
  this.container_.appendChild(this.yourName_);

  this.gridContainer_ = document.createElement('babyStatsGridContainer');
  this.container_.appendChild(this.gridContainer_);

  this.gridOverlay_ = document.createElement('babyStatsGridOverlay');
  this.container_.appendChild(this.gridOverlay_);

  this.checkOverlay_();
};

/**
 * Make the grid overlay visible/hidden based on input field status.
 * @private
 */
BabyStats.prototype.checkOverlay_ = function() {
  var message = '';
  if (!this.childName_.value) {
    message = 'Please enter child name above';
  } else if (!this.yourName_.value) {
    message = 'Please enter your name above';
  }

  if (message) {
    this.gridOverlay_.style.visibility = 'visible';
    this.gridOverlay_.style.opacity = 0.7;
    this.gridOverlay_.textContent = message;
  } else {
    this.gridOverlay_.style.visibility = 'hidden';
    this.gridOverlay_.style.opacity = 0.0;
  }
};

/**
 * @private
 */
BabyStats.prototype.onChildNameChange_ = function() {
  this.chat_.sendMessage({
    type: 'child_name_change',
    child_name: this.childName_.value,
  });
};

/**
 * Store your name value locally.
 * @private
 */
BabyStats.prototype.onYourNameChange_ = function() {
  localStorage.setItem('babyStats:yourName', this.yourName_.value);
};

/**
 * Construct the grid objects in the DOM.
 * @private
 */
BabyStats.prototype.buildGrid_ = function() {
  this.gridContainer_.innerHTML = '';

  this.rowRule_.style.height = 100 / this.gridHeightCells_ + '%';
  this.cellRule_.style.width = 100 / this.gridWidthCells_ + '%';

  var i = 0;
  for (var y = 0; y < this.gridHeightCells_; y++) {
    var row = document.createElement('babyStatsRow');
    for (var x = 0; x < this.gridWidthCells_; x++) {
      if (i < this.cells_.length) {
        var cell = this.cells_[i];
        row.appendChild(cell);
        i++;
      }
    }
    this.gridContainer_.appendChild(row);
  }
};
<|endoftext|>


<commit_before>var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

var players = {},
    swaps = [],
    swapSpeed = null;

function onYouTubeIframeAPIReady() {
    //  iframe ready
}

$(document).ready(function(){
    $('[data-load]').click(function(event){
        var target = $(event.currentTarget),
            videoBox = target.closest('.video-box'),
            playerID = videoBox.data('player-id'),
            playerEl = videoBox.find('#' + playerID),
            videoUrl = videoBox.find('[data-url]').val(),
            videoID = videoUrl.split('?v=')[1],
            player = players[playerID];

        if (!videoID) {
            return;
        }

        if (player) {
            if (player.getVolume() > 0) {
                return;
            }

            var wrapper = playerEl.closest('.player-wrapper');
            playerEl.remove();
            wrapper.append('

');
        }

        var player = new YT.Player(playerID, {
            height: '150px',
            width: '100%',
            videoId: videoID,
            events: {
                onReady: function(event) {
                    player.setVolume(0);
                    event.target.playVideo();
                }
            }
        });

        players[playerID] = player;
    });

    $('[data-swap]').change(function(event){
        var target = $(event.currentTarget),
            isChecked = target.is(':checked'),
            playerID = target.data('swap');

        if (isChecked) {
            swaps.push(playerID);
        }else{
            var index = swaps.indexOf(playerID);
            if (index > -1) {
                swaps.splice(index, 1);
            }
        }

        if (swaps.length == 2) {
            $('[data-swap]').each(function(index, el){
                el = $(el);
                if (_.indexOf(swaps, el.data('swap')) == -1) {
                    el.attr('disabled', true);
                }
            });

            if (players[swaps[0]] || players[swaps[1]]) {
                $('[data-do-swap]').removeAttr('disabled');
            }else{
                $('[data-do-swap]').attr('disabled', true);
            }
        }else{
            $('[data-swap]').each(function(index, el){
                $(el).removeAttr('disabled');
            });
            $('[data-do-swap]').attr('disabled', true);
        }
    });

    var swapVolumes = function(player1, player2, changeCount) {
        //  increased player1's and decreases player2's volumes
        changeCount = changeCount || 0;

        if (changeCount < 20) {
            if (changeCount == 0) {
                $('[data-do-swap]').attr('disabled', true);
                player1.setVolume(0);
                player1.unMute();
            }

            player1.setVolume(player1.getVolume() + 5);
            player2.setVolume(player2.getVolume() - 5);

            _.delay(function(){
                swapVolumes(player1, player2, changeCount + 1);
            }, swapSpeed);
        }else{
            player2.mute();

            swaps = [];
            $('[data-swap]').attr('checked', false);
        }
    };

    $('[data-do-swap]').click(function(event){
        var player1 = players[swaps[0]],
            player2 = players[swaps[1]];

        swapSpeed = parseInt($('[name="swap-speed"]:checked').val());

        if (player1.isMuted()) {
            swapVolumes(player1, player2);
        }else{
            swapVolumes(player2, player1);
        }
    });
});<commit_msg>enable checkboxes after swap<commit_after>var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

var players = {},
    swaps = [],
    swapSpeed = null;

function onYouTubeIframeAPIReady() {
    //  iframe ready
}

$(document).ready(function(){
    $('[data-load]').click(function(event){
        var target = $(event.currentTarget),
            videoBox = target.closest('.video-box'),
            playerID = videoBox.data('player-id'),
            playerEl = videoBox.find('#' + playerID),
            videoUrl = videoBox.find('[data-url]').val(),
            videoID = videoUrl.split('?v=')[1],
            player = players[playerID];

        if (!videoID) {
            return;
        }

        if (player) {
            if (player.getVolume() > 0) {
                return;
            }

            var wrapper = playerEl.closest('.player-wrapper');
            playerEl.remove();
            wrapper.append('

');
        }

        var player = new YT.Player(playerID, {
            height: '150px',
            width: '100%',
            videoId: videoID,
            events: {
                onReady: function(event) {
                    player.setVolume(0);
                    event.target.playVideo();
                }
            }
        });

        players[playerID] = player;
    });

    $('[data-swap]').change(function(event){
        var target = $(event.currentTarget),
            isChecked = target.is(':checked'),
            playerID = target.data('swap');

        if (isChecked) {
            swaps.push(playerID);
        }else{
            var index = swaps.indexOf(playerID);
            if (index > -1) {
                swaps.splice(index, 1);
            }
        }

        if (swaps.length == 2) {
            $('[data-swap]').each(function(index, el){
                el = $(el);
                if (_.indexOf(swaps, el.data('swap')) == -1) {
                    el.attr('disabled', true);
                }
            });

            if (players[swaps[0]] || players[swaps[1]]) {
                $('[data-do-swap]').removeAttr('disabled');
            }else{
                $('[data-do-swap]').attr('disabled', true);
            }
        }else{
            $('[data-swap]').each(function(index, el){
                $(el).removeAttr('disabled');
            });
            $('[data-do-swap]').attr('disabled', true);
        }
    });

    var swapVolumes = function(player1, player2, changeCount) {
        //  increased player1's and decreases player2's volumes
        changeCount = changeCount || 0;

        if (changeCount < 20) {
            if (changeCount == 0) {
                $('[data-do-swap]').attr('disabled', true);
                player1.setVolume(0);
                player1.unMute();
            }

            player1.setVolume(player1.getVolume() + 5);
            player2.setVolume(player2.getVolume() - 5);

            _.delay(function(){
                swapVolumes(player1, player2, changeCount + 1);
            }, swapSpeed);
        }else{
            player2.mute();

            swaps = [];
            $('[data-swap]').attr('checked', false);
            $('[data-swap]').removeAttr('disabled');
        }
    };

    $('[data-do-swap]').click(function(event){
        var player1 = players[swaps[0]],
            player2 = players[swaps[1]];

        swapSpeed = parseInt($('[name="swap-speed"]:checked').val());

        if (player1.isMuted()) {
            swapVolumes(player1, player2);
        }else{
            swapVolumes(player2, player1);
        }
    });
});<|endoftext|>


<commit_before>(function ($, Backbone, _, app, DataChannel) {

    var Socket = function (server) {
        this.server = server;
        this.ws = null;
        this.connected = new $.Deferred();
        this.open();
    };

    Socket.prototype = _.extend(Socket.prototype, Backbone.Events, {
        open: function () {
            if (this.ws === null) {
                this.ws = new WebSocket(this.server);
                this.ws.onopen = $.proxy(this.onopen, this);
                this.ws.onmessage = $.proxy(this.onmessage, this);
                this.ws.onclose = $.proxy(this.onclose, this);
                this.ws.onerror = $.proxy(this.onerror, this);
            }
            return this.connected;
        },
        close: function () {
            if (this.ws && this.ws.close) {
                this.ws.close();
            }
            this.ws = null;
            this.connected = new $.Deferred();
            this.trigger('closed');
        },
        onopen: function () {
            this.connected.resolve(true);
            this.trigger('open');
        },
        onmessage: function (message) {
            var result = JSON.parse(message.data);
            this.trigger('message', result, message);
        },
        onclose: function () {
            this.close();
        },
        onerror: function (error) {
            this.trigger('error', error);
            this.close();
        },
        send: function (message) {
            var self = this,
                payload = JSON.stringify(message);
            this.connected.done(function () {
                self.ws.send(payload);
            });
        }
    });

    var Room = Backbone.Model.extend({
        idAttribute: 'room',
        getChannel: function () {
            var self = this,
                channel = new DataChannel();
            channel.openSignalingChannel = function (config) {
                var socket = new Socket(self.get('socket'));
                if (config.onopen) setTimeout(config.onopen, 1000);
                socket.on('message', function (message, raw) {
                    config.onmessage(message);
                });
                socket.channel = config.channel || this.channel;
                return socket;
            };
            channel.autoCloseEntireSession = true;
            channel.autoSaveToDisk = false;
            return channel;
        }
    }),

    Rooms = Backbone.Collection.extend({
        model: Room,
        url: app.roomsURL
    });

    app.rooms = new Rooms();

})(jQuery, Backbone, _, app, DataChannel);
<commit_msg>Handle disconnect/reconnect for simple reboots.<commit_after>(function ($, Backbone, _, app, DataChannel) {

    var Socket = function (server) {
        this.server = server;
        this.ws = null;
        this.connected = new $.Deferred();
        this.open();
    };

    Socket.prototype = _.extend(Socket.prototype, Backbone.Events, {
        open: function () {
            if (this.ws === null) {
                this.ws = new WebSocket(this.server);
                this.ws.onopen = $.proxy(this.onopen, this);
                this.ws.onmessage = $.proxy(this.onmessage, this);
                this.ws.onclose = $.proxy(this.onclose, this);
                this.ws.onerror = $.proxy(this.onerror, this);
            }
            return this.connected;
        },
        close: function () {
            if (this.ws && this.ws.close) {
                this.ws.close();
            }
            this.ws = null;
            this.connected = new $.Deferred();
            this.trigger('closed');
        },
        onopen: function () {
            this.connected.resolve(true);
            this.trigger('open');
        },
        onmessage: function (message) {
            var result = JSON.parse(message.data);
            this.trigger('message', result, message);
        },
        onclose: function (e) {
            var next = null,
                step = 0,
                count = 0,
                callback;
            console.debug('Websocket connection closed. ', e.reason);
            this.close();
            if (4200 <= e.code && e.code < 4299) {
                // Fast reconnect with slow backoff
                next = 100;
                step = 10;
            } else if (4100 <= e.code && e.code < 4199) {
                // Reconnect after a few seconds and backoff quickly
                next = 2000;
                step = 1000;
            }
            if (next !== null) {
                this.on('open', function () {
                    next = null;
                    this.off('open');
                    this.off('error');
                }, this);
                callback = $.proxy(function () {
                    count = count + 1;
                    next = next + step * count;
                    if (count < 1000) {
                        console.debug('Connect retry number ', count);
                        this.open();
                    } else {
                        next = null;
                        console.debug('Giving up on reconnection after ', count, ' attempts.');
                    }
                }, this);
                this.on('error', function () {
                    setTimeout(callback, next);
                }, this);
                setTimeout(callback, next);
            } else {
                this.off('open');
                this.off('error');
            }
        },
        onerror: function (error) {
            this.trigger('error', error);
            this.close();
        },
        send: function (message) {
            var self = this,
                payload = JSON.stringify(message);
            this.connected.done(function () {
                self.ws.send(payload);
            });
        }
    });

    var Room = Backbone.Model.extend({
        idAttribute: 'room',
        getChannel: function () {
            var self = this,
                channel = new DataChannel();
            channel.openSignalingChannel = function (config) {
                var socket = new Socket(self.get('socket'));
                if (config.onopen) setTimeout(config.onopen, 1000);
                socket.on('message', function (message, raw) {
                    config.onmessage(message);
                });
                socket.channel = config.channel || this.channel;
                return socket;
            };
            channel.autoCloseEntireSession = true;
            channel.autoSaveToDisk = false;
            return channel;
        }
    }),

    Rooms = Backbone.Collection.extend({
        model: Room,
        url: app.roomsURL
    });

    app.rooms = new Rooms();

})(jQuery, Backbone, _, app, DataChannel);
<|endoftext|>


<commit_before>//--
// Copyright (c) 2012, Net-ng.
// All rights reserved.
//
// This software is licensed under the BSD License, as described in
// the file LICENSE.txt, which you should have received as part of
// this distribution.
//--

var requestsByAction = {};
var requestsById = {};

function hasRequest(action) {
    return !YAHOO.lang.isUndefined(requestsByAction[action]);
}

function addRequest(id, action) {
    requestsByAction[action] = id;
    requestsById[id] = action;
}

function removeRequest(id) {
    var action = requestsById[id];
    delete requestsById[id];
    delete requestsByAction[action];
}

// Callbacks
nagare_callbacks = {
    failure: function (o) {
        removeRequest(o.tId);
        var url = o.status ? o.getResponseHeader["X-Debug-URL"] : undefined;
        if (url) {
            window.location = url;
        } else {
            var data = YAHOO.lang.JSON.parse(o.responseText);
            YAHOO.kansha.app.syncError(data.status, data.details);
        }
    },

    success: function (o) {
        removeRequest(o.tId);
        if (o.responseText.substring(0, 1) !== "<") {
            setTimeout(o.responseText, 0);
        } else {
            YAHOO.kansha.app.syncError(500, 'Must reload all');
        }
    },

    upload: function (o) {
        removeRequest(o.tId);
        if (o.responseText.substring(0, 5) !== "URL: ") {
            var js = "", i;
            var js_fragments = o.responseXML.lastChild.lastChild.firstChild;

            for (i = 0; i < js_fragments.childNodes.length; i++) {
                js += js_fragments.childNodes[i].data;
            }
            setTimeout(js, 0);
        } else {
            var data = YAHOO.lang.JSON.parse(o.responseText);
            YAHOO.kansha.app.syncError(data.status, data.details);
        }
    }
};

// Callers
function nagare_getAndEval(href) {
    var actionId = /_action([0-9]+)/.exec(href)[1];
    YAHOO.util.Connect.initHeader("ACCEPT", NAGARE_CONTENT_TYPE);
    if (!hasRequest(actionId)) {
        var req = YAHOO.util.Connect.asyncRequest("GET", href, nagare_callbacks);
        addRequest(req.tId, actionId);
    }
    return false;
}

function nagare_hasUpload(f) {
    var inputs = f.getElementsByTagName("input"),
        i;
    for (i = 0; i < inputs.length; i++) {
        if (inputs[i].getAttribute("type") === "file") {
            return true;
        }
    }
    return false;
}

function nagare_postAndEval(f, action) {
    var actionId = /_action([0-9]+)/.exec(action)[1];
    YAHOO.util.Connect.initHeader("ACCEPT", NAGARE_CONTENT_TYPE);
    YAHOO.util.Connect.setForm(f, nagare_hasUpload(f));
    if (!hasRequest(actionId)) {
        var req = YAHOO.util.Connect.asyncRequest("POST", "?_a&" + action, nagare_callbacks);
        addRequest(req.tId, actionId);
    }
    return false;
}

function nagare_imageInputSubmit(event, button, action) {
    var evt = event || window.event;
    var xy = YAHOO.util.Dom.getXY(button);
    return nagare_postAndEval(button.form, action + ".x=" + (evt.clientX - xy[0]) + "&" + action + ".y=" + (evt.clientY - xy[1]));
}
<commit_msg>Don't allow parallel nagare XHRs. Fixes #216.<commit_after>//--
// Copyright (c) 2012, Net-ng.
// All rights reserved.
//
// This software is licensed under the BSD License, as described in
// the file LICENSE.txt, which you should have received as part of
// this distribution.
//--

var nagare_ongoing_request = false;

// Callbacks
nagare_callbacks = {
    failure: function (o) {
        nagare_ongoing_request = false;
        var url = o.status ? o.getResponseHeader["X-Debug-URL"] : undefined;
        if (url) {
            window.location = url;
        } else {
            var data = YAHOO.lang.JSON.parse(o.responseText);
            YAHOO.kansha.app.syncError(data.status, data.details);
        }
    },

    success: function (o) {
        nagare_ongoing_request = false;
        if (o.responseText.substring(0, 1) !== "<") {
            setTimeout(o.responseText, 0);
        } else {
            YAHOO.kansha.app.syncError(500, 'Must reload all');
        }
    },

    upload: function (o) {
        nagare_ongoing_request = false;
        if (o.responseText.substring(0, 5) !== "URL: ") {
            var js = "", i;
            var js_fragments = o.responseXML.lastChild.lastChild.firstChild;

            for (i = 0; i < js_fragments.childNodes.length; i++) {
                js += js_fragments.childNodes[i].data;
            }
            setTimeout(js, 0);
        } else {
            var data = YAHOO.lang.JSON.parse(o.responseText);
            YAHOO.kansha.app.syncError(data.status, data.details);
        }
    }
};

// Callers
function nagare_getAndEval(href) {
    YAHOO.util.Connect.initHeader("ACCEPT", NAGARE_CONTENT_TYPE);
    if (!nagare_ongoing_request) {
        var req = YAHOO.util.Connect.asyncRequest("GET", href, nagare_callbacks);
        nagare_ongoing_request = true;
    }
    return false;
}

function nagare_hasUpload(f) {
    var inputs = f.getElementsByTagName("input"),
        i;
    for (i = 0; i < inputs.length; i++) {
        if (inputs[i].getAttribute("type") === "file") {
            return true;
        }
    }
    return false;
}

function nagare_postAndEval(f, action) {
    YAHOO.util.Connect.initHeader("ACCEPT", NAGARE_CONTENT_TYPE);
    YAHOO.util.Connect.setForm(f, nagare_hasUpload(f));
    if (!nagare_ongoing_request) {
        var req = YAHOO.util.Connect.asyncRequest("POST", "?_a&" + action, nagare_callbacks);
        nagare_ongoing_request = true;
    }
    return false;
}

function nagare_imageInputSubmit(event, button, action) {
    var evt = event || window.event;
    var xy = YAHOO.util.Dom.getXY(button);
    return nagare_postAndEval(button.form, action + ".x=" + (evt.clientX - xy[0]) + "&" + action + ".y=" + (evt.clientY - xy[1]));
}
<|endoftext|>


<commit_before>import {addDays} from "date-fns";
import $ from "jquery";

import {localstorage} from "./localstorage";
import * as notifications from "./notifications";
import {page_params} from "./page_params";
import * as unread_ops from "./unread_ops";
import * as unread_ui from "./unread_ui";
import * as util from "./util";

/* This is called by resize.js, and thus indirectly when we trigger
 * resize events in the logic below. */
export function resize_app() {
    const panels_height = $("#panels").height();
    $("body > .app").height("calc(100% - " + panels_height + "px)");

    // the floating recipient bar is usually positioned right below
    // the `.header` element (including padding).
    const frb_top =
        panels_height +
        $(".header").height() +
        Number.parseInt($(".header").css("paddingBottom"), 10);
    $("#floating_recipient_bar").css("top", frb_top + "px");
}

const show_step = function ($process, step) {
    $process
        .find("[data-step]")
        .hide()
        .filter("[data-step=" + step + "]")
        .show();
};

const get_step = function ($process) {
    return $process.find("[data-step]:visible").data("step");
};

export function should_show_notifications(ls) {
    // if the user said to never show banner on this computer again, it will
    // be stored as `true` so we want to negate that.
    if (localstorage.supported() && ls.get("dontAskForNotifications") === true) {
        return false;
    }

    return (
        // notifications *basically* don't work on any mobile platforms, so don't
        // event show the banners. This prevents trying to access things that
        // don't exist like `Notification.permission`.
        !util.is_mobile() &&
        // if permission has not been granted yet.
        !notifications.granted_desktop_notifications_permission() &&
        // if permission is allowed to be requested (e.g. not in "denied" state).
        notifications.permission_state() !== "denied"
    );
}

export function should_show_server_upgrade_notification(ls) {
    // We do not show the server upgrade nag for a week after the user
    // clicked "dismiss".
    if (!localstorage.supported() || ls.get("lastUpgradeNagDismissalTime") === undefined) {
        return true;
    }

    const last_notification_dismissal_time = ls.get("lastUpgradeNagDismissalTime");

    const upgrade_nag_dismissal_duration = addDays(new Date(last_notification_dismissal_time), 7);

    // show the notification only if the time duration is completed.
    return Date.now() > upgrade_nag_dismissal_duration;
}

export function dismiss_upgrade_nag(ls) {
    $(".alert[data-process='server-needs-upgrade'").hide();
    if (localstorage.supported()) {
        ls.set("lastUpgradeNagDismissalTime", Date.now());
    }
}

export function check_profile_incomplete() {
    if (!page_params.is_admin) {
        return false;
    }

    // Eventually, we might also check page_params.realm_icon_source,
    // but it feels too aggressive to ask users to do change that
    // since their organization might not have a logo yet.
    if (
        page_params.realm_description === "" ||
        /^Organization imported from [A-Za-z]+[!.]$/.test(page_params.realm_description)
    ) {
        return true;
    }
    return false;
}

export function show_profile_incomplete(is_profile_incomplete) {
    if (is_profile_incomplete) {
        $("[data-process='profile-incomplete']").show();
    } else {
        $("[data-process='profile-incomplete']").hide();
    }
}

export function initialize() {
    const ls = localstorage();
    if (page_params.insecure_desktop_app) {
        open($("[data-process='insecure-desktop-app']"));
    } else if (page_params.server_needs_upgrade) {
        if (should_show_server_upgrade_notification(ls)) {
            open($("[data-process='server-needs-upgrade']"));
        }
    } else if (page_params.warn_no_email === true && page_params.is_admin) {
        // if email has not been set up and the user is the admin,
        // display a warning to tell them to set up an email server.
        open($("[data-process='email-server']"));
    } else if (should_show_notifications(ls)) {
        open($("[data-process='notifications']"));
    } else if (unread_ui.should_display_bankruptcy_banner()) {
        open($("[data-process='bankruptcy']"));
    } else if (check_profile_incomplete()) {
        open($("[data-process='profile-incomplete']"));
    }

    // Configure click handlers.
    $(".request-desktop-notifications").on("click", function (e) {
        e.preventDefault();
        $(this).closest(".alert").hide();
        notifications.request_desktop_notifications_permission();
        $(window).trigger("resize");
    });

    $(".reject-notifications").on("click", function () {
        $(this).closest(".alert").hide();
        ls.set("dontAskForNotifications", true);
        $(window).trigger("resize");
    });

    $(".accept-bankruptcy").on("click", function (e) {
        e.preventDefault();
        $(this).closest(".alert").hide();
        $(".bankruptcy-loader").show();
        setTimeout(unread_ops.mark_all_as_read, 1000);
        $(window).trigger("resize");
    });

    $(".dismiss-upgrade-nag").on("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        dismiss_upgrade_nag(ls);
    });

    $("#panels").on("click", ".alert .close, .alert .exit", function (e) {
        e.stopPropagation();
        const $process = $(e.target).closest("[data-process]");
        if (get_step($process) === 1 && $process.data("process") === "notifications") {
            show_step($process, 2);
        } else {
            $(this).closest(".alert").hide();
        }
        $(window).trigger("resize");
    });

    // Treat Enter with links in the panels UI focused like a click.,
    $("#panels").on("keyup", ".alert-link[role=button]", function (e) {
        e.stopPropagation();
        if (e.key === "Enter") {
            $(this).click();
        }
    });
}

export function open($process) {
    $("[data-process]").hide();
    $process.show();
    $(window).trigger("resize");
}
<commit_msg>panels: Replace deprecated jQuery event trigger shorthand.<commit_after>import {addDays} from "date-fns";
import $ from "jquery";

import {localstorage} from "./localstorage";
import * as notifications from "./notifications";
import {page_params} from "./page_params";
import * as unread_ops from "./unread_ops";
import * as unread_ui from "./unread_ui";
import * as util from "./util";

/* This is called by resize.js, and thus indirectly when we trigger
 * resize events in the logic below. */
export function resize_app() {
    const panels_height = $("#panels").height();
    $("body > .app").height("calc(100% - " + panels_height + "px)");

    // the floating recipient bar is usually positioned right below
    // the `.header` element (including padding).
    const frb_top =
        panels_height +
        $(".header").height() +
        Number.parseInt($(".header").css("paddingBottom"), 10);
    $("#floating_recipient_bar").css("top", frb_top + "px");
}

const show_step = function ($process, step) {
    $process
        .find("[data-step]")
        .hide()
        .filter("[data-step=" + step + "]")
        .show();
};

const get_step = function ($process) {
    return $process.find("[data-step]:visible").data("step");
};

export function should_show_notifications(ls) {
    // if the user said to never show banner on this computer again, it will
    // be stored as `true` so we want to negate that.
    if (localstorage.supported() && ls.get("dontAskForNotifications") === true) {
        return false;
    }

    return (
        // notifications *basically* don't work on any mobile platforms, so don't
        // event show the banners. This prevents trying to access things that
        // don't exist like `Notification.permission`.
        !util.is_mobile() &&
        // if permission has not been granted yet.
        !notifications.granted_desktop_notifications_permission() &&
        // if permission is allowed to be requested (e.g. not in "denied" state).
        notifications.permission_state() !== "denied"
    );
}

export function should_show_server_upgrade_notification(ls) {
    // We do not show the server upgrade nag for a week after the user
    // clicked "dismiss".
    if (!localstorage.supported() || ls.get("lastUpgradeNagDismissalTime") === undefined) {
        return true;
    }

    const last_notification_dismissal_time = ls.get("lastUpgradeNagDismissalTime");

    const upgrade_nag_dismissal_duration = addDays(new Date(last_notification_dismissal_time), 7);

    // show the notification only if the time duration is completed.
    return Date.now() > upgrade_nag_dismissal_duration;
}

export function dismiss_upgrade_nag(ls) {
    $(".alert[data-process='server-needs-upgrade'").hide();
    if (localstorage.supported()) {
        ls.set("lastUpgradeNagDismissalTime", Date.now());
    }
}

export function check_profile_incomplete() {
    if (!page_params.is_admin) {
        return false;
    }

    // Eventually, we might also check page_params.realm_icon_source,
    // but it feels too aggressive to ask users to do change that
    // since their organization might not have a logo yet.
    if (
        page_params.realm_description === "" ||
        /^Organization imported from [A-Za-z]+[!.]$/.test(page_params.realm_description)
    ) {
        return true;
    }
    return false;
}

export function show_profile_incomplete(is_profile_incomplete) {
    if (is_profile_incomplete) {
        $("[data-process='profile-incomplete']").show();
    } else {
        $("[data-process='profile-incomplete']").hide();
    }
}

export function initialize() {
    const ls = localstorage();
    if (page_params.insecure_desktop_app) {
        open($("[data-process='insecure-desktop-app']"));
    } else if (page_params.server_needs_upgrade) {
        if (should_show_server_upgrade_notification(ls)) {
            open($("[data-process='server-needs-upgrade']"));
        }
    } else if (page_params.warn_no_email === true && page_params.is_admin) {
        // if email has not been set up and the user is the admin,
        // display a warning to tell them to set up an email server.
        open($("[data-process='email-server']"));
    } else if (should_show_notifications(ls)) {
        open($("[data-process='notifications']"));
    } else if (unread_ui.should_display_bankruptcy_banner()) {
        open($("[data-process='bankruptcy']"));
    } else if (check_profile_incomplete()) {
        open($("[data-process='profile-incomplete']"));
    }

    // Configure click handlers.
    $(".request-desktop-notifications").on("click", function (e) {
        e.preventDefault();
        $(this).closest(".alert").hide();
        notifications.request_desktop_notifications_permission();
        $(window).trigger("resize");
    });

    $(".reject-notifications").on("click", function () {
        $(this).closest(".alert").hide();
        ls.set("dontAskForNotifications", true);
        $(window).trigger("resize");
    });

    $(".accept-bankruptcy").on("click", function (e) {
        e.preventDefault();
        $(this).closest(".alert").hide();
        $(".bankruptcy-loader").show();
        setTimeout(unread_ops.mark_all_as_read, 1000);
        $(window).trigger("resize");
    });

    $(".dismiss-upgrade-nag").on("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        dismiss_upgrade_nag(ls);
    });

    $("#panels").on("click", ".alert .close, .alert .exit", function (e) {
        e.stopPropagation();
        const $process = $(e.target).closest("[data-process]");
        if (get_step($process) === 1 && $process.data("process") === "notifications") {
            show_step($process, 2);
        } else {
            $(this).closest(".alert").hide();
        }
        $(window).trigger("resize");
    });

    // Treat Enter with links in the panels UI focused like a click.,
    $("#panels").on("keyup", ".alert-link[role=button]", function (e) {
        e.stopPropagation();
        if (e.key === "Enter") {
            $(this).trigger("click");
        }
    });
}

export function open($process) {
    $("[data-process]").hide();
    $process.show();
    $(window).trigger("resize");
}
<|endoftext|>


<commit_before>$(document).ready(function()
{
  $("#registerbutton").click(register_office);

  function register_office()
  {
    var url = "/offices/"+$("#office").attr("value");
    var res = $.post(url, {}, show_info);
  }

  function show_info(data, textstatus)
  {
    alert("Foo bar bla!" + data);
  }

});
<commit_msg>only return data on adding office<commit_after>$(document).ready(function()
{
  $("#registerbutton").click(register_office);

  function register_office()
  {
    var url = "/offices/"+$("#office").attr("value");
    var res = $.post(url, {}, show_info);
  }

  function show_info(data, textstatus)
  {
    alert(data);
  }

});
<|endoftext|>


<commit_before>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        geocoder = new google.maps.Geocoder(),
        youMarkerCollection = new Array(),
        needMarkerCollection = new Array(),
        userLocation,
        boundingBox = new google.maps.LatLngBounds(new google.maps.LatLng(39.8480851,-75.395736), new google.maps.LatLng(40.15211,-74.863586)),
        _self;

    var markerShadow = new google.maps.MarkerImage('/img/marker_shadow.png',
      new google.maps.Size(51, 37),
      new google.maps.Point(0, 0),
      new google.maps.Point(16, 37));

    _self = {
      create: function (options) {
        var settings = {
              mapId: 'map',
              zoom: 14,
              center: new google.maps.LatLng(39.95240, -75.16362),
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        if (options) {
          $.extend(settings, options);
        }
        map = new google.maps.Map(document.getElementById(settings.mapId), settings);
      },

      getShelters: function (userLocation, plot) {
        plot = true || plot;

        var lat = parseFloat(userLocation.lat());
        var lng = parseFloat(userLocation.lng());

        $.ajax({
          url: '_map?lat=' + lat + '&long=' + lng,
          success: function(data) {

            SH.state.needs = data;
            if (plot === true) {
              _self.addSheltersToMap(data);
            }
            SH.needs.list(SH.state.needs);
          }
        });

      },

      addSheltersToMap: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            options,
            icon,
            sheltersLength = shelters.result.length;

        _self.removeMarkers(needMarkerCollection);

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.result[i].Latitude;
          lng = shelters.result[i].Longitude;
          latlng = new google.maps.LatLng(lat, lng);

          icon = SH.map.prototype.selectMarkerIcon(shelters.result[i]);

          options = {icon: icon, shadow: markerShadow};

          _self.createMarker(latlng, shelters.result[i].Name + "
" + shelters.result[i].Address1, options, shelters.result[i].id);
        }
      },

      createMarker: function (latlng, description, options, id) {
        var settings = {
              position: latlng,
              map: map,
          };

        if (options) {
          $.extend(settings, options);
        }

        var marker = new google.maps.Marker(settings);

        google.maps.event.addListener(marker, 'click', function() {
          infoWindow.close();
          infoWindow.setContent(description);
          infoWindow.open(map,marker);
        });

        if (description === 'Your Location') { //TODO: this is fragile.
          google.maps.event.addListener(marker, 'dragend', function () {
            _self.getShelters(marker.getPosition(),false);
            _self.updateMapCenter(marker.getPosition());
          });

          _self.removeMarkers(youMarkerCollection)
          youMarkerCollection.push({"id": 'you', "marker": marker});
        } else {
          needMarkerCollection.push({"id": id, "marker": marker});
        }
      },

      orientUser: function () {
        //var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            _self.create({ center: userLocation });
            _self.getShelters(userLocation,true);
            _self.updateMapCenter(userLocation);
            _self.createMarker(userLocation, 'Your Location', {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }, function() {
            _self.create();
          });
        }

        return userLocation;
      },

      updateMapCenter: function (latLongObj) {
        map.setCenter(latLongObj);
      },

      geocode: function (addr, description) {
        var lat,
            lng,
            latlng;

        addr = addr + ', Philadelphia, PA'

        geocoder.geocode({
          'address': addr, 'bounds': boundingBox
        }, function (results, status) {

          if (status === google.maps.GeocoderStatus.OK) {

            if (boundingBox.contains(results[0].geometry.location)) {
              _self.createMarker(results[0].geometry.location, 'Your Location',  {
                animation: google.maps.Animation.DROP,
                draggable: true,
                icon: '/img/you.png',
                shadow: markerShadow
              });
              _self.updateMapCenter(results[0].geometry.location);
              _self.getShelters(results[0].geometry.location,false);
            } else {
              alert("Please restrict your search to the Philadelphia area.")
            }
          } else {
            alert("Search was not successful for the following reason: " + status);
          }
        });
      },

      removeMarkers: function(markerArray) {
        var i;

        if (markerArray) {
          for (i in markerArray) {
            markerArray[i].marker.setMap(null);
          }
          markerArray.length = 0;
        }
      },

    };

    SH.map.prototype.zoomToNeedMarker = function(needID) {
      var i,
      needsLength = needMarkerCollection.length;

      for (i = 0; i < needsLength; i++) {
        if(needID == needMarkerCollection[i].id) {
          map.setCenter(needMarkerCollection[i].marker.getPosition());
          map.setZoom(18)
          break;
        }
      }
    }

    SH.map.prototype.selectMarkerIcon = function(need) {
      var icon

      if (need.HasMeals === "Y") {
        icon = '/img/food.png';
      }
      if (need.IsShelter === "Y") {
        icon = '/img/shelter.png';
      }
      if (need.IsShelter === "Y" && need.HasMeals === "Y") {
        icon = '/img/shelter_food.png';
      }
      if (need.IsIntake === "Y") {
        icon = '/img/intake.png';
      } else {
        icon = '/img/shelter.png'; //Mill Creek Baptist Church currently doesn't meet any of these conditions. This will give it the shelter icon (I'm assuming its a shelter).
      }

      return icon;
    }

    return _self;
  };

})(jQuery);
<commit_msg>Added 'Get directions' link to need infowindows<commit_after>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        geocoder = new google.maps.Geocoder(),
        youMarkerCollection = new Array(),
        needMarkerCollection = new Array(),
        userLocation,
        boundingBox = new google.maps.LatLngBounds(new google.maps.LatLng(39.8480851,-75.395736), new google.maps.LatLng(40.15211,-74.863586)),
        _self;

    var markerShadow = new google.maps.MarkerImage('/img/marker_shadow.png',
      new google.maps.Size(51, 37),
      new google.maps.Point(0, 0),
      new google.maps.Point(16, 37));

    _self = {
      create: function (options) {
        var settings = {
              mapId: 'map',
              zoom: 14,
              center: new google.maps.LatLng(39.95240, -75.16362),
              mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        if (options) {
          $.extend(settings, options);
        }
        map = new google.maps.Map(document.getElementById(settings.mapId), settings);
      },

      getShelters: function (userLocation, plot) {
        plot = true || plot;

        var lat = parseFloat(userLocation.lat());
        var lng = parseFloat(userLocation.lng());

        $.ajax({
          url: '_map?lat=' + lat + '&long=' + lng,
          success: function(data) {

            SH.state.needs = data;
            if (plot === true) {
              _self.addSheltersToMap(data);
            }
            SH.needs.list(SH.state.needs);
          }
        });

      },

      addSheltersToMap: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            options,
            icon,
            description,
            sheltersLength = shelters.result.length;

        _self.removeMarkers(needMarkerCollection);

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.result[i].Latitude;
          lng = shelters.result[i].Longitude;
          latlng = new google.maps.LatLng(lat, lng);

          description = shelters.result[i].Name + "
" + shelters.result[i].Address1 + "
Get Directions"

          icon = SH.map.prototype.selectMarkerIcon(shelters.result[i]);
          options = {icon: icon, shadow: markerShadow};

          _self.createMarker(latlng, description, options, shelters.result[i].id);
        }
      },

      createMarker: function (latlng, description, options, id) {
        var settings = {
              position: latlng,
              map: map,
          };

        if (options) {
          $.extend(settings, options);
        }

        var marker = new google.maps.Marker(settings);

        google.maps.event.addListener(marker, 'click', function() {
          infoWindow.close();
          infoWindow.setContent(description);
          infoWindow.open(map,marker);
        });

        if (description === 'Your Location') { //TODO: this is fragile.
          google.maps.event.addListener(marker, 'dragend', function () {
            _self.getShelters(marker.getPosition(),false);
            _self.updateMapCenter(marker.getPosition());
          });

          _self.removeMarkers(youMarkerCollection)
          youMarkerCollection.push({"id": 'you', "marker": marker});
        } else {
          needMarkerCollection.push({"id": id, "marker": marker});
        }
      },

      orientUser: function () {
        //var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            _self.create({ center: userLocation });
            _self.getShelters(userLocation,true);
            _self.updateMapCenter(userLocation);
            _self.createMarker(userLocation, 'Your Location', {
              animation: google.maps.Animation.DROP,
              draggable: true,
              icon: '/img/you.png',
              shadow: markerShadow
            });
          }, function() {
            _self.create();
          });
        }

        return userLocation;
      },

      updateMapCenter: function (latLongObj) {
        map.setCenter(latLongObj);
      },

      geocode: function (addr, description) {
        var lat,
            lng,
            latlng;

        addr = addr + ', Philadelphia, PA'

        geocoder.geocode({
          'address': addr, 'bounds': boundingBox
        }, function (results, status) {

          if (status === google.maps.GeocoderStatus.OK) {

            if (boundingBox.contains(results[0].geometry.location)) {
              _self.createMarker(results[0].geometry.location, 'Your Location',  {
                animation: google.maps.Animation.DROP,
                draggable: true,
                icon: '/img/you.png',
                shadow: markerShadow
              });
              _self.updateMapCenter(results[0].geometry.location);
              _self.getShelters(results[0].geometry.location,false);
            } else {
              alert("Please restrict your search to the Philadelphia area.")
            }
          } else {
            alert("Search was not successful for the following reason: " + status);
          }
        });
      },

      removeMarkers: function(markerArray) {
        var i;

        if (markerArray) {
          for (i in markerArray) {
            markerArray[i].marker.setMap(null);
          }
          markerArray.length = 0;
        }
      },

    };

    SH.map.prototype.zoomToNeedMarker = function(needID) {
      var i,
      needsLength = needMarkerCollection.length;

      for (i = 0; i < needsLength; i++) {
        if(needID == needMarkerCollection[i].id) {
          map.setCenter(needMarkerCollection[i].marker.getPosition());
          map.setZoom(18)
          break;
        }
      }
    }

    SH.map.prototype.selectMarkerIcon = function(need) {
      var icon

      if (need.HasMeals === "Y") {
        icon = '/img/food.png';
      }
      if (need.IsShelter === "Y") {
        icon = '/img/shelter.png';
      }
      if (need.IsShelter === "Y" && need.HasMeals === "Y") {
        icon = '/img/shelter_food.png';
      }
      if (need.IsIntake === "Y") {
        icon = '/img/intake.png';
      } else {
        icon = '/img/shelter.png'; //Mill Creek Baptist Church currently doesn't meet any of these conditions. This will give it the shelter icon (I'm assuming its a shelter).
      }

      return icon;
    }

    return _self;
  };

})(jQuery);
<|endoftext|>


<commit_before>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        _self;

    _self = {
      create: function (options) {
        var settings = {
            mapId: 'map',
            zoom: 12,
				    center: new google.maps.LatLng(39.95240, -75.16362),
            mapTypeId: google.maps.MapTypeId.ROADMAP
          };

        if (options) {
          $.extend(settings, options);
        }
			  map = new google.maps.Map(document.getElementById(settings.mapId), settings);

      },

      addShelters: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            sheltersLength = shelters.shelters.length;

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.shelters[i].lat;
          lng = shelters.shelters[i].lng;
          latlng = new google.maps.LatLng(lat, lng);

				  this.createMarker(latlng, shelters.shelters[i].desc);
        }
      },

      createMarker: function (latlng, description) {
        marker = new google.maps.Marker({
          position: latlng,
          map: map
        });

        google.maps.event.addListener(marker, 'click', function() {
          infowindow.close();
          infowindow.setContent(description);
          infowindow.open(map,marker);
        });
      }
    };

    return _self;
  };
})(jQuery);
<commit_msg>Added userLocation method to sh.map for detectiong geo location.<commit_after>if (typeof SH === 'undefined' || !SH) {
  var SH = {};
}

(function($) {
  SH.map = function () {
    var map,
        infoWindow = new google.maps.InfoWindow(),
        _self;

    _self = {
      create: function (options) {
        var settings = {
            mapId: 'map',
            zoom: 12,
				    center: new google.maps.LatLng(39.95240, -75.16362),
            mapTypeId: google.maps.MapTypeId.ROADMAP
          };

        if (options) {
          $.extend(settings, options);
        }
			  map = new google.maps.Map(document.getElementById(settings.mapId), settings);
        console.log('map obj: ', map);
      },

      addShelters: function (shelters) {
        var i,
            lat,
            lng,
            latlng,
            sheltersLength = shelters.shelters.length;

        for (i=0; i<sheltersLength; i++) {
          lat = shelters.shelters[i].lat;
          lng = shelters.shelters[i].lng;
          latlng = new google.maps.LatLng(lat, lng);

				  this.createMarker(latlng, shelters.shelters[i].desc);
        }
      },

      createMarker: function (latlng, description) {
        marker = new google.maps.Marker({
          position: latlng,
          map: map
        });

        google.maps.event.addListener(marker, 'click', function() {
          infowindow.close();
          infowindow.setContent(description);
          infowindow.open(map,marker);
        });
      },

      locateUser: function () {
        var userLocation = null;

        // Try W3C Geolocation
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(function(position) {
            userLocation = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
            return userLocation;
          }, function() {
            return userLocation;
          });
        }
      }
    };

    return _self;
  };
})(jQuery);
<|endoftext|>


<commit_before>import Ember from 'ember';
import Component from 'ember-component';
import layout from '../../templates/components/power-select-multiple/trigger';
import get from 'ember-metal/get';
import computed from 'ember-computed';
import service from 'ember-service/inject';
import { scheduleOnce } from 'ember-runloop';
import { assert } from 'ember-metal/utils';
import { isBlank } from 'ember-utils';
import { htmlSafe } from 'ember-string';

const { testing } = Ember;

const ua = self.window && self.window.navigator ? self.window.navigator.userAgent : '';
const isIE = ua.indexOf('MSIE ') > -1 || ua.indexOf('Trident/') > -1;
const isTouchDevice = (testing || !!self.window && 'ontouchstart' in self.window);

export default Component.extend({
  tagName: '',
  layout,
  textMeasurer: service(),
  _lastIsOpen: false,

  // Lifecycle hooks
  didInsertElement() {
    this._super(...arguments);
    let select = this.get('select');
    this.input = document.getElementById(`ember-power-select-trigger-multiple-input-${select.uniqueId}`);
    let inputStyle = this.input ? window.getComputedStyle(this.input) : null;
    this.inputFont = inputStyle ? `${ inputStyle.fontStyle } ${  inputStyle.fontVariant} ${ inputStyle.fontWeight } ${ inputStyle.fontSize}/${ inputStyle.lineHeight } ${ inputStyle.fontFamily }` : null;
    let optionsList = document.getElementById(`ember-power-select-multiple-options-${select.uniqueId}`);
    let chooseOption = (e) => {
      let selectedIndex = e.target.getAttribute('data-selected-index');
      if (selectedIndex) {
        e.stopPropagation();
        e.preventDefault();

        let select = this.get('select');
        let object = this.selectedObject(select.selected, selectedIndex);
        select.actions.choose(object);
      }
    };
    if (isTouchDevice) {
      optionsList.addEventListener('touchstart', chooseOption);
    }
    optionsList.addEventListener('mousedown', chooseOption);
  },

  didReceiveAttrs() {
    let oldSelect = this.get('oldSelect') || {};
    let select = this.set('oldSelect', this.get('select'));
    if (oldSelect.isOpen && !select.isOpen) {
      scheduleOnce('actions', null, select.actions.search, '');
    }
  },

  // CPs
  triggerMultipleInputStyle: computed('select.searchText.length', 'select.selected.length', function() {
    let select = this.get('select');
    select.actions.reposition();
    if (!select.selected || select.selected.length === 0) {
      return htmlSafe('width: 100%;');
    } else {
      let textWidth = 0;
      if (this.inputFont) {
        textWidth = this.get('textMeasurer').width(select.searchText, this.inputFont);
      }
      return htmlSafe(`width: ${textWidth + 25}px`);
    }
  }),

  maybePlaceholder: computed('placeholder', 'select.selected.length', function() {
    if (isIE) {
      return null;
    }
    let select = this.get('select');
    return (!select.selected || get(select.selected, 'length') === 0) ? (this.get('placeholder') || '') : '';
  }),

  // Actions
  actions: {
    onInput(e) {
      let action = this.get('onInput');
      if (action &&  action(e) === false) {
        return;
      }
      this.get('select').actions.open(e);
    },

    onKeydown(e) {
      let { onKeydown, select } = this.getProperties('onKeydown', 'select');
      if (onKeydown && onKeydown(e) === false) {
        e.stopPropagation();
        return false;
      }
      if (e.keyCode === 8) {
        e.stopPropagation();
        if (isBlank(e.target.value)) {
          let lastSelection = select.selected[select.selected.length - 1];
          if (lastSelection) {
            select.actions.select(this.get('buildSelection')(lastSelection, select), e);
            if (typeof lastSelection === 'string') {
              select.actions.search(lastSelection);
            } else {
              let searchField = this.get('searchField');
              assert('`{{power-select-multiple}}` requires a `searchField` when the options are not strings to remove options using backspace', searchField);
              select.actions.search(get(lastSelection, searchField));
            }
            select.actions.open(e);
          }
        }
      } else if (e.keyCode >= 48 && e.keyCode <= 90 || e.keyCode === 32) { // Keys 0-9, a-z or SPACE
        e.stopPropagation();
      }
    }
  },

  // Methods
  selectedObject(list, index) {
    if (list.objectAt) {
      return list.objectAt(index);
    } else {
      return get(list, index);
    }
  }
});
<commit_msg>Fix double-render issue in multiple select<commit_after>import Ember from 'ember';
import Component from 'ember-component';
import layout from '../../templates/components/power-select-multiple/trigger';
import get from 'ember-metal/get';
import computed from 'ember-computed';
import service from 'ember-service/inject';
import { scheduleOnce } from 'ember-runloop';
import { assert } from 'ember-metal/utils';
import { isBlank } from 'ember-utils';
import { htmlSafe } from 'ember-string';

const { testing } = Ember;

const ua = self.window && self.window.navigator ? self.window.navigator.userAgent : '';
const isIE = ua.indexOf('MSIE ') > -1 || ua.indexOf('Trident/') > -1;
const isTouchDevice = (testing || !!self.window && 'ontouchstart' in self.window);

export default Component.extend({
  tagName: '',
  layout,
  textMeasurer: service(),
  _lastIsOpen: false,

  // Lifecycle hooks
  didInsertElement() {
    this._super(...arguments);
    let select = this.get('select');
    this.input = document.getElementById(`ember-power-select-trigger-multiple-input-${select.uniqueId}`);
    let inputStyle = this.input ? window.getComputedStyle(this.input) : null;
    this.inputFont = inputStyle ? `${ inputStyle.fontStyle } ${  inputStyle.fontVariant} ${ inputStyle.fontWeight } ${ inputStyle.fontSize}/${ inputStyle.lineHeight } ${ inputStyle.fontFamily }` : null;
    let optionsList = document.getElementById(`ember-power-select-multiple-options-${select.uniqueId}`);
    let chooseOption = (e) => {
      let selectedIndex = e.target.getAttribute('data-selected-index');
      if (selectedIndex) {
        e.stopPropagation();
        e.preventDefault();

        let select = this.get('select');
        let object = this.selectedObject(select.selected, selectedIndex);
        select.actions.choose(object);
      }
    };
    if (isTouchDevice) {
      optionsList.addEventListener('touchstart', chooseOption);
    }
    optionsList.addEventListener('mousedown', chooseOption);
  },

  didReceiveAttrs() {
    let oldSelect = this.get('oldSelect') || {};
    let select = this.set('oldSelect', this.get('select'));
    if (oldSelect.isOpen && !select.isOpen) {
      scheduleOnce('actions', null, select.actions.search, '');
    }
  },

  // CPs
  triggerMultipleInputStyle: computed('select.searchText.length', 'select.selected.length', function() {
    let select = this.get('select');
    scheduleOnce('actions', select.actions.reposition);
    if (!select.selected || select.selected.length === 0) {
      return htmlSafe('width: 100%;');
    } else {
      let textWidth = 0;
      if (this.inputFont) {
        textWidth = this.get('textMeasurer').width(select.searchText, this.inputFont);
      }
      return htmlSafe(`width: ${textWidth + 25}px`);
    }
  }),

  maybePlaceholder: computed('placeholder', 'select.selected.length', function() {
    if (isIE) {
      return null;
    }
    let select = this.get('select');
    return (!select.selected || get(select.selected, 'length') === 0) ? (this.get('placeholder') || '') : '';
  }),

  // Actions
  actions: {
    onInput(e) {
      let action = this.get('onInput');
      if (action &&  action(e) === false) {
        return;
      }
      this.get('select').actions.open(e);
    },

    onKeydown(e) {
      let { onKeydown, select } = this.getProperties('onKeydown', 'select');
      if (onKeydown && onKeydown(e) === false) {
        e.stopPropagation();
        return false;
      }
      if (e.keyCode === 8) {
        e.stopPropagation();
        if (isBlank(e.target.value)) {
          let lastSelection = select.selected[select.selected.length - 1];
          if (lastSelection) {
            select.actions.select(this.get('buildSelection')(lastSelection, select), e);
            if (typeof lastSelection === 'string') {
              select.actions.search(lastSelection);
            } else {
              let searchField = this.get('searchField');
              assert('`{{power-select-multiple}}` requires a `searchField` when the options are not strings to remove options using backspace', searchField);
              select.actions.search(get(lastSelection, searchField));
            }
            select.actions.open(e);
          }
        }
      } else if (e.keyCode >= 48 && e.keyCode <= 90 || e.keyCode === 32) { // Keys 0-9, a-z or SPACE
        e.stopPropagation();
      }
    }
  },

  // Methods
  selectedObject(list, index) {
    if (list.objectAt) {
      return list.objectAt(index);
    } else {
      return get(list, index);
    }
  }
});
<|endoftext|>


<commit_before>/* jshint node: true */
/* jshint esnext: true */
/* global describe, beforeAll, beforeEach, afterEach, afterAll, it, expect, xit*/
'use strict';

const helper = require('./journeyHelper');
const config = require('config');
const crypto = require('crypto');
const participants = require('../service/participants');
const registration = require('../service/registration');
const participant = require('../domain/participant');
const _ = require('lodash');

describe('admin page', () => {

  let loginUrl = helper.paceUrl + 'login';
  let originalRegistrationStatus;

  beforeAll((done) => {
    registration.isClosed().then(isClosed => {
      originalRegistrationStatus = isClosed;
      done();
    });
  });

  beforeEach((done) => {
    helper.changeOriginalTimeout();
    helper.setupDbConnection(done);
  });

  afterEach((done) => {
    helper.resetToOriginalTimeout();
    helper.closeDbConnection(done);
  });

  afterAll((done) => {
    if (originalRegistrationStatus) {
      registration.close().then(() => {
        done();
      });
    } else {
      registration.reopen().then(() => {
        done();
      });
    }
  });

  let loginAdmin = () => {
    return helper.setUpClient().url(loginUrl)
      .setValue('input#username', config.get('admin.username'))
      .setValue('input#password', config.get('admin.password'))
      .click('button#submit');
  };

  it('should go to admin page, show statistics and generate start number buttons (registered and on-site)', (done) => {
    loginAdmin().url(helper.paceUrl + 'admin')
      .isVisible('canvas#registrationsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('canvas#regularShirtsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('canvas#slimShirtsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('canvas#participantsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('button#generate-start-numbers')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('button#generate-on-site-participants')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('input#amountOnSite')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('span#currentNumOfOnSite')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  it('should close and reopen the registration', (done) => {
    loginAdmin().url(helper.paceUrl + 'admin')
      .click('button#close-registration')
      .isVisible('p#registration-closed-message')
      .then((isVisible) => {
        expect(isVisible).toBe(true);
      })
      .click('button#reopen-registration')
      .isVisible('canvas#registrationsCtx')
      .then((isVisible) => {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  it('should redirect to login page if the user is not logged in', (done) => {
    helper.setUpClient().url(helper.paceUrl + 'admin')
      .isVisible('form#loginForm')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  it('should redirect to the start page after logout', (done) => {
    helper.setUpClient().url(helper.paceUrl + 'logout')
      .isVisible('h3*=Stadtpark-Staffelmarathon')
      .then((isVisible) => {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  let givenAValidUserExists = () => {
    let randomString = crypto.randomBytes(8).toString('hex');

    let aParticipant = participant.from({
      firstname: 'Friedrich',
      lastname: 'Schiller',
      email: randomString + '@example.com',
      category: 'f',
      birthyear: 1980,
      team: 'Crazy runners',
      visibility: 'no'
    }).withToken(randomString).withSecureId('secureIdForTheEditLink').withStartNr(10);

    return participants.save(aParticipant);
  };

  it('should go to edit user when clicking edit button (admin is signed in)', (done) => {
    var firstName = 'not set yet';
    var lastName = 'not set yet';

    givenAValidUserExists().then(() => {
      loginAdmin().url(helper.paceUrl + 'admin/participants')
        .getText('.first-name')
        .then((firstNames) => {
          firstName = _.isArray(firstNames) ? firstNames[0] : firstNames;
        })
        .getText('.last-name')
        .then((lastNames) => {
          lastName = _.isArray(lastNames) ? lastNames[0] : lastNames;
        })
        .click('a.edit-button')
        .isVisible('#firstname')
        .then(isVisible => {
          expect(isVisible).toBe(true);
        })
        .getValue('#firstname')
        .then((value) => {
          expect(value).toBe(firstName);
        })
        .getValue('#lastname')
        .then((value) => {
          expect(value).toBe(lastName);
        })
        .end(done);
    });
  });

  it('should redirect to login page when an unauthenticated user requests admin edit page', (done) => {
    givenAValidUserExists().then(() => {
      helper.setUpClient().url(helper.paceUrl + 'admin/editparticipant/secureIdForTheEditLink')
        .isVisible('form#loginForm')
        .then(function (isVisible) {
          expect(isVisible).toBe(true);
        })
        .end(done);
    });
  });

  it('should be able to define the color, name and start time of a block', (done) => {
    loginAdmin().url(helper.paceUrl + 'admin/after')
      .setValue('input#color0', '#cafe00')
      .setValue('input#name0', 'Startblock Rot')
      .setValue('input#hours0', '10')
      .setValue('input#minutes0', '15')
      .setValue('input#seconds0', '10')
      .click('button#set_race_starttime')
      .isVisible('input#hours1')
      .then(isVisible => {
          expect(isVisible).toBe(true);
        })
      .getValue('input#color1')
      .then((value) => {
        expect(value).toBe('#cafe00');
      })
      .getValue('input#name1')
      .then((value) => {
        expect(value).toBe('Startblock Rot');
      })
      .getValue('input#hours1')
      .then((value) => {
        expect(value).toBe('10');
      })
      .getValue('input#minutes1')
      .then((value) => {
        expect(value).toBe('15');
      })
      .getValue('input#seconds1')
      .then((value) => {
        expect(value).toBe('10');
      })
      .end(done);
  });

});
<commit_msg>fcking test<commit_after>/* jshint node: true */
/* jshint esnext: true */
/* global describe, beforeAll, beforeEach, afterEach, afterAll, it, expect, xit*/
'use strict';

const helper = require('./journeyHelper');
const config = require('config');
const crypto = require('crypto');
const participants = require('../service/participants');
const registration = require('../service/registration');
const participant = require('../domain/participant');
const _ = require('lodash');

describe('admin page', () => {

  let loginUrl = helper.paceUrl + 'login';
  let originalRegistrationStatus;

  beforeAll((done) => {
    registration.isClosed().then(isClosed => {
      originalRegistrationStatus = isClosed;
      done();
    });
  });

  beforeEach((done) => {
    helper.changeOriginalTimeout();
    helper.setupDbConnection(done);
  });

  afterEach((done) => {
    helper.resetToOriginalTimeout();
    helper.closeDbConnection(done);
  });

  afterAll((done) => {
    if (originalRegistrationStatus) {
      registration.close().then(() => {
        done();
      });
    } else {
      registration.reopen().then(() => {
        done();
      });
    }
  });

  let loginAdmin = () => {
    return helper.setUpClient().url(loginUrl)
      .setValue('input#username', config.get('admin.username'))
      .setValue('input#password', config.get('admin.password'))
      .click('button#submit');
  };

  it('should go to admin page, show statistics and generate start number buttons (registered and on-site)', (done) => {
    loginAdmin().url(helper.paceUrl + 'admin')
      .isVisible('canvas#registrationsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('canvas#regularShirtsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('canvas#slimShirtsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('canvas#participantsCtx')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('button#generate-start-numbers')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('button#generate-on-site-participants')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('input#amountOnSite')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .isVisible('span#currentNumOfOnSite')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  it('should close and reopen the registration', (done) => {
    loginAdmin().url(helper.paceUrl + 'admin')
      .click('button#close-registration')
      .isVisible('p#registration-closed-message')
      .then((isVisible) => {
        expect(isVisible).toBe(true);
      })
      .click('button#reopen-registration')
      .isVisible('canvas#registrationsCtx')
      .then((isVisible) => {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  it('should redirect to login page if the user is not logged in', (done) => {
    helper.setUpClient().url(helper.paceUrl + 'admin')
      .isVisible('form#loginForm')
      .then(function (isVisible) {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  it('should redirect to the start page after logout', (done) => {
    helper.setUpClient().url(helper.paceUrl + 'logout')
      .isVisible('h3*=Stadtpark-Staffelmarathon')
      .then((isVisible) => {
        expect(isVisible).toBe(true);
      })
      .end(done);
  });

  let givenAValidUserExists = () => {
    let randomString = crypto.randomBytes(8).toString('hex');

    let aParticipant = participant.from({
      firstname: 'Friedrich',
      lastname: 'Schiller',
      email: randomString + '@example.com',
      category: 'f',
      birthyear: 1980,
      team: 'Crazy runners',
      visibility: 'no'
    }).withToken(randomString).withSecureId('secureIdForTheEditLink').withStartNr(10);

    return participants.save(aParticipant);
  };

  it('should go to edit user when clicking edit button (admin is signed in)', (done) => {
    var firstName = 'not set yet';
    var lastName = 'not set yet';

    givenAValidUserExists().then(() => {
      loginAdmin().url(helper.paceUrl + 'admin/participants')
        .getText('.first-name')
        .then((firstNames) => {
          firstName = _.isArray(firstNames) ? firstNames[0] : firstNames;
        })
        .getText('.last-name')
        .then((lastNames) => {
          lastName = _.isArray(lastNames) ? lastNames[0] : lastNames;
        })
        .click('a.edit-button')
        .isVisible('#firstname')
        .then(isVisible => {
          expect(isVisible).toBe(true);
        })
        .getValue('#firstname')
        .then((value) => {
          expect(value).toBe(firstName);
        })
        .getValue('#lastname')
        .then((value) => {
          expect(value).toBe(lastName);
        })
        .end(done);
    });
  });

  it('should redirect to login page when an unauthenticated user requests admin edit page', (done) => {
    givenAValidUserExists().then(() => {
      helper.setUpClient().url(helper.paceUrl + 'admin/editparticipant/secureIdForTheEditLink')
        .isVisible('form#loginForm')
        .then(function (isVisible) {
          expect(isVisible).toBe(true);
        })
        .end(done);
    });
  });

  xit('should be able to define the color, name and start time of a block', (done) => {
    loginAdmin().url(helper.paceUrl + 'admin/after')
      .setValue('input#color0', '#cafe00')
      .setValue('input#name0', 'Startblock Rot')
      .setValue('input#hours0', '10')
      .setValue('input#minutes0', '15')
      .setValue('input#seconds0', '10')
      .click('button#set_race_starttime')
      .isVisible('input#hours1')
      .then(isVisible => {
          expect(isVisible).toBe(true);
        })
      .getValue('input#color1')
      .then((value) => {
        expect(value).toBe('#cafe00');
      })
      .getValue('input#name1')
      .then((value) => {
        expect(value).toBe('Startblock Rot');
      })
      .getValue('input#hours1')
      .then((value) => {
        expect(value).toBe('10');
      })
      .getValue('input#minutes1')
      .then((value) => {
        expect(value).toBe('15');
      })
      .getValue('input#seconds1')
      .then((value) => {
        expect(value).toBe('10');
      })
      .end(done);
  });

});
<|endoftext|>


<commit_before><commit_msg>Add describeServices api<commit_after><|endoftext|>