<commit_before>var createTextGeometry = require('three-bmfont-text');
var loadBMFont = require('load-bmfont');

var registerComponent = require('../core/component').registerComponent;
var coreShader = require('../core/shader');
var THREE = require('../lib/three');
var utils = require('../utils/');

var error = utils.debug('components:text:error');
var shaders = coreShader.shaders;
var warn = utils.debug('components:text:warn');

// 1 to match other A-Frame default widths.
var DEFAULT_WIDTH = 1;

// @bryik set anisotropy to 16. Improves look of large amounts of text when viewed from angle.
var MAX_ANISOTROPY = 16;

var FONT_BASE_URL = 'https://cdn.aframe.io/fonts/';
var FONTS = {
  aileronsemibold: FONT_BASE_URL + 'Aileron-Semibold.fnt',
  dejavu: FONT_BASE_URL + 'DejaVu-sdf.fnt',
  exo2bold: FONT_BASE_URL + 'Exo2Bold.fnt',
  exo2semibold: FONT_BASE_URL + 'Exo2SemiBold.fnt',
  kelsonsans: FONT_BASE_URL + 'KelsonSans.fnt',
  monoid: FONT_BASE_URL + 'Monoid.fnt',
  mozillavr: FONT_BASE_URL + 'mozillavr.fnt',
  roboto: FONT_BASE_URL + 'Roboto-msdf.json',
  sourcecodepro: FONT_BASE_URL + 'SourceCodePro.fnt'
};
var MSDF_FONTS = ['roboto'];
var DEFAULT_FONT = 'roboto';
module.exports.FONTS = FONTS;

var cache = new PromiseCache();
var fontWidthFactors = {};
var textures = {};

/**
 * SDF-based text component.
 * Based on https://github.com/Jam3/three-bmfont-text.
 *
 * All the stock fonts are for the `sdf` registered shader, an improved version of jam3's
 * original `sdf` shader.
 */
module.exports.Component = registerComponent('text', {
  multiple: true,

  schema: {
    align: {type: 'string', default: 'left', oneOf: ['left', 'right', 'center']},
    alphaTest: {default: 0.5},
    // `anchor` defaults to center to match geometries.
    anchor: {default: 'center', oneOf: ['left', 'right', 'center', 'align']},
    baseline: {default: 'center', oneOf: ['top', 'center', 'bottom']},
    color: {type: 'color', default: '#FFF'},
    font: {type: 'string', default: DEFAULT_FONT},
    // `fontImage` defaults to the font name as a .png (e.g., mozillavr.fnt -> mozillavr.png).
    fontImage: {type: 'string'},
    // `height` has no default, will be populated at layout.
    height: {type: 'number'},
    letterSpacing: {type: 'number', default: 0},
    // `lineHeight` defaults to font's `lineHeight` value.
    lineHeight: {type: 'number'},
    opacity: {type: 'number', default: 1.0},
    shader: {default: 'sdf', oneOf: shaders},
    side: {default: 'front', oneOf: ['front', 'back', 'double']},
    tabSize: {default: 4},
    transparent: {default: true},
    value: {type: 'string'},
    whiteSpace: {default: 'normal', oneOf: ['normal', 'pre', 'nowrap']},
    // `width` defaults to geometry width if present, else `DEFAULT_WIDTH`.
    width: {type: 'number'},
    // `wrapCount` units are about one default font character. Wrap roughly at this number.
    wrapCount: {type: 'number', default: 40},
    // `wrapPixels` will wrap using bmfont pixel units (e.g., dejavu's is 32 pixels).
    wrapPixels: {type: 'number'},
    // `yOffset` to adjust generated fonts from tools that may have incorrect metrics.
    yOffset: {type: 'number', default: 0},
    // `zOffset` will provide a small z offset to avoid z-fighting.
    zOffset: {type: 'number', default: 0.001}
  },

  init: function () {
    this.geometry = createTextGeometry();
    this.createOrUpdateMaterial();
    this.mesh = new THREE.Mesh(this.geometry, this.material);
    this.el.setObject3D(this.attrName, this.mesh);
  },

  update: function (oldData) {
    var data = coerceData(this.data);
    var font = this.currentFont;
    var fontImage = this.getFontImageSrc();

    if (textures[fontImage]) {
      this.texture = textures[fontImage];
    } else {
      // Create texture per font.
      this.texture = textures[fontImage] = new THREE.Texture();
      this.texture.anisotropy = MAX_ANISOTROPY;
    }

    // Update material.
    this.createOrUpdateMaterial();

    // New font. `updateFont` will later change data and layout.
    if (oldData.font !== data.font) {
      this.updateFont();
      return;
    }

    // Update geometry and layout.
    if (font) {
      this.updateGeometry(this.geometry, data, font);
      this.updateLayout(data);
    }
  },

  /**
   * Clean up geometry, material, texture, mesh, objects.
   */
  remove: function () {
    this.geometry.dispose();
    this.geometry = null;
    this.el.removeObject3D(this.attrName);
    this.material.dispose();
    this.material = null;
    this.texture.dispose();
    this.texture = null;
    if (this.shaderObject) {
      delete this.shaderObject;
    }
  },

  /**
   * Update the shader of the material.
   */
  createOrUpdateMaterial: function () {
    var data = this.data;
    var hasChangedShader;
    var material = this.material;
    var NewShader;
    var shaderData;
    var shaderName;

    // Infer shader if using a stock font (or from `-msdf` filename convention).
    shaderName = data.shader;
    if (MSDF_FONTS.indexOf(data.font) !== -1 || data.font.indexOf('-msdf.') >= 0) {
      shaderName = 'msdf';
    } else if (data.font in FONTS && MSDF_FONTS.indexOf(data.font) === -1) {
      shaderName = 'sdf';
    }

    hasChangedShader = (this.shaderObject && this.shaderObject.name) !== shaderName;
    shaderData = {
      alphaTest: data.alphaTest,
      color: data.color,
      map: this.texture,
      opacity: data.opacity,
      side: parseSide(data.side),
      transparent: data.transparent
    };

    // Shader has not changed, do an update.
    if (!hasChangedShader) {
      // Update shader material.
      this.shaderObject.update(shaderData);
      // Apparently, was not set on `init` nor `update`.
      material.transparent = shaderData.transparent;
      updateBaseMaterial(material, shaderData);
      return;
    }

    // Shader has changed. Create a shader material.
    NewShader = createShader(this.el, shaderName, shaderData);
    this.material = NewShader.material;
    this.shaderObject = NewShader.shader;

    // Set new shader material.
    updateBaseMaterial(this.material, shaderData);
    if (this.mesh) { this.mesh.material = this.material; }
  },

  /**
   * Load font for geometry, load font image for material, and apply.
   */
  updateFont: function () {
    var data = this.data;
    var el = this.el;
    var fontSrc;
    var geometry = this.geometry;
    var self = this;

    if (!data.font) { warn('No font specified. Using the default font.'); }

    // Make invisible during font swap.
    this.mesh.visible = false;

    // Look up font URL to use, and perform cached load.
    fontSrc = this.lookupFont(data.font || DEFAULT_FONT) || data.font;
    cache.get(fontSrc, function doLoadFont () {
      return loadFont(fontSrc, data.yOffset);
    }).then(function setFont (font) {
      var coercedData;
      var fontImgSrc;

      if (font.pages.length !== 1) {
        throw new Error('Currently only single-page bitmap fonts are supported.');
      }

      if (!fontWidthFactors[fontSrc]) {
        font.widthFactor = fontWidthFactors[font] = computeFontWidthFactor(font);
      }

      // Update geometry given font metrics.
      coercedData = coerceData(data);
      self.updateGeometry(geometry, self.data, font);

      // Set font and update layout.
      self.currentFont = font;
      self.updateLayout(coercedData);

      // Look up font image URL to use, and perform cached load.
      fontImgSrc = self.getFontImageSrc();
      cache.get(fontImgSrc, function () {
        return loadTexture(fontImgSrc);
      }).then(function (image) {
        // Make mesh visible and apply font image as texture.
        var texture = self.texture;
        texture.image = image;
        texture.needsUpdate = true;
        textures[fontImgSrc] = texture;
        self.texture = texture;
        self.mesh.visible = true;
        el.emit('textfontset', {font: data.font, fontObj: font});
      }).catch(function (err) {
        error(err);
        throw err;
      });
    }).catch(function (err) {
      error(err);
      throw err;
    });
  },

  getFontImageSrc: function () {
    var fontSrc = this.lookupFont(this.data.font || DEFAULT_FONT) || this.data.font;
    return this.data.fontImage || fontSrc.replace(/(\.fnt)|(\.json)/, '.png');
  },

  /**
   * Update layout with anchor, alignment, baseline, and considering any meshes.
   */
  updateLayout: function (data) {
    var anchor;
    var baseline;
    var el = this.el;
    var geometry = this.geometry;
    var geometryComponent = el.getAttribute('geometry');
    var height;
    var layout = geometry.layout;
    var mesh = this.mesh;
    var textRenderWidth;
    var textScale;
    var width;
    var x;
    var y;

    // Determine width to use (defined width, geometry's width, or default width).
    geometryComponent = el.getAttribute('geometry');
    width = data.width || (geometryComponent && geometryComponent.width) || DEFAULT_WIDTH;

    // Determine wrap pixel count. Either specified or by experimental fudge factor.
    // Note that experimental factor will never be correct for variable width fonts.
    textRenderWidth = computeWidth(data.wrapPixels, data.wrapCount,
                                   this.currentFont.widthFactor);
    textScale = width / textRenderWidth;

    // Determine height to use.
    height = textScale * (layout.height + layout.descender);

    // Update geometry dimensions to match text layout if width and height are set to 0.
    // For example, scales a plane to fit text.
    if (geometryComponent) {
      if (!geometryComponent.width) { el.setAttribute('geometry', 'width', width); }
      if (!geometryComponent.height) { el.setAttribute('geometry', 'height', height); }
    }

    // Calculate X position to anchor text left, center, or right.
    anchor = data.anchor === 'align' ? data.align : data.anchor;
    if (anchor === 'left') {
      x = 0;
    } else if (anchor === 'right') {
      x = -1 * layout.width;
    } else if (anchor === 'center') {
      x = -1 * layout.width / 2;
    } else {
      throw new TypeError('Invalid text.anchor property value', anchor);
    }

    // Calculate Y position to anchor text top, center, or bottom.
    baseline = data.baseline;
    if (baseline === 'bottom') {
      y = 0;
    } else if (baseline === 'top') {
      y = -1 * layout.height + layout.ascender;
    } else if (baseline === 'center') {
      y = -1 * layout.height / 2;
    } else {
      throw new TypeError('Invalid text.baseline property value', baseline);
    }

    // Position and scale mesh to apply layout.
    mesh.position.x = x * textScale;
    mesh.position.y = y * textScale;
    // Place text slightly in front to avoid Z-fighting.
    mesh.position.z = data.zOffset;
    mesh.scale.set(textScale, -1 * textScale, textScale);
    this.geometry.computeBoundingSphere();
  },

  /**
   * Grab font from the constant.
   * Set as a method for test stubbing purposes.
   */
  lookupFont: function (key) {
    return FONTS[key];
  },

  /**
   * Update the text geometry using `three-bmfont-text.update`.
   */
  updateGeometry: function (geometry, data, font) {
    geometry.update(utils.extend({}, data, {
      font: font,
      width: computeWidth(data.wrapPixels, data.wrapCount, font.widthFactor),
      text: data.value.toString().replace(/\\n/g, '\n').replace(/\\t/g, '\t'),
      lineHeight: data.lineHeight || font.common.lineHeight
    }));
  }
});

function parseSide (side) {
  switch (side) {
    case 'back': {
      return THREE.BackSide;
    }
    case 'double': {
      return THREE.DoubleSide;
    }
    default: {
      return THREE.FrontSide;
    }
  }
}

/**
 * Coerce some data to numbers.
 * as they will be passed directly into text creation and update
 */
function coerceData (data) {
  data = utils.clone(data);
  if (data.lineHeight !== undefined) {
    data.lineHeight = parseFloat(data.lineHeight);
    if (!isFinite(data.lineHeight)) { data.lineHeight = undefined; }
  }
  if (data.width !== undefined) {
    data.width = parseFloat(data.width);
    if (!isFinite(data.width)) { data.width = undefined; }
  }
  return data;
}

/**
 * @returns {Promise}
 */
function loadFont (src, yOffset) {
  return new Promise(function (resolve, reject) {
    loadBMFont(src, function (err, font) {
      if (err) {
        error('Error loading font', src);
        reject(err);
        return;
      }

      // Fix negative Y offsets for Roboto MSDF font from tool. Experimentally determined.
      if (src.indexOf('/Roboto-msdf.json') >= 0) { yOffset = 30; }
      if (yOffset) { font.chars.map(function doOffset (ch) { ch.yoffset += yOffset; }); }

      resolve(font);
    });
  });
}

/**
 * @returns {Promise}
 */
function loadTexture (src) {
  return new Promise(function (resolve, reject) {
    new THREE.ImageLoader().load(src, function (image) {
      resolve(image);
    }, undefined, function () {
      error('Error loading font image', src);
      reject(null);
    });
  });
}

function createShader (el, shaderName, data) {
  var shader;
  var shaderObject;

  // Set up Shader.
  shaderObject = new shaders[shaderName].Shader();
  shaderObject.el = el;
  shaderObject.init(data);
  shaderObject.update(data);

  // Get material.
  shader = shaderObject.material;
  // Apparently, was not set on `init` nor `update`.
  shader.transparent = data.transparent;

  return {
    material: shader,
    shader: shaderObject
  };
}

/**
 * @todo Add more supported material properties (e.g., `visible`).
 */
function updateBaseMaterial (material, data) {
  material.side = data.side;
}

/**
 * Determine wrap pixel count. Either specified or by experimental fudge factor.
 * Note that experimental factor will never be correct for variable width fonts.
 */
function computeWidth (wrapPixels, wrapCount, widthFactor) {
  return wrapPixels || ((0.5 + wrapCount) * widthFactor);
}

/**
 * Compute default font width factor to use.
 */
function computeFontWidthFactor (font) {
  var sum = 0;
  var digitsum = 0;
  var digits = 0;
  font.chars.map(function (ch) {
    sum += ch.xadvance;
    if (ch.id >= 48 && ch.id <= 57) {
      digits++;
      digitsum += ch.xadvance;
    }
  });
  return digits ? digitsum / digits : sum / font.chars.length;
}

/**
 * Get or create a promise given a key and promise generator.
 * @todo Move to a utility and use in other parts of A-Frame.
 */
function PromiseCache () {
  var cache = this.cache = {};

  this.get = function (key, promiseGenerator) {
    if (key in cache) {
      return cache[key];
    }
    cache[key] = promiseGenerator();
    return cache[key];
  };
}
<commit_msg>added: depthTest and depthWrite properties to the Text component<commit_after>var createTextGeometry = require('three-bmfont-text');
var loadBMFont = require('load-bmfont');

var registerComponent = require('../core/component').registerComponent;
var coreShader = require('../core/shader');
var THREE = require('../lib/three');
var utils = require('../utils/');

var error = utils.debug('components:text:error');
var shaders = coreShader.shaders;
var warn = utils.debug('components:text:warn');

// 1 to match other A-Frame default widths.
var DEFAULT_WIDTH = 1;

// @bryik set anisotropy to 16. Improves look of large amounts of text when viewed from angle.
var MAX_ANISOTROPY = 16;

var FONT_BASE_URL = 'https://cdn.aframe.io/fonts/';
var FONTS = {
  aileronsemibold: FONT_BASE_URL + 'Aileron-Semibold.fnt',
  dejavu: FONT_BASE_URL + 'DejaVu-sdf.fnt',
  exo2bold: FONT_BASE_URL + 'Exo2Bold.fnt',
  exo2semibold: FONT_BASE_URL + 'Exo2SemiBold.fnt',
  kelsonsans: FONT_BASE_URL + 'KelsonSans.fnt',
  monoid: FONT_BASE_URL + 'Monoid.fnt',
  mozillavr: FONT_BASE_URL + 'mozillavr.fnt',
  roboto: FONT_BASE_URL + 'Roboto-msdf.json',
  sourcecodepro: FONT_BASE_URL + 'SourceCodePro.fnt'
};
var MSDF_FONTS = ['roboto'];
var DEFAULT_FONT = 'roboto';
module.exports.FONTS = FONTS;

var cache = new PromiseCache();
var fontWidthFactors = {};
var textures = {};

/**
 * SDF-based text component.
 * Based on https://github.com/Jam3/three-bmfont-text.
 *
 * All the stock fonts are for the `sdf` registered shader, an improved version of jam3's
 * original `sdf` shader.
 */
module.exports.Component = registerComponent('text', {
  multiple: true,

  schema: {
    align: {type: 'string', default: 'left', oneOf: ['left', 'right', 'center']},
    alphaTest: {default: 0.5},
    depthTest: {default: true},
    depthWrite: {default: true},
    // `anchor` defaults to center to match geometries.
    anchor: {default: 'center', oneOf: ['left', 'right', 'center', 'align']},
    baseline: {default: 'center', oneOf: ['top', 'center', 'bottom']},
    color: {type: 'color', default: '#FFF'},
    font: {type: 'string', default: DEFAULT_FONT},
    // `fontImage` defaults to the font name as a .png (e.g., mozillavr.fnt -> mozillavr.png).
    fontImage: {type: 'string'},
    // `height` has no default, will be populated at layout.
    height: {type: 'number'},
    letterSpacing: {type: 'number', default: 0},
    // `lineHeight` defaults to font's `lineHeight` value.
    lineHeight: {type: 'number'},
    opacity: {type: 'number', default: 1.0},
    shader: {default: 'sdf', oneOf: shaders},
    side: {default: 'front', oneOf: ['front', 'back', 'double']},
    tabSize: {default: 4},
    transparent: {default: true},
    value: {type: 'string'},
    whiteSpace: {default: 'normal', oneOf: ['normal', 'pre', 'nowrap']},
    // `width` defaults to geometry width if present, else `DEFAULT_WIDTH`.
    width: {type: 'number'},
    // `wrapCount` units are about one default font character. Wrap roughly at this number.
    wrapCount: {type: 'number', default: 40},
    // `wrapPixels` will wrap using bmfont pixel units (e.g., dejavu's is 32 pixels).
    wrapPixels: {type: 'number'},
    // `yOffset` to adjust generated fonts from tools that may have incorrect metrics.
    yOffset: {type: 'number', default: 0},
    // `zOffset` will provide a small z offset to avoid z-fighting.
    zOffset: {type: 'number', default: 0.001}
  },

  init: function () {
    this.geometry = createTextGeometry();
    this.createOrUpdateMaterial();
    this.mesh = new THREE.Mesh(this.geometry, this.material);
    this.el.setObject3D(this.attrName, this.mesh);
  },

  update: function (oldData) {
    var data = coerceData(this.data);
    var font = this.currentFont;
    var fontImage = this.getFontImageSrc();

    if (textures[fontImage]) {
      this.texture = textures[fontImage];
    } else {
      // Create texture per font.
      this.texture = textures[fontImage] = new THREE.Texture();
      this.texture.anisotropy = MAX_ANISOTROPY;
    }

    // Update material.
    this.createOrUpdateMaterial();

    // New font. `updateFont` will later change data and layout.
    if (oldData.font !== data.font) {
      this.updateFont();
      return;
    }

    // Update geometry and layout.
    if (font) {
      this.updateGeometry(this.geometry, data, font);
      this.updateLayout(data);
    }
  },

  /**
   * Clean up geometry, material, texture, mesh, objects.
   */
  remove: function () {
    this.geometry.dispose();
    this.geometry = null;
    this.el.removeObject3D(this.attrName);
    this.material.dispose();
    this.material = null;
    this.texture.dispose();
    this.texture = null;
    if (this.shaderObject) {
      delete this.shaderObject;
    }
  },

  /**
   * Update the shader of the material.
   */
  createOrUpdateMaterial: function () {
    var data = this.data;
    var hasChangedShader;
    var material = this.material;
    var NewShader;
    var shaderData;
    var shaderName;

    // Infer shader if using a stock font (or from `-msdf` filename convention).
    shaderName = data.shader;
    if (MSDF_FONTS.indexOf(data.font) !== -1 || data.font.indexOf('-msdf.') >= 0) {
      shaderName = 'msdf';
    } else if (data.font in FONTS && MSDF_FONTS.indexOf(data.font) === -1) {
      shaderName = 'sdf';
    }

    hasChangedShader = (this.shaderObject && this.shaderObject.name) !== shaderName;
    shaderData = {
      alphaTest: data.alphaTest,
      color: data.color,
      map: this.texture,
      opacity: data.opacity,
      side: parseSide(data.side),
      transparent: data.transparent
    };

    // Shader has not changed, do an update.
    if (!hasChangedShader) {
      // Update shader material.
      this.shaderObject.update(shaderData);
      // Apparently, was not set on `init` nor `update`.
      material.transparent = shaderData.transparent;
      updateBaseMaterial(material, shaderData);
      return;
    }

    // Shader has changed. Create a shader material.
    NewShader = createShader(this.el, shaderName, shaderData);
    this.material = NewShader.material;
    this.shaderObject = NewShader.shader;

    // Set new shader material.
    updateBaseMaterial(this.material, shaderData);
    if (this.mesh) { this.mesh.material = this.material; }
  },

  /**
   * Load font for geometry, load font image for material, and apply.
   */
  updateFont: function () {
    var data = this.data;
    var el = this.el;
    var fontSrc;
    var geometry = this.geometry;
    var self = this;

    if (!data.font) { warn('No font specified. Using the default font.'); }

    // Make invisible during font swap.
    this.mesh.visible = false;

    // Look up font URL to use, and perform cached load.
    fontSrc = this.lookupFont(data.font || DEFAULT_FONT) || data.font;
    cache.get(fontSrc, function doLoadFont () {
      return loadFont(fontSrc, data.yOffset);
    }).then(function setFont (font) {
      var coercedData;
      var fontImgSrc;

      if (font.pages.length !== 1) {
        throw new Error('Currently only single-page bitmap fonts are supported.');
      }

      if (!fontWidthFactors[fontSrc]) {
        font.widthFactor = fontWidthFactors[font] = computeFontWidthFactor(font);
      }

      // Update geometry given font metrics.
      coercedData = coerceData(data);
      self.updateGeometry(geometry, self.data, font);

      // Set font and update layout.
      self.currentFont = font;
      self.updateLayout(coercedData);

      // Look up font image URL to use, and perform cached load.
      fontImgSrc = self.getFontImageSrc();
      cache.get(fontImgSrc, function () {
        return loadTexture(fontImgSrc);
      }).then(function (image) {
        // Make mesh visible and apply font image as texture.
        var texture = self.texture;
        texture.image = image;
        texture.needsUpdate = true;
        textures[fontImgSrc] = texture;
        self.texture = texture;
        self.mesh.visible = true;
        el.emit('textfontset', {font: data.font, fontObj: font});
      }).catch(function (err) {
        error(err);
        throw err;
      });
    }).catch(function (err) {
      error(err);
      throw err;
    });
  },

  getFontImageSrc: function () {
    var fontSrc = this.lookupFont(this.data.font || DEFAULT_FONT) || this.data.font;
    return this.data.fontImage || fontSrc.replace(/(\.fnt)|(\.json)/, '.png');
  },

  /**
   * Update layout with anchor, alignment, baseline, and considering any meshes.
   */
  updateLayout: function (data) {
    var anchor;
    var baseline;
    var el = this.el;
    var geometry = this.geometry;
    var geometryComponent = el.getAttribute('geometry');
    var height;
    var layout = geometry.layout;
    var mesh = this.mesh;
    var textRenderWidth;
    var textScale;
    var width;
    var x;
    var y;

    // Determine width to use (defined width, geometry's width, or default width).
    geometryComponent = el.getAttribute('geometry');
    width = data.width || (geometryComponent && geometryComponent.width) || DEFAULT_WIDTH;

    // Determine wrap pixel count. Either specified or by experimental fudge factor.
    // Note that experimental factor will never be correct for variable width fonts.
    textRenderWidth = computeWidth(data.wrapPixels, data.wrapCount,
                                   this.currentFont.widthFactor);
    textScale = width / textRenderWidth;

    // Determine height to use.
    height = textScale * (layout.height + layout.descender);

    // Update geometry dimensions to match text layout if width and height are set to 0.
    // For example, scales a plane to fit text.
    if (geometryComponent) {
      if (!geometryComponent.width) { el.setAttribute('geometry', 'width', width); }
      if (!geometryComponent.height) { el.setAttribute('geometry', 'height', height); }
    }

    // Calculate X position to anchor text left, center, or right.
    anchor = data.anchor === 'align' ? data.align : data.anchor;
    if (anchor === 'left') {
      x = 0;
    } else if (anchor === 'right') {
      x = -1 * layout.width;
    } else if (anchor === 'center') {
      x = -1 * layout.width / 2;
    } else {
      throw new TypeError('Invalid text.anchor property value', anchor);
    }

    // Calculate Y position to anchor text top, center, or bottom.
    baseline = data.baseline;
    if (baseline === 'bottom') {
      y = 0;
    } else if (baseline === 'top') {
      y = -1 * layout.height + layout.ascender;
    } else if (baseline === 'center') {
      y = -1 * layout.height / 2;
    } else {
      throw new TypeError('Invalid text.baseline property value', baseline);
    }

    // Position and scale mesh to apply layout.
    mesh.position.x = x * textScale;
    mesh.position.y = y * textScale;
    // Place text slightly in front to avoid Z-fighting.
    mesh.position.z = data.zOffset;
    mesh.scale.set(textScale, -1 * textScale, textScale);
    this.geometry.computeBoundingSphere();
  },

  /**
   * Grab font from the constant.
   * Set as a method for test stubbing purposes.
   */
  lookupFont: function (key) {
    return FONTS[key];
  },

  /**
   * Update the text geometry using `three-bmfont-text.update`.
   */
  updateGeometry: function (geometry, data, font) {
    geometry.update(utils.extend({}, data, {
      font: font,
      width: computeWidth(data.wrapPixels, data.wrapCount, font.widthFactor),
      text: data.value.toString().replace(/\\n/g, '\n').replace(/\\t/g, '\t'),
      lineHeight: data.lineHeight || font.common.lineHeight
    }));
  }
});

function parseSide (side) {
  switch (side) {
    case 'back': {
      return THREE.BackSide;
    }
    case 'double': {
      return THREE.DoubleSide;
    }
    default: {
      return THREE.FrontSide;
    }
  }
}

/**
 * Coerce some data to numbers.
 * as they will be passed directly into text creation and update
 */
function coerceData (data) {
  data = utils.clone(data);
  if (data.lineHeight !== undefined) {
    data.lineHeight = parseFloat(data.lineHeight);
    if (!isFinite(data.lineHeight)) { data.lineHeight = undefined; }
  }
  if (data.width !== undefined) {
    data.width = parseFloat(data.width);
    if (!isFinite(data.width)) { data.width = undefined; }
  }
  return data;
}

/**
 * @returns {Promise}
 */
function loadFont (src, yOffset) {
  return new Promise(function (resolve, reject) {
    loadBMFont(src, function (err, font) {
      if (err) {
        error('Error loading font', src);
        reject(err);
        return;
      }

      // Fix negative Y offsets for Roboto MSDF font from tool. Experimentally determined.
      if (src.indexOf('/Roboto-msdf.json') >= 0) { yOffset = 30; }
      if (yOffset) { font.chars.map(function doOffset (ch) { ch.yoffset += yOffset; }); }

      resolve(font);
    });
  });
}

/**
 * @returns {Promise}
 */
function loadTexture (src) {
  return new Promise(function (resolve, reject) {
    new THREE.ImageLoader().load(src, function (image) {
      resolve(image);
    }, undefined, function () {
      error('Error loading font image', src);
      reject(null);
    });
  });
}

function createShader (el, shaderName, data) {
  var shader;
  var shaderObject;

  // Set up Shader.
  shaderObject = new shaders[shaderName].Shader();
  shaderObject.el = el;
  shaderObject.init(data);
  shaderObject.update(data);

  // Get material.
  shader = shaderObject.material;
  // Apparently, was not set on `init` nor `update`.
  shader.transparent = data.transparent;

  return {
    material: shader,
    shader: shaderObject
  };
}

/**
 * @todo Add more supported material properties (e.g., `visible`).
 */
function updateBaseMaterial (material, data) {
  material.side = data.side;
  material.depthTest = data.depthTest;
  material.depthWrite = data.depthWrite;
}

/**
 * Determine wrap pixel count. Either specified or by experimental fudge factor.
 * Note that experimental factor will never be correct for variable width fonts.
 */
function computeWidth (wrapPixels, wrapCount, widthFactor) {
  return wrapPixels || ((0.5 + wrapCount) * widthFactor);
}

/**
 * Compute default font width factor to use.
 */
function computeFontWidthFactor (font) {
  var sum = 0;
  var digitsum = 0;
  var digits = 0;
  font.chars.map(function (ch) {
    sum += ch.xadvance;
    if (ch.id >= 48 && ch.id <= 57) {
      digits++;
      digitsum += ch.xadvance;
    }
  });
  return digits ? digitsum / digits : sum / font.chars.length;
}

/**
 * Get or create a promise given a key and promise generator.
 * @todo Move to a utility and use in other parts of A-Frame.
 */
function PromiseCache () {
  var cache = this.cache = {};

  this.get = function (key, promiseGenerator) {
    if (key in cache) {
      return cache[key];
    }
    cache[key] = promiseGenerator();
    return cache[key];
  };
}
<|endoftext|>


<commit_before>/*
 * Copy lib images & rebase urls
 */
module.exports = function (grunt) {
    grunt.registerTask('libcssimg', 'Copy lib images & rebase urls', function () {
        var css = grunt.file.read('css/lib.css'),
            out = css,
            urls = /url\((['"]?)(.+?)\1\)/g,
            repl = {
                'jquery-ui':    /^\.\.\/lib\/jquery-ui-.+?\/images\/ui-/,
                'select2':      /^\.\.\/lib\/select2\//,
                'jquery-upl':   /^\.\.\/lib\/jquery-fileupload\/img\//
            },
            done = {},
            url,
            dest,
            to;

        grunt.file.mkdir('img/lib');

        while ((url = urls.exec(css)) !== null) {
            for (to in repl) {
                dest = url[2].replace(repl[to], 'img/lib/' + to + '-');
                if (dest !== url[2]) {
                    if (!done[dest]) {
                        grunt.file.copy(url[2].replace(/^\.\.\/lib\//, 'lib/'), dest);
                        out = out.replace(
                            new RegExp(url[0].replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), 'g'),
                            'url(' + url[1] + '../' + dest + url[1] + ')'
                        );
                        done[dest] = 1;
                    }
                    break;
                } else {
                    dest = false;
                }
            }
            if (!dest && !url[2].match(/^data:/)) {
                grunt.fail.warn('URL not handled: ' + url[2]);
            }
        }
        grunt.file.write('css/lib.css', out);
    });
};
<commit_msg>Make usage of grunt templates in libcssimg task<commit_after>/*
 * Copy lib images & rebase urls
 */
module.exports = function (grunt, option) {
    grunt.registerTask('libcssimg', 'Copy lib images & rebase urls', function () {
        var css = grunt.file.read(option.path.dest.css + '/lib.css'),
            out = css,
            urls = /url\((['"]?)(.+?)\1\)/g,
            repl = {
                'jquery-ui':    /^\.\.\/lib\/jquery-ui-.+?\/images\/ui-/,
                'select2':      /^\.\.\/lib\/select2\//,
                'jquery-upl':   /^\.\.\/lib\/jquery-fileupload\/img\//
            },
            done = {},
            url,
            dest,
            to;

        grunt.file.mkdir(option.path.dest.img + '/lib');

        while ((url = urls.exec(css)) !== null) {
            for (to in repl) {
                dest = url[2].replace(repl[to], option.path.dest.img + '/lib/' + to + '-');
                if (dest !== url[2]) {
                    if (!done[dest]) {
                        grunt.file.copy(url[2].replace(/^\.\.\/lib\//, 'lib/'), dest);
                        out = out.replace(
                            new RegExp(url[0].replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), 'g'),
                            'url(' + url[1] + '../' + dest + url[1] + ')'
                        );
                        done[dest] = 1;
                    }
                    break;
                } else {
                    dest = false;
                }
            }
            if (!dest && !url[2].match(/^data:/)) {
                grunt.fail.warn('URL not handled: ' + url[2]);
            }
        }
        grunt.file.write(option.path.dest.css + '/lib.css', out);
    });
};
<|endoftext|>


<commit_before>
var HomeMarksModal = Class.create(HomeMarksBase,{

  initialize: function() {
    this.build();
    this.mask = $('modalmask');
    this.progress = $('modal_progress');
    this.apWrapper = $('modal_html_ap-wrapper');
    this.relWrapper = $('modal_html_rel-wrapper');
    this.topShadow = $('modal_html_top');
    this.contentWrap = $('modal_html');
    this.content = $('modal_content');
    this.initButtons();
    this.queue = {position:'end', scope:'modalscope'};
  },

  build: function() {
    if (!$('modalmask')) {
      var body = $$('body').first();
      var maskHTML = DIV({id:'modalmask',style:'display:none;'},[DIV({id:'modal_progress',style:'display:none;',onclick:'location.reload();'})]);
      var modalHTML = DIV({id:'modal_html_ap-wrapper'},[
        DIV({id:'modal_html_rel-wrapper',style:'display:none;'},[
          DIV({id:'modal_html_border'},[
            DIV({id:'modal_html'},[
              DIV({id:'modal_content'}),
              DIV({id:'modal_buttons',className:'clearfix'},[
                DIV({id:'modal_button_cancel',style:'display:none;'})
              ])
            ]),
          ]),
          DIV({id:'modal_html_top'})
        ])]
      );
      body.insert({top:modalHTML});
      body.insert({top:maskHTML});
    };
  },

  show: function(content) {
    var options = Object.extend({contentFor:'misc',color:'timberwolf',showProgress:false}, arguments[1] || {});
    this.contentFor = options.contentFor;
    this.color = options.color;
    this.updateContent(content);
    this.showContentButtons();
    this.toggleMask('on');
    if (options.showProgress) { this.toggleProgress('on') };
    this.toggleObservers('on');
    this.toggleModal('on');
    // document.stopObserving('keypress', actionAreaHelper);
    // if (this.action_bar().hasClassName('barout')) { toggleActionArea('inbox'); }
  },

  hide: function(boxid) {
    this.toggleObservers('off');
    this.toggleProgress('off');
    this.toggleMask('off');
    this.toggleModal('off');
    // document.observe('keypress', actionAreaHelper);
  },

  updateContent: function(content) {
    this.topShadow.setStyle({width:this.dimensions().topWidth+'px'});
    this.contentWrap.setStyle({width:this.dimensions().contentWidth, height:this.dimensions().contentHeight});
    this.contentWrap.addClassName(this.color);
    this.content.update(content);
    this.center();
  },

  showContentButtons: function() {
    this.allButtons.invoke('hide');
    switch (this.contentFor) {
      case 'misc'     : $A([this.cancelButton]).invoke('show'); break;
      case 'box'      : $A([]); break;
      case 'bookmark' : $A([]); break;
    }
  },

  center: function() {
    this.mask.setStyle({height: this.pageSize().height + 'px'});
    this.progress.setStyle({top: (this.scroll().top + 60) + 'px'});
    var total = this.pageSize().width - this.dimensions().topWidth;
    var left = (total/2).ceil();
    if (left < 0) left = 0;
    this.apWrapper.setStyle({left:left+'px'});
  },

  dimensions: function() {
    switch (this.contentFor) {
      case 'misc'     : return { topWidth:452, contentWidth:'400px', contentHeight:'auto' };
      case 'box'      : return { topWidth:652, contentWidth:'600px', contentHeight:'300px' };
      case 'bookmark' : return { topWidth:352, contentWidth:'300px', contentHeight:'145px' };
    }
  },

  toggleMask: function(toggle) {
    var options = { duration:0.2, queue:this.queue };
    if (toggle == 'on') { this.mask.appear(Object.extend(options,{from:0.0,to:0.9})); }
    else { this.mask.fade(options); };
  },

  toggleObservers: function(toggle) {
    if (toggle == 'on') {
      this.modalResizeObserver = this.center.bindAsEventListener(this);
      Event.observe(window,'resize', this.modalResizeObserver);
      Event.observe(window,'scroll', this.modalResizeObserver);
      this.keypressObserver = this.keypress.bindAsEventListener(this);
      document.observe('keypress', this.keypressObserver);
    }
    else {
      Event.stopObserving(window,'resize',this.modalResizeObserver);
      Event.stopObserving(window,'scroll',this.modalResizeObserver);
      document.stopObserving('keypress',this.keypressObserver);
    };
  },

  toggleProgress: function(toggle) {
    var options = {duration:0.2, queue:this.queue}
    if (toggle == 'on') { this.progress.appear(options); } else { this.progress.fade(options); };
  },

  toggleModal: function(toggle) {
    var options = { duration:0.4 }; // Helps speed by removing: queue:this.queue}
    if (toggle == 'on') { this.relWrapper.slideDown(options); } else { this.relWrapper.slideUp(options); };
  },

  keypress: function(event) {
    if (event.keyCode == Event.KEY_ESC) { this.hide(); };
  },

  initButtons: function() {
    this.cancelButton = $('modal_button_cancel');
    this.cancelButton.observe('click',this.hide.bindAsEventListener(this))
    this.allButtons = $A([this.cancelButton]);
  }

  // goHere: function() {
  //   window.location.reload();
  // }

});

<commit_msg>Fixing modal JS class to set current color consistently.<commit_after>
var HomeMarksModal = Class.create(HomeMarksBase,{

  initialize: function() {
    this.build();
    this.mask = $('modalmask');
    this.progress = $('modal_progress');
    this.apWrapper = $('modal_html_ap-wrapper');
    this.relWrapper = $('modal_html_rel-wrapper');
    this.topShadow = $('modal_html_top');
    this.contentWrap = $('modal_html');
    this.content = $('modal_content');
    this.initButtons();
    this.queue = {position:'end', scope:'modalscope'};
  },

  build: function() {
    if (!$('modalmask')) {
      var body = $$('body').first();
      var maskHTML = DIV({id:'modalmask',style:'display:none;'},[DIV({id:'modal_progress',style:'display:none;',onclick:'location.reload();'})]);
      var modalHTML = DIV({id:'modal_html_ap-wrapper'},[
        DIV({id:'modal_html_rel-wrapper',style:'display:none;'},[
          DIV({id:'modal_html_border'},[
            DIV({id:'modal_html'},[
              DIV({id:'modal_content'}),
              DIV({id:'modal_buttons',className:'clearfix'},[
                DIV({id:'modal_button_cancel',style:'display:none;'})
              ])
            ]),
          ]),
          DIV({id:'modal_html_top'})
        ])]
      );
      body.insert({top:modalHTML});
      body.insert({top:maskHTML});
    };
  },

  show: function(content) {
    var options = Object.extend({contentFor:'misc',color:'timberwolf',showProgress:false}, arguments[1] || {});
    this.contentFor = options.contentFor;
    this.color = options.color;
    this.updateContent(content);
    this.showContentButtons();
    this.toggleMask('on');
    if (options.showProgress) { this.toggleProgress('on') };
    this.toggleObservers('on');
    this.toggleModal('on');
    // document.stopObserving('keypress', actionAreaHelper);
    // if (this.action_bar().hasClassName('barout')) { toggleActionArea('inbox'); }
  },

  hide: function(boxid) {
    this.toggleObservers('off');
    this.toggleProgress('off');
    this.toggleMask('off');
    this.toggleModal('off');
    // document.observe('keypress', actionAreaHelper);
  },

  updateContent: function(content) {
    this.topShadow.setStyle({width:this.dimensions().topWidth+'px'});
    this.contentWrap.setStyle({width:this.dimensions().contentWidth, height:this.dimensions().contentHeight});
    this.contentWrap.className = this.color;
    this.content.update(content);
    this.center();
  },

  showContentButtons: function() {
    this.allButtons.invoke('hide');
    switch (this.contentFor) {
      case 'misc'     : $A([this.cancelButton]).invoke('show'); break;
      case 'box'      : $A([]); break;
      case 'bookmark' : $A([]); break;
    }
  },

  center: function() {
    this.mask.setStyle({height: this.pageSize().height + 'px'});
    this.progress.setStyle({top: (this.scroll().top + 60) + 'px'});
    var total = this.pageSize().width - this.dimensions().topWidth;
    var left = (total/2).ceil();
    if (left < 0) left = 0;
    this.apWrapper.setStyle({left:left+'px'});
  },

  dimensions: function() {
    switch (this.contentFor) {
      case 'misc'     : return { topWidth:452, contentWidth:'400px', contentHeight:'auto' };
      case 'box'      : return { topWidth:652, contentWidth:'600px', contentHeight:'300px' };
      case 'bookmark' : return { topWidth:352, contentWidth:'300px', contentHeight:'145px' };
    }
  },

  toggleMask: function(toggle) {
    var options = { duration:0.2, queue:this.queue };
    if (toggle == 'on') { this.mask.appear(Object.extend(options,{from:0.0,to:0.9})); }
    else { this.mask.fade(options); };
  },

  toggleObservers: function(toggle) {
    if (toggle == 'on') {
      this.modalResizeObserver = this.center.bindAsEventListener(this);
      Event.observe(window,'resize', this.modalResizeObserver);
      Event.observe(window,'scroll', this.modalResizeObserver);
      this.keypressObserver = this.keypress.bindAsEventListener(this);
      document.observe('keypress', this.keypressObserver);
    }
    else {
      Event.stopObserving(window,'resize',this.modalResizeObserver);
      Event.stopObserving(window,'scroll',this.modalResizeObserver);
      document.stopObserving('keypress',this.keypressObserver);
    };
  },

  toggleProgress: function(toggle) {
    var options = {duration:0.2, queue:this.queue}
    if (toggle == 'on') { this.progress.appear(options); } else { this.progress.fade(options); };
  },

  toggleModal: function(toggle) {
    var options = { duration:0.4 }; // Helps speed by removing: queue:this.queue}
    if (toggle == 'on') { this.relWrapper.slideDown(options); } else { this.relWrapper.slideUp(options); };
  },

  keypress: function(event) {
    if (event.keyCode == Event.KEY_ESC) { this.hide(); };
  },

  initButtons: function() {
    this.cancelButton = $('modal_button_cancel');
    this.cancelButton.observe('click',this.hide.bindAsEventListener(this))
    this.allButtons = $A([this.cancelButton]);
  }

  // goHere: function() {
  //   window.location.reload();
  // }

});

<|endoftext|>


<commit_before>const _          = require('lodash');
const Promise    = require('bluebird');
const HttpStatus = require('http-status');
const config     = require('../../config/index');

const log = config.log;

const MAX_FLUSH_RETRY         = 5;
const MIN_RETRY_WAIT_MS       = 2000;
const MAX_RETRY_WAIT_MS       = 7000;
const BULK_REQUEST_TIMEOUT_MS = 3600000;

/**
 * Transfer constructor
 *
 * @param sourceEs
 * @param destEs
 * @returns {Transfer}
 * @constructor
 */
const Transfer = function (sourceEs, destEs) {
  const self         = this;
  let updateCallback = null;

  let flushRetryCount = 0;
  const bulkQueue     = [];
  let mutatorsByType  = {};
  let queueSummary    = {
    tick:        0,
    transferred: 0,
    scrolled:    0,
    errors:      0
  };

  self.source = sourceEs;
  self.dest = destEs;

  /**
   * Flushes any queued bulk inserts
   *
   * @returns {Promise.<TResult>}
   */
  const flushQueue = () => {
    if (bulkQueue.length > 0) {
      const bulkBody = [];
      while (bulkQueue.length > 0) {
        bulkBody.push(bulkQueue.shift());
      }

      queueSummary.tick = 0;

      return self.dest.bulk({refresh: true, body: bulkBody, requestTimeout: BULK_REQUEST_TIMEOUT_MS})
      .then((results) => {
        log.trace('response: %s', JSON.stringify(results, null, config.jsonIndent));

        if (results.errors && results.errors > 0) {
          return self.handleBulkErrors(results, bulkBody);
        } else {
          queueSummary.transferred += results.items.length;
          queueSummary.tick = results.items.length;

          if (_.isFunction(updateCallback)) {
            updateCallback(queueSummary);
          }

          log.debug('flush complete: ', queueSummary);
          return Promise.resolve();
        }
      });
    } else {
      return Promise.resolve();
    }
  };

  /**
   * Scan and scroll to get data from specific index and type in source ES.
   *
   * Apply any relevant mutations to the data.
   *
   * Queue it for sending to destination.
   *
   * Optionally provide query to restrict the data to be retrieved.
   * @param targetIndex
   * @param targetType
   * @param flushSize
   */
  self.transferData = (targetIndex, targetType, flushSize) => {
    queueSummary = {
      tick:        0,
      transferred: 0,
      scrolled:    0,
      errors:      0
    };

    if (!_.isString(targetIndex) || targetIndex.length === 0) {
      throw new Error('targetIndex must be string with length');
    } else if (!_.isString(targetType) || targetType.length === 0) {
      throw new Error('targetType must be string with length');
    }

    flushRetryCount = 0;

    const scrollAndGetData = (response) => {
      const documents = [];
      response.hits.hits.forEach((hit) => {
        documents.push(hit);
        queueSummary.scrolled++;
      });

      return putData(self.mutate(documents, 'data'), flushSize)
      .then(() => {
        if (response.hits.total !== queueSummary.scrolled) {
          return self.source.scroll({
            scroll_id: response._scroll_id,
            scroll:    '1h'
          })
          .then((inner_response) => {
            log.debug('scrolling: ', queueSummary);
            return scrollAndGetData(inner_response);
          });
        } else {
          return flushQueue()
          .then(() => {
            log.debug('transfer complete: ', queueSummary);
            return queueSummary.transferred;
          });
        }
      });
    };
    return self.source.search({
      index:  targetIndex,
      type:   targetType,
      scroll: '5m',
      size:   flushSize
    })
    .then(scrollAndGetData)
    .catch((error) => {
      log.error('Error during search: ', error);
      return Promise.reject(error);
    });
  };

  /**
   * Queue docs for upsert into dest elasticsearch
   * @param documents
   * @param flushSize
   * @returns {*}
   */
  const putData = (documents, flushSize) => {
    documents.reduce(docToBulk, bulkQueue);
    if (bulkQueue.length > flushSize) {
      return flushQueue();
    } else {
      return Promise.resolve();
    }
  };

  /**
   * Add provided document to bulk queue as upsert.
   *
   * @param queue
   * @param document
   * @returns {*}
   */
  const docToBulk = (queue, document) => {
    queue.push({
      update: {
        _index: document._index,
        _type:  document._type,
        _id:    document._id
      }
    });
    queue.push({
      doc:           document._source,
      doc_as_upsert: true
    });

    return queue;
  };

  /**
   * Add all provided templates to dest ES
   *
   * @param templates
   * @returns {*|Array}
   */
  self.putTemplates = (templates) => {
    if (!_.isArray(templates)) {
      throw new Error('templates must be an array');
    }

    return Promise.map(templates, (template) => {
      const name = template.name;
      delete template.name;

      log.info('putting template: ', name);
      return self.dest.indices.putTemplate({
        name: name,
        body: template
      })
      .catch((e) => {
        log.error('Error during put templates: %s', JSON.stringify(e));
        return Promise.reject(e);
      });
    });
  };

  /**
   * Push index configurations (not data) to destination
   *
   * @param indices
   * @returns {Array|*}
   */
  self.putIndices = (indices) => {
    if (!_.isArray(indices)) {
      throw new Error('indices must be an array');
    }

    return Promise.map(indices, (index) => {
      const name = index.name;
      delete index.name;

      log.info('creating index: ', name);

      if (!_.isString(name)) {
        log.error('bad index object: ', JSON.stringify(index, null, config.jsonIndent));
        throw new Error('name must be defined');
      }

      return self.dest.indices.create({
        index: name,
        body:  index
      }).catch((error) => {
        log.error(`Error during index (${name}) put: `, error);
        return Promise.reject(error);
      });
    });
  };

  self.getMutators = () => _.cloneDeep(mutatorsByType);
  self.setMutators = (newMutators) => mutatorsByType = newMutators;
  self.clearMutators = () => mutatorsByType = {};

  /**
   * Apply appropriate mutations each original object
   *
   * @param objs
   * @param type
   * @returns {*}
   */
  self.mutate = (objs, type) => {
    const shouldDrop    = (obj) => _.isUndefined(obj) || _.isNull(obj) || _.isEmpty(obj);
    const mutators      = mutatorsByType[type];
    const applyMutators = (obj) => mutators.reduce((result, mutator) => {
      if (shouldDrop(result)) {
        return null;
      } else if (mutator.predicate(result, mutator.arguments)) {
        return mutator.mutate(result, mutator.arguments);
      } else {
        return result;
      }
    }, obj);
    return !_.isArray(mutators) || mutators.length === 0 ? objs : objs.map(applyMutators).filter((obj) => !shouldDrop(obj));
  };

  self.setUpdateCallback = (callback) => {
    updateCallback = callback;
  };

  self.transferIndices = (indicesNames) =>
      Transfer.getIndices(self.source, indicesNames)
      .then((indices) => self.putIndices(self.mutate(indices, 'index')));

  self.transferTemplates = (templateNames) =>
      Transfer.getTemplates(self.source, templateNames)
      .then((templates) => self.putTemplates(self.mutate(templates, 'template')));

  /**
   * If any errors are detected in the transfer, they are handled here for possible recovery
   *
   * @param results
   * @param bulkBody
   * @param retryTimeout
   * @returns {*}
   */
  self.handleBulkErrors = (results, bulkBody, retryTimeout = null) => {
    const skip                = 2;
    const unrecoverableErrors = [];
    results.items.forEach((item, id) => {
      // There is only ever one key per item in the response
      const actionType = Object.keys(item)[0];

      // If there is a rejection error, we're just overloading the ingress of the destination
      // Re-add the relevant record to the queue and try again later
      if (item[actionType].error) {
        const index = id * skip;
        if (item[actionType].error.type === 'es_rejected_execution_exception') {
          log.warn('Recoverable error during batch, retrying later', item[actionType].error);
          bulkQueue.push(bulkBody[index]);
          bulkQueue.push(bulkBody[index + 1]);
        } else {
          log.error('Unrecoverable error during batch', item[actionType]);
          log.error('Source action: ', bulkBody[index]);
          log.error('Source data: ', JSON.stringify(bulkBody[index + 1]));
          unrecoverableErrors.push(item[actionType]);
        }
        queueSummary.errors++;
      } else {
        queueSummary.transferred++;
        queueSummary.tick++;
      }
    });

    if (unrecoverableErrors.length > 0) {
      return Promise.reject(JSON.stringify(unrecoverableErrors, null, config.jsonIndent));
    } else if (flushRetryCount > MAX_FLUSH_RETRY) {
      return Promise.reject('Exceeded max flush retries');
    } else {
      flushRetryCount++;

      return new Promise((resolve) => {
        const timeout = retryTimeout || _.random(MIN_RETRY_WAIT_MS, MAX_RETRY_WAIT_MS);

        log.warn(`Recoverable errors detected, sleeping ${timeout} msec and retrying...`);

        setTimeout(() => {
          log.warn(`Flush retry ${flushRetryCount}`);
          resolve(flushQueue());
        }, timeout);
      });
    }
  };

  return self;
};

/**
 * Get mappings from source ES
 *
 * @param client
 * @param targetIndices
 * @returns {Promise.<TResult>}
 */
Transfer.getIndices = (client, targetIndices) =>
    !_.isString(targetIndices) || targetIndices.length === 0 ?
        Promise.reject(new Error('targetIndices must be string with length')) :
        client.indices.get({index: targetIndices, allowNoIndices: true})
        .then((response) => _.reduce(response, (result, index, name) => {
          log.info('got index: ', name);
          return result.concat(_.assign(index, {name}));
        }, []))
        .catch((e) => {
          log.error('Error during index get: %s', JSON.stringify(e));
          return Promise.reject(e);
        });

/**
 * Returns an array of the templates found using the elasticsearch multi-index definition.
 *
 * The format is similar to an ES template GET command, but with the name nested in the element.
 *
 * @param client
 * @param targetTemplates
 * @returns {Promise.<T>}
 */
Transfer.getTemplates = (client, targetTemplates) =>
    !_.isString(targetTemplates) || targetTemplates.length === 0 ?
        Promise.reject(new Error('targetTemplates must be string with length')) :
        client.indices.getTemplate({name: targetTemplates})
        .then((templates) => _.reduce(templates, (result, template, name) => result.concat(_.assign(template, {name})), []))
        .catch((error) => {
          if (error.status === HttpStatus.NOT_FOUND) {
            log.warn('Templates asked to be copied, but none found');
            return Promise.reject('Templates asked to be copied, but none found');
          } else {
            return Promise.reject(error);
          }
        });

module.exports = Transfer;

<commit_msg>retries on ES search errors<commit_after>const _          = require('lodash');
const Promise    = require('bluebird');
const HttpStatus = require('http-status');
const config     = require('../../config/index');

const log = config.log;

const MAX_FLUSH_RETRY         = 5;
const MIN_RETRY_WAIT_MS       = 2000;
const MAX_RETRY_WAIT_MS       = 7000;
const BULK_REQUEST_TIMEOUT_MS = 3600000;

/**
 * Transfer constructor
 *
 * @param sourceEs
 * @param destEs
 * @returns {Transfer}
 * @constructor
 */
const Transfer = function (sourceEs, destEs) {
  const self         = this;
  let updateCallback = null;

  let flushRetryCount = 0;
  const bulkQueue     = [];
  let mutatorsByType  = {};
  let queueSummary    = {
    tick:        0,
    transferred: 0,
    scrolled:    0,
    errors:      0
  };

  self.source = sourceEs;
  self.dest = destEs;

  /**
   * Flushes any queued bulk inserts
   *
   * @returns {Promise.<TResult>}
   */
  const flushQueue = () => {
    if (bulkQueue.length > 0) {
      const bulkBody = [];
      while (bulkQueue.length > 0) {
        bulkBody.push(bulkQueue.shift());
      }

      queueSummary.tick = 0;

      return self.dest.bulk({refresh: true, body: bulkBody, requestTimeout: BULK_REQUEST_TIMEOUT_MS})
      .then((results) => {
        log.trace('response: %s', JSON.stringify(results, null, config.jsonIndent));

        if (results.errors && results.errors > 0) {
          return self.handleBulkErrors(results, bulkBody);
        } else {
          queueSummary.transferred += results.items.length;
          queueSummary.tick = results.items.length;

          if (_.isFunction(updateCallback)) {
            updateCallback(queueSummary);
          }

          log.debug('flush complete: ', queueSummary);
          return Promise.resolve();
        }
      });
    } else {
      return Promise.resolve();
    }
  };

  self.scroll = (response, retries = 0) =>
    self.source.scroll({
      scroll_id: response._scroll_id,
      scroll:    '1h'
    }).catch(() => retries > 3 ? Promise.reject(new Error(`can't scroll: ${response._scroll_id}`)) : self.scroll(response, retries + 1));

  self.search = (request, retries = 0) =>
    self.source.search(request)
      .catch(() => retries > 3 ? Promise.reject(new Error(`can't search: ${request}`)) : self.search(request, retries + 1));

  /**
   * Scan and scroll to get data from specific index and type in source ES.
   *
   * Apply any relevant mutations to the data.
   *
   * Queue it for sending to destination.
   *
   * Optionally provide query to restrict the data to be retrieved.
   * @param targetIndex
   * @param targetType
   * @param flushSize
   */
  self.transferData = (targetIndex, targetType, flushSize) => {
    queueSummary = {
      tick:        0,
      transferred: 0,
      scrolled:    0,
      errors:      0
    };

    if (!_.isString(targetIndex) || targetIndex.length === 0) {
      throw new Error('targetIndex must be string with length');
    } else if (!_.isString(targetType) || targetType.length === 0) {
      throw new Error('targetType must be string with length');
    }

    flushRetryCount = 0;

    const scrollAndGetData = (response) => {
      const documents = [];
      response.hits.hits.forEach((hit) => {
        documents.push(hit);
        queueSummary.scrolled++;
      });

      return putData(self.mutate(documents, 'data'), flushSize)
      .then(() => {
        if (response.hits.total !== queueSummary.scrolled) {
          return self.scroll(response)
          .then((inner_response) => {
            log.debug('scrolling: ', queueSummary);
            return scrollAndGetData(inner_response);
          });
        } else {
          return flushQueue()
          .then(() => {
            log.debug('transfer complete: ', queueSummary);
            return queueSummary.transferred;
          });
        }
      });
    };
    return self.search({
      index:  targetIndex,
      type:   targetType,
      scroll: '5m',
      size:   flushSize
    })
    .then(scrollAndGetData)
    .catch((error) => {
      log.error('Error during search: ', error);
      return Promise.reject(error);
    });
  };

  /**
   * Queue docs for upsert into dest elasticsearch
   * @param documents
   * @param flushSize
   * @returns {*}
   */
  const putData = (documents, flushSize) => {
    documents.reduce(docToBulk, bulkQueue);
    if (bulkQueue.length > flushSize) {
      return flushQueue();
    } else {
      return Promise.resolve();
    }
  };

  /**
   * Add provided document to bulk queue as upsert.
   *
   * @param queue
   * @param document
   * @returns {*}
   */
  const docToBulk = (queue, document) => {
    queue.push({
      update: {
        _index: document._index,
        _type:  document._type,
        _id:    document._id
      }
    });
    queue.push({
      doc:           document._source,
      doc_as_upsert: true
    });

    return queue;
  };

  /**
   * Add all provided templates to dest ES
   *
   * @param templates
   * @returns {*|Array}
   */
  self.putTemplates = (templates) => {
    if (!_.isArray(templates)) {
      throw new Error('templates must be an array');
    }

    return Promise.map(templates, (template) => {
      const name = template.name;
      delete template.name;

      log.info('putting template: ', name);
      return self.dest.indices.putTemplate({
        name: name,
        body: template
      })
      .catch((e) => {
        log.error('Error during put templates: %s', JSON.stringify(e));
        return Promise.reject(e);
      });
    });
  };

  /**
   * Push index configurations (not data) to destination
   *
   * @param indices
   * @returns {Array|*}
   */
  self.putIndices = (indices) => {
    if (!_.isArray(indices)) {
      throw new Error('indices must be an array');
    }

    return Promise.map(indices, (index) => {
      const name = index.name;
      delete index.name;

      log.info('creating index: ', name);

      if (!_.isString(name)) {
        log.error('bad index object: ', JSON.stringify(index, null, config.jsonIndent));
        throw new Error('name must be defined');
      }

      return self.dest.indices.create({
        index: name,
        body:  index
      }).catch((error) => {
        log.error(`Error during index (${name}) put: `, error);
        return Promise.reject(error);
      });
    });
  };

  self.getMutators = () => _.cloneDeep(mutatorsByType);
  self.setMutators = (newMutators) => mutatorsByType = newMutators;
  self.clearMutators = () => mutatorsByType = {};

  /**
   * Apply appropriate mutations each original object
   *
   * @param objs
   * @param type
   * @returns {*}
   */
  self.mutate = (objs, type) => {
    const shouldDrop    = (obj) => _.isUndefined(obj) || _.isNull(obj) || _.isEmpty(obj);
    const mutators      = mutatorsByType[type];
    const applyMutators = (obj) => mutators.reduce((result, mutator) => {
      if (shouldDrop(result)) {
        return null;
      } else if (mutator.predicate(result, mutator.arguments)) {
        return mutator.mutate(result, mutator.arguments);
      } else {
        return result;
      }
    }, obj);
    return !_.isArray(mutators) || mutators.length === 0 ? objs : objs.map(applyMutators).filter((obj) => !shouldDrop(obj));
  };

  self.setUpdateCallback = (callback) => {
    updateCallback = callback;
  };

  self.transferIndices = (indicesNames) =>
      Transfer.getIndices(self.source, indicesNames)
      .then((indices) => self.putIndices(self.mutate(indices, 'index')));

  self.transferTemplates = (templateNames) =>
      Transfer.getTemplates(self.source, templateNames)
      .then((templates) => self.putTemplates(self.mutate(templates, 'template')));

  /**
   * If any errors are detected in the transfer, they are handled here for possible recovery
   *
   * @param results
   * @param bulkBody
   * @param retryTimeout
   * @returns {*}
   */
  self.handleBulkErrors = (results, bulkBody, retryTimeout = null) => {
    const skip                = 2;
    const unrecoverableErrors = [];
    results.items.forEach((item, id) => {
      // There is only ever one key per item in the response
      const actionType = Object.keys(item)[0];

      // If there is a rejection error, we're just overloading the ingress of the destination
      // Re-add the relevant record to the queue and try again later
      if (item[actionType].error) {
        const index = id * skip;
        if (item[actionType].error.type === 'es_rejected_execution_exception') {
          log.warn('Recoverable error during batch, retrying later', item[actionType].error);
          bulkQueue.push(bulkBody[index]);
          bulkQueue.push(bulkBody[index + 1]);
        } else {
          log.error('Unrecoverable error during batch', item[actionType]);
          log.error('Source action: ', bulkBody[index]);
          log.error('Source data: ', JSON.stringify(bulkBody[index + 1]));
          unrecoverableErrors.push(item[actionType]);
        }
        queueSummary.errors++;
      } else {
        queueSummary.transferred++;
        queueSummary.tick++;
      }
    });

    if (unrecoverableErrors.length > 0) {
      return Promise.reject(JSON.stringify(unrecoverableErrors, null, config.jsonIndent));
    } else if (flushRetryCount > MAX_FLUSH_RETRY) {
      return Promise.reject('Exceeded max flush retries');
    } else {
      flushRetryCount++;

      return new Promise((resolve) => {
        const timeout = retryTimeout || _.random(MIN_RETRY_WAIT_MS, MAX_RETRY_WAIT_MS);

        log.warn(`Recoverable errors detected, sleeping ${timeout} msec and retrying...`);

        setTimeout(() => {
          log.warn(`Flush retry ${flushRetryCount}`);
          resolve(flushQueue());
        }, timeout);
      });
    }
  };

  return self;
};

/**
 * Get mappings from source ES
 *
 * @param client
 * @param targetIndices
 * @returns {Promise.<TResult>}
 */
Transfer.getIndices = (client, targetIndices) =>
    !_.isString(targetIndices) || targetIndices.length === 0 ?
        Promise.reject(new Error('targetIndices must be string with length')) :
        client.indices.get({index: targetIndices, allowNoIndices: true})
        .then((response) => _.reduce(response, (result, index, name) => {
          log.info('got index: ', name);
          return result.concat(_.assign(index, {name}));
        }, []))
        .catch((e) => {
          log.error('Error during index get: %s', JSON.stringify(e));
          return Promise.reject(e);
        });

/**
 * Returns an array of the templates found using the elasticsearch multi-index definition.
 *
 * The format is similar to an ES template GET command, but with the name nested in the element.
 *
 * @param client
 * @param targetTemplates
 * @returns {Promise.<T>}
 */
Transfer.getTemplates = (client, targetTemplates) =>
    !_.isString(targetTemplates) || targetTemplates.length === 0 ?
        Promise.reject(new Error('targetTemplates must be string with length')) :
        client.indices.getTemplate({name: targetTemplates})
        .then((templates) => _.reduce(templates, (result, template, name) => result.concat(_.assign(template, {name})), []))
        .catch((error) => {
          if (error.status === HttpStatus.NOT_FOUND) {
            log.warn('Templates asked to be copied, but none found');
            return Promise.reject('Templates asked to be copied, but none found');
          } else {
            return Promise.reject(error);
          }
        });

module.exports = Transfer;

<|endoftext|>


<commit_before>function fromDataToCharts(data, query){
    $( document ).ready(function() {

        if(data && data != "None" && data != [] && data != '[]') {

            data = data.replace(/l'/g,'l_')
            data = data.replace(/d'/g,'d_')
            data = data.replace(/D'/g,'D_')
            data = data.replace(/l'/g,'L_')
            data = data.replace(/u'/g,'"')
            data = data.replace(/'/g,'"')
            data = data.replace(/u"/g,'"')
            data = data.replace(/"/g,'"')
            data = data.replace(/[\])[(]/g, '')
            data = '['+data+']'

            json = JSON.parse(data)
            renderDashboardCharts(json, query);
        }

    });
    $(window).resize(function(){
      setChartWidth();
      dc.renderAll();
    });
}

function datePickerSet(start, end) {

    $( function() {
        $( ".datepicker" ).datepicker();
    } );

    $('#reportrange span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
    $('input#datarangepicker').daterangepicker({
        startDate: start,
        endDate: end,
        maxDate: end
        minDate: '05/01/2015'
        ranges: {
           'Today': [moment(), moment()],
           'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
           'Last 7 Days': [moment().subtract(6, 'days'), moment()],
           'Last 30 Days': [moment().subtract(29, 'days'), moment()],
           'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
           'Last Year': [moment().subtract(364, 'days'), moment().subtract(1, 'days')]
        }
    }, datePickerSet);
}
<commit_msg>fixed issue on max date<commit_after>function fromDataToCharts(data, query){
    $( document ).ready(function() {

        if(data && data != "None" && data != [] && data != '[]') {

            data = data.replace(/l'/g,'l_')
            data = data.replace(/d'/g,'d_')
            data = data.replace(/D'/g,'D_')
            data = data.replace(/l'/g,'L_')
            data = data.replace(/u'/g,'"')
            data = data.replace(/'/g,'"')
            data = data.replace(/u"/g,'"')
            data = data.replace(/"/g,'"')
            data = data.replace(/[\])[(]/g, '')
            data = '['+data+']'

            json = JSON.parse(data)
            renderDashboardCharts(json, query);
        }

    });
    $(window).resize(function(){
      setChartWidth();
      dc.renderAll();
    });
}

function datePickerSet(start, end) {

    $( function() {
        $( ".datepicker" ).datepicker();
    } );

    $('#reportrange span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY'));
    $('input#datarangepicker').daterangepicker({
        startDate: start,
        endDate: end,
        maxDate: end,
        minDate: '05/01/2015',
        ranges: {
           'Today': [moment(), moment()],
           'Yesterday': [moment().subtract(1, 'days'), moment().subtract(1, 'days')],
           'Last 7 Days': [moment().subtract(6, 'days'), moment()],
           'Last 30 Days': [moment().subtract(29, 'days'), moment()],
           'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')],
           'Last Year': [moment().subtract(364, 'days'), moment().subtract(1, 'days')]
        }
    }, datePickerSet);
}
<|endoftext|>


<commit_before>

    /*global d3, startDate, endDate, startTime, endTime, formatWeek, formatHour, numberToHumanSize, formatFixed, formatDate, formatTime, numberWithDelimiter */

    // var width = 340,
    //     height = 100
    //     margin = { top: 7, right: 10, bottom: 5, left: 5 },
    //     colors = ["#1abc9c","#2ecc71","#3498db","#9b59b6","#34495e","#95a6a6"],
    //     l = 250, // left margin
    //     r = 150, // right margin
    //     w = 400, // width of drawing area
    //     h = 24,  // bar height
    //     s = 2;   // spacing between bars

    // let data = (this.data) ? this.data : [];

    // let height = 100;

    // let startDate = new Date("2010-01-01");
    // let endDate = new Date("2020-01-01");
    // let domain = [startDate, endDate];
    // let length = d3.time.years(startDate, endDate).length;
    // let width = length * 22;

    function bar2Viz(data, div, count, format) {

      var startDate = new Date(data[0].id);
      var endDate = new Date(data[data.length - 1].id);

      var timeStamp = null;
      let formatYear = d3.time.format.utc("%Y");
      let formatHour = d3.time.format.utc("%h");
      let formatMonthYear = d3.time.format.utc("%Y-%B");
      let formatFixed = d3.format(",.0f");
      let formatTime = d3.time.format.utc("%H:%M:%S");

      let margin = { top: 10, right: 5, bottom: 20, left: 5 };

      if (format === "days") {
        var domain = [startDate, endDate];
        var length = 30;
      } else if (format === "months") {
        var domain = [startDate, endDate];
        var length = d3.time.months(startDate, endDate).length;
        width = length * 22;
      } else {
        var domain = [startTime, endTime];
        var length = 24;
      }

      var x = d3.time.scale.utc()
        .domain(domain)
        .rangeRound([0, width]);

      var y = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.sum; })])
        .rangeRound([height, 0]);

      var xAxis = d3.svg.axis()
        .scale(x)
        .tickSize(0)
        .ticks(0);

      var chart = d3.select(div).append("svg")
        .data([data])
        // .attr("width", margin.left + width + margin.right)
        // .attr("height", margin.top + height + margin.bottom)
        .attr("preserveAspectRatio","meet")
        .attr("viewBox","-30 0 350 200")
        .attr("style","position:relative")
        .attr("class", "chart barchart")
        .append("svg:g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      chart.selectAll(".bar")
        .data(data)
        .enter().append("rect")
        .attr("class", function(d) {
          if (format === "days") {
            timeStamp = Date.parse(d.key + 'T12:00:00Z');
            var weekNumber = formatWeek(new Date(timeStamp));
            return (weekNumber % 2 === 0) ? "bar relations" : "bar relations-alt";
          } else if (format === "months") {
            timeStamp = Date.parse(d.key + '-01T12:00:00Z');
            var year = formatYear(new Date(timeStamp));
            return (year % 2 === 0) ? "bar relations" : "bar relations-alt";
          } else {
            timeStamp = Date.parse(d.key + ':00:01Z');
            var hour = formatHour(new Date(timeStamp));
            return (hour >= 11 && hour <= 22) ? "bar relations-alt" : "bar relations";
          }})
        .attr("x", function(d) {
          if (format === "days") {
            return x(new Date(Date.parse(d.id + 'T12:00:00Z')));
          } else if (format === "months") {
            return x(new Date(Date.parse(d.id + '-01T12:00:00Z')));
          } else {
            return x(new Date(Date.parse(d.id + ':00:00Z')));
          }})
        .attr("width", width/length - 1)
        .attr("y", function(d) { return y(d.sum); })
        .attr("height", function(d) { return height - y(d.sum); });

      chart.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      chart.append("text")
        .attr("class", "label")
        .attr("text-anchor", "middle")
        .attr("transform", "translate(11," + (height + 18) + ")")
        .text(formatYear(startDate));

      chart.append("text")
        .attr("class", "label")
        .attr("text-anchor", "middle")
        .attr("transform", "translate(" + (width - 11) + "," + (height + 18) + ")")
        .text(formatYear(endDate));

      chart.selectAll("rect").each(
        function(d) {
          var title = null,
              dateStamp = null,
              dateString = null;

          if (count === "sfum") {
            title = numberToHumanSize(d.sum);
          } else if (count === "sum") {
            title = formatFixed(d.sum) + " counts";
          } else {
            title = formatFixed(d.sum);
          }

          if (format === "days") {
            dateStamp = Date.parse(d.id + 'T12:00:00Z');
            dateString = " on " + formatDate(new Date(dateStamp));
          } else if (format === "months") {
            dateStamp = Date.parse(d.id + '-01T12:00:00Z');
            dateString = " in " + formatMonthYear(new Date(dateStamp));
          } else {
            dateStamp = Date.parse(d.id + ':00:00Z');
            dateString = " at " + formatTime(new Date(dateStamp));
          }

          $(this).tooltip({ title: title + dateString, container: "body"});
        }
      );

      d3.select(div + "-loading").remove();

      // return chart object
      return chart;
    }

    // horizontal bar chart
    function hBarViz(data, name) {
      // make sure we have data for the chart
      if (typeof data === "undefined") {
        d3.select("#" + name + "-loading").remove();
        return;
      }

      // Works tab
      var chart = d3.select("div#" + name + "-body").append("svg")
        .attr("width", w + l + r)
        .attr("height", data.length * (h + 2 * s) + 30)
        .attr("class", "chart")
        .append("g")
        .attr("transform", "translate(" + l + "," + h + ")");

      var x = null;

      if (name === "work") {
        x = d3.scale.linear()
          .domain([0, d3.max(data, function(d) { return d[name + "_count"]; })])
          .range([0, w]);
      } else {
        x = d3.scale.log()
          .domain([0.1, d3.max(data, function(d) { return d[name + "_count"]; })])
          .range([1, w]);
      }
      var y = d3.scale.ordinal()
        .domain(data.map(function(d) { return d.title; }))
        .rangeBands([0, (h + 2 * s) * data.length]);
      var z = d3.scale.ordinal()
        .domain(data.map(function(d) { return d.group_id; }))
        .range(colors);

      chart.selectAll("text.labels")
        .data(data)
        .enter().append("a").attr("xlink:href", function(d) { return "/sources/" + d.id; }).append("text")
        .attr("x", 0)
        .attr("y", function(d) { return y(d.title) + y.rangeBand() / 2; })
        .attr("dx", 0 - l) // padding-right
        .attr("dy", ".18em") // vertical-align: middle
        .text(function(d) { return d.title; });

      chart.selectAll("rect")
        .data(data)
        .enter().append("rect")
        .attr("fill", function(d) { return z(d.group_id); })
        .attr("y", function(d) { return y(d.title); })
        .attr("height", h)
        .attr("width", function(d) { return x(d[name + "_count"]); });

      chart.selectAll("text.values")
        .data(data)
        .enter().append("text")
        .attr("x", function(d) { return x(d[name + "_count"]); })
        .attr("y", function(d) { return y(d.title) + y.rangeBand() / 2; })
        .attr("dx", 5) // padding-right
        .attr("dy", ".18em") // vertical-align: middle
        .text(function(d) { return numberWithDelimiter(d[name + "_count"]); });

      d3.select("#" + name + "-loading").remove();
    }

$(document).ready(function(e) {
    var views = gon.chart_views;
    var downloads = gon.chart_downloads;

    bar2Viz(views,"#views-chart","sum","months");
    // bar2Viz(downloads,"#downloads-chart","sum","months");
});<commit_msg>enable dowloads chart<commit_after>

    /*global d3, startDate, endDate, startTime, endTime, formatWeek, formatHour, numberToHumanSize, formatFixed, formatDate, formatTime, numberWithDelimiter */

    // var width = 340,
    //     height = 100
    //     margin = { top: 7, right: 10, bottom: 5, left: 5 },
    //     colors = ["#1abc9c","#2ecc71","#3498db","#9b59b6","#34495e","#95a6a6"],
    //     l = 250, // left margin
    //     r = 150, // right margin
    //     w = 400, // width of drawing area
    //     h = 24,  // bar height
    //     s = 2;   // spacing between bars

    // let data = (this.data) ? this.data : [];

    // let height = 100;

    // let startDate = new Date("2010-01-01");
    // let endDate = new Date("2020-01-01");
    // let domain = [startDate, endDate];
    // let length = d3.time.years(startDate, endDate).length;
    // let width = length * 22;

    function bar2Viz(data, div, count, format) {

      var startDate = new Date(data[0].id);
      var endDate = new Date(data[data.length - 1].id);

      var timeStamp = null;
      let formatYear = d3.time.format.utc("%Y");
      let formatHour = d3.time.format.utc("%h");
      let formatMonthYear = d3.time.format.utc("%Y-%B");
      let formatFixed = d3.format(",.0f");
      let formatTime = d3.time.format.utc("%H:%M:%S");

      let margin = { top: 10, right: 5, bottom: 20, left: 5 };

      if (format === "days") {
        var domain = [startDate, endDate];
        var length = 30;
      } else if (format === "months") {
        var domain = [startDate, endDate];
        var length = d3.time.months(startDate, endDate).length;
        width = length * 22;
      } else {
        var domain = [startTime, endTime];
        var length = 24;
      }

      var x = d3.time.scale.utc()
        .domain(domain)
        .rangeRound([0, width]);

      var y = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.sum; })])
        .rangeRound([height, 0]);

      var xAxis = d3.svg.axis()
        .scale(x)
        .tickSize(0)
        .ticks(0);

      var chart = d3.select(div).append("svg")
        .data([data])
        // .attr("width", margin.left + width + margin.right)
        // .attr("height", margin.top + height + margin.bottom)
        .attr("preserveAspectRatio","meet")
        .attr("viewBox","-30 0 350 200")
        .attr("style","position:relative")
        .attr("class", "chart barchart")
        .append("svg:g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      chart.selectAll(".bar")
        .data(data)
        .enter().append("rect")
        .attr("class", function(d) {
          if (format === "days") {
            timeStamp = Date.parse(d.key + 'T12:00:00Z');
            var weekNumber = formatWeek(new Date(timeStamp));
            return (weekNumber % 2 === 0) ? "bar relations" : "bar relations-alt";
          } else if (format === "months") {
            timeStamp = Date.parse(d.key + '-01T12:00:00Z');
            var year = formatYear(new Date(timeStamp));
            return (year % 2 === 0) ? "bar relations" : "bar relations-alt";
          } else {
            timeStamp = Date.parse(d.key + ':00:01Z');
            var hour = formatHour(new Date(timeStamp));
            return (hour >= 11 && hour <= 22) ? "bar relations-alt" : "bar relations";
          }})
        .attr("x", function(d) {
          if (format === "days") {
            return x(new Date(Date.parse(d.id + 'T12:00:00Z')));
          } else if (format === "months") {
            return x(new Date(Date.parse(d.id + '-01T12:00:00Z')));
          } else {
            return x(new Date(Date.parse(d.id + ':00:00Z')));
          }})
        .attr("width", width/length - 1)
        .attr("y", function(d) { return y(d.sum); })
        .attr("height", function(d) { return height - y(d.sum); });

      chart.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

      chart.append("text")
        .attr("class", "label")
        .attr("text-anchor", "middle")
        .attr("transform", "translate(11," + (height + 18) + ")")
        .text(formatYear(startDate));

      chart.append("text")
        .attr("class", "label")
        .attr("text-anchor", "middle")
        .attr("transform", "translate(" + (width - 11) + "," + (height + 18) + ")")
        .text(formatYear(endDate));

      chart.selectAll("rect").each(
        function(d) {
          var title = null,
              dateStamp = null,
              dateString = null;

          if (count === "sfum") {
            title = numberToHumanSize(d.sum);
          } else if (count === "sum") {
            title = formatFixed(d.sum) + " counts";
          } else {
            title = formatFixed(d.sum);
          }

          if (format === "days") {
            dateStamp = Date.parse(d.id + 'T12:00:00Z');
            dateString = " on " + formatDate(new Date(dateStamp));
          } else if (format === "months") {
            dateStamp = Date.parse(d.id + '-01T12:00:00Z');
            dateString = " in " + formatMonthYear(new Date(dateStamp));
          } else {
            dateStamp = Date.parse(d.id + ':00:00Z');
            dateString = " at " + formatTime(new Date(dateStamp));
          }

          $(this).tooltip({ title: title + dateString, container: "body"});
        }
      );

      d3.select(div + "-loading").remove();

      // return chart object
      return chart;
    }

    // horizontal bar chart
    function hBarViz(data, name) {
      // make sure we have data for the chart
      if (typeof data === "undefined") {
        d3.select("#" + name + "-loading").remove();
        return;
      }

      // Works tab
      var chart = d3.select("div#" + name + "-body").append("svg")
        .attr("width", w + l + r)
        .attr("height", data.length * (h + 2 * s) + 30)
        .attr("class", "chart")
        .append("g")
        .attr("transform", "translate(" + l + "," + h + ")");

      var x = null;

      if (name === "work") {
        x = d3.scale.linear()
          .domain([0, d3.max(data, function(d) { return d[name + "_count"]; })])
          .range([0, w]);
      } else {
        x = d3.scale.log()
          .domain([0.1, d3.max(data, function(d) { return d[name + "_count"]; })])
          .range([1, w]);
      }
      var y = d3.scale.ordinal()
        .domain(data.map(function(d) { return d.title; }))
        .rangeBands([0, (h + 2 * s) * data.length]);
      var z = d3.scale.ordinal()
        .domain(data.map(function(d) { return d.group_id; }))
        .range(colors);

      chart.selectAll("text.labels")
        .data(data)
        .enter().append("a").attr("xlink:href", function(d) { return "/sources/" + d.id; }).append("text")
        .attr("x", 0)
        .attr("y", function(d) { return y(d.title) + y.rangeBand() / 2; })
        .attr("dx", 0 - l) // padding-right
        .attr("dy", ".18em") // vertical-align: middle
        .text(function(d) { return d.title; });

      chart.selectAll("rect")
        .data(data)
        .enter().append("rect")
        .attr("fill", function(d) { return z(d.group_id); })
        .attr("y", function(d) { return y(d.title); })
        .attr("height", h)
        .attr("width", function(d) { return x(d[name + "_count"]); });

      chart.selectAll("text.values")
        .data(data)
        .enter().append("text")
        .attr("x", function(d) { return x(d[name + "_count"]); })
        .attr("y", function(d) { return y(d.title) + y.rangeBand() / 2; })
        .attr("dx", 5) // padding-right
        .attr("dy", ".18em") // vertical-align: middle
        .text(function(d) { return numberWithDelimiter(d[name + "_count"]); });

      d3.select("#" + name + "-loading").remove();
    }

$(document).ready(function(e) {
    var views = gon.chart_views;
    var downloads = gon.chart_downloads;

    bar2Viz(views,"#views-chart","sum","months");
    bar2Viz(downloads,"#downloads-chart","sum","months");
});<|endoftext|>


<commit_before>define(function (require, exports, module) {
  require('lib/diffDOM');

  //https://github.com/fiduswriter/diffDOM
  ;(function ($) {
    $.fn.superHtml = $.fn.html;
    $.fn.html = diffDOM
      ?function (html,diff,diffOptions) {
        if(diff && this._diffdom){
          var VDOM = this[0].cloneNode(),
            VDD = new diffDOM(diffOptions),
            VDiff;
          VDOM.innerHTML = html;
          VDiff = VDD.diff(this[0], VDOM);
          if(VDD.apply(this[0], VDiff)){
            this.eq(0).trigger('virtualdomrendered');
          }
          return this;
        }else{
          this._diffdom = 1;
          var res = this.superHtml.apply(this,arguments);
          this.eq(0).trigger('virtualdomrendered');
          return res;
        }
      }
      :$.fn.superHtml;

  })(window.Zepto);
});
<commit_msg>replace diffOptions with diff<commit_after>define(function (require, exports, module) {
  require('lib/diffDOM');

  //https://github.com/fiduswriter/diffDOM
  ;(function ($) {
    $.fn.superHtml = $.fn.html;
    $.fn.html = diffDOM
      ?function (html,diff) {
        if(diff && this._diffdom){
          var VDOM = this[0].cloneNode(),
            VDD = new diffDOM(diff),
            VDiff;
          VDOM.innerHTML = html;
          VDiff = VDD.diff(this[0], VDOM);
          if(VDD.apply(this[0], VDiff)){
            this.eq(0).trigger('virtualdomrendered');
          }
          return this;
        }else{
          this._diffdom = 1;
          var res = this.superHtml.apply(this,arguments);
          this.eq(0).trigger('virtualdomrendered');
          return res;
        }
      }
      :$.fn.superHtml;

  })(window.Zepto);
});
<|endoftext|>


<commit_before>var async = require('async');
var Blog = require('blog');
var url_parser = require('url').parse;
var dns = require('dns');
var BLOT_IP = '54.191.179.131';

var RESULT;

function capitalize (str) {
  return str[0].toUpperCase() + str.slice(1);
}

// Add new sites at the start

var sites = [

  ["www.matthewbattles.org", "Matthew Battles directs metaLAB at Harvard. Matthew’s website"],
  ["hannautkin.com", "Hanna Utkin is a filmmaker and producer from New York City. Hanna’s website"],
  ["sportsmanfc.com", "The Sportsman Flying Club is a pigeon‐racing club in Toft Hill, UK. Their website"],

  ["tf2.blot.im", "Theo Francis is a reporter at The Wall Street Journal. Theo’s blog"],
  ["fagerheimen.no", "Fagerheimen Borettslag is an apartment building in Oslo, Norway. Its website"],
  ["www.rebeccatron.com", "Rebecca Kukshtel is an engineer at Betterment in New York City. Rebecca "],

  ["www.jacobyyoung.com/about", "Jacoby Young works at Kapālama Elementary School in Hawaii. Jacoby"],
  ["www.kristianhjelle.com", "Kristian Hjelle is a designer and partner at Bakken & Bæck. Kristian"],
  ["www.alexgibson.nyc", "Alexander Gibson is an artist from New York City. Alexander’s website"],

  ["blog.aurynn.com", "Aurynn Shaw is a writer and engineer from New Zealand. Aurynn’s site"],
  ["john.pavlusoffice.com", "John Pavlus is a nonfiction writer and filmmaker based in Portland, Oregon. John"],
  ["www.theliminal.co", "The Liminal is a podcast about metaphysical speculation. Their website"],

  ["iiiiiiiii.in", "Ishtaarth Dalmia writes about internet culture and economics from Bengaluru, India. Ishtaarth"],
  ["kiefer.design", "Kiefer Sutherland is a freelance designer and art director. Kiefer’s website"],
  ["www.failuretolerated.com", "Sean McCoy is a game designer from Dallas, Texas. Sean’s site"],
  ["mrfris.by", "Stuart Frisby is the Director of Design at Booking.com. Stuart’s site"],
  ["www.querlin.com", "Querlin Ricci is a designer in Atlanta. Querlin’s website"],
];

module.exports = function (callback) {

  if (RESULT) return callback(null, RESULT);

  var result = [];

  // Only show the first 9 sites on the homepage
  sites = sites.slice(0, 9);

  async.eachSeries(sites, function(site, next){

    var parsed_URL = url_parser('https://' + site[0]);

    var domain = parsed_URL.host;
    var description = site[1].trim();
    var template;
    var is_subdomain = domain.indexOf('blot.im') !== -1;
    var url, display_url, protocol, by, custom_template, template_name;

    dns.lookup(domain, function(err, res) {

      if (!is_subdomain && res !== BLOT_IP) {
        console.log(domain, 'is not on blot it points to', res);
        return next();
      }

      if (is_subdomain) {
        by = {handle: domain.split('.blot.im').join('')};
      } else {
        by = {domain: domain};
      }

      Blog.get(by, function(err, blog){

        if (err || !blog) {
          console.log(domain, 'is not in the database');
          return next();
        }

        template = blog.template;

        protocol = domain.indexOf('blot.im') > -1 ? 'http' : 'https';

        url = protocol + '://' + site[0];
        display_url = domain.split('www.').join('');
        custom_template = template.indexOf('SITE') === -1;
        template_name = custom_template ? 'custom' : capitalize(template.split('SITE:').join(''));

        description += ' uses ';
        description += custom_template ? 'a' : 'the';
        description += ' ' + template_name + ' template.';

        // Redirect
        if (display_url === 'john.pavlusoffice.com') display_url = 'johnpavlus.com';

        result.push({
          url: url,
          first: result.length === 0,
          clear: (result.length + 1) % 3 === 0,
          display_url: display_url,
          description: description,
          domain: domain,
          template: template
        });

        next();
      });
    });

  }, function(){

    RESULT = result;
    callback(null, result);
  });
};<commit_msg>Added adam to homepage<commit_after>var async = require('async');
var Blog = require('blog');
var url_parser = require('url').parse;
var dns = require('dns');
var BLOT_IP = '54.191.179.131';

var RESULT;

function capitalize (str) {
  return str[0].toUpperCase() + str.slice(1);
}

// Add new sites at the start

var sites = [

  ["www.matthewbattles.org", "Matthew Battles directs metaLAB at Harvard. Matthew’s website"],
  ["hannautkin.com", "Hanna Utkin is a filmmaker and producer from New York City. Hanna’s website"],
  ["sportsmanfc.com", "The Sportsman Flying Club is a pigeon‐racing club in Toft Hill, UK. Their website"],

  ["asw.fyi", "Adam Wood is a writer based in Reykjavík. Adam’s website"],
  ["fagerheimen.no", "Fagerheimen Borettslag is an apartment building in Oslo, Norway. Its website"],
  ["www.rebeccatron.com", "Rebecca Kukshtel is an engineer at Betterment in New York City. Rebecca "],

  ["www.jacobyyoung.com/about", "Jacoby Young works at Kapālama Elementary School in Hawaii. Jacoby"],
  ["www.kristianhjelle.com", "Kristian Hjelle is a designer and partner at Bakken & Bæck. Kristian"],
  ["www.alexgibson.nyc", "Alexander Gibson is an artist from New York City. Alexander’s website"],

  ["tf2.blot.im", "Theo Francis is a reporter at The Wall Street Journal. Theo’s blog"],
  ["john.pavlusoffice.com", "John Pavlus is a nonfiction writer and filmmaker based in Portland, Oregon. John"],
  ["www.theliminal.co", "The Liminal is a podcast about metaphysical speculation. Their website"],

  ["blog.aurynn.com", "Aurynn Shaw is a writer and engineer from New Zealand. Aurynn’s site"],
  ["iiiiiiiii.in", "Ishtaarth Dalmia writes about internet culture and economics from Bengaluru, India. Ishtaarth"],
  ["kiefer.design", "Kiefer Sutherland is a freelance designer and art director. Kiefer’s website"],
  ["www.failuretolerated.com", "Sean McCoy is a game designer from Dallas, Texas. Sean’s site"],
  ["mrfris.by", "Stuart Frisby is the Director of Design at Booking.com. Stuart’s site"],
  ["www.querlin.com", "Querlin Ricci is a designer in Atlanta. Querlin’s website"],
];

module.exports = function (callback) {

  if (RESULT) return callback(null, RESULT);

  var result = [];

  // Only show the first 9 sites on the homepage
  sites = sites.slice(0, 12);

  async.eachSeries(sites, function(site, next){

    var parsed_URL = url_parser('https://' + site[0]);

    var domain = parsed_URL.host;
    var description = site[1].trim();
    var template;
    var is_subdomain = domain.indexOf('blot.im') !== -1;
    var url, display_url, protocol, by, custom_template, template_name;

    dns.lookup(domain, function(err, res) {

      if (!is_subdomain && res !== BLOT_IP) {
        console.log(domain, 'is not on blot it points to', res);
        return next();
      }

      if (is_subdomain) {
        by = {handle: domain.split('.blot.im').join('')};
      } else {
        by = {domain: domain};
      }

      Blog.get(by, function(err, blog){

        if (err || !blog) {
          console.log(domain, 'is not in the database');
          return next();
        }

        template = blog.template;

        protocol = domain.indexOf('blot.im') > -1 ? 'http' : 'https';

        url = protocol + '://' + site[0];
        display_url = domain.split('www.').join('');
        custom_template = template.indexOf('SITE') === -1;
        template_name = custom_template ? 'custom' : capitalize(template.split('SITE:').join(''));

        description += ' uses ';
        description += custom_template ? 'a' : 'the';
        description += ' ' + template_name + ' template.';

        // Redirect
        if (display_url === 'john.pavlusoffice.com') display_url = 'johnpavlus.com';

        result.push({
          url: url,
          first: result.length === 0,
          clear: (result.length + 1) % 3 === 0,
          display_url: display_url,
          description: description,
          domain: domain,
          template: template
        });

        next();
      });
    });

  }, function(){

    RESULT = result;
    callback(null, result);
  });
};<|endoftext|>