<commit_before>/**
 * @fileoverview Helpers for measuring elements.
 */

goog.provide('treesaver.dimensions');

goog.require('treesaver.capabilities');
goog.require('treesaver.constants');

/**
 * Alias for Size type
 *
 * @typedef {{ w: number, h: number }}
 */
treesaver.dimensions.Size;

/**
 * Alias for SizeRange type
 *
 * @typedef {{ w: number, h: number, maxW: number, maxH: number }}
 */
treesaver.dimensions.SizeRange;

/**
 * Regex to determine whether a value is a pixel value.
 * @private
 */
treesaver.dimensions.pixel = /^-?\d+(?:px)?$/i;

/**
 * Regex to determine whether a value contains a number.
 * @private
 */
treesaver.dimensions.number = /^-?\d/;

/**
 * Whether the given size fits within the bounds set by the range
 *
 * @param {treesaver.dimensions.SizeRange} range
 * @param {treesaver.dimensions.Size} size
 * @return {boolean} True if both dimensions are within the min and max.
 */
treesaver.dimensions.inSizeRange = function(range, size) {
  if (!range) {
    return false;
  }

  return size.w >= range.minW && size.h >= range.minH &&
    // If Max isn't set, assume Infinity
    (!range.maxW || size.w <= range.maxW) &&
    (!range.maxH || size.h <= range.maxH);
};

/**
 *
 * @param {treesaver.dimensions.SizeRange} a
 * @param {treesaver.dimensions.Metrics} b
 * @param {boolean} outer
 * @return {treesaver.dimensions.SizeRange}
 */
treesaver.dimensions.mergeSizeRange = function(a, b, outer) {
  a = a || {};
  b = b || {};

  var bpHeight = outer ? b.bpHeight || (b.outerH ? b.outerH - b.h : 0) : 0,
      bpWidth = outer ? b.bpWidth || (b.outerW ? b.outerW - b.w : 0) : 0;

  return {
    w: Math.max(a.w || 0, (b.w + bpWidth) || 0),
    h: Math.max(a.h || 0, (b.h + bpHeight) || 0),
    maxW: Math.min(a.maxW || Infinity, b.maxW + bpWidth || Infinity),
    maxH: Math.min(a.maxH || Infinity, b.maxH + bpHeight || Infinity)
  };
};

/**
 * Convert a string value to pixels
 *
 * @param {!Element} el
 * @param {?string} val
 * @return {?number} Value in pixels.
 */
treesaver.dimensions.toPixels = function(el, val) {
  if (val && treesaver.dimensions.pixel.test(val)) {
    return parseFloat(val);
  }

  return null;
};

/**
 * Return the computedStyle object, which varies based on
 * browsers
 * @param {?Element} el
 * @return {Object}
 */
treesaver.dimensions.getStyleObject = function(el) {
  return document.defaultView.getComputedStyle(el, null);
};

/**
 * Return the width and height element in a simple object
 *
 * @param {Element} el
 * @return {!treesaver.dimensions.Size}
 */
treesaver.dimensions.getSize = function(el) {
  return {
    w: treesaver.dimensions.getOffsetWidth(el),
    h: treesaver.dimensions.getOffsetHeight(el)
  };
};

/**
 * Return the offsetHeight of the element.
 *
 * @param {?Element} el
 * @return {!number} Value in pixels.
 */
treesaver.dimensions.getOffsetHeight = function(el) {
  return el && el.offsetHeight || 0;
};

/**
 * Return the offsetWidth of the element.
 *
 * @param {?Element} el
 * @return {!number} Value in pixels.
 */
treesaver.dimensions.getOffsetWidth = function(el) {
  return el && el.offsetWidth || 0;
};

/**
 * Return the offsetTop of the element.
 *
 * @param {?Element} el
 * @return {!number} Value in pixels.
 */
treesaver.dimensions.getOffsetTop = function(el) {
  return el && el.offsetTop || 0;
}

// IE doesn't support getComputedStyle
if (SUPPORT_IE &&
    !(document.defaultView && document.defaultView.getComputedStyle)) {
  // Patch to use MSIE API
  treesaver.dimensions.getStyleObject = function(el) {
    return el.currentStyle;
  };

  if (treesaver.capabilities.IS_LEGACY) {
    treesaver.dimensions.getOffsetTop = function(el) {
      if (el) {
        el.style.zoom = 1;
        return el.offsetTop;
      }
      return 0;
    };

    treesaver.dimensions.getOffsetHeight = function(el) {
      if (el) {
        el.style.zoom = 1;
        return el.offsetHeight;
      }
      return 0;
    };

    treesaver.dimensions.getOffsetWidth = function(el) {
      if (el) {
        el.style.zoom = 1;
        return el.offsetWidth;
      }
      return 0;
    };
  }

  // If we are dealing with IE and the value contains some sort
  // of number we try Dean Edward's hack:
  // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
  treesaver.dimensions.toPixels = function(el, val) {
    var style, runtimeStyle, value;

    if (val && treesaver.dimensions.pixel.test(val)) {
      return parseFloat(val);
    }
    else if (val && treesaver.dimensions.number.test(val)) {
      style = el.style.left;
      runtimeStyle = el.runtimeStyle.left;
      el.runtimeStyle.left = el.currentStyle.left;
      el.style.left = val || 0;
      value = el.style.pixelLeft;
      el.style.left = style;
      el.runtimeStyle.left = runtimeStyle;
      return value;
    }
    return null;
  };
}

/**
 * Helper for setting a CSS value in pixels
 *
 * @param {!Element} el
 * @param {!string} propName
 * @param {!number} val
 * @return {!number} The value supplied.
 */
treesaver.dimensions.setCssPx = function(el, propName, val) {
  el.style[propName] = val + 'px';

  return val;
};

/**
 * Helper for setting the offset on an element, using CSS transforms if
 * supported, absolute positioning if not
 *
 * @param {!Element} el
 * @param {!number} x
 * @param {!number} y
 */
treesaver.dimensions.setOffset;

/**
 * Helper for setting the x-offset on an element, using CSS transforms if
 * supported, absolute positioning if not
 *
 * @param {!Element} el
 * @param {!number} x
 */
treesaver.dimensions.setOffsetX;

if (treesaver.capabilities.SUPPORTS_CSSTRANSFORMS) {
  /**
   * Helper for setting the transform property on an element
   *
   * @param {!Element} el
   * @param {!string} val
   */
  treesaver.dimensions.setTransformProperty_ = function(el, val) {
    // TODO: Detect once
    if ('transformProperty' in el.style) {
      el.style['transformProperty'] = val;
    }
    else {
      el.style[treesaver.capabilities.domCSSPrefix + 'Transform'] = val;
    }
  };

  if (treesaver.capabilities.SUPPORTS_CSSTRANSFORMS3D) {
    treesaver.dimensions.setOffset = function(el, x, y) {
      treesaver.dimensions.setTransformProperty_(el,
        'translate3d(' + x + 'px,' + y + 'px,0)');
    };
  }
  else {
    treesaver.dimensions.setOffset = function(el, x, y) {
      treesaver.dimensions.setTransformProperty_(el,
        'translate(' + x + 'px,' + y + 'px)');
    };
  }

  // Take the easy way out of setting the x offset
  treesaver.dimensions.setOffsetX = function(el, x) {
    treesaver.dimensions.setOffset(el, x, 0);
  };
}
else {
  // Fall back to absolute positioning
  treesaver.dimensions.setOffset = function(el, x, y) {
    treesaver.dimensions.setCssPx(el, 'left', x);
    treesaver.dimensions.setCssPx(el, 'top', y);
  };

  // Take the easy way out of setting the x offset
  treesaver.dimensions.setOffsetX = function(el, x) {
    treesaver.dimensions.setCssPx(el, 'left', x);
  };
}

/**
 * Round up to the nearest multiple of the base number
 *
 * @param {!number} number
 * @param {!number} base
 * @return {number} A multiple of the base number.
 */
treesaver.dimensions.roundUp = function(number, base) {
  return Math.ceil(number) + base - (number % base);
};

/**
 * The style dimensions of an element including margin, border, and
 * padding as well as line height
 *
 * @constructor
 * @param {!Element=} el
 */
treesaver.dimensions.Metrics = function(el) {
  if (!el) {
    return;
  }

  var style = treesaver.dimensions.getStyleObject(el),
      oldPosition = el.style.position,
      oldStyleAttribute = el.getAttribute('style'),
      tmp;

  this.display = style.display;
  this.position = style.position;

  // Webkit gives incorrect right margins for non-absolutely
  // positioned elements
  //if (this.position !== 'absolute') {
    //el.style.position = 'absolute';
  //}
  // Disable this for now, as it can give incorrect formatting
  // for elements in the flow
  // Also: Getting computed style is kinda silly if we change the
  // styling -- may affect the measurements anyway

  // Force hasLayout on IE7 so we get accurate measurements.
  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    el.style.zoom = 1;
  }

  // Margin
  this.marginTop = treesaver.dimensions.toPixels(el, style.marginTop) || 0;
  this.marginBottom = treesaver.dimensions.toPixels(el, style.marginBottom) || 0;
  this.marginLeft = treesaver.dimensions.toPixels(el, style.marginLeft) || 0;
  this.marginRight = treesaver.dimensions.toPixels(el, style.marginRight) || 0;
  // Summed totals
  this.marginHeight = this.marginTop + this.marginBottom;
  this.marginWidth = this.marginLeft + this.marginRight;

  // Border
  this.borderTop = treesaver.dimensions.toPixels(el, style.borderTopWidth) || 0;
  this.borderBottom = treesaver.dimensions.toPixels(el, style.borderBottomWidth) || 0;
  this.borderLeft = treesaver.dimensions.toPixels(el, style.borderLeftWidth) || 0;
  this.borderRight = treesaver.dimensions.toPixels(el, style.borderRightWidth) || 0;

  // Padding
  this.paddingTop = treesaver.dimensions.toPixels(el, style.paddingTop) || 0;
  this.paddingBottom = treesaver.dimensions.toPixels(el, style.paddingBottom) || 0;
  this.paddingLeft = treesaver.dimensions.toPixels(el, style.paddingLeft) || 0;
  this.paddingRight = treesaver.dimensions.toPixels(el, style.paddingRight) || 0;

  // Summed totals for border & padding
  this.bpTop = this.borderTop + this.paddingTop;
  this.bpBottom = this.borderBottom + this.paddingBottom;
  this.bpHeight = this.bpTop + this.bpBottom;
  this.bpLeft = this.borderLeft + this.paddingLeft;
  this.bpRight = this.borderRight + this.paddingRight;
  this.bpWidth = this.bpLeft + this.bpRight;

  // Outer Width & Height
  this.outerW = treesaver.dimensions.getOffsetWidth(el);
  this.outerH = treesaver.dimensions.getOffsetHeight(el);

  // Inner Width & Height
  this.w = this.outerW - this.bpWidth;
  this.h = this.outerH - this.bpHeight;

  // Min & Max : Width & Height
  this.minW = treesaver.dimensions.toPixels(el, style.minWidth) || 0;
  this.minH = treesaver.dimensions.toPixels(el, style.minHeight) || 0;

  // Opera returns -1 for a max-width or max-height that is not set.
  tmp = treesaver.dimensions.toPixels(el, style.maxWidth);
  this.maxW = (!tmp || tmp === -1) ? Infinity : tmp;

  tmp = treesaver.dimensions.toPixels(el, style.maxHeight);
  this.maxH = (!tmp || tmp === -1) ? Infinity : tmp;

  // Line height
  this.lineHeight = treesaver.dimensions.toPixels(el, style.lineHeight) || null;

  // Restore the original position property on style
  //if (this.position !== 'absolute') {
    //el.style.position = oldPosition;
    //if (!el.getAttribute('style')) {
      //el.removeAttribute('style');
    //}
  //}

  // Not sure if resetting hasLayout is the right thing to do
  // here, as it might affect the measurements we just did.
  //if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
  //  el.style.zoom = 'normal';
  //}
};

/**
 * Make a copy of the object
 *
 * @return {!treesaver.dimensions.Metrics}
 */
treesaver.dimensions.Metrics.prototype.clone = function() {
  var copy = new treesaver.dimensions.Metrics(),
      key;

  for (key in this) {
    if (copy[key] !== this[key]) {
      copy[key] = this[key];
    }
  }

  return copy;
};

// TODO: MergeSizeRange

if (goog.DEBUG) {
  treesaver.dimensions.Metrics.prototype.toString = function() {
    return '[Metrics: ' + this.w + 'x' + this.h + ']';
  };
}
<commit_msg>Fix bug in inSizeRange function, Closes #111.<commit_after>/**
 * @fileoverview Helpers for measuring elements.
 */

goog.provide('treesaver.dimensions');

goog.require('treesaver.capabilities');
goog.require('treesaver.constants');

/**
 * Alias for Size type
 *
 * @typedef {{ w: number, h: number }}
 */
treesaver.dimensions.Size;

/**
 * Alias for SizeRange type
 *
 * @typedef {{ w: number, h: number, maxW: number, maxH: number }}
 */
treesaver.dimensions.SizeRange;

/**
 * Regex to determine whether a value is a pixel value.
 * @private
 */
treesaver.dimensions.pixel = /^-?\d+(?:px)?$/i;

/**
 * Regex to determine whether a value contains a number.
 * @private
 */
treesaver.dimensions.number = /^-?\d/;

/**
 * Whether the given size fits within the bounds set by the range
 *
 * @param {treesaver.dimensions.SizeRange|treesaver.dimensions.Metrics} range
 * @param {treesaver.dimensions.Size} size
 * @return {boolean} True if both dimensions are within the min and max.
 */
treesaver.dimensions.inSizeRange = function(range, size) {
  if (!range) {
    return false;
  }

  // Use minW/minH for Metrics, w/h for a range
  // TODO: Make this consistent
  var w = 'minW' in range ? range.minW : range.w,
      h = 'minH' in range ? range.minH : range.h;

  return size.w >= w && size.h >= h &&
    size.w <= range.maxW && size.h <= range.maxH;
};

/**
 *
 * @param {treesaver.dimensions.SizeRange} a
 * @param {treesaver.dimensions.Metrics} b
 * @param {boolean} outer
 * @return {treesaver.dimensions.SizeRange}
 */
treesaver.dimensions.mergeSizeRange = function(a, b, outer) {
  a = a || {};
  b = b || {};

  var bpHeight = outer ? b.bpHeight || (b.outerH ? b.outerH - b.h : 0) : 0,
      bpWidth = outer ? b.bpWidth || (b.outerW ? b.outerW - b.w : 0) : 0;

  return {
    w: Math.max(a.w || 0, (b.w + bpWidth) || 0),
    h: Math.max(a.h || 0, (b.h + bpHeight) || 0),
    maxW: Math.min(a.maxW || Infinity, b.maxW + bpWidth || Infinity),
    maxH: Math.min(a.maxH || Infinity, b.maxH + bpHeight || Infinity)
  };
};

/**
 * Convert a string value to pixels
 *
 * @param {!Element} el
 * @param {?string} val
 * @return {?number} Value in pixels.
 */
treesaver.dimensions.toPixels = function(el, val) {
  if (val && treesaver.dimensions.pixel.test(val)) {
    return parseFloat(val);
  }

  return null;
};

/**
 * Return the computedStyle object, which varies based on
 * browsers
 * @param {?Element} el
 * @return {Object}
 */
treesaver.dimensions.getStyleObject = function(el) {
  return document.defaultView.getComputedStyle(el, null);
};

/**
 * Return the width and height element in a simple object
 *
 * @param {Element} el
 * @return {!treesaver.dimensions.Size}
 */
treesaver.dimensions.getSize = function(el) {
  return {
    w: treesaver.dimensions.getOffsetWidth(el),
    h: treesaver.dimensions.getOffsetHeight(el)
  };
};

/**
 * Return the offsetHeight of the element.
 *
 * @param {?Element} el
 * @return {!number} Value in pixels.
 */
treesaver.dimensions.getOffsetHeight = function(el) {
  return el && el.offsetHeight || 0;
};

/**
 * Return the offsetWidth of the element.
 *
 * @param {?Element} el
 * @return {!number} Value in pixels.
 */
treesaver.dimensions.getOffsetWidth = function(el) {
  return el && el.offsetWidth || 0;
};

/**
 * Return the offsetTop of the element.
 *
 * @param {?Element} el
 * @return {!number} Value in pixels.
 */
treesaver.dimensions.getOffsetTop = function(el) {
  return el && el.offsetTop || 0;
}

// IE doesn't support getComputedStyle
if (SUPPORT_IE &&
    !(document.defaultView && document.defaultView.getComputedStyle)) {
  // Patch to use MSIE API
  treesaver.dimensions.getStyleObject = function(el) {
    return el.currentStyle;
  };

  if (treesaver.capabilities.IS_LEGACY) {
    treesaver.dimensions.getOffsetTop = function(el) {
      if (el) {
        el.style.zoom = 1;
        return el.offsetTop;
      }
      return 0;
    };

    treesaver.dimensions.getOffsetHeight = function(el) {
      if (el) {
        el.style.zoom = 1;
        return el.offsetHeight;
      }
      return 0;
    };

    treesaver.dimensions.getOffsetWidth = function(el) {
      if (el) {
        el.style.zoom = 1;
        return el.offsetWidth;
      }
      return 0;
    };
  }

  // If we are dealing with IE and the value contains some sort
  // of number we try Dean Edward's hack:
  // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
  treesaver.dimensions.toPixels = function(el, val) {
    var style, runtimeStyle, value;

    if (val && treesaver.dimensions.pixel.test(val)) {
      return parseFloat(val);
    }
    else if (val && treesaver.dimensions.number.test(val)) {
      style = el.style.left;
      runtimeStyle = el.runtimeStyle.left;
      el.runtimeStyle.left = el.currentStyle.left;
      el.style.left = val || 0;
      value = el.style.pixelLeft;
      el.style.left = style;
      el.runtimeStyle.left = runtimeStyle;
      return value;
    }
    return null;
  };
}

/**
 * Helper for setting a CSS value in pixels
 *
 * @param {!Element} el
 * @param {!string} propName
 * @param {!number} val
 * @return {!number} The value supplied.
 */
treesaver.dimensions.setCssPx = function(el, propName, val) {
  el.style[propName] = val + 'px';

  return val;
};

/**
 * Helper for setting the offset on an element, using CSS transforms if
 * supported, absolute positioning if not
 *
 * @param {!Element} el
 * @param {!number} x
 * @param {!number} y
 */
treesaver.dimensions.setOffset;

/**
 * Helper for setting the x-offset on an element, using CSS transforms if
 * supported, absolute positioning if not
 *
 * @param {!Element} el
 * @param {!number} x
 */
treesaver.dimensions.setOffsetX;

if (treesaver.capabilities.SUPPORTS_CSSTRANSFORMS) {
  /**
   * Helper for setting the transform property on an element
   *
   * @param {!Element} el
   * @param {!string} val
   */
  treesaver.dimensions.setTransformProperty_ = function(el, val) {
    // TODO: Detect once
    if ('transformProperty' in el.style) {
      el.style['transformProperty'] = val;
    }
    else {
      el.style[treesaver.capabilities.domCSSPrefix + 'Transform'] = val;
    }
  };

  if (treesaver.capabilities.SUPPORTS_CSSTRANSFORMS3D) {
    treesaver.dimensions.setOffset = function(el, x, y) {
      treesaver.dimensions.setTransformProperty_(el,
        'translate3d(' + x + 'px,' + y + 'px,0)');
    };
  }
  else {
    treesaver.dimensions.setOffset = function(el, x, y) {
      treesaver.dimensions.setTransformProperty_(el,
        'translate(' + x + 'px,' + y + 'px)');
    };
  }

  // Take the easy way out of setting the x offset
  treesaver.dimensions.setOffsetX = function(el, x) {
    treesaver.dimensions.setOffset(el, x, 0);
  };
}
else {
  // Fall back to absolute positioning
  treesaver.dimensions.setOffset = function(el, x, y) {
    treesaver.dimensions.setCssPx(el, 'left', x);
    treesaver.dimensions.setCssPx(el, 'top', y);
  };

  // Take the easy way out of setting the x offset
  treesaver.dimensions.setOffsetX = function(el, x) {
    treesaver.dimensions.setCssPx(el, 'left', x);
  };
}

/**
 * Round up to the nearest multiple of the base number
 *
 * @param {!number} number
 * @param {!number} base
 * @return {number} A multiple of the base number.
 */
treesaver.dimensions.roundUp = function(number, base) {
  return Math.ceil(number) + base - (number % base);
};

/**
 * The style dimensions of an element including margin, border, and
 * padding as well as line height
 *
 * @constructor
 * @param {!Element=} el
 */
treesaver.dimensions.Metrics = function(el) {
  if (!el) {
    return;
  }

  var style = treesaver.dimensions.getStyleObject(el),
      oldPosition = el.style.position,
      oldStyleAttribute = el.getAttribute('style'),
      tmp;

  this.display = style.display;
  this.position = style.position;

  // Webkit gives incorrect right margins for non-absolutely
  // positioned elements
  //if (this.position !== 'absolute') {
    //el.style.position = 'absolute';
  //}
  // Disable this for now, as it can give incorrect formatting
  // for elements in the flow
  // Also: Getting computed style is kinda silly if we change the
  // styling -- may affect the measurements anyway

  // Force hasLayout on IE7 so we get accurate measurements.
  if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
    el.style.zoom = 1;
  }

  // Margin
  this.marginTop = treesaver.dimensions.toPixels(el, style.marginTop) || 0;
  this.marginBottom = treesaver.dimensions.toPixels(el, style.marginBottom) || 0;
  this.marginLeft = treesaver.dimensions.toPixels(el, style.marginLeft) || 0;
  this.marginRight = treesaver.dimensions.toPixels(el, style.marginRight) || 0;
  // Summed totals
  this.marginHeight = this.marginTop + this.marginBottom;
  this.marginWidth = this.marginLeft + this.marginRight;

  // Border
  this.borderTop = treesaver.dimensions.toPixels(el, style.borderTopWidth) || 0;
  this.borderBottom = treesaver.dimensions.toPixels(el, style.borderBottomWidth) || 0;
  this.borderLeft = treesaver.dimensions.toPixels(el, style.borderLeftWidth) || 0;
  this.borderRight = treesaver.dimensions.toPixels(el, style.borderRightWidth) || 0;

  // Padding
  this.paddingTop = treesaver.dimensions.toPixels(el, style.paddingTop) || 0;
  this.paddingBottom = treesaver.dimensions.toPixels(el, style.paddingBottom) || 0;
  this.paddingLeft = treesaver.dimensions.toPixels(el, style.paddingLeft) || 0;
  this.paddingRight = treesaver.dimensions.toPixels(el, style.paddingRight) || 0;

  // Summed totals for border & padding
  this.bpTop = this.borderTop + this.paddingTop;
  this.bpBottom = this.borderBottom + this.paddingBottom;
  this.bpHeight = this.bpTop + this.bpBottom;
  this.bpLeft = this.borderLeft + this.paddingLeft;
  this.bpRight = this.borderRight + this.paddingRight;
  this.bpWidth = this.bpLeft + this.bpRight;

  // Outer Width & Height
  this.outerW = treesaver.dimensions.getOffsetWidth(el);
  this.outerH = treesaver.dimensions.getOffsetHeight(el);

  // Inner Width & Height
  this.w = this.outerW - this.bpWidth;
  this.h = this.outerH - this.bpHeight;

  // Min & Max : Width & Height
  this.minW = treesaver.dimensions.toPixels(el, style.minWidth) || 0;
  this.minH = treesaver.dimensions.toPixels(el, style.minHeight) || 0;

  // Opera returns -1 for a max-width or max-height that is not set.
  tmp = treesaver.dimensions.toPixels(el, style.maxWidth);
  this.maxW = (!tmp || tmp === -1) ? Infinity : tmp;

  tmp = treesaver.dimensions.toPixels(el, style.maxHeight);
  this.maxH = (!tmp || tmp === -1) ? Infinity : tmp;

  // Line height
  this.lineHeight = treesaver.dimensions.toPixels(el, style.lineHeight) || null;

  // Restore the original position property on style
  //if (this.position !== 'absolute') {
    //el.style.position = oldPosition;
    //if (!el.getAttribute('style')) {
      //el.removeAttribute('style');
    //}
  //}

  // Not sure if resetting hasLayout is the right thing to do
  // here, as it might affect the measurements we just did.
  //if (SUPPORT_IE && treesaver.capabilities.IS_LEGACY) {
  //  el.style.zoom = 'normal';
  //}
};

/**
 * Make a copy of the object
 *
 * @return {!treesaver.dimensions.Metrics}
 */
treesaver.dimensions.Metrics.prototype.clone = function() {
  var copy = new treesaver.dimensions.Metrics(),
      key;

  for (key in this) {
    if (copy[key] !== this[key]) {
      copy[key] = this[key];
    }
  }

  return copy;
};

// TODO: MergeSizeRange

if (goog.DEBUG) {
  treesaver.dimensions.Metrics.prototype.toString = function() {
    return '[Metrics: ' + this.w + 'x' + this.h + ']';
  };
}
<|endoftext|>


<commit_before>'use strict';

var angular = require('angular'),
  isNumber = require('lodash/lang/isNumber'),
//isUndefined = require('lodash/lang/isUndefined'),
//pull = require('lodash/array/pull'),
  difference = require('lodash/array/difference'),
  includes = require('lodash/collection/includes'),
  mixtubePlayback = require('mixtube-playback');

function orchestratorFactory($rootScope, $timeout, QueueManager, NotificationCentersRegistry, ScenesRegistry,
                             Configuration, LoggerFactory) {

  var _logger = LoggerFactory('Orchestrator'),
    _playback,
    _playbackState,
    _playingEntry,
    _loadingEntry;

  activate();

  function activate() {

    ScenesRegistry('scene').ready(function(scene) {

      var transitionDurationInMillis = Configuration.fadeDuration * 1000;

      var playbackConfig = {
        elementProducer: function() {
          return scene.newHostElement()[0];
        },

        videoProducer: function(entry) {
          return entry.video;
        },

        nextEntryProducer: function(entry) {
          return QueueManager.closestValidEntryByIndex(QueueManager.queue.entries.indexOf(entry) + 1);
        },

        transitionDuration: transitionDurationInMillis,

        stateChanged: function(prevState, state) {
          $rootScope.$evalAsync(function() {
            _playbackState = state;

            // when the playbacks stops we need to tell that the last playing video is not playing anymore
            if (_playbackState === mixtubePlayback.States.stopped) {
              _playingEntry = null;
            }
          });
        },

        playingChanged: function(entry) {
          $rootScope.$evalAsync(function() {
            _playingEntry = entry;
          });
        },

        loadingChanged: function(entry, loading) {
          $rootScope.$evalAsync(function() {
            if (loading) {
              _loadingEntry = entry;
            } else if (_loadingEntry === entry) {
              _loadingEntry = null;
            }
          });
        },

        comingNext: function(currentEntry, nextEntry) {
          // notify only if there was a current entry
          // otherwise it means we just started to play and we don't need to show anything in this case
          if (currentEntry) {
            NotificationCentersRegistry('notificationCenter').ready(
              function(notificationCenter) {
                var closeComingNextFn = notificationCenter.comingNext({
                  current: currentEntry.video.title,
                  next: nextEntry ? nextEntry.video.title : null,
                  imageUrl: nextEntry ? nextEntry.video.thumbnailUrl : null
                });

                $timeout(closeComingNextFn, 2 * transitionDurationInMillis);
              });
          }
        },

        loadFailed: function(entry, error) {
          $rootScope.$evalAsync(function() {
            entry.skippedAtRuntime = true;
            _logger.warn('Error while loading a entry');
            _logger.warn(error.stack);
          });
        }
      };

      if (isNumber(Configuration.mediaDuration)) {
        playbackConfig.debug = {
          mediaDuration: Configuration.mediaDuration,
          mediaQuality: 'low'
        };
      }

      _playback = mixtubePlayback(playbackConfig);
    });

    $rootScope.$watchCollection(function() {
      return QueueManager.queue.entries;
    }, function entriesWatcherChangeHandler(/**Array*/ newEntries, /**Array*/ oldEntries) {
      if (!angular.equals(newEntries, oldEntries)) {
        var removedEntries = difference(oldEntries, newEntries);
        if (includes(removedEntries, _playingEntry)) {
          skipTo(oldEntries.indexOf(_playingEntry));
        } else if (includes(removedEntries, _loadingEntry)) {
          skipTo(oldEntries.indexOf(_loadingEntry));
        } else {
          // we can get here if some entry has been deleted but was not playing or loading
          // or if an entry has been added
          _playback.checkNextEntry();
        }
      }
    });

    /**
     * The watcher bellow observes the queue entries to check if a modification the queue and impacts the playback if required.
     *
     * Two main cases:
     *  - the currently playing entry has been removed -> move to the next valid one
     *  - something has changed between the currently playing entry and the auto prepared one -> launch a new cycle to pick and prepare
     */
    //  $rootScope.$watchCollection(function() {
    //    return QueueManager.queue.entries;
    //  }, function entriesWatcherChangeHandler(/**Array*/ newEntries, /**Array*/ oldEntries) {
    //    if (!angular.equals(newEntries, oldEntries)) {
    //      var startedEntry = _startedSlot && _startedSlot.actualQueueEntry;
    //      // the concept of activated entry is only relevant here
    //      // we want to consider the currently started entry or the one about to be the next started entry mainly
    //      // to properly manage moved to entry removal while still preparing
    //      var activatedEntry = _movePreparedSlot && (_movePreparedSlot.actualQueueEntry || _movePreparedSlot.tryingQueueEntry)
    //        || startedEntry;
    //      var removedEntries = difference(oldEntries, newEntries);
    //      if (includes(removedEntries, activatedEntry)) {
    //        // the active entry has just been removed
    //        // move to the entry which is now at the same position in the queue
    //        moveTo(oldEntries.indexOf(activatedEntry));
    //      } else if (startedEntry) {
    //        // nothing changed for the active entry
    //        // we need the check if the change impacted the auto prepared entry
    //        var prepareAutoRequired = false;
    //        var startedEntryOldIndex = oldEntries.indexOf(startedEntry);
    //        var startedEntryNewIndex = newEntries.indexOf(startedEntry);
    //        var autoPreparedEntry = _autoPreparedSlot && (_autoPreparedSlot.actualQueueEntry || _autoPreparedSlot.tryingQueueEntry);
    //        var autoPreparedEntryNewIndex = newEntries.indexOf(autoPreparedEntry);
    //        if (autoPreparedEntryNewIndex === -1) {
    //          // the auto prepared entry has just been removed so we know straight that we have to re-prepare
    //          prepareAutoRequired = true;
    //        } else {
    //          // we have to compare slices of old and new entries from the entry following the started one
    //          // to the auto prepared one the check if something changes in between
    //          var autoPreparedEntryOldIndex = oldEntries.indexOf(autoPreparedEntry);
    //          var sliceOfOldEntries = oldEntries.slice(startedEntryOldIndex + 1, autoPreparedEntryOldIndex);
    //          var sliceOfNewEntries = newEntries.slice(startedEntryNewIndex + 1, autoPreparedEntryNewIndex);
    //          prepareAutoRequired = !angular.equals(sliceOfOldEntries, sliceOfNewEntries);
    //        }
    //        if (prepareAutoRequired) {
    //          // something changed so just in case we re (auto)prepare the entry
    //          prepareAuto(startedEntryNewIndex + 1);
    //          _logger.debug('something changed in the queue that required a re-preparation of the previously ' +
    //          'auto prepared entry %O', autoPreparedEntry);
    //        }
    //      }
    //    }
    //  });
  }

  function skipTo(queueIndex) {
    var entry = QueueManager.closestValidEntryByIndex(queueIndex);
    if (!entry) {
      _playback.stop();
    } else {
      _playback.skip(entry);

      if (_playbackState !== mixtubePlayback.States.playing) {
        _playback.play();
      }
    }
  }

  function togglePlayback() {
    if (_playbackState === mixtubePlayback.States.playing) {
      _playback.pause();
    } else if (_playbackState === mixtubePlayback.States.paused) {
      _playback.play();
    } else if (QueueManager.queue.entries.length) {
      // stopped or pristine
      _playback.skip(QueueManager.queue.entries[0]);
      _playback.play();
    }
  }

  /**
   * @name Orchestrator
   */
  var Orchestrator = {

    /**
     * The currently playing queue entry.
     *
     * @returns {?mt.QueueEntry}
     */
    get runningQueueEntry() {
      return _playingEntry;
    },

    /**
     * The currently loading queue entry.
     *
     * This value is not null only if the loading has been initiated by a external action ie. not by
     * the orchestrator itself.
     *
     * @returns {?mt.QueueEntry}
     */
    get loadingQueueEntry() {
      return _loadingEntry;
    },

    /**
     * @returns {boolean}
     */
    get playing() {
      return _playbackState === mixtubePlayback.States.playing;
    },

    /**
     * @param {number} queueIndex
     */
    skipTo: skipTo,

    togglePlayback: togglePlayback
  };

  return Orchestrator;
}

module.exports = orchestratorFactory;<commit_msg>removed commented out code (old deletion / addition handling code)<commit_after>'use strict';

var angular = require('angular'),
  isNumber = require('lodash/lang/isNumber'),
  difference = require('lodash/array/difference'),
  includes = require('lodash/collection/includes'),
  mixtubePlayback = require('mixtube-playback');

function orchestratorFactory($rootScope, $timeout, QueueManager, NotificationCentersRegistry, ScenesRegistry,
                             Configuration, LoggerFactory) {

  var _logger = LoggerFactory('Orchestrator'),
    _playback,
    _playbackState,
    _playingEntry,
    _loadingEntry;

  activate();

  function activate() {

    ScenesRegistry('scene').ready(function(scene) {

      var transitionDurationInMillis = Configuration.fadeDuration * 1000;

      var playbackConfig = {
        elementProducer: function() {
          return scene.newHostElement()[0];
        },

        videoProducer: function(entry) {
          return entry.video;
        },

        nextEntryProducer: function(entry) {
          return QueueManager.closestValidEntryByIndex(QueueManager.queue.entries.indexOf(entry) + 1);
        },

        transitionDuration: transitionDurationInMillis,

        stateChanged: function(prevState, state) {
          $rootScope.$evalAsync(function() {
            _playbackState = state;

            // when the playbacks stops we need to tell that the last playing video is not playing anymore
            if (_playbackState === mixtubePlayback.States.stopped) {
              _playingEntry = null;
            }
          });
        },

        playingChanged: function(entry) {
          $rootScope.$evalAsync(function() {
            _playingEntry = entry;
          });
        },

        loadingChanged: function(entry, loading) {
          $rootScope.$evalAsync(function() {
            if (loading) {
              _loadingEntry = entry;
            } else if (_loadingEntry === entry) {
              _loadingEntry = null;
            }
          });
        },

        comingNext: function(currentEntry, nextEntry) {
          // notify only if there was a current entry
          // otherwise it means we just started to play and we don't need to show anything in this case
          if (currentEntry) {
            NotificationCentersRegistry('notificationCenter').ready(
              function(notificationCenter) {
                var closeComingNextFn = notificationCenter.comingNext({
                  current: currentEntry.video.title,
                  next: nextEntry ? nextEntry.video.title : null,
                  imageUrl: nextEntry ? nextEntry.video.thumbnailUrl : null
                });

                $timeout(closeComingNextFn, 2 * transitionDurationInMillis);
              });
          }
        },

        loadFailed: function(entry, error) {
          $rootScope.$evalAsync(function() {
            entry.skippedAtRuntime = true;
            _logger.warn('Error while loading a entry');
            _logger.warn(error.stack);
          });
        }
      };

      if (isNumber(Configuration.mediaDuration)) {
        playbackConfig.debug = {
          mediaDuration: Configuration.mediaDuration,
          mediaQuality: 'low'
        };
      }

      _playback = mixtubePlayback(playbackConfig);
    });

    $rootScope.$watchCollection(function() {
      return QueueManager.queue.entries;
    }, function entriesWatcherChangeHandler(/**Array*/ newEntries, /**Array*/ oldEntries) {
      if (!angular.equals(newEntries, oldEntries)) {
        var removedEntries = difference(oldEntries, newEntries);
        if (includes(removedEntries, _playingEntry)) {
          skipTo(oldEntries.indexOf(_playingEntry));
        } else if (includes(removedEntries, _loadingEntry)) {
          skipTo(oldEntries.indexOf(_loadingEntry));
        } else {
          // we can get here if some entry has been deleted but was not playing or loading
          // or if an entry has been added
          _playback.checkNextEntry();
        }
      }
    });
  }

  function skipTo(queueIndex) {
    var entry = QueueManager.closestValidEntryByIndex(queueIndex);
    if (!entry) {
      _playback.stop();
    } else {
      _playback.skip(entry);

      if (_playbackState !== mixtubePlayback.States.playing) {
        _playback.play();
      }
    }
  }

  function togglePlayback() {
    if (_playbackState === mixtubePlayback.States.playing) {
      _playback.pause();
    } else if (_playbackState === mixtubePlayback.States.paused) {
      _playback.play();
    } else if (QueueManager.queue.entries.length) {
      // stopped or pristine
      _playback.skip(QueueManager.queue.entries[0]);
      _playback.play();
    }
  }

  /**
   * @name Orchestrator
   */
  var Orchestrator = {

    /**
     * The currently playing queue entry.
     *
     * @returns {?mt.QueueEntry}
     */
    get runningQueueEntry() {
      return _playingEntry;
    },

    /**
     * The currently loading queue entry.
     *
     * This value is not null only if the loading has been initiated by a external action ie. not by
     * the orchestrator itself.
     *
     * @returns {?mt.QueueEntry}
     */
    get loadingQueueEntry() {
      return _loadingEntry;
    },

    /**
     * @returns {boolean}
     */
    get playing() {
      return _playbackState === mixtubePlayback.States.playing;
    },

    /**
     * @param {number} queueIndex
     */
    skipTo: skipTo,

    togglePlayback: togglePlayback
  };

  return Orchestrator;
}

module.exports = orchestratorFactory;<|endoftext|>


<commit_before>/*
 Copyright 2010 WebDriver committers
 Copyright 2010 Google Inc.

 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.
 */

/**
 * @fileoverview Functions that form common utility methods in the firefox
 * driver.
 */

// Large parts of this file are derived from Mozilla's MozMill tool.

goog.provide('webdriver.firefox.utils');

goog.require('bot.Error');
goog.require('bot.ErrorCode');

var CC = Components.classes;
var CI = Components.interfaces;

webdriver.firefox.utils.windowMediator = function() {
  var clazz = CC['@mozilla.org/appshell/window-mediator;1'];
  if (!clazz) {
    throw new bot.Error(bot.ErrorCode.UNKNOWN_COMMAND);
  }

  return clazz.getService(CI['nsIWindowMediator']);
};

webdriver.firefox.utils.getChromeWindow = function(win) {
  return win
      .QueryInterface(CI.nsIInterfaceRequestor)
      .getInterface(CI.nsIWebNavigation)
      .QueryInterface(CI.nsIDocShellTreeItem)
      .rootTreeItem
      .QueryInterface(CI.nsIInterfaceRequestor)
      .getInterface(CI.nsIDOMWindow)
      .QueryInterface(CI.nsIDOMChromeWindow);
};

// TODO(simon): Roll this out throughout the codebase before Firefox 4 is released.
// This method closely follows unwrapNode() from mozmill-tests
/**
 * Unwraps a something which is wrapped into a XPCNativeWrapper or XrayWrapper.
 *
 * @param {!Object} thing The "something" to unwrap.
 * @returns {!Object} The object, unwrapped if possible.
 */
webdriver.firefox.utils.unwrap = function(thing) {
  if (!goog.isDefAndNotNull(thing)) {
    return thing;
  }

  // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596
  if (XPCNativeWrapper && "unwrap" in XPCNativeWrapper) {
    return XPCNativeWrapper.unwrap(thing);
  }

  if (thing['wrappedJSObject']) {
    return thing.wrappedJSObject;
  }

  return thing;
};
<commit_msg>EranMes: Fix for Firefox 4. XPCNativeWrapper throws if it's passed a literal - catch nhis exception and move on to returning the value itself.<commit_after>/*
 Copyright 2010 WebDriver committers
 Copyright 2010 Google Inc.

 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.
 */

/**
 * @fileoverview Functions that form common utility methods in the firefox
 * driver.
 */

// Large parts of this file are derived from Mozilla's MozMill tool.

goog.provide('webdriver.firefox.utils');

goog.require('bot.Error');
goog.require('bot.ErrorCode');

var CC = Components.classes;
var CI = Components.interfaces;

webdriver.firefox.utils.windowMediator = function() {
  var clazz = CC['@mozilla.org/appshell/window-mediator;1'];
  if (!clazz) {
    throw new bot.Error(bot.ErrorCode.UNKNOWN_COMMAND);
  }

  return clazz.getService(CI['nsIWindowMediator']);
};

webdriver.firefox.utils.getChromeWindow = function(win) {
  return win
      .QueryInterface(CI.nsIInterfaceRequestor)
      .getInterface(CI.nsIWebNavigation)
      .QueryInterface(CI.nsIDocShellTreeItem)
      .rootTreeItem
      .QueryInterface(CI.nsIInterfaceRequestor)
      .getInterface(CI.nsIDOMWindow)
      .QueryInterface(CI.nsIDOMChromeWindow);
};

// TODO(simon): Roll this out throughout the codebase before Firefox 4 is released.
// This method closely follows unwrapNode() from mozmill-tests
/**
 * Unwraps a something which is wrapped into a XPCNativeWrapper or XrayWrapper.
 *
 * @param {!Object} thing The "something" to unwrap.
 * @returns {!Object} The object, unwrapped if possible.
 */
webdriver.firefox.utils.unwrap = function(thing) {
  if (!goog.isDefAndNotNull(thing)) {
    return thing;
  }

  // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596

  if (XPCNativeWrapper && "unwrap" in XPCNativeWrapper) {
    try {
      return XPCNativeWrapper.unwrap(thing);
    } catch(e) {
      //Unwrapping will fail for JS literals - numbers, for example. Catch
      // the exception and proceed, it will eventually be returend as-is.
      Logger.dumpn("Unwrap failed: " + e);
    }

  }

  if (thing['wrappedJSObject']) {
    return thing.wrappedJSObject;
  }

  return thing;
};
<|endoftext|>


<commit_before>// @flow

import { table } from 'table';
import R from 'ramda';

import gql from '../gql';
import { runGQLQuery } from '../query';
import { printErrors, printGraphQLErrors } from '../printErrors';

import typeof Yargs from 'yargs';
import type { BaseArgs } from '.';

const tableConfig = {
  columns: {
    // $FlowIssue: Flow doesn't understand numbers as keys https://github.com/facebook/flow/issues/380
    0: {
      alignment: 'left',
      minWidth: 15,
    },
    // $FlowIssue: Flow doesn't understand numbers as keys https://github.com/facebook/flow/issues/380
    1: {
      alignment: 'left',
      minWidth: 15,
    },
  },
};

// Common filter
const onlyValues = ([, value]: [string, string]) =>
  value != null && value !== '';

const name = 'project';
const args = ['<name>'];
const fullName = `${name} ${args.join(' ')}`;
const description = 'Show project details';

export function setup(yargs: Yargs): Yargs {
  return yargs
    .usage(`$0 ${fullName} - ${description}`)
    .positional('name', {
      describe: 'Name of project to display details',
      type: 'string',
    })
    .example(`$0 ${name} myproject`, 'Show details of project "myproject"');
}

type projectDetailsArgs = {
  projectName: string,
  clog: typeof console.log,
  cerr: typeof console.error,
};

export async function projectDetails({
  projectName,
  clog,
  cerr,
}:
projectDetailsArgs): Promise<number> {
  const query = gql`
    query queryProject($project: String!) {
      projectByName(name: $project) {
        name
        customer {
          name
        }
        git_url
        active_systems_deploy
        active_systems_remove
        branches
        pullrequests
        openshift {
          name
        }
        created
      }
    }
  `;

  const result = await runGQLQuery({
    cerr,
    query,
    variables: { project: projectName },
  });

  const { errors } = result;
  if (errors != null) {
    return printGraphQLErrors(cerr, ...errors);
  }

  const project = R.path(['data', 'projectByName'])(result);

  if (project == null) {
    return printErrors(clog, `No project '${projectName}' found`);
  }

  const tableBody = [
    ['Project Name', R.prop('name', project)],
    ['Customer', R.path(['customer', 'name'], project)],
    ['Git URL', R.prop('git_url', project)],
    ['Active Systems Deploy', R.prop('active_systems_deploy', project)],
    ['Active Systems Remove', R.prop('active_systems_remove', project)],
    ['Branches', String(R.prop('branches', project))],
    ['Pull Requests', String(R.prop('pullrequests', project))],
    ['Openshift', R.path(['openshift', 'name'], project)],
    ['Created', R.path(['created'], project)],
  ];

  const tableData = R.filter(onlyValues)(tableBody);

  clog(`Project details for '${projectName}':`);
  clog(table(tableData, tableConfig));

  return 0;
}

type Args = BaseArgs & {
  name: ?string,
};

export async function run({
  clog,
  cerr,
  name: projectName,
}:
Args): Promise<number> {
  return projectDetails({ projectName, clog, cerr });
}

export default {
  setup,
  name: fullName,
  description,
  run,
};
<commit_msg>Read project configuration from yaml config file<commit_after>// @flow

import { table } from 'table';
import R from 'ramda';

import gql from '../gql';
import { runGQLQuery } from '../query';
import {
  printErrors,
  printGraphQLErrors,
  printNoConfigError,
  printProjectConfigurationError,
} from '../printErrors';

import typeof Yargs from 'yargs';
import type { BaseArgs } from '.';

const tableConfig = {
  columns: {
    // $FlowIssue: Flow doesn't understand numbers as keys https://github.com/facebook/flow/issues/380
    0: {
      alignment: 'left',
      minWidth: 15,
    },
    // $FlowIssue: Flow doesn't understand numbers as keys https://github.com/facebook/flow/issues/380
    1: {
      alignment: 'left',
      minWidth: 15,
    },
  },
};

// Common filter
const onlyValues = ([, value]: [string, string]) =>
  value != null && value !== '';

const name = 'project';
const description = 'Show project details';

export function setup(yargs: Yargs): Yargs {
  return yargs
    .usage(`$0 ${name} - ${description}`)
    .options({
      project: {
        demandOption: false,
        describe: 'Name of project to display details',
        type: 'string',
      },
    })
    .alias('p', 'project')
    .example(
      `$0 ${name}`,
      'Show details for the project configured in .lagoon.yml',
    )
    .example(`$0 ${name} myproject`, 'Show details of project "myproject"');
}

type projectDetailsArgs = {
  projectName: string,
  clog: typeof console.log,
  cerr: typeof console.error,
};

export async function projectDetails({
  projectName,
  clog,
  cerr,
}:
projectDetailsArgs): Promise<number> {
  const query = gql`
    query queryProject($project: String!) {
      projectByName(name: $project) {
        name
        customer {
          name
        }
        git_url
        active_systems_deploy
        active_systems_remove
        branches
        pullrequests
        openshift {
          name
        }
        created
      }
    }
  `;

  const result = await runGQLQuery({
    cerr,
    query,
    variables: { project: projectName },
  });

  const { errors } = result;
  if (errors != null) {
    return printGraphQLErrors(cerr, ...errors);
  }

  const project = R.path(['data', 'projectByName'])(result);

  if (project == null) {
    return printErrors(clog, `No project '${projectName}' found`);
  }

  const tableBody = [
    ['Project Name', R.prop('name', project)],
    ['Customer', R.path(['customer', 'name'], project)],
    ['Git URL', R.prop('git_url', project)],
    ['Active Systems Deploy', R.prop('active_systems_deploy', project)],
    ['Active Systems Remove', R.prop('active_systems_remove', project)],
    ['Branches', String(R.prop('branches', project))],
    ['Pull Requests', String(R.prop('pullrequests', project))],
    ['Openshift', R.path(['openshift', 'name'], project)],
    ['Created', R.path(['created'], project)],
  ];

  const tableData = R.filter(onlyValues)(tableBody);

  clog(`Project details for '${projectName}':`);
  clog(table(tableData, tableConfig));

  return 0;
}

type Args = BaseArgs & {
  name: ?string,
};

export async function run({
  clog,
  cerr,
  config,
  project: projectOption,
}:
Args): Promise<number> {
  if (config == null) {
    return printNoConfigError(cerr);
  }

  const projectName = projectOption || config.project;

  if (projectName == null) {
    return printProjectConfigurationError(cerr);
  }

  return projectDetails({ projectName, clog, cerr });
}

export default {
  setup,
  name,
  description,
  run,
};
<|endoftext|>


<commit_before>var React = require('react')
var fetchJson = require('./utilities/fetch-json.js')
var SqlpadTauChart = require('./components/SqlpadTauChart.js')
var DropdownButton = require('react-bootstrap/lib/DropdownButton')
var MenuItem = require('react-bootstrap/lib/MenuItem')
import IncompleteDataNotification from './components/IncompleteDataNotification'

var QueryEditor = React.createClass({
  getInitialState: function () {
    return {
      isRunning: false,
      runQueryStartTime: undefined,
      queryResult: undefined
    }
  },
  runQuery: function (queryId) {
    this.setState({
      isRunning: true,
      runQueryStartTime: new Date()
    })
    fetchJson('GET', this.props.config.baseUrl + '/api/queries/' + queryId)
      .then((json) => {
        if (json.error) console.error(json.error)
        this.setState({
          query: json.query
        })
      })
      .then(() => {
        return fetchJson('GET', this.props.config.baseUrl + '/api/query-result/' + queryId)
      })
      .then((json) => {
        if (json.error) console.error(json.error)
        this.setState({
          isRunning: false,
          queryError: json.error,
          queryResult: json.queryResult
        })
      })
      .catch((ex) => {
        console.error(ex.toString())
        this.setState({
          isRunning: false
        })
      })
  },
  componentDidMount: function () {
    this.runQuery(this.props.queryId)
  },
  onSaveImageClick: function (e) {
    if (this.sqlpadTauChart && this.sqlpadTauChart.chart) {
      this.sqlpadTauChart.chart.fire('exportTo', 'png')
    }
  },
  render: function () {
    var csvDownloadLink
    var xlsxDownloadLink
    if (this.state.queryResult) {
      csvDownloadLink = this.props.config.baseUrl + '/download-results/' + this.state.queryResult.cacheKey + '.csv'
      xlsxDownloadLink = this.props.config.baseUrl + '/download-results/' + this.state.queryResult.cacheKey + '.xlsx'
    }
    return (

{(this.state.query ? this.state.query.name : '')}

          <IncompleteDataNotification queryResult={this.state.queryResult} />
          {(this.state.queryResult ? (
            <DropdownButton title='Export' id='export-dropdown-button' pullRight>
              <MenuItem eventKey='1' onClick={this.onSaveImageClick}>png</MenuItem>
              {(this.props.config.allowCsvDownload ? (<MenuItem eventKey='2' target='_blank' href={csvDownloadLink}>csv</MenuItem>) : null)}
              {(this.props.config.allowCsvDownload ? (<MenuItem eventKey='3' target='_blank' href={xlsxDownloadLink}>xlsx</MenuItem>) : null)}
            </DropdownButton>
          ) : null)}

          <SqlpadTauChart
            query={this.state.query}
            config={this.props.config}
            queryResult={this.state.queryResult}
            queryError={this.state.queryError}
            isRunning={this.state.isRunning}
            ref={(ref) => {
              this.sqlpadTauChart = ref
            }} />

    )
  }
})

module.exports = QueryEditor
<commit_msg>fix chart-only view<commit_after>var React = require('react')
var fetchJson = require('./utilities/fetch-json.js')
var SqlpadTauChart = require('./components/SqlpadTauChart.js')
var DropdownButton = require('react-bootstrap/lib/DropdownButton')
var MenuItem = require('react-bootstrap/lib/MenuItem')
import IncompleteDataNotification from './components/IncompleteDataNotification'

var QueryEditor = React.createClass({
  getInitialState: function () {
    return {
      isRunning: false,
      runQueryStartTime: undefined,
      queryResult: undefined
    }
  },
  runQuery: function (queryId) {
    this.setState({
      isRunning: true,
      runQueryStartTime: new Date()
    })
    fetchJson('GET', this.props.config.baseUrl + '/api/queries/' + queryId)
      .then((json) => {
        if (json.error) console.error(json.error)
        this.setState({
          query: json.query
        })
      })
      .then(() => {
        return fetchJson('GET', this.props.config.baseUrl + '/api/query-result/' + queryId)
      })
      .then((json) => {
        if (json.error) console.error(json.error)
        this.setState({
          isRunning: false,
          queryError: json.error,
          queryResult: json.queryResult
        })
      })
      .catch((ex) => {
        console.error(ex.toString())
        this.setState({
          isRunning: false
        })
      })
  },
  componentDidMount: function () {
    this.runQuery(this.props.queryId)
  },
  onSaveImageClick: function (e) {
    if (this.sqlpadTauChart && this.sqlpadTauChart.chart) {
      this.sqlpadTauChart.chart.fire('exportTo', 'png')
    }
  },
  hasRows: function () {
    var queryResult = this.state.queryResult
    return !!(queryResult && queryResult.rows && queryResult.rows.length)
  },
  isChartable: function () {
    var pending = this.state.isRunning || this.state.queryError
    return !pending && this.hasRows()
  },
  render: function () {
    var csvDownloadLink
    var xlsxDownloadLink
    if (this.state.queryResult) {
      csvDownloadLink = this.props.config.baseUrl + '/download-results/' + this.state.queryResult.cacheKey + '.csv'
      xlsxDownloadLink = this.props.config.baseUrl + '/download-results/' + this.state.queryResult.cacheKey + '.xlsx'
    }
    return (

{(this.state.query ? this.state.query.name : '')}

          <IncompleteDataNotification queryResult={this.state.queryResult} />
          {(this.state.queryResult ? (
            <DropdownButton title='Export' id='export-dropdown-button' pullRight>
              <MenuItem eventKey='1' onClick={this.onSaveImageClick}>png</MenuItem>
              {(this.props.config.allowCsvDownload ? (<MenuItem eventKey='2' target='_blank' href={csvDownloadLink}>csv</MenuItem>) : null)}
              {(this.props.config.allowCsvDownload ? (<MenuItem eventKey='3' target='_blank' href={xlsxDownloadLink}>xlsx</MenuItem>) : null)}
            </DropdownButton>
          ) : null)}

          <SqlpadTauChart
            query={this.state.query}
            config={this.props.config}
            queryResult={this.state.queryResult}
            queryError={this.state.queryError}
            isRunning={this.state.isRunning}
            renderChart={this.isChartable()}
            ref={(ref) => {
              this.sqlpadTauChart = ref
            }} />

    )
  }
})

module.exports = QueryEditor
<|endoftext|>


<commit_before>import React from 'react'
import {compose, withState} from 'recompose'

import {clickable} from '../style.css'
import {Audio, View, Text, TextInput} from '../components'

let Player = compose(
  withState('volume', 'setVolume', 0.1)
)(({volume, setVolume, time, URL}) => (
  <View>
    <Audio volume={volume} src={`${URL}${time}.mp3`} autoPlay />

    <TextInput
      style={{width: 30}}
      onTextChange={text => {
        let num = parseInt(text)
        if (!Number.isNaN(num)) {
          setVolume(num / 100)
        } else {
          setVolume(0)
        }
      }}
      value={Math.ceil(volume * 100)}
    />%

    <View>
      <View className={clickable} onClick={() => setVolume(Math.min(volume + 0.1, 1))}>
        HARDER { volume >= 1 && '(Nog harder heeft geen zin srry)'}
      </View>
      <View className={clickable} onClick={() => setVolume(Math.max(volume - 0.1, 0))}>
        ZACHTER { volume <= 0 && '(Hij is al op z\'n zachts)'}
      </View>
    </View>
  </View>
))

export default Player
<commit_msg>SOUNDS FIX BEST EVAHH<commit_after>import React from 'react'
import {compose, withState} from 'recompose'

import {clickable} from '../style.css'
import {Audio, View, Text, TextInput} from '../components'

let Player = compose(
  withState('volume', 'setVolume', '10')
)(({volume, setVolume, time, URL}) => {
  let volumeFix = volume.replace(/,/g, '.')

  let numberVolume =
    Number.isNaN(Number(volumeFix))
    ? 0 : Number(volumeFix) / 100

  let setVolumeX = num => setVolume(String(num))

  return (
    <View>
      <Audio
        volume={numberVolume}
        src={`${URL}${time}.mp3`}
        autoPlay
      />

      <TextInput
        style={{width: 30}}
        onTextChange={setVolume}
        value={volume}
      />%

      <View>
        <View className={clickable} onClick={() => setVolumeX(Math.min(numberVolume*100 + 10, 100))}>
          HARDER { numberVolume >= 1 && '(Nog harder heeft geen zin srry)'}
        </View>
        <View className={clickable} onClick={() => setVolumeX(Math.max(numberVolume*100 - 10, 0))}>
          ZACHTER { numberVolume <= 0 && '(Hij is al op z\'n zachts)'}
        </View>
      </View>
    </View>
  )
})

export default Player
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import TextArea from './textArea';
import Tile from './tile';
import Word from './word';
import update from 'immutability-helper';

function isLetter(char) {
  return char.match(/^[A-Za-z]+$/);
}

export default class Puzzle extends Component {
  constructor(props) {
    super(props);
    this.handleQuoteChange = this.handleQuoteChange.bind(this);
    this.handleAuthorChange = this.handleAuthorChange.bind(this);
    this.validatePuzzle = this.validatePuzzle.bind(this);
    this.continueToNextStep = this.continueToNextStep.bind(this);
    this.handleStepChange = this.handleStepChange.bind(this);
    this.createGrid = this.createGrid.bind(this);
    this.createWordRows = this.createWordRows.bind(this);
    this.handleWordInput = this.handleWordInput.bind(this);
    this.handleLetterInput = this.handleLetterInput.bind(this);
    this.handleLetterRemoval = this.handleLetterRemoval.bind(this);
    this.state = {
      isValid: false,
      quote:'',
      indexedQuoteLetters: [],
      indexedWords: {},
      authorLetters: [],
      quoteLetterTracker: {},
      gridComponent: null,
      wordsComponent:null,
      currentStep: 1,
    };
  }

  handleQuoteChange(string) {
    var library = {};
    string.split('').forEach(function(char){
      if (isLetter(char)) {
        if (library[char.toLowerCase()]) {
          library[char.toLowerCase()] += 1;
        } else {
          library[char.toLowerCase()] = 1;
        }
      }
    });
    this.setState({
      quote: string,
      quoteLetterTracker: library
    },function(){
      this.validatePuzzle();
    });
  }
  handleAuthorChange(string) {
    var array = [];
    if (string) {
      string.split('').forEach(function(char){
        if (isLetter(char)) {
          array.push(char.toLowerCase());
        }
      });
    }
    this.setState({authorLetters: array},function(){
      this.validatePuzzle();
    });
  }
  validatePuzzle() {
    var quoteLetterTrackerClone = Object.assign({}, this.state.quoteLetterTracker);
    var puzzleValidity = true;
    if (this.state.authorLetters) {
      this.state.authorLetters.forEach(function(char){
        if (!quoteLetterTrackerClone[char] || quoteLetterTrackerClone[char] == 0) {
          puzzleValidity = false;
        }
        if (quoteLetterTrackerClone[char]) {
          quoteLetterTrackerClone[char] -= 1;
        }
      });
    }
    this.setState({isValid:puzzleValidity});
  }

  continueToNextStep(e) {
    // $('.step-1').hide();
    e.preventDefault();
    var step = this.state.currentStep + 1;
    this.setState({currentStep: step}, function(){
      this.handleStepChange(step);
    });
  }

  handleStepChange(step) {
    if (step == 2) {
      this.createGrid();
      this.createWordRows();
    }
  }

  handleWordInput(text,wordId) {
    // get old state of the word
    const Puzzle = this;
    var oldWord = Puzzle.state.indexedWords[wordId];
    var newWord = oldWord[0] + text;
    // update that word in Component state
    Puzzle.state.indexedWords[wordId] = newWord;
    Puzzle.forceUpdate();
    // compare oldWord and newWord to figure out if the event is addition or deletion
    if (newWord.length > oldWord.length) {
      var newChar = newWord.replace(oldWord,'');
      this.handleLetterInput(newChar,wordId);
    } else {
      var deletedChar = oldWord.replace(newWord,'');
      this.handleLetterRemoval(deletedChar,wordId);
    }
  }

  handleLetterInput(char,wordId) {
    for (i in this.state.indexedQuoteLetters) {
      var obj = this.state.indexedQuoteLetters[i];
      if (obj.letter == char && !obj.wordId) {
        this.state.indexedQuoteLetters[i].wordId = wordId;
        break
      }
    }
    var grid = this.state.indexedQuoteLetters.map(function(obj,i){
        return (<Tile key={i} letter={obj.letter} index={obj.index} wordId={obj.wordId} />);
    })
    this.setState({gridComponent:grid},function(){
      console.log(this.state.indexedQuoteLetters)
    });
  }

  handleLetterRemoval(char) {
    console.log('do something with '+char)
    // console.log(this.state.quoteLetterTracker)
  }

  createWordRows() {
    const Alphabet = "abcdefghijklmnopqrstuvwxyz".toUpperCase().split("");
    const Puzzle = this;
    // create word storage
    var words = {};
    Puzzle.state.authorLetters.forEach(function(char,i){
      words[Alphabet[i]] = char;
      Puzzle.handleLetterInput(char);
    });
    this.setState({indexedWords:words})

    // create words Component
    var wordsComponent = Puzzle.state.authorLetters.map(function(char,i){
      return (
        <Word
          key={i}
          wordId={Alphabet[i]}
          firstLetter={char}
          handleWordInput={Puzzle.handleWordInput}
        />
      );
    });
    this.setState({wordsComponent:wordsComponent});
  }

  createGrid() {
    var index = 1;
    var tempArray = [];
    var grid;
    const Puzzle = this;
    Puzzle.state.quote.split('').forEach(function(char){
      var letterInfo = {};
      if (isLetter(char)) {
        letterInfo.letter = char;
        letterInfo.index = index;
        letterInfo.wordId = null;
        index ++;
        tempArray.push(letterInfo);
      } else {
        letterInfo.letter = char;
        letterInfo.index = null;
        letterInfo.wordId = null;
        tempArray.push(letterInfo);
      }
    });

    while (tempArray.length%10 != 0) {
      tempArray.push({letter:' '});
    }
    Puzzle.setState({indexedQuoteLetters:tempArray},function(){
      grid = tempArray.map(function(obj,i){
        return (<Tile key={i} letter={obj.letter} index={obj.index}/>);
      });
      Puzzle.setState({gridComponent:grid});
    });
  }

  render() {
    var continueButtonDisabled, quoteConstraintClass, authorConstraintClass, validityConstraintClass;

    if (this.state.quote.length) {
      var quoteConstraintClass = "green";
    } else {
      var quoteConstraintClass = "red";
    }

    if (this.state.authorLetters.length) {
      authorConstraintClass = "green";
    } else {
      authorConstraintClass = "red";
    }

    if (this.state.quote.length && this.state.authorLetters.length && this.state.isValid) {
      continueButtonDisabled = false;
      validityConstraintClass = "green";
    } else {
      continueButtonDisabled = true;
      validityConstraintClass = "red";
    }

    return (

Enter a quote and its author

          <form>

              Please Enter Quote
              <TextArea
                className="quote-input"
                rows="5" handleQuoteChange={this.handleQuoteChange} />

              Please Enter Author
              <TextArea
                className="author-input"
                rows="1"
                handleQuoteChange={this.handleAuthorChange}
                />

            <input disabled={continueButtonDisabled} onClick={this.continueToNextStep} className="form-control" type="submit" value="Continue" />
          </form>

Constraints

Quote not empty

Author not empty

Author name can be made up by letters from the quote

            {this.state.gridComponent}

            {this.state.wordsComponent}

    );
  }
}
Puzzle.propTypes = {
};
<commit_msg>finish grid update when addin letters<commit_after>import React, { Component, PropTypes } from 'react';
import TextArea from './textArea';
import Tile from './tile';
import Word from './word';
import update from 'immutability-helper';

function isLetter(char) {
  return char.match(/^[A-Za-z]+$/);
}

export default class Puzzle extends Component {
  constructor(props) {
    super(props);
    this.handleQuoteChange = this.handleQuoteChange.bind(this);
    this.handleAuthorChange = this.handleAuthorChange.bind(this);
    this.validatePuzzle = this.validatePuzzle.bind(this);
    this.continueToNextStep = this.continueToNextStep.bind(this);
    this.handleStepChange = this.handleStepChange.bind(this);
    this.createGrid = this.createGrid.bind(this);
    this.updateGrid = this.updateGrid.bind(this);
    this.createWordRows = this.createWordRows.bind(this);
    this.handleWordInput = this.handleWordInput.bind(this);
    this.handleLetterInput = this.handleLetterInput.bind(this);
    this.handleLetterRemoval = this.handleLetterRemoval.bind(this);
    this.state = {
      isValid: false,
      quote:'',
      indexedQuoteLetters: [],
      indexedWords: {},
      authorLetters: [],
      quoteLetterTracker: {},
      gridComponent: null,
      wordsComponent:null,
      currentStep: 1,
    };
  }

  handleQuoteChange(string) {
    var library = {};
    string.split('').forEach(function(char){
      if (isLetter(char)) {
        if (library[char.toLowerCase()]) {
          library[char.toLowerCase()] += 1;
        } else {
          library[char.toLowerCase()] = 1;
        }
      }
    });
    this.setState({
      quote: string,
      quoteLetterTracker: library
    },function(){
      this.validatePuzzle();
    });
  }
  handleAuthorChange(string) {
    var array = [];
    if (string) {
      string.split('').forEach(function(char){
        if (isLetter(char)) {
          array.push(char.toLowerCase());
        }
      });
    }
    this.setState({authorLetters: array},function(){
      this.validatePuzzle();
    });
  }
  validatePuzzle() {
    var quoteLetterTrackerClone = Object.assign({}, this.state.quoteLetterTracker);
    var puzzleValidity = true;
    if (this.state.authorLetters) {
      this.state.authorLetters.forEach(function(char){
        if (!quoteLetterTrackerClone[char] || quoteLetterTrackerClone[char] == 0) {
          puzzleValidity = false;
        }
        if (quoteLetterTrackerClone[char]) {
          quoteLetterTrackerClone[char] -= 1;
        }
      });
    }
    this.setState({isValid:puzzleValidity});
  }

  continueToNextStep(e) {
    // $('.step-1').hide();
    e.preventDefault();
    var step = this.state.currentStep + 1;
    this.setState({currentStep: step}, function(){
      this.handleStepChange(step);
    });
  }

  handleStepChange(step) {
    if (step == 2) {
      this.createGrid();
      this.createWordRows();
    }
  }

  handleWordInput(text,wordId) {
    // get old state of the word
    const Puzzle = this;
    var oldWord = Puzzle.state.indexedWords[wordId];
    var newWord = oldWord[0] + text;
    // update that word in Component state
    Puzzle.state.indexedWords[wordId] = newWord;
    Puzzle.forceUpdate();
    // compare oldWord and newWord to figure out if the event is addition or deletion
    if (newWord.length > oldWord.length) {
      var newChar = newWord.replace(oldWord,'');
      this.handleLetterInput(newChar,wordId);
    } else {
      var deletedChar = oldWord.replace(newWord,'');
      this.handleLetterRemoval(deletedChar,wordId);
    }
  }

  handleLetterInput(char,wordId) {
    for (i in this.state.indexedQuoteLetters) {
      var obj = this.state.indexedQuoteLetters[i];
      if (obj.letter == char && !obj.wordId) {
        this.state.indexedQuoteLetters[i].wordId = wordId;
        this.updateGrid(this.state.indexedQuoteLetters);
        return
      }
    }
  }

  handleLetterRemoval(char) {
    console.log('do something with '+char)
    // console.log(this.state.quoteLetterTracker)
  }

  createWordRows() {
    const Alphabet = "abcdefghijklmnopqrstuvwxyz".toUpperCase().split("");
    const Puzzle = this;
    // create word storage
    var words = {};
    Puzzle.state.authorLetters.forEach(function(char,i){
      words[Alphabet[i]] = char;
      Puzzle.handleLetterInput(char);
    });
    this.setState({indexedWords:words})

    // create words Component
    var wordsComponent = Puzzle.state.authorLetters.map(function(char,i){
      return (
        <Word
          key={i}
          wordId={Alphabet[i]}
          firstLetter={char}
          handleWordInput={Puzzle.handleWordInput}
        />
      );
    });
    this.setState({wordsComponent:wordsComponent});
  }

  createGrid() {
    var index = 1;
    var tempArray = [];
    var grid;
    const Puzzle = this;
    Puzzle.state.quote.split('').forEach(function(char){
      var letterInfo = {};
      if (isLetter(char)) {
        letterInfo.letter = char;
        letterInfo.index = index;
        letterInfo.wordId = null;
        index ++;
        tempArray.push(letterInfo);
      } else {
        letterInfo.letter = char;
        letterInfo.index = null;
        letterInfo.wordId = null;
        tempArray.push(letterInfo);
      }
    });

    while (tempArray.length%10 != 0) {
      tempArray.push({letter:' '});
    }
    Puzzle.setState({indexedQuoteLetters:tempArray},function(){
      Puzzle.updateGrid(Puzzle.state.indexedQuoteLetters);
    });
  }

  updateGrid(indexedLetters) {
    grid = indexedLetters.map(function(obj,i){
      return (
        <Tile
          key={i}
          letter={obj.letter}
          index={obj.index}
          wordId={obj.wordId}
        />
      );
    });
    this.setState({gridComponent:grid});
  }

  render() {
    var continueButtonDisabled, quoteConstraintClass, authorConstraintClass, validityConstraintClass;

    if (this.state.quote.length) {
      var quoteConstraintClass = "green";
    } else {
      var quoteConstraintClass = "red";
    }

    if (this.state.authorLetters.length) {
      authorConstraintClass = "green";
    } else {
      authorConstraintClass = "red";
    }

    if (this.state.quote.length && this.state.authorLetters.length && this.state.isValid) {
      continueButtonDisabled = false;
      validityConstraintClass = "green";
    } else {
      continueButtonDisabled = true;
      validityConstraintClass = "red";
    }

    return (

Enter a quote and its author

          <form>

              Please Enter Quote
              <TextArea
                className="quote-input"
                rows="5" handleQuoteChange={this.handleQuoteChange} />

              Please Enter Author
              <TextArea
                className="author-input"
                rows="1"
                handleQuoteChange={this.handleAuthorChange}
                />

            <input disabled={continueButtonDisabled} onClick={this.continueToNextStep} className="form-control" type="submit" value="Continue" />
          </form>

Constraints

Quote not empty

Author not empty

Author name can be made up by letters from the quote

            {this.state.gridComponent}

            {this.state.wordsComponent}

    );
  }
}
Puzzle.propTypes = {
};
<|endoftext|>


<commit_before>/* This file contains tiles for creating different types/shapes of houses.
 * Although they could be used to construct full-sized levels, the focus
 * is on narrow spaces and houses.
 * */

const RG = require('../src/rg');
RG.Template = require('../src/template');

const Houses5x5 = {tiles: {}, templates: {}, Models: {}};
const RNG = RG.Random.getRNG();

Houses5x5.tiles.start1x1 = [
`
name:start1x1_A
A=#
B=#
C=#
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`,

`
name:start1x1_B
A=#
B=#
C=#
D=#
E=#
F=#

.ABC.
D#:##
E:::#
F:::#
##+##`,

`
name:start1x1_C
A=#
B=#
C=#
D=#
E=#
F=#

.ABC.
D#:#.
E::##
F:::#
##+##`,

`
name:start1x1_D
A=.
B=.
C=.
D=#
E=#
F=#

.ABC.
D###.
E::##
F:::#
##+##`

];

Houses5x5.tiles.start1xN = [
`
dir:N
startY:max
startX:first
name:start1xN_A
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`,

`
dir:N
startY:max
startX:first
name:start1xN_B
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D#:##
E:::#
F:::#
##+##`,

`
dir:N
startY:max
startX:first
name:start1xN_C
A=:
B=:
C=:
D=#
E=#
F=.

#ABC#
D:::#
E#:##
F#:#.
.#+#.`,

`
dir:N
startY:max
startX:first
name:start1xD_C
A=#
B=:
C=#
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`

];

Houses5x5.tiles.start2xN = [
`
dir:NE
startX:first
startY:max
name:start2xN_A
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D:::#
E::::
F:::#
##+##`,

`
dir:NW
startX:max
startY:max
name:start2xN_B
A=#
B=:
C=#
D=#
E=:
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`
];

Houses5x5.tiles.start2xN = Houses5x5.tiles.start2xN.concat(
    Houses5x5.tiles.start1xN);

// Good starting rooms for houses
Houses5x5.tiles.start = [
`
dir:NEW
name:entrance2
A=:
B=:
C=:
D=#
E=:
F=#

#ABC#
D:::#
E::::
F:::#
##+##`,

`
dir:NW
name:entrance3
A=#
B=:
C=#
D=#
E=:
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`
];

Houses5x5.tiles.corners = [
`
dir:SE
name:corner1
A=#
B=#
C=#
D=#
E=#
F=#

#ABC#
D::::
E::::
F:::#
#::##`,

`
dir:SEW
name:corner2
A=#
B=#
C=#
D=:
E=:
F=#

#ABC#
D::::
E::::
F:::#
##:##`,

`
dir:SE
name:corner3
A=.
B=.
C=.
D=#
E=#
F=#

.ABC#
D####
E::::
F:::#
##:##`,

`
dir:NSEW
name:corner4
A=:
B=:
C=:
D=:
E=:
F=:

#ABC#
D::::
E::::
F::::
#:::#`

];

Houses5x5.tiles.body = [

`
dir:NS
name:corridor
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
#:::#`,

`
dir:NSEW
name:body1
A=:
B=:
C=:
D=:
E=:
F=:

#ABC#
D::::
E:#::
F::::
#:::#`,

`
dir:NSEW
name:body_cross
A=:
B=:
C=:
D=:
E=:
F=:

#ABC#
D:#::
E###:
F:#::
#:::#`

];

Houses5x5.tiles.terms = [
`
dir:S
name:term3x1
A=.
B=.
C=.
D=.
E=.
F=#

.ABC.
D....
E....
F####
#:::#`,

`
dir:S
name:term3x2
A=.
B=.
C=.
D=.
E=#
F=#

.ABC.
D....
E####
F:::#
#:::#`,

`
dir:S
name:term3x3
A=.
B=.
C=.
D=#
E=#
F=#

.ABC.
D####
E:::#
F:::#
#:::#`,

`
dir:S
name:term3x4
A=#
B=#
C=#
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
#:::#`

];

/* Used to free up the entrance.*/
Houses5x5.tiles.blocker = `
name:BLOCKER
A=.
B=.
C=.
D=.
E=.
F=.

.ABC.
D....
E....
F....
.....`;

Houses5x5.tiles.filler = `
name:FILLER
A=.
B=.
C=.
D=.
E=.
F=.

.ABC.
D....
E....
F....
.....`;

/* Must be passed to Template.Level using setStartRoomFunc(), to ensure
 * that entrance tile is placed in sane way. */
Houses5x5.startRoomFunc = function() {
    if (this.tilesX === 1 && this.tilesY === 1) {
        return {x: 0, y: 0,
            room: RNG.arrayGetRand(Houses5x5.templates.start1x1)
        };
    }
    else if (this.tilesX === 1) {
        const usableTempl = Houses5x5.templates.start1xN.filter(templ => (
            !(/(R90|R270)/i).test(templ.getProp('name'))
        ));
        const templ = RNG.arrayGetRand(usableTempl);
        let y = this.tilesY - 1;
        if (/R180/i.test(templ.getProp('name'))) {y = 0;}
        return {x: 0, y, room: templ};
    }
    else if (this.tilesY === 1) {
        const usableTempl = Houses5x5.templates.start1xN.filter(templ => (
            (/(R90|R270)/i).test(templ.getProp('name'))
        ));
        const templ = RNG.arrayGetRand(usableTempl);
        let x = 0;
        if (/R270/i.test(templ.getProp('name'))) {x = this.tilesX - 1;}
        return {x, y: 0, room: templ};
    }
    else if (this.tilesX === 2 || this.tilesY === 2) {
        const usableTempl = Houses5x5.templates.start2xN;
        const templ = RNG.arrayGetRand(usableTempl);
        let x = 0;
        let y = 0;
        if (templ.getProp('startX') === 'max') {
            x = this.tilesX - 1;
        }
        if (templ.getProp('startY') === 'max') {
            y = this.tilesY - 1;
        }
        return {x, y, room: templ};
    }
    const midX = Math.floor(this.tilesX / 2);
    const midY = Math.floor(this.tilesY / 2);
    const tile = RNG.arrayGetRand(Houses5x5.tiles.start);

    const blocker = RG.Template.createTemplate(Houses5x5.tiles.blocker);
    for (let y = midY; y < this.tilesY; y++) {
        this.addRoom(blocker, midX, y);
    }

    const templ = RG.Template.createTemplate(tile);
    return {
        x: midX, y: midY, room: templ
    };
};

Houses5x5.Models.default = []
    .concat(Houses5x5.tiles.terms)
    .concat(Houses5x5.tiles.body)
    .concat(Houses5x5.tiles.corners);
Houses5x5.tiles.all = Houses5x5.Models.default;

const startNames = ['all', 'start1x1', 'start1xN', 'start2xN'];
startNames.forEach(name => {
    Houses5x5.templates[name] = Houses5x5.tiles[name].map(tile => (
        RG.Template.createTemplate(tile)
    ));

    const transformed = RG.Template.transformList(Houses5x5.templates[name]);
    Houses5x5.templates[name] = Houses5x5.templates[name].concat(transformed);
});

// Transform also properties startX,startY
Houses5x5.templates.start2xN.forEach(templ => {
    const name = templ.getProp('name');
    if (/_flip/.test(name)) {
        flipPropsVer(templ);
    }
    if (/R90/i.test(name)) {
        rotatePropsR90(templ);
    }
    else if (/R180/i.test(name)) {
        rotatePropsR90(templ);
        rotatePropsR90(templ);
    }
    else if (/R270/i.test(name)) {
        rotatePropsR90(templ);
        rotatePropsR90(templ);
        rotatePropsR90(templ);
    }
});

function rotatePropsR90(templ) {
    const startX = templ.getProp('startX');
    const startY = templ.getProp('startY');
    if (!RG.isNullOrUndef([startX])) {
        templ.setProp('startY', startX);
    }
    if (startY === 'max') {
        templ.setProp('startX', 'first');
    }
    else if (startY === 'first') {
        templ.setProp('startX', 'max');
    }
}

function flipPropsVer(templ) {
    const startX = templ.getProp('startX');
    if (startX === 'max') {
        console.log('Flipped X -> 0 for', templ.getProp('name'));
        templ.setProp('startX', 'first');
        console.log(templ);
    }
    else if (startX === 'first') {
        console.log('Flipped X -> max for', templ.getProp('name'));
        templ.setProp('startX', 'max');
        console.log(templ);
    }
}

Houses5x5.templates.start2xN.forEach(templ => {
    console.log(JSON.stringify(templ));
});

module.exports = {Houses5x5};

<commit_msg>Removed debug prints.<commit_after>/* This file contains tiles for creating different types/shapes of houses.
 * Although they could be used to construct full-sized levels, the focus
 * is on narrow spaces and houses.
 * */

const RG = require('../src/rg');
RG.Template = require('../src/template');

const Houses5x5 = {tiles: {}, templates: {}, Models: {}};
const RNG = RG.Random.getRNG();

Houses5x5.tiles.start1x1 = [
`
name:start1x1_A
A=#
B=#
C=#
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`,

`
name:start1x1_B
A=#
B=#
C=#
D=#
E=#
F=#

.ABC.
D#:##
E:::#
F:::#
##+##`,

`
name:start1x1_C
A=#
B=#
C=#
D=#
E=#
F=#

.ABC.
D#:#.
E::##
F:::#
##+##`,

`
name:start1x1_D
A=.
B=.
C=.
D=#
E=#
F=#

.ABC.
D###.
E::##
F:::#
##+##`

];

Houses5x5.tiles.start1xN = [
`
dir:N
startY:max
startX:first
name:start1xN_A
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`,

`
dir:N
startY:max
startX:first
name:start1xN_B
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D#:##
E:::#
F:::#
##+##`,

`
dir:N
startY:max
startX:first
name:start1xN_C
A=:
B=:
C=:
D=#
E=#
F=.

#ABC#
D:::#
E#:##
F#:#.
.#+#.`,

`
dir:N
startY:max
startX:first
name:start1xD_C
A=#
B=:
C=#
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`

];

Houses5x5.tiles.start2xN = [
`
dir:NE
startX:first
startY:max
name:start2xN_A
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D:::#
E::::
F:::#
##+##`,

`
dir:NW
startX:max
startY:max
name:start2xN_B
A=#
B=:
C=#
D=#
E=:
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`
];

Houses5x5.tiles.start2xN = Houses5x5.tiles.start2xN.concat(
    Houses5x5.tiles.start1xN);

// Good starting rooms for houses
Houses5x5.tiles.start = [
`
dir:NEW
name:entrance2
A=:
B=:
C=:
D=#
E=:
F=#

#ABC#
D:::#
E::::
F:::#
##+##`,

`
dir:NW
name:entrance3
A=#
B=:
C=#
D=#
E=:
F=#

#ABC#
D:::#
E:::#
F:::#
##+##`
];

Houses5x5.tiles.corners = [
`
dir:SE
name:corner1
A=#
B=#
C=#
D=#
E=#
F=#

#ABC#
D::::
E::::
F:::#
#::##`,

`
dir:SEW
name:corner2
A=#
B=#
C=#
D=:
E=:
F=#

#ABC#
D::::
E::::
F:::#
##:##`,

`
dir:SE
name:corner3
A=.
B=.
C=.
D=#
E=#
F=#

.ABC#
D####
E::::
F:::#
##:##`,

`
dir:NSEW
name:corner4
A=:
B=:
C=:
D=:
E=:
F=:

#ABC#
D::::
E::::
F::::
#:::#`

];

Houses5x5.tiles.body = [

`
dir:NS
name:corridor
A=:
B=:
C=:
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
#:::#`,

`
dir:NSEW
name:body1
A=:
B=:
C=:
D=:
E=:
F=:

#ABC#
D::::
E:#::
F::::
#:::#`,

`
dir:NSEW
name:body_cross
A=:
B=:
C=:
D=:
E=:
F=:

#ABC#
D:#::
E###:
F:#::
#:::#`

];

Houses5x5.tiles.terms = [
`
dir:S
name:term3x1
A=.
B=.
C=.
D=.
E=.
F=#

.ABC.
D....
E....
F####
#:::#`,

`
dir:S
name:term3x2
A=.
B=.
C=.
D=.
E=#
F=#

.ABC.
D....
E####
F:::#
#:::#`,

`
dir:S
name:term3x3
A=.
B=.
C=.
D=#
E=#
F=#

.ABC.
D####
E:::#
F:::#
#:::#`,

`
dir:S
name:term3x4
A=#
B=#
C=#
D=#
E=#
F=#

#ABC#
D:::#
E:::#
F:::#
#:::#`

];

/* Used to free up the entrance.*/
Houses5x5.tiles.blocker = `
name:BLOCKER
A=.
B=.
C=.
D=.
E=.
F=.

.ABC.
D....
E....
F....
.....`;

Houses5x5.tiles.filler = `
name:FILLER
A=.
B=.
C=.
D=.
E=.
F=.

.ABC.
D....
E....
F....
.....`;

/* Must be passed to Template.Level using setStartRoomFunc(), to ensure
 * that entrance tile is placed in sane way. */
Houses5x5.startRoomFunc = function() {
    if (this.tilesX === 1 && this.tilesY === 1) {
        return {x: 0, y: 0,
            room: RNG.arrayGetRand(Houses5x5.templates.start1x1)
        };
    }
    else if (this.tilesX === 1) {
        const usableTempl = Houses5x5.templates.start1xN.filter(templ => (
            !(/(R90|R270)/i).test(templ.getProp('name'))
        ));
        const templ = RNG.arrayGetRand(usableTempl);
        let y = this.tilesY - 1;
        if (/R180/i.test(templ.getProp('name'))) {y = 0;}
        return {x: 0, y, room: templ};
    }
    else if (this.tilesY === 1) {
        const usableTempl = Houses5x5.templates.start1xN.filter(templ => (
            (/(R90|R270)/i).test(templ.getProp('name'))
        ));
        const templ = RNG.arrayGetRand(usableTempl);
        let x = 0;
        if (/R270/i.test(templ.getProp('name'))) {x = this.tilesX - 1;}
        return {x, y: 0, room: templ};
    }
    else if (this.tilesX === 2 || this.tilesY === 2) {
        const usableTempl = Houses5x5.templates.start2xN;
        const templ = RNG.arrayGetRand(usableTempl);
        let x = 0;
        let y = 0;
        if (templ.getProp('startX') === 'max') {
            x = this.tilesX - 1;
        }
        if (templ.getProp('startY') === 'max') {
            y = this.tilesY - 1;
        }
        return {x, y, room: templ};
    }
    const midX = Math.floor(this.tilesX / 2);
    const midY = Math.floor(this.tilesY / 2);
    const tile = RNG.arrayGetRand(Houses5x5.tiles.start);

    const blocker = RG.Template.createTemplate(Houses5x5.tiles.blocker);
    for (let y = midY; y < this.tilesY; y++) {
        this.addRoom(blocker, midX, y);
    }

    const templ = RG.Template.createTemplate(tile);
    return {
        x: midX, y: midY, room: templ
    };
};

Houses5x5.Models.default = []
    .concat(Houses5x5.tiles.terms)
    .concat(Houses5x5.tiles.body)
    .concat(Houses5x5.tiles.corners);
Houses5x5.tiles.all = Houses5x5.Models.default;

const startNames = ['all', 'start1x1', 'start1xN', 'start2xN'];
startNames.forEach(name => {
    Houses5x5.templates[name] = Houses5x5.tiles[name].map(tile => (
        RG.Template.createTemplate(tile)
    ));

    const transformed = RG.Template.transformList(Houses5x5.templates[name]);
    Houses5x5.templates[name] = Houses5x5.templates[name].concat(transformed);
});

// Transform also properties startX,startY
Houses5x5.templates.start2xN.forEach(templ => {
    const name = templ.getProp('name');
    if (/_flip/.test(name)) {
        flipPropsVer(templ);
    }
    if (/R90/i.test(name)) {
        rotatePropsR90(templ);
    }
    else if (/R180/i.test(name)) {
        rotatePropsR90(templ);
        rotatePropsR90(templ);
    }
    else if (/R270/i.test(name)) {
        rotatePropsR90(templ);
        rotatePropsR90(templ);
        rotatePropsR90(templ);
    }
});

function rotatePropsR90(templ) {
    const startX = templ.getProp('startX');
    const startY = templ.getProp('startY');
    if (!RG.isNullOrUndef([startX])) {
        templ.setProp('startY', startX);
    }
    if (startY === 'max') {
        templ.setProp('startX', 'first');
    }
    else if (startY === 'first') {
        templ.setProp('startX', 'max');
    }
}

function flipPropsVer(templ) {
    const startX = templ.getProp('startX');
    if (startX === 'max') {
        templ.setProp('startX', 'first');
    }
    else if (startX === 'first') {
        templ.setProp('startX', 'max');
    }
}

module.exports = {Houses5x5};

<|endoftext|>