<commit_before>import Util from '../../core/util.js';
export class Grid extends Util {
  constructor(SL, config) {
    super(SL, config);
    this.name = 'Grid';
    this.configure();
  }
  reset() {
    super.reset();
    this.unregisterSnappers();
  }

  configure(config) {
    config = super.configure(config);

    // base definition
    this.definition = {
      offset: { x: 0, y: 0 },
      width: '100%',
      height: '100%',
      cell: {},
      cols: '+',
      rows: '+'
    };

    // use config to customize the definition
    if (config.offset) {
      this.definition.offset.x = config.offset.x || this.definition.offset.x;
      this.definition.offset.y = config.offset.y || this.definition.offset.y;
    }
    this.definition.width = config.width || this.definition.width;
    this.definition.height = config.height || this.definition.height;

    this.definition.cell.width = (config.cell && config.cell.width) || (config.cell && config.cell.size) || config.size || this.definition.cell.width;
    this.definition.cell.height = (config.cell && config.cell.height) || (config.cell && config.cell.size) || config.size || this.definition.cell.height;

    this.definition.cols = config.cols || this.definition.cols;
    this.definition.rows = config.rows || this.definition.rows;

    // set things up
    this.registerSnappers();
    this.renderGrid();

    return this.config;
  }

  getCurrentDefinition() {
    let currentDef = {};
    let Parser = this.SL.Utils.gets('ConfigParser');
    let View = this.SL.Paper.view;
    let Def = this.definition;
    let parsed;

    // parse grid offset
    currentDef.offset = { x: 0, y: 0 };
    parsed = Parser.parseNumber(Def.offset.x);
    currentDef.offset.x = parsed.value;
    if (parsed.percent) {
      currentDef.offset.x *= View.size.width;
    }
    parsed = Parser.parseNumber(Def.offset.y);
    currentDef.offset.y = parsed.value;
    if (parsed.percent) {
      currentDef.offset.y *= View.size.height;
    }

    // parse grid width
    currentDef.width = View.size.width;
    parsed = Parser.parseNumber(Def.width);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.width *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.width = parsed.value;
    }

    // parse grid height
    currentDef.height = View.size.height;
    parsed = Parser.parseNumber(Def.height);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.height *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.height = parsed.value;
    }

    // read row and col preferences
    let cols = Parser.parseNumber(Def.cols);
    let rows = Parser.parseNumber(Def.rows);
    currentDef.cell = {};

    // calculate cell width + cols
    parsed = Parser.parseNumber(Def.cell.width);
    currentDef.cell.width = parsed.value;
    if (!parsed.value) {
      if (cols.value) {
        currentDef.cell.width = currentDef.width / cols.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.width = currentDef.width * parsed.value;
    }
    currentDef.cell.width = Parser.unparseNumber(currentDef.cell.width, parsed);

    // calculate cell height + rows
    parsed = Parser.parseNumber(Def.cell.height);
    currentDef.cell.height = parsed.value;
    if (!parsed.value) {
      if (rows.value) {
        currentDef.cell.height = currentDef.height / rows.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.height = currentDef.height * parsed.value;
    }
    currentDef.cell.height = Parser.unparseNumber(currentDef.cell.height, parsed);

    // if width or height is missing, try to use the other
    currentDef.cell.width = currentDef.cell.width || currentDef.cell.height;
    currentDef.cell.height = currentDef.cell.height || currentDef.cell.width;

    // finalize number of cols
    currentDef.cols = currentDef.width / currentDef.cell.width;
    currentDef.cols = Parser.unparseNumber(currentDef.cols, cols);
    // finalize number of rows
    currentDef.rows = currentDef.height / currentDef.cell.height;
    currentDef.rows = Parser.unparseNumber(currentDef.rows, rows);

    return currentDef;
  }

  registerSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      if (!this.Snappers) {
        this.Snappers = {};
      }
      this.Snappers.point = Snap.addSnapper('point', {
        priority: 75,
        callback: (point, config) => {
          return this.snapPoint(point, config);
        }
      });
      this.Snappers.rectangle = Snap.addSnapper('rectangle', {
        priority: 75,
        callback: (rectangle, config) => {
          return this.snapRectangle(rectangle, config);
        }
      });
      this.Snappers.item = Snap.addSnapper('item', {
        priority: 75,
        callback: (item, config) => {
          return this.snapItem(item, config);
        }
      });
    }
  }
  unregisterSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (!Snap || !this.Snappers) {
      return;
    }
    if (this.Snappers.point) {
      Snap.dropSnapper('point', this.Snappers.point.id);
      this.Snappers.point = undefined;
    }
    if (Snap && this.Snappers.rectangle) {
      Snap.dropSnapper('rectangle', this.Snappers.rectangle.id);
      this.Snappers.rectangle = undefined;
    }
    if (Snap && this.Snappers.item) {
      Snap.dropSnapper('item', this.Snappers.item.id);
      this.Snappers.item = undefined;
    }
  }
  snapPoint(point, config={}) {
    if (config && config.interactive) {
      return point;
    }
    let def = this.getCurrentDefinition();
    let min = { Col: -1, Row: -1 };
    let max = { Col: -1, Row: -1 };
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      let pointMin = Snap.PointMin(new paper.Point);
      min.Col = Math.ceil(pointMin.x / def.cell.width);
      min.Row = Math.ceil(pointMin.y / def.cell.height);

      let pointMax = Snap.PointMax(new paper.Point);
      max.Col = Math.floor(pointMax.x / def.cell.width);
      max.Row = Math.floor(pointMax.y / def.cell.height);
    }
    let newPoint = new paper.Point(def.offset).add(point);
    let target = {
      Col: Math.round(newPoint.x / def.cell.width),
      Row: Math.round(newPoint.y / def.cell.height)
    };
    if (min.Col >= 0 && target.Col < min.Col) {
      target.Col = min.Col;
    }
    else if (max.Col >= 0 && target.Col > max.Col) {
      target.Col = max.Col;
    }
    if (min.Row >= 0 && target.Row < min.Row) {
      target.Row = min.Row;
    }
    else if (max.Row >= 0 && target.Row > max.Row) {
      target.Row = max.Row;
    }
    newPoint.set({
      x: (target.Col * def.cell.width) - def.offset.x,
      y: (target.Row * def.cell.height) - def.offset.y
    });
    point.set(newPoint);
    return point;
  }
  snapRectangle(rectangle, config={}) {
    if (config && config.interactive && config.size === false) {
      return rectangle;
    }
    let interactive = !!(config && config.interactive);
    let Geo = this.SL.Utils.get('Geo');
    let anchor = config.anchor || rectangle.center;
    if (config.anchorEdge && Geo) {
      anchor = Geo.Direction.edgePoint(config.anchorEdge, rectangle);
    }
    let rectangleWidth = rectangle.width;
    let rectangleHeight = rectangle.height;
    let topLeft = new paper.Point({x: rectangle.left, y: rectangle.top});
    let bottomRight = new paper.Point({x: rectangle.right, y: rectangle.bottom});
    let pointMin, pointMax;
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      pointMin = Snap.PointMin(new paper.Point);
      pointMax = Snap.PointMax(new paper.Point);
    }
    let snapped = {
      left: undefined,
      top: undefined,
      right: undefined,
      bottom: undefined
    };

    // horizontal snapping
    if (anchor.x > rectangle.center.x) {
      // anchored at right, snap right edge first
      snapped.right = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
      }
      else {
        snapped.left = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.left.x) < pointMin.x) {
        // if horizontal snapping by Right would exceed pointMin, re-snap horizontally based on Left
        snapped.left = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
        }
        else {
          snapped.right = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap left edge first
      // first try horizontal snapping based on Left
      snapped.left = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
      }
      else {
        snapped.right = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.left.x+rectangleWidth) > pointMax.x) {
        // if horizontal snapping by Left would exceed pointMax, re-snap horizontally based on Right
        snapped.right = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
        }
        else {
          snapped.left = topLeft.clone();
        }
      }
    }

    // vertical snapping
    if (anchor.y > rectangle.center.y) {
      // anchored at bottom, snap bottom edge first
      snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
      }
      else {
        snapped.top = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.top.y) < pointMin.y) {
        // if vertical snapping by Bottom would exceed pointMin, re-snap vertically based on Top
        snapped.top = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
        }
        else {
          snapped.bottom = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap top edge first
      // first try vertical snapping based on Top
      snapped.top = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
      }
      else {
        snapped.bottom = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.top.y+rectangleHeight) > pointMax.y) {
        // if vertical snapping by Top would exceed pointMax, re-snap vertically based on Bottom
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
        }
        else {
          snapped.top = topLeft.clone();
        }
      }
    }

    // apply the snapped sides
    rectangle.set({
      top: snapped.top.y,
      left: snapped.left.x,
      bottom: snapped.bottom.y,
      right: snapped.right.x
    });
    return rectangle;
  }
  snapItem(item, config={}) {
    if (config && config.interactive) {
      return item;
    }
    let rotation = item.rotation;
    if (rotation) {
      item.rotate(-rotation);
    }

    if (item.segments) {
      for (let segment of item.segments) {
        let snapped = this.snapPoint(segment.point.clone(), config);
        segment.point.set(snapped);
      }
    }
    else if (item.bounds) {
      let snapped = this.snapRectangle(item.bounds.clone(), config);
      item.bounds.set(snapped);
    }

    if (rotation) {
      item.rotate(rotation);
    }
    return item;
  }

  renderGrid(style) {
    style = style || this.config.style || Grid.DEFAULT.style;
    let def = this.getCurrentDefinition();
    let grid = this.SL.Paper.generatePaperItem({Class:'UI', Layer:'BG', Grid:'GRID', Source: this}, paper.Group);
    let n, pt1, pt2;
    let strong = this.config.strong;
    let strongStyle = $.extend({}, style,
      { strokeWidth: (style.strokeWidth ? style.strokeWidth*2.0 : 2.0) },
      this.config.strongStyle);

    // render vertical lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x, def.offset.y+def.height);
    while (n <= def.cols) {
      pt2.x = pt1.x;
      let gridLine = this.SL.Paper.generatePaperItem({Class:'UI.Grid', Layer:'GROUPED', Grid:'COL-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.x += def.cell.width;
      n++;
    }

    // render horizontal lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x+def.width, def.offset.y);
    while (n <= def.rows) {
      pt2.y = pt1.y;
      let gridLine = this.SL.Paper.generatePaperItem({Class:'UI.Grid', Layer:'GROUPED', Grid:'ROW-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.y += def.cell.height;
      n++;
    }

    return grid;
  }
}
Grid.DEFAULT = {
  style: { strokeWidth: 1.5, strokeColor: (2.0/3.0), opacity: 0.25 }
};
<commit_msg>Add UI class onto UI.Grid items<commit_after>import Util from '../../core/util.js';
export class Grid extends Util {
  constructor(SL, config) {
    super(SL, config);
    this.name = 'Grid';
    this.configure();
  }
  reset() {
    super.reset();
    this.unregisterSnappers();
  }

  configure(config) {
    config = super.configure(config);

    // base definition
    this.definition = {
      offset: { x: 0, y: 0 },
      width: '100%',
      height: '100%',
      cell: {},
      cols: '+',
      rows: '+'
    };

    // use config to customize the definition
    if (config.offset) {
      this.definition.offset.x = config.offset.x || this.definition.offset.x;
      this.definition.offset.y = config.offset.y || this.definition.offset.y;
    }
    this.definition.width = config.width || this.definition.width;
    this.definition.height = config.height || this.definition.height;

    this.definition.cell.width = (config.cell && config.cell.width) || (config.cell && config.cell.size) || config.size || this.definition.cell.width;
    this.definition.cell.height = (config.cell && config.cell.height) || (config.cell && config.cell.size) || config.size || this.definition.cell.height;

    this.definition.cols = config.cols || this.definition.cols;
    this.definition.rows = config.rows || this.definition.rows;

    // set things up
    this.registerSnappers();
    this.renderGrid();

    return this.config;
  }

  getCurrentDefinition() {
    let currentDef = {};
    let Parser = this.SL.Utils.gets('ConfigParser');
    let View = this.SL.Paper.view;
    let Def = this.definition;
    let parsed;

    // parse grid offset
    currentDef.offset = { x: 0, y: 0 };
    parsed = Parser.parseNumber(Def.offset.x);
    currentDef.offset.x = parsed.value;
    if (parsed.percent) {
      currentDef.offset.x *= View.size.width;
    }
    parsed = Parser.parseNumber(Def.offset.y);
    currentDef.offset.y = parsed.value;
    if (parsed.percent) {
      currentDef.offset.y *= View.size.height;
    }

    // parse grid width
    currentDef.width = View.size.width;
    parsed = Parser.parseNumber(Def.width);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.width *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.width = parsed.value;
    }

    // parse grid height
    currentDef.height = View.size.height;
    parsed = Parser.parseNumber(Def.height);
    if (!parsed.value) {
      parsed.value = 1.0;
      parsed.percent = true;
    }
    if (parsed.percent) {
      currentDef.height *= parsed.value;
    }
    else if (parsed.value) {
      currentDef.height = parsed.value;
    }

    // read row and col preferences
    let cols = Parser.parseNumber(Def.cols);
    let rows = Parser.parseNumber(Def.rows);
    currentDef.cell = {};

    // calculate cell width + cols
    parsed = Parser.parseNumber(Def.cell.width);
    currentDef.cell.width = parsed.value;
    if (!parsed.value) {
      if (cols.value) {
        currentDef.cell.width = currentDef.width / cols.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.width = currentDef.width * parsed.value;
    }
    currentDef.cell.width = Parser.unparseNumber(currentDef.cell.width, parsed);

    // calculate cell height + rows
    parsed = Parser.parseNumber(Def.cell.height);
    currentDef.cell.height = parsed.value;
    if (!parsed.value) {
      if (rows.value) {
        currentDef.cell.height = currentDef.height / rows.value;
      }
    }
    else if (parsed.percent) {
      currentDef.cell.height = currentDef.height * parsed.value;
    }
    currentDef.cell.height = Parser.unparseNumber(currentDef.cell.height, parsed);

    // if width or height is missing, try to use the other
    currentDef.cell.width = currentDef.cell.width || currentDef.cell.height;
    currentDef.cell.height = currentDef.cell.height || currentDef.cell.width;

    // finalize number of cols
    currentDef.cols = currentDef.width / currentDef.cell.width;
    currentDef.cols = Parser.unparseNumber(currentDef.cols, cols);
    // finalize number of rows
    currentDef.rows = currentDef.height / currentDef.cell.height;
    currentDef.rows = Parser.unparseNumber(currentDef.rows, rows);

    return currentDef;
  }

  registerSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      if (!this.Snappers) {
        this.Snappers = {};
      }
      this.Snappers.point = Snap.addSnapper('point', {
        priority: 75,
        callback: (point, config) => {
          return this.snapPoint(point, config);
        }
      });
      this.Snappers.rectangle = Snap.addSnapper('rectangle', {
        priority: 75,
        callback: (rectangle, config) => {
          return this.snapRectangle(rectangle, config);
        }
      });
      this.Snappers.item = Snap.addSnapper('item', {
        priority: 75,
        callback: (item, config) => {
          return this.snapItem(item, config);
        }
      });
    }
  }
  unregisterSnappers() {
    let Snap = this.SL.Utils.get('Snap');
    if (!Snap || !this.Snappers) {
      return;
    }
    if (this.Snappers.point) {
      Snap.dropSnapper('point', this.Snappers.point.id);
      this.Snappers.point = undefined;
    }
    if (Snap && this.Snappers.rectangle) {
      Snap.dropSnapper('rectangle', this.Snappers.rectangle.id);
      this.Snappers.rectangle = undefined;
    }
    if (Snap && this.Snappers.item) {
      Snap.dropSnapper('item', this.Snappers.item.id);
      this.Snappers.item = undefined;
    }
  }
  snapPoint(point, config={}) {
    if (config && config.interactive) {
      return point;
    }
    let def = this.getCurrentDefinition();
    let min = { Col: -1, Row: -1 };
    let max = { Col: -1, Row: -1 };
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      let pointMin = Snap.PointMin(new paper.Point);
      min.Col = Math.ceil(pointMin.x / def.cell.width);
      min.Row = Math.ceil(pointMin.y / def.cell.height);

      let pointMax = Snap.PointMax(new paper.Point);
      max.Col = Math.floor(pointMax.x / def.cell.width);
      max.Row = Math.floor(pointMax.y / def.cell.height);
    }
    let newPoint = new paper.Point(def.offset).add(point);
    let target = {
      Col: Math.round(newPoint.x / def.cell.width),
      Row: Math.round(newPoint.y / def.cell.height)
    };
    if (min.Col >= 0 && target.Col < min.Col) {
      target.Col = min.Col;
    }
    else if (max.Col >= 0 && target.Col > max.Col) {
      target.Col = max.Col;
    }
    if (min.Row >= 0 && target.Row < min.Row) {
      target.Row = min.Row;
    }
    else if (max.Row >= 0 && target.Row > max.Row) {
      target.Row = max.Row;
    }
    newPoint.set({
      x: (target.Col * def.cell.width) - def.offset.x,
      y: (target.Row * def.cell.height) - def.offset.y
    });
    point.set(newPoint);
    return point;
  }
  snapRectangle(rectangle, config={}) {
    if (config && config.interactive && config.size === false) {
      return rectangle;
    }
    let interactive = !!(config && config.interactive);
    let Geo = this.SL.Utils.get('Geo');
    let anchor = config.anchor || rectangle.center;
    if (config.anchorEdge && Geo) {
      anchor = Geo.Direction.edgePoint(config.anchorEdge, rectangle);
    }
    let rectangleWidth = rectangle.width;
    let rectangleHeight = rectangle.height;
    let topLeft = new paper.Point({x: rectangle.left, y: rectangle.top});
    let bottomRight = new paper.Point({x: rectangle.right, y: rectangle.bottom});
    let pointMin, pointMax;
    let Snap = this.SL.Utils.get('Snap');
    if (Snap) {
      pointMin = Snap.PointMin(new paper.Point);
      pointMax = Snap.PointMax(new paper.Point);
    }
    let snapped = {
      left: undefined,
      top: undefined,
      right: undefined,
      bottom: undefined
    };

    // horizontal snapping
    if (anchor.x > rectangle.center.x) {
      // anchored at right, snap right edge first
      snapped.right = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
      }
      else {
        snapped.left = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.left.x) < pointMin.x) {
        // if horizontal snapping by Right would exceed pointMin, re-snap horizontally based on Left
        snapped.left = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
        }
        else {
          snapped.right = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap left edge first
      // first try horizontal snapping based on Left
      snapped.left = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.right = this.snapPoint(new paper.Point(snapped.left.x+rectangleWidth, bottomRight.y));
      }
      else {
        snapped.right = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.left.x+rectangleWidth) > pointMax.x) {
        // if horizontal snapping by Left would exceed pointMax, re-snap horizontally based on Right
        snapped.right = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.left = this.snapPoint(new paper.Point(snapped.right.x-rectangleWidth, topLeft.y));
        }
        else {
          snapped.left = topLeft.clone();
        }
      }
    }

    // vertical snapping
    if (anchor.y > rectangle.center.y) {
      // anchored at bottom, snap bottom edge first
      snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
      if (!interactive) {
        snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
      }
      else {
        snapped.top = topLeft.clone();
      }
      if (pointMin != undefined && (snapped.top.y) < pointMin.y) {
        // if vertical snapping by Bottom would exceed pointMin, re-snap vertically based on Top
        snapped.top = this.snapPoint(new paper.Point(topLeft));
        if (!interactive) {
          snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
        }
        else {
          snapped.bottom = bottomRight.clone();
        }
      }
    }
    else {
      // otherwise, snap top edge first
      // first try vertical snapping based on Top
      snapped.top = this.snapPoint(new paper.Point(topLeft));
      if (!interactive) {
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight.x, snapped.top.y+rectangleHeight));
      }
      else {
        snapped.bottom = bottomRight.clone();
      }
      if (pointMax != undefined && (snapped.top.y+rectangleHeight) > pointMax.y) {
        // if vertical snapping by Top would exceed pointMax, re-snap vertically based on Bottom
        snapped.bottom = this.snapPoint(new paper.Point(bottomRight));
        if (!interactive) {
          snapped.top = this.snapPoint(new paper.Point(topLeft.x, snapped.bottom.y-rectangleHeight));
        }
        else {
          snapped.top = topLeft.clone();
        }
      }
    }

    // apply the snapped sides
    rectangle.set({
      top: snapped.top.y,
      left: snapped.left.x,
      bottom: snapped.bottom.y,
      right: snapped.right.x
    });
    return rectangle;
  }
  snapItem(item, config={}) {
    if (config && config.interactive) {
      return item;
    }
    let rotation = item.rotation;
    if (rotation) {
      item.rotate(-rotation);
    }

    if (item.segments) {
      for (let segment of item.segments) {
        let snapped = this.snapPoint(segment.point.clone(), config);
        segment.point.set(snapped);
      }
    }
    else if (item.bounds) {
      let snapped = this.snapRectangle(item.bounds.clone(), config);
      item.bounds.set(snapped);
    }

    if (rotation) {
      item.rotate(rotation);
    }
    return item;
  }

  renderGrid(style) {
    style = style || this.config.style || Grid.DEFAULT.style;
    let def = this.getCurrentDefinition();
    let grid = this.SL.Paper.generatePaperItem({Class:'UI', Layer:'BG', Grid:'GRID', Source: this}, paper.Group);
    let n, pt1, pt2;
    let strong = this.config.strong;
    let strongStyle = $.extend({}, style,
      { strokeWidth: (style.strokeWidth ? style.strokeWidth*2.0 : 2.0) },
      this.config.strongStyle);

    // render vertical lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x, def.offset.y+def.height);
    while (n <= def.cols) {
      pt2.x = pt1.x;
      let gridLine = this.SL.Paper.generatePaperItem({Class:['UI','UI.Grid'], Layer:'GROUPED', Grid:'COL-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.x += def.cell.width;
      n++;
    }

    // render horizontal lines
    n = 0;
    pt1 = new paper.Point(def.offset.x, def.offset.y);
    pt2 = new paper.Point(def.offset.x+def.width, def.offset.y);
    while (n <= def.rows) {
      pt2.y = pt1.y;
      let gridLine = this.SL.Paper.generatePaperItem({Class:['UI','UI.Grid'], Layer:'GROUPED', Grid:'ROW-'+n}, paper.Path.Line, pt1, pt2);
      let applyStyle = style;
      if (strong && n % strong == 0) {
        applyStyle = strongStyle;
      }
      this.SL.Paper.applyStyle(gridLine, applyStyle);
      grid.addChild(gridLine);
      pt1.y += def.cell.height;
      n++;
    }

    return grid;
  }
}
Grid.DEFAULT = {
  style: { strokeWidth: 1.5, strokeColor: (2.0/3.0), opacity: 0.25 }
};
<|endoftext|>


<commit_before>/**
 * 在uri中附加uid方便接入层操作
 */

import config from './initConfig.js'

// TODO 上报请求
export function appendOnline(uri) {
  return uri + '?__deuid=' + config.uid + '&__deappid=' + config.appId
}

export function appendEcho(uri) {
  return `${uri}?type=h520&appId=${config.appId}&uid=${config.uid}&mac=${config.mac || ''}&imei=${config.imei || ''}&idfa=${config.idfa || ''}`
}
<commit_msg>remove a todo<commit_after>/**
 * 在uri中附加uid方便接入层操作
 */

import config from './initConfig.js'

export function appendOnline(uri) {
  return uri + '?__deuid=' + config.uid + '&__deappid=' + config.appId
}

export function appendEcho(uri) {
  return `${uri}?type=h520&appId=${config.appId}&uid=${config.uid}&mac=${config.mac || ''}&imei=${config.imei || ''}&idfa=${config.idfa || ''}`
}
<|endoftext|>


<commit_before>// Get required modules
var Rules = require('./rules');
var Lang = require('./lang');
var Errors = require('./validatorerrors');
var AsyncResolvers = require('./async');

function langs() {
	require('./lang/en');
	require('./lang/ru');
}

var Validator = function(input, rules, customMessages) {
	var lang = Validator.getLang();
	this.input = input;

	this.messages = Lang._get(lang);
	this.messages._setCustom(customMessages);
	this.errors = new Errors();

	this.errorCount = 0;
	this.hasAsync = false;
	this.rules = this._parseRules(rules);
};

Validator.prototype = {

	constructor: Validator,

	/**
	 * Default language
	 *
	 * @type {string}
	 */
	lang: 'en',

	/**
	 * Numeric based rules
	 *
	 * @type {array}
	 */
	numericRules: ['integer', 'numeric'],

	/**
	 * Run validator
	 *
	 * @return {boolean} Whether it passes; true = passes, false = fails
	 */
	check: function() {
		var self = this;

		for (var attribute in this.rules) {
			var attributeRules = this.rules[attribute];
			var inputValue = this.input[attribute]; // if it doesnt exist in input, it will be undefined

			for (var i = 0, len = attributeRules.length, rule, ruleOptions; i < len; i++) {
				ruleOptions = attributeRules[i];
				rule = this.getRule(ruleOptions.name);

				if (!this._isValidatable(rule, attribute, inputValue)) {
					continue;
				}

				if (!rule.validate(inputValue, ruleOptions.value, attribute)) {
					this._addFailure(rule);
				}
			}
		}

		return this.errorCount === 0;
	},

	/**
	 * Run async validator
	 *
	 * @param {function} passes
	 * @param {function} fails
	 * @return {void}
	 */
	checkAsync: function(passes, fails) {
		var _this = this;

		var failsOne = function(rule, message) {
			_this._addFailure(rule, message);
		};

		var resolvedAll = function(allPassed) {
			if (allPassed) {
				passes();
			}
			else {
				fails();
			}
		};

		var asyncResolvers = new AsyncResolvers(failsOne, resolvedAll);

		for (var attribute in this.rules) {
			var attributeRules = this.rules[attribute];
			var inputValue = this.input[attribute]; // if it doesnt exist in input, it will be undefined

			for (var i = 0, len = attributeRules.length, rule, ruleOptions; i < len; i++) {
				ruleOptions = attributeRules[i];

				rule = this.getRule(ruleOptions.name);

				asyncResolvers.add(rule, i);

				(function(inputValue, ruleOptions, attribute, i, rule) {
					rule.validate(inputValue, ruleOptions.value, attribute, function() { asyncResolvers.resolve(i); });
				})(inputValue, ruleOptions, attribute, i, rule);
			}
		}

		asyncResolvers.enableFiring();
		asyncResolvers.fire();
	},

	/**
	 * Add failure and error message for given rule
	 *
	 * @param {Rule} rule
	 */
	_addFailure: function(rule) {
		if ( !this.errors.hasOwnProperty(rule.attribute) ) {
			this.errors[rule.attribute] = [];
		}

		var msg = this.messages.render(rule);

		this.errors[rule.attribute].push(msg);
		this.errorCount++;
	},

	/**
	 * Parse rules, normalizing format into: { attribute: [{ name: 'age', value: 3 }] }
	 *
	 * @param  {object} rules
	 * @return {object}
	 */
	_parseRules: function(rules) {
		var parsedRules = {};
		for (var attribute in rules) {
			var rulesArray = rules[attribute];
			var attributeRules = [];

			if (typeof rulesArray === 'string') {
				rulesArray = rulesArray.split('|');
			}

			for (var i = 0, len = rulesArray.length, rule; i < len; i++) {
				rule = this._extractRuleAndRuleValue(rulesArray[i]);
				if (Rules.isAsync(rule.name)) {
					this.hasAsync = true;
				}
				attributeRules.push(rule);
			}

			parsedRules[attribute] = attributeRules;
		}
		return parsedRules;
	},

	/**
	 * Extract a rule and a value from a ruleString (i.e. min:3), rule = min, value = 3
	 *
	 * @param  {string} ruleString min:3
	 * @return {object} object containing the name of the rule and value
	 */
	_extractRuleAndRuleValue: function(ruleString) {
		var rule = {}, ruleArray;

		rule.name = ruleString;

		if (ruleString.indexOf(':') >= 0) {
			ruleArray = ruleString.split(':');
			rule.name = ruleArray[0];
			rule.value = ruleArray.slice(1).join(":");
		}

		return rule;
	},

	/**
	 * Determine if attribute has any of the given rules
	 *
	 * @param  {string}  attribute
	 * @param  {array}   findRules
	 * @return {boolean}
	 */
	_hasRule: function(attribute, findRules) {
		var rules = this.rules[attribute] || [];
		for (var i = 0, len = rules.length; i < len; i++) {
			if (findRules.indexOf(rules[i].name) > -1) {
				return true;
			}
		}
		return false;
	},

	/**
	 * Determine if rule is validatable
	 *
	 * @param  {Rule}   rule
	 * @param  {string} attribute
	 * @param  {mixed}  value
	 * @return {boolean}
	 */
	_isValidatable: function(rule, attribute, value) {
		if (rule.name == 'required' || rule.name == 'accepted') {
			return true;
		}

		return this.getRule('required').validate(value);
	},

	/**
	 * Get size of value
	 *
	 * @param  {string} attribute
	 * @param  {mixed} value
	 * @return {integer|float}
	 */
	_getSize: function(attribute, value) {
		if (typeof value === 'number') {
			return value;
		}

		if (this._hasRule(attribute, this.numericRules)) {
			return parseFloat(value, 10);
		}

		return value.length;
	},

	/**
	 * Get validation rule
	 *
	 * @param  {string} name
	 * @return {Rule}
	 */
	getRule: function(name) {
		return Rules.make(name, this);
	},

	/**
	 * Determine if validation passes
	 *
	 * @param {function} passes
	 * @return {boolean}
	 */
	passes: function(passes) {
		var async = this._checkAsync('passes', passes);
		if (async) {
			return this.checkAsync(passes);
		}
		return this.check();
	},

	/**
	 * Determine if validation fails
	 *
	 * @param {function} fails
	 * @return {boolean}
	 */
	fails: function(fails) {
		var async = this._checkAsync('fails', fails);
		if (async) {
			return this.checkAsync(undefined, fails);
		}
		return !this.check();
	},

	/**
	 * Check if validation should be called asynchronously
	 *
 	 * @param  {string}   funcName Name of the caller
	 * @param  {function} callback
	 * @return {boolean}
	 */
	_checkAsync: function(funcName, callback) {
		var hasCallback = typeof callback === 'function';
		if (this.hasAsync && !hasCallback) {
			throw funcName + ' expects a callback when async rules are being tested.';
		}

		return this.hasAsync || hasCallback;
	}

};

/**
 * Set messages for language
 *
 * @param {string} lang
 * @param {object} messages
 */
Validator.setMessages = function(lang, messages) {
	Lang._set(lang, messages);
	return this;
};

/**
 * Get messages instance for given language
 *
 * @param  {string} lang
 * @return {Messages}
 */
Validator.getMessages = function(lang) {
	return Lang._get(lang);
};

/**
 * Set default language
 *
 * @param {string} lang
 */
Validator.setLang = function(lang) {
	this.prototype.lang = lang;
	return this;
};

/**
 * Get default language
 *
 * @return {string}
 */
Validator.getLang = function() {
	return this.prototype.lang;
};

/**
 * Register custom validation rule
 *
 * @param  {string}   name
 * @param  {function} fn
 * @param  {string}   message
 * @return {void}
 */
Validator.register = function(name, fn, message) {
	var lang = Validator.getLang();
	var messages = Validator.getMessages(lang);
	Rules.register(name, fn);
	messages.set(name, message);
};

/**
 * Register asynchronous validation rule
 *
 * @param  {string}   name
 * @param  {function} fn
 * @param  {string}   message
 * @return {void}
 */
Validator.registerAsync = function(name, fn, message) {
	var lang = Validator.getLang();
	var messages = Validator.getMessages(lang);
	Rules.registerAsync(name, fn);
	messages.set(name, message);
};

/**
 * Make validator
 *
 * @param  {object} input
 * @param  {object} rules
 * @param  {object} customMessages
 * @return {Validator}
 */
Validator.make = function(input, rules, customMessages) {
	return new Validator(input, rules, customMessages);
};

module.exports = Validator;<commit_msg>Stricter type check on implicit rule checking<commit_after>// Get required modules
var Rules = require('./rules');
var Lang = require('./lang');
var Errors = require('./validatorerrors');
var AsyncResolvers = require('./async');

function langs() {
	require('./lang/en');
	require('./lang/ru');
}

var Validator = function(input, rules, customMessages) {
	var lang = Validator.getLang();
	this.input = input;

	this.messages = Lang._get(lang);
	this.messages._setCustom(customMessages);
	this.errors = new Errors();

	this.errorCount = 0;
	this.hasAsync = false;
	this.rules = this._parseRules(rules);
};

Validator.prototype = {

	constructor: Validator,

	/**
	 * Default language
	 *
	 * @type {string}
	 */
	lang: 'en',

	/**
	 * Numeric based rules
	 *
	 * @type {array}
	 */
	numericRules: ['integer', 'numeric'],

	/**
	 * Run validator
	 *
	 * @return {boolean} Whether it passes; true = passes, false = fails
	 */
	check: function() {
		var self = this;

		for (var attribute in this.rules) {
			var attributeRules = this.rules[attribute];
			var inputValue = this.input[attribute]; // if it doesnt exist in input, it will be undefined

			for (var i = 0, len = attributeRules.length, rule, ruleOptions; i < len; i++) {
				ruleOptions = attributeRules[i];
				rule = this.getRule(ruleOptions.name);

				if (!this._isValidatable(rule, attribute, inputValue)) {
					continue;
				}

				if (!rule.validate(inputValue, ruleOptions.value, attribute)) {
					this._addFailure(rule);
				}
			}
		}

		return this.errorCount === 0;
	},

	/**
	 * Run async validator
	 *
	 * @param {function} passes
	 * @param {function} fails
	 * @return {void}
	 */
	checkAsync: function(passes, fails) {
		var _this = this;

		var failsOne = function(rule, message) {
			_this._addFailure(rule, message);
		};

		var resolvedAll = function(allPassed) {
			if (allPassed) {
				passes();
			}
			else {
				fails();
			}
		};

		var asyncResolvers = new AsyncResolvers(failsOne, resolvedAll);

		for (var attribute in this.rules) {
			var attributeRules = this.rules[attribute];
			var inputValue = this.input[attribute]; // if it doesnt exist in input, it will be undefined

			for (var i = 0, len = attributeRules.length, rule, ruleOptions; i < len; i++) {
				ruleOptions = attributeRules[i];

				rule = this.getRule(ruleOptions.name);

				asyncResolvers.add(rule, i);

				(function(inputValue, ruleOptions, attribute, i, rule) {
					rule.validate(inputValue, ruleOptions.value, attribute, function() { asyncResolvers.resolve(i); });
				})(inputValue, ruleOptions, attribute, i, rule);
			}
		}

		asyncResolvers.enableFiring();
		asyncResolvers.fire();
	},

	/**
	 * Add failure and error message for given rule
	 *
	 * @param {Rule} rule
	 */
	_addFailure: function(rule) {
		if ( !this.errors.hasOwnProperty(rule.attribute) ) {
			this.errors[rule.attribute] = [];
		}

		var msg = this.messages.render(rule);

		this.errors[rule.attribute].push(msg);
		this.errorCount++;
	},

	/**
	 * Parse rules, normalizing format into: { attribute: [{ name: 'age', value: 3 }] }
	 *
	 * @param  {object} rules
	 * @return {object}
	 */
	_parseRules: function(rules) {
		var parsedRules = {};
		for (var attribute in rules) {
			var rulesArray = rules[attribute];
			var attributeRules = [];

			if (typeof rulesArray === 'string') {
				rulesArray = rulesArray.split('|');
			}

			for (var i = 0, len = rulesArray.length, rule; i < len; i++) {
				rule = this._extractRuleAndRuleValue(rulesArray[i]);
				if (Rules.isAsync(rule.name)) {
					this.hasAsync = true;
				}
				attributeRules.push(rule);
			}

			parsedRules[attribute] = attributeRules;
		}
		return parsedRules;
	},

	/**
	 * Extract a rule and a value from a ruleString (i.e. min:3), rule = min, value = 3
	 *
	 * @param  {string} ruleString min:3
	 * @return {object} object containing the name of the rule and value
	 */
	_extractRuleAndRuleValue: function(ruleString) {
		var rule = {}, ruleArray;

		rule.name = ruleString;

		if (ruleString.indexOf(':') >= 0) {
			ruleArray = ruleString.split(':');
			rule.name = ruleArray[0];
			rule.value = ruleArray.slice(1).join(":");
		}

		return rule;
	},

	/**
	 * Determine if attribute has any of the given rules
	 *
	 * @param  {string}  attribute
	 * @param  {array}   findRules
	 * @return {boolean}
	 */
	_hasRule: function(attribute, findRules) {
		var rules = this.rules[attribute] || [];
		for (var i = 0, len = rules.length; i < len; i++) {
			if (findRules.indexOf(rules[i].name) > -1) {
				return true;
			}
		}
		return false;
	},

	/**
	 * Determine if rule is validatable
	 *
	 * @param  {Rule}   rule
	 * @param  {string} attribute
	 * @param  {mixed}  value
	 * @return {boolean}
	 */
	_isValidatable: function(rule, attribute, value) {
		if (rule.name === 'required' || rule.name === 'accepted') {
			return true;
		}

		return this.getRule('required').validate(value);
	},

	/**
	 * Get size of value
	 *
	 * @param  {string} attribute
	 * @param  {mixed} value
	 * @return {integer|float}
	 */
	_getSize: function(attribute, value) {
		if (typeof value === 'number') {
			return value;
		}

		if (this._hasRule(attribute, this.numericRules)) {
			return parseFloat(value, 10);
		}

		return value.length;
	},

	/**
	 * Get validation rule
	 *
	 * @param  {string} name
	 * @return {Rule}
	 */
	getRule: function(name) {
		return Rules.make(name, this);
	},

	/**
	 * Determine if validation passes
	 *
	 * @param {function} passes
	 * @return {boolean}
	 */
	passes: function(passes) {
		var async = this._checkAsync('passes', passes);
		if (async) {
			return this.checkAsync(passes);
		}
		return this.check();
	},

	/**
	 * Determine if validation fails
	 *
	 * @param {function} fails
	 * @return {boolean}
	 */
	fails: function(fails) {
		var async = this._checkAsync('fails', fails);
		if (async) {
			return this.checkAsync(undefined, fails);
		}
		return !this.check();
	},

	/**
	 * Check if validation should be called asynchronously
	 *
 	 * @param  {string}   funcName Name of the caller
	 * @param  {function} callback
	 * @return {boolean}
	 */
	_checkAsync: function(funcName, callback) {
		var hasCallback = typeof callback === 'function';
		if (this.hasAsync && !hasCallback) {
			throw funcName + ' expects a callback when async rules are being tested.';
		}

		return this.hasAsync || hasCallback;
	}

};

/**
 * Set messages for language
 *
 * @param {string} lang
 * @param {object} messages
 */
Validator.setMessages = function(lang, messages) {
	Lang._set(lang, messages);
	return this;
};

/**
 * Get messages instance for given language
 *
 * @param  {string} lang
 * @return {Messages}
 */
Validator.getMessages = function(lang) {
	return Lang._get(lang);
};

/**
 * Set default language
 *
 * @param {string} lang
 */
Validator.setLang = function(lang) {
	this.prototype.lang = lang;
	return this;
};

/**
 * Get default language
 *
 * @return {string}
 */
Validator.getLang = function() {
	return this.prototype.lang;
};

/**
 * Register custom validation rule
 *
 * @param  {string}   name
 * @param  {function} fn
 * @param  {string}   message
 * @return {void}
 */
Validator.register = function(name, fn, message) {
	var lang = Validator.getLang();
	var messages = Validator.getMessages(lang);
	Rules.register(name, fn);
	messages.set(name, message);
};

/**
 * Register asynchronous validation rule
 *
 * @param  {string}   name
 * @param  {function} fn
 * @param  {string}   message
 * @return {void}
 */
Validator.registerAsync = function(name, fn, message) {
	var lang = Validator.getLang();
	var messages = Validator.getMessages(lang);
	Rules.registerAsync(name, fn);
	messages.set(name, message);
};

/**
 * Make validator
 *
 * @param  {object} input
 * @param  {object} rules
 * @param  {object} customMessages
 * @return {Validator}
 */
Validator.make = function(input, rules, customMessages) {
	return new Validator(input, rules, customMessages);
};

module.exports = Validator;<|endoftext|>


<commit_before>/*jslint browser: true, on: true, eqeqeq: true, newcap: true, immed: true */

/*
	TODOs:

	Fo sho:
		- page controls
		- chrome frame / ExplorerCanvas
		- show loading graphic & then fade in new page if user is ahead of the last loaded page
		- check for html5 feature support where used: diveintohtml5.org/everything.html or www.modernizr.com

	Nice 2 have:
		- really need to speed up enhancements, try to use webworkers
		- remember position (use localStorage)
		- offline access
		- thumbnail browser
		- page turn animation? (http://www.cynergysystems.com/blogs/page/rickbarraza?entry=the_secret_behind_the_page)
*/

/**
 * Merge two arrays. Any properties in b will replace the same properties in
 * a. New properties from b will be added to a.
 *
 * @param a {Object}
 * @param b {Object}
 */
function merge(a, b) {

	var prop;

	if (typeof b === "undefined") { b = {}; }

	for (prop in a) {
		if (a.hasOwnProperty(prop)) {
			if (prop in b) { continue; }
			b[prop] = a[prop];
		}
	}

	return b;
}

function ComicBook(id, srcs, opts) {

	var canvas_id = id;		// canvas element id
	this.srcs = srcs;	// array of image srcs for pages

	var defaults = {
		displayMode: "double",	// single / double
		zoomMode: "fitWidth",	// manual / fitWidth
		manga: false,			// true / false
		enhance: {}
	};

	var options = merge(defaults, opts); // options array for internal use

	var no_pages = srcs.length;
	var pages = [];		// array of preloaded Image objects
	var canvas;			// the HTML5 canvas object
	var context;		// the 2d drawing context

	var buffer = 4;		// image preload buffer level
	var pointer = 0;	// the current page
	var loaded = 0;		// the amount of images that have been loaded so far

	var scale = 1;		// page zoom scale, 1 = 100%

	/**
	 * Figure out the cursor position relative to the canvas.
	 *
	 * Thanks to: Mark Pilgrim & http://diveintohtml5.org/canvas.html
	 */
	function getCursorPosition(e) {

		var x; // horizontal cursor position

		// check if page relative positions exist, if not figure them out
		if (e.pageX) {
			x = e.pageX;
		} else {
			x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
		}

		// make the position relative to the canvas
		x -= canvas.offsetLeft;

		// check if the user clicked on the left or right side
		return (x <= canvas.width / 2) ? 'left' : 'right';
	}

	/**
	 * Setup the canvas element for use throughout the class.
	 *
	 * @see #ComicBook.prototype.draw
	 * @see #ComicBook.prototype.enhance
	 */
	function init() {

		// setup canvas
		canvas = document.getElementById(canvas_id);
		context = canvas.getContext("2d");

		// add page controls
		canvas.addEventListener("click", ComicBook.prototype.navigation, false);
	}

	/*
	 * @param {String} id The canvas ID to draw the comic on.
	 * @param {Object} srcs An array of all the comic page srcs, in order
	 * @see #preload
	 */
	ComicBook.prototype.draw = function () {

		init();

		// preload images if needed
		if (pages.length !== no_pages) { this.preload(this.srcs); }
		else { this.drawPage(); }
	};

	/*
	 * Zoom the canvas
	 *
	 * @param new_scale {Number} Scale the canvas to this ratio
	 */
	ComicBook.prototype.zoom = function (new_scale) {
		options.zoomMode = "manual";
		scale = new_scale;
		if (typeof pages[pointer] === "object") { this.drawPage(); }
	};

	/**
	 * Preload all images, draw the page only after a given number have been loaded.
	 *
	 * @param srcs {Object} srcs
	 * @see #drawPage
	 */
	ComicBook.prototype.preload = function (srcs) {

		if (no_pages < buffer) { buffer = no_pages; } // don't get stuck if the buffer level is higher than the number of pages

		var i = 0; // the current page counter for this method

		// show load status panel
		if ($("#status").length === 0) {
			$(canvas).after('

');
		}

		// I am using recursion instead of a forEach loop so that the next image is
		// only loaded when the previous one has completely finished
		function preload(i) {

			var page = new Image();

			$("#status p").text("loading page " + (i + 1) + " of " + no_pages);

			page.src = srcs[i];

			page.onload = function () {

				// console.info("loaded: " + srcs[i]);

				pages[i] = this;
				loaded++;

				// there are still more pages to load, do it
				if (loaded < no_pages) {
					i++;
					preload(i);
				}

				// start rendering the comic when the buffer level has been reached
				if (loaded === buffer) { ComicBook.prototype.drawPage(); }
				if (loaded === no_pages) { $("#status").fadeOut(150).remove(); }
			};
		}

		// manually trigger the first load
		if (i === 0) { preload(i); }
	};

	/**
	 * Draw the current page in the canvas
	 *
	 * TODO: break this down into drawSinglePage() & drawDoublePage()?
	 * TODO: if the current browser doesn't have canvas support, use img tags
	 */
	ComicBook.prototype.drawPage = function() {

		var zoom_scale;
		var offsetW = 0, offsetH = 0;

		var page = pages[pointer];
		var page2 = pages[pointer + 1];

		if (typeof page !== "object") { throw "invalid page type '"+ typeof page +"'"; }

		var width = page.width;

		// reset the canvas to stop duplicate pages showing
		canvas.width = 0;
		canvas.height = 0;

		if (options.displayMode === "double") {

			// for double page spreads, factor in the width of both pages
			if (typeof page2 === "object") { width += page2.width; }

			// if this is the last page and there is no page2, still keep the canvas wide
			else { width += width; }
		}

		// update the page scale if a non manual mode has been chosen
		switch(options.zoomMode) {

			case "manual":
				document.body.style.overflowX = "auto";
				zoom_scale = (options.displayMode === "double") ? scale * 2 : scale;
				break;

			case "fitWidth":
				document.body.style.overflowX = "hidden";
				zoom_scale =  (window.innerWidth > width) ? ((window.innerWidth - width) / window.innerWidth) + 1 // scale up if the window is wider than the page
					  : window.innerWidth / width; // scale down if the window is narrower than the page
				break;

			default:throw "invalid zoomMode";
		}

		var canvas_width  = page.width * zoom_scale;
		var canvas_height = page.height * zoom_scale;

		var page_width = (options.zoomMode === "manual") ? page.width * scale : canvas_width;
		var page_height = (options.zoomMode === "manual") ? page.height * scale : canvas_height;

		canvas_height = page_height;

		// make sure the canvas is always at least full screen, even if the page is more narrow than the screen
		canvas.width = (canvas_width < window.innerWidth) ? window.innerWidth : canvas_width;
		canvas.height = (canvas_height < window.innerHeight) ? window.innerHeight : canvas_height;

		// disable scrollbars if not needed
		document.body.style.overflowX = (canvas_width < window.innerWidth) ? "hidden" : "auto";
		document.body.style.overflowY = (canvas_height < window.innerHeight) ? "hidden" : "auto";

		// work out a horizontal position that will keep the pages always centred
		if (canvas_width < window.innerWidth && options.zoomMode === "manual") {
			offsetW = (window.innerWidth - page_width) / 2;
			if (options.displayMode === "double") { offsetW = offsetW - page_width / 2; }
		}

		// work out a vertical position that will keep the pages always centred
		if (canvas_height < window.innerHeight && options.zoomMode === "manual") {
			offsetH = (window.innerHeight - page_height) / 2;
		}

		// in manga double page mode reverse the page(s)
		if (options.manga && options.displayMode === "double") {
			var tmpPage = page;
			var tmpPage2 = page2; // FIXME: check this exists before using
			page = tmpPage2;
			page2 = tmpPage;
		}

		// draw the page(s)
		context.drawImage(page, offsetW, offsetH, page_width, page_height);
		if (options.displayMode === "double" && typeof page2 === "object") { context.drawImage(page2, page_width + offsetW, offsetH, page_width, page_height); }

		// apply any image enhancements previously defined
		$.each(options.enhance, function(action, options) {
			ComicBook.prototype.enhance[action](options);
		});
	};

	/**
	 * Increment the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawNextPage = function () {
		if (pointer + 1 < pages.length) {
			pointer += (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Decrement the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawPrevPage = function () {
		if (pointer > 0) {
			pointer -= (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Apply image enhancements to the canvas.
	 *
	 * Powered by the awesome Pixastic: http://www.pixastic.com/
	 *
	 * TODO: reset & apply all image enhancements each time before applying new one
	 *
	 * TODO: abstract this into an "Enhance" object, separate from ComicBook?
	 */
	ComicBook.prototype.enhance = {

		/**
		 * Reset enhancements.
		 * This can reset a specific enhancement if the method name is passed, or
		 * it will reset all.
		 *
		 * @param method {string} the specific enhancement to reset
		 */
		reset: function (method) {
			if (!method) {
				options.enhance = {};
			} else {
				delete options.enhance[method];
			}
			ComicBook.prototype.drawPage();
		},

		/**
		 * Adjust brightness / contrast
		 *
		 * options
		 *    brightness (int) -150 to 150
		 *    contrast: (float) -1 to infinity
		 *
		 * @param {Object} options
		 */
		brightness: function (params, reset) {

			if (reset !== false) { this.reset("brightness"); }

			// merge user options with defaults
			var opts = merge({ brightness: 0, contrast: 0 }, params);

			// remember options for later
			options.enhance.brightness = opts;

			// run the enhancement
			Pixastic.process(canvas, "brightness", {
				brightness: opts.brightness,
				contrast: opts.contrast,
				legacy: true
			});

			init();
		},

		/**
		 * Force black and white
		 */
		desaturate: function () {

			options.enhance.desaturate = {};

			Pixastic.process(canvas, "desaturate", { average : false });

			init();
		},

		/**
		 * Sharpen
		 *
		 * options:
		 *   amount: number (-1 to infinity)
		 *
		 * @param {Object} options
		 */
		sharpen: function (params) {

			var opts = merge({ amount: 0 }, params);

			options.enhance.sharpen = opts;

			Pixastic.process(canvas, "sharpen", {
				amount: opts.amount
			});

			init();
		}
	};

	ComicBook.prototype.navigation = function (e) {

		if (e.type === "click") {

			var side = getCursorPosition(e);

			window.scroll(0,0); // make sure the top of the page is in view

			// western style (left to right)
			if (!options.manga) {
				if (side === "left") { ComicBook.prototype.drawPrevPage(); }
				if (side === "right") { ComicBook.prototype.drawNextPage(); }
			}
			// manga style (right to left)
			else {
				if (side === "left") { ComicBook.prototype.drawNextPage(); }
				if (side === "right") { ComicBook.prototype.drawPrevPage(); }
			}
		}
	};
}

var book;

$(document).ready(function() {

	var pages = [
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/00.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/01.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/02.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/03.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/04.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/05.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/06.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/07.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/08.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/09.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/10.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/11.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/12.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/13.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/14.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/15.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/16.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/17.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/18.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/19.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/20.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/21.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/22.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/23.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/24.jpg",
		"http://dev.justforcomics.com/get/image/?f=comics/extracted/oni_whiteout_melt_1/whiteout_melt_backcover.jpg"
	];

	var options = {
		displayMode: "double",
		zoomMode: "fitWidth",
		manga: false
	};

	book = new ComicBook("comic", pages, options);
	book.draw();
});

$(window).resize(function() {
	book.draw();
});
<commit_msg>updated todos<commit_after>/*jslint browser: true, on: true, eqeqeq: true, newcap: true, immed: true */

/*
	TODOs:

	Fo sho:
		- page controls
		- chrome frame / ExplorerCanvas / non canvas version?
		- show loading graphic & then fade in new page if user is ahead of the last loaded page
		- check for html5 feature support where used: diveintohtml5.org/everything.html or www.modernizr.com
		- remember position (use localStorage/cookie/sql)
		- really need to speed up enhancements, try to use webworkers

	Nice 2 have:
		- offline access
		- thumbnail browser
		- page turn animation? (http://www.cynergysystems.com/blogs/page/rickbarraza?entry=the_secret_behind_the_page)
*/

/**
 * Merge two arrays. Any properties in b will replace the same properties in
 * a. New properties from b will be added to a.
 *
 * @param a {Object}
 * @param b {Object}
 */
function merge(a, b) {

	var prop;

	if (typeof b === "undefined") { b = {}; }

	for (prop in a) {
		if (a.hasOwnProperty(prop)) {
			if (prop in b) { continue; }
			b[prop] = a[prop];
		}
	}

	return b;
}

function ComicBook(id, srcs, opts) {

	var canvas_id = id;		// canvas element id
	this.srcs = srcs;	// array of image srcs for pages

	var defaults = {
		displayMode: "double",	// single / double
		zoomMode: "fitWidth",	// manual / fitWidth
		manga: false,			// true / false
		enhance: {}
	};

	var options = merge(defaults, opts); // options array for internal use

	var no_pages = srcs.length;
	var pages = [];		// array of preloaded Image objects
	var canvas;			// the HTML5 canvas object
	var context;		// the 2d drawing context

	var buffer = 4;		// image preload buffer level
	var pointer = 0;	// the current page
	var loaded = 0;		// the amount of images that have been loaded so far

	var scale = 1;		// page zoom scale, 1 = 100%

	/**
	 * Figure out the cursor position relative to the canvas.
	 *
	 * Thanks to: Mark Pilgrim & http://diveintohtml5.org/canvas.html
	 */
	function getCursorPosition(e) {

		var x; // horizontal cursor position

		// check if page relative positions exist, if not figure them out
		if (e.pageX) {
			x = e.pageX;
		} else {
			x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
		}

		// make the position relative to the canvas
		x -= canvas.offsetLeft;

		// check if the user clicked on the left or right side
		return (x <= canvas.width / 2) ? 'left' : 'right';
	}

	/**
	 * Setup the canvas element for use throughout the class.
	 *
	 * @see #ComicBook.prototype.draw
	 * @see #ComicBook.prototype.enhance
	 */
	function init() {

		// setup canvas
		canvas = document.getElementById(canvas_id);
		context = canvas.getContext("2d");

		// add page controls
		canvas.addEventListener("click", ComicBook.prototype.navigation, false);
	}

	/*
	 * @param {String} id The canvas ID to draw the comic on.
	 * @param {Object} srcs An array of all the comic page srcs, in order
	 * @see #preload
	 */
	ComicBook.prototype.draw = function () {

		init();

		// preload images if needed
		if (pages.length !== no_pages) { this.preload(this.srcs); }
		else { this.drawPage(); }
	};

	/*
	 * Zoom the canvas
	 *
	 * @param new_scale {Number} Scale the canvas to this ratio
	 */
	ComicBook.prototype.zoom = function (new_scale) {
		options.zoomMode = "manual";
		scale = new_scale;
		if (typeof pages[pointer] === "object") { this.drawPage(); }
	};

	/**
	 * Preload all images, draw the page only after a given number have been loaded.
	 *
	 * @param srcs {Object} srcs
	 * @see #drawPage
	 */
	ComicBook.prototype.preload = function (srcs) {

		if (no_pages < buffer) { buffer = no_pages; } // don't get stuck if the buffer level is higher than the number of pages

		var i = 0; // the current page counter for this method

		// show load status panel
		if ($("#status").length === 0) {
			$(canvas).after('

');
		}

		// I am using recursion instead of a forEach loop so that the next image is
		// only loaded when the previous one has completely finished
		function preload(i) {

			var page = new Image();

			$("#status p").text("loading page " + (i + 1) + " of " + no_pages);

			page.src = srcs[i];

			page.onload = function () {

				// console.info("loaded: " + srcs[i]);

				pages[i] = this;
				loaded++;

				// there are still more pages to load, do it
				if (loaded < no_pages) {
					i++;
					preload(i);
				}

				// start rendering the comic when the buffer level has been reached
				if (loaded === buffer) { ComicBook.prototype.drawPage(); }
				if (loaded === no_pages) { $("#status").fadeOut(150).remove(); }
			};
		}

		// manually trigger the first load
		if (i === 0) { preload(i); }
	};

	/**
	 * Draw the current page in the canvas
	 *
	 * TODO: break this down into drawSinglePage() & drawDoublePage()?
	 * TODO: if the current browser doesn't have canvas support, use img tags
	 */
	ComicBook.prototype.drawPage = function() {

		var zoom_scale;
		var offsetW = 0, offsetH = 0;

		var page = pages[pointer];
		var page2 = pages[pointer + 1];

		if (typeof page !== "object") { throw "invalid page type '"+ typeof page +"'"; }

		var width = page.width;

		// reset the canvas to stop duplicate pages showing
		canvas.width = 0;
		canvas.height = 0;

		if (options.displayMode === "double") {

			// for double page spreads, factor in the width of both pages
			if (typeof page2 === "object") { width += page2.width; }

			// if this is the last page and there is no page2, still keep the canvas wide
			else { width += width; }
		}

		// update the page scale if a non manual mode has been chosen
		switch(options.zoomMode) {

			case "manual":
				document.body.style.overflowX = "auto";
				zoom_scale = (options.displayMode === "double") ? scale * 2 : scale;
				break;

			case "fitWidth":
				document.body.style.overflowX = "hidden";
				zoom_scale =  (window.innerWidth > width) ? ((window.innerWidth - width) / window.innerWidth) + 1 // scale up if the window is wider than the page
					  : window.innerWidth / width; // scale down if the window is narrower than the page
				break;

			default:throw "invalid zoomMode";
		}

		var canvas_width  = page.width * zoom_scale;
		var canvas_height = page.height * zoom_scale;

		var page_width = (options.zoomMode === "manual") ? page.width * scale : canvas_width;
		var page_height = (options.zoomMode === "manual") ? page.height * scale : canvas_height;

		canvas_height = page_height;

		// make sure the canvas is always at least full screen, even if the page is more narrow than the screen
		canvas.width = (canvas_width < window.innerWidth) ? window.innerWidth : canvas_width;
		canvas.height = (canvas_height < window.innerHeight) ? window.innerHeight : canvas_height;

		// disable scrollbars if not needed
		document.body.style.overflowX = (canvas_width < window.innerWidth) ? "hidden" : "auto";
		document.body.style.overflowY = (canvas_height < window.innerHeight) ? "hidden" : "auto";

		// work out a horizontal position that will keep the pages always centred
		if (canvas_width < window.innerWidth && options.zoomMode === "manual") {
			offsetW = (window.innerWidth - page_width) / 2;
			if (options.displayMode === "double") { offsetW = offsetW - page_width / 2; }
		}

		// work out a vertical position that will keep the pages always centred
		if (canvas_height < window.innerHeight && options.zoomMode === "manual") {
			offsetH = (window.innerHeight - page_height) / 2;
		}

		// in manga double page mode reverse the page(s)
		if (options.manga && options.displayMode === "double") {
			var tmpPage = page;
			var tmpPage2 = page2; // FIXME: check this exists before using
			page = tmpPage2;
			page2 = tmpPage;
		}

		// draw the page(s)
		context.drawImage(page, offsetW, offsetH, page_width, page_height);
		if (options.displayMode === "double" && typeof page2 === "object") { context.drawImage(page2, page_width + offsetW, offsetH, page_width, page_height); }

		// apply any image enhancements previously defined
		$.each(options.enhance, function(action, options) {
			ComicBook.prototype.enhance[action](options);
		});
	};

	/**
	 * Increment the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawNextPage = function () {
		if (pointer + 1 < pages.length) {
			pointer += (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Decrement the counter and draw the page in the canvas
	 *
	 * @see #drawPage
	 */
	ComicBook.prototype.drawPrevPage = function () {
		if (pointer > 0) {
			pointer -= (options.displayMode === "single") ? 1 : 2;
			this.drawPage();
		}
	};

	/**
	 * Apply image enhancements to the canvas.
	 *
	 * Powered by the awesome Pixastic: http://www.pixastic.com/
	 *
	 * TODO: reset & apply all image enhancements each time before applying new one
	 *
	 * TODO: abstract this into an "Enhance" object, separate from ComicBook?
	 */
	ComicBook.prototype.enhance = {

		/**
		 * Reset enhancements.
		 * This can reset a specific enhancement if the method name is passed, or
		 * it will reset all.
		 *
		 * @param method {string} the specific enhancement to reset
		 */
		reset: function (method) {
			if (!method) {
				options.enhance = {};
			} else {
				delete options.enhance[method];
			}
			ComicBook.prototype.drawPage();
		},

		/**
		 * Adjust brightness / contrast
		 *
		 * options
		 *    brightness (int) -150 to 150
		 *    contrast: (float) -1 to infinity
		 *
		 * @param {Object} options
		 */
		brightness: function (params, reset) {

			if (reset !== false) { this.reset("brightness"); }

			// merge user options with defaults
			var opts = merge({ brightness: 0, contrast: 0 }, params);

			// remember options for later
			options.enhance.brightness = opts;

			// run the enhancement
			Pixastic.process(canvas, "brightness", {
				brightness: opts.brightness,
				contrast: opts.contrast,
				legacy: true
			});

			init();
		},

		/**
		 * Force black and white
		 */
		desaturate: function () {

			options.enhance.desaturate = {};

			Pixastic.process(canvas, "desaturate", { average : false });

			init();
		},

		/**
		 * Sharpen
		 *
		 * options:
		 *   amount: number (-1 to infinity)
		 *
		 * @param {Object} options
		 */
		sharpen: function (params) {

			var opts = merge({ amount: 0 }, params);

			options.enhance.sharpen = opts;

			Pixastic.process(canvas, "sharpen", {
				amount: opts.amount
			});

			init();
		}
	};

	ComicBook.prototype.navigation = function (e) {

		if (e.type === "click") {

			var side = getCursorPosition(e);

			window.scroll(0,0); // make sure the top of the page is in view

			// western style (left to right)
			if (!options.manga) {
				if (side === "left") { ComicBook.prototype.drawPrevPage(); }
				if (side === "right") { ComicBook.prototype.drawNextPage(); }
			}
			// manga style (right to left)
			else {
				if (side === "left") { ComicBook.prototype.drawNextPage(); }
				if (side === "right") { ComicBook.prototype.drawPrevPage(); }
			}
		}
	};
}
<|endoftext|>


<commit_before>/**
 * HTTPError
 * @param string message
 * @param code int HTTP Code
 * @param string redirect
 */
function HTTPError(message = '', code = 500, redirect = 'back') {

  this.name = 'HTTPError'
  this.message = message
  this.stack = (new Error()).stack
  this.code = code
  this.redirect = redirect
}

export default HTTPError
<commit_msg>fix(HTTPError): Bad error stack trace<commit_after>/**
 * HTTPError
 * @param string message
 * @param code int HTTP Code
 * @param string redirect
 */
function HTTPError(message = '', code = 500, redirect = 'back') {

  this.name = 'HTTPError - '
  this.message = message
  this.stack = (new Error(message)).stack
  this.code = code
  this.redirect = redirect
}

export default HTTPError
<|endoftext|>


<commit_before>/**
 *  @overview
 *  @copyright Copyright 2012 Cloud9 IDE, Inc.
 *
 *  This product includes software developed by
 *  Cloud9 IDE, Inc (http://c9.io).
 *
 *  Author: Mike de Boer <mike@c9.io>
 **/
define('HttpError',
    /**
     * @exports HttpError
     */
        function () {
        'use strict';

        var statusCodes = {
            400: 'Bad Request',
            401: 'Unauthorized',
            402: 'Payment Required',
            403: 'Forbidden',
            404: 'Not Found',
            405: 'Method Not Allowed',
            406: 'Not Acceptable',
            407: 'Proxy Authentication Required',
            408: 'Request Timeout',
            409: 'Conflict',
            410: 'Gone',
            411: 'Length Required',
            412: 'Precondition Failed',
            413: 'Request Entity Too Large',
            414: 'Request-URI Too Long',
            415: 'Unsupported Media Type',
            416: 'Requested Range Not Satisfiable',
            417: 'Expectation Failed',
            420: 'Enhance Your Calm',
            422: 'Unprocessable Entity',
            423: 'Locked',
            424: 'Failed Dependency',
            425: 'Unordered Collection',
            426: 'Upgrade Required',
            428: 'Precondition Required',
            429: 'Too Many Requests',
            431: 'Request Header Fields Too Large',
            444: 'No Response',
            449: 'Retry With',
            499: 'Client Closed Request',
            500: 'Internal Server Error',
            501: 'Not Implemented',
            502: 'Bad Gateway',
            503: 'Service Unavailable',
            504: 'Gateway Timeout',
            505: 'HTTP Version Not Supported',
            506: 'Variant Also Negotiates',
            507: 'Insufficient Storage',
            508: 'Loop Detected',
            509: 'Bandwidth Limit Exceeded',
            510: 'Not Extended',
            511: 'Network Authentication Required'
        };

        /**
         * @constructor
         * @param {String} message
         * @param {Number} code
         * @property {String} message
         * @property {Number} code
         * @property {String} defaultMessage
         */
        function HttpError(message, code) {
            this.message = message;
            this.code = code;
            this.defaultMessage = statusCodes[code];
        }

        /**
         *
         * @returns {String}
         * Returns the stringified version of the error (i.e. the message).
         */
        HttpError.prototype.toString = function () {
            return this.message;
        };

        /**
         *
         * @returns {{code: Number, status: String, message: String}}
         * Returns a JSON object representation of the error.
         */
        HttpError.prototype.toJSON = function () {
            return {
                code: this.code,
                status: this.defaultMessage,
                message: this.message
            };
        };

        return HttpError;
    });<commit_msg>* remove module name<commit_after>/**
 *  @overview
 *  @copyright Copyright 2012 Cloud9 IDE, Inc.
 *
 *  This product includes software developed by
 *  Cloud9 IDE, Inc (http://c9.io).
 *
 *  Author: Mike de Boer <mike@c9.io>
 **/
define(
    /**
     * @exports HttpError
     */
        function () {
        'use strict';

        var statusCodes = {
            400: 'Bad Request',
            401: 'Unauthorized',
            402: 'Payment Required',
            403: 'Forbidden',
            404: 'Not Found',
            405: 'Method Not Allowed',
            406: 'Not Acceptable',
            407: 'Proxy Authentication Required',
            408: 'Request Timeout',
            409: 'Conflict',
            410: 'Gone',
            411: 'Length Required',
            412: 'Precondition Failed',
            413: 'Request Entity Too Large',
            414: 'Request-URI Too Long',
            415: 'Unsupported Media Type',
            416: 'Requested Range Not Satisfiable',
            417: 'Expectation Failed',
            420: 'Enhance Your Calm',
            422: 'Unprocessable Entity',
            423: 'Locked',
            424: 'Failed Dependency',
            425: 'Unordered Collection',
            426: 'Upgrade Required',
            428: 'Precondition Required',
            429: 'Too Many Requests',
            431: 'Request Header Fields Too Large',
            444: 'No Response',
            449: 'Retry With',
            499: 'Client Closed Request',
            500: 'Internal Server Error',
            501: 'Not Implemented',
            502: 'Bad Gateway',
            503: 'Service Unavailable',
            504: 'Gateway Timeout',
            505: 'HTTP Version Not Supported',
            506: 'Variant Also Negotiates',
            507: 'Insufficient Storage',
            508: 'Loop Detected',
            509: 'Bandwidth Limit Exceeded',
            510: 'Not Extended',
            511: 'Network Authentication Required'
        };

        /**
         * @constructor
         * @param {String} message
         * @param {Number} code
         * @property {String} message
         * @property {Number} code
         * @property {String} defaultMessage
         */
        function HttpError(message, code) {
            this.message = message;
            this.code = code;
            this.defaultMessage = statusCodes[code];
        }

        /**
         *
         * @returns {String}
         * Returns the stringified version of the error (i.e. the message).
         */
        HttpError.prototype.toString = function () {
            return this.message;
        };

        /**
         *
         * @returns {{code: Number, status: String, message: String}}
         * Returns a JSON object representation of the error.
         */
        HttpError.prototype.toJSON = function () {
            return {
                code: this.code,
                status: this.defaultMessage,
                message: this.message
            };
        };

        return HttpError;
    });<|endoftext|>


<commit_before>var Botkit = require(__dirname + '/CoreBot.js');
const Stanza = require('node-xmpp-client').Stanza;
const GroupManager = require('./JabberGroupManager.js')

function JabberBot(configuration) {
    // Create a core botkit bot
    var controller = Botkit(configuration || {});

    controller.middleware.format.use(function (bot, message, platform_message, next) {
        // clone the incoming message
        for (var k in message) {
            platform_message[k] = message[k];
        }
        next();
    });

    // customize the bot definition, which will be used when new connections
    // spawn!
    controller.defineBot(function (botkit, config) {
        var xmpp = require('simple-xmpp');

        var bot = {
            type: 'xmpp',
            botkit: botkit,
            config: config || {},
            client_jid: config.client.jid,
            utterances: botkit.utterances,
        };

        GroupManager(config, xmpp, bot, controller);

        xmpp.on('online', function (data) {
            console.log('Connected with JID: ' + data.jid.user);
            console.log('Yes, I\'m connected!');

        });

        xmpp.on('close', function () {
            console.log('connection has been closed!');
        });

        xmpp.on('error', function (err) {
            console.log(err);
        });

        xmpp.on('subscribe', function (from) {
            xmpp.acceptSubscription(from);
            console.log('accept subscribe from' + from);
            controller.trigger('subscribe', [bot, from]);
        });

        xmpp.on('unsubscribe', function (from) {
            xmpp.acceptUnsubscription(from);
        });

        function findBotJid(jid) {
            return jid === bot.client_jid;
        }

        function IsBotMentioned(message)
        {
            let mention_jids = ExtractMentionJids(message);
            if (mention_jids.find(findBotJid))
            {
                return true;
            }
            return false;
        }

        function ExtractMentionJids(message) {
            let direct_mention_reg = /href="xmpp:\s?(\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+)\s?"/ig;
            let email_reg = /\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+/i;
            let match = message.stanza.toString().match(direct_mention_reg);
            let mention_jids = [];
            if (match) {
                for (let i = 0; i < match.length; i++) {
                    let jid_match = match[i].match(email_reg);
                    if (jid_match) {
                        let jid = jid_match[0];
                        mention_jids.push(jid);
                    }
                }
            }
            return mention_jids;
        }

        controller.on('message_received', function (bot, message) {
            if (message.group == false)
            {
                if (message.user === bot.client_jid) {
                    controller.trigger('self_message', [bot, message]);
                    return false;
                } else {
                    controller.trigger('direct_message', [bot, message]);
                    return false;
                }
            }
            else
            {
                if (IsBotMentioned(message))
                {
                    if (bot.client_jid == message.from_jid)
                    {
                        controller.trigger('self_message', [bot, message]);
                    }
                    else
                    {
                        controller.trigger('direct_mention', [bot, message]);
                    }
                    return false;
                }
            }
        });

        xmpp.on('stanza', function (stanza) {
            if (stanza.is('message'))
            {
                if (stanza.attrs.type == 'chat') {
                    var body = stanza.getChild('body');
                    if (body) {
                        var message = body.getText();
                        var from = stanza.attrs.from;
                        var id = from.split('/')[0];

                        var xmpp_message = {};
                        xmpp_message.user = from;
                        xmpp_message.text = message;
                        xmpp_message.group = false;
                        xmpp_message.stanza = stanza;
                        xmpp_message.channel = 'chat',
                        controller.ingest(bot, xmpp_message, null);
                    }
                }
                else if (stanza.attrs.type == 'groupchat')
                {
                    var body = stanza.getChild('body');
                    if (body) {
                        let message = body.getText();
                        let from = stanza.attrs.from;
                        let from_split = from.split('/');
                        let conference = from_split[0];
                        let from_jid = null;
                        if (from_split.length > 1)
                            from_jid = from_split[1];
                        if (!from_jid)
                            return;

                        let history_reg = /xmlns="http:\/\/www.jabber.com\/protocol\/muc#history"/i;
                        if (history_reg.test(stanza.toString()))
                            return false;

                        var xmpp_message = {};
                        xmpp_message.user = conference;
                        xmpp_message.text = message;
                        xmpp_message.group = true;
                        xmpp_message.channel = 'groupchat';
                        xmpp_message.from_jid = from_jid;
                        xmpp_message.stanza = stanza;
                        controller.ingest(bot, xmpp_message, null);
                    }
                }
            }
        });

        bot.startConversation = function (message, cb) {
            botkit.startConversation(this, message, cb);
        };

        bot.createConversation = function (message, cb) {
            botkit.createConversation(this, message, cb);
        };

        bot.send = function (message, cb) {
            if (message.stanza)
            {
                xmpp.conn.send(message.stanza);
            }
            else
            {
                xmpp.send(message.user, message.text, message.group);
            }

            if (cb) {
                cb();
            }
        };

        bot.reply = function (src, resp, cb) {
            var msg = {};

            if (typeof (resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }
            msg.user = src.user;
            msg.channel = src.channel;
            msg.group = src.group;

            bot.say(msg, cb);
        };

        bot.findConversation = function (message, cb) {
            botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
            for (var t = 0; t < botkit.tasks.length; t++) {
                for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
                    if (
                        botkit.tasks[t].convos[c].isActive() &&
                        botkit.tasks[t].convos[c].source_message.user == message.user &&
                        botkit.tasks[t].convos[c].source_message.channel == message.channel
                    ) {
                        botkit.debug('FOUND EXISTING CONVO!');
                        cb(botkit.tasks[t].convos[c]);
                        return;
                    }
                }
            }

            cb();
        };

        xmpp.connect(config.client);
        return bot;
    });

    controller.startTicking();

    return controller;
}

module.exports = JabberBot;
<commit_msg>Make bot can receive subscribe and unsubscribe notification<commit_after>var Botkit = require(__dirname + '/CoreBot.js');
const Stanza = require('node-xmpp-client').Stanza;
const GroupManager = require('./JabberGroupManager.js')

function JabberBot(configuration) {
    // Create a core botkit bot
    var controller = Botkit(configuration || {});

    controller.middleware.format.use(function (bot, message, platform_message, next) {
        // clone the incoming message
        for (var k in message) {
            platform_message[k] = message[k];
        }
        next();
    });

    // customize the bot definition, which will be used when new connections
    // spawn!
    controller.defineBot(function (botkit, config) {
        var xmpp = require('simple-xmpp');

        var bot = {
            type: 'xmpp',
            botkit: botkit,
            config: config || {},
            client_jid: config.client.jid,
            utterances: botkit.utterances,
        };

        GroupManager(config, xmpp, bot, controller);

		function request_roster() {
            let roster_stanza = new Stanza('iq', { 'from': config.client, 'type': 'get'});
            roster_stanza.c('query', { xmlns: 'jabber:iq:roster'});
            xmpp.conn.send(roster_stanza);
        }

        xmpp.on('online', function (data) {
            console.log('Connected with JID: ' + data.jid.user);
            console.log('Yes, I\'m connected!');
			request_roster();
        });

        xmpp.on('close', function () {
            console.log('connection has been closed!');
        });

        xmpp.on('error', function (err) {
            console.log(err);
        });

        xmpp.on('subscribe', function (from) {
            xmpp.acceptSubscription(from);
            console.log('accept subscribe from' + from);
            controller.trigger('subscribe', [bot, from]);
        });

        xmpp.on('unsubscribe', function (from) {
            xmpp.acceptUnsubscription(from);
        });

        function findBotJid(jid) {
            return jid === bot.client_jid;
        }

        function IsBotMentioned(message)
        {
            let mention_jids = ExtractMentionJids(message);
            if (mention_jids.find(findBotJid))
            {
                return true;
            }
            return false;
        }

        function ExtractMentionJids(message) {
            let direct_mention_reg = /href="xmpp:\s?(\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+)\s?"/ig;
            let email_reg = /\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+/i;
            let match = message.stanza.toString().match(direct_mention_reg);
            let mention_jids = [];
            if (match) {
                for (let i = 0; i < match.length; i++) {
                    let jid_match = match[i].match(email_reg);
                    if (jid_match) {
                        let jid = jid_match[0];
                        mention_jids.push(jid);
                    }
                }
            }
            return mention_jids;
        }

        controller.on('message_received', function (bot, message) {
            if (message.group == false)
            {
                if (message.user === bot.client_jid) {
                    controller.trigger('self_message', [bot, message]);
                    return false;
                } else {
                    controller.trigger('direct_message', [bot, message]);
                    return false;
                }
            }
            else
            {
                if (IsBotMentioned(message))
                {
                    if (bot.client_jid == message.from_jid)
                    {
                        controller.trigger('self_message', [bot, message]);
                    }
                    else
                    {
                        controller.trigger('direct_mention', [bot, message]);
                    }
                    return false;
                }
            }
        });

        xmpp.on('stanza', function (stanza) {
            if (stanza.is('message'))
            {
                if (stanza.attrs.type == 'chat') {
                    var body = stanza.getChild('body');
                    if (body) {
                        var message = body.getText();
                        var from = stanza.attrs.from;
                        var id = from.split('/')[0];

                        var xmpp_message = {};
                        xmpp_message.user = from;
                        xmpp_message.text = message;
                        xmpp_message.group = false;
                        xmpp_message.stanza = stanza;
                        xmpp_message.channel = 'chat',
                        controller.ingest(bot, xmpp_message, null);
                    }
                }
                else if (stanza.attrs.type == 'groupchat')
                {
                    var body = stanza.getChild('body');
                    if (body) {
                        let message = body.getText();
                        let from = stanza.attrs.from;
                        let from_split = from.split('/');
                        let conference = from_split[0];
                        let from_jid = null;
                        if (from_split.length > 1)
                            from_jid = from_split[1];
                        if (!from_jid)
                            return;

                        let history_reg = /xmlns="http:\/\/www.jabber.com\/protocol\/muc#history"/i;
                        if (history_reg.test(stanza.toString()))
                            return false;

                        var xmpp_message = {};
                        xmpp_message.user = conference;
                        xmpp_message.text = message;
                        xmpp_message.group = true;
                        xmpp_message.channel = 'groupchat';
                        xmpp_message.from_jid = from_jid;
                        xmpp_message.stanza = stanza;
                        controller.ingest(bot, xmpp_message, null);
                    }
                }
            }
        });

        bot.startConversation = function (message, cb) {
            botkit.startConversation(this, message, cb);
        };

        bot.createConversation = function (message, cb) {
            botkit.createConversation(this, message, cb);
        };

        bot.send = function (message, cb) {
            if (message.stanza)
            {
                xmpp.conn.send(message.stanza);
            }
            else
            {
                xmpp.send(message.user, message.text, message.group);
            }

            if (cb) {
                cb();
            }
        };

        bot.reply = function (src, resp, cb) {
            var msg = {};

            if (typeof (resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }
            msg.user = src.user;
            msg.channel = src.channel;
            msg.group = src.group;

            bot.say(msg, cb);
        };

        bot.findConversation = function (message, cb) {
            botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
            for (var t = 0; t < botkit.tasks.length; t++) {
                for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
                    if (
                        botkit.tasks[t].convos[c].isActive() &&
                        botkit.tasks[t].convos[c].source_message.user == message.user &&
                        botkit.tasks[t].convos[c].source_message.channel == message.channel
                    ) {
                        botkit.debug('FOUND EXISTING CONVO!');
                        cb(botkit.tasks[t].convos[c]);
                        return;
                    }
                }
            }

            cb();
        };

        xmpp.connect(config.client);
        return bot;
    });

    controller.startTicking();

    return controller;
}

module.exports = JabberBot;
<|endoftext|>