<commit_before>/**
 * @constructor
 * @param {webfont.UserAgent} userAgent
 * @param {webfont.DomHelper} domHelper
 * @param {webfont.EventDispatcher} eventDispatcher
 * @param {Object.<string, function(Object): number>} fontSizer
 * @param {function(function(), number=)} asyncCall
 * @param {function(): number} getTime
 */
webfont.FontWatcher = function(userAgent, domHelper, eventDispatcher, fontSizer,
    asyncCall, getTime) {
  this.domHelper_ = domHelper;
  this.eventDispatcher_ = eventDispatcher;
  this.fontSizer_ = fontSizer;
  this.asyncCall_ = asyncCall;
  this.getTime_ = getTime;
  this.currentlyWatched_ = 0;
  this.hasWebkitFallbackBug_ = userAgent.getEngine() === 'AppleWebKit' ? this.checkWebkitFallbackBug_() : false;
  this.last_ = false;
  this.success_ = false;
};

/**
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_VARIATION = 'n4';

/**
 * Returns true if this browser has a bug that causes the font stack
 * to not be respected while loading webfonts.
 *
 * @see https://bugs.webkit.org/show_bug.cgi?id=76684
 *
 * @return {boolean}
 * @private
 */
webfont.FontWatcher.prototype.checkWebkitFallbackBug_ = function() {
  // We build an empty webfont and try to set it as the font for our
  // ruler. Even though this will fail (since our webfont is invalid)
  // it will actually trigger the Webkit fallback bug.
  var font = this.domHelper_.createElement('style', null,
        "@font-face{" +
          "font-family:'__webfontloader_test__';" +
          "src:url(data:application/x-font-woff;base64,) format('woff')," +
          "url(data:font/truetype;base64,) format('truetype');" +
        "}"),
      ruler = new webfont.FontRuler(this.domHelper_, this.fontSizer_, 'iii');

  ruler.insert();

  // First we set the font to monospace and the test string to `iii`. Based
  // on our research, all platforms have at least a monospace, sans-serif,
  // and serif font installed. By using a test string that has a very
  // narrow width in non-monospace fonts it becomes easy to detect changes
  // in width.
  ruler.setFont('monospace');
  this.domHelper_.insertInto('head', font);

  // Measure the original size (of our monospace font)
  var beforeWidth = ruler.getSize().width;

  // Set the font to include our fake webfont, and then fallback to
  // `monospace` and `sans-serif`. Browsers without the bug will fall
  // back on the `monospace` font while loading the webfont, while
  // Webkit with the bug will fall back to its last resort font (which
  // according to our data is never `monospace`.) The `sans-serif` is
  // included here to deal with another bug in Chrome Android where
  // instead of using the last resort font it picks the last font in
  // the stack.
  //
  // See http://code.google.com/p/chromium/issues/detail?id=138257
  // for more information on the Chrome Android bug.
  ruler.setFont("'__webfontloader_test__', monospace, sans-serif");

  // Finally we compare the initial width and the current width. If
  // they do not match (i.e. it is either the sans-serif font, or
  // the last resort font) we assume the bug is present.
  var hasBug = beforeWidth !== ruler.getSize().width;
  this.domHelper_.removeElement(font);
  ruler.remove();

  return hasBug;
};

/**
 * Watches a set of font families.
 * @param {Array.<string>} fontFamilies The font family names to watch.
 * @param {Object.<string, Array.<string>>} fontDescriptions The font variations
 *     of each family to watch. Described with FVD.
 * @param {Object.<string, string>} fontTestStrings The font test strings for
 *     each family.
 * @param {function(new:webfont.FontWatchRunner, function(string, string),
 *     function(string, string), webfont.DomHelper,
 *     Object.<string, function(Object): number>,
 *     function(function(), number=), function(): number, string, string,
 *     boolean, string=)} fontWatchRunnerCtor The font watch runner constructor.
 * @param {boolean} last True if this is the last set of families to watch.
 */
webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
    fontTestStrings, fontWatchRunnerCtor, last) {
  var length = fontFamilies.length;

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    if (!fontDescriptions[fontFamily]) {
      fontDescriptions[fontFamily] = [webfont.FontWatcher.DEFAULT_VARIATION];
    }
    this.currentlyWatched_ += fontDescriptions[fontFamily].length;
  }

  if (last) {
    this.last_ = last;
  }

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    var descriptions = fontDescriptions[fontFamily];
    var fontTestString  = fontTestStrings[fontFamily];

    for (var j = 0, len = descriptions.length; j < len; j++) {
      var fontDescription = descriptions[j];

      this.eventDispatcher_.dispatchFontLoading(fontFamily, fontDescription);

      var activeCallback = webfont.bind(this, this.fontActive_);
      var inactiveCallback = webfont.bind(this, this.fontInactive_)
      var fontWatchRunner = new fontWatchRunnerCtor(activeCallback,
          inactiveCallback, this.domHelper_, this.fontSizer_, this.asyncCall_,
          this.getTime_, fontFamily, fontDescription, this.hasWebkitFallbackBug_, fontTestString);

      fontWatchRunner.start();
    }
  }
};

/**
 * Called by a FontWatchRunner when a font has been detected as active.
 * @param {string} fontFamily
 * @param {string} fontDescription
 * @private
 */
webfont.FontWatcher.prototype.fontActive_ = function(fontFamily, fontDescription) {
  this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
  this.success_ = true;
  this.decreaseCurrentlyWatched_();
};

/**
 * Called by a FontWatchRunner when a font has been detected as inactive.
 * @param {string} fontFamily
 * @param {string} fontDescription
 * @private
 */
webfont.FontWatcher.prototype.fontInactive_ = function(fontFamily, fontDescription) {
  this.eventDispatcher_.dispatchFontInactive(fontFamily, fontDescription);
  this.decreaseCurrentlyWatched_();
};

/**
 * @private
 */
webfont.FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
  if (--this.currentlyWatched_ == 0 && this.last_) {
    if (this.success_) {
      this.eventDispatcher_.dispatchActive();
    } else {
      this.eventDispatcher_.dispatchInactive();
    }
  }
};
<commit_msg>Moved inserting the ruler adjacent to the measurement call.<commit_after>/**
 * @constructor
 * @param {webfont.UserAgent} userAgent
 * @param {webfont.DomHelper} domHelper
 * @param {webfont.EventDispatcher} eventDispatcher
 * @param {Object.<string, function(Object): number>} fontSizer
 * @param {function(function(), number=)} asyncCall
 * @param {function(): number} getTime
 */
webfont.FontWatcher = function(userAgent, domHelper, eventDispatcher, fontSizer,
    asyncCall, getTime) {
  this.domHelper_ = domHelper;
  this.eventDispatcher_ = eventDispatcher;
  this.fontSizer_ = fontSizer;
  this.asyncCall_ = asyncCall;
  this.getTime_ = getTime;
  this.currentlyWatched_ = 0;
  this.hasWebkitFallbackBug_ = userAgent.getEngine() === 'AppleWebKit' ? this.checkWebkitFallbackBug_() : false;
  this.last_ = false;
  this.success_ = false;
};

/**
 * @type {string}
 * @const
 */
webfont.FontWatcher.DEFAULT_VARIATION = 'n4';

/**
 * Returns true if this browser has a bug that causes the font stack
 * to not be respected while loading webfonts.
 *
 * @see https://bugs.webkit.org/show_bug.cgi?id=76684
 *
 * @return {boolean}
 * @private
 */
webfont.FontWatcher.prototype.checkWebkitFallbackBug_ = function() {
  // We build an empty webfont and try to set it as the font for our
  // ruler. Even though this will fail (since our webfont is invalid)
  // it will actually trigger the Webkit fallback bug.
  var font = this.domHelper_.createElement('style', null,
        "@font-face{" +
          "font-family:'__webfontloader_test__';" +
          "src:url(data:application/x-font-woff;base64,) format('woff')," +
          "url(data:font/truetype;base64,) format('truetype');" +
        "}"),
      ruler = new webfont.FontRuler(this.domHelper_, this.fontSizer_, 'iii');

  // First we set the font to monospace and the test string to `iii`. Based
  // on our research, all platforms have at least a monospace, sans-serif,
  // and serif font installed. By using a test string that has a very
  // narrow width in non-monospace fonts it becomes easy to detect changes
  // in width.
  ruler.setFont('monospace');
  this.domHelper_.insertInto('head', font);

  ruler.insert();

  // Measure the original size (of our monospace font)
  var beforeWidth = ruler.getSize().width;

  // Set the font to include our fake webfont, and then fallback to
  // `monospace` and `sans-serif`. Browsers without the bug will fall
  // back on the `monospace` font while loading the webfont, while
  // Webkit with the bug will fall back to its last resort font (which
  // according to our data is never `monospace`.) The `sans-serif` is
  // included here to deal with another bug in Chrome Android where
  // instead of using the last resort font it picks the last font in
  // the stack.
  //
  // See http://code.google.com/p/chromium/issues/detail?id=138257
  // for more information on the Chrome Android bug.
  ruler.setFont("'__webfontloader_test__', monospace, sans-serif");

  // Finally we compare the initial width and the current width. If
  // they do not match (i.e. it is either the sans-serif font, or
  // the last resort font) we assume the bug is present.
  var hasBug = beforeWidth !== ruler.getSize().width;
  this.domHelper_.removeElement(font);
  ruler.remove();

  return hasBug;
};

/**
 * Watches a set of font families.
 * @param {Array.<string>} fontFamilies The font family names to watch.
 * @param {Object.<string, Array.<string>>} fontDescriptions The font variations
 *     of each family to watch. Described with FVD.
 * @param {Object.<string, string>} fontTestStrings The font test strings for
 *     each family.
 * @param {function(new:webfont.FontWatchRunner, function(string, string),
 *     function(string, string), webfont.DomHelper,
 *     Object.<string, function(Object): number>,
 *     function(function(), number=), function(): number, string, string,
 *     boolean, string=)} fontWatchRunnerCtor The font watch runner constructor.
 * @param {boolean} last True if this is the last set of families to watch.
 */
webfont.FontWatcher.prototype.watch = function(fontFamilies, fontDescriptions,
    fontTestStrings, fontWatchRunnerCtor, last) {
  var length = fontFamilies.length;

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    if (!fontDescriptions[fontFamily]) {
      fontDescriptions[fontFamily] = [webfont.FontWatcher.DEFAULT_VARIATION];
    }
    this.currentlyWatched_ += fontDescriptions[fontFamily].length;
  }

  if (last) {
    this.last_ = last;
  }

  for (var i = 0; i < length; i++) {
    var fontFamily = fontFamilies[i];
    var descriptions = fontDescriptions[fontFamily];
    var fontTestString  = fontTestStrings[fontFamily];

    for (var j = 0, len = descriptions.length; j < len; j++) {
      var fontDescription = descriptions[j];

      this.eventDispatcher_.dispatchFontLoading(fontFamily, fontDescription);

      var activeCallback = webfont.bind(this, this.fontActive_);
      var inactiveCallback = webfont.bind(this, this.fontInactive_)
      var fontWatchRunner = new fontWatchRunnerCtor(activeCallback,
          inactiveCallback, this.domHelper_, this.fontSizer_, this.asyncCall_,
          this.getTime_, fontFamily, fontDescription, this.hasWebkitFallbackBug_, fontTestString);

      fontWatchRunner.start();
    }
  }
};

/**
 * Called by a FontWatchRunner when a font has been detected as active.
 * @param {string} fontFamily
 * @param {string} fontDescription
 * @private
 */
webfont.FontWatcher.prototype.fontActive_ = function(fontFamily, fontDescription) {
  this.eventDispatcher_.dispatchFontActive(fontFamily, fontDescription);
  this.success_ = true;
  this.decreaseCurrentlyWatched_();
};

/**
 * Called by a FontWatchRunner when a font has been detected as inactive.
 * @param {string} fontFamily
 * @param {string} fontDescription
 * @private
 */
webfont.FontWatcher.prototype.fontInactive_ = function(fontFamily, fontDescription) {
  this.eventDispatcher_.dispatchFontInactive(fontFamily, fontDescription);
  this.decreaseCurrentlyWatched_();
};

/**
 * @private
 */
webfont.FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
  if (--this.currentlyWatched_ == 0 && this.last_) {
    if (this.success_) {
      this.eventDispatcher_.dispatchActive();
    } else {
      this.eventDispatcher_.dispatchInactive();
    }
  }
};
<|endoftext|>


<commit_before>/**
 * @module Rendering
 * @submodule Rendering
 * @for p5
 */

import p5 from './main';
import * as constants from '../core/constants';

/**
 * Main graphics and rendering context, as well as the base API
 * implementation for p5.js "core". To be used as the superclass for
 * Renderer2D and Renderer3D classes, respecitvely.
 *
 * @class p5.Renderer
 * @constructor
 * @extends p5.Element
 * @param {String} elt DOM node that is wrapped
 * @param {p5} [pInst] pointer to p5 instance
 * @param {Boolean} [isMainCanvas] whether we're using it as main canvas
 */
p5.Renderer = function(elt, pInst, isMainCanvas) {
  p5.Element.call(this, elt, pInst);
  this.canvas = elt;
  this._pixelsState = pInst;
  if (isMainCanvas) {
    this._isMainCanvas = true;
    // for pixel method sharing with pimage
    this._pInst._setProperty('_curElement', this);
    this._pInst._setProperty('canvas', this.canvas);
    this._pInst._setProperty('width', this.width);
    this._pInst._setProperty('height', this.height);
  } else {
    // hide if offscreen buffer by default
    this.canvas.style.display = 'none';
    this._styles = []; // non-main elt styles stored in p5.Renderer
  }

  this._textSize = 12;
  this._textLeading = 15;
  this._textFont = 'sans-serif';
  this._textStyle = constants.NORMAL;
  this._textAscent = null;
  this._textDescent = null;
  this._textAlign = constants.LEFT;
  this._textBaseline = constants.BASELINE;

  this._rectMode = constants.CORNER;
  this._ellipseMode = constants.CENTER;
  this._curveTightness = 0;
  this._imageMode = constants.CORNER;

  this._tint = null;
  this._doStroke = true;
  this._doFill = true;
  this._strokeSet = false;
  this._fillSet = false;
};

p5.Renderer.prototype = Object.create(p5.Element.prototype);

// the renderer should return a 'style' object that it wishes to
// store on the push stack.
p5.Renderer.prototype.push = function() {
  return {
    properties: {
      _doStroke: this._doStroke,
      _strokeSet: this._strokeSet,
      _doFill: this._doFill,
      _fillSet: this._fillSet,
      _tint: this._tint,
      _imageMode: this._imageMode,
      _rectMode: this._rectMode,
      _ellipseMode: this._ellipseMode,
      _textFont: this._textFont,
      _textLeading: this._textLeading,
      _textSize: this._textSize,
      _textAlign: this._textAlign,
      _textBaseline: this._textBaseline,
      _textStyle: this._textStyle
    }
  };
};

// a pop() operation is in progress
// the renderer is passed the 'style' object that it returned
// from its push() method.
p5.Renderer.prototype.pop = function(style) {
  if (style.properties) {
    // copy the style properties back into the renderer
    Object.assign(this, style.properties);
  }
};

/**
 * Resize our canvas element.
 */
p5.Renderer.prototype.resize = function(w, h) {
  this.width = w;
  this.height = h;
  this.elt.width = w * this._pInst._pixelDensity;
  this.elt.height = h * this._pInst._pixelDensity;
  this.elt.style.width = `${w}px`;
  this.elt.style.height = `${h}px`;
  if (this._isMainCanvas) {
    this._pInst._setProperty('width', this.width);
    this._pInst._setProperty('height', this.height);
  }
};

p5.Renderer.prototype.get = function(x, y, w, h) {
  const pixelsState = this._pixelsState;
  const pd = pixelsState._pixelDensity;
  const canvas = this.canvas;

  if (typeof x === 'undefined' && typeof y === 'undefined') {
    // get()
    x = y = 0;
    w = pixelsState.width;
    h = pixelsState.height;
  } else {
    x *= pd;
    y *= pd;

    if (typeof w === 'undefined' && typeof h === 'undefined') {
      // get(x,y)
      if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) {
        return [0, 0, 0, 0];
      }

      return this._getPixel(x, y);
    }
    // get(x,y,w,h)
  }

  const region = new p5.Image(w, h);
  region.canvas
    .getContext('2d')
    .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w, h);

  return region;
};

p5.Renderer.prototype.textLeading = function(l) {
  if (typeof l === 'number') {
    this._setProperty('_textLeading', l);
    return this._pInst;
  }

  return this._textLeading;
};

p5.Renderer.prototype.textSize = function(s) {
  if (typeof s === 'number') {
    this._setProperty('_textSize', s);
    this._setProperty('_textLeading', s * constants._DEFAULT_LEADMULT);
    return this._applyTextProperties();
  }

  return this._textSize;
};

p5.Renderer.prototype.textStyle = function(s) {
  if (s) {
    if (
      s === constants.NORMAL ||
      s === constants.ITALIC ||
      s === constants.BOLD ||
      s === constants.BOLDITALIC
    ) {
      this._setProperty('_textStyle', s);
    }

    return this._applyTextProperties();
  }

  return this._textStyle;
};

p5.Renderer.prototype.textAscent = function() {
  if (this._textAscent === null) {
    this._updateTextMetrics();
  }
  return this._textAscent;
};

p5.Renderer.prototype.textDescent = function() {
  if (this._textDescent === null) {
    this._updateTextMetrics();
  }
  return this._textDescent;
};

p5.Renderer.prototype.textAlign = function(h, v) {
  if (typeof h !== 'undefined') {
    this._setProperty('_textAlign', h);

    if (typeof v !== 'undefined') {
      this._setProperty('_textBaseline', v);
    }

    return this._applyTextProperties();
  } else {
    return {
      horizontal: this._textAlign,
      vertical: this._textBaseline
    };
  }
};

p5.Renderer.prototype.text = function(str, x, y, maxWidth, maxHeight) {
  const p = this._pInst;
  let cars;
  let n;
  let ii;
  let jj;
  let line;
  let testLine;
  let testWidth;
  let words;
  let totalHeight;
  let finalMaxHeight = Number.MAX_VALUE;

  if (!(this._doFill || this._doStroke)) {
    return;
  }

  if (typeof str === 'undefined') {
    return;
  } else if (typeof str !== 'string') {
    str = str.toString();
  }

  str = str.replace(/(\t)/g, '  ');
  cars = str.split('\n');

  if (typeof maxWidth !== 'undefined') {
    totalHeight = 0;
    for (ii = 0; ii < cars.length; ii++) {
      line = '';
      words = cars[ii].split(' ');
      for (n = 0; n < words.length; n++) {
        testLine = `${line + words[n]} `;
        testWidth = this.textWidth(testLine);
        if (testWidth > maxWidth) {
          line = `${words[n]} `;
          totalHeight += p.textLeading();
        } else {
          line = testLine;
        }
      }
    }

    if (this._rectMode === constants.CENTER) {
      x -= maxWidth / 2;
      y -= maxHeight / 2;
    }

    switch (this._textAlign) {
      case constants.CENTER:
        x += maxWidth / 2;
        break;
      case constants.RIGHT:
        x += maxWidth;
        break;
    }

    let baselineHacked = false;
    if (typeof maxHeight !== 'undefined') {
      switch (this._textBaseline) {
        case constants.BOTTOM:
          y += maxHeight - totalHeight;
          break;
        case constants.CENTER:
          y += (maxHeight - totalHeight) / 2;
          break;
        case constants.BASELINE:
          baselineHacked = true;
          this._textBaseline = constants.TOP;
          break;
      }

      // remember the max-allowed y-position for any line (fix to #928)
      finalMaxHeight = y + maxHeight - p.textAscent();
    }

    for (ii = 0; ii < cars.length; ii++) {
      line = '';
      words = cars[ii].split(' ');
      for (n = 0; n < words.length; n++) {
        testLine = `${line + words[n]} `;
        testWidth = this.textWidth(testLine);
        if (testWidth > maxWidth && line.length > 0) {
          this._renderText(p, line, x, y, finalMaxHeight);
          line = `${words[n]} `;
          y += p.textLeading();
        } else {
          line = testLine;
        }
      }

      this._renderText(p, line, x, y, finalMaxHeight);
      y += p.textLeading();

      if (baselineHacked) {
        this._textBaseline = constants.BASELINE;
      }
    }
  } else {
    // Offset to account for vertically centering multiple lines of text - no
    // need to adjust anything for vertical align top or baseline
    let offset = 0;

    const vAlign = p.textAlign().vertical;
    if (vAlign === constants.CENTER) {
      offset = (cars.length - 1) * p.textLeading() / 2;
    } else if (vAlign === constants.BOTTOM) {
      offset = (cars.length - 1) * p.textLeading();
    }

    for (jj = 0; jj < cars.length; jj++) {
      this._renderText(p, cars[jj], x, y - offset, finalMaxHeight);
      y += p.textLeading();
    }
  }

  return p;
};

p5.Renderer.prototype._applyDefaults = function() {
  return this;
};

/**
 * Helper fxn to check font type (system or otf)
 */
p5.Renderer.prototype._isOpenType = function(f = this._textFont) {
  return typeof f === 'object' && f.font && f.font.supported;
};

p5.Renderer.prototype._updateTextMetrics = function() {
  if (this._isOpenType()) {
    this._setProperty('_textAscent', this._textFont._textAscent());
    this._setProperty('_textDescent', this._textFont._textDescent());
    return this;
  }

  // Adapted from http://stackoverflow.com/a/25355178
  const text = document.createElement('span');
  text.style.fontFamily = this._textFont;
  text.style.fontSize = `${this._textSize}px`;
  text.innerHTML = 'ABCjgq|';

  const block = document.createElement('div');
  block.style.display = 'inline-block';
  block.style.width = '1px';
  block.style.height = '0px';

  const container = document.createElement('div');
  container.appendChild(text);
  container.appendChild(block);

  container.style.height = '0px';
  container.style.overflow = 'hidden';
  document.body.appendChild(container);

  block.style.verticalAlign = 'baseline';
  let blockOffset = calculateOffset(block);
  let textOffset = calculateOffset(text);
  const ascent = blockOffset[1] - textOffset[1];

  block.style.verticalAlign = 'bottom';
  blockOffset = calculateOffset(block);
  textOffset = calculateOffset(text);
  const height = blockOffset[1] - textOffset[1];
  const descent = height - ascent;

  document.body.removeChild(container);

  this._setProperty('_textAscent', ascent);
  this._setProperty('_textDescent', descent);

  return this;
};

/**
 * Helper fxn to measure ascent and descent.
 * Adapted from http://stackoverflow.com/a/25355178
 */
function calculateOffset(object) {
  let currentLeft = 0,
    currentTop = 0;
  if (object.offsetParent) {
    do {
      currentLeft += object.offsetLeft;
      currentTop += object.offsetTop;
    } while ((object = object.offsetParent));
  } else {
    currentLeft += object.offsetLeft;
    currentTop += object.offsetTop;
  }
  return [currentLeft, currentTop];
}

export default p5.Renderer;
<commit_msg>Stop adjusting height if new line isnt created<commit_after>/**
 * @module Rendering
 * @submodule Rendering
 * @for p5
 */

import p5 from './main';
import * as constants from '../core/constants';

/**
 * Main graphics and rendering context, as well as the base API
 * implementation for p5.js "core". To be used as the superclass for
 * Renderer2D and Renderer3D classes, respecitvely.
 *
 * @class p5.Renderer
 * @constructor
 * @extends p5.Element
 * @param {String} elt DOM node that is wrapped
 * @param {p5} [pInst] pointer to p5 instance
 * @param {Boolean} [isMainCanvas] whether we're using it as main canvas
 */
p5.Renderer = function(elt, pInst, isMainCanvas) {
  p5.Element.call(this, elt, pInst);
  this.canvas = elt;
  this._pixelsState = pInst;
  if (isMainCanvas) {
    this._isMainCanvas = true;
    // for pixel method sharing with pimage
    this._pInst._setProperty('_curElement', this);
    this._pInst._setProperty('canvas', this.canvas);
    this._pInst._setProperty('width', this.width);
    this._pInst._setProperty('height', this.height);
  } else {
    // hide if offscreen buffer by default
    this.canvas.style.display = 'none';
    this._styles = []; // non-main elt styles stored in p5.Renderer
  }

  this._textSize = 12;
  this._textLeading = 15;
  this._textFont = 'sans-serif';
  this._textStyle = constants.NORMAL;
  this._textAscent = null;
  this._textDescent = null;
  this._textAlign = constants.LEFT;
  this._textBaseline = constants.BASELINE;

  this._rectMode = constants.CORNER;
  this._ellipseMode = constants.CENTER;
  this._curveTightness = 0;
  this._imageMode = constants.CORNER;

  this._tint = null;
  this._doStroke = true;
  this._doFill = true;
  this._strokeSet = false;
  this._fillSet = false;
};

p5.Renderer.prototype = Object.create(p5.Element.prototype);

// the renderer should return a 'style' object that it wishes to
// store on the push stack.
p5.Renderer.prototype.push = function() {
  return {
    properties: {
      _doStroke: this._doStroke,
      _strokeSet: this._strokeSet,
      _doFill: this._doFill,
      _fillSet: this._fillSet,
      _tint: this._tint,
      _imageMode: this._imageMode,
      _rectMode: this._rectMode,
      _ellipseMode: this._ellipseMode,
      _textFont: this._textFont,
      _textLeading: this._textLeading,
      _textSize: this._textSize,
      _textAlign: this._textAlign,
      _textBaseline: this._textBaseline,
      _textStyle: this._textStyle
    }
  };
};

// a pop() operation is in progress
// the renderer is passed the 'style' object that it returned
// from its push() method.
p5.Renderer.prototype.pop = function(style) {
  if (style.properties) {
    // copy the style properties back into the renderer
    Object.assign(this, style.properties);
  }
};

/**
 * Resize our canvas element.
 */
p5.Renderer.prototype.resize = function(w, h) {
  this.width = w;
  this.height = h;
  this.elt.width = w * this._pInst._pixelDensity;
  this.elt.height = h * this._pInst._pixelDensity;
  this.elt.style.width = `${w}px`;
  this.elt.style.height = `${h}px`;
  if (this._isMainCanvas) {
    this._pInst._setProperty('width', this.width);
    this._pInst._setProperty('height', this.height);
  }
};

p5.Renderer.prototype.get = function(x, y, w, h) {
  const pixelsState = this._pixelsState;
  const pd = pixelsState._pixelDensity;
  const canvas = this.canvas;

  if (typeof x === 'undefined' && typeof y === 'undefined') {
    // get()
    x = y = 0;
    w = pixelsState.width;
    h = pixelsState.height;
  } else {
    x *= pd;
    y *= pd;

    if (typeof w === 'undefined' && typeof h === 'undefined') {
      // get(x,y)
      if (x < 0 || y < 0 || x >= canvas.width || y >= canvas.height) {
        return [0, 0, 0, 0];
      }

      return this._getPixel(x, y);
    }
    // get(x,y,w,h)
  }

  const region = new p5.Image(w, h);
  region.canvas
    .getContext('2d')
    .drawImage(canvas, x, y, w * pd, h * pd, 0, 0, w, h);

  return region;
};

p5.Renderer.prototype.textLeading = function(l) {
  if (typeof l === 'number') {
    this._setProperty('_textLeading', l);
    return this._pInst;
  }

  return this._textLeading;
};

p5.Renderer.prototype.textSize = function(s) {
  if (typeof s === 'number') {
    this._setProperty('_textSize', s);
    this._setProperty('_textLeading', s * constants._DEFAULT_LEADMULT);
    return this._applyTextProperties();
  }

  return this._textSize;
};

p5.Renderer.prototype.textStyle = function(s) {
  if (s) {
    if (
      s === constants.NORMAL ||
      s === constants.ITALIC ||
      s === constants.BOLD ||
      s === constants.BOLDITALIC
    ) {
      this._setProperty('_textStyle', s);
    }

    return this._applyTextProperties();
  }

  return this._textStyle;
};

p5.Renderer.prototype.textAscent = function() {
  if (this._textAscent === null) {
    this._updateTextMetrics();
  }
  return this._textAscent;
};

p5.Renderer.prototype.textDescent = function() {
  if (this._textDescent === null) {
    this._updateTextMetrics();
  }
  return this._textDescent;
};

p5.Renderer.prototype.textAlign = function(h, v) {
  if (typeof h !== 'undefined') {
    this._setProperty('_textAlign', h);

    if (typeof v !== 'undefined') {
      this._setProperty('_textBaseline', v);
    }

    return this._applyTextProperties();
  } else {
    return {
      horizontal: this._textAlign,
      vertical: this._textBaseline
    };
  }
};

p5.Renderer.prototype.text = function(str, x, y, maxWidth, maxHeight) {
  const p = this._pInst;
  let cars;
  let n;
  let ii;
  let jj;
  let line;
  let testLine;
  let currentLineLength;
  let testWidth;
  let words;
  let totalHeight;
  let finalMaxHeight = Number.MAX_VALUE;

  if (!(this._doFill || this._doStroke)) {
    return;
  }

  if (typeof str === 'undefined') {
    return;
  } else if (typeof str !== 'string') {
    str = str.toString();
  }

  str = str.replace(/(\t)/g, '  ');
  cars = str.split('\n');

  if (typeof maxWidth !== 'undefined') {
    totalHeight = 0;
    currentLineLength = 1;
    for (ii = 0; ii < cars.length; ii++) {
      line = '';
      words = cars[ii].split(' ');
      for (n = 0; n < words.length; n++) {
        testLine = `${line + words[n]} `;
        testWidth = this.textWidth(testLine);
        if (testWidth > maxWidth && currentLineLength > 1) {
          line = `${words[n]} `;
          totalHeight += p.textLeading();
          currentLineLength = 1;
        } else {
          line = testLine;
          currentLineLength += 1;
        }
      }
    }

    if (this._rectMode === constants.CENTER) {
      x -= maxWidth / 2;
      y -= maxHeight / 2;
    }

    switch (this._textAlign) {
      case constants.CENTER:
        x += maxWidth / 2;
        break;
      case constants.RIGHT:
        x += maxWidth;
        break;
    }

    let baselineHacked = false;
    if (typeof maxHeight !== 'undefined') {
      switch (this._textBaseline) {
        case constants.BOTTOM:
          y += maxHeight - totalHeight;
          break;
        case constants.CENTER:
          y += (maxHeight - totalHeight) / 2;
          break;
        case constants.BASELINE:
          baselineHacked = true;
          this._textBaseline = constants.TOP;
          break;
      }

      // remember the max-allowed y-position for any line (fix to #928)
      finalMaxHeight = y + maxHeight - p.textAscent();
    }

    for (ii = 0; ii < cars.length; ii++) {
      line = '';
      words = cars[ii].split(' ');
      for (n = 0; n < words.length; n++) {
        testLine = `${line + words[n]} `;
        testWidth = this.textWidth(testLine);
        if (testWidth > maxWidth && line.length > 0) {
          this._renderText(p, line, x, y, finalMaxHeight);
          line = `${words[n]} `;
          y += p.textLeading();
        } else {
          line = testLine;
        }
      }

      this._renderText(p, line, x, y, finalMaxHeight);
      y += p.textLeading();

      if (baselineHacked) {
        this._textBaseline = constants.BASELINE;
      }
    }
  } else {
    // Offset to account for vertically centering multiple lines of text - no
    // need to adjust anything for vertical align top or baseline
    let offset = 0;

    const vAlign = p.textAlign().vertical;
    if (vAlign === constants.CENTER) {
      offset = (cars.length - 1) * p.textLeading() / 2;
    } else if (vAlign === constants.BOTTOM) {
      offset = (cars.length - 1) * p.textLeading();
    }

    for (jj = 0; jj < cars.length; jj++) {
      this._renderText(p, cars[jj], x, y - offset, finalMaxHeight);
      y += p.textLeading();
    }
  }

  return p;
};

p5.Renderer.prototype._applyDefaults = function() {
  return this;
};

/**
 * Helper fxn to check font type (system or otf)
 */
p5.Renderer.prototype._isOpenType = function(f = this._textFont) {
  return typeof f === 'object' && f.font && f.font.supported;
};

p5.Renderer.prototype._updateTextMetrics = function() {
  if (this._isOpenType()) {
    this._setProperty('_textAscent', this._textFont._textAscent());
    this._setProperty('_textDescent', this._textFont._textDescent());
    return this;
  }

  // Adapted from http://stackoverflow.com/a/25355178
  const text = document.createElement('span');
  text.style.fontFamily = this._textFont;
  text.style.fontSize = `${this._textSize}px`;
  text.innerHTML = 'ABCjgq|';

  const block = document.createElement('div');
  block.style.display = 'inline-block';
  block.style.width = '1px';
  block.style.height = '0px';

  const container = document.createElement('div');
  container.appendChild(text);
  container.appendChild(block);

  container.style.height = '0px';
  container.style.overflow = 'hidden';
  document.body.appendChild(container);

  block.style.verticalAlign = 'baseline';
  let blockOffset = calculateOffset(block);
  let textOffset = calculateOffset(text);
  const ascent = blockOffset[1] - textOffset[1];

  block.style.verticalAlign = 'bottom';
  blockOffset = calculateOffset(block);
  textOffset = calculateOffset(text);
  const height = blockOffset[1] - textOffset[1];
  const descent = height - ascent;

  document.body.removeChild(container);

  this._setProperty('_textAscent', ascent);
  this._setProperty('_textDescent', descent);

  return this;
};

/**
 * Helper fxn to measure ascent and descent.
 * Adapted from http://stackoverflow.com/a/25355178
 */
function calculateOffset(object) {
  let currentLeft = 0,
    currentTop = 0;
  if (object.offsetParent) {
    do {
      currentLeft += object.offsetLeft;
      currentTop += object.offsetTop;
    } while ((object = object.offsetParent));
  } else {
    currentLeft += object.offsetLeft;
    currentTop += object.offsetTop;
  }
  return [currentLeft, currentTop];
}

export default p5.Renderer;
<|endoftext|>


<commit_before>'use strict'

const { join } = require('path')
const Boom = require('@hapi/boom')
const authCanExecuteResource = require('./authCanExecuteResource.js')
const debugLog = require('./debugLog.js')
const HandlerRunner = require('./handler-runner/index.js')
const LambdaContext = require('./LambdaContext.js')
const serverlessLog = require('./serverlessLog.js')
const {
  nullIfEmpty,
  parseMultiValueQueryStringParameters,
  parseQueryStringParameters,
  splitHandlerPathAndName,
} = require('./utils/index.js')

module.exports = function createAuthScheme(
  authFun,
  authorizerOptions,
  functionName,
  endpointPath,
  options,
  servicePath,
  provider,
) {
  const authFunName = authorizerOptions.name

  let identityHeader = 'authorization'

  if (authorizerOptions.type !== 'request') {
    const identitySourceMatch = /^method.request.header.((?:\w+-?)+\w+)$/.exec(
      authorizerOptions.identitySource,
    )
    if (!identitySourceMatch || identitySourceMatch.length !== 2) {
      throw new Error(
        `Serverless Offline only supports retrieving tokens from the headers (λ: ${authFunName})`,
      )
    }
    identityHeader = identitySourceMatch[1].toLowerCase()
  }

  const { handler, memorySize, name, runtime, timeout } = authFun
  const [handlerPath, handlerName] = splitHandlerPathAndName(handler)

  const funOptions = {
    functionName,
    handlerName, // i.e. run
    handlerPath: join(servicePath, handlerPath),
    lambdaName: name,
    memorySize,
    runtime: runtime || provider.runtime,
    timeout: (timeout || 30) * 1000,
  }

  // Create Auth Scheme
  return () => ({
    authenticate(request, h) {
      process.env = {
        ...provider.environment,
        ...authFun.environment,
        ...process.env,
      }

      console.log('') // Just to make things a little pretty
      serverlessLog(
        `Running Authorization function for ${request.method} ${request.path} (λ: ${authFunName})`,
      )

      // Get Authorization header
      const { req } = request.raw

      // Get path params
      // aws doesn't auto decode path params - hapi does
      const pathParams = { ...request.params }

      let event

      // Create event Object for authFunction
      //   methodArn is the ARN of the function we are running we are authorizing access to (or not)
      //   Account ID and API ID are not simulated
      if (authorizerOptions.type === 'request') {
        const multiValueQueryStringParameters = parseMultiValueQueryStringParameters(
          req.url,
        )

        const queryStringParameters = parseQueryStringParameters(req.url)

        event = {
          headers: {},
          httpMethod: request.method.toUpperCase(),
          multiValueHeaders: {},
          multiValueQueryStringParameters,
          path: request.path,
          pathParameters: nullIfEmpty(pathParams),
          queryStringParameters,
          type: 'REQUEST',
        }

        for (let i = 0; i < req.rawHeaders.length; i += 2) {
          event.headers[req.rawHeaders[i]] = req.rawHeaders[i + 1]
          event.multiValueHeaders[req.rawHeaders[i]] = (
            event.multiValueHeaders[req.rawHeaders[i]] || []
          ).concat(req.rawHeaders[i + 1])
        }
      } else {
        const authorization = req.headers[identityHeader]

        const identityValidationExpression = new RegExp(
          authorizerOptions.identityValidationExpression,
        )
        const matchedAuthorization = identityValidationExpression.test(
          authorization,
        )
        const finalAuthorization = matchedAuthorization ? authorization : ''

        debugLog(`Retrieved ${identityHeader} header "${finalAuthorization}"`)

        event = {
          authorizationToken: finalAuthorization,
          type: 'TOKEN',
        }
      }

      const httpMethod = request.method.toUpperCase()
      const apiId = 'random-api-id'
      const accountId = 'random-account-id'
      const resourcePath = request.path.replace(
        new RegExp(`^/${provider.stage}`),
        '',
      )

      event.methodArn = `arn:aws:execute-api:${provider.region}:${accountId}:${apiId}/${provider.stage}/${httpMethod}${resourcePath}`

      event.requestContext = {
        accountId,
        apiId,
        httpMethod,
        requestId: 'random-request-id',
        resourceId: 'random-resource-id',
        resourcePath,
        stage: provider.stage,
      }

      let handlerRunner

      // Create the Authorization function handler
      try {
        handlerRunner = new HandlerRunner(funOptions, options, provider.stage)
      } catch (err) {
        debugLog(`create authorization function handler error: ${err}`)

        throw Boom.badImplementation(
          null,
          `Error while loading ${authFunName}: ${err.message}`,
        )
      }

      return new Promise((resolve, reject) => {
        const callback = (err, data) => {
          // Return an unauthorized response
          const onError = (error) => {
            serverlessLog(
              `Authorization function returned an error response: (λ: ${authFunName})`,
              error,
            )

            return reject(Boom.unauthorized('Unauthorized'))
          }

          if (err) {
            onError(err)

            return
          }

          const onSuccess = (policy) => {
            // Validate that the policy document has the principalId set
            if (!policy.principalId) {
              serverlessLog(
                `Authorization response did not include a principalId: (λ: ${authFunName})`,
                err,
              )

              return reject(
                Boom.forbidden('No principalId set on the Response'),
              )
            }

            if (
              !authCanExecuteResource(policy.policyDocument, event.methodArn)
            ) {
              serverlessLog(
                `Authorization response didn't authorize user to access resource: (λ: ${authFunName})`,
                err,
              )

              return reject(
                Boom.forbidden(
                  'User is not authorized to access this resource',
                ),
              )
            }

            serverlessLog(
              `Authorization function returned a successful response: (λ: ${authFunName})`,
              policy,
            )

            // Set the credentials for the rest of the pipeline
            return resolve(
              h.authenticated({
                credentials: {
                  context: policy.context,
                  usageIdentifierKey: policy.usageIdentifierKey,
                  user: policy.principalId,
                },
              }),
            )
          }

          if (data && typeof data.then === 'function') {
            debugLog('Auth function returned a promise')
            data.then(onSuccess).catch(onError)
          } else if (data instanceof Error) {
            onError(data)
          } else {
            onSuccess(data)
          }
        }

        // TODO FIXME this should just use the LambdaFunction class
        // Creat the Lambda Context for the Auth function
        const lambdaContext = new LambdaContext({
          callback,
          functionName: authFun.name,
          memorySize: authFun.memorySize || provider.memorySize,
          timeout: authFun.timeout || provider.timeout,
        })

        // TODO FIXME this should just use the LambdaFunction class
        const result = handlerRunner.run(event, lambdaContext, callback)

        // Promise support
        if (result && typeof result.then === 'function') {
          result
            .then((data) => callback(null, data))
            .catch((err) => callback(err, null))
        } else if (result instanceof Error) {
          callback(result, null)
        }
      })
    },
  })
}
<commit_msg>Use header parsers in createAuthScheme<commit_after>'use strict'

const { join } = require('path')
const Boom = require('@hapi/boom')
const authCanExecuteResource = require('./authCanExecuteResource.js')
const debugLog = require('./debugLog.js')
const HandlerRunner = require('./handler-runner/index.js')
const LambdaContext = require('./LambdaContext.js')
const serverlessLog = require('./serverlessLog.js')
const {
  nullIfEmpty,
  parseHeaders,
  parseMultiValueHeaders,
  parseMultiValueQueryStringParameters,
  parseQueryStringParameters,
  splitHandlerPathAndName,
} = require('./utils/index.js')

module.exports = function createAuthScheme(
  authFun,
  authorizerOptions,
  functionName,
  endpointPath,
  options,
  servicePath,
  provider,
) {
  const authFunName = authorizerOptions.name

  let identityHeader = 'authorization'

  if (authorizerOptions.type !== 'request') {
    const identitySourceMatch = /^method.request.header.((?:\w+-?)+\w+)$/.exec(
      authorizerOptions.identitySource,
    )
    if (!identitySourceMatch || identitySourceMatch.length !== 2) {
      throw new Error(
        `Serverless Offline only supports retrieving tokens from the headers (λ: ${authFunName})`,
      )
    }
    identityHeader = identitySourceMatch[1].toLowerCase()
  }

  const { handler, memorySize, name, runtime, timeout } = authFun
  const [handlerPath, handlerName] = splitHandlerPathAndName(handler)

  const funOptions = {
    functionName,
    handlerName, // i.e. run
    handlerPath: join(servicePath, handlerPath),
    lambdaName: name,
    memorySize,
    runtime: runtime || provider.runtime,
    timeout: (timeout || 30) * 1000,
  }

  // Create Auth Scheme
  return () => ({
    authenticate(request, h) {
      process.env = {
        ...provider.environment,
        ...authFun.environment,
        ...process.env,
      }

      console.log('') // Just to make things a little pretty
      serverlessLog(
        `Running Authorization function for ${request.method} ${request.path} (λ: ${authFunName})`,
      )

      // Get Authorization header
      const { req } = request.raw

      // Get path params
      // aws doesn't auto decode path params - hapi does
      const pathParams = { ...request.params }

      let event

      // Create event Object for authFunction
      //   methodArn is the ARN of the function we are running we are authorizing access to (or not)
      //   Account ID and API ID are not simulated
      if (authorizerOptions.type === 'request') {
        const { rawHeaders, url } = req

        event = {
          headers: parseHeaders(rawHeaders),
          httpMethod: request.method.toUpperCase(),
          multiValueHeaders: parseMultiValueHeaders(rawHeaders),
          multiValueQueryStringParameters: parseMultiValueQueryStringParameters(
            url,
          ),
          path: request.path,
          pathParameters: nullIfEmpty(pathParams),
          queryStringParameters: parseQueryStringParameters(url),
          type: 'REQUEST',
        }
      } else {
        const authorization = req.headers[identityHeader]

        const identityValidationExpression = new RegExp(
          authorizerOptions.identityValidationExpression,
        )
        const matchedAuthorization = identityValidationExpression.test(
          authorization,
        )
        const finalAuthorization = matchedAuthorization ? authorization : ''

        debugLog(`Retrieved ${identityHeader} header "${finalAuthorization}"`)

        event = {
          authorizationToken: finalAuthorization,
          type: 'TOKEN',
        }
      }

      const httpMethod = request.method.toUpperCase()
      const apiId = 'random-api-id'
      const accountId = 'random-account-id'
      const resourcePath = request.path.replace(
        new RegExp(`^/${provider.stage}`),
        '',
      )

      event.methodArn = `arn:aws:execute-api:${provider.region}:${accountId}:${apiId}/${provider.stage}/${httpMethod}${resourcePath}`

      event.requestContext = {
        accountId,
        apiId,
        httpMethod,
        requestId: 'random-request-id',
        resourceId: 'random-resource-id',
        resourcePath,
        stage: provider.stage,
      }

      let handlerRunner

      // Create the Authorization function handler
      try {
        handlerRunner = new HandlerRunner(funOptions, options, provider.stage)
      } catch (err) {
        debugLog(`create authorization function handler error: ${err}`)

        throw Boom.badImplementation(
          null,
          `Error while loading ${authFunName}: ${err.message}`,
        )
      }

      return new Promise((resolve, reject) => {
        const callback = (err, data) => {
          // Return an unauthorized response
          const onError = (error) => {
            serverlessLog(
              `Authorization function returned an error response: (λ: ${authFunName})`,
              error,
            )

            return reject(Boom.unauthorized('Unauthorized'))
          }

          if (err) {
            onError(err)

            return
          }

          const onSuccess = (policy) => {
            // Validate that the policy document has the principalId set
            if (!policy.principalId) {
              serverlessLog(
                `Authorization response did not include a principalId: (λ: ${authFunName})`,
                err,
              )

              return reject(
                Boom.forbidden('No principalId set on the Response'),
              )
            }

            if (
              !authCanExecuteResource(policy.policyDocument, event.methodArn)
            ) {
              serverlessLog(
                `Authorization response didn't authorize user to access resource: (λ: ${authFunName})`,
                err,
              )

              return reject(
                Boom.forbidden(
                  'User is not authorized to access this resource',
                ),
              )
            }

            serverlessLog(
              `Authorization function returned a successful response: (λ: ${authFunName})`,
              policy,
            )

            // Set the credentials for the rest of the pipeline
            return resolve(
              h.authenticated({
                credentials: {
                  context: policy.context,
                  usageIdentifierKey: policy.usageIdentifierKey,
                  user: policy.principalId,
                },
              }),
            )
          }

          if (data && typeof data.then === 'function') {
            debugLog('Auth function returned a promise')
            data.then(onSuccess).catch(onError)
          } else if (data instanceof Error) {
            onError(data)
          } else {
            onSuccess(data)
          }
        }

        // TODO FIXME this should just use the LambdaFunction class
        // Creat the Lambda Context for the Auth function
        const lambdaContext = new LambdaContext({
          callback,
          functionName: authFun.name,
          memorySize: authFun.memorySize || provider.memorySize,
          timeout: authFun.timeout || provider.timeout,
        })

        // TODO FIXME this should just use the LambdaFunction class
        const result = handlerRunner.run(event, lambdaContext, callback)

        // Promise support
        if (result && typeof result.then === 'function') {
          result
            .then((data) => callback(null, data))
            .catch((err) => callback(err, null))
        } else if (result instanceof Error) {
          callback(result, null)
        }
      })
    },
  })
}
<|endoftext|>


<commit_before>/** MIT License (c) copyright 2010-2013 B Cavalier & J Hann */

/**
 * curl json! plugin
 *
 * Like the text! plugin, will only load same-domain resources.
 */

(function (globalEval) {
define(/*=='curl/plugin/json',==*/ ['./_fetchText'], function (fetchText) {

	var hasJsonParse, missingJsonMsg;

	hasJsonParse = typeof JSON != 'undefined' && JSON.parse;
	missingJsonMsg = 'Cannot use strictJSONParse without JSON.parse';

	return {

		load: function (absId, require, loaded, config) {
			var evaluator, errback;

			errback = loaded['error'] || error;

			if (config.strictJSONParse) {
				if (!hasJsonParse) error(new Error(missingJsonMsg));
				evaluator = parseSource;
			}
			else {
				evaluator = evalSource;
			}

			// get the text, then eval it
			fetchText(require['toUrl'](absId), evaluator, errback);

			function evalSource (source) {
				try {
					loaded(globalEval('(' + source + ')'));
				}
				catch (ex) {
					errback(ex);
				}
			}

			function parseSource (source) {
				return JSON.parse(source);
			}

		},

		'cramPlugin': '../cram/json'

	};

});
}(
	function (source) {/*jshint evil:true*/ eval(source); }
));
<commit_msg>ensure json is evaled in global scope and eliminate pollution from local arguments<commit_after>/** MIT License (c) copyright 2010-2013 B Cavalier & J Hann */

/**
 * curl json! plugin
 *
 * Like the text! plugin, will only load same-domain resources.
 */

(function (globalEval) {
define(/*=='curl/plugin/json',==*/ ['./_fetchText'], function (fetchText) {

	var hasJsonParse, missingJsonMsg;

	hasJsonParse = typeof JSON != 'undefined' && JSON.parse;
	missingJsonMsg = 'Cannot use strictJSONParse without JSON.parse';

	return {

		load: function (absId, require, loaded, config) {
			var evaluator, errback;

			errback = loaded['error'] || error;

			if (config.strictJSONParse) {
				if (!hasJsonParse) error(new Error(missingJsonMsg));
				evaluator = parseSource;
			}
			else {
				evaluator = evalSource;
			}

			// get the text, then eval it
			fetchText(require['toUrl'](absId), evaluator, errback);

			function evalSource (source) {
				try {
					loaded(globalEval('(' + source + ')'));
				}
				catch (ex) {
					errback(ex);
				}
			}

			function parseSource (source) {
				return JSON.parse(source);
			}

		},

		'cramPlugin': '../cram/json'

	};

});
}(
	function () {/*jshint evil:true*/ return (1,eval)(arguments[0]); }
));
<|endoftext|>


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

  function CommitStreamAdminBoot(el) {
    var persistentOptions = {
      headers: {
        Bearer: ''
      }
    };

    var app = angular.module('commitStreamAdmin', ['commitStreamAdmin.config',
      'angular-hal', 'ngRoute', 'ui.bootstrap', 'cgPrompt'
    ]);
    app.config(['$sceProvider', '$sceDelegateProvider', '$httpProvider',
      function($sceProvider, $sceDelegateProvider, $httpProvider) {
        $sceProvider.enabled(false);
        // $sceProvider.enabled(true);
        // $sceDelegateProvider.resourceUrlWhitelist([
        //   'self',
        //   'http://v1commitstream.cloudapp.net:6565/partials/instances.html',
        //   'http://v1commitstream.cloudapp.net:6565/partials/inboxes.html'
        // ]);

        //initialize get if not there
        if (!$httpProvider.defaults.headers.get) {
            $httpProvider.defaults.headers.get = {};
        }

        //disable IE ajax request caching
        $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
        $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
        $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
      }
    ]);

    app.factory('CommitStreamApi', ['serviceUrl', 'halClient',
      function(serviceUrl, halClient) {
        return {
          'load': function() {
            return halClient.$get(serviceUrl + '/api/public', persistentOptions);
          },
        };
      }
    ]);

    app.config(['serviceUrlProvider', '$routeProvider',
      function(serviceUrlProvider, $routeProvider) {
        var serviceUrl = serviceUrlProvider.$get();
        $routeProvider.when('/', {
          templateUrl: serviceUrl + '/partials/instances.html',
          controller: 'InstancesController'
        });
        $routeProvider.when('/inboxes', {
          templateUrl: serviceUrl + '/partials/inboxes.html',
          controller: 'InboxesController'
        });
        $routeProvider.otherwise({
          redirectTo: serviceUrl + '/'
        });
      }
    ]);

    app.directive('resizableOverlay', function($window) {
      return function($scope) {
        return angular.element($window).bind('resize', function() {
          $scope.adjustOverlay();
          return $scope.$apply();
        });
      };
    });

    app.directive('toggleCheckbox', function($timeout) {
      /**
       * Directive
       */
      return {
        restrict: 'A',
        transclude: true,
        replace: false,
        require: 'ngModel',
        link: function($scope, $element, $attr, ngModel) {
          // update model from Element
          var updateModelFromElement = function() {
            // If modified
            var checked = $element.prop('checked');
            if (checked != ngModel.$viewValue) {
              // Update ngModel
              ngModel.$setViewValue(checked);
              $scope.$apply();
            }
          };

          // Update input from Model
          var updateElementFromModel = function(newValue) {
            $element.trigger('change');
          };

          // Observe: Element changes affect Model
          $element.on('change', function() {
            updateModelFromElement();
          });

          $scope.$watch(function() {
            return ngModel.$viewValue;
          }, function(newValue) {
            updateElementFromModel(newValue);
          }, true);

          // Initialise BootstrapToggle
          $element.bootstrapToggle();
        }
      };
    });

    var isInstanceMode = function(config) {
      if (!config.configMode) return true;
      return config.configMode.type === 'instance';
    };

    var isDigestMode = function(config) {
      return config.configMode && config.configMode.type === 'digest';
    };

    var isDigestConfigured = function(config) {
      return config.configMode.configured;
    };

    app.controller('InstancesController', ['$rootScope', '$scope', '$http', '$q', '$location', 'CommitStreamApi', 'serviceUrl', 'configGetUrl', 'configSaveUrl',
      function($rootScope, $scope, $http, $q, $location, CommitStreamApi, serviceUrl, configGetUrl, configSaveUrl) {
        var config;

        $scope.loaderUrl = serviceUrl + '/ajax-loader.gif';

        var loading = true;

        $scope.loading = function() {
          return loading;
        };

        var errorHandler = function(error) {
          loading = false;
          if (error.data && error.data.errors && error.data.errors.length) {
            $scope.error.value = error.data.errors[0];
          } else {
            $scope.error.value = 'There was an unexpected error when processing your request.';
          }
        };

        $scope.error = {
          value: ''
        };

        $scope.errorActive = function() {
          return $scope.error.value !== '';
        };

        // For teamroom settings:
        var configDigestModeSave = function(configMode) {
          if (!configMode.configured) {
            if (configSaveUrl) return $http.post(configSaveUrl, configMode);
            return $q.when(true);
          }
          // TODO: do we even need this below?
          return $q.when(true);
        };

        CommitStreamApi
          .load()
          .then(function(resources) {
            $rootScope.resources = resources;
            if (!configGetUrl) return {
              data: {
                configMode: {
                  type: 'instance',
                  digestId: '',
                  configured: false
                },
                serviceUrl: serviceUrl,
                instanceId: '',
                apiKey: '',
                globalDigestId: '',
                configured: false,
                enabled: false
              }
            }
            return $http.get(configGetUrl);
          })
          .then(function(configRes) {
            // TODO handle null case?
            config = configRes.data;
            $rootScope.config = config;

            if (config.configured) {
              persistentOptions.headers.Bearer = config.apiKey;
              return $rootScope.resources.$get('instance', {
                instanceId: config.instanceId
              });
            } else {
              // return $rootScope.resources.$post('instances');
              return false;
            }
          })
          .then(function(instance) {
            if (instance) {
              persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
              $rootScope.instance = instance;

              if (isInstanceMode(config)) {
                if (config.configured) {
                  return $rootScope.resources.$get('digest', {
                    instanceId: config.instanceId,
                    digestId: config.globalDigestId
                  });
                } else {
                  return instance.$post('digest-create', {}, {
                    description: 'Global Repositories List'
                  });
                }
              } else if (isDigestMode(config)) {
                if (isDigestConfigured(config)) {
                  return $rootScope.resources.$get('digest', {
                    instanceId: config.instanceId,
                    digestId: config.configMode.digestId
                  });
                } else {
                  return instance.$post('digest-create', {}, {
                    description: 'Repositories List'
                  });
                }
              } else {
                // Don't know what state it's in here, but probably didn't get here naturally, so just return false
                return false;
              }
            }
          })
          .then(function(digest) {
            if (digest) {
              $rootScope.digest = digest;
            }
            // Check if we are in digestMode and need to save
            if (isDigestMode(config) && !isDigestConfigured(config)) {
              config.configMode.digestId = digest.digestId;
              configDigestModeSave(config.configMode)
              .then(function(configModeSaveResult) {
                console.log('configModeSave:');
                console.log(configModeSaveResult);
                $location.path('/inboxes');
              })
              .catch(errorHandler);
            } else {
              $location.path('/inboxes');
            }
          })
          .catch(errorHandler);
      }
    ]);

    app.controller('InboxesController', ['$rootScope', '$scope', '$timeout', 'serviceUrl',
      'configSaveUrl', '$http', '$q', 'prompt', '$location',
      function($rootScope, $scope, $timeout, serviceUrl,
        configSaveUrl, $http, $q, prompt, $location) {

        $scope.inboxesVisible = function() {
          // Only display when we actually have the config in $scope!
          return $rootScope.config;
        };

        if (!$rootScope.config) {
          $location.path('/');
          return;
        }

        $scope.newInbox = {
          url: '',
          name: '',
          family: 'GitHub' // Until we support other systems, hard-code this.
        };

        $scope.serviceUrl = serviceUrl;
        $scope.inboxes = [];

        $scope.enabledState = {
          enabled: $rootScope.config.enabled,
          applying: false,
          onText: 'Enabled',
          offText: 'Disabled'
        };

        $scope.message = {
          value: ''
        };

        $scope.messageActive = function() {
          return $scope.message.value !== '';
        };

        $scope.error = {
          value: ''
        };

        $scope.errorActive = function() {
          return $scope.error.value !== '';
        };

        // NOTE: this is a bit of a hack to remove errors upon network request to clear
        // out the UI prompts
        persistentOptions.transformUrl = function(url) {
          $scope.error.value = '';
          return url;
        };

        $scope.urlPattern = /^https?\:\/\/.{1,}\/.{1,}$/;

        $scope.inboxName = function() {
          if (!$scope.newInbox.url || $scope.newInbox.url.length < 1) return '...';
          var index = $scope.newInbox.url.lastIndexOf('/');
          if (index < 0) return '...';
          if (index === $scope.newInbox.url.length - 1) return '...';
          return $scope.newInbox.url.substr(index + 1);
        };

        var errorHandler = function(error) {
          if (error.data && error.data.errors && error.data.errors.length) {
            $scope.error.value = error.data.errors[0];
          } else {
            $scope.error.value = 'There was an unexpected error when processing your request.';
          }
        };

        var configSave = function(enabled) {
          $rootScope.config.enabled = enabled;

          if (!$rootScope.config.configured) {
            return $rootScope.resources.$post('instances')
              .then(function(instance) {
                persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
                $rootScope.instance = instance;
                return instance.$post('digest-create', {}, {
                  description: 'Global Repositories List'
                });
              })
              .then(function(digest) {
                $rootScope.digest = digest;
                $rootScope.config.instanceId = $rootScope.instance.instanceId;
                $rootScope.config.globalDigestId = digest.digestId;
                $rootScope.config.apiKey = $rootScope.instance.apiKey;
                $rootScope.config.configured = true;
                if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
                return $q.when(true);
              });
          } else {
            if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
            return $q.when(true);
          }
        };

        var inboxConfigure = function(inbox) {
          var links = inbox.$links();
          inbox.addCommit = links['add-commit'].href + '?apiKey=' + persistentOptions.headers.Bearer;
          inbox.removeHref = links['self'].href + '?apiKey=' + persistentOptions.headers.Bearer;
        };

        var inboxesGet = function() {
          if ($rootScope.digest) {
            $rootScope.digest.$get('inboxes')
              .then(function(inboxesRes) {
                return inboxesRes.$get('inboxes');
              }).then(function(inboxes) {
                $scope.inboxes.length = 0;
                inboxes.forEach(function(inbox) {
                  inboxConfigure(inbox);
                  $scope.inboxes.unshift(inbox);
                });
              })
              .catch(errorHandler);
          }
        };

        var inboxesUpdate = function(enabled) {
          if (enabled) {
            $timeout(function() {
              $('.inbox-url').select().focus();
            });
          }
          inboxesGet();
        }

        inboxesUpdate($rootScope.config.enabled);

        $scope.isInstanceMode = function() {
          return isInstanceMode($rootScope.config);
        };

        $scope.isDigestMode = function() {
          return isDigestMode($rootScope.config);
        };

        $scope.enabledChanged = function() {
          $scope.newInbox.url = '';

          var toggle = $('.commitstream-admin .enabled');

          var enabled = toggle.prop('checked');

          $scope.enabledState.applying = true;
          toggle.bootstrapToggle('disable');

          configSave(enabled).then(function(configSaveResult) {
            // TODO need to handle configSaveResult?
            inboxesUpdate(enabled);
          })
            .catch(errorHandler)
            .finally(function() {
              $scope.enabledState.applying = false;
              toggle.bootstrapToggle('enable');
            });
        };

        $scope.applying = function() {
          return $scope.enabledState.applying;
        };

        $scope.overlayVisible = function() {
          return $scope.enabledState.applying || !$rootScope.config.enabled;
        };

        $scope.adjustOverlay = function() {
          var repolistWidth = $('.repos-list').width();
          var repolistHeight = $('.repos-list').height();
          $('.repos-section .overlay').height(repolistHeight);
          $('.repos-section .overlay').width(repolistWidth);
        };

        // A jQuery based way of handling resizing of the window to adjust the overlay
        // when CS is disabled.
        // $(window).resize(function(){
        //   $scope.adjustOverlay();
        // });

        $scope.inboxCreating = false;

        $scope.inboxCreate = function() {
          //TODO: put the first 3 lines after the try in a promise
          try {
            $scope.inboxCreating = true;
            var index = $scope.newInbox.url.lastIndexOf('/');
            $scope.newInbox.name = $scope.newInbox.url.substr(index + 1);

            $rootScope.digest.$post('inbox-create', {}, $scope.newInbox)
              .then(function(inbox) {
                inboxConfigure(inbox);
                $scope.inboxes.unshift(inbox);
                $scope.newInbox.url = '';
                $scope.inboxHighlightTop(inbox.removeHref);
              })
              .catch(errorHandler)
              .finally(function() {
                $scope.inboxCreating = false;
              });
          } catch (e) {
            $scope.inboxCreating = false;
          }
        };

        $scope.inboxRemove = function(inbox) {
          prompt({
            title: 'Remove Repository?',
            message: 'Are you sure you want to remove the repository ' + inbox.name + '?',
            buttons: [{
              label: 'Remove',
              primary: true,
              class: 'action-remove'
            }, {
              label: 'Cancel',
              cancel: true,
              class: 'action-cancel'
            }]
          }).then(function() {
            inbox.$del('self').then(function(result) {
              $scope.message.value = 'Successfully removed repository';
              var index = $scope.inboxes.indexOf(inbox);
              $scope.inboxes.splice(index, 1);
              $timeout(function() {
                $('.commitstream-admin .message').fadeOut('slow', function() {
                  $scope.message.value = '';
                });
              }, 4000);
            })
              .catch(errorHandler);
          });
        };

        var inboxHighlight = function(el) {
          if ($rootScope.config.enabled) {
            el.focus();
            el.select();
          }
        };

        $scope.inboxHighlight = function(evt) {
          inboxHighlight(evt.currentTarget);
        };

        $scope.inboxHighlightTop = function() {
          $timeout(function() {
            var el = $($('.commitstream-admin .inbox')[0]);
            inboxHighlight(el);
          }, 0);
        };

      }
    ]);

    angular.bootstrap(el, ['commitStreamAdmin']);
  };
  window.CommitStreamAdminBoot = CommitStreamAdminBoot;
}());<commit_msg>S-52700 Killed old console.log statements<commit_after>(function() {
  'use strict';

  function CommitStreamAdminBoot(el) {
    var persistentOptions = {
      headers: {
        Bearer: ''
      }
    };

    var app = angular.module('commitStreamAdmin', ['commitStreamAdmin.config',
      'angular-hal', 'ngRoute', 'ui.bootstrap', 'cgPrompt'
    ]);
    app.config(['$sceProvider', '$sceDelegateProvider', '$httpProvider',
      function($sceProvider, $sceDelegateProvider, $httpProvider) {
        $sceProvider.enabled(false);
        // $sceProvider.enabled(true);
        // $sceDelegateProvider.resourceUrlWhitelist([
        //   'self',
        //   'http://v1commitstream.cloudapp.net:6565/partials/instances.html',
        //   'http://v1commitstream.cloudapp.net:6565/partials/inboxes.html'
        // ]);

        //initialize get if not there
        if (!$httpProvider.defaults.headers.get) {
            $httpProvider.defaults.headers.get = {};
        }

        //disable IE ajax request caching
        $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
        $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
        $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
      }
    ]);

    app.factory('CommitStreamApi', ['serviceUrl', 'halClient',
      function(serviceUrl, halClient) {
        return {
          'load': function() {
            return halClient.$get(serviceUrl + '/api/public', persistentOptions);
          },
        };
      }
    ]);

    app.config(['serviceUrlProvider', '$routeProvider',
      function(serviceUrlProvider, $routeProvider) {
        var serviceUrl = serviceUrlProvider.$get();
        $routeProvider.when('/', {
          templateUrl: serviceUrl + '/partials/instances.html',
          controller: 'InstancesController'
        });
        $routeProvider.when('/inboxes', {
          templateUrl: serviceUrl + '/partials/inboxes.html',
          controller: 'InboxesController'
        });
        $routeProvider.otherwise({
          redirectTo: serviceUrl + '/'
        });
      }
    ]);

    app.directive('resizableOverlay', function($window) {
      return function($scope) {
        return angular.element($window).bind('resize', function() {
          $scope.adjustOverlay();
          return $scope.$apply();
        });
      };
    });

    app.directive('toggleCheckbox', function($timeout) {
      /**
       * Directive
       */
      return {
        restrict: 'A',
        transclude: true,
        replace: false,
        require: 'ngModel',
        link: function($scope, $element, $attr, ngModel) {
          // update model from Element
          var updateModelFromElement = function() {
            // If modified
            var checked = $element.prop('checked');
            if (checked != ngModel.$viewValue) {
              // Update ngModel
              ngModel.$setViewValue(checked);
              $scope.$apply();
            }
          };

          // Update input from Model
          var updateElementFromModel = function(newValue) {
            $element.trigger('change');
          };

          // Observe: Element changes affect Model
          $element.on('change', function() {
            updateModelFromElement();
          });

          $scope.$watch(function() {
            return ngModel.$viewValue;
          }, function(newValue) {
            updateElementFromModel(newValue);
          }, true);

          // Initialise BootstrapToggle
          $element.bootstrapToggle();
        }
      };
    });

    var isInstanceMode = function(config) {
      if (!config.configMode) return true;
      return config.configMode.type === 'instance';
    };

    var isDigestMode = function(config) {
      return config.configMode && config.configMode.type === 'digest';
    };

    var isDigestConfigured = function(config) {
      return config.configMode.configured;
    };

    app.controller('InstancesController', ['$rootScope', '$scope', '$http', '$q', '$location', 'CommitStreamApi', 'serviceUrl', 'configGetUrl', 'configSaveUrl',
      function($rootScope, $scope, $http, $q, $location, CommitStreamApi, serviceUrl, configGetUrl, configSaveUrl) {
        var config;

        $scope.loaderUrl = serviceUrl + '/ajax-loader.gif';

        var loading = true;

        $scope.loading = function() {
          return loading;
        };

        var errorHandler = function(error) {
          loading = false;
          if (error.data && error.data.errors && error.data.errors.length) {
            $scope.error.value = error.data.errors[0];
          } else {
            $scope.error.value = 'There was an unexpected error when processing your request.';
          }
        };

        $scope.error = {
          value: ''
        };

        $scope.errorActive = function() {
          return $scope.error.value !== '';
        };

        // For teamroom settings:
        var configDigestModeSave = function(configMode) {
          if (!configMode.configured) {
            if (configSaveUrl) return $http.post(configSaveUrl, configMode);
            return $q.when(true);
          }
          // TODO: do we even need this below?
          return $q.when(true);
        };

        CommitStreamApi
          .load()
          .then(function(resources) {
            $rootScope.resources = resources;
            if (!configGetUrl) return {
              data: {
                configMode: {
                  type: 'instance',
                  digestId: '',
                  configured: false
                },
                serviceUrl: serviceUrl,
                instanceId: '',
                apiKey: '',
                globalDigestId: '',
                configured: false,
                enabled: false
              }
            }
            return $http.get(configGetUrl);
          })
          .then(function(configRes) {
            // TODO handle null case?
            config = configRes.data;
            $rootScope.config = config;

            if (config.configured) {
              persistentOptions.headers.Bearer = config.apiKey;
              return $rootScope.resources.$get('instance', {
                instanceId: config.instanceId
              });
            } else {
              // return $rootScope.resources.$post('instances');
              return false;
            }
          })
          .then(function(instance) {
            if (instance) {
              persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
              $rootScope.instance = instance;

              if (isInstanceMode(config)) {
                if (config.configured) {
                  return $rootScope.resources.$get('digest', {
                    instanceId: config.instanceId,
                    digestId: config.globalDigestId
                  });
                } else {
                  return instance.$post('digest-create', {}, {
                    description: 'Global Repositories List'
                  });
                }
              } else if (isDigestMode(config)) {
                if (isDigestConfigured(config)) {
                  return $rootScope.resources.$get('digest', {
                    instanceId: config.instanceId,
                    digestId: config.configMode.digestId
                  });
                } else {
                  return instance.$post('digest-create', {}, {
                    description: 'Repositories List'
                  });
                }
              } else {
                // Don't know what state it's in here, but probably didn't get here naturally, so just return false
                return false;
              }
            }
          })
          .then(function(digest) {
            if (digest) {
              $rootScope.digest = digest;
            }
            // Check if we are in digestMode and need to save
            if (isDigestMode(config) && !isDigestConfigured(config)) {
              config.configMode.digestId = digest.digestId;
              configDigestModeSave(config.configMode)
              .then(function(configModeSaveResult) {
                $location.path('/inboxes');
              })
              .catch(errorHandler);
            } else {
              $location.path('/inboxes');
            }
          })
          .catch(errorHandler);
      }
    ]);

    app.controller('InboxesController', ['$rootScope', '$scope', '$timeout', 'serviceUrl',
      'configSaveUrl', '$http', '$q', 'prompt', '$location',
      function($rootScope, $scope, $timeout, serviceUrl,
        configSaveUrl, $http, $q, prompt, $location) {

        $scope.inboxesVisible = function() {
          // Only display when we actually have the config in $scope!
          return $rootScope.config;
        };

        if (!$rootScope.config) {
          $location.path('/');
          return;
        }

        $scope.newInbox = {
          url: '',
          name: '',
          family: 'GitHub' // Until we support other systems, hard-code this.
        };

        $scope.serviceUrl = serviceUrl;
        $scope.inboxes = [];

        $scope.enabledState = {
          enabled: $rootScope.config.enabled,
          applying: false,
          onText: 'Enabled',
          offText: 'Disabled'
        };

        $scope.message = {
          value: ''
        };

        $scope.messageActive = function() {
          return $scope.message.value !== '';
        };

        $scope.error = {
          value: ''
        };

        $scope.errorActive = function() {
          return $scope.error.value !== '';
        };

        // NOTE: this is a bit of a hack to remove errors upon network request to clear
        // out the UI prompts
        persistentOptions.transformUrl = function(url) {
          $scope.error.value = '';
          return url;
        };

        $scope.urlPattern = /^https?\:\/\/.{1,}\/.{1,}$/;

        $scope.inboxName = function() {
          if (!$scope.newInbox.url || $scope.newInbox.url.length < 1) return '...';
          var index = $scope.newInbox.url.lastIndexOf('/');
          if (index < 0) return '...';
          if (index === $scope.newInbox.url.length - 1) return '...';
          return $scope.newInbox.url.substr(index + 1);
        };

        var errorHandler = function(error) {
          if (error.data && error.data.errors && error.data.errors.length) {
            $scope.error.value = error.data.errors[0];
          } else {
            $scope.error.value = 'There was an unexpected error when processing your request.';
          }
        };

        var configSave = function(enabled) {
          $rootScope.config.enabled = enabled;

          if (!$rootScope.config.configured) {
            return $rootScope.resources.$post('instances')
              .then(function(instance) {
                persistentOptions.headers.Bearer = instance.apiKey; // Ensure apiKey for NEW instance
                $rootScope.instance = instance;
                return instance.$post('digest-create', {}, {
                  description: 'Global Repositories List'
                });
              })
              .then(function(digest) {
                $rootScope.digest = digest;
                $rootScope.config.instanceId = $rootScope.instance.instanceId;
                $rootScope.config.globalDigestId = digest.digestId;
                $rootScope.config.apiKey = $rootScope.instance.apiKey;
                $rootScope.config.configured = true;
                if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
                return $q.when(true);
              });
          } else {
            if (configSaveUrl) return $http.post(configSaveUrl, $rootScope.config);
            return $q.when(true);
          }
        };

        var inboxConfigure = function(inbox) {
          var links = inbox.$links();
          inbox.addCommit = links['add-commit'].href + '?apiKey=' + persistentOptions.headers.Bearer;
          inbox.removeHref = links['self'].href + '?apiKey=' + persistentOptions.headers.Bearer;
        };

        var inboxesGet = function() {
          if ($rootScope.digest) {
            $rootScope.digest.$get('inboxes')
              .then(function(inboxesRes) {
                return inboxesRes.$get('inboxes');
              }).then(function(inboxes) {
                $scope.inboxes.length = 0;
                inboxes.forEach(function(inbox) {
                  inboxConfigure(inbox);
                  $scope.inboxes.unshift(inbox);
                });
              })
              .catch(errorHandler);
          }
        };

        var inboxesUpdate = function(enabled) {
          if (enabled) {
            $timeout(function() {
              $('.inbox-url').select().focus();
            });
          }
          inboxesGet();
        }

        inboxesUpdate($rootScope.config.enabled);

        $scope.isInstanceMode = function() {
          return isInstanceMode($rootScope.config);
        };

        $scope.isDigestMode = function() {
          return isDigestMode($rootScope.config);
        };

        $scope.enabledChanged = function() {
          $scope.newInbox.url = '';

          var toggle = $('.commitstream-admin .enabled');

          var enabled = toggle.prop('checked');

          $scope.enabledState.applying = true;
          toggle.bootstrapToggle('disable');

          configSave(enabled).then(function(configSaveResult) {
            // TODO need to handle configSaveResult?
            inboxesUpdate(enabled);
          })
            .catch(errorHandler)
            .finally(function() {
              $scope.enabledState.applying = false;
              toggle.bootstrapToggle('enable');
            });
        };

        $scope.applying = function() {
          return $scope.enabledState.applying;
        };

        $scope.overlayVisible = function() {
          return $scope.enabledState.applying || !$rootScope.config.enabled;
        };

        $scope.adjustOverlay = function() {
          var repolistWidth = $('.repos-list').width();
          var repolistHeight = $('.repos-list').height();
          $('.repos-section .overlay').height(repolistHeight);
          $('.repos-section .overlay').width(repolistWidth);
        };

        // A jQuery based way of handling resizing of the window to adjust the overlay
        // when CS is disabled.
        // $(window).resize(function(){
        //   $scope.adjustOverlay();
        // });

        $scope.inboxCreating = false;

        $scope.inboxCreate = function() {
          //TODO: put the first 3 lines after the try in a promise
          try {
            $scope.inboxCreating = true;
            var index = $scope.newInbox.url.lastIndexOf('/');
            $scope.newInbox.name = $scope.newInbox.url.substr(index + 1);

            $rootScope.digest.$post('inbox-create', {}, $scope.newInbox)
              .then(function(inbox) {
                inboxConfigure(inbox);
                $scope.inboxes.unshift(inbox);
                $scope.newInbox.url = '';
                $scope.inboxHighlightTop(inbox.removeHref);
              })
              .catch(errorHandler)
              .finally(function() {
                $scope.inboxCreating = false;
              });
          } catch (e) {
            $scope.inboxCreating = false;
          }
        };

        $scope.inboxRemove = function(inbox) {
          prompt({
            title: 'Remove Repository?',
            message: 'Are you sure you want to remove the repository ' + inbox.name + '?',
            buttons: [{
              label: 'Remove',
              primary: true,
              class: 'action-remove'
            }, {
              label: 'Cancel',
              cancel: true,
              class: 'action-cancel'
            }]
          }).then(function() {
            inbox.$del('self').then(function(result) {
              $scope.message.value = 'Successfully removed repository';
              var index = $scope.inboxes.indexOf(inbox);
              $scope.inboxes.splice(index, 1);
              $timeout(function() {
                $('.commitstream-admin .message').fadeOut('slow', function() {
                  $scope.message.value = '';
                });
              }, 4000);
            })
              .catch(errorHandler);
          });
        };

        var inboxHighlight = function(el) {
          if ($rootScope.config.enabled) {
            el.focus();
            el.select();
          }
        };

        $scope.inboxHighlight = function(evt) {
          inboxHighlight(evt.currentTarget);
        };

        $scope.inboxHighlightTop = function() {
          $timeout(function() {
            var el = $($('.commitstream-admin .inbox')[0]);
            inboxHighlight(el);
          }, 0);
        };

      }
    ]);

    angular.bootstrap(el, ['commitStreamAdmin']);
  };
  window.CommitStreamAdminBoot = CommitStreamAdminBoot;
}());<|endoftext|>