<commit_before>/*
 * Copyright (C) 2017 Alasdair Mercer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

'use strict';

/* eslint "no-process-exit": "off" */

const chalk = require('chalk');
const Command = require('commander').Command;
const debug = require('debug')('throne:cli');
const EOL = require('os').EOL;
const groupBy = require('lodash.groupby');
const rightPad = require('right-pad');
const trim = require('lodash.trim');

const pkg = require('../package.json');
const Throne = require('./Throne');

const _checkServices = Symbol('checkServices');
const _command = Symbol('command');
const _createFilter = Symbol('createFilter');
const _errorStream = Symbol('errorStream');
const _listServices = Symbol('listServices');
const _outputStream = Symbol('outputStream');
const _printServiceStatus = Symbol('printServiceStatus');
const _sanitizeForSearch = Symbol('sanitizeForSearch');
const _throne = Symbol('throne');

/**
 * TODO: Document
 */
class CLI {

  static [_createFilter](category, service) {
    category = CLI[_sanitizeForSearch](category);
    service = CLI[_sanitizeForSearch](service);

    return (descriptor) => {
      if (category && category !== CLI[_sanitizeForSearch](descriptor.category)) {
        return false;
      }
      if (service && service !== CLI[_sanitizeForSearch](descriptor.title)) {
        return false;
      }
      return true;
    };
  }

  static [_sanitizeForSearch](str) {
    return str.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
  }

  /**
   * TODO: Document
   *
   * @param {CLI~Options} [options] -
   * @public
   */
  constructor(options) {
    if (!options) {
      options = {};
    }

    this[_errorStream] = options.errorStream || process.stderr;
    this[_outputStream] = options.outputStream || process.stdout;
    this[_command] = new Command()
      .arguments('<name>')
      .version(pkg.version)
      .option('-c, --category <name>', 'only check name for services in category')
      .option('-l, --list', 'list available services and categories')
      .option('-s, --service <title>', 'only check name for service')
      .option('-t, --timeout <ms>', 'control timeout for individual service checks', parseInt);
    this[_throne] = new Throne();
  }

  /**
   * TODO: Document
   *
   * @param {string|string[]} [args] -
   * @return {void}
   * @public
   */
  parse(args) {
    if (!args) {
      args = [];
    }

    args = Array.isArray(args) ? args : [ args ];

    debug('Parsing arguments: %o', args);

    const command = this[_command].parse(args);
    const name = trim(command.args[0]);

    if (command.list) {
      this[_listServices]();
    } else if (name) {
      this[_checkServices](name, trim(command.category), trim(command.service), command.timeout);
    } else {
      command.outputHelp();

      process.exit(0);
    }
  }

  [_checkServices](name, category, service, timeout) {
    let maxLength = 0;

    this[_throne].addListener('check', (event) => {
      this[_outputStream].write(`Checking availability of name: ${name}${EOL}${EOL}`);

      maxLength = event.services.reduce((acc, descriptor) => {
        return Math.max(acc, `${descriptor.category} > ${descriptor.title}`.length);
      }, 0);

      debug('Calculated maximum length for service descriptor: %d', maxLength);
    });

    this[_throne].addListener('checkservice', (event) => {
      this[_printServiceStatus](event, chalk.dim('CHECKING'), maxLength);
    });

    this[_throne].addListener('result', (event) => {
      let status;
      if (event.error) {
        status = chalk.red('FAILED');
      } else if (event.available == null) {
        status = chalk.yellow('UNKNOWN');
      } else if (event.available) {
        status = chalk.green('AVAILABLE');
      } else {
        status = chalk.yellow('TAKEN');
      }

      this[_outputStream].clearLine();
      this[_outputStream].cursorTo(0);
      this[_printServiceStatus](event, status, maxLength, true);
    });

    this[_throne].addListener('report', (event) => {
      this[_outputStream].write(`${EOL}${chalk.underline('Summary:')}${EOL}`);
      this[_outputStream].write(`Available on ${event.stats.available}/${event.stats.total} services${EOL}`);
      if (event.stats.failed) {
        this[_outputStream].write(`${event.stats.failed}/${event.stats.total} services failed!${EOL}`);
      } else {
        this[_outputStream].write(`No services failed!${EOL}`);
      }

      if (event.unique) {
        this[_outputStream].write(`It's truly unique. Grab it quick!${EOL}`);
      }
    });

    return this[_throne].check(name, { filter: CLI[_createFilter](category, service), timeout })
      .then(() => {
        process.exit(0);
      })
      .catch((error) => {
        this[_errorStream].write(`Throne failed: ${error.message}${EOL}`);

        process.exit(1);
      });
  }

  [_listServices]() {
    return this[_throne].getServiceDescriptors()
      .then((descriptors) => {
        const categorizedDescriptors = groupBy(descriptors, 'category');
        const categories = Object.keys(categorizedDescriptors).sort();

        this[_outputStream].write(`${descriptors.length} services found within ${categories.length} categories!` +
          `${EOL}${EOL}`);

        categories.forEach((category) => {
          this[_outputStream].write(`${category}:${EOL}`);

          categorizedDescriptors[category].forEach((descriptor) => {
            this[_outputStream].write(` - ${descriptor.title}${EOL}`);
          });
        });

        process.exit(0);
      })
      .catch((error) => {
        this[_errorStream].write(`Throne failed: ${error.message}${EOL}`);

        process.exit(1);
      });
  }

  [_printServiceStatus](descriptor, status, maxLength, endLine) {
    let output = rightPad(`${descriptor.category} > ${descriptor.title}`, maxLength + 6, '.');
    output += chalk.bold(status);
    if (endLine) {
      output += EOL;
    }

    this[_outputStream].write(output);
  }

}

module.exports = CLI;

/**
 * TODO: Document
 *
 * @typedef {Object} CLI~Options
 * @property {Writable} [errorStream=process.stderr] -
 * @property {Writable} [outputStream=process.stdout] -
 */
<commit_msg>adding support for passing multiple categories and services to CLI at once<commit_after>/*
 * Copyright (C) 2017 Alasdair Mercer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

'use strict';

/* eslint "no-process-exit": "off" */

const chalk = require('chalk');
const Command = require('commander').Command;
const debug = require('debug')('throne:cli');
const EOL = require('os').EOL;
const groupBy = require('lodash.groupby');
const rightPad = require('right-pad');
const trim = require('lodash.trim');

const pkg = require('../package.json');
const Throne = require('./Throne');

const _checkServices = Symbol('checkServices');
const _command = Symbol('command');
const _createFilter = Symbol('createFilter');
const _errorStream = Symbol('errorStream');
const _listServices = Symbol('listServices');
const _outputStream = Symbol('outputStream');
const _printServiceStatus = Symbol('printServiceStatus');
const _sanitizeForSearch = Symbol('sanitizeForSearch');
const _throne = Symbol('throne');

/**
 * TODO: Document
 */
class CLI {

  static [_createFilter](categories, services) {
    categories = categories.map((category) => CLI[_sanitizeForSearch](trim(category)));
    services = services.map((service) => CLI[_sanitizeForSearch](trim(service)));

    return (descriptor) => {
      if (categories.length > 0 && categories.indexOf(CLI[_sanitizeForSearch](descriptor.category)) === -1) {
        return false;
      }
      if (services.length > 0 && services.indexOf(CLI[_sanitizeForSearch](descriptor.title)) === -1) {
        return false;
      }
      return true;
    };
  }

  static [_sanitizeForSearch](str) {
    return str.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
  }

  /**
   * TODO: Document
   *
   * @param {CLI~Options} [options] -
   * @public
   */
  constructor(options) {
    if (!options) {
      options = {};
    }

    this[_errorStream] = options.errorStream || process.stderr;
    this[_outputStream] = options.outputStream || process.stdout;
    this[_command] = new Command()
      .arguments('<name>')
      .version(pkg.version)
      .option('-c, --category <name>', 'only check name for services in category', (c, list) => list.concat(c), [])
      .option('-l, --list', 'list available services and categories')
      .option('-s, --service <title>', 'only check name for service', (s, list) => list.concat(s), [])
      .option('-t, --timeout <ms>', 'control timeout for individual service checks', parseInt);
    this[_throne] = new Throne();
  }

  /**
   * TODO: Document
   *
   * @param {string|string[]} [args] -
   * @return {void}
   * @public
   */
  parse(args) {
    if (!args) {
      args = [];
    }

    args = Array.isArray(args) ? args : [ args ];

    debug('Parsing arguments: %o', args);

    const command = this[_command].parse(args);
    const name = trim(command.args[0]);

    if (command.list) {
      this[_listServices]();
    } else if (name) {
      this[_checkServices](name, command.category, command.service, command.timeout);
    } else {
      command.outputHelp();

      process.exit(0);
    }
  }

  [_checkServices](name, categories, services, timeout) {
    let maxLength = 0;

    this[_throne].addListener('check', (event) => {
      this[_outputStream].write(`Checking availability of name: ${name}${EOL}${EOL}`);

      maxLength = event.services.reduce((acc, descriptor) => {
        return Math.max(acc, `${descriptor.category} > ${descriptor.title}`.length);
      }, 0);

      debug('Calculated maximum length for service descriptor: %d', maxLength);
    });

    this[_throne].addListener('checkservice', (event) => {
      this[_printServiceStatus](event, chalk.dim('CHECKING'), maxLength);
    });

    this[_throne].addListener('result', (event) => {
      let status;
      if (event.error) {
        status = chalk.red('FAILED');
      } else if (event.available == null) {
        status = chalk.yellow('UNKNOWN');
      } else if (event.available) {
        status = chalk.green('AVAILABLE');
      } else {
        status = chalk.yellow('TAKEN');
      }

      this[_outputStream].clearLine();
      this[_outputStream].cursorTo(0);
      this[_printServiceStatus](event, status, maxLength, true);
    });

    this[_throne].addListener('report', (event) => {
      this[_outputStream].write(`${EOL}${chalk.underline('Summary:')}${EOL}`);
      this[_outputStream].write(`Available on ${event.stats.available}/${event.stats.total} services${EOL}`);
      if (event.stats.failed) {
        this[_outputStream].write(`${event.stats.failed}/${event.stats.total} services failed!${EOL}`);
      } else {
        this[_outputStream].write(`No services failed!${EOL}`);
      }

      if (event.unique) {
        this[_outputStream].write(`It's truly unique. Grab it quick!${EOL}`);
      }
    });

    return this[_throne].check(name, { filter: CLI[_createFilter](categories, services), timeout })
      .then(() => {
        process.exit(0);
      })
      .catch((error) => {
        this[_errorStream].write(`Throne failed: ${error.message}${EOL}`);

        process.exit(1);
      });
  }

  [_listServices]() {
    return this[_throne].getServiceDescriptors()
      .then((descriptors) => {
        const categorizedDescriptors = groupBy(descriptors, 'category');
        const categories = Object.keys(categorizedDescriptors).sort();

        this[_outputStream].write(`${descriptors.length} services found within ${categories.length} categories!` +
          `${EOL}${EOL}`);

        categories.forEach((category) => {
          this[_outputStream].write(`${category}:${EOL}`);

          categorizedDescriptors[category].forEach((descriptor) => {
            this[_outputStream].write(` - ${descriptor.title}${EOL}`);
          });
        });

        process.exit(0);
      })
      .catch((error) => {
        this[_errorStream].write(`Throne failed: ${error.message}${EOL}`);

        process.exit(1);
      });
  }

  [_printServiceStatus](descriptor, status, maxLength, endLine) {
    let output = rightPad(`${descriptor.category} > ${descriptor.title}`, maxLength + 6, '.');
    output += chalk.bold(status);
    if (endLine) {
      output += EOL;
    }

    this[_outputStream].write(output);
  }

}

module.exports = CLI;

/**
 * TODO: Document
 *
 * @typedef {Object} CLI~Options
 * @property {Writable} [errorStream=process.stderr] -
 * @property {Writable} [outputStream=process.stdout] -
 */
<|endoftext|>


<commit_before>// Load in dependencies
var computedStyle = require('computed-style');

// Create our lineHeight calculator
function lineHeight(node) {
  // Grab the line-height via style
  var lnHeightStr = computedStyle(node, 'line-height'),
      lnHeight = parseFloat(lnHeightStr, 10);

  // If the lineHeight did not contain a unit (i.e. it was numeric), convert it to ems (e.g. '2.3' === '2.3em')
  if (lnHeightStr === lnHeight + '') {
    // Save the old lineHeight style and update the em unit to the element
    var _lnHeightStyle = node.style.lineHeight;
    node.style.lineHeight = lnHeightStr + 'em';

    // Calculate the em based height
    lnHeightStr = computedStyle(node, 'line-height');
    lnHeight = parseFloat(lnHeightStr, 10);

    // Revert the lineHeight style
    if (_lnHeightStyle) {
      node.style.lineHeight = _lnHeightStyle;
    } else {
      delete node.style.lineHeight;
    }
  }

  // TODO: This should be broken out into its own node module but how do we test it? (font-size?)
  // If the lineHeight is in pt, convert it to pixels
  // DEV: `em` units are converted to `pt` in IE6
  // Conversion ratio from https://developer.mozilla.org/en-US/docs/Web/CSS/length
  if (lnHeightStr.indexOf('pt') !== -1) {
    lnHeight *= 4;
    lnHeight /= 3;
  } else if (lnHeightStr.indexOf('mm') !== -1) {
  // Otherwise, if the lineHeight is in mm, convert it to pixels
    lnHeight *= 96;
    lnHeight /= 25.4;
  } else if (lnHeightStr.indexOf('cm') !== -1) {
  // Otherwise, if the lineHeight is in cm, convert it to pixels
    lnHeight *= 96;
    lnHeight /= 2.54;
  }

  // Continue our computation
  lnHeight = Math.round(lnHeight);

  // If the line-height is "normal", calculate by font-size
  if (lnHeightStr === 'normal') {
    // Create a temporary node
    var nodeName = node.nodeName,
        _node = document.createElement(nodeName);
    _node.innerHTML = ' ';

    // Set the font-size of the element
    var fontSizeStr = computedStyle(node, 'font-size');
    _node.style.fontSize = fontSizeStr;

    // Append it to the body
    var body = document.body;
    body.appendChild(_node);

    // Assume the line height of the element is the height
    var height = _node.offsetHeight;
    lnHeight = height;

    // Remove our child from the DOM
    body.removeChild(_node);
  }

  // Return the calculated height
  return lnHeight;
}

// Export lineHeight
module.exports = lineHeight;<commit_msg>Passing in test<commit_after>// Load in dependencies
var computedStyle = require('computed-style');

// Create our lineHeight calculator
function lineHeight(node) {
  // Grab the line-height via style
  var lnHeightStr = computedStyle(node, 'line-height'),
      lnHeight = parseFloat(lnHeightStr, 10);

  // If the lineHeight did not contain a unit (i.e. it was numeric), convert it to ems (e.g. '2.3' === '2.3em')
  if (lnHeightStr === lnHeight + '') {
    // Save the old lineHeight style and update the em unit to the element
    var _lnHeightStyle = node.style.lineHeight;
    node.style.lineHeight = lnHeightStr + 'em';

    // Calculate the em based height
    lnHeightStr = computedStyle(node, 'line-height');
    lnHeight = parseFloat(lnHeightStr, 10);

    // Revert the lineHeight style
    if (_lnHeightStyle) {
      node.style.lineHeight = _lnHeightStyle;
    } else {
      delete node.style.lineHeight;
    }
  }

  // TODO: This should be broken out into its own node module but how do we test it? (font-size?)
  // If the lineHeight is in pt, convert it to pixels (4px for 3pt)
  // DEV: `em` units are converted to `pt` in IE6
  // Conversion ratio from https://developer.mozilla.org/en-US/docs/Web/CSS/length
  if (lnHeightStr.indexOf('pt') !== -1) {
    lnHeight *= 4;
    lnHeight /= 3;
  } else if (lnHeightStr.indexOf('mm') !== -1) {
  // Otherwise, if the lineHeight is in mm, convert it to pixels (96px for 25.4mm)
    lnHeight *= 96;
    lnHeight /= 25.4;
  } else if (lnHeightStr.indexOf('cm') !== -1) {
  // Otherwise, if the lineHeight is in cm, convert it to pixels (96px for 2.54cm)
    lnHeight *= 96;
    lnHeight /= 2.54;
  } else if (lnHeightStr.indexOf('in') !== -1) {
  // Otherwise, if the lineHeight is in in, convert it to pixels (96px for 1in)
    lnHeight *= 96;
  }

  // Continue our computation
  lnHeight = Math.round(lnHeight);

  // If the line-height is "normal", calculate by font-size
  if (lnHeightStr === 'normal') {
    // Create a temporary node
    var nodeName = node.nodeName,
        _node = document.createElement(nodeName);
    _node.innerHTML = ' ';

    // Set the font-size of the element
    var fontSizeStr = computedStyle(node, 'font-size');
    _node.style.fontSize = fontSizeStr;

    // Append it to the body
    var body = document.body;
    body.appendChild(_node);

    // Assume the line height of the element is the height
    var height = _node.offsetHeight;
    lnHeight = height;

    // Remove our child from the DOM
    body.removeChild(_node);
  }

  // Return the calculated height
  return lnHeight;
}

// Export lineHeight
module.exports = lineHeight;<|endoftext|>


<commit_before>{
  "id":26,
  "name":"Digital Delivery Platform",
  "description":"Discovery will look at how to support and digitalise the implementation and delivery of the full Universal Credit service into Jobcentres and Service Centres.",
  "theme": "Universal Credit",
  "location": "Leeds",
  "phase":"backlog",
  "sro":"Erica Thompson",
  "service_man":"Neil Couling",
  "facing":"internal",
  "phase-history":{
    "discovery": [
      {"label":"Predicted Start Date", "date": "February 2016"}
    ]
  },
  "priority":"Top"
}
<commit_msg>Description added<commit_after>{
  "id":26,
  "name":"Digital Delivery Platform",
  "description":"Discovery will look at how to support and digitalise the implementation and delivery of the full Universal Credit service into Jobcentres and Service Centres.",
  "theme": "Universal Credit",
  "location": "Leeds",
  "phase":"backlog",
  "sro":"Neil Couling",
  "service_man":"Erica Thompson",
  "facing":"internal",
  "phase-history":{
    "discovery": [
      {"label":"Predicted Start Date", "date": "February 2016"}
    ]
  },
  "priority":"Top"
}
<|endoftext|>


<commit_before>// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function() {
  'use strict';

  var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';

  Polymer({
    is: 'gr-file-list',

    properties: {
      patchRange: Object,
      patchNum: String,
      changeNum: String,
      comments: Object,
      drafts: Object,
      revisions: Object,
      projectConfig: Object,
      selectedIndex: {
        type: Number,
        notify: true,
      },
      keyEventTarget: {
        type: Object,
        value: function() { return document.body; },
      },
      change: Object,

      _files: {
        type: Array,
        observer: '_filesChanged',
      },
      _loggedIn: {
        type: Boolean,
        value: false,
      },
      _reviewed: {
        type: Array,
        value: function() { return []; },
      },
      _diffPrefs: Object,
      _userPrefs: Object,
      _localPrefs: Object,
      _showInlineDiffs: Boolean,
    },

    behaviors: [
      Gerrit.KeyboardShortcutBehavior,
    ],

    reload: function() {
      if (!this.changeNum || !this.patchRange.patchNum) {
        return Promise.resolve();
      }

      this._collapseAllDiffs();

      var promises = [];
      var _this = this;

      promises.push(this._getFiles().then(function(files) {
        _this._files = files;
      }));
      promises.push(this._getLoggedIn().then(function(loggedIn) {
        return _this._loggedIn = loggedIn;
      }).then(function(loggedIn) {
        if (!loggedIn) { return; }

        return _this._getReviewedFiles().then(function(reviewed) {
          _this._reviewed = reviewed;
        });
      }));

      this._localPrefs = this.$.storage.getPreferences();
      promises.push(this._getDiffPreferences().then(function(prefs) {
        this._diffPrefs = prefs;
      }.bind(this)));

      promises.push(this._getPreferences().then(function(prefs) {
        this._userPrefs = prefs;
      }.bind(this)));
    },

    get diffs() {
      return Polymer.dom(this.root).querySelectorAll('gr-diff');
    },

    _getDiffPreferences: function() {
      return this.$.restAPI.getDiffPreferences();
    },

    _getPreferences: function() {
      return this.$.restAPI.getPreferences();
    },

    _computePatchSets: function(revisions) {
      var patchNums = [];
      for (var commit in revisions) {
        patchNums.push(revisions[commit]._number);
      }
      return patchNums.sort(function(a, b) { return a - b; });
    },

    _computePatchSetDisabled: function(patchNum, currentPatchNum) {
      return parseInt(patchNum, 10) >= parseInt(currentPatchNum, 10);
    },

    _computePatchSetSelected: function(patchNum, basePatchNum) {
      return parseInt(patchNum, 10) === parseInt(basePatchNum, 10);
    },

    _handleHiddenChange: function(e) {
      var model = e.model;
      model.set('file.__expanded', !model.file.__expanded);
    },

    _handlePatchChange: function(e) {
      this.set('patchRange.basePatchNum', Polymer.dom(e).rootTarget.value);
      page.show('/c/' + encodeURIComponent(this.changeNum) + '/' +
          encodeURIComponent(this._patchRangeStr(this.patchRange)));
    },

    _forEachDiff: function(fn) {
      var diffs = this.diffs;
      for (var i = 0; i < diffs.length; i++) {
        fn(diffs[i]);
      }
    },

    _expandAllDiffs: function(e) {
      this._showInlineDiffs = true;
      for (var index in this._files) {
        this.set(['_files', index, '__expanded'], true);
      }
      if (e && e.target) {
        e.target.blur();
      }
    },

    _collapseAllDiffs: function(e) {
      this._showInlineDiffs = false;
      for (var index in this._files) {
        this.set(['_files', index, '__expanded'], false);
      }
      this.$.cursor.handleDiffUpdate();
      if (e && e.target) {
        e.target.blur();
      }
    },

    _computeCommentsString: function(comments, patchNum, path) {
      return this._computeCountString(comments, patchNum, path, 'comment');
    },

    _computeDraftsString: function(drafts, patchNum, path) {
      return this._computeCountString(drafts, patchNum, path, 'draft');
    },

    _computeCountString: function(comments, patchNum, path, noun) {
      if (!comments) { return ''; }

      var patchComments = (comments[path] || []).filter(function(c) {
        return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
      });
      var num = patchComments.length;
      if (num === 0) { return ''; }
      return num + ' ' + noun + (num > 1 ? 's' : '');
    },

    _computeReviewed: function(file, _reviewed) {
      return _reviewed.indexOf(file.__path) !== -1;
    },

    _handleReviewedChange: function(e) {
      var path = Polymer.dom(e).rootTarget.getAttribute('data-path');
      var index = this._reviewed.indexOf(path);
      var reviewed = index !== -1;
      if (reviewed) {
        this.splice('_reviewed', index, 1);
      } else {
        this.push('_reviewed', path);
      }

      this._saveReviewedState(path, !reviewed).catch(function(err) {
        alert('Couldn’t change file review status. Check the console ' +
            'and contact the PolyGerrit team for assistance.');
        throw err;
      }.bind(this));
    },

    _saveReviewedState: function(path, reviewed) {
      return this.$.restAPI.saveFileReviewed(this.changeNum,
          this.patchRange.patchNum, path, reviewed);
    },

    _getLoggedIn: function() {
      return this.$.restAPI.getLoggedIn();
    },

    _getReviewedFiles: function() {
      return this.$.restAPI.getReviewedFiles(this.changeNum,
          this.patchRange.patchNum);
    },

    _getFiles: function() {
      return this.$.restAPI.getChangeFilesAsSpeciallySortedArray(
          this.changeNum, this.patchRange).then(function(files) {
            // Append UI-specific properties.
            return files.map(function(file) {
              file.__expanded = false;
              return file;
            });
          });
    },

    _handleKey: function(e) {
      if (this.shouldSupressKeyboardShortcut(e)) { return; }
      switch (e.keyCode) {
        case 37: // left
          if (e.shiftKey && this._showInlineDiffs) {
            e.preventDefault();
            this.$.cursor.moveLeft();
          }
          break;
        case 39: // right
          if (e.shiftKey && this._showInlineDiffs) {
            e.preventDefault();
            this.$.cursor.moveRight();
          }
          break;
        case 73:  // 'i'
          if (e.shiftKey) {
            e.preventDefault();
            this._toggleInlineDiffs();
          } else if (this.selectedIndex !== undefined) {
            e.preventDefault();
            var expanded = this._files[this.selectedIndex].__expanded;
            this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
          }
          break;
        case 40:  // down
        case 74:  // 'j'
          e.preventDefault();
          if (this._showInlineDiffs) {
            this.$.cursor.moveDown();
          } else {
            this.selectedIndex =
                Math.min(this._files.length - 1, this.selectedIndex + 1);
            this._scrollToSelectedFile();
          }
          break;
        case 38:  // up
        case 75:  // 'k'
          e.preventDefault();
          if (this._showInlineDiffs) {
            this.$.cursor.moveUp();
          } else {
            this.selectedIndex = Math.max(0, this.selectedIndex - 1);
            this._scrollToSelectedFile();
          }
          break;
        case 67: // 'c'
          var isRangeSelected = this.diffs.some(function(diff) {
            return diff.isRangeSelected();
          }, this);
          if (this._showInlineDiffs && !isRangeSelected) {
            e.preventDefault();
            this._addDraftAtTarget();
          }
          break;
        case 219:  // '['
          e.preventDefault();
          this._openSelectedFile(this._files.length - 1);
          break;
        case 221:  // ']'
          e.preventDefault();
          this._openSelectedFile(0);
          break;
        case 13:  // <enter>
        case 79:  // 'o'
          e.preventDefault();
          if (this._showInlineDiffs) {
            this._openCursorFile();
          } else {
            this._openSelectedFile();
          }
          break;
        case 78:  // 'n'
          if (this._showInlineDiffs) {
            e.preventDefault();
            if (e.shiftKey) {
              this.$.cursor.moveToNextCommentThread();
            } else {
              this.$.cursor.moveToNextChunk();
            }
          }
          break;
        case 80:  // 'p'
          if (this._showInlineDiffs) {
            e.preventDefault();
            if (e.shiftKey) {
              this.$.cursor.moveToPreviousCommentThread();
            } else {
              this.$.cursor.moveToPreviousChunk();
            }
          }
          break;
        case 65:  // 'a'
          if (e.shiftKey) { // Hide left diff.
            e.preventDefault();
            this._forEachDiff(function(diff) {
              diff.toggleLeftDiff();
            });
          }
          break;
      }
    },

    _toggleInlineDiffs: function() {
      if (this._showInlineDiffs) {
        this._collapseAllDiffs();
      } else {
        this._expandAllDiffs();
      }
    },

    _openCursorFile: function() {
      var diff = this.$.cursor.getTargetDiffElement();
      page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
          diff.path));
    },

    _openSelectedFile: function(opt_index) {
      if (opt_index != null) {
        this.selectedIndex = opt_index;
      }
      page.show(this._computeDiffURL(this.changeNum, this.patchRange,
          this._files[this.selectedIndex].__path));
    },

    _addDraftAtTarget: function() {
      var diff = this.$.cursor.getTargetDiffElement();
      var target = this.$.cursor.getTargetLineElement();
      if (diff && target) {
        diff.addDraftAtLine(target);
      }
    },

    _scrollToSelectedFile: function() {
      var el = this.$$('.row[selected]');
      var top = 0;
      for (var node = el; node; node = node.offsetParent) {
        top += node.offsetTop;
      }

      // Don't scroll if it's already in view.
      if (top > window.pageYOffset &&
          top < window.pageYOffset + window.innerHeight - el.clientHeight) {
        return;
      }

      window.scrollTo(0, top - document.body.clientHeight / 2);
    },

    _computeFileSelected: function(index, selectedIndex) {
      return index === selectedIndex;
    },

    _computeFileStatus: function(status) {
      return status || 'M';
    },

    _computeDiffURL: function(changeNum, patchRange, path) {
      // @see Issue 4255 regarding double-encoding.
      path = encodeURIComponent(encodeURIComponent(path));
      // @see Issue 4577 regarding more readable URLs.
      path = path.replace(/%252F/g, '/');
      path = path.replace(/%2520/g, '+');
      return '/c/' +
          encodeURIComponent(changeNum) +
          '/' +
          encodeURIComponent(this._patchRangeStr(patchRange)) +
          '/' +
          path;
    },

    _patchRangeStr: function(patchRange) {
      return patchRange.basePatchNum !== 'PARENT' ?
          patchRange.basePatchNum + '..' + patchRange.patchNum :
          patchRange.patchNum + '';
    },

    _computeFileDisplayName: function(path) {
      return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
    },

    _computeClass: function(baseClass, path) {
      var classes = [baseClass];
      if (path === COMMIT_MESSAGE_PATH) {
        classes.push('invisible');
      }
      return classes.join(' ');
    },

    _computeShowHideText: function(expanded) {
      return expanded ? '▼' : '◀';
    },

    _computeHiddenState: function(expanded) {
      return !expanded;
    },

    _filesChanged: function() {
      this.async(function() {
        var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');

        // Overwrite the cursor's list of diffs:
        this.$.cursor.splice.apply(this.$.cursor,
            ['diffs', 0, this.$.cursor.diffs.length].concat(diffElements));
      }.bind(this), 1);
    },
  });
})();
<commit_msg>Don’t directly change the patchRange.basePatchNum in gr-file-list<commit_after>// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function() {
  'use strict';

  var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';

  Polymer({
    is: 'gr-file-list',

    properties: {
      patchRange: Object,
      patchNum: String,
      changeNum: String,
      comments: Object,
      drafts: Object,
      revisions: Object,
      projectConfig: Object,
      selectedIndex: {
        type: Number,
        notify: true,
      },
      keyEventTarget: {
        type: Object,
        value: function() { return document.body; },
      },
      change: Object,

      _files: {
        type: Array,
        observer: '_filesChanged',
      },
      _loggedIn: {
        type: Boolean,
        value: false,
      },
      _reviewed: {
        type: Array,
        value: function() { return []; },
      },
      _diffPrefs: Object,
      _userPrefs: Object,
      _localPrefs: Object,
      _showInlineDiffs: Boolean,
    },

    behaviors: [
      Gerrit.KeyboardShortcutBehavior,
    ],

    reload: function() {
      if (!this.changeNum || !this.patchRange.patchNum) {
        return Promise.resolve();
      }

      this._collapseAllDiffs();

      var promises = [];
      var _this = this;

      promises.push(this._getFiles().then(function(files) {
        _this._files = files;
      }));
      promises.push(this._getLoggedIn().then(function(loggedIn) {
        return _this._loggedIn = loggedIn;
      }).then(function(loggedIn) {
        if (!loggedIn) { return; }

        return _this._getReviewedFiles().then(function(reviewed) {
          _this._reviewed = reviewed;
        });
      }));

      this._localPrefs = this.$.storage.getPreferences();
      promises.push(this._getDiffPreferences().then(function(prefs) {
        this._diffPrefs = prefs;
      }.bind(this)));

      promises.push(this._getPreferences().then(function(prefs) {
        this._userPrefs = prefs;
      }.bind(this)));
    },

    get diffs() {
      return Polymer.dom(this.root).querySelectorAll('gr-diff');
    },

    _getDiffPreferences: function() {
      return this.$.restAPI.getDiffPreferences();
    },

    _getPreferences: function() {
      return this.$.restAPI.getPreferences();
    },

    _computePatchSets: function(revisions) {
      var patchNums = [];
      for (var commit in revisions) {
        patchNums.push(revisions[commit]._number);
      }
      return patchNums.sort(function(a, b) { return a - b; });
    },

    _computePatchSetDisabled: function(patchNum, currentPatchNum) {
      return parseInt(patchNum, 10) >= parseInt(currentPatchNum, 10);
    },

    _computePatchSetSelected: function(patchNum, basePatchNum) {
      return parseInt(patchNum, 10) === parseInt(basePatchNum, 10);
    },

    _handleHiddenChange: function(e) {
      var model = e.model;
      model.set('file.__expanded', !model.file.__expanded);
    },

    _handlePatchChange: function(e) {
      var patchRange = Object.assign({}, this.patchRange);
      patchRange.basePatchNum = Polymer.dom(e).rootTarget.value;
      page.show('/c/' + encodeURIComponent(this.changeNum) + '/' +
          encodeURIComponent(this._patchRangeStr(patchRange)));
    },

    _forEachDiff: function(fn) {
      var diffs = this.diffs;
      for (var i = 0; i < diffs.length; i++) {
        fn(diffs[i]);
      }
    },

    _expandAllDiffs: function(e) {
      this._showInlineDiffs = true;
      for (var index in this._files) {
        this.set(['_files', index, '__expanded'], true);
      }
      if (e && e.target) {
        e.target.blur();
      }
    },

    _collapseAllDiffs: function(e) {
      this._showInlineDiffs = false;
      for (var index in this._files) {
        this.set(['_files', index, '__expanded'], false);
      }
      this.$.cursor.handleDiffUpdate();
      if (e && e.target) {
        e.target.blur();
      }
    },

    _computeCommentsString: function(comments, patchNum, path) {
      return this._computeCountString(comments, patchNum, path, 'comment');
    },

    _computeDraftsString: function(drafts, patchNum, path) {
      return this._computeCountString(drafts, patchNum, path, 'draft');
    },

    _computeCountString: function(comments, patchNum, path, noun) {
      if (!comments) { return ''; }

      var patchComments = (comments[path] || []).filter(function(c) {
        return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
      });
      var num = patchComments.length;
      if (num === 0) { return ''; }
      return num + ' ' + noun + (num > 1 ? 's' : '');
    },

    _computeReviewed: function(file, _reviewed) {
      return _reviewed.indexOf(file.__path) !== -1;
    },

    _handleReviewedChange: function(e) {
      var path = Polymer.dom(e).rootTarget.getAttribute('data-path');
      var index = this._reviewed.indexOf(path);
      var reviewed = index !== -1;
      if (reviewed) {
        this.splice('_reviewed', index, 1);
      } else {
        this.push('_reviewed', path);
      }

      this._saveReviewedState(path, !reviewed).catch(function(err) {
        alert('Couldn’t change file review status. Check the console ' +
            'and contact the PolyGerrit team for assistance.');
        throw err;
      }.bind(this));
    },

    _saveReviewedState: function(path, reviewed) {
      return this.$.restAPI.saveFileReviewed(this.changeNum,
          this.patchRange.patchNum, path, reviewed);
    },

    _getLoggedIn: function() {
      return this.$.restAPI.getLoggedIn();
    },

    _getReviewedFiles: function() {
      return this.$.restAPI.getReviewedFiles(this.changeNum,
          this.patchRange.patchNum);
    },

    _getFiles: function() {
      return this.$.restAPI.getChangeFilesAsSpeciallySortedArray(
          this.changeNum, this.patchRange).then(function(files) {
            // Append UI-specific properties.
            return files.map(function(file) {
              file.__expanded = false;
              return file;
            });
          });
    },

    _handleKey: function(e) {
      if (this.shouldSupressKeyboardShortcut(e)) { return; }
      switch (e.keyCode) {
        case 37: // left
          if (e.shiftKey && this._showInlineDiffs) {
            e.preventDefault();
            this.$.cursor.moveLeft();
          }
          break;
        case 39: // right
          if (e.shiftKey && this._showInlineDiffs) {
            e.preventDefault();
            this.$.cursor.moveRight();
          }
          break;
        case 73:  // 'i'
          if (e.shiftKey) {
            e.preventDefault();
            this._toggleInlineDiffs();
          } else if (this.selectedIndex !== undefined) {
            e.preventDefault();
            var expanded = this._files[this.selectedIndex].__expanded;
            this.set(['_files', this.selectedIndex, '__expanded'], !expanded);
          }
          break;
        case 40:  // down
        case 74:  // 'j'
          e.preventDefault();
          if (this._showInlineDiffs) {
            this.$.cursor.moveDown();
          } else {
            this.selectedIndex =
                Math.min(this._files.length - 1, this.selectedIndex + 1);
            this._scrollToSelectedFile();
          }
          break;
        case 38:  // up
        case 75:  // 'k'
          e.preventDefault();
          if (this._showInlineDiffs) {
            this.$.cursor.moveUp();
          } else {
            this.selectedIndex = Math.max(0, this.selectedIndex - 1);
            this._scrollToSelectedFile();
          }
          break;
        case 67: // 'c'
          var isRangeSelected = this.diffs.some(function(diff) {
            return diff.isRangeSelected();
          }, this);
          if (this._showInlineDiffs && !isRangeSelected) {
            e.preventDefault();
            this._addDraftAtTarget();
          }
          break;
        case 219:  // '['
          e.preventDefault();
          this._openSelectedFile(this._files.length - 1);
          break;
        case 221:  // ']'
          e.preventDefault();
          this._openSelectedFile(0);
          break;
        case 13:  // <enter>
        case 79:  // 'o'
          e.preventDefault();
          if (this._showInlineDiffs) {
            this._openCursorFile();
          } else {
            this._openSelectedFile();
          }
          break;
        case 78:  // 'n'
          if (this._showInlineDiffs) {
            e.preventDefault();
            if (e.shiftKey) {
              this.$.cursor.moveToNextCommentThread();
            } else {
              this.$.cursor.moveToNextChunk();
            }
          }
          break;
        case 80:  // 'p'
          if (this._showInlineDiffs) {
            e.preventDefault();
            if (e.shiftKey) {
              this.$.cursor.moveToPreviousCommentThread();
            } else {
              this.$.cursor.moveToPreviousChunk();
            }
          }
          break;
        case 65:  // 'a'
          if (e.shiftKey) { // Hide left diff.
            e.preventDefault();
            this._forEachDiff(function(diff) {
              diff.toggleLeftDiff();
            });
          }
          break;
      }
    },

    _toggleInlineDiffs: function() {
      if (this._showInlineDiffs) {
        this._collapseAllDiffs();
      } else {
        this._expandAllDiffs();
      }
    },

    _openCursorFile: function() {
      var diff = this.$.cursor.getTargetDiffElement();
      page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
          diff.path));
    },

    _openSelectedFile: function(opt_index) {
      if (opt_index != null) {
        this.selectedIndex = opt_index;
      }
      page.show(this._computeDiffURL(this.changeNum, this.patchRange,
          this._files[this.selectedIndex].__path));
    },

    _addDraftAtTarget: function() {
      var diff = this.$.cursor.getTargetDiffElement();
      var target = this.$.cursor.getTargetLineElement();
      if (diff && target) {
        diff.addDraftAtLine(target);
      }
    },

    _scrollToSelectedFile: function() {
      var el = this.$$('.row[selected]');
      var top = 0;
      for (var node = el; node; node = node.offsetParent) {
        top += node.offsetTop;
      }

      // Don't scroll if it's already in view.
      if (top > window.pageYOffset &&
          top < window.pageYOffset + window.innerHeight - el.clientHeight) {
        return;
      }

      window.scrollTo(0, top - document.body.clientHeight / 2);
    },

    _computeFileSelected: function(index, selectedIndex) {
      return index === selectedIndex;
    },

    _computeFileStatus: function(status) {
      return status || 'M';
    },

    _computeDiffURL: function(changeNum, patchRange, path) {
      // @see Issue 4255 regarding double-encoding.
      path = encodeURIComponent(encodeURIComponent(path));
      // @see Issue 4577 regarding more readable URLs.
      path = path.replace(/%252F/g, '/');
      path = path.replace(/%2520/g, '+');
      return '/c/' +
          encodeURIComponent(changeNum) +
          '/' +
          encodeURIComponent(this._patchRangeStr(patchRange)) +
          '/' +
          path;
    },

    _patchRangeStr: function(patchRange) {
      return patchRange.basePatchNum !== 'PARENT' ?
          patchRange.basePatchNum + '..' + patchRange.patchNum :
          patchRange.patchNum + '';
    },

    _computeFileDisplayName: function(path) {
      return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
    },

    _computeClass: function(baseClass, path) {
      var classes = [baseClass];
      if (path === COMMIT_MESSAGE_PATH) {
        classes.push('invisible');
      }
      return classes.join(' ');
    },

    _computeShowHideText: function(expanded) {
      return expanded ? '▼' : '◀';
    },

    _computeHiddenState: function(expanded) {
      return !expanded;
    },

    _filesChanged: function() {
      this.async(function() {
        var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');

        // Overwrite the cursor's list of diffs:
        this.$.cursor.splice.apply(this.$.cursor,
            ['diffs', 0, this.$.cursor.diffs.length].concat(diffElements));
      }.bind(this), 1);
    },
  });
})();
<|endoftext|>


<commit_before>import fetch from 'unfetch';

const isJsonType = type => type.endsWith('json');

// Fetch an address, expecting a json answer, and throwing if the requests failed.
const jsonThrowingFetch = (...args) =>
  fetch(...args).then((resp) => {
    if (resp.ok) {
      // Every response are supposed to be json.
      return resp.json();
    }
    // If the requests has failed, throw an error.
    return resp.json().then(
      (errorDescr) => {
        const e = new Error(errorDescr.message);
        e.type = errorDescr.type;
        throw e;
      },
      () => {
        throw new Error(resp.statusText);
      }
    );
  });

// Creates an address from an array.
const buildAddress = addrOrPathList =>
  (typeof addrOrPathList === 'string' ? addrOrPathList : addrOrPathList.join('/'));

// Returns an interface toward the provided server address.
export default function getServerInterface(serverAddress) {
  // Ask data to the server.
  const ask = (addrOrPathList, options = {}) => {
    // Build the address (join arrays of path parts).
    const addr = buildAddress(addrOrPathList);
    // Create the request header (all requests are xhr).
    const headers = Object.assign({ 'X-Requested-With': 'XMLHttpRequest' }, options.headers);
    // Create the options for fetch.
    const fetchOptions = Object.assign({ cache: 'no-cache' }, options, { headers });
    // Fetch.
    return jsonThrowingFetch(`${serverAddress}/${addr}`, fetchOptions);
  };

  // Send data to the server.
  const send = (addrOrPathList, content, contentType = 'application/json', options = {}) => {
    // Build the address (join arrays of path parts).
    const addr = buildAddress(addrOrPathList);
    // Set up the request headers, the content type and the request as xhr.
    const headers = Object.assign(
      {
        'Content-Type': contentType,
        'X-Requested-With': 'XMLHttpRequest'
      },
      options.headers
    );
    // Automatically stringify non string content if the content type is json.
    const body =
      typeof content !== 'string' && (isJsonType(contentType) ? JSON.stringify(content) : content);
    // Create the options for fetch.
    const fetchOptions = Object.assign({ method: 'POST', body }, options, { headers });
    // Fetch.
    return jsonThrowingFetch(`${serverAddress}/${addr}`, fetchOptions);
  };

  return {
    experiments: () => ask('experiments'),
    availableRun: xpId => ask(['experiment', xpId, 'available_run']),
    run: (xpId, runId) => ask(['run', xpId, runId]),
    plan: (xpId, runId) => ask(['run', xpId, runId, 'plan']),
    lock: (xpId, runId) => ask(['run', xpId, runId, 'lock']),
    currentTrial: (xpId, runId) => ask(['run', xpId, runId, 'current_trial']),
    postTrialResults: (experimentId, runId, blockNumber, trialNumber, data) =>
      send(['trial', experimentId, runId, blockNumber, trialNumber], data),
    postExperimentDesign: expeDesign => send('import', expeDesign, 'text/xml')
  };
}
<commit_msg>Update server-interface to comply with xpserver 2.<commit_after>import fetch from 'unfetch';

const isJsonType = type => type.endsWith('json');

// Fetch an address, expecting a json answer, and throwing if the requests failed.
const jsonThrowingFetch = (...args) =>
  fetch(...args).then((resp) => {
    if (resp.ok) {
      // Every response are supposed to be json.
      return resp.json();
    }
    // If the requests has failed, throw an error.
    return resp.json().then(
      (errorDescr) => {
        const e = new Error(errorDescr.message);
        e.type = errorDescr.type;
        throw e;
      },
      () => {
        throw new Error(resp.statusText);
      }
    );
  });

// From a server address and the path to an API, return the api address (the main point of this
// function is to deal with the different form that can be tacken by api path).
const getApiAddress = (serverAddress, apiPath) => {
  if (typeof apiPath === 'string') {
    return [serverAddress, apiPath].join('/');
  } else if (apiPath) {
    return [serverAddress, ...apiPath].join('/');
  }
  return serverAddress;
};

// Returns an interface toward the provided server address.
export default function getServerInterface(serverAddress, apiPath = 'api') {
  // Init the api address once and for all.
  const apiAddress = getApiAddress(serverAddress, apiPath);

  // Get the address of an API endpoint on the server using either a simple string, or an
  // array.
  const getAddress = addrOrPathList =>
    (typeof addrOrPathList === 'string'
      ? [apiAddress, addrOrPathList]
      : [apiAddress, ...addrOrPathList]).join('/');

  // Ask data to the server.
  const ask = (addrOrPathList, options = {}) => {
    // Create the request header (all requests are xhr).
    const headers = Object.assign({ 'X-Requested-With': 'XMLHttpRequest' }, options.headers);
    // Create the options for fetch.
    const fetchOptions = Object.assign({ cache: 'no-cache' }, options, { headers });
    // Fetch.
    return jsonThrowingFetch(getAddress(addrOrPathList), fetchOptions);
  };

  // Send data to the server.
  const send = (addrOrPathList, content, contentType = 'application/json', options = {}) => {
    // Set up the request headers, the content type and the request as xhr.
    const headers = Object.assign(
      {
        'Content-Type': contentType,
        'X-Requested-With': 'XMLHttpRequest'
      },
      options.headers
    );
    // Automatically stringify non string content if the content type is json.
    const body =
      typeof content !== 'string' && (isJsonType(contentType) ? JSON.stringify(content) : content);
    // Create the options for fetch.
    const fetchOptions = Object.assign({ method: 'POST', body }, options, { headers });
    // Fetch.
    return jsonThrowingFetch(getAddress(addrOrPathList), fetchOptions);
  };

  return {
    experiments: () => ask('experiments'),
    availableRun: xpId => ask(['experiment', xpId, 'available_run']),
    run: (xpId, runId) => ask(['run', xpId, runId]),
    plan: (xpId, runId) => ask(['run', xpId, runId, 'plan']),
    lock: (xpId, runId) => ask(['run', xpId, runId, 'lock']),
    currentTrial: (xpId, runId) => ask(['run', xpId, runId, 'current_trial']),
    postTrialResults: (experimentId, runId, blockNumber, trialNumber, data) =>
      send(['trial', experimentId, runId, blockNumber, trialNumber], data),
    postExperimentDesign: expeDesign => send('import', expeDesign, 'text/xml')
  };
}
<|endoftext|>


<commit_before>
    /**
     * If needed, adds line numbers with a proper start to the given
     * dom_element
     *
     * @param {Element} dom_element
     */
    function checkLineNumbers( dom_element )
    {
        var $that = $(dom_element).parent();
        var classes = $that.attr( 'class' );
        var ln = /ln-(\d+)-([\w][\w\-]*)|ln-(\d+)|ln-/.exec( classes );
        if ( ln )
        {
            addLineNumbers( dom_element );
            setLineNumbersStart.apply( dom_element, ln );
        }
        else if ( $.chili.options.lineNumbers )
        {
            addLineNumbers(dom_element);
        }
        return;

        /**
         * Sets the start of the ol tag of the current DOM element
         *
         * @param {String} groupStart
         * @param {String} groupId
         * @param {String} start
         */
        function setLineNumbersStart( all, groupStart, groupId, start )
        {
            if ( groupStart )
            {
                start = parseInt( groupStart, 10 );
                var $pieces = $( '.ln-' + groupStart + '-' + groupId );
                var $that = $(this).parent();
                var pos = $pieces.index( $that[0] );
                $pieces
                    .slice( 0, pos )
                    .each(
                        function()
                        {
                            start += $( this ).find( 'li' ).length;
                        }
                    )
                ;
            }
            else if ( start )
            {
                start = parseInt( start, 10 );
            }
            else
            {
                start = 1;
            }
            $(this).find( 'ol' )[0].start = start;
            // the following should refresh the window
            $('body')
                .width( $('body').width() - 1 )
                .width( $('body').width() + 1 )
            ;
        }

        /**
         * Converts lines inside the given dom_element to list items
         *
         * @param {Element} dom_element
         */
        function addLineNumbers( dom_element )
        {
            var result = $( dom_element ).html();
            result = extractImmediateBreaksInsideSpans(result);
            result = convertLinesInsideSpans(result);
            result = convertRemainingLines(result);
            result = fillEmptyListItems(result);
            result = '
' + result + '
';
            dom_element.innerHTML = result;
        }

    	/**
         * Returns a list item that wraps he given textBeforeLastLine or
         * lastLine
         *
         * @param {String} lineBeforeLastLine
         * @param {String} textBeforeLastLine
         * @param {String} lastLine
         * @param {String} openSpan
         *
         * @return String
         */
        function makeListItem( lineBeforeLastLine, textBeforeLastLine, lastLine, openSpan )
        {
            var closeSpan = openSpan ? '' : '';
            var result = '';
            if (lineBeforeLastLine)
            {
                result = '
' + openSpan + textBeforeLastLine + closeSpan + '
';
            }
            else if (lastLine)
            {
                result = '
' + openSpan + lastLine + closeSpan + '
';
            }
            return result;
        }

        /**
         * Returns the given html, after moving out of the spans their immediate
         * breaks, if any
         *
         * @param {String} html
         *
         * @return String
         */
        function extractImmediateBreaksInsideSpans( html )
        {
            var expr = /(]+>)((?:(?: |\xA0)
)+)(.*?)(<\/span>)/ig;
            var repl = '$2$1$3$4';
            var result = html.replace( expr, repl );
            return result;
        }

        /**
         * Returns the given html, after wrapping each line inside spans with an
         *
 element
         *
         * @param {String} html
         *
         * @return String
         */
        function convertLinesInsideSpans( html )
        {
            var expr = /(.*?)()(.*?)(?:<\/span>(?: |\xA0)
|<\/span>)/ig;
            var repl = function( all, anythingBeforeSpan, openSpan, anythingInsideSpan )
            {
                if (! /
/i.test(anythingInsideSpan))
                {
                    return all;
                }
                var expr = /((.*?)(?: |\xA0)
)|(.*)/ig;
                var repl = function( all, lineBeforeLastLine, textBeforeLastLine, lastLine )
                {
                    var result = makeListItem(lineBeforeLastLine, textBeforeLastLine, lastLine, openSpan);
                    return result;
                };
                var result = anythingBeforeSpan + anythingInsideSpan.replace(expr, repl);
                return result;
            };
            var result = html.replace(expr, repl);
            return result;
        }

        /**
         * Returns the given html, after wrapping each remaining line with an
         *
 element
         *
         * @param {String} html
         *
         * @return String
         */
        function convertRemainingLines( html )
        {
            var expr = /(
.*?<\/li>)|((.*?)(?: |\xA0)
)|(.+)/ig;
            var repl = function( all, prev_li, lineBeforeLastLine, textBeforeLastLine, lastLine )
            {
                if (prev_li)
                {
                    return all;
                }
                var result = makeListItem(lineBeforeLastLine, textBeforeLastLine, lastLine, '');
                return result;
            };
            var result = html.replace(expr, repl);
            return result;
        }

        /**
         * Returns the given html, after filling each empty
 element with a
         * space replacement
         *
         * @param {String} html
         *
         * @return String
         */
        function fillEmptyListItems( html )
        {
            var expr = /
<\/li>/ig;
            var repl = '
' + $.chili.replaceSpace + '
';
            var result = html.replace( expr, repl );
            return result;
        }
    }
<commit_msg>fix - simplified line numbers generation: better and faster<commit_after>
    /**
     * If needed, adds line numbers with a proper start to the given
     * dom_element
     *
     * @param {Element} dom_element
     */
    function checkLineNumbers( dom_element )
    {
        var $that = $(dom_element).parent();
        var classes = $that.attr( 'class' );
        var ln = /ln-(\d+)-([\w][\w\-]*)|ln-(\d+)|ln-/.exec( classes );
        if ( ln )
        {
            addLineNumbers( dom_element );
            setLineNumbersStart.apply( dom_element, ln );
        }
        else if ( $.chili.options.lineNumbers )
        {
            addLineNumbers(dom_element);
        }
        return;

        /**
         * Sets the start of the ol tag of the current DOM element
         *
         * @param {String} groupStart
         * @param {String} groupId
         * @param {String} start
         */
        function setLineNumbersStart( all, groupStart, groupId, start )
        {
            if ( groupStart )
            {
                start = parseInt( groupStart, 10 );
                var $pieces = $( '.ln-' + groupStart + '-' + groupId );
                var $that = $(this).parent();
                var pos = $pieces.index( $that[0] );
                $pieces
                    .slice( 0, pos )
                    .each(
                        function()
                        {
                            start += $( this ).find( 'li' ).length;
                        }
                    )
                ;
            }
            else if ( start )
            {
                start = parseInt( start, 10 );
            }
            else
            {
                start = 1;
            }
            $(this).find( 'ol' )[0].start = start;
            // the following should refresh the window
            $('body')
                .width( $('body').width() - 1 )
                .width( $('body').width() + 1 )
            ;
        }

        /**
         * Converts lines inside the given dom_element to list items into an
         * ordered list element
         *
         * @param {Element} dom_element
         */
        function addLineNumbers( dom_element )
        {
            var result = $( dom_element ).html();
            var expr = /(.*?)
/ig;
            result = result.replace(expr, function( all, line )
            {
                return '
' + line + '
';
            });
            expr = /(]+>)(.*?)(<\/span>)/ig;
            result = result.replace(expr, function( all, openSpan, insideSpan, closeSpan )
            {
                var expr = /<\/li>
/ig;
                insideSpan = insideSpan.replace(expr, function( all )
                {
                    var result = closeSpan + all + openSpan;
                    return result;
                });
                result = openSpan + insideSpan + closeSpan;
                return result;
            });
            result = '
' + result + '
';
            dom_element.innerHTML = result;
        }
    }
<|endoftext|>


<commit_before>/*global define, exports, module, require*/

// This boilerplate is to support running this code with either
// - just the browser
// - RequireJS
// - node.js / npm (browserify, webpack, etc.)
// Do not think this boilerplate is necessary to run Meiosis. It is for your convenience when
// running this example, so that you can use any of the above according to your preference.
(function(root, moduleName, depNames, depVars, factory) {
  if (typeof define === "function" && define.amd) {
    define(depNames, factory);
  }
  else if (typeof exports === "object") {
    var requires = depNames.map(function(depName) {
      return require(depName);
    });
    module.exports = factory.apply(root, requires);
  }
  else {
    var vars = depVars.map(function(depVar) {
      return root[depVar];
    });
    root[moduleName] = factory.apply(root, vars);
  }
}(this,
  "mainView",
  ["jquery", "handlebars"],
  ["$", "Handlebars"],

  function($, Handlebars) {
    var mainTemplate = Handlebars.compile($("#main").html());

    return function(renderedTodos) {
      return mainTemplate({renderedTodos: renderedTodos.join("")});
    };
  }
));

<commit_msg>umd notice<commit_after>/*global define, exports, module, require*/

// This boilerplate is to support running this code with either, just the browser, or RequireJS,
// or node.js / npm (browserify, webpack, etc.) Do not think this boilerplate is necessary to run
// Meiosis. It is for convenience to be able to run the example with your preferred module system.
(function(root, moduleName, depNames, depVars, factory) {
  if (typeof define === "function" && define.amd) {
    define(depNames, factory);
  }
  else if (typeof exports === "object") {
    var requires = depNames.map(function(depName) {
      return require(depName);
    });
    module.exports = factory.apply(root, requires);
  }
  else {
    var vars = depVars.map(function(depVar) {
      return root[depVar];
    });
    root[moduleName] = factory.apply(root, vars);
  }
}(this, // above is boilerplate code, the "real" code starts below.
  "mainView",
  ["jquery", "handlebars"],
  ["$", "Handlebars"],

  function($, Handlebars) {
    var mainTemplate = Handlebars.compile($("#main").html());

    return function(renderedTodos) {
      return mainTemplate({renderedTodos: renderedTodos.join("")});
    };
  }
));

<|endoftext|>


<commit_before>var youAre = function(dbot) {
    var dbot = dbot;

    return {
        'listener': function(data) {
            var key = data.message.valMatch(/(\bis\b|\bare\b)\s+([\w\s\d]*?)(\s+)?(,|\.|\band\b|$)/, 5);

            console.log(key[1]);
            console.log(key[2]);
            console.log(key[3]);
            if(key && key[2] != "" && Number.prototype.chanceIn(1, 25) && data.user != 'aisbot') {
                dbot.say(data.channel, data.user + ': You\'re ' + key[2] + '.');
            }
        },

        'on': 'PRIVMSG'
    };
};

exports.fetch = function(dbot) {
    return youAre(dbot);
};
<commit_msg>karma fix<commit_after>var youAre = function(dbot) {
    var dbot = dbot;

    return {
        'listener': function(data) {
            var key = data.message.valMatch(/(\bis\b|\bare\b)\s+([\w\s\d]*?)(\s+)?(,|\.|\band\b|$)/, 5);

            if(key && key[2] != "" && Number.prototype.chanceIn(1, 25) && data.user != 'aisbot') {
                dbot.say(data.channel, data.user + ': You\'re ' + key[2] + '.');
            }
        },

        'on': 'PRIVMSG'
    };
};

exports.fetch = function(dbot) {
    return youAre(dbot);
};
<|endoftext|>


<commit_before>'use strict';
/**
 * lite-server : Simple server for angular/SPA projects
 *
 * Simply loads some default browser-sync options that apply to SPAs,
 * applies custom config overrides from user's own local `bs-config.{js|json}` file,
 * and launches browser-sync.
 */
var browserSync = require('browser-sync').create();
var path = require('path');
var _ = require('lodash');

// Load defaults
var options = require('./config-defaults');

// Load optional browser-sync config file from user's project dir
var bsConfigPath = path.resolve('bs-config');
var overrides = {};
try {
  overrides = require(bsConfigPath);
} catch (err) { } // silent: optional load
_.merge(options, overrides);

// Fixes browsersync error when overriding middleware array
if (options.server.middleware) {
  options.server.middleware = _.compact(options.server.middleware);
}

// Run browser-sync
browserSync.init(options);
<commit_msg>add error handling<commit_after>'use strict';
/**
 * lite-server : Simple server for angular/SPA projects
 *
 * Simply loads some default browser-sync options that apply to SPAs,
 * applies custom config overrides from user's own local `bs-config.{js|json}` file,
 * and launches browser-sync.
 */
var browserSync = require('browser-sync').create();
var path = require('path');
var _ = require('lodash');

// Load defaults
var options = require('./config-defaults');

// Load optional browser-sync config file from user's project dir
var bsConfigPath = path.resolve('bs-config');
var overrides = {};
try {
  overrides = require(bsConfigPath);
} catch (err) {
  if (!(err.code && err.code === 'MODULE_NOT_FOUND')) {
    throw(err);
  }
}
_.merge(options, overrides);

// Fixes browsersync error when overriding middleware array
if (options.server.middleware) {
  options.server.middleware = _.compact(options.server.middleware);
}

// Run browser-sync
browserSync.init(options);
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function() {
  'use strict';
  const HOVER_CLASS = 'hovered';

  /**
   * When the hovercard is positioned diagonally (bottom-left, bottom-right,
   * top-left, or top-right), we add additional (invisible) padding so that the
   * area that a user can hover over to access the hovercard is larger.
   */
  const DIAGONAL_OVERFLOW = 15;

  Polymer({
    is: 'gr-hovercard',
    _legacyUndefinedCheck: true,

    properties: {
      /**
       * @type {?}
       */
      _target: Object,

      /**
       * Determines whether or not the hovercard is visible.
       *
       * @type {boolean}
       */
      _isShowing: {
        type: Boolean,
        value: false,
      },
      /**
       * The `id` of the element that the hovercard is anchored to.
       *
       * @type {string}
       */
      for: {
        type: String,
        observer: '_forChanged',
      },

      /**
       * The spacing between the top of the hovercard and the element it is
       * anchored to.
       *
       * @type {number}
       */
      offset: {
        type: Number,
        value: 14,
      },

      /**
       * Positions the hovercard to the top, right, bottom, left, bottom-left,
       * bottom-right, top-left, or top-right of its content.
       *
       * @type {string}
       */
      position: {
        type: String,
        value: 'bottom',
      },

      container: Object,
      /**
       * ID for the container element.
       *
       * @type {string}
       */
      containerId: {
        type: String,
        value: 'gr-hovercard-container',
      },
    },

    listeners: {
      mouseleave: 'hide',
    },

    attached() {
      if (!this._target) { this._target = this.target; }
      this.listen(this._target, 'mouseenter', 'show');
      this.listen(this._target, 'focus', 'show');
      this.listen(this._target, 'mouseleave', 'hide');
      this.listen(this._target, 'blur', 'hide');
      this.listen(this._target, 'tap', 'hide');
    },

    ready() {
      // First, check to see if the container has already been created.
      this.container = Gerrit.getRootElement()
          .querySelector('#' + this.containerId);

      if (this.container) { return; }

      // If it does not exist, create and initialize the hovercard container.
      this.container = document.createElement('div');
      this.container.setAttribute('id', this.containerId);
      Gerrit.getRootElement().appendChild(this.container);
    },

    removeListeners() {
      this.unlisten(this._target, 'mouseenter', 'show');
      this.unlisten(this._target, 'focus', 'show');
      this.unlisten(this._target, 'mouseleave', 'hide');
      this.unlisten(this._target, 'blur', 'hide');
      this.unlisten(this._target, 'tap', 'hide');
    },

    /**
     * Returns the target element that the hovercard is anchored to (the `id` of
     * the `for` property).
     *
     * @type {HTMLElement}
     */
    get target() {
      const parentNode = Polymer.dom(this).parentNode;
      // If the parentNode is a document fragment, then we need to use the host.
      const ownerRoot = Polymer.dom(this).getOwnerRoot();
      let target;
      if (this.for) {
        target = Polymer.dom(ownerRoot).querySelector('#' + this.for);
      } else {
        target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
          ownerRoot.host :
          parentNode;
      }
      return target;
    },

    /**
     * Hides/closes the hovercard. This occurs when the user triggers the
     * `mouseleave` event on the hovercard's `target` element (as long as the
     * user is not hovering over the hovercard).
     *
     * @param {Event} e DOM Event (e.g. `mouseleave` event)
     */
    hide(e) {
      const targetRect = this._target.getBoundingClientRect();
      const x = e.clientX;
      const y = e.clientY;
      if (x > targetRect.left && x < targetRect.right && y > targetRect.top &&
          y < targetRect.bottom) {
        // Sometimes the hovercard itself obscures the mouse pointer, and
        // that generates a mouseleave event. We don't want to hide the hovercard
        // in that situation.
        return;
      }

      // If the hovercard is already hidden or the user is now hovering over the
      //  hovercard or the user is returning from the hovercard but now hovering
      //  over the target (to stop an annoying flicker effect), just return.
      if (!this._isShowing || e.toElement === this ||
          (e.fromElement === this && e.toElement === this._target)) {
        return;
      }

      // Mark that the hovercard is not visible and do not allow focusing
      this._isShowing = false;

      // Clear styles in preparation for the next time we need to show the card
      this.classList.remove(HOVER_CLASS);

      // Reset and remove the hovercard from the DOM
      this.style.cssText = '';
      this.$.hovercard.setAttribute('tabindex', -1);

      // Remove the hovercard from the container, given that it is still a child
      // of the container.
      if (this.container.contains(this)) {
        this.container.removeChild(this);
      }
    },

    /**
     * Shows/opens the hovercard. This occurs when the user triggers the
     * `mousenter` event on the hovercard's `target` element.
     *
     * @param {Event} e DOM Event (e.g., `mouseenter` event)
     */
    show(e) {
      if (this._isShowing) {
        return;
      }

      // Mark that the hovercard is now visible
      this._isShowing = true;
      this.setAttribute('tabindex', 0);

      // Add it to the DOM and calculate its position
      this.container.appendChild(this);
      this.updatePosition();

      // Trigger the transition
      this.classList.add(HOVER_CLASS);
    },

    /**
     * Updates the hovercard's position based on the `position` attribute
     * and the current position of the `target` element.
     *
     * The hovercard is supposed to stay open if the user hovers over it.
     * To keep it open when the user moves away from the target, the bounding
     * rects of the target and hovercard must touch or overlap.
     *
     * NOTE: You do not need to directly call this method unless you need to
     * update the position of the tooltip while it is already visible (the
     * target element has moved and the tooltip is still open).
     */
    updatePosition() {
      if (!this._target) { return; }

      // Calculate the necessary measurements and positions
      const parentRect = document.documentElement.getBoundingClientRect();
      const targetRect = this._target.getBoundingClientRect();
      const thisRect = this.getBoundingClientRect();

      const targetLeft = targetRect.left - parentRect.left;
      const targetTop = targetRect.top - parentRect.top;

      let hovercardLeft;
      let hovercardTop;
      const diagonalPadding = this.offset + DIAGONAL_OVERFLOW;
      let cssText = '';

      // Find the top and left position values based on the position attribute
      // of the hovercard.
      switch (this.position) {
        case 'top':
          hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
          hovercardTop = targetTop - thisRect.height - this.offset;
          cssText += `padding-bottom:${this.offset
          }px; margin-bottom:-${this.offset}px;`;
          break;
        case 'bottom':
          hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
          hovercardTop = targetTop + targetRect.height + this.offset;
          cssText +=
              `padding-top:${this.offset}px; margin-top:-${this.offset}px;`;
          break;
        case 'left':
          hovercardLeft = targetLeft - thisRect.width - this.offset;
          hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
          cssText +=
              `padding-right:${this.offset}px; margin-right:-${this.offset}px;`;
          break;
        case 'right':
          hovercardLeft = targetRect.right + this.offset;
          hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
          cssText +=
              `padding-left:${this.offset}px; margin-left:-${this.offset}px;`;
          break;
        case 'bottom-right':
          hovercardLeft = targetRect.left + targetRect.width + this.offset;
          hovercardTop = targetRect.top + targetRect.height + this.offset;
          cssText += `padding-top:${diagonalPadding}px;`;
          cssText += `padding-left:${diagonalPadding}px;`;
          cssText += `margin-left:-${diagonalPadding}px;`;
          cssText += `margin-top:-${diagonalPadding}px;`;
          break;
        case 'bottom-left':
          hovercardLeft = targetRect.left - thisRect.width - this.offset;
          hovercardTop = targetRect.top + targetRect.height + this.offset;
          cssText += `padding-top:${diagonalPadding}px;`;
          cssText += `padding-right:${diagonalPadding}px;`;
          cssText += `margin-right:-${diagonalPadding}px;`;
          cssText += `margin-top:-${diagonalPadding}px;`;
          break;
        case 'top-left':
          hovercardLeft = targetRect.left - thisRect.width - this.offset;
          hovercardTop = targetRect.top - thisRect.height - this.offset;
          cssText += `padding-bottom:${diagonalPadding}px;`;
          cssText += `padding-right:${diagonalPadding}px;`;
          cssText += `margin-bottom:-${diagonalPadding}px;`;
          cssText += `margin-right:-${diagonalPadding}px;`;
          break;
        case 'top-right':
          hovercardLeft = targetRect.left + targetRect.width + this.offset;
          hovercardTop = targetRect.top - thisRect.height - this.offset;
          cssText += `padding-bottom:${diagonalPadding}px;`;
          cssText += `padding-left:${diagonalPadding}px;`;
          cssText += `margin-bottom:-${diagonalPadding}px;`;
          cssText += `margin-left:-${diagonalPadding}px;`;
          break;
      }

      // Prevent hovercard from appearing outside the viewport.
      // TODO(kaspern): fix hovercard appearing outside viewport on bottom and
      // right.
      if (hovercardLeft < 0) { hovercardLeft = 0; }
      if (hovercardTop < 0) { hovercardTop = 0; }
      // Set the hovercard's position
      cssText += `left:${hovercardLeft}px; top:${hovercardTop}px;`;
      this.style.cssText = cssText;
    },

    /**
     * Responds to a change in the `for` value and gets the updated `target`
     * element for the hovercard.
     *
     * @private
     */
    _forChanged() {
      this._target = this.target;
    },
  });
})();
<commit_msg>Fix gr-hovercard-behavior under Firefox<commit_after>/**
 * @license
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function() {
  'use strict';
  const HOVER_CLASS = 'hovered';

  /**
   * When the hovercard is positioned diagonally (bottom-left, bottom-right,
   * top-left, or top-right), we add additional (invisible) padding so that the
   * area that a user can hover over to access the hovercard is larger.
   */
  const DIAGONAL_OVERFLOW = 15;

  Polymer({
    is: 'gr-hovercard',
    _legacyUndefinedCheck: true,

    properties: {
      /**
       * @type {?}
       */
      _target: Object,

      /**
       * Determines whether or not the hovercard is visible.
       *
       * @type {boolean}
       */
      _isShowing: {
        type: Boolean,
        value: false,
      },
      /**
       * The `id` of the element that the hovercard is anchored to.
       *
       * @type {string}
       */
      for: {
        type: String,
        observer: '_forChanged',
      },

      /**
       * The spacing between the top of the hovercard and the element it is
       * anchored to.
       *
       * @type {number}
       */
      offset: {
        type: Number,
        value: 14,
      },

      /**
       * Positions the hovercard to the top, right, bottom, left, bottom-left,
       * bottom-right, top-left, or top-right of its content.
       *
       * @type {string}
       */
      position: {
        type: String,
        value: 'bottom',
      },

      container: Object,
      /**
       * ID for the container element.
       *
       * @type {string}
       */
      containerId: {
        type: String,
        value: 'gr-hovercard-container',
      },
    },

    listeners: {
      mouseleave: 'hide',
    },

    attached() {
      if (!this._target) { this._target = this.target; }
      this.listen(this._target, 'mouseenter', 'show');
      this.listen(this._target, 'focus', 'show');
      this.listen(this._target, 'mouseleave', 'hide');
      this.listen(this._target, 'blur', 'hide');
      this.listen(this._target, 'tap', 'hide');
    },

    ready() {
      // First, check to see if the container has already been created.
      this.container = Gerrit.getRootElement()
          .querySelector('#' + this.containerId);

      if (this.container) { return; }

      // If it does not exist, create and initialize the hovercard container.
      this.container = document.createElement('div');
      this.container.setAttribute('id', this.containerId);
      Gerrit.getRootElement().appendChild(this.container);
    },

    removeListeners() {
      this.unlisten(this._target, 'mouseenter', 'show');
      this.unlisten(this._target, 'focus', 'show');
      this.unlisten(this._target, 'mouseleave', 'hide');
      this.unlisten(this._target, 'blur', 'hide');
      this.unlisten(this._target, 'tap', 'hide');
    },

    /**
     * Returns the target element that the hovercard is anchored to (the `id` of
     * the `for` property).
     *
     * @type {HTMLElement}
     */
    get target() {
      const parentNode = Polymer.dom(this).parentNode;
      // If the parentNode is a document fragment, then we need to use the host.
      const ownerRoot = Polymer.dom(this).getOwnerRoot();
      let target;
      if (this.for) {
        target = Polymer.dom(ownerRoot).querySelector('#' + this.for);
      } else {
        target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
          ownerRoot.host :
          parentNode;
      }
      return target;
    },

    /**
     * Hides/closes the hovercard. This occurs when the user triggers the
     * `mouseleave` event on the hovercard's `target` element (as long as the
     * user is not hovering over the hovercard).
     *
     * @param {Event} e DOM Event (e.g. `mouseleave` event)
     */
    hide(e) {
      const targetRect = this._target.getBoundingClientRect();
      const x = e.clientX;
      const y = e.clientY;
      if (x > targetRect.left && x < targetRect.right && y > targetRect.top &&
          y < targetRect.bottom) {
        // Sometimes the hovercard itself obscures the mouse pointer, and
        // that generates a mouseleave event. We don't want to hide the hovercard
        // in that situation.
        return;
      }

      // If the hovercard is already hidden or the user is now hovering over the
      //  hovercard or the user is returning from the hovercard but now hovering
      //  over the target (to stop an annoying flicker effect), just return.
      if (!this._isShowing || e.relatedTarget === this ||
          (e.target === this && e.relatedTarget === this._target)) {
        return;
      }

      // Mark that the hovercard is not visible and do not allow focusing
      this._isShowing = false;

      // Clear styles in preparation for the next time we need to show the card
      this.classList.remove(HOVER_CLASS);

      // Reset and remove the hovercard from the DOM
      this.style.cssText = '';
      this.$.hovercard.setAttribute('tabindex', -1);

      // Remove the hovercard from the container, given that it is still a child
      // of the container.
      if (this.container.contains(this)) {
        this.container.removeChild(this);
      }
    },

    /**
     * Shows/opens the hovercard. This occurs when the user triggers the
     * `mousenter` event on the hovercard's `target` element.
     *
     * @param {Event} e DOM Event (e.g., `mouseenter` event)
     */
    show(e) {
      if (this._isShowing) {
        return;
      }

      // Mark that the hovercard is now visible
      this._isShowing = true;
      this.setAttribute('tabindex', 0);

      // Add it to the DOM and calculate its position
      this.container.appendChild(this);
      this.updatePosition();

      // Trigger the transition
      this.classList.add(HOVER_CLASS);
    },

    /**
     * Updates the hovercard's position based on the `position` attribute
     * and the current position of the `target` element.
     *
     * The hovercard is supposed to stay open if the user hovers over it.
     * To keep it open when the user moves away from the target, the bounding
     * rects of the target and hovercard must touch or overlap.
     *
     * NOTE: You do not need to directly call this method unless you need to
     * update the position of the tooltip while it is already visible (the
     * target element has moved and the tooltip is still open).
     */
    updatePosition() {
      if (!this._target) { return; }

      // Calculate the necessary measurements and positions
      const parentRect = document.documentElement.getBoundingClientRect();
      const targetRect = this._target.getBoundingClientRect();
      const thisRect = this.getBoundingClientRect();

      const targetLeft = targetRect.left - parentRect.left;
      const targetTop = targetRect.top - parentRect.top;

      let hovercardLeft;
      let hovercardTop;
      const diagonalPadding = this.offset + DIAGONAL_OVERFLOW;
      let cssText = '';

      // Find the top and left position values based on the position attribute
      // of the hovercard.
      switch (this.position) {
        case 'top':
          hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
          hovercardTop = targetTop - thisRect.height - this.offset;
          cssText += `padding-bottom:${this.offset
          }px; margin-bottom:-${this.offset}px;`;
          break;
        case 'bottom':
          hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
          hovercardTop = targetTop + targetRect.height + this.offset;
          cssText +=
              `padding-top:${this.offset}px; margin-top:-${this.offset}px;`;
          break;
        case 'left':
          hovercardLeft = targetLeft - thisRect.width - this.offset;
          hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
          cssText +=
              `padding-right:${this.offset}px; margin-right:-${this.offset}px;`;
          break;
        case 'right':
          hovercardLeft = targetRect.right + this.offset;
          hovercardTop = targetTop + (targetRect.height - thisRect.height) / 2;
          cssText +=
              `padding-left:${this.offset}px; margin-left:-${this.offset}px;`;
          break;
        case 'bottom-right':
          hovercardLeft = targetRect.left + targetRect.width + this.offset;
          hovercardTop = targetRect.top + targetRect.height + this.offset;
          cssText += `padding-top:${diagonalPadding}px;`;
          cssText += `padding-left:${diagonalPadding}px;`;
          cssText += `margin-left:-${diagonalPadding}px;`;
          cssText += `margin-top:-${diagonalPadding}px;`;
          break;
        case 'bottom-left':
          hovercardLeft = targetRect.left - thisRect.width - this.offset;
          hovercardTop = targetRect.top + targetRect.height + this.offset;
          cssText += `padding-top:${diagonalPadding}px;`;
          cssText += `padding-right:${diagonalPadding}px;`;
          cssText += `margin-right:-${diagonalPadding}px;`;
          cssText += `margin-top:-${diagonalPadding}px;`;
          break;
        case 'top-left':
          hovercardLeft = targetRect.left - thisRect.width - this.offset;
          hovercardTop = targetRect.top - thisRect.height - this.offset;
          cssText += `padding-bottom:${diagonalPadding}px;`;
          cssText += `padding-right:${diagonalPadding}px;`;
          cssText += `margin-bottom:-${diagonalPadding}px;`;
          cssText += `margin-right:-${diagonalPadding}px;`;
          break;
        case 'top-right':
          hovercardLeft = targetRect.left + targetRect.width + this.offset;
          hovercardTop = targetRect.top - thisRect.height - this.offset;
          cssText += `padding-bottom:${diagonalPadding}px;`;
          cssText += `padding-left:${diagonalPadding}px;`;
          cssText += `margin-bottom:-${diagonalPadding}px;`;
          cssText += `margin-left:-${diagonalPadding}px;`;
          break;
      }

      // Prevent hovercard from appearing outside the viewport.
      // TODO(kaspern): fix hovercard appearing outside viewport on bottom and
      // right.
      if (hovercardLeft < 0) { hovercardLeft = 0; }
      if (hovercardTop < 0) { hovercardTop = 0; }
      // Set the hovercard's position
      cssText += `left:${hovercardLeft}px; top:${hovercardTop}px;`;
      this.style.cssText = cssText;
    },

    /**
     * Responds to a change in the `for` value and gets the updated `target`
     * element for the hovercard.
     *
     * @private
     */
    _forChanged() {
      this._target = this.target;
    },
  });
})();
<|endoftext|>