<commit_before>/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview New (evolving) renderer.
 * Thrasos: spirit of boldness.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.provide('Blockly.thrasos');
goog.provide('Blockly.thrasos.RenderInfo');

goog.require('Blockly.blockRendering.InRowSpacer');
goog.require('Blockly.blockRendering.RenderInfo');
goog.require('Blockly.blockRendering.Types');
goog.require('Blockly.utils.object');

goog.requireType('Blockly.blockRendering.Field');
goog.requireType('Blockly.BlockSvg');
goog.requireType('Blockly.thrasos.Renderer');

/**
 * An object containing all sizing information needed to draw this block.
 *
 * This measure pass does not propagate changes to the block (although fields
 * may choose to rerender when getSize() is called).  However, calling it
 * repeatedly may be expensive.
 *
 * @param {!Blockly.thrasos.Renderer} renderer The renderer in use.
 * @param {!Blockly.BlockSvg} block The block to measure.
 * @constructor
 * @package
 * @extends {Blockly.blockRendering.RenderInfo}
 */
Blockly.thrasos.RenderInfo = function(renderer, block) {
  Blockly.thrasos.RenderInfo.superClass_.constructor.call(this, renderer, block);
};
Blockly.utils.object.inherits(Blockly.thrasos.RenderInfo,
    Blockly.blockRendering.RenderInfo);

/**
 * Get the block renderer in use.
 * @return {!Blockly.thrasos.Renderer} The block renderer in use.
 * @package
 */
Blockly.thrasos.RenderInfo.prototype.getRenderer = function() {
  return /** @type {!Blockly.thrasos.Renderer} */ (this.renderer_);
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.addElemSpacing_ = function() {
  var hasExternalInputs = false;
  for (var i = 0, row; (row = this.rows[i]); i++) {
    if (row.hasExternalInput) {
      hasExternalInputs = true;
    }
  }
  for (var i = 0, row; (row = this.rows[i]); i++) {
    var oldElems = row.elements;
    row.elements = [];
    // No spacing needed before the corner on the top row or the bottom row.
    if (row.startsWithElemSpacer()) {
      // There's a spacer before the first element in the row.
      row.elements.push(new Blockly.blockRendering.InRowSpacer(
          this.constants_, this.getInRowSpacing_(null, oldElems[0])));
    }
    for (var e = 0; e < oldElems.length - 1; e++) {
      row.elements.push(oldElems[e]);
      var spacing = this.getInRowSpacing_(oldElems[e], oldElems[e + 1]);
      row.elements.push(
          new Blockly.blockRendering.InRowSpacer(this.constants_, spacing));
    }
    row.elements.push(oldElems[oldElems.length - 1]);
    if (row.endsWithElemSpacer()) {
      var spacing = this.getInRowSpacing_(oldElems[oldElems.length - 1], null);
      if (hasExternalInputs && row.hasDummyInput) {
        spacing += this.constants_.TAB_WIDTH;
      }
      // There's a spacer after the last element in the row.
      row.elements.push(new Blockly.blockRendering.InRowSpacer(
          this.constants_, spacing));
    }
  }
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
  if (!prev) {
    // Between an editable field and the beginning of the row.
    if (next && Blockly.blockRendering.Types.isField(next) &&
        (/** @type {Blockly.blockRendering.Field} */ (next)).isEditable) {
      return this.constants_.MEDIUM_PADDING;
    }
    // Inline input at the beginning of the row.
    if (next && Blockly.blockRendering.Types.isInlineInput(next)) {
      return this.constants_.MEDIUM_LARGE_PADDING;
    }
    if (next && Blockly.blockRendering.Types.isStatementInput(next)) {
      return this.constants_.STATEMENT_INPUT_PADDING_LEFT;
    }
    // Anything else at the beginning of the row.
    return this.constants_.LARGE_PADDING;
  }

  // Spacing between a non-input and the end of the row.
  if (!Blockly.blockRendering.Types.isInput(prev) && !next) {
    // Between an editable field and the end of the row.
    if (Blockly.blockRendering.Types.isField(prev) &&
        (/** @type {Blockly.blockRendering.Field} */ (prev)).isEditable) {
      return this.constants_.MEDIUM_PADDING;
    }
    // Padding at the end of an icon-only row to make the block shape clearer.
    if (Blockly.blockRendering.Types.isIcon(prev)) {
      return (this.constants_.LARGE_PADDING * 2) + 1;
    }
    if (Blockly.blockRendering.Types.isHat(prev)) {
      return this.constants_.NO_PADDING;
    }
    // Establish a minimum width for a block with a previous or next connection.
    if (Blockly.blockRendering.Types.isPreviousOrNextConnection(prev)) {
      return this.constants_.LARGE_PADDING;
    }
    // Between rounded corner and the end of the row.
    if (Blockly.blockRendering.Types.isLeftRoundedCorner(prev)) {
      return this.constants_.MIN_BLOCK_WIDTH;
    }
    // Between a jagged edge and the end of the row.
    if (Blockly.blockRendering.Types.isJaggedEdge(prev)) {
      return this.constants_.NO_PADDING;
    }
    // Between noneditable fields and icons and the end of the row.
    return this.constants_.LARGE_PADDING;
  }

  // Between inputs and the end of the row.
  if (Blockly.blockRendering.Types.isInput(prev) && !next) {
    if (Blockly.blockRendering.Types.isExternalInput(prev)) {
      return this.constants_.NO_PADDING;
    } else if (Blockly.blockRendering.Types.isInlineInput(prev)) {
      return this.constants_.LARGE_PADDING;
    } else if (Blockly.blockRendering.Types.isStatementInput(prev)) {
      return this.constants_.NO_PADDING;
    }
  }

  // Spacing between a non-input and an input.
  if (!Blockly.blockRendering.Types.isInput(prev) &&
      next && Blockly.blockRendering.Types.isInput(next)) {
    // Between an editable field and an input.
    if (Blockly.blockRendering.Types.isField(prev) &&
        (/** @type {Blockly.blockRendering.Field} */ (prev)).isEditable) {
      if (Blockly.blockRendering.Types.isInlineInput(next)) {
        return this.constants_.SMALL_PADDING;
      } else if (Blockly.blockRendering.Types.isExternalInput(next)) {
        return this.constants_.SMALL_PADDING;
      }
    } else {
      if (Blockly.blockRendering.Types.isInlineInput(next)) {
        return this.constants_.MEDIUM_LARGE_PADDING;
      } else if (Blockly.blockRendering.Types.isExternalInput(next)) {
        return this.constants_.MEDIUM_LARGE_PADDING;
      } else if (Blockly.blockRendering.Types.isStatementInput(next)) {
        return this.constants_.LARGE_PADDING;
      }
    }
    return this.constants_.LARGE_PADDING - 1;
  }

  // Spacing between an icon and an icon or field.
  if (Blockly.blockRendering.Types.isIcon(prev) &&
      next && !Blockly.blockRendering.Types.isInput(next)) {
    return this.constants_.LARGE_PADDING;
  }

  // Spacing between an inline input and a field.
  if (Blockly.blockRendering.Types.isInlineInput(prev) &&
      next && Blockly.blockRendering.Types.isField(next)) {
    // Editable field after inline input.
    if ((/** @type {Blockly.blockRendering.Field} */ (next)).isEditable) {
      return this.constants_.MEDIUM_PADDING;
    } else {
      // Noneditable field after inline input.
      return this.constants_.LARGE_PADDING;
    }
  }

  if (Blockly.blockRendering.Types.isLeftSquareCorner(prev) && next) {
    // Spacing between a hat and a corner
    if (Blockly.blockRendering.Types.isHat(next)) {
      return this.constants_.NO_PADDING;
    }
    // Spacing between a square corner and a previous or next connection
    if (Blockly.blockRendering.Types.isPreviousConnection(next) ||
        Blockly.blockRendering.Types.isNextConnection(next)) {
      return next.notchOffset;
    }
  }

  // Spacing between a rounded corner and a previous or next connection.
  if (Blockly.blockRendering.Types.isLeftRoundedCorner(prev) && next) {
    return next.notchOffset - this.constants_.CORNER_RADIUS;
  }

  // Spacing between two fields of the same editability.
  if (Blockly.blockRendering.Types.isField(prev) &&
      next && Blockly.blockRendering.Types.isField(next) &&
      ((/** @type {Blockly.blockRendering.Field} */ (prev)).isEditable ==
          (/** @type {Blockly.blockRendering.Field} */ (next)).isEditable)) {
    return this.constants_.LARGE_PADDING;
  }

  // Spacing between anything and a jagged edge.
  if (next && Blockly.blockRendering.Types.isJaggedEdge(next)) {
    return this.constants_.LARGE_PADDING;
  }

  return this.constants_.MEDIUM_PADDING;
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.getSpacerRowHeight_ = function(
    prev, next) {
  // If we have an empty block add a spacer to increase the height.
  if (Blockly.blockRendering.Types.isTopRow(prev) &&
      Blockly.blockRendering.Types.isBottomRow(next)) {
    return this.constants_.EMPTY_BLOCK_SPACER_HEIGHT;
  }
  // Top and bottom rows act as a spacer so we don't need any extra padding.
  if (Blockly.blockRendering.Types.isTopRow(prev) ||
      Blockly.blockRendering.Types.isBottomRow(next)) {
    return this.constants_.NO_PADDING;
  }
  if (prev.hasExternalInput && next.hasExternalInput) {
    return this.constants_.LARGE_PADDING;
  }
  if (!prev.hasStatement && next.hasStatement) {
    return this.constants_.BETWEEN_STATEMENT_PADDING_Y;
  }
  if (prev.hasStatement && next.hasStatement) {
    return this.constants_.LARGE_PADDING;
  }
  if (prev.hasDummyInput || next.hasDummyInput) {
    return this.constants_.LARGE_PADDING;
  }
  return this.constants_.MEDIUM_PADDING;
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.getElemCenterline_ = function(row, elem) {
  if (Blockly.blockRendering.Types.isSpacer(elem)) {
    return row.yPos + elem.height / 2;
  }
  if (Blockly.blockRendering.Types.isBottomRow(row)) {
    var baseline = row.yPos + row.height - row.descenderHeight;
    if (Blockly.blockRendering.Types.isNextConnection(elem)) {
      return baseline + elem.height / 2;
    }
    return baseline - elem.height / 2;
  }
  if (Blockly.blockRendering.Types.isTopRow(row)) {
    if (Blockly.blockRendering.Types.isHat(elem)) {
      return row.capline - elem.height / 2;
    }
    return row.capline + elem.height / 2;
  }

  var result = row.yPos;
  if (Blockly.blockRendering.Types.isField(elem) && row.hasStatement) {
    var offset = this.constants_.TALL_INPUT_FIELD_OFFSET_Y +
        elem.height / 2;
    result += offset;
  } else {
    result += (row.height / 2);
  }
  return result;
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.finalize_ = function() {
  // Performance note: this could be combined with the draw pass, if the time
  // that this takes is excessive.  But it shouldn't be, because it only
  // accesses and sets properties that already exist on the objects.
  var widestRowWithConnectedBlocks = 0;
  var yCursor = 0;
  for (var i = 0, row; (row = this.rows[i]); i++) {
    row.yPos = yCursor;
    row.xPos = this.startX;
    yCursor += row.height;

    widestRowWithConnectedBlocks =
        Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
    // Add padding to the bottom row if block height is less than minimum
    var heightWithoutHat = yCursor - this.topRow.ascenderHeight;
    if (row == this.bottomRow &&
        heightWithoutHat < this.constants_.MIN_BLOCK_HEIGHT) {
      // But the hat height shouldn't be part of this.
      var diff = this.constants_.MIN_BLOCK_HEIGHT - heightWithoutHat;
      this.bottomRow.height += diff;
      yCursor += diff;
    }
    this.recordElemPositions_(row);
  }
  if (this.outputConnection && this.block_.nextConnection &&
      this.block_.nextConnection.isConnected()) {
    // Include width of connected block in value to stack width measurement.
    widestRowWithConnectedBlocks =
        Math.max(widestRowWithConnectedBlocks,
            this.block_.nextConnection.targetBlock().getHeightWidth().width);
  }

  this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
  this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;

  this.height = yCursor;
  this.startY = this.topRow.capline;
};
<commit_msg>Migrate core/renderers/thrasos/info.js to ES6 const/let<commit_after>/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview New (evolving) renderer.
 * Thrasos: spirit of boldness.
 * @author fenichel@google.com (Rachel Fenichel)
 */
'use strict';

goog.provide('Blockly.thrasos');
goog.provide('Blockly.thrasos.RenderInfo');

goog.require('Blockly.blockRendering.InRowSpacer');
goog.require('Blockly.blockRendering.RenderInfo');
goog.require('Blockly.blockRendering.Types');
goog.require('Blockly.utils.object');

goog.requireType('Blockly.blockRendering.Field');
goog.requireType('Blockly.BlockSvg');
goog.requireType('Blockly.thrasos.Renderer');

/**
 * An object containing all sizing information needed to draw this block.
 *
 * This measure pass does not propagate changes to the block (although fields
 * may choose to rerender when getSize() is called).  However, calling it
 * repeatedly may be expensive.
 *
 * @param {!Blockly.thrasos.Renderer} renderer The renderer in use.
 * @param {!Blockly.BlockSvg} block The block to measure.
 * @constructor
 * @package
 * @extends {Blockly.blockRendering.RenderInfo}
 */
Blockly.thrasos.RenderInfo = function(renderer, block) {
  Blockly.thrasos.RenderInfo.superClass_.constructor.call(this, renderer, block);
};
Blockly.utils.object.inherits(Blockly.thrasos.RenderInfo,
    Blockly.blockRendering.RenderInfo);

/**
 * Get the block renderer in use.
 * @return {!Blockly.thrasos.Renderer} The block renderer in use.
 * @package
 */
Blockly.thrasos.RenderInfo.prototype.getRenderer = function() {
  return /** @type {!Blockly.thrasos.Renderer} */ (this.renderer_);
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.addElemSpacing_ = function() {
  let hasExternalInputs = false;
  for (let i = 0; i < this.rows.length; i++) {
    const row = this.rows[i];
    if (row.hasExternalInput) {
      hasExternalInputs = true;
      break;
    }
  }
  for (let i = 0; i < this.rows.length; i++) {
    const row = this.rows[i];
    const oldElems = row.elements;
    row.elements = [];
    // No spacing needed before the corner on the top row or the bottom row.
    if (row.startsWithElemSpacer()) {
      // There's a spacer before the first element in the row.
      row.elements.push(new Blockly.blockRendering.InRowSpacer(
          this.constants_, this.getInRowSpacing_(null, oldElems[0])));
    }
    for (let e = 0; e < oldElems.length - 1; e++) {
      row.elements.push(oldElems[e]);
      const spacing = this.getInRowSpacing_(oldElems[e], oldElems[e + 1]);
      row.elements.push(
          new Blockly.blockRendering.InRowSpacer(this.constants_, spacing));
    }
    row.elements.push(oldElems[oldElems.length - 1]);
    if (row.endsWithElemSpacer()) {
      let spacing = this.getInRowSpacing_(oldElems[oldElems.length - 1], null);
      if (hasExternalInputs && row.hasDummyInput) {
        spacing += this.constants_.TAB_WIDTH;
      }
      // There's a spacer after the last element in the row.
      row.elements.push(new Blockly.blockRendering.InRowSpacer(
          this.constants_, spacing));
    }
  }
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.getInRowSpacing_ = function(prev, next) {
  if (!prev) {
    // Between an editable field and the beginning of the row.
    if (next && Blockly.blockRendering.Types.isField(next) &&
        (/** @type {Blockly.blockRendering.Field} */ (next)).isEditable) {
      return this.constants_.MEDIUM_PADDING;
    }
    // Inline input at the beginning of the row.
    if (next && Blockly.blockRendering.Types.isInlineInput(next)) {
      return this.constants_.MEDIUM_LARGE_PADDING;
    }
    if (next && Blockly.blockRendering.Types.isStatementInput(next)) {
      return this.constants_.STATEMENT_INPUT_PADDING_LEFT;
    }
    // Anything else at the beginning of the row.
    return this.constants_.LARGE_PADDING;
  }

  // Spacing between a non-input and the end of the row.
  if (!Blockly.blockRendering.Types.isInput(prev) && !next) {
    // Between an editable field and the end of the row.
    if (Blockly.blockRendering.Types.isField(prev) &&
        (/** @type {Blockly.blockRendering.Field} */ (prev)).isEditable) {
      return this.constants_.MEDIUM_PADDING;
    }
    // Padding at the end of an icon-only row to make the block shape clearer.
    if (Blockly.blockRendering.Types.isIcon(prev)) {
      return (this.constants_.LARGE_PADDING * 2) + 1;
    }
    if (Blockly.blockRendering.Types.isHat(prev)) {
      return this.constants_.NO_PADDING;
    }
    // Establish a minimum width for a block with a previous or next connection.
    if (Blockly.blockRendering.Types.isPreviousOrNextConnection(prev)) {
      return this.constants_.LARGE_PADDING;
    }
    // Between rounded corner and the end of the row.
    if (Blockly.blockRendering.Types.isLeftRoundedCorner(prev)) {
      return this.constants_.MIN_BLOCK_WIDTH;
    }
    // Between a jagged edge and the end of the row.
    if (Blockly.blockRendering.Types.isJaggedEdge(prev)) {
      return this.constants_.NO_PADDING;
    }
    // Between noneditable fields and icons and the end of the row.
    return this.constants_.LARGE_PADDING;
  }

  // Between inputs and the end of the row.
  if (Blockly.blockRendering.Types.isInput(prev) && !next) {
    if (Blockly.blockRendering.Types.isExternalInput(prev)) {
      return this.constants_.NO_PADDING;
    } else if (Blockly.blockRendering.Types.isInlineInput(prev)) {
      return this.constants_.LARGE_PADDING;
    } else if (Blockly.blockRendering.Types.isStatementInput(prev)) {
      return this.constants_.NO_PADDING;
    }
  }

  // Spacing between a non-input and an input.
  if (!Blockly.blockRendering.Types.isInput(prev) &&
      next && Blockly.blockRendering.Types.isInput(next)) {
    // Between an editable field and an input.
    if (Blockly.blockRendering.Types.isField(prev) &&
        (/** @type {Blockly.blockRendering.Field} */ (prev)).isEditable) {
      if (Blockly.blockRendering.Types.isInlineInput(next)) {
        return this.constants_.SMALL_PADDING;
      } else if (Blockly.blockRendering.Types.isExternalInput(next)) {
        return this.constants_.SMALL_PADDING;
      }
    } else {
      if (Blockly.blockRendering.Types.isInlineInput(next)) {
        return this.constants_.MEDIUM_LARGE_PADDING;
      } else if (Blockly.blockRendering.Types.isExternalInput(next)) {
        return this.constants_.MEDIUM_LARGE_PADDING;
      } else if (Blockly.blockRendering.Types.isStatementInput(next)) {
        return this.constants_.LARGE_PADDING;
      }
    }
    return this.constants_.LARGE_PADDING - 1;
  }

  // Spacing between an icon and an icon or field.
  if (Blockly.blockRendering.Types.isIcon(prev) &&
      next && !Blockly.blockRendering.Types.isInput(next)) {
    return this.constants_.LARGE_PADDING;
  }

  // Spacing between an inline input and a field.
  if (Blockly.blockRendering.Types.isInlineInput(prev) &&
      next && Blockly.blockRendering.Types.isField(next)) {
    // Editable field after inline input.
    if ((/** @type {Blockly.blockRendering.Field} */ (next)).isEditable) {
      return this.constants_.MEDIUM_PADDING;
    } else {
      // Noneditable field after inline input.
      return this.constants_.LARGE_PADDING;
    }
  }

  if (Blockly.blockRendering.Types.isLeftSquareCorner(prev) && next) {
    // Spacing between a hat and a corner
    if (Blockly.blockRendering.Types.isHat(next)) {
      return this.constants_.NO_PADDING;
    }
    // Spacing between a square corner and a previous or next connection
    if (Blockly.blockRendering.Types.isPreviousConnection(next) ||
        Blockly.blockRendering.Types.isNextConnection(next)) {
      return next.notchOffset;
    }
  }

  // Spacing between a rounded corner and a previous or next connection.
  if (Blockly.blockRendering.Types.isLeftRoundedCorner(prev) && next) {
    return next.notchOffset - this.constants_.CORNER_RADIUS;
  }

  // Spacing between two fields of the same editability.
  if (Blockly.blockRendering.Types.isField(prev) &&
      next && Blockly.blockRendering.Types.isField(next) &&
      ((/** @type {Blockly.blockRendering.Field} */ (prev)).isEditable ==
          (/** @type {Blockly.blockRendering.Field} */ (next)).isEditable)) {
    return this.constants_.LARGE_PADDING;
  }

  // Spacing between anything and a jagged edge.
  if (next && Blockly.blockRendering.Types.isJaggedEdge(next)) {
    return this.constants_.LARGE_PADDING;
  }

  return this.constants_.MEDIUM_PADDING;
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.getSpacerRowHeight_ = function(
    prev, next) {
  // If we have an empty block add a spacer to increase the height.
  if (Blockly.blockRendering.Types.isTopRow(prev) &&
      Blockly.blockRendering.Types.isBottomRow(next)) {
    return this.constants_.EMPTY_BLOCK_SPACER_HEIGHT;
  }
  // Top and bottom rows act as a spacer so we don't need any extra padding.
  if (Blockly.blockRendering.Types.isTopRow(prev) ||
      Blockly.blockRendering.Types.isBottomRow(next)) {
    return this.constants_.NO_PADDING;
  }
  if (prev.hasExternalInput && next.hasExternalInput) {
    return this.constants_.LARGE_PADDING;
  }
  if (!prev.hasStatement && next.hasStatement) {
    return this.constants_.BETWEEN_STATEMENT_PADDING_Y;
  }
  if (prev.hasStatement && next.hasStatement) {
    return this.constants_.LARGE_PADDING;
  }
  if (prev.hasDummyInput || next.hasDummyInput) {
    return this.constants_.LARGE_PADDING;
  }
  return this.constants_.MEDIUM_PADDING;
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.getElemCenterline_ = function(row, elem) {
  if (Blockly.blockRendering.Types.isSpacer(elem)) {
    return row.yPos + elem.height / 2;
  }
  if (Blockly.blockRendering.Types.isBottomRow(row)) {
    const baseline = row.yPos + row.height - row.descenderHeight;
    if (Blockly.blockRendering.Types.isNextConnection(elem)) {
      return baseline + elem.height / 2;
    }
    return baseline - elem.height / 2;
  }
  if (Blockly.blockRendering.Types.isTopRow(row)) {
    if (Blockly.blockRendering.Types.isHat(elem)) {
      return row.capline - elem.height / 2;
    }
    return row.capline + elem.height / 2;
  }

  let result = row.yPos;
  if (Blockly.blockRendering.Types.isField(elem) && row.hasStatement) {
    const offset = this.constants_.TALL_INPUT_FIELD_OFFSET_Y +
        elem.height / 2;
    result += offset;
  } else {
    result += (row.height / 2);
  }
  return result;
};

/**
 * @override
 */
Blockly.thrasos.RenderInfo.prototype.finalize_ = function() {
  // Performance note: this could be combined with the draw pass, if the time
  // that this takes is excessive.  But it shouldn't be, because it only
  // accesses and sets properties that already exist on the objects.
  let widestRowWithConnectedBlocks = 0;
  let yCursor = 0;
  for (let i = 0; i < this.rows.length; i++) {
    const row = this.rows[i];
    row.yPos = yCursor;
    row.xPos = this.startX;
    yCursor += row.height;

    widestRowWithConnectedBlocks =
        Math.max(widestRowWithConnectedBlocks, row.widthWithConnectedBlocks);
    // Add padding to the bottom row if block height is less than minimum
    const heightWithoutHat = yCursor - this.topRow.ascenderHeight;
    if (row == this.bottomRow &&
        heightWithoutHat < this.constants_.MIN_BLOCK_HEIGHT) {
      // But the hat height shouldn't be part of this.
      const diff = this.constants_.MIN_BLOCK_HEIGHT - heightWithoutHat;
      this.bottomRow.height += diff;
      yCursor += diff;
    }
    this.recordElemPositions_(row);
  }
  if (this.outputConnection && this.block_.nextConnection &&
      this.block_.nextConnection.isConnected()) {
    // Include width of connected block in value to stack width measurement.
    widestRowWithConnectedBlocks =
        Math.max(widestRowWithConnectedBlocks,
            this.block_.nextConnection.targetBlock().getHeightWidth().width);
  }

  this.bottomRow.baseline = yCursor - this.bottomRow.descenderHeight;
  this.widthWithChildren = widestRowWithConnectedBlocks + this.startX;

  this.height = yCursor;
  this.startY = this.topRow.capline;
};
<|endoftext|>


<commit_before>/*
	ColorBox v1.05 - a full featured, light-weight, customizable lightbox based on jQuery 1.3
	(c) 2009 Jack Moore - www.colorpowered.com - jack@colorpowered.com
	Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/

(function($){

var index, related, closeModal, loadingElement, modal, modalOverlay, modalLoadingOverlay, modalContent, modalLoadedContent, modalClose, borderTopLeft, borderTopCenter, borderTopRight, borderMiddleLeft, borderMiddleRight, borderBottomLeft, borderBottomCenter, borderBottomRight;

function setModalOverlay(){
	$([modalOverlay]).css({"position":"absolute", width:$(window).width(), height:$(window).height(), top:$(window).scrollTop(), left:$(window).scrollLeft()});
}

function keypressEvents(e){
	if(e.keyCode == 27){
		closeModal();
	}
	else if(e.keyCode == 37){
		$("a#contentPrevious").click();
	}
	else if(e.keyCode == 39){
		$("a#contentNext").click();
	}
}

closeModal = function(){
	$(modal).removeData("open");
	$([modalOverlay, modal]).fadeOut("fast", function(){
		$(modalLoadedContent).empty();
		$([modalOverlay, modal]).hide();//Seems unnecessary, but sometimes IE6 does not hide the modal.
	});
	if(loadingElement){$(loadingElement).remove();}
	$(document).unbind('keydown', keypressEvents);
	$(window).unbind('resize scroll', setModalOverlay);
};

$(function(){
	//Initialize the modal, preload the interface graphics, and wait until called.
	$("body").append(
		$([
			modalOverlay = $('
')[0],
			modal = $('
')[0]
		]).hide()
	);

	$(modal).append(
		$([
			borderTopLeft = $('
')[0],
			borderTopCenter = $('
')[0],
			borderTopRight = $('
')[0],
			borderMiddleLeft = $('
')[0],
			borderMiddleRight = $('
')[0],
			borderBottomLeft = $('
')[0],
			borderBottomCenter = $('
')[0],
			borderBottomRight = $('
')[0],
			modalContent = $('
')[0]
		])
	);

	$(modalContent).append(
		$([
			modalLoadedContent = $('

')[0],
			modalLoadingOverlay = $('
')[0],
			modalClose = $('')[0]
		])
	);

	$(modalClose).click(function(){
		closeModal();
		return false;
	});
});

$.fn.colorbox = function(settings) {

	settings = $.extend({}, $.fn.colorbox.settings, settings);

	//sets the position of the modal on screen.  A transition speed of 0 will result in no animation.
	function modalPosition(modalWidth, modalHeight, transitionSpeed, callback){
		var windowHeight = typeof window.innerHeight == 'number' ? window.innerHeight : document.documentElement.clientHeight;
		var colorboxHeight = modalHeight + $(borderTopLeft).height() + $(borderBottomLeft).height();
		var colorboxWidth = modalWidth + $(borderTopLeft).width() + $(borderBottomLeft).width();
		var posTop = windowHeight/2 - colorboxHeight/2 + $(window).scrollTop();
		var posLeft = $(window).width()/2 - colorboxWidth/2 + $(window).scrollLeft();
		if(colorboxHeight > windowHeight){
			posTop -=(colorboxHeight - windowHeight);
		}
		if(posTop < 0){posTop = 0;} //keeps the box from expanding to an inaccessible area offscreen.
		if(posLeft < 0){posLeft = 0;}
		$(modal).animate({height:colorboxHeight, top:posTop, left:posLeft, width:colorboxWidth}, transitionSpeed);

		//each part is animated seperately to keep them from disappearing during the animation process, which is what would happen if they were positioned relative to a single element being animated.
		$(borderMiddleLeft).animate({top:$(borderTopLeft).height(), left:0, height:modalHeight}, transitionSpeed);
		$(borderMiddleRight).animate({top:$(borderTopRight).height(), left:colorboxWidth-$(borderMiddleRight).width(), height:modalHeight}, transitionSpeed);

		$(borderTopLeft).animate({top:0, left:0}, transitionSpeed);
		$(borderTopCenter).animate({top:0, left:$(borderTopLeft).width(), width:modalWidth}, transitionSpeed);
		$(borderTopRight).animate({top: 0, left: colorboxWidth - $(borderTopRight).width()}, transitionSpeed);

		$(borderBottomLeft).animate({top:colorboxHeight-$(borderBottomLeft).height(), left:0}, transitionSpeed);
		$(borderBottomCenter).animate({top:colorboxHeight-$(borderBottomLeft).height(), left:$(borderBottomLeft).width(), width:modalWidth}, transitionSpeed);
		$(borderBottomRight).animate({top: colorboxHeight - $(borderBottomLeft).height(),	left: colorboxWidth - $(borderBottomRight).width()}, transitionSpeed);
		$(modalContent).animate({height:modalHeight, width:modalWidth, top:$(borderTopLeft).height(), left:$(borderTopLeft).width()}, transitionSpeed, function(){
			if(callback){callback();}
			if($.browser.msie && $.browser.version < 7){
				setModalOverlay();
			}
		});
	}

	var preloads = [];

	function preload(){
		if(settings.preloading !== false && related.length>1){
			var previous, next;
			previous = index > 0 ? related[index-1].href : related[related.length-1].href;
			next = index < related.length-1 ? related[index+1].href : related[0].href;
			return [$("").attr("src", next), $("").attr("src", previous)];
		}
	}

	function centerModal(contentHtml, contentInfo){
		$(modalLoadedContent).hide().html(contentHtml).append(contentInfo);
		if(settings.contentWidth){$(modalLoadedContent).css({"width":settings.contentWidth});}
		if(settings.contentHeight){$(modalLoadedContent).css({"height":settings.contentHeight});}
		if (settings.transition == "elastic") {
			modalPosition($(modalLoadedContent).outerWidth(true), $(modalLoadedContent).outerHeight(true), settings.transitionSpeed, function(){
				$(modalLoadedContent).show();
				$(modalLoadingOverlay).hide();
			});

		}
		else {
			$(modal).animate({"opacity":0}, settings.transitionSpeed, function(){
				modalPosition($(modalLoadedContent).outerWidth(true), $(modalLoadedContent).outerHeight(true), 0, function(){
					$(modalLoadedContent).show();
					$(modalLoadingOverlay).hide();
					$(modal).animate({"opacity":1}, settings.transitionSpeed);
				});
			});
		}
		var preloads = preload();
	}

	function buildGallery(that){

		var contentInfo = "
"+that.title+"";

		if(related.length>1){
			contentInfo += " " + settings.contentCurrent + "";
			contentInfo = contentInfo.replace(/\{current\}/, index+1).replace(/\{total\}/, related.length);
			contentInfo += ""+settings.contentPrevious+" ";
			contentInfo += ""+settings.contentNext+" ";
		}

		if (settings.contentInline) {
			centerModal($(settings.contentInline).html(), contentInfo);
		} else if (settings.contentIframe) {
			centerModal("", contentInfo);
		} else if (that.href.match(/.(gif|png|jpg|jpeg|bmp|tif)$/i) && !settings.contentAjax){
			loadingElement = $("").load(function(){
				centerModal("", contentInfo);
			}).attr("src",that.href);
		}else {
			loadingElement = $('

').load(((settings.contentAjax) ? settings.contentAjax : that.href), function(data, textStatus){
				if(textStatus == "success"){
					centerModal($(this).html(), contentInfo);
				} else {
					centerModal("
Ajax request unsuccessful
");
				}
			});
		}
	}

	function contentNav(){
		$(modalLoadingOverlay).show();
		if($(this).attr("id") == "contentPrevious"){
			index = index > 0 ? index-1 : related.length-1;
		} else {
			index = index < related.length-1 ? index+1 : 0;
		}
		buildGallery(related[index]);
		return false;
	}

	$(this).bind("click.colorbox", function () {
		if ($(modal).data("open") !== true) {
			$(modal).data("open", true);
			$(modalLoadedContent).empty().css({
				"height": "auto",
				"width": "auto"
			});
			$(modalClose).html(settings.modalClose);
			$(modalOverlay).css({
				"opacity": settings.bgOpacity
			});
			$([modalOverlay, modal, modalLoadingOverlay]).show();
			$(modalContent).css({
				width: settings.initialWidth,
				height: settings.initialHeight
			});
			modalPosition($(modalContent).width(), $(modalContent).height(), 0);
			if (this.rel) {
				related = $("a[rel='" + this.rel + "']");
				index = $(related).index(this);
			}
			else {
				related = $(this);
				index = 0;
			}
			buildGallery(related[index]);
			$("a#contentPrevious, a#contentNext").die().live("click", contentNav);
			$(document).bind('keydown', keypressEvents);
			if ($.browser.msie && $.browser.version < 7) {
				$(window).bind("resize scroll", setModalOverlay);
			}
		}
		return false;
	});

	if(settings.open===true && $(modal).data("open")!==true){
		$(this).triggerHandler('click.colorbox');
	}

	return this.each(function() {
	});
};

/*
	ColorBox Default Settings.

	The colorbox() function takes one argument, an object of key/value pairs, that are used to initialize the modal.

	Please do not change these settings here, instead overwrite these settings when attaching the colorbox() event to your anchors.
	Example (Global)	: $.fn.colorbox.settings.transition = "fade"; //changes the transition to fade for all colorBox() events proceeding it's declaration.
	Example (Specific)	: $("a[href='http://www.google.com']").colorbox({contentWidth:"700px", contentHeight:"450px", contentIframe:true});
*/
$.fn.colorbox.settings = {
	transition : "elastic", // "elastic" or "fade". Set transitionSpeed to 0 for no transition.
	transitionSpeed : 350, // Sets the speed of the fade and elastic transition, in milliseconds. Set to 0 for no transition.
	initialWidth : 300, // Set the initial width of the modal, prior to any content being loaded.
	initialHeight : 100, // Set the initial height of the modal, prior to any content being loaded.
	contentWidth : false, // Set a fixed width for div#modalLoadedContent.  Example: "500px"
	contentHeight : false, // Set a fixed height for div#modalLoadedContent.  Example: "500px"
	contentAjax : false, // Set this to the file, or file+selector of content that will be loaded through an external file.  Example "include.html" or "company.inc.php div#ceo_bio"
	contentInline : false, // Set this to the selector, in jQuery selector format, of inline content to be displayed.  Example "#myHiddenDiv".
	contentIframe : false, // If 'true' specifies that content should be displayed in an iFrame.
	bgOpacity : 0.85, // The modalBackgroundOverlay opacity level. Range: 0 to 1.
	preloading : true, // Allows for preloading of 'Next' and 'Previous' content in a shared relation group (same values for the 'rel' attribute), after the current content has finished loading.  Set to 'false' to disable.
	contentCurrent : "{current} of {total}", // the format of the contentCurrent information
	contentPrevious : "previous", // the anchor text for the previous link in a shared relation group (same values for 'rel').
	contentNext : "next", // the anchor text for the next link in a shared relation group (same 'rel' attribute').
	modalClose : "close", // the anchor text for the close link.  Esc will also close the modal.
	open : false //Automatically opens ColorBox. (fires the click.colorbox event without waiting for user input).
};

})(jQuery);

<commit_msg>Signed-off-by: Jack Moore <jack@colorpowered.com><commit_after>/*
	ColorBox v1.05 - a full featured, light-weight, customizable lightbox based on jQuery 1.3
	(c) 2009 Jack Moore - www.colorpowered.com - jack@colorpowered.com
	Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
*/

(function($){
var interfaceHeight, interfaceWidth, loadedHeight, loadedWidth, index, related, closeModal, loadingElement, modal, modalOverlay, modalLoadingOverlay, modalContent, modalLoadedContent, modalClose, borderTopLeft, borderTopCenter, borderTopRight, borderMiddleLeft, borderMiddleRight, borderBottomLeft, borderBottomCenter, borderBottomRight;
function setModalOverlay(){
	$([modalOverlay]).css({"position":"absolute", width:$(window).width(), height:$(window).height(), top:$(window).scrollTop(), left:$(window).scrollLeft()});
}
function keypressEvents(e){
	if(e.keyCode == 37){
		$("a#contentPrevious").click();
		$(document).unbind('keydown', keypressEvents);
	}
	else if(e.keyCode == 39){
		$("a#contentNext").click();
		$(document).unbind('keydown', keypressEvents);
	}
}
closeModal = function(){
	$(modal).removeData("open");
	$([modalOverlay, modal]).fadeOut("fast", function(){
		$(modalLoadedContent).empty();
		$([modalOverlay, modal]).hide();//Seems unnecessary, but sometimes IE6 does not hide the modal.
	});
	if(loadingElement){$(loadingElement).remove();}
	$(document).unbind('keydown', keypressEvents);
	$(window).unbind('resize scroll', setModalOverlay);
};
// Convert % values to pixels
function setSize(size, dimension){
	return (typeof size == 'string') ? (size.match(/%/) ? (dimension/100)*parseInt(size) : parseInt(size)) : size;
}
function windowHeight(){return typeof window.innerHeight == 'number' ? window.innerHeight : document.documentElement.clientHeight;}
function windowWidth(){return typeof window.innerWidth == 'number' ? window.innerWidth : document.documentElement.clientWidth;}

//Initialize the modal: store common calculations, preload the interface graphics, append the html.
$(function(){
	$("body").append(
		$([
			modalOverlay = $('
')[0],
			modal = $('
')[0]
		]).hide()
	);
	$(modal).append(
		$([
			borderTopLeft = $('
')[0],
			borderTopCenter = $('
')[0],
			borderTopRight = $('
')[0],
			borderMiddleLeft = $('
')[0],
			borderMiddleRight = $('
')[0],
			borderBottomLeft = $('
')[0],
			borderBottomCenter = $('
')[0],
			borderBottomRight = $('
')[0],
			modalContent = $('
')[0]
		])
	);
	$(modalContent).append(
		$([
			modalLoadedContent = $('

')[0],
			modalLoadingOverlay = $('
')[0],
			modalClose = $('')[0]
		])
	);
	$(modalClose).click(function(){
		closeModal();
		return false;
	});
	$(document).bind('keydown', function(e){if(e.keyCode == 27){closeModal();}});

	$(modal).css("opacity", 0).show();
	loadedHeight = $(modalLoadedContent).outerHeight(true);
	loadedWidth = $(modalLoadedContent).outerWidth(true);
	interfaceHeight = $(borderTopCenter).height()+$(borderBottomCenter).height() + loadedHeight;
	interfaceWidth = $(borderMiddleLeft).width()+$(borderMiddleRight).width() + loadedWidth;
	$(borderMiddleLeft).css({top:$(borderTopLeft).height()});
	$(borderMiddleRight).css({top:$(borderTopRight).height()});
	$(borderTopCenter).css({left:$(borderTopLeft).width()});
	$(borderBottomCenter).css({left:$(borderBottomLeft).width()});
	$(modalContent).css({top:$(borderTopLeft).height(), left:$(borderTopLeft).width()});
	$(modal).hide().css("opacity", 1);
});

$.fn.colorbox = function(settings) {

	settings = $.extend({}, $.fn.colorbox.settings, settings);

	//sets the position of the modal on screen.  A transition speed of 0 will result in no animation.
	function modalPosition(modalWidth, modalHeight, transitionSpeed, callback){
		var winHeight = windowHeight();
		var colorboxHeight = modalHeight + interfaceHeight;
		var colorboxWidth = modalWidth + interfaceWidth;

		var posTop = winHeight/2 - colorboxHeight/2 + $(window).scrollTop();
		var posLeft = $(window).width()/2 - colorboxWidth/2 + $(window).scrollLeft();
		if(colorboxHeight > winHeight){
			posTop -=(colorboxHeight - winHeight);
		}
		if(posTop < 0){posTop = 0;} //keeps the box from expanding to an inaccessible area offscreen.
		if(posLeft < 0){posLeft = 0;}
		//each part is animated seperately to keep them from disappearing during the animation process, which is what would happen if they were positioned relative to a single element being animated.
		$(modal).animate({top:posTop, left:posLeft, height:modalHeight + interfaceHeight, width:modalWidth + interfaceWidth}, transitionSpeed);
		$([borderMiddleLeft, borderMiddleRight]).animate({height:modalHeight+loadedHeight}, transitionSpeed);
		$([borderTopCenter, borderBottomCenter]).animate({width:modalWidth+loadedWidth}, transitionSpeed);
		$(modalContent).animate({height:modalHeight+loadedHeight, width:modalWidth+loadedWidth}, transitionSpeed, function(){
			if(callback){callback();}
			$(document).bind('keydown', keypressEvents);
			if($.browser.msie && $.browser.version < 7){setModalOverlay();}
		});
	}

	var preloads = [];

	function preload(){
		if(settings.preloading !== false && related.length>1){
			var previous, next;
			previous = index > 0 ? related[index-1].href : related[related.length-1].href;
			next = index < related.length-1 ? related[index+1].href : related[0].href;
			return [$("").attr("src", next), $("").attr("src", previous)];
		}
	}

	function centerModal(contentHtml, contentInfo){
		$(modalLoadedContent).hide().html(contentHtml).append(contentInfo);

		if(settings.fixedWidth){$(modalLoadedContent).css({"width":settings.fixedWidth - interfaceWidth}); }
		if(settings.fixedHeight){$(modalLoadedContent).css({"height":settings.fixedHeight - interfaceHeight});}

		if (settings.transition == "elastic") {
			modalPosition($(modalLoadedContent).width(), $(modalLoadedContent).height(), settings.transitionSpeed, function(){
				$(modalLoadedContent).show();
				$(modalLoadingOverlay).hide();
			});
		}
		else {
			$(modal).animate({"opacity":0}, settings.transitionSpeed, function(){
				modalPosition($(modalLoadedContent).outerWidth(true), $(modalLoadedContent).outerHeight(true), 0, function(){
					$(modalLoadedContent).show();
					$(modalLoadingOverlay).hide();
					$(modal).animate({"opacity":1}, settings.transitionSpeed);
				});
			});
		}
		var preloads = preload();
	}

	function buildGallery(that){

		var contentInfo = "
"+that.title+"";

		if(related.length>1){
			contentInfo += " " + settings.contentCurrent + "";
			contentInfo = contentInfo.replace(/\{current\}/, index+1).replace(/\{total\}/, related.length);
			contentInfo += ""+settings.contentPrevious+" ";
			contentInfo += ""+settings.contentNext+" ";
		}

		if (settings.contentInline) {
			centerModal($(settings.contentInline).html(), contentInfo);
		} else if (settings.contentIframe) {
			centerModal("", contentInfo);
		} else if (that.href.match(/.(gif|png|jpg|jpeg|bmp|tif)$/i) && !settings.contentAjax){
			loadingElement = $("").load(function(){
				centerModal("", contentInfo);
			}).attr("src",that.href);
		}else {
			loadingElement = $('

').load(((settings.contentAjax) ? settings.contentAjax : that.href), function(data, textStatus){
				if(textStatus == "success"){
					centerModal($(this).html(), contentInfo);
				} else {
					centerModal("
Ajax request unsuccessful
");
				}
			});
		}
	}

	function contentNav(){
		$(modalLoadingOverlay).show();
		if($(this).attr("id") == "contentPrevious"){
			index = index > 0 ? index-1 : related.length-1;
		} else {
			index = index < related.length-1 ? index+1 : 0;
		}
		buildGallery(related[index]);
		return false;
	}

	$(this).bind("click.colorbox", function () {
		if ($(modal).data("open") !== true) {
			$(modal).data("open", true);
			$(modalLoadedContent).empty().css({
				"height": "auto",
				"width": "auto"
			});
			$(modalClose).html(settings.modalClose);
			$(modalOverlay).css({"opacity": settings.bgOpacity});

			$([modal, modalLoadingOverlay, modalOverlay]).show();

			if(settings.fixedWidth){ settings.fixedWidth = setSize(settings.fixedWidth, windowWidth());}
			if(settings.fixedHeight){ settings.fixedHeight = setSize(settings.fixedHeight, windowHeight());}

			modalPosition(setSize(settings.initialWidth, windowWidth()), setSize(settings.initialHeight, windowHeight()), 0);

			if (this.rel) {
				related = $("a[rel='" + this.rel + "']");
				index = $(related).index(this);
			}
			else {
				related = $(this);
				index = 0;
			}
			buildGallery(related[index]);
			$("a#contentPrevious, a#contentNext").die().live("click", contentNav);
			$(document).bind('keydown', keypressEvents);
			if ($.browser.msie && $.browser.version < 7) {
				$(window).bind("resize scroll", setModalOverlay);
			}
		}
		return false;
	});

	if(settings.open!==false && $(modal).data("open")!==true){
		$(this).triggerHandler('click.colorbox');
	}

	return this.each(function() {
	});
};

/*
	ColorBox Default Settings.

	The colorbox() function takes one argument, an object of key/value pairs, that are used to initialize the modal.

	Please do not change these settings here, instead overwrite these settings when attaching the colorbox() event to your anchors.
	Example (Global)	: $.fn.colorbox.settings.transition = "fade"; //changes the transition to fade for all colorBox() events proceeding it's declaration.
	Example (Specific)	: $("a[href='http://www.google.com']").colorbox({fixedWidth:"700px", fixedHeight:"450px", contentIframe:true});
*/
$.fn.colorbox.settings = {
	transition : "elastic", // "elastic" or "fade". Set transitionSpeed to 0 for no transition.
	transitionSpeed : 250, // Sets the speed of the fade and elastic transition, in milliseconds. Set to 0 for no transition.
	initialWidth : "50%", // Set the initial width of the modal, prior to any content being loaded.
	initialHeight : "50%", // Set the initial height of the modal, prior to any content being loaded.
	fixedWidth : false, // Set a fixed width for div#modalLoadedContent.  Example: "500px"
	fixedHeight : false, // Set a fixed height for div#modalLoadedContent.  Example: "500px"
	contentAjax : false, // Set this to the file, or file+selector of content that will be loaded through an external file.  Example "include.html" or "company.inc.php div#ceo_bio"
	contentInline : false, // Set this to the selector, in jQuery selector format, of inline content to be displayed.  Example "#myHiddenDiv".
	contentIframe : false, // If 'true' specifies that content should be displayed in an iFrame.
	bgOpacity : 0.85, // The modalBackgroundOverlay opacity level. Range: 0 to 1.
	preloading : true, // Allows for preloading of 'Next' and 'Previous' content in a shared relation group (same values for the 'rel' attribute), after the current content has finished loading.  Set to 'false' to disable.
	contentCurrent : "{current} of {total}", // the format of the contentCurrent information
	contentPrevious : "previous", // the anchor text for the previous link in a shared relation group (same values for 'rel').
	contentNext : "next", // the anchor text for the next link in a shared relation group (same 'rel' attribute').
	modalClose : "close", // the anchor text for the close link.  Esc will also close the modal.
	open : false //Automatically opens ColorBox. (fires the click.colorbox event without waiting for user input).
};

})(jQuery);

<|endoftext|>


<commit_before>define(function(require, exports, module) {
    main.consumes = [
        "c9", "Previewer", "fs", "dialog.error", "commands", "tabManager"
    ];
    main.provides = ["preview.markdown"];
    return main;

    // @todo possible improvements: http://benweet.github.io/stackedit/#

    function main(options, imports, register) {
        var Previewer  = imports.Previewer;
        var c9         = imports.c9;
        var fs         = imports.fs;
        var commands   = imports.commands;
        var tabManager = imports.tabManager;
        var showError  = imports["dialog.error"].show;
        var dirname    = require("path").dirname;

        /***** Initialization *****/

        var plugin = new Previewer("Ajax.org", main.consumes, {
            caption  : "Markdown",
            index    : 200,
            selector : function(path){
                return path && path.match(/(?:\.md|\.markdown)$/i);
            }
        });
        var emit = plugin.getEmitter();

        var counter = 0;
        var HTMLURL, previewOrigin;

        /***** Methods *****/

        function getPreviewUrl(fn){
            if (options.local && document.baseURI.substr(0, 5) == "file:")
                return setTimeout(getPreviewUrl.bind(null, fn), 100);
            else if (HTMLURL)
                return fn(HTMLURL);

            HTMLURL = (options.staticPrefix
                ? (options.local ? dirname(document.baseURI) + "/static" : options.staticPrefix)
                    + options.htmlPath
                : "/static/plugins/c9.ide.preview/previewers/markdown.html")
                    + "?host=" + (options.local ? "local" : location.origin);

            if (HTMLURL.charAt(0) == "/")
                HTMLURL = location.protocol + "//" + location.host + HTMLURL;

            previewOrigin = HTMLURL.match(/^(?:[^\/]|\/\/)*/)[0];

            fn(HTMLURL);
        }

        // function cleanIframeSrc(src){
        //     return src
        //         .replace(/\?host=.*?(?:\&|$)/, "")
        //         .replace(/[\?\&]$/, "");
        // }

        /***** Lifecycle *****/

        plugin.on("load", function(){

        });
        plugin.on("documentLoad", function(e){
            var doc     = e.doc;
            var session = doc.getSession();
            var tab     = doc.tab;
            var editor  = e.editor;

            if (session.iframe) {
                session.editor = editor;
                editor.container.appendChild(session.iframe);
                return;
            }

            var iframe = document.createElement("iframe");

            iframe.setAttribute("nwfaketop", true);
            iframe.setAttribute("nwdisable", true);

            iframe.style.width    = "100%";
            iframe.style.height   = "100%";
            iframe.style.border   = 0;
            iframe.style.backgroundColor = "rgba(255, 255, 255, 0.88)";

            if (options.local) {
                iframe.addEventListener("load", function(){
                    // @todo to do this correctly stack needs to allow switching previewer
                    // plugin.activeSession.add(iframe.contentWindow.location.href);

                    iframe.contentWindow.opener = window;
                    if (iframe.contentWindow.start)
                        iframe.contentWindow.start(window);
                });
            }

            var onMessage = function(e) {
                if (c9.hosted && event.origin !== previewOrigin)
                    return;

                if (e.data.id != session.id)
                    return;

                if (e.data.message == "exec") {
                    commands.exec(e.data.command);
                }
                else if (e.data.message == "focus") {
                    tabManager.focusTab(tab);
                }
                else if (e.data.message == "stream.document") {
                    session.source = e.source;

                    if (session.previewTab) {
                        var doc = session.previewTab.document;

                        session.source.postMessage({
                            type    : "document",
                            content : doc.value
                        }, "*");

                        if (!doc.hasValue())
                            doc.once("setValue", function(){
                                emit("update", { previewDocument: doc });
                            });
                    }
                    else {
                        fs.readFile(session.path, function(err, data){
                            if (err)
                                return showError(err.message);

                            session.source.postMessage({
                                type    : "document",
                                content : data
                            }, "*");
                        });
                    }

                    session.source.postMessage({
                        type : "keys",
                        keys : commands.getExceptionBindings()
                    }, "*");

                    tab.className.remove("loading");
                }
            };
            window.addEventListener("message", onMessage, false);
            session.addOther(function(){
                window.removeEventListener("message", onMessage, false);
            });

            // Set iframe
            session.iframe = iframe;
            session.id     = "markdown" + counter++;

            session.destroy = function(){
                delete session.editor;
                delete session.iframe;
            }

            // Load the markup renderer
            getPreviewUrl(function(url){
                iframe.src = url + "&id=" + session.id;
            });

            session.editor = editor;
            editor.container.appendChild(session.iframe);
        });
        plugin.on("documentUnload", function(e){
            var doc     = e.doc;
            var session = doc.getSession();
            var iframe  = session.iframe;

            iframe.parentNode.removeChild(iframe);

            doc.tab.className.remove("loading");
        });
        plugin.on("documentActivate", function(e){
            var session = e.doc.getSession();

            session.iframe.style.display = "block";
            session.editor.setLocation(session.path);
            session.editor.setButtonStyle("Markdown", "page_white.png");
        });
        plugin.on("documentDeactivate", function(e){
            var session = e.doc.getSession();
            session.iframe.style.display = "none";
        });
        plugin.on("navigate", function(e){
            var tab    = plugin.activeDocument.tab;
            var iframe = plugin.activeSession.iframe;
            var editor = plugin.activeSession.editor;

            tab.className.add("loading");

            tab.title    =
            tab.tooltip  = "[M] " + e.url;
            editor.setLocation(e.url);

            iframe.src = iframe.src;
        });
        plugin.on("update", function(e){
            var session = plugin.activeSession;
            if (!session.source) return; // Renderer is not loaded yet

            session.source.postMessage({
                type    : "document",
                content : e.previewDocument.value
            }, "*");
        });
        plugin.on("reload", function(){
            var iframe = plugin.activeSession.iframe;
            var tab    = plugin.activeDocument.tab;
            tab.className.add("loading");
            iframe.src = iframe.src;
        });
        plugin.on("popout", function(){
            var src = plugin.activeSession.iframe.src;
            window.open(src);
        });
        plugin.on("enable", function(){

        });
        plugin.on("disable", function(){

        });
        plugin.on("unload", function(){
        });

        /***** Register and define API *****/

        /**
         * Previewer for markdown content.
         **/
        plugin.freezePublicAPI({
        });

        register(null, {
            "preview.markdown": plugin
        });
    }
});<commit_msg>Apply scripts/apply-conventions.sh<commit_after>define(function(require, exports, module) {
    main.consumes = [
        "c9", "Previewer", "fs", "dialog.error", "commands", "tabManager"
    ];
    main.provides = ["preview.markdown"];
    return main;

    // @todo possible improvements: http://benweet.github.io/stackedit/#

    function main(options, imports, register) {
        var Previewer = imports.Previewer;
        var c9 = imports.c9;
        var fs = imports.fs;
        var commands = imports.commands;
        var tabManager = imports.tabManager;
        var showError = imports["dialog.error"].show;
        var dirname = require("path").dirname;

        /***** Initialization *****/

        var plugin = new Previewer("Ajax.org", main.consumes, {
            caption: "Markdown",
            index: 200,
            selector: function(path) {
                return path && path.match(/(?:\.md|\.markdown)$/i);
            }
        });
        var emit = plugin.getEmitter();

        var counter = 0;
        var HTMLURL, previewOrigin;

        /***** Methods *****/

        function getPreviewUrl(fn) {
            if (options.local && document.baseURI.substr(0, 5) == "file:")
                return setTimeout(getPreviewUrl.bind(null, fn), 100);
            else if (HTMLURL)
                return fn(HTMLURL);

            HTMLURL = (options.staticPrefix
                ? (options.local ? dirname(document.baseURI) + "/static" : options.staticPrefix)
                    + options.htmlPath
                : "/static/plugins/c9.ide.preview/previewers/markdown.html")
                    + "?host=" + (options.local ? "local" : location.origin);

            if (HTMLURL.charAt(0) == "/")
                HTMLURL = location.protocol + "//" + location.host + HTMLURL;

            previewOrigin = HTMLURL.match(/^(?:[^\/]|\/\/)*/)[0];

            fn(HTMLURL);
        }

        // function cleanIframeSrc(src) {
        //     return src
        //         .replace(/\?host=.*?(?:\&|$)/, "")
        //         .replace(/[\?\&]$/, "");
        // }

        /***** Lifecycle *****/

        plugin.on("load", function(){

        });
        plugin.on("documentLoad", function(e) {
            var doc = e.doc;
            var session = doc.getSession();
            var tab = doc.tab;
            var editor = e.editor;

            if (session.iframe) {
                session.editor = editor;
                editor.container.appendChild(session.iframe);
                return;
            }

            var iframe = document.createElement("iframe");

            iframe.setAttribute("nwfaketop", true);
            iframe.setAttribute("nwdisable", true);

            iframe.style.width = "100%";
            iframe.style.height = "100%";
            iframe.style.border = 0;
            iframe.style.backgroundColor = "rgba(255, 255, 255, 0.88)";

            if (options.local) {
                iframe.addEventListener("load", function(){
                    // @todo to do this correctly stack needs to allow switching previewer
                    // plugin.activeSession.add(iframe.contentWindow.location.href);

                    iframe.contentWindow.opener = window;
                    if (iframe.contentWindow.start)
                        iframe.contentWindow.start(window);
                });
            }

            var onMessage = function(e) {
                if (c9.hosted && event.origin !== previewOrigin)
                    return;

                if (e.data.id != session.id)
                    return;

                if (e.data.message == "exec") {
                    commands.exec(e.data.command);
                }
                else if (e.data.message == "focus") {
                    tabManager.focusTab(tab);
                }
                else if (e.data.message == "stream.document") {
                    session.source = e.source;

                    if (session.previewTab) {
                        var doc = session.previewTab.document;

                        session.source.postMessage({
                            type: "document",
                            content: doc.value
                        }, "*");

                        if (!doc.hasValue())
                            doc.once("setValue", function(){
                                emit("update", { previewDocument: doc });
                            });
                    }
                    else {
                        fs.readFile(session.path, function(err, data) {
                            if (err)
                                return showError(err.message);

                            session.source.postMessage({
                                type: "document",
                                content: data
                            }, "*");
                        });
                    }

                    session.source.postMessage({
                        type: "keys",
                        keys: commands.getExceptionBindings()
                    }, "*");

                    tab.className.remove("loading");
                }
            };
            window.addEventListener("message", onMessage, false);
            session.addOther(function(){
                window.removeEventListener("message", onMessage, false);
            });

            // Set iframe
            session.iframe = iframe;
            session.id = "markdown" + counter++;

            session.destroy = function(){
                delete session.editor;
                delete session.iframe;
            }

            // Load the markup renderer
            getPreviewUrl(function(url) {
                iframe.src = url + "&id=" + session.id;
            });

            session.editor = editor;
            editor.container.appendChild(session.iframe);
        });
        plugin.on("documentUnload", function(e) {
            var doc = e.doc;
            var session = doc.getSession();
            var iframe = session.iframe;

            iframe.parentNode.removeChild(iframe);

            doc.tab.className.remove("loading");
        });
        plugin.on("documentActivate", function(e) {
            var session = e.doc.getSession();

            session.iframe.style.display = "block";
            session.editor.setLocation(session.path);
            session.editor.setButtonStyle("Markdown", "page_white.png");
        });
        plugin.on("documentDeactivate", function(e) {
            var session = e.doc.getSession();
            session.iframe.style.display = "none";
        });
        plugin.on("navigate", function(e) {
            var tab = plugin.activeDocument.tab;
            var iframe = plugin.activeSession.iframe;
            var editor = plugin.activeSession.editor;

            tab.className.add("loading");

            tab.title =
            tab.tooltip = "[M] " + e.url;
            editor.setLocation(e.url);

            iframe.src = iframe.src;
        });
        plugin.on("update", function(e) {
            var session = plugin.activeSession;
            if (!session.source) return; // Renderer is not loaded yet

            session.source.postMessage({
                type: "document",
                content: e.previewDocument.value
            }, "*");
        });
        plugin.on("reload", function(){
            var iframe = plugin.activeSession.iframe;
            var tab = plugin.activeDocument.tab;
            tab.className.add("loading");
            iframe.src = iframe.src;
        });
        plugin.on("popout", function(){
            var src = plugin.activeSession.iframe.src;
            window.open(src);
        });
        plugin.on("enable", function(){

        });
        plugin.on("disable", function(){

        });
        plugin.on("unload", function(){
        });

        /***** Register and define API *****/

        /**
         * Previewer for markdown content.
         **/
        plugin.freezePublicAPI({
        });

        register(null, {
            "preview.markdown": plugin
        });
    }
});<|endoftext|>


<commit_before>'use strict';

var BASE_URL = location.host;
var PORT     = location.port;

var funs = funs || {};

// GAME STATE
(function(funs){
    funs.state = {

    };
})(funs);

// WEB SOCKET
(function(funs){
    var ws;

    funs.websocket = {
        _ws : new WebSocket("ws://" + BASE_URL + '/ws'),
        _events : {},
        send : function(m){
            if(typeof m !== 'string'){
                m = JSON.stringify(m);
            }
            this._ws.send(m);
            return this;
        },
        on : function(event, callback){
            this._events[event] = this._events[event] || [];
            this._events[event].push(callback);
            return this;
        },
        off : function(event, callback){
            if(this._events[event]){
                if(callback){
                    this._events[event] = this._events[event].filter(function(c){
                        return callback !== c;
                    });
                }else{
                    delete this._events[event];
                }
            }
            return this;
        }
    };

    ws = funs.websocket._ws;

    ws.onopen = function(event){
        ws.onmessage = function (event) {
            var message = JSON.parse(event.data),
                events  = funs.websocket._events[message.type];
            if(events){
                events.forEach(function(callback){
                    callback(message.data);
                });
            }
        };
    };

})(funs);

// WEBSOCKET ROUTER
(function(funs){
    var ws = funs.websocket;

    var ROUTES = {
        'game-request-ok' :  function(data){
            console.log('game-request-ok', data);
            funs.state.uuid = data.uuid;
        },
        'start-game' : function(data){
            console.log('start-game', data);
            ws.send({
                type: 'start-game-ok',
                data: {
                    'game-id' : data['game-id']
                }
            });
        }
    };

    for(var route in ROUTES){
        ws.on(route, ROUTES[route]);
    }
})(funs);

// START
(function(funs){
    // draw cards on a field
    var flyingCards = document.getElementById('flyingCards');
    var CARDS = [
        "mutator_left_gap.svg"
        ,"mutator_pos_gap.svg"
        ,"mutator_right_gap.svg"
        ,"terminal_id.svg"
        ,"terminal_left_paren.svg"
        ,"terminal_left_square.svg"
        ,"terminal_num.svg"
        ,"terminal_right_paren.svg"
        ,"terminal_right_square.svg"
    ];
    var requested = false;

    drawCards();

    var $form = $('#startForm');
    $form.on('submit', function(e){
        e.preventDefault();
        if(requested){
            return;
        }
//        requested = true;
        funs.websocket.send({type:"game-request"});
    });

    function drawCards(){
        var $body = $('body');
        var width  = $body.width();
        var height = $body.height();
        var cards = React.createClass({
            render: function() {
                var R = React.DOM;
                var d = 75;
                var cards = this.props.cards;

                return R.div({
                    key: 'cards'
                }, cards.map(function(card, i){
                    var xr = Math.round(Math.random() * d);
                    var yr = Math.round(Math.random() * d);
                    var zr = Math.round(Math.random() * d);

                    var style = {
                        transform: 'rotateX( ' + xr + 'deg ) rotateY( ' + yr + 'deg ) rotateZ( ' + zr + 'deg ) ',
                        left : (200 + Math.random() * (width - 400)) + 'px',
                        top  : (200 + Math.random() * (height - 400)) + 'px'
                    };
                    return funs.Views.Card({
                        url: card,
                        style: style
                    });
                }));
            }
        });

        React.renderComponent(cards({
            cards : CARDS
        }), flyingCards);
    }

    function wait(){

    };
})(funs);

(function(funs){
})(funs);
<commit_msg>UI. Add 'user-name' to 'game-request' message<commit_after>'use strict';

var BASE_URL = location.host;
var PORT     = location.port;

var funs = funs || {};

// GAME STATE
(function(funs){
    funs.state = {

    };
})(funs);

// WEB SOCKET
(function(funs){
    var ws;

    funs.websocket = {
        _ws : new WebSocket("ws://" + BASE_URL + '/ws'),
        _events : {},
        send : function(m){
            if(typeof m !== 'string'){
                m = JSON.stringify(m);
            }
            this._ws.send(m);
            return this;
        },
        on : function(event, callback){
            this._events[event] = this._events[event] || [];
            this._events[event].push(callback);
            return this;
        },
        off : function(event, callback){
            if(this._events[event]){
                if(callback){
                    this._events[event] = this._events[event].filter(function(c){
                        return callback !== c;
                    });
                }else{
                    delete this._events[event];
                }
            }
            return this;
        }
    };

    ws = funs.websocket._ws;

    ws.onopen = function(event){
        ws.onmessage = function (event) {
            var message = JSON.parse(event.data),
                events  = funs.websocket._events[message.type];
            if(events){
                events.forEach(function(callback){
                    callback(message.data);
                });
            }
        };
    };

})(funs);

// WEBSOCKET ROUTER
(function(funs){
    var ws = funs.websocket;

    var ROUTES = {
        'game-request-ok' :  function(data){
            console.log('game-request-ok', data);
            funs.state.uuid = data.uuid;
        },
        'start-game' : function(data){
            console.log('start-game', data);
            funs.state['game-id'] = data['game-id'];
            ws.send({
                type: 'start-game-ok',
                data: {
                    'game-id' : data['game-id']
                }
            });
        }
    };

    for(var route in ROUTES){
        ws.on(route, ROUTES[route]);
    }
})(funs);

// START
(function(funs){
    // draw cards on a field
    var flyingCards = document.getElementById('flyingCards');
    var CARDS = [
        "mutator_left_gap.svg"
        ,"mutator_pos_gap.svg"
        ,"mutator_right_gap.svg"
        ,"terminal_id.svg"
        ,"terminal_left_paren.svg"
        ,"terminal_left_square.svg"
        ,"terminal_num.svg"
        ,"terminal_right_paren.svg"
        ,"terminal_right_square.svg"
    ];
    var requested = false;

    drawCards();

    var $form = $('#startForm');
    $form.on('submit', function(e){
        e.preventDefault();
        if(requested){
            return;
        }
        var name = $form.find('input[type=text]').eq(0).val();
//        requested = true;
        funs.websocket.send({
            type: "game-request",
            data: {
                'user-name' : name
            }
        });
    });

    function drawCards(){
        var $body = $('body');
        var width  = $body.width();
        var height = $body.height();
        var cards = React.createClass({
            render: function() {
                var R = React.DOM;
                var d = 75;
                var cards = this.props.cards;

                return R.div({
                    key: 'cards'
                }, cards.map(function(card, i){
                    var xr = Math.round(Math.random() * d);
                    var yr = Math.round(Math.random() * d);
                    var zr = Math.round(Math.random() * d);

                    var style = {
                        transform: 'rotateX( ' + xr + 'deg ) rotateY( ' + yr + 'deg ) rotateZ( ' + zr + 'deg ) ',
                        left : (200 + Math.random() * (width - 400)) + 'px',
                        top  : (200 + Math.random() * (height - 400)) + 'px'
                    };
                    return funs.Views.Card({
                        url: card,
                        style: style
                    });
                }));
            }
        });

        React.renderComponent(cards({
            cards : CARDS
        }), flyingCards);
    }

    function wait(){

    };
})(funs);

(function(funs){
})(funs);
<|endoftext|>


<commit_before>var m = require('mithril');
var classSet = require('common').classSet;
var plural = require('../../util').plural;

var firstRender = true;

function selector(data) {
  if (!firstRender && m.redraw.strategy() === 'diff') return {
    subtree: 'retain'
  };
  firstRender = false;
  return m('select.selector', {
    onchange: function(e) {
      location.href = '/practice/' + e.target.value;
    }
  }, [
    m('option[disabled][selected]', 'Practice list'),
    data.structure.map(function(section) {
      return m('optgroup', {
        label: section.name
      }, section.studies.map(function(study) {
        return m('option', {
          value: '/' + section.id + '/' + study.slug + '/' + study.id
        }, study.name);
      }));
    })
  ]);
};

function renderGoal(practice, inMoves) {
  switch (practice.goal().result) {
    case 'mate':
      return 'Checkmate the opponent';
    case 'mateIn':
      return 'Checkmate the opponent in ' + plural('move', inMoves);
    case 'drawIn':
      return 'Hold the draw for ' + plural('more move', inMoves);
    case 'equalIn':
      return 'Equalize in ' + plural('move', inMoves);
    case 'evalIn':
      return 'Get a winning position in ' + plural('move', inMoves);
    case 'promotion':
      return 'Safely promote your pawn';
  }
}

module.exports = {
  underboard: function(ctrl) {
    if (ctrl.vm.loading) return m('div.feedback', m.trust(lichess.spinnerHtml));
    var p = ctrl.practice;
    switch (p.success()) {
      case true:
        var next = p.nextChapter();
        if (next) return m('a.feedback.win', {
          onclick: function() {
            ctrl.setChapter(next.id);
          }
        }, [
          m('span', 'Success!'), [
            'Next: ',
            m('strong', next.name)
          ]
        ]);
        return m('a.feedback.win[href=/practice]', [
          m('span', 'Success!'),
          'Back to practice menu'
        ]);
      case false:
        return m('a.feedback.fail', {
          onclick: p.reset
        }, [
          m('span', renderGoal(p, p.goal().moves)),
          m('strong', 'Click to retry')
        ]);
      default:
        return m('div.feedback.ongoing', [
          m('div.goal', renderGoal(p, p.goal().moves - p.nbMoves())),
          p.comment() ? m('div.comment', p.comment()) : null
        ]);
    }
  },
  main: function(ctrl) {

    var current = ctrl.currentChapter();
    var data = ctrl.practice.data;

    return [
      m('div.title', [
        m('i.practice.icon.' + data.study.id),
        m('div.text', [
          m('h1', data.study.name),
          m('em', data.study.desc)
        ])
      ]),
      m('div', {
        key: 'chapters',
        class: 'list chapters',
        config: function(el, isUpdate) {
          if (!isUpdate)
            el.addEventListener('click', function(e) {
              e.preventDefault();
              var id = e.target.parentNode.getAttribute('data-id') || e.target.getAttribute('data-id');
              if (id) ctrl.setChapter(id);
              return false;
            });
        }
      }, [
        ctrl.chapters.list().map(function(chapter, i) {
          var loading = ctrl.vm.loading && chapter.id === ctrl.vm.nextChapterId;
          var active = !ctrl.vm.loading && current && current.id === chapter.id;
          var completion = data.completion[chapter.id] ? 'done' : 'ongoing';
          return [
            m('a', {
              href: data.url + '/' + chapter.id,
              key: chapter.id,
              'data-id': chapter.id,
              class: 'elem chapter ' + classSet({
                active: active,
                loading: loading
              })
            }, [
              m('span', {
                'data-icon': ((loading || active) && completion === 'ongoing') ? 'G' : 'E',
                class: 'status ' + completion
              }),
              m('h3', chapter.name)
            ])
          ];
        })
      ]),
      m('div.finally', [
        m('a.back', {
          'data-icon': 'I',
          href: '/practice',
          title: 'More practice'
        }),
        selector(data)
      ])
    ];
  }
};
<commit_msg>hackfix practice chapter switching - REVERT ME<commit_after>var m = require('mithril');
var classSet = require('common').classSet;
var plural = require('../../util').plural;

var firstRender = true;

function selector(data) {
  if (!firstRender && m.redraw.strategy() === 'diff') return {
    subtree: 'retain'
  };
  firstRender = false;
  return m('select.selector', {
    onchange: function(e) {
      location.href = '/practice/' + e.target.value;
    }
  }, [
    m('option[disabled][selected]', 'Practice list'),
    data.structure.map(function(section) {
      return m('optgroup', {
        label: section.name
      }, section.studies.map(function(study) {
        return m('option', {
          value: '/' + section.id + '/' + study.slug + '/' + study.id
        }, study.name);
      }));
    })
  ]);
};

function renderGoal(practice, inMoves) {
  switch (practice.goal().result) {
    case 'mate':
      return 'Checkmate the opponent';
    case 'mateIn':
      return 'Checkmate the opponent in ' + plural('move', inMoves);
    case 'drawIn':
      return 'Hold the draw for ' + plural('more move', inMoves);
    case 'equalIn':
      return 'Equalize in ' + plural('move', inMoves);
    case 'evalIn':
      return 'Get a winning position in ' + plural('move', inMoves);
    case 'promotion':
      return 'Safely promote your pawn';
  }
}

module.exports = {
  underboard: function(ctrl) {
    if (ctrl.vm.loading) return m('div.feedback', m.trust(lichess.spinnerHtml));
    var p = ctrl.practice;
    switch (p.success()) {
      case true:
        var next = p.nextChapter();
        if (next) return m('a.feedback.win', {
          onclick: function() {
            ctrl.setChapter(next.id);
          }
        }, [
          m('span', 'Success!'), [
            'Next: ',
            m('strong', next.name)
          ]
        ]);
        return m('a.feedback.win[href=/practice]', [
          m('span', 'Success!'),
          'Back to practice menu'
        ]);
      case false:
        return m('a.feedback.fail', {
          onclick: p.reset
        }, [
          m('span', renderGoal(p, p.goal().moves)),
          m('strong', 'Click to retry')
        ]);
      default:
        return m('div.feedback.ongoing', [
          m('div.goal', renderGoal(p, p.goal().moves - p.nbMoves())),
          p.comment() ? m('div.comment', p.comment()) : null
        ]);
    }
  },
  main: function(ctrl) {

    var current = ctrl.currentChapter();
    var data = ctrl.practice.data;

    return [
      m('div.title', [
        m('i.practice.icon.' + data.study.id),
        m('div.text', [
          m('h1', data.study.name),
          m('em', data.study.desc)
        ])
      ]),
      m('div', {
        key: 'chapters',
        class: 'list chapters',
        // config: function(el, isUpdate) {
        //   if (!isUpdate)
        //     el.addEventListener('click', function(e) {
        //       e.preventDefault();
        //       var id = e.target.parentNode.getAttribute('data-id') || e.target.getAttribute('data-id');
        //       if (id) ctrl.setChapter(id);
        //       return false;
        //     });
        // }
      }, [
        ctrl.chapters.list().map(function(chapter, i) {
          var loading = ctrl.vm.loading && chapter.id === ctrl.vm.nextChapterId;
          var active = !ctrl.vm.loading && current && current.id === chapter.id;
          var completion = data.completion[chapter.id] ? 'done' : 'ongoing';
          return [
            m('a', {
              href: data.url + '/' + chapter.id,
              key: chapter.id,
              'data-id': chapter.id,
              class: 'elem chapter ' + classSet({
                active: active,
                loading: loading
              })
            }, [
              m('span', {
                'data-icon': ((loading || active) && completion === 'ongoing') ? 'G' : 'E',
                class: 'status ' + completion
              }),
              m('h3', chapter.name)
            ])
          ];
        })
      ]),
      m('div.finally', [
        m('a.back', {
          'data-icon': 'I',
          href: '/practice',
          title: 'More practice'
        }),
        selector(data)
      ])
    ];
  }
};
<|endoftext|>


<commit_before>var participants = [];		//people currently running an instance of the hangout
var queue = [];				//list of participant ids waiting to speak. 0 is the current speaker
var timeOut = -1;				//unix time of when the current speaker's turn will end
var currentSpeakerId;		//id of the current speaker; on updates if it doesn't match queue, manager sets new time

var greenDot = gapi.hangout.av.effects.createImageResource('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90BDwMNIxL4LdEAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAuNJREFUeNrtmy2M2mAYx3/tTsxchliWnDi1kAxBhsE0aTBkbkPg0Ljhlp1CYnF3Dj03wbBnSJOaMywTXLKcOkGyICBnJrhsog9Lj7Xlo3y078s/qSx9/z+e9/N5XoMdy3Z5BpwBOaAgTw44B14CM2AC3ANDYCDPEBg5Fo+7bJ+xI9MvgBJQB97H/Lke0AH6jsU0sQBsFwMoAs0tmI6C0QJuHIs/iQAgxsvAFwnpfWgM1IDruCCMmObfAl+B1xxGd0DVsfi+VwC2y3MJxU8kQ22g6Vj83jkA2+UN8AM4IVmaAXnH4nadl8w1zX+Q6Slp5pE2DaWN2wdgu3wEuiRfXWnr9gDYLp+BS9KjS2lz/DFAaKbJvF8Nx+JqYwDSn7qkWxXH4tvaAGS0H6KGcmGzgxExzz8kdLTfdIo8DVonhA2CLYXMz6fI1koRIMvbAWqqsLhsNgI2Nj8PuLbfx94h699ALXaBssLmEW/lwAiQf//XHre0h9IYeDWPAn8EFDUwj3gsBnWBJvqo+aQLyBneBL2UcSym8wgooZ9K/i5Q1xBAHcCQc/sZeurExEta6KozEy9Lo6tyJl6qSlcVjgCOXcDL0uqqcxPIaAwgY6K5TA33AH5NTLzKDF11b6LO0fcmGpqoewC6igZHAMcuACONAYxMqcPraWi+51g8zhdCHQ0BdOYLIYC+hgD6/wBIBWZPs/Cf+iMAQrKniqrl3wvMdYOXNlJdY/H6FIDkymoaAKhFZYev8VLIqupOPBIIQMhUFQZQXSyu/u9ARCoo2gqabwcVVYedCDVRK1s0IyT7HQhAqqnyCgHIh1WSh54JSl1dRQHzlagK8shDUamwbKTYfCOqSnQpAIFwBVyk0PzFsjphWOPCRMqKphurmF8LgEBIQ/F0ZVnYbwxAIOh7ZcY3O5wmbLHUxiuGvl33Re2vzcXKDcqHs8C7PW+lx/LNbBzzsSNgIRr0vDobAkO/y9MRMBJ9ff4vhV3btM/DqFkAAAAASUVORK5CYII=');
var greenDotOverlay = greenDot.createOverlay(
	{'scale':
		{'magnitude': 0.15, 'reference': gapi.hangout.av.effects.ScaleReference.WIDTH}});

var yellowDot = gapi.hangout.av.effects.createImageResource('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90BDwMXCA5pLkoAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAuFJREFUeNrtmyts21AUhj97BiPVCiZLBUVzpAVUKykuqca2gLDgsoVNKwoMDWtZ8NhAFloSXJJpIJXmooJIVkCjkoFbbcAnk5vZzsN52Pfml8yS+P5fzn2ecy3WrMB3XgAHQBk4lqcMHAKvAQU8APfAAOjLMwCGrqee1tk+a02mXwGnwDnwIePPdYE20HM9Nc4tgMB3LOAEaKzAdBqMJnDjeupPLgCI8TPgq4T0JjQCasB1VhBWRvPvgG/AG7ajO6DqeurHRgEEvvNSQvEz+VALaLie+r12AIHvvAV+Ag75kgKOXE/dLvIle0HzH2V6ypt5pE0DaePqAQS+8wnokH91pK2rAxD4zhfgkuLoUtqcfQwQmkUyH1Xd9dTV0gCkP3Uotiqup74vDEBG+wF6qJw0O1gp8/xjTkf7ZafIvbh1QtIg2NTI/GSKbM4VAbK87aOnjqeXzVbMxubXFtf2m9g7lKIbqOkucKaxecTbWWwEyL8fbHBLuy2NAHcSBdEIODHAPOLxJK4LNDBHjWddQM7wHjBL+66nxpMIOMU8nUa7wLmBAM4BLDm3V5gpxyZMWpiqA5swS2OqyjZhqspUHe8A7LpAmKU1VYc2sG8wgH0bw2UbuAeI6sEmrMwwVfc2+hx9L6OBjb4HoPOovwOw6wIwNBjA0JY6vK6B5ruup54mC6G2gQDak4UQQM9AAL1/AKQCs2tY+I+jEQAJ2VNN1YzuBSa6IUwb6a6ReH0OQHJlNQMA1NKyw9eEKWRddSceiQUgZKoaA6hOF1f/dyAiFRQtDc234oqqd0VScZ+WDx5p9O8fJVWSJ54JSl1dRQPzlbQK8tRDUamwrBfYfD2tSnQmAIFwBVwU0PzFrDrhxEEwYWAsUtF0fR7zCwEQCEUonq7MCvulAQgEc6/MRGaHvZwtlloyz98u+kXjr81lyg3Ki0vA+w1vpUfyzlIW85kjYCoazLw6mwDDvMvTKTByfX3+L4KL/xorzXR/AAAAAElFTkSuQmCC');
	var yellowDotOverlay = yellowDot.createOverlay(
	{'scale':
		{'magnitude': 0.15, 'reference': gapi.hangout.av.effects.ScaleReference.WIDTH}});

var buttonDisabled = false;	//true when button is clicked to prevent joining the queue several times

gapi.hangout.onApiReady.add(function(eventObj){
	if (eventObj.isApiReady) {
		buttonDisabled = false;
		participants = gapi.hangout.getEnabledParticipants();
		printParticipants();

		gapi.hangout.onParticipantsChanged.add(onParticipantsChange);
		gapi.hangout.data.onStateChanged.add(onDataChange);

		//setInterval(updateTimeOutText, 200);
	}
});

function onParticipantsChange(eventObj) {
	participants = eventObj.participants;

	onServerUpdate();
};

function onDataChange (eventObj){
	var state = gapi.hangout.data.getState();
	queue = JSON.parse(state.queue);
	try{
		timeOut = JSON.parse((state.timeOut) ? state.timeOut : -1);
	}
	catch (e){
		console.log(e);
		timeOut = -1;
	}

	onServerUpdate();
}

function onServerUpdate(){
	console.log("onServerUpdate called");
	buttonDisabled = false;
	printParticipants();
	clearOverlay();
	var queuePosition = queue.indexOf(gapi.hangout.getParticipantId());
	if (queuePosition == 0){
		placeGreenDot();
		document.getElementById('queueButton').innerHTML = "End Early";
	}
	else if (queuePosition > 0){
		placeYellowDot();
		document.getElementById('queueButton').innerHTML = "Leave Queue";
	}
	else{
		document.getElementById('queueButton').innerHTML = "Raise Hand";
	}

	//lowest order participant manages time updates
	//and checks for disconnected queue memembers
	if (isManager()){
		console.log("managing update...");
		var validSpeaker = false;
		var updateRequired = false;
		while (queue.length > 0 && !validSpeaker){

			var speakerInHangout = false;
			for (var i = 0; i < participants.length; i++){
				if (participants[i].id == queue[0]){
					if (!participants[i].hasAppEnabled){
						console.log("speaker doesn't have app on");
						queue.shift();
						updateRequired = true;
						speakerInHangout = true;
					}
					else{
						console.log("speaker running app");
						validSpeaker = true;
						speakerInHangout = true;
					}
				}
			}
			if (!speakerInHangout){
				console.log("speaker not in hangout");
				queue.shift();
				updateRequire = true;
			}
		}
		if (timeOut == -1 || new Date().getTime() > timeOut){
			console.log('new timeout value required');
			//updateRequired = true;
		}
		if (updateRequired){
			console.log("sending new update");
			timeOut = new Date().getTime() + 1000*60;
			gapi.hangout.data.submitDelta({'queue':JSON.stringify(queue), 'timeOut': JSON.stringify(timeOut)});
		}
		//set time
	}
}

function printParticipants(){
	var members = "";
	if (queue.length > 0){
		document.getElementById('speakerName').innerHTML = getParticipantNameById(queue[0]);
	}
	for (var i = 1; i < queue.length; i++){
		members = members + getParticipantNameById(queue[i]) + '
';
	}
	for (var i = 0; i < participants.length; i++){
		if (queue.indexOf(participants[i].id) == -1){
			members = members + participants[i].person.displayName + '
';
		}
	}
	document.getElementById('otherParticipants').innerHTML = members;
}

function getParticipantNameById(id){
	for (var i = 0; i < participants.length; i++){
		if (participants.id == id){
			return participants[i].person.displayName;
		}
	}
	return '';
}

function buttonClick(){
	if (!buttonDisabled){
		buttonDisabled = true;
		var queuePosition = queue.indexOf(gapi.hangout.getParticipantId());
		if (queuePosition == 0){
			queue.shift();
			gapi.hangout.data.submitDelta({'queue':JSON.stringify(queue)});
		}
		else if (queuePosition == -1){
			queue.push(gapi.hangout.getParticipantId());
			gapi.hangout.data.submitDelta({'queue':JSON.stringify(queue)});
		}
	}
}

function placeGreenDot(){
	greenDotOverlay.setPosition(.4, -.35);
	greenDotOverlay.setVisible(true);
}

function placeYellowDot(){
	yellowDotOverlay.setPosition(.4, -.35);
	yellowDotOverlay.setVisible(true);
}

function clearOverlay(){
	greenDotOverlay.setVisible(false);
	yellowDotOverlay.setVisible(false);
}

function isManager(){
	return (participants[0].id == gapi.hangout.getParticipantId());
}

function updateTimeOutText(){
	if (timeOut != -1){
		document.getElementById('timeLeft').innerHTML = Math.round((timeOut - new Date.getTime()/1000));
	}
	else{
		document.getElementById('timeLeft').innerHTML = '';
	}
}<commit_msg>fixed getParticipantNameById()<commit_after>var participants = [];		//people currently running an instance of the hangout
var queue = [];				//list of participant ids waiting to speak. 0 is the current speaker
var timeOut = -1;				//unix time of when the current speaker's turn will end
var currentSpeakerId;		//id of the current speaker; on updates if it doesn't match queue, manager sets new time

var greenDot = gapi.hangout.av.effects.createImageResource('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90BDwMNIxL4LdEAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAuNJREFUeNrtmy2M2mAYx3/tTsxchliWnDi1kAxBhsE0aTBkbkPg0Ljhlp1CYnF3Dj03wbBnSJOaMywTXLKcOkGyICBnJrhsog9Lj7Xlo3y078s/qSx9/z+e9/N5XoMdy3Z5BpwBOaAgTw44B14CM2AC3ANDYCDPEBg5Fo+7bJ+xI9MvgBJQB97H/Lke0AH6jsU0sQBsFwMoAs0tmI6C0QJuHIs/iQAgxsvAFwnpfWgM1IDruCCMmObfAl+B1xxGd0DVsfi+VwC2y3MJxU8kQ22g6Vj83jkA2+UN8AM4IVmaAXnH4nadl8w1zX+Q6Slp5pE2DaWN2wdgu3wEuiRfXWnr9gDYLp+BS9KjS2lz/DFAaKbJvF8Nx+JqYwDSn7qkWxXH4tvaAGS0H6KGcmGzgxExzz8kdLTfdIo8DVonhA2CLYXMz6fI1koRIMvbAWqqsLhsNgI2Nj8PuLbfx94h699ALXaBssLmEW/lwAiQf//XHre0h9IYeDWPAn8EFDUwj3gsBnWBJvqo+aQLyBneBL2UcSym8wgooZ9K/i5Q1xBAHcCQc/sZeurExEta6KozEy9Lo6tyJl6qSlcVjgCOXcDL0uqqcxPIaAwgY6K5TA33AH5NTLzKDF11b6LO0fcmGpqoewC6igZHAMcuACONAYxMqcPraWi+51g8zhdCHQ0BdOYLIYC+hgD6/wBIBWZPs/Cf+iMAQrKniqrl3wvMdYOXNlJdY/H6FIDkymoaAKhFZYev8VLIqupOPBIIQMhUFQZQXSyu/u9ARCoo2gqabwcVVYedCDVRK1s0IyT7HQhAqqnyCgHIh1WSh54JSl1dRQHzlagK8shDUamwbKTYfCOqSnQpAIFwBVyk0PzFsjphWOPCRMqKphurmF8LgEBIQ/F0ZVnYbwxAIOh7ZcY3O5wmbLHUxiuGvl33Re2vzcXKDcqHs8C7PW+lx/LNbBzzsSNgIRr0vDobAkO/y9MRMBJ9ff4vhV3btM/DqFkAAAAASUVORK5CYII=');
var greenDotOverlay = greenDot.createOverlay(
	{'scale':
		{'magnitude': 0.15, 'reference': gapi.hangout.av.effects.ScaleReference.WIDTH}});

var yellowDot = gapi.hangout.av.effects.createImageResource('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90BDwMXCA5pLkoAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAuFJREFUeNrtmyts21AUhj97BiPVCiZLBUVzpAVUKykuqca2gLDgsoVNKwoMDWtZ8NhAFloSXJJpIJXmooJIVkCjkoFbbcAnk5vZzsN52Pfml8yS+P5fzn2ecy3WrMB3XgAHQBk4lqcMHAKvAQU8APfAAOjLMwCGrqee1tk+a02mXwGnwDnwIePPdYE20HM9Nc4tgMB3LOAEaKzAdBqMJnDjeupPLgCI8TPgq4T0JjQCasB1VhBWRvPvgG/AG7ajO6DqeurHRgEEvvNSQvEz+VALaLie+r12AIHvvAV+Ag75kgKOXE/dLvIle0HzH2V6ypt5pE0DaePqAQS+8wnokH91pK2rAxD4zhfgkuLoUtqcfQwQmkUyH1Xd9dTV0gCkP3Uotiqup74vDEBG+wF6qJw0O1gp8/xjTkf7ZafIvbh1QtIg2NTI/GSKbM4VAbK87aOnjqeXzVbMxubXFtf2m9g7lKIbqOkucKaxecTbWWwEyL8fbHBLuy2NAHcSBdEIODHAPOLxJK4LNDBHjWddQM7wHjBL+66nxpMIOMU8nUa7wLmBAM4BLDm3V5gpxyZMWpiqA5swS2OqyjZhqspUHe8A7LpAmKU1VYc2sG8wgH0bw2UbuAeI6sEmrMwwVfc2+hx9L6OBjb4HoPOovwOw6wIwNBjA0JY6vK6B5ruup54mC6G2gQDak4UQQM9AAL1/AKQCs2tY+I+jEQAJ2VNN1YzuBSa6IUwb6a6ReH0OQHJlNQMA1NKyw9eEKWRddSceiQUgZKoaA6hOF1f/dyAiFRQtDc234oqqd0VScZ+WDx5p9O8fJVWSJ54JSl1dRQPzlbQK8tRDUamwrBfYfD2tSnQmAIFwBVwU0PzFrDrhxEEwYWAsUtF0fR7zCwEQCEUonq7MCvulAQgEc6/MRGaHvZwtlloyz98u+kXjr81lyg3Ki0vA+w1vpUfyzlIW85kjYCoazLw6mwDDvMvTKTByfX3+L4KL/xorzXR/AAAAAElFTkSuQmCC');
	var yellowDotOverlay = yellowDot.createOverlay(
	{'scale':
		{'magnitude': 0.15, 'reference': gapi.hangout.av.effects.ScaleReference.WIDTH}});

var buttonDisabled = false;	//true when button is clicked to prevent joining the queue several times

gapi.hangout.onApiReady.add(function(eventObj){
	if (eventObj.isApiReady) {
		buttonDisabled = false;
		participants = gapi.hangout.getEnabledParticipants();
		printParticipants();

		gapi.hangout.onParticipantsChanged.add(onParticipantsChange);
		gapi.hangout.data.onStateChanged.add(onDataChange);

		//setInterval(updateTimeOutText, 200);
	}
});

function onParticipantsChange(eventObj) {
	participants = eventObj.participants;

	onServerUpdate();
};

function onDataChange (eventObj){
	var state = gapi.hangout.data.getState();
	queue = JSON.parse(state.queue);
	try{
		timeOut = JSON.parse((state.timeOut) ? state.timeOut : -1);
	}
	catch (e){
		console.log(e);
		timeOut = -1;
	}

	onServerUpdate();
}

function onServerUpdate(){
	console.log("onServerUpdate called");
	buttonDisabled = false;
	printParticipants();
	clearOverlay();
	var queuePosition = queue.indexOf(gapi.hangout.getParticipantId());
	if (queuePosition == 0){
		placeGreenDot();
		document.getElementById('queueButton').innerHTML = "End Early";
	}
	else if (queuePosition > 0){
		placeYellowDot();
		document.getElementById('queueButton').innerHTML = "Leave Queue";
	}
	else{
		document.getElementById('queueButton').innerHTML = "Raise Hand";
	}

	//lowest order participant manages time updates
	//and checks for disconnected queue memembers
	if (isManager()){
		console.log("managing update...");
		var validSpeaker = false;
		var updateRequired = false;
		while (queue.length > 0 && !validSpeaker){

			var speakerInHangout = false;
			for (var i = 0; i < participants.length; i++){
				if (participants[i].id == queue[0]){
					if (!participants[i].hasAppEnabled){
						console.log("speaker doesn't have app on");
						queue.shift();
						updateRequired = true;
						speakerInHangout = true;
					}
					else{
						console.log("speaker running app");
						validSpeaker = true;
						speakerInHangout = true;
					}
				}
			}
			if (!speakerInHangout){
				console.log("speaker not in hangout");
				queue.shift();
				updateRequire = true;
			}
		}
		if (timeOut == -1 || new Date().getTime() > timeOut){
			console.log('new timeout value required');
			//updateRequired = true;
		}
		if (updateRequired){
			console.log("sending new update");
			timeOut = new Date().getTime() + 1000*60;
			gapi.hangout.data.submitDelta({'queue':JSON.stringify(queue), 'timeOut': JSON.stringify(timeOut)});
		}
		//set time
	}
}

function printParticipants(){
	var members = "";
	if (queue.length > 0){
		document.getElementById('speakerName').innerHTML = getParticipantNameById(queue[0]);
	}
	for (var i = 1; i < queue.length; i++){
		members = members + getParticipantNameById(queue[i]) + '
';
	}
	for (var i = 0; i < participants.length; i++){
		if (queue.indexOf(participants[i].id) == -1){
			members = members + participants[i].person.displayName + '
';
		}
	}
	document.getElementById('otherParticipants').innerHTML = members;
}

function getParticipantNameById(id){
	for (var i = 0; i < participants.length; i++){
		if (participants[i].id == id){
			return participants[i].person.displayName;
		}
	}
	return '';
}

function buttonClick(){
	if (!buttonDisabled){
		buttonDisabled = true;
		var queuePosition = queue.indexOf(gapi.hangout.getParticipantId());
		if (queuePosition == 0){
			queue.shift();
			gapi.hangout.data.submitDelta({'queue':JSON.stringify(queue)});
		}
		else if (queuePosition == -1){
			queue.push(gapi.hangout.getParticipantId());
			gapi.hangout.data.submitDelta({'queue':JSON.stringify(queue)});
		}
	}
}

function placeGreenDot(){
	greenDotOverlay.setPosition(.4, -.35);
	greenDotOverlay.setVisible(true);
}

function placeYellowDot(){
	yellowDotOverlay.setPosition(.4, -.35);
	yellowDotOverlay.setVisible(true);
}

function clearOverlay(){
	greenDotOverlay.setVisible(false);
	yellowDotOverlay.setVisible(false);
}

function isManager(){
	return (participants[0].id == gapi.hangout.getParticipantId());
}

function updateTimeOutText(){
	if (timeOut != -1){
		document.getElementById('timeLeft').innerHTML = Math.round((timeOut - new Date.getTime()/1000));
	}
	else{
		document.getElementById('timeLeft').innerHTML = '';
	}
}<|endoftext|>