<commit_before>/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */

//
// Dalliance Genome Explorer
// (c) Thomas Down 2006-2014
//
// export-image.js
//

"use strict";

if (typeof(require) !== 'undefined') {
    var browser = require('./cbrowser');
    var Browser = browser.Browser;

    var g = require('./glyphs');
    var OverlayLabelCanvas = g.OverlayLabelCanvas;

    var nf = require('./numformats');
    var formatQuantLabel = nf.formatQuantLabel;
}

function fillTextRightJustified(g, text, x, y) {
    g.fillText(text, x - g.measureText(text).width, y);
}

Browser.prototype.exportImage = function(opts) {
    opts = opts || {};

    var fpw = this.featurePanelWidth;
    var padding = 3;
    var totHeight = 0;
    for (var ti = 0; ti < this.tiers.length; ++ti) {
        if (ti > 0)
            totHeight += padding;
        var tier = this.tiers[ti];
        if (tier.layoutHeight !== undefined)
            totHeight += tier.layoutHeight;
    }
    var mult = opts.resolutionMultiplier || 1.0;
    var margin = 200;

    var cw = ((fpw + margin) * mult)|0;
    var ch = (totHeight * mult)|0;
    var c = makeElement('canvas', null, {width: cw, height: ch});
    var g = c.getContext('2d');
    g.fillStyle = 'white';
    g.fillRect(0, 0, cw, ch);

    g.scale(mult, mult);

    var ypos = 0;
    for (var ti = 0; ti < this.tiers.length; ++ti) {
        var tier = this.tiers[ti];
        var offset = ((tier.glyphCacheOrigin - this.viewStart)*this.scale);

        var oc = new OverlayLabelCanvas();
        g.save();       // 1
        g.translate(0, ypos);

        g.save();       // 2
        g.beginPath();
        g.moveTo(margin, 0);
        g.lineTo(margin + fpw, 0);
        g.lineTo(margin + fpw, tier.layoutHeight);
        g.lineTo(margin, tier.layoutHeight);
        g.closePath();
        g.clip();
        g.translate(margin, 0);

        g.save();      // 3
        g.translate(offset, 0);
        if (tier.subtiers) {
            tier.paintToContext(g, oc, offset + 1000);
        } else {
            drawSeqTierGC(tier, tier.currentSequence, g);
        }
        g.restore();   // 2

        g.save()       // 3
        g.translate(offset, 0);
        oc.draw(g, -offset, fpw - offset);
        g.restore();   // 2
        g.restore();   // 1

        var hasQuant = false;
        var pos = 0;
        var subtiers = tier.subtiers || [];
        for (var sti = 0; sti < subtiers.length; ++sti) {
            var subtier = subtiers[sti];

            if (subtier.quant) {
                hasQuant = true;
                var q = subtier.quant;
                var h = subtier.height;

                var numTics = 2;
                if (h > 40) {
                    numTics = 1 + ((h/20) | 0);
                }
                var ticSpacing = h / (numTics - 1);
                var ticInterval = (q.max - q.min) / (numTics - 1);

                g.beginPath();
                g.moveTo(margin + 5, pos);
                g.lineTo(margin, pos);
                g.lineTo(margin, pos + subtier.height);
                g.lineTo(margin + 5, pos + subtier.height);
                for (var t = 1; t < numTics-1; ++t) {
                    var ty = t*ticSpacing;
                    g.moveTo(margin, pos + ty);
                    g.lineTo(margin+3, pos + ty);
                }
                g.strokeStyle = 'black';
                g.strokeWidth = 2;
                g.stroke();

                g.fillStyle = 'black';
                fillTextRightJustified(g, formatQuantLabel(q.max), margin - 3, pos + 7);
                fillTextRightJustified(g, formatQuantLabel(q.min), margin - 3, pos + subtier.height);
                for (var t = 1; t < numTics-1; ++t) {
                    var ty = t*ticSpacing;
                    fillTextRightJustified(g, formatQuantLabel((1.0*q.max) - (t*ticInterval)), margin - 3, pos + ty + 3);
                }
            }

            pos += subtier.height + padding;
        }

        var labelName;
        if (typeof tier.config.name === 'string')
            labelName = tier.config.name;
        else
            labelName = tier.dasSource.name;
        var labelWidth = g.measureText(labelName).width;
        g.fillStyle = 'black';
        g.fillText(labelName, margin - (hasQuant ? 22 : 12) - labelWidth, (tier.layoutHeight + 6) / 2);

        g.restore(); // 0

        ypos += tier.layoutHeight + padding;
    }

    if (opts.highlights) {
        g.save();

        g.beginPath();
        g.moveTo(margin, 0);
        g.lineTo(margin + fpw, 0);
        g.lineTo(margin + fpw, ypos);
        g.lineTo(margin, ypos);
        g.closePath();
        g.clip();

        g.translate(margin + offset, 0);
        var origin = this.viewStart;
        var visStart = this.viewStart;
        var visEnd = this.viewEnd;

        for (var hi = 0; hi < this.highlights.length; ++hi) {
            var h = this.highlights[hi];
            if (((h.chr === this.chr) || (h.chr === ('chr' + this.chr))) && h.min < visEnd && h.max > visStart) {
                g.globalAlpha = this.defaultHighlightAlpha;
                g.fillStyle = this.defaultHighlightFill;
                g.fillRect((h.min - origin) * this.scale,
                           0,
                           (h.max - h.min) * this.scale,
                           ypos);
            }
        }
        g.restore();
    }

    var rulerPos = -1;
    if (opts.ruler == 'center') {
        rulerPos = margin + ((this.viewEnd - this.viewStart + 1)*this.scale) / 2;
    } else if (opts.ruler == 'left') {
        rulerPos = margin;
    } else if (opts.ruler == 'right') {
        rulerPos = margin + ((this.viewEnd - this.viewStart + 1)*this.scale);
    }
    if (rulerPos >= 0) {
        g.strokeStyle = 'blue';
        g.beginPath();
        g.moveTo(rulerPos, 0);
        g.lineTo(rulerPos, ypos);
        g.stroke();
    }

    return c.toDataURL('image/png');
}<commit_msg>Imports.<commit_after>/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */

//
// Dalliance Genome Explorer
// (c) Thomas Down 2006-2014
//
// export-image.js
//

"use strict";

if (typeof(require) !== 'undefined') {
    var browser = require('./cbrowser');
    var Browser = browser.Browser;

    var g = require('./glyphs');
    var OverlayLabelCanvas = g.OverlayLabelCanvas;

    var nf = require('./numformats');
    var formatQuantLabel = nf.formatQuantLabel;

    var drawSeqTierGC = require('./sequence-draw').drawSeqTierGC;
}

function fillTextRightJustified(g, text, x, y) {
    g.fillText(text, x - g.measureText(text).width, y);
}

Browser.prototype.exportImage = function(opts) {
    opts = opts || {};

    var fpw = this.featurePanelWidth;
    var padding = 3;
    var totHeight = 0;
    for (var ti = 0; ti < this.tiers.length; ++ti) {
        if (ti > 0)
            totHeight += padding;
        var tier = this.tiers[ti];
        if (tier.layoutHeight !== undefined)
            totHeight += tier.layoutHeight;
    }
    var mult = opts.resolutionMultiplier || 1.0;
    var margin = 200;

    var cw = ((fpw + margin) * mult)|0;
    var ch = (totHeight * mult)|0;
    var c = makeElement('canvas', null, {width: cw, height: ch});
    var g = c.getContext('2d');
    g.fillStyle = 'white';
    g.fillRect(0, 0, cw, ch);

    g.scale(mult, mult);

    var ypos = 0;
    for (var ti = 0; ti < this.tiers.length; ++ti) {
        var tier = this.tiers[ti];
        var offset = ((tier.glyphCacheOrigin - this.viewStart)*this.scale);

        var oc = new OverlayLabelCanvas();
        g.save();       // 1
        g.translate(0, ypos);

        g.save();       // 2
        g.beginPath();
        g.moveTo(margin, 0);
        g.lineTo(margin + fpw, 0);
        g.lineTo(margin + fpw, tier.layoutHeight);
        g.lineTo(margin, tier.layoutHeight);
        g.closePath();
        g.clip();
        g.translate(margin, 0);

        g.save();      // 3
        g.translate(offset, 0);
        if (tier.subtiers) {
            tier.paintToContext(g, oc, offset + 1000);
        } else {
            drawSeqTierGC(tier, tier.currentSequence, g);
        }
        g.restore();   // 2

        g.save()       // 3
        g.translate(offset, 0);
        oc.draw(g, -offset, fpw - offset);
        g.restore();   // 2
        g.restore();   // 1

        var hasQuant = false;
        var pos = 0;
        var subtiers = tier.subtiers || [];
        for (var sti = 0; sti < subtiers.length; ++sti) {
            var subtier = subtiers[sti];

            if (subtier.quant) {
                hasQuant = true;
                var q = subtier.quant;
                var h = subtier.height;

                var numTics = 2;
                if (h > 40) {
                    numTics = 1 + ((h/20) | 0);
                }
                var ticSpacing = h / (numTics - 1);
                var ticInterval = (q.max - q.min) / (numTics - 1);

                g.beginPath();
                g.moveTo(margin + 5, pos);
                g.lineTo(margin, pos);
                g.lineTo(margin, pos + subtier.height);
                g.lineTo(margin + 5, pos + subtier.height);
                for (var t = 1; t < numTics-1; ++t) {
                    var ty = t*ticSpacing;
                    g.moveTo(margin, pos + ty);
                    g.lineTo(margin+3, pos + ty);
                }
                g.strokeStyle = 'black';
                g.strokeWidth = 2;
                g.stroke();

                g.fillStyle = 'black';
                fillTextRightJustified(g, formatQuantLabel(q.max), margin - 3, pos + 7);
                fillTextRightJustified(g, formatQuantLabel(q.min), margin - 3, pos + subtier.height);
                for (var t = 1; t < numTics-1; ++t) {
                    var ty = t*ticSpacing;
                    fillTextRightJustified(g, formatQuantLabel((1.0*q.max) - (t*ticInterval)), margin - 3, pos + ty + 3);
                }
            }

            pos += subtier.height + padding;
        }

        var labelName;
        if (typeof tier.config.name === 'string')
            labelName = tier.config.name;
        else
            labelName = tier.dasSource.name;
        var labelWidth = g.measureText(labelName).width;
        g.fillStyle = 'black';
        g.fillText(labelName, margin - (hasQuant ? 22 : 12) - labelWidth, (tier.layoutHeight + 6) / 2);

        g.restore(); // 0

        ypos += tier.layoutHeight + padding;
    }

    if (opts.highlights) {
        g.save();

        g.beginPath();
        g.moveTo(margin, 0);
        g.lineTo(margin + fpw, 0);
        g.lineTo(margin + fpw, ypos);
        g.lineTo(margin, ypos);
        g.closePath();
        g.clip();

        g.translate(margin + offset, 0);
        var origin = this.viewStart;
        var visStart = this.viewStart;
        var visEnd = this.viewEnd;

        for (var hi = 0; hi < this.highlights.length; ++hi) {
            var h = this.highlights[hi];
            if (((h.chr === this.chr) || (h.chr === ('chr' + this.chr))) && h.min < visEnd && h.max > visStart) {
                g.globalAlpha = this.defaultHighlightAlpha;
                g.fillStyle = this.defaultHighlightFill;
                g.fillRect((h.min - origin) * this.scale,
                           0,
                           (h.max - h.min) * this.scale,
                           ypos);
            }
        }
        g.restore();
    }

    var rulerPos = -1;
    if (opts.ruler == 'center') {
        rulerPos = margin + ((this.viewEnd - this.viewStart + 1)*this.scale) / 2;
    } else if (opts.ruler == 'left') {
        rulerPos = margin;
    } else if (opts.ruler == 'right') {
        rulerPos = margin + ((this.viewEnd - this.viewStart + 1)*this.scale);
    }
    if (rulerPos >= 0) {
        g.strokeStyle = 'blue';
        g.beginPath();
        g.moveTo(rulerPos, 0);
        g.lineTo(rulerPos, ypos);
        g.stroke();
    }

    return c.toDataURL('image/png');
}<|endoftext|>


<commit_before>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  this.setup();
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var value = Math.random() < 0.9 ? 2048 : 4096;
    var tile = new Tile(this.grid.randomAvailableCell(), value);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() > this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && next.value === tile.value && !next.mergedFrom) {
          var merged = new Tile(positions.next, tile.value * 2);
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score -= merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<commit_msg>Update game_manager.js<commit_after>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.startTiles     = 2;

  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  this.setup();
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;

    // Add the initial tiles
    this.addStartTiles();
  }

  // Update the actuator
  this.actuate();
};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    var value = Math.random() < 0.9 ? 2 : 4;
    var tile = new Tile(this.grid.randomAvailableCell(), value);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() > this.score) {
    this.storageManager.setBestScore(this.score);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    over:       this.over,
    won:        this.won,
    bestScore:  this.storageManager.getBestScore(),
    terminated: this.isGameTerminated()
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    over:        this.over,
    won:         this.won,
    keepPlaying: this.keepPlaying
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left
  var self = this;

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && next.value === tile.value && !next.mergedFrom) {
          var merged = new Tile(positions.next, tile.value * 2);
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score -= merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {
          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<|endoftext|>


<commit_before>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.startTiles     = 2;

/*   this.inputManager.on("crowd", this.crowd.bind(this)); */
  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  //this.undoStack = [];

  this.setup();

}

// Set Captions
function caption(exp) {
  var caption = [];
  caption[2]    = 'ciondolo piastrina a cuore';
  caption[4]    = 'ciondolo a cuore';
  caption[8]    = 'bracciale di perle';
  caption[16]   = 'bracciale love Paloma Picasso';
  caption[32]   = 'anello cocktail in ametista';
  caption[64]   = 'anello Sommerset in oro rosa';
  caption[128]  = 'orecchini lunghi Hearths';
  caption[256]  = 'orecchini a cerchio rope';
  caption[512]  = 'collana a sciarpa Mesh';
  caption[1024] = 'bracciale stretto Schlunberger';
  caption[2048] = 'collana di diamanti';

  return caption[exp];
}

// Set Prices
function prezzo(exp) {
  var prezzo = [];
  prezzo[2]=70;
  prezzo[4]=250;
  prezzo[8]=420;
  prezzo[16]=670;
  prezzo[32]=1050;
  prezzo[64]=2000;
  prezzo[128]=4350;
  prezzo[256]=8300;
  prezzo[512]=11700;
  prezzo[1024]=54000;
  prezzo[2048]=149000;

  return prezzo[exp];
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Crowd board
GameManager.prototype.crowd = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.grid        = new Grid(this.size);
  this.score       = 0;
  this.level       = 2;
  this.over        = false;
  this.won         = false;
  this.keepPlaying = false;
  this.actuate();
  var counter = 0;
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 4; j++) {
      counter++;
      var value = Math.pow(2, counter);
      var tile = new Tile({ x: j, y: i }, value);
      if (value <= 2048) this.grid.insertTile(tile);
    }
  }
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.level       = previousState.level;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
    this.seed        = previousState.seed;
    this.undoStack   = previousState.undoStack;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.level       = 2;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;
    this.seed        = Math.random();
    this.undoStack   = [];

    // Add the initial tiles
    this.addStartTiles();
  }

  // Fill legend
  this.fillLegend();

  // Update the actuator
  this.actuate();
};

// Fill legend
GameManager.prototype.fillLegend = function () {

  var legend = document.getElementsByClassName("tile-legend");
  for (var i = 1; i <= 11; i++) {
    var exp = Math.pow(2, i);
    var row = document.createElement("div");
    var grid = document.createElement("div");
    var cell = document.createElement("div");
    var img = document.createElement("img");
    var p = document.createElement("p");
    row.classList.add('legend-row');
    grid.classList.add('legend-grid');
    cell.classList.add('legend-cell');
    cell.classList.add('cell-' + exp);
    img.src = "style/img/" + exp + ".jpg";
    cell.appendChild(img);
    grid.appendChild(cell);
    row.appendChild(grid);
    p.innerHTML = caption(exp) + "
" + prezzo(exp) + ' €';
    row.appendChild(p);

    legend[0].appendChild(row);
  }

};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    Math.seedrandom(this.seed);
    for (var i=0; i<this.score; i++) {
      Math.random();
    }
    var value = Math.random() < 0.9 ? 2 : 4;
    var tile = new Tile(this.grid.randomAvailableCell(), value);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }
  if (this.storageManager.getBestLevel() < this.level) {
    this.storageManager.setBestLevel(this.level);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    level:      this.level,
    over:       this.over,
    won:        this.won,
    seed:       this.seed,
    bestScore:  this.storageManager.getBestScore(),
    bestLevel:  this.storageManager.getBestLevel(),
    terminated: this.isGameTerminated(),
    undoStack:  this.undoStack
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    level:       this.level,
    over:        this.over,
    won:         this.won,
    seed:        this.seed,
    keepPlaying: this.keepPlaying,
    undoStack:   this.undoStack
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left, -1 undo
  var self = this;

  if (direction == -1) {
    if (this.undoStack.length > 0) {
      var prev = this.undoStack.pop();

      this.grid.build();
      this.score = prev.score;
      for (var i in prev.tiles) {
        var t = prev.tiles[i];
        var tile = new Tile({x: t.x, y: t.y}, t.value);
        tile.previousPosition = {
          x: t.previousPosition.x,
          y: t.previousPosition.y
        };
        this.grid.cells[tile.x][tile.y] = tile;
      }
      this.over = false;
      this.won = false;
      this.keepPlaying = false;
      this.actuator.continueGame();
      this.actuate();
    }
    return;
  }

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;
  var undo       = {score: this.score, tiles: []};

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && next.value === tile.value && !next.mergedFrom) {

          // We need to save tile since it will get removed
          undo.tiles.push(tile.save(positions.next));

          //var merged = new Tile(positions.next, prezzo[tile.value]);
          var merged = new Tile(positions.next, tile.value * 2);
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score += prezzo(merged.value);
          if (merged.value > self.level) self.level = merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {

          // Save backup information
          undo.tiles.push(tile.save(positions.farthest));

          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    // Save state
    this.undoStack.push(undo);

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<commit_msg>Update game_manager.js<commit_after>function GameManager(size, InputManager, Actuator, StorageManager) {
  this.size           = size; // Size of the grid
  this.inputManager   = new InputManager;
  this.storageManager = new StorageManager;
  this.actuator       = new Actuator;

  this.startTiles     = 2;

/*   this.inputManager.on("crowd", this.crowd.bind(this)); */
  this.inputManager.on("move", this.move.bind(this));
  this.inputManager.on("restart", this.restart.bind(this));
  this.inputManager.on("keepPlaying", this.keepPlaying.bind(this));

  //this.undoStack = [];

  this.setup();

}

// Set Captions
function caption(exp) {
  var caption = [];
  caption[2]    = 'ciondolo piastrina a cuore';
  caption[4]    = 'ciondolo a cuore';
  caption[8]    = 'bracciale di perle';
  caption[16]   = 'bracciale love Paloma Picasso';
  caption[32]   = 'anello cocktail in ametista';
  caption[64]   = 'anello Sommerset in oro rosa';
  caption[128]  = 'orecchini lunghi Hearths';
  caption[256]  = 'orecchini a cerchio rope';
  caption[512]  = 'collana a sciarpa Mesh';
  caption[1024] = 'bracciale stretto Schlunberger';
  caption[2048] = 'collana di diamanti';

  return caption[exp];
}

// Set Prices
function prezzo(exp) {
  var prezzo = [];
  prezzo[2]=35;
  prezzo[4]=150;
  prezzo[8]=320;
  prezzo[16]=875;
  prezzo[32]=1425;
  prezzo[64]=2100;
  prezzo[128]=4000;
  prezzo[256]=7600;
  prezzo[512]=14400;
  prezzo[1024]=33000;
  prezzo[2048]=71800;
  prezzo[4096]=122500;
  prezzo[8192]=212200;
  prezzo[16384]=515500;
  prezzo[32768]=948800;
  prezzo[65536]=1625500;
  prezzo[131072]=2888900;

  return prezzo[exp];
}

// Restart the game
GameManager.prototype.restart = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.setup();
};

// Crowd board
GameManager.prototype.crowd = function () {
  this.storageManager.clearGameState();
  this.actuator.continueGame(); // Clear the game won/lost message
  this.grid        = new Grid(this.size);
  this.score       = 0;
  this.level       = 2;
  this.over        = false;
  this.won         = false;
  this.keepPlaying = false;
  this.actuate();
  var counter = 0;
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 4; j++) {
      counter++;
      var value = Math.pow(2, counter);
      var tile = new Tile({ x: j, y: i }, value);
      if (value <= 2048) this.grid.insertTile(tile);
    }
  }
};

// Keep playing after winning (allows going over 2048)
GameManager.prototype.keepPlaying = function () {
  this.keepPlaying = true;
  this.actuator.continueGame(); // Clear the game won/lost message
};

// Return true if the game is lost, or has won and the user hasn't kept playing
GameManager.prototype.isGameTerminated = function () {
  if (this.over || (this.won && !this.keepPlaying)) {
    return true;
  } else {
    return false;
  }
};

// Set up the game
GameManager.prototype.setup = function () {
  var previousState = this.storageManager.getGameState();

  // Reload the game from a previous game if present
  if (previousState) {
    this.grid        = new Grid(previousState.grid.size,
                                previousState.grid.cells); // Reload grid
    this.score       = previousState.score;
    this.level       = previousState.level;
    this.over        = previousState.over;
    this.won         = previousState.won;
    this.keepPlaying = previousState.keepPlaying;
    this.seed        = previousState.seed;
    this.undoStack   = previousState.undoStack;
  } else {
    this.grid        = new Grid(this.size);
    this.score       = 0;
    this.level       = 2;
    this.over        = false;
    this.won         = false;
    this.keepPlaying = false;
    this.seed        = Math.random();
    this.undoStack   = [];

    // Add the initial tiles
    this.addStartTiles();
  }

  // Fill legend
  this.fillLegend();

  // Update the actuator
  this.actuate();
};

// Fill legend
GameManager.prototype.fillLegend = function () {

  var legend = document.getElementsByClassName("tile-legend");
  for (var i = 1; i <= 11; i++) {
    var exp = Math.pow(2, i);
    var row = document.createElement("div");
    var grid = document.createElement("div");
    var cell = document.createElement("div");
    var img = document.createElement("img");
    var p = document.createElement("p");
    row.classList.add('legend-row');
    grid.classList.add('legend-grid');
    cell.classList.add('legend-cell');
    cell.classList.add('cell-' + exp);
    img.src = "style/img/" + exp + ".jpg";
    cell.appendChild(img);
    grid.appendChild(cell);
    row.appendChild(grid);
    p.innerHTML = caption(exp) + "
" + prezzo(exp) + ' €';
    row.appendChild(p);

    legend[0].appendChild(row);
  }

};

// Set up the initial tiles to start the game with
GameManager.prototype.addStartTiles = function () {
  for (var i = 0; i < this.startTiles; i++) {
    this.addRandomTile();
  }
};

// Adds a tile in a random position
GameManager.prototype.addRandomTile = function () {
  if (this.grid.cellsAvailable()) {
    Math.seedrandom(this.seed);
    for (var i=0; i<this.score; i++) {
      Math.random();
    }
    var value = Math.random() < 0.9 ? 2 : 4;
    var tile = new Tile(this.grid.randomAvailableCell(), value);

    this.grid.insertTile(tile);
  }
};

// Sends the updated grid to the actuator
GameManager.prototype.actuate = function () {
  if (this.storageManager.getBestScore() < this.score) {
    this.storageManager.setBestScore(this.score);
  }
  if (this.storageManager.getBestLevel() < this.level) {
    this.storageManager.setBestLevel(this.level);
  }

  // Clear the state when the game is over (game over only, not win)
  if (this.over) {
    this.storageManager.clearGameState();
  } else {
    this.storageManager.setGameState(this.serialize());
  }

  this.actuator.actuate(this.grid, {
    score:      this.score,
    level:      this.level,
    over:       this.over,
    won:        this.won,
    seed:       this.seed,
    bestScore:  this.storageManager.getBestScore(),
    bestLevel:  this.storageManager.getBestLevel(),
    terminated: this.isGameTerminated(),
    undoStack:  this.undoStack
  });

};

// Represent the current game as an object
GameManager.prototype.serialize = function () {
  return {
    grid:        this.grid.serialize(),
    score:       this.score,
    level:       this.level,
    over:        this.over,
    won:         this.won,
    seed:        this.seed,
    keepPlaying: this.keepPlaying,
    undoStack:   this.undoStack
  };
};

// Save all tile positions and remove merger info
GameManager.prototype.prepareTiles = function () {
  this.grid.eachCell(function (x, y, tile) {
    if (tile) {
      tile.mergedFrom = null;
      tile.savePosition();
    }
  });
};

// Move a tile and its representation
GameManager.prototype.moveTile = function (tile, cell) {
  this.grid.cells[tile.x][tile.y] = null;
  this.grid.cells[cell.x][cell.y] = tile;
  tile.updatePosition(cell);
};

// Move tiles on the grid in the specified direction
GameManager.prototype.move = function (direction) {
  // 0: up, 1: right, 2: down, 3: left, -1 undo
  var self = this;

  if (direction == -1) {
    if (this.undoStack.length > 0) {
      var prev = this.undoStack.pop();

      this.grid.build();
      this.score = prev.score;
      for (var i in prev.tiles) {
        var t = prev.tiles[i];
        var tile = new Tile({x: t.x, y: t.y}, t.value);
        tile.previousPosition = {
          x: t.previousPosition.x,
          y: t.previousPosition.y
        };
        this.grid.cells[tile.x][tile.y] = tile;
      }
      this.over = false;
      this.won = false;
      this.keepPlaying = false;
      this.actuator.continueGame();
      this.actuate();
    }
    return;
  }

  if (this.isGameTerminated()) return; // Don't do anything if the game's over

  var cell, tile;

  var vector     = this.getVector(direction);
  var traversals = this.buildTraversals(vector);
  var moved      = false;
  var undo       = {score: this.score, tiles: []};

  // Save the current tile positions and remove merger information
  this.prepareTiles();

  // Traverse the grid in the right direction and move tiles
  traversals.x.forEach(function (x) {
    traversals.y.forEach(function (y) {
      cell = { x: x, y: y };
      tile = self.grid.cellContent(cell);

      if (tile) {
        var positions = self.findFarthestPosition(cell, vector);
        var next      = self.grid.cellContent(positions.next);

        // Only one merger per row traversal?
        if (next && next.value === tile.value && !next.mergedFrom) {

          // We need to save tile since it will get removed
          undo.tiles.push(tile.save(positions.next));

          //var merged = new Tile(positions.next, prezzo[tile.value]);
          var merged = new Tile(positions.next, tile.value * 2);
          merged.mergedFrom = [tile, next];

          self.grid.insertTile(merged);
          self.grid.removeTile(tile);

          // Converge the two tiles' positions
          tile.updatePosition(positions.next);

          // Update the score
          self.score += prezzo(merged.value);
          if (merged.value > self.level) self.level = merged.value;

          // The mighty 2048 tile
          if (merged.value === 2048) self.won = true;
        } else {

          // Save backup information
          undo.tiles.push(tile.save(positions.farthest));

          self.moveTile(tile, positions.farthest);
        }

        if (!self.positionsEqual(cell, tile)) {
          moved = true; // The tile moved from its original cell!
        }
      }
    });
  });

  if (moved) {
    this.addRandomTile();

    if (!this.movesAvailable()) {
      this.over = true; // Game over!
    }

    // Save state
    this.undoStack.push(undo);

    this.actuate();
  }
};

// Get the vector representing the chosen direction
GameManager.prototype.getVector = function (direction) {
  // Vectors representing tile movement
  var map = {
    0: { x: 0,  y: -1 }, // Up
    1: { x: 1,  y: 0 },  // Right
    2: { x: 0,  y: 1 },  // Down
    3: { x: -1, y: 0 }   // Left
  };

  return map[direction];
};

// Build a list of positions to traverse in the right order
GameManager.prototype.buildTraversals = function (vector) {
  var traversals = { x: [], y: [] };

  for (var pos = 0; pos < this.size; pos++) {
    traversals.x.push(pos);
    traversals.y.push(pos);
  }

  // Always traverse from the farthest cell in the chosen direction
  if (vector.x === 1) traversals.x = traversals.x.reverse();
  if (vector.y === 1) traversals.y = traversals.y.reverse();

  return traversals;
};

GameManager.prototype.findFarthestPosition = function (cell, vector) {
  var previous;

  // Progress towards the vector direction until an obstacle is found
  do {
    previous = cell;
    cell     = { x: previous.x + vector.x, y: previous.y + vector.y };
  } while (this.grid.withinBounds(cell) &&
           this.grid.cellAvailable(cell));

  return {
    farthest: previous,
    next: cell // Used to check if a merge is required
  };
};

GameManager.prototype.movesAvailable = function () {
  return this.grid.cellsAvailable() || this.tileMatchesAvailable();
};

// Check for available matches between tiles (more expensive check)
GameManager.prototype.tileMatchesAvailable = function () {
  var self = this;

  var tile;

  for (var x = 0; x < this.size; x++) {
    for (var y = 0; y < this.size; y++) {
      tile = this.grid.cellContent({ x: x, y: y });

      if (tile) {
        for (var direction = 0; direction < 4; direction++) {
          var vector = self.getVector(direction);
          var cell   = { x: x + vector.x, y: y + vector.y };

          var other  = self.grid.cellContent(cell);

          if (other && other.value === tile.value) {
            return true; // These two tiles can be merged
          }
        }
      }
    }
  }

  return false;
};

GameManager.prototype.positionsEqual = function (first, second) {
  return first.x === second.x && first.y === second.y;
};
<|endoftext|>


<commit_before>// Plugin to jQuery
(function($) {
	/*
	// Create a universal animation function that optimizes for the browsers capabilities
	$.fn.optAnimate = function(props, speed, easing, callback) {
		// If modernizr reports that the browser supports CSS animations, and the animateWithCss function has correctly been loaded/defined
		if (Modernizr.cssanimations && typeof($.fn.animateWithCss) == 'function') {
			return $(this).animateWithCss(props, speed, easing, callback);
		}
		else {
			return $(this).animate(props, speed, easing, callback);
		}
	};
	*/
})(jQuery);

// Bind events to be triggered BEFORE jQuery Mobile loads/executes
$(document).bind('mobileinit', function() {
	// Set jQuery mobile settings
	//$.mobile.touchOverflowEnabled = true;
});

// Bind events to be triggered on page initialization
$(document).bind('pageinit', function() {
	// Detect orientation change
	// Bind the event to the window
	$(window).bind('orientationchange', function(event){
		changeOrientation(event.orientation);
	});

	// Detect and mark the "middle" elements of the dashboard
	function detectMiddleElements() {
		// Grab the width of the entire dashboard
		var dashWidth = parseInt($('nav#dashboard').width());

		// Grab the percentage width of the first element
		var elemWidth = parseInt($('nav#dashboard ul#dashboard-mapps li').width());

		// Find the number of elements per row
		var elemPerRow = Math.floor(dashWidth / elemWidth);

		// Set the number of elements per row as a css class on the dashboard tag
		$('nav#dashboard').addClass(elemPerRow + '-per-row');

		// Calculate the middle-th element
		var middleCount = Math.ceil(elemPerRow / 2);

		// Create the nth-child expression
		var everyNthChild = elemPerRow + String('n+') + middleCount;
		console.log(elemPerRow);
		console.log(middleCount);
		console.log(everyNthChild);
		console.log(elemPerRow + 'n+' + middleCount);

		// Finally, set every middle-th element to have a class
		$('nav#dashboard ul#dashboard-mapps li:nth-child(' + everyNthChild +')').addClass('dash-middle-element');
	}

	// Get the device's OS and add it as a class to the HTML tag
	// To be used for OS-specific styling (trying to stay more true to the native styling)
	function deviceOS() {
		var deviceOS = '';

		// Get the data
		var devicePlatform = navigator.userAgent.match(/Android/i)
			|| navigator.userAgent.match(/iPod/i)
			|| navigator.userAgent.match(/iPad/i)
			|| navigator.userAgent.match(/iPhone/i)
			|| navigator.userAgent.match(/webOS/i)
			|| 'Other';

		if (devicePlatform == 'iPod' || devicePlatform == 'iPad' || devicePlatform == 'iPhone') {
			deviceOS = 'ios';
		}
		else if (devicePlatform == 'Android') {
			deviceOS = 'android';
		}
		else {
			deviceOS = devicePlatform.toLowerCase();
		}

		// Add the deviceOS as a CSS class to the HTML tag
		$('html').addClass(deviceOS);

		return deviceOS;
	}

	// Detect device and meta info and display it on the page
	function deviceInfo() {
		// Get the data
		var devicePlatform = deviceOS();
		var displayDensity = window.devicePixelRatio;

		// Display it by adding it to the mobile-info span
		$('#mobile-info').append('
Device Platform: ' + devicePlatform + '
');
		$('#mobile-info').append('
Display Density: ' + displayDensity + '
');

	}

	$('.info-button').click(function() {
		$('#hidden-info-div').animate({ height: 'toggle', leaveTransforms: true, useTranslate3d: true}, 800, 'easeOutExpo', function() {
			// Fix window height bugs by triggering an updatelayout and resize (repaint, please)
			$(window).trigger('resize');
			$(this).trigger('updatelayout');
		});
	});

	// Functions to run on orientation change
	function changeOrientation(orientation) {
		// Add the orientation as a CSS class to the HTML tag
		$('html').addClass(orientation);

		// Fire off some functions
		detectMiddleElements();
	}

	// Functions to run on page-load
	(function() {
		$(window).trigger('orientationchange');
		detectMiddleElements();
		deviceOS();
		deviceInfo();
	})();
});
<commit_msg>Completely restructured the global js. Everything's handled with page-specific event handlers and works properly on multiple pages.<commit_after>// Plugin to jQuery
(function($) {
	/*
	// Create a universal animation function that optimizes for the browsers capabilities
	$.fn.optAnimate = function(props, speed, easing, callback) {
		// If modernizr reports that the browser supports CSS animations, and the animateWithCss function has correctly been loaded/defined
		if (Modernizr.cssanimations && typeof($.fn.animateWithCss) == 'function') {
			return $(this).animateWithCss(props, speed, easing, callback);
		}
		else {
			return $(this).animate(props, speed, easing, callback);
		}
	};
	*/

	// Get the device's OS and add it as a class to the HTML tag
	// To be used for OS-specific styling (trying to stay more true to the native styling)
	$.fn.deviceOS = function() {
		var deviceOS = '';

		// Get the data
		var devicePlatform = navigator.userAgent.match(/Android/i)
			|| navigator.userAgent.match(/iPod/i)
			|| navigator.userAgent.match(/iPad/i)
			|| navigator.userAgent.match(/iPhone/i)
			|| navigator.userAgent.match(/webOS/i)
			|| 'Other';

		if (devicePlatform == 'iPod' || devicePlatform == 'iPad' || devicePlatform == 'iPhone') {
			deviceOS = 'ios';
		}
		else if (devicePlatform == 'Android') {
			deviceOS = 'android';
		}
		else {
			deviceOS = devicePlatform.toLowerCase();
		}

		// Add the deviceOS as a CSS class to the HTML tag
		$('html').addClass(deviceOS);

		return deviceOS;
	};

})(jQuery);

// Bind events to be triggered BEFORE jQuery Mobile loads/executes
$(document).bind('mobileinit', function() {
	// Set jQuery mobile settings
	//$.mobile.touchOverflowEnabled = true;
});

// Bind generic events to be triggered on EVERY page initialization
$(document).live('pageinit', function() {
	// Function to change the class of the HTML tag based on the orientation of the device
	function changeOrientationClass(orientation) {
		// Add the orientation as a CSS class to the HTML tag
		$('html').addClass(orientation);
	}

	// Functions to run on orientation change
	$(window).bind('orientationchange', function(event){
		changeOrientationClass(event.orientation);
	});

	// Functions to run on page-load
	(function() {
		$(window).trigger('orientationchange');
		$().deviceOS();
	})();
});

// Bind generic events to be triggered on the DASHBOARD page initialization
$('#page-dashboard').live('pageinit', function() {
	// Detect and mark the "middle" elements of the dashboard
	function detectMiddleElements() {
		// Grab the width of the entire dashboard
		var dashWidth = parseInt($('nav#dashboard').width());

		// Grab the percentage width of the first element
		var elemWidth = parseInt($('nav#dashboard ul#dashboard-mapps li').width());

		// Find the number of elements per row
		var elemPerRow = Math.floor(dashWidth / elemWidth);

		// Set the number of elements per row as a css class on the dashboard tag
		$('nav#dashboard').addClass(elemPerRow + '-per-row');

		// Calculate the middle-th element
		var middleCount = Math.ceil(elemPerRow / 2);

		// Create the nth-child expression
		var everyNthChild = elemPerRow + String('n+') + middleCount;
		console.log(elemPerRow);
		console.log(middleCount);
		console.log(everyNthChild);
		console.log(elemPerRow + 'n+' + middleCount);

		// Finally, set every middle-th element to have a class
		$('nav#dashboard ul#dashboard-mapps li:nth-child(' + everyNthChild +')').addClass('dash-middle-element');
	}

	// Detect device and meta info and display it on the page
	function deviceInfo() {
		// Get the data
		var devicePlatform = $().deviceOS();
		var displayDensity = window.devicePixelRatio;

		// Display it by adding it to the mobile-info span
		$('#mobile-info').append('
Device Platform: ' + devicePlatform + '
');
		$('#mobile-info').append('
Display Density: ' + displayDensity + '
');

	}

	// Make the info button footer clickable
	$('.info-button').click(function() {
		$('#hidden-info-div').animate({ height: 'toggle', leaveTransforms: true, useTranslate3d: true}, 800, 'easeOutExpo', function() {
			// Fix window height bugs by triggering an updatelayout and resize (repaint, please)
			$(window).trigger('resize');
			$(this).trigger('updatelayout');
		});
	});

	// Functions to run on orientation change
	$(window).bind('orientationchange', function(event){
		detectMiddleElements();
	});

	// Functions to run on page-load
	(function() {
		detectMiddleElements();
		deviceInfo();
	})();
});

// Bind generic events to be triggered on the CAMPUS MAP page initialization
$('#page-campusmap').live('pageinit', function() {
	// Create a Google Map
	var yourStartLatLng = new google.maps.LatLng(43.758976, -71.688709);
	var zoomLevel = 15;
	var gmapObject = {'center': yourStartLatLng, 'zoom': zoomLevel};

	// Refresh it on show
	$('#page-campusmap').live("pageshow", function() {
		// Create the map
		$('div#campus-google-map').gmap('refresh');
	});

	// Fire on page-create
	$('#page-campusmap').live("pagecreate", function() {
		// Create the map
		$('div#campus-google-map').gmap( gmapObject );
	});
});
<|endoftext|>


<commit_before>( function( $ ) {
	var oldContent = '';
	(function() {
		$('.mw-wiki-graph').each(function () {
			// This should not be a loop - there must be only one value with this class
			var el = this,
				textbox = $('#wpTextbox1'),
				context = textbox && textbox.data('wikiEditor-context');

			if (!context || !context.fn || !context.fn.getContents) {
				return;
			}
			var content = context.fn.getContents();
			if (oldContent === content) {
				return;
			}
			oldContent = content;
			var spec = null;
			try {
				spec = $.parseJSON(content);
			} catch(err) {
			}
			if (spec === null) {
				return;
			}
			if (this.hasAttribute('spec'))
				this.removeAttribute('spec');
			vg.parse.spec(spec, function(chart) { chart({el:el}).update(); });
		});
		// FIXME: This should be done on data modification, not on timer
		setTimeout(arguments.callee, 300);
	})();
} ( jQuery ) );
<commit_msg>Even simpler error handling for auto-refresh<commit_after>(function( $ ) {
	var oldContent = '';
	(function() {
		try {
			$('.mw-wiki-graph').each(function () {
				// This should not be a loop - there must be only one value with this class
				var el = this,
					textbox = $('#wpTextbox1'),
					context = textbox && textbox.data('wikiEditor-context');

				var content = context.fn.getContents();
				if (oldContent === content) return;
				oldContent = content;
				var spec = $.parseJSON(content);
				if (spec === null) return;
				if (this.hasAttribute('spec')) this.removeAttribute('spec');
				vg.parse.spec(spec, function(chart) { chart({el:el}).update(); });
			});
		} catch(err) {
			// Ignore all errors
		}
		// FIXME: This should be done on data modification, not on timer
		setTimeout(arguments.callee, 300);
	})();
} ( jQuery ) );
<|endoftext|>


<commit_before>// ==UserScript==
// @name           TabPlus.uc.js
// @description    Enhance the Tabs.
// @charset        UTF-8
// @history        2020-12-30  Added support for Firefox 84.
// @homepageURL    https://github.com/nonoroazoro/firefox/tree/master/userchrome/quantum
// @include        chrome://browser/content/browser.xhtml
// @include        chrome://browser/content/browser.xul
// ==/UserScript==

const TabPlus = {
    init()
    {
        // this.openBookmarksInNewTab();
        // this.openLocationsInNewTab();
        this.doubleClickCloseTab();
        // this.newTabRight();
        this.openLeftTabAfterClose();
        this.viewImageInNewTab();
    },

    /*
     * Open in new Tab: Bookmarks.
     */
    openBookmarksInNewTab()
    {
        try
        {
            let newFunc = openLinkIn.toString().replace(`w.gBrowser.getTabForBrowser(targetBrowser).pinned`, `true`);

            // Open in current tab when the uri is same.
            newFunc = newFunc.replace(
                "targetBrowser.currentURI.host != uriObj.host",
                "targetBrowser.currentURI.spec != uriObj.spec"
            );

            eval("openLinkIn = " + newFunc);
        }
        catch (e)
        { }
    },

    /*
     * Open in new Tab: Location Bar.
     */
    openLocationsInNewTab()
    {
        try
        {
            // Ignore Alt + Enter.
            let newFunc = gURLBar.handleCommand.toString().replace(/&&\n.+altKey &&/, "&&");

            // Open in current tab when the uri is same.
            newFunc = newFunc.replace(
                "!isTabEmpty(gBrowser.selectedTab);",
                "!isTabEmpty(gBrowser.selectedTab) && (encodeURI(this.value) !== gBrowser.selectedBrowser.lastURI.spec);"
            );

            eval("gURLBar.handleCommand = " + newFunc);
        }
        catch (e)
        { }
    },

    /*
     * Double click to close tab.
     */
    doubleClickCloseTab()
    {
        gBrowser.tabContainer.addEventListener("dblclick", (e) =>
        {
            if (e.button == 0 & !e.ctrlKey)
            {
                const tab = e.target.closest(".tabbrowser-tab");
                if (tab)
                {
                    gBrowser.removeTab(tab, { animate: true });
                }
            }
        });
    },

    /*
     * View image in new tab.
     */
    viewImageInNewTab()
    {
        document.getElementById("context-viewimage")
            .setAttribute("oncommand", `openTrustedLinkIn(gContextMenu.imageURL, "tab")`);
    },

    /*
     * Create new Tabs on the right of the current Tab.
     *
     */
    newTabRight()
    {
        gBrowser.tabContainer.addEventListener("TabOpen", (e) =>
        {
            gBrowser.moveTabTo(e.target, gBrowser.tabContainer.selectedIndex + 1);
        });
    },

    /*
     * Open left tab after closing current tab.
     */
    openLeftTabAfterClose()
    {
        gBrowser.tabContainer.addEventListener("TabClose", (e) =>
        {
            const leftTab = e.target.previousSibling;
            if (leftTab)
            {
                gBrowser.selectedTab = leftTab;
            }
        });
    }
};

TabPlus.init();
<commit_msg>refactor with Common.register<commit_after>// ==UserScript==
// @name           TabPlus.uc.js
// @description    Enhance the Tabs.
// @charset        UTF-8
// @history        2020-12-30  Added support for Firefox 84.
// @homepageURL    https://github.com/nonoroazoro/firefox/tree/master/userchrome/quantum
// @include        chrome://browser/content/browser.xhtml
// @include        chrome://browser/content/browser.xul
// ==/UserScript==

const TabPlus = {
    activate()
    {
        // Double click to close the tab.
        gBrowser.tabContainer.addEventListener("dblclick", this._handleTabDoubleClick);

        // Open left tab after the current tab is closed.
        gBrowser.tabContainer.addEventListener("TabClose", this._handleTabClose);

        this.viewImageInNewTab();
    },

    deactivate()
    {
        gBrowser.tabContainer.removeEventListener("dblclick", this._handleTabDoubleClick);
        gBrowser.tabContainer.removeEventListener("TabClose", this._handleTabClose);
    },

    /*
     * Open in new Tab: Location Bar.
     */
    openLocationsInNewTab()
    {
        try
        {
            // Ignore Alt + Enter.
            let newFunc = gURLBar.handleCommand.toString().replace(/&&\n.+altKey &&/, "&&");

            // Open in current tab when the uri is same.
            newFunc = newFunc.replace(
                "!isTabEmpty(gBrowser.selectedTab);",
                "!isTabEmpty(gBrowser.selectedTab) && (encodeURI(this.value) !== gBrowser.selectedBrowser.lastURI.spec);"
            );

            eval("gURLBar.handleCommand = " + newFunc);
        } catch { }
    },

    /*
     * View image in new tab.
     */
    viewImageInNewTab()
    {
        document.getElementById("context-viewimage")
            .setAttribute("oncommand", `openTrustedLinkIn(gContextMenu.imageURL, "tab")`);
    },

    _handleTabDoubleClick(e)
    {
        if (e.button == 0 & !e.ctrlKey)
        {
            const tab = e.target.closest(".tabbrowser-tab");
            if (tab)
            {
                gBrowser.removeTab(tab, { animate: true });
            }
        }
    },

    _handleTabClose(e)
    {
        const leftTab = e.target.previousSibling;
        if (leftTab)
        {
            gBrowser.selectedTab = leftTab;
        }
    }
};

Common.register(TabPlus);
<|endoftext|>


<commit_before>/* ==========================================================================
   KO.Trak - Universal event tracking API

   # Default implementation uses is Google Analytics:
   https://developers.google.com/analytics/devguides/collection/analyticsjs/

   ## Page and event tracking
   https://developers.google.com/analytics/devguides/collection/analyticsjs/events

   ### Usage:
   KO.Trak('category', 'action');
   KO.Trak('category', 'action', 'label');
   KO.Trak('category', 'action', 'label', value); // value is a number
   ========================================================================== */
;(function(KO) {
	KO.Trak = {
		clean : function(str) {
			return str.toString().replace(/\s|'|"/g, '-');
		},

		event : function (category, action, label, value) {
			if (typeof(ga) !== 'undefined') { // use _gaq for old style
				ga('send', 'event', this.clean(category), this.clean(action), this.clean(label), value);

				// Old style:
				//_gaq.push(['_trackEvent', this.clean(category), this.clean(action), this.clean(label), value]);
			}
		}
	};
})(window.KO = window.KO || {});
<commit_msg>Fix usage notes<commit_after>/* ==========================================================================
   KO.Trak - Universal event tracking API

   # Default implementation uses is Google Analytics:
   https://developers.google.com/analytics/devguides/collection/analyticsjs/

   ## Page and event tracking
   https://developers.google.com/analytics/devguides/collection/analyticsjs/events

   ### Usage:
   KO.Trak.event('category', 'action');
   KO.Trak.event('category', 'action', 'label');
   KO.Trak.event('category', 'action', 'label', value); // value is a number
   ========================================================================== */
;(function(KO) {
	KO.Trak = {
		clean : function(str) {
			return str.toString().replace(/\s|'|"/g, '-');
		},

		event : function (category, action, label, value) {
			if (typeof(ga) !== 'undefined') { // use _gaq for old style
				ga('send', 'event', this.clean(category), this.clean(action), this.clean(label), value);

				// Old style:
				//_gaq.push(['_trackEvent', this.clean(category), this.clean(action), this.clean(label), value]);
			}
		}
	};
})(window.KO = window.KO || {});
<|endoftext|>


<commit_before>iD.ui.Commit = function(context) {
    var event = d3.dispatch('cancel', 'save');

    function commit(selection) {
        var changes = context.history().changes(),
            summary = context.history().difference().summary();

        function zoomToEntity(change) {
            var entity = change.entity;
            if (change.changeType !== 'deleted' &&
                context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') {
                context.map().zoomTo(entity);
                context.surface().selectAll(
                    iD.util.entityOrMemberSelector([entity.id], context.graph()))
                    .classed('hover', true);
            }
        }

        var header = selection.append('div')
            .attr('class', 'header fillL');

        header.append('button')
            .attr('class', 'fr')
            .on('click', event.cancel)
            .append('span')
            .attr('class', 'icon close');

        header.append('h3')
            .text(t('commit.title'));

        var body = selection.append('div')
            .attr('class', 'body');

        // Comment Section
        var commentSection = body.append('div')
            .attr('class', 'modal-section form-field commit-form');

        commentSection.append('label')
            .attr('class', 'form-label')
            .text(t('commit.message_label'));

        var commentField = commentSection.append('textarea')
            .attr('placeholder', t('commit.description_placeholder'))
            .property('value', context.storage('comment') || '')
            .on('blur.save', function () {
                context.storage('comment', this.value);
            });

        commentField.node().select();

        // Warnings
        var warnings = body.selectAll('div.warning-section')
            .data([iD.validate(changes, context.graph())])
            .enter()
            .append('div')
            .attr('class', 'modal-section warning-section fillL2')
            .style('display', function(d) { return _.isEmpty(d) ? 'none' : null; })
            .style('background', '#ffb');

        warnings.append('h3')
            .text(t('commit.warnings'));

        var warningLi = warnings.append('ul')
            .attr('class', 'changeset-list')
            .selectAll('li')
            .data(function(d) { return d; })
            .enter()
            .append('li')
            .style()
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', warningClick);

        warningLi.append('span')
            .attr('class', 'alert icon icon-pre-text');

        warningLi.append('strong').text(function(d) {
            return d.message;
        });

        warningLi.filter(function(d) { return d.tooltip; })
            .call(bootstrap.tooltip()
                .title(function(d) { return d.tooltip; })
                .placement('top')
            );

        // Save Section
        var saveSection = body.append('div')
            .attr('class','modal-section fillL cf');

        var prose = saveSection.append('p')
            .attr('class', 'commit-info')
            .html(t('commit.upload_explanation'));

        context.connection().userDetails(function(err, user) {
            if (err) return;

            var userLink = d3.select(document.createElement('div'));

            if (user.image_url) {
                userLink.append('img')
                    .attr('src', user.image_url)
                    .attr('class', 'icon icon-pre-text user-icon');
            }

            userLink.append('a')
                .attr('class','user-info')
                .text(user.display_name)
                .attr('href', context.connection().userURL(user.display_name))
                .attr('tabindex', -1)
                .attr('target', '_blank');

            prose.html(t('commit.upload_explanation_with_user', {user: userLink.html()}));
        });

        // Confirm Button
        var saveButton = saveSection.append('button')
            .attr('class', 'action col4 button')
            .on('click.save', function() {
                event.save({
                    comment: commentField.node().value
                });
            });

        saveButton.append('span')
            .attr('class', 'label')
            .text(t('commit.save'));

        var changeSection = body.selectAll('div.commit-section')
            .data([0])
            .enter()
            .append('div')
            .attr('class', 'commit-section modal-section fillL2');

        changeSection.append('h3')
            .text(summary.length + ' Changes');

        var li = changeSection.append('ul')
            .attr('class', 'changeset-list')
            .selectAll('li')
            .data(summary)
            .enter()
            .append('li')
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', zoomToEntity);

        li.append('span')
            .attr('class', function(d) {
                return d.entity.geometry(d.graph) + ' ' + d.changeType + ' icon icon-pre-text';
            });

        li.append('span')
            .attr('class', 'change-type')
            .text(function(d) {
                return d.changeType + ' ';
            });

        li.append('strong')
            .attr('class', 'entity-type')
            .text(function(d) {
                return context.presets().match(d.entity, d.graph).name();
            });

        li.append('span')
            .attr('class', 'entity-name')
            .text(function(d) {
                var name = iD.util.displayName(d.entity) || '',
                    string = '';
                if (name !== '') string += ':';
                return string += ' ' + name;
            });

        li.style('opacity', 0)
            .transition()
            .style('opacity', 1);

        li.style('opacity', 0)
            .transition()
            .style('opacity', 1);

        function mouseover(d) {
            if (d.entity) {
                context.surface().selectAll(
                    iD.util.entityOrMemberSelector([d.entity.id], context.graph())
                ).classed('hover', true);
            }
        }

        function mouseout() {
            context.surface().selectAll('.hover')
                .classed('hover', false);
        }

        function warningClick(d) {
            if (d.entity) {
                context.map().zoomTo(d.entity);
                context.enter(
                    iD.modes.Select(context, [d.entity.id])
                        .suppressMenu(true));
            }
        }
    }

    return d3.rebind(commit, event, 'on');
};
<commit_msg>maxlength="255" on commit comment textarea<commit_after>iD.ui.Commit = function(context) {
    var event = d3.dispatch('cancel', 'save');

    function commit(selection) {
        var changes = context.history().changes(),
            summary = context.history().difference().summary();

        function zoomToEntity(change) {
            var entity = change.entity;
            if (change.changeType !== 'deleted' &&
                context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') {
                context.map().zoomTo(entity);
                context.surface().selectAll(
                    iD.util.entityOrMemberSelector([entity.id], context.graph()))
                    .classed('hover', true);
            }
        }

        var header = selection.append('div')
            .attr('class', 'header fillL');

        header.append('button')
            .attr('class', 'fr')
            .on('click', event.cancel)
            .append('span')
            .attr('class', 'icon close');

        header.append('h3')
            .text(t('commit.title'));

        var body = selection.append('div')
            .attr('class', 'body');

        // Comment Section
        var commentSection = body.append('div')
            .attr('class', 'modal-section form-field commit-form');

        commentSection.append('label')
            .attr('class', 'form-label')
            .text(t('commit.message_label'));

        var commentField = commentSection.append('textarea')
            .attr('placeholder', t('commit.description_placeholder'))
            .attr('maxlength', 255)
            .property('value', context.storage('comment') || '')
            .on('blur.save', function () {
                context.storage('comment', this.value);
            });

        commentField.node().select();

        // Warnings
        var warnings = body.selectAll('div.warning-section')
            .data([iD.validate(changes, context.graph())])
            .enter()
            .append('div')
            .attr('class', 'modal-section warning-section fillL2')
            .style('display', function(d) { return _.isEmpty(d) ? 'none' : null; })
            .style('background', '#ffb');

        warnings.append('h3')
            .text(t('commit.warnings'));

        var warningLi = warnings.append('ul')
            .attr('class', 'changeset-list')
            .selectAll('li')
            .data(function(d) { return d; })
            .enter()
            .append('li')
            .style()
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', warningClick);

        warningLi.append('span')
            .attr('class', 'alert icon icon-pre-text');

        warningLi.append('strong').text(function(d) {
            return d.message;
        });

        warningLi.filter(function(d) { return d.tooltip; })
            .call(bootstrap.tooltip()
                .title(function(d) { return d.tooltip; })
                .placement('top')
            );

        // Save Section
        var saveSection = body.append('div')
            .attr('class','modal-section fillL cf');

        var prose = saveSection.append('p')
            .attr('class', 'commit-info')
            .html(t('commit.upload_explanation'));

        context.connection().userDetails(function(err, user) {
            if (err) return;

            var userLink = d3.select(document.createElement('div'));

            if (user.image_url) {
                userLink.append('img')
                    .attr('src', user.image_url)
                    .attr('class', 'icon icon-pre-text user-icon');
            }

            userLink.append('a')
                .attr('class','user-info')
                .text(user.display_name)
                .attr('href', context.connection().userURL(user.display_name))
                .attr('tabindex', -1)
                .attr('target', '_blank');

            prose.html(t('commit.upload_explanation_with_user', {user: userLink.html()}));
        });

        // Confirm Button
        var saveButton = saveSection.append('button')
            .attr('class', 'action col4 button')
            .on('click.save', function() {
                event.save({
                    comment: commentField.node().value
                });
            });

        saveButton.append('span')
            .attr('class', 'label')
            .text(t('commit.save'));

        var changeSection = body.selectAll('div.commit-section')
            .data([0])
            .enter()
            .append('div')
            .attr('class', 'commit-section modal-section fillL2');

        changeSection.append('h3')
            .text(summary.length + ' Changes');

        var li = changeSection.append('ul')
            .attr('class', 'changeset-list')
            .selectAll('li')
            .data(summary)
            .enter()
            .append('li')
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', zoomToEntity);

        li.append('span')
            .attr('class', function(d) {
                return d.entity.geometry(d.graph) + ' ' + d.changeType + ' icon icon-pre-text';
            });

        li.append('span')
            .attr('class', 'change-type')
            .text(function(d) {
                return d.changeType + ' ';
            });

        li.append('strong')
            .attr('class', 'entity-type')
            .text(function(d) {
                return context.presets().match(d.entity, d.graph).name();
            });

        li.append('span')
            .attr('class', 'entity-name')
            .text(function(d) {
                var name = iD.util.displayName(d.entity) || '',
                    string = '';
                if (name !== '') string += ':';
                return string += ' ' + name;
            });

        li.style('opacity', 0)
            .transition()
            .style('opacity', 1);

        li.style('opacity', 0)
            .transition()
            .style('opacity', 1);

        function mouseover(d) {
            if (d.entity) {
                context.surface().selectAll(
                    iD.util.entityOrMemberSelector([d.entity.id], context.graph())
                ).classed('hover', true);
            }
        }

        function mouseout() {
            context.surface().selectAll('.hover')
                .classed('hover', false);
        }

        function warningClick(d) {
            if (d.entity) {
                context.map().zoomTo(d.entity);
                context.enter(
                    iD.modes.Select(context, [d.entity.id])
                        .suppressMenu(true));
            }
        }
    }

    return d3.rebind(commit, event, 'on');
};
<|endoftext|>


<commit_before>import mixin from 'reactjs-mixin';
import React from 'react';
import {StoreMixin} from 'mesosphere-shared-reactjs';

import ClipboardTrigger from '../components/ClipboardTrigger';
import ClusterName from './ClusterName';
import MetadataStore from '../stores/MetadataStore';

class ClusterHeader extends mixin(StoreMixin) {
  constructor() {
    super(...arguments);

    this.store_listeners = [
      {
        name: 'metadata',
        events: ['success'],
        listenAlways: false
      }
    ];
  }

  shouldComponentUpdate(nextProps) {
    if (nextProps.useClipboard !== this.props.useClipboard) {
      return true;
    }

    return false;
  }

  getFlashButton() {
    if (!this.props.useClipboard) {
      return null;
    }

    return <ClipboardTrigger copyText={this.getPublicIP()} />;
  }

  getPublicIP() {
    let metadata = MetadataStore.get('metadata');

    if ((typeof metadata !== 'object') ||
      metadata.PUBLIC_IPV4 == null ||
      metadata.PUBLIC_IPV4.length === 0) {
      return null;
    }

    return metadata.PUBLIC_IPV4;
  }

  getHostName() {
    let ip = this.getPublicIP();

    return (

          {ip}

          {this.getFlashButton()}

    );
  }

  render() {
    return (

        <ClusterName />
        {this.getHostName()}

    );
  }
}

ClusterHeader.defaultProps = {
  useClipboard: true
};

ClusterHeader.propTypes = {
  useClipboard: React.PropTypes.bool
};

module.exports = ClusterHeader;
<commit_msg>Allow caret in ClusterHeader<commit_after>import mixin from 'reactjs-mixin';
import React from 'react';
import {StoreMixin} from 'mesosphere-shared-reactjs';

import ClipboardTrigger from '../components/ClipboardTrigger';
import ClusterName from './ClusterName';
import Icon from './Icon';
import MetadataStore from '../stores/MetadataStore';

class ClusterHeader extends mixin(StoreMixin) {
  constructor() {
    super(...arguments);

    this.store_listeners = [
      {
        name: 'metadata',
        events: ['success'],
        listenAlways: false
      }
    ];
  }

  shouldComponentUpdate(nextProps) {
    if (nextProps.useClipboard !== this.props.useClipboard) {
      return true;
    }

    return false;
  }

  getFlashButton() {
    return <ClipboardTrigger copyText={this.getPublicIP()} />;
  }

  getPublicIP() {
    let metadata = MetadataStore.get('metadata');

    if ((typeof metadata !== 'object') ||
      metadata.PUBLIC_IPV4 == null ||
      metadata.PUBLIC_IPV4.length === 0) {
      return null;
    }

    return metadata.PUBLIC_IPV4;
  }

  getHostName() {
    let flashButton = null;
    let ip = this.getPublicIP();

    if (this.props.useClipboard) {
      flashButton = (

          {this.getFlashButton()}

      );
    }

    return (

          {ip}

        {flashButton}

    );
  }

  render() {
    let caret = null;

    if (this.props.showCaret) {
      caret = <Icon family="mini" id="caret-down" key="caret" size="mini" />;
    }

    return (

          <ClusterName />{caret}

        {this.getHostName()}

    );
  }
}

ClusterHeader.defaultProps = {
  showCaret: false,
  useClipboard: true
};

ClusterHeader.propTypes = {
  showCaret: React.PropTypes.bool,
  useClipboard: React.PropTypes.bool
};

module.exports = ClusterHeader;
<|endoftext|>


<commit_before>var imgurClient = {
  $: null,
  base_url: null,
  client_id: null,

  init: function($, base_url, client_id){
    this.$ = $;
    this.base_url = base_url;
    this.client_id = client_id;
  },

  accountImages: function(account, page, done, fail, always){
    this._ajaxRequest("account/" + account + "/images/" + page.toString(),
                      {},
                      done, fail, always);
  },

  _ajaxRequest: function(endpoint, data, done, fail, always){
    this.$.ajax({
      url: this.base_url + endpoint,
      dataType: "json",
      headers: {
        "Authorization": "Client-ID " + this.client_id
      }
    }).
      done(done || function(){}).
      fail(fail || function(){}).
      always(always || function(){});
  }
}

var imgurViewer = {
  client: imgurClient,
  $: null,
  $images: null,
  $accountText: null,
  $accountSubmit: null,

  swipeboxClass: "swipebox-image",

  init: function($, base_url, client_id,
                 imagesId, accountTextId, accountSubmitId){
    this.$ = $;
    this.client.init($, base_url, client_id);
    this.$images = this.$("#" + imagesId);
    this.$accountSubmit = this.$("#" + accountSubmitId);
    this.$accountText = this.$("#" + accountTextId);

    if (! this.$images) {
      // TODO: Notify dom Error
      return;
    }

    this.$(window).bind("hashchange", this.onHashChange.bind(this));
    this.onHashChange();

    this.$accountText.bind("keypress", (function(e){
      if (e.keyCode === 13) {
        this.updateHash(this.$accountText.val());
        return;
      }
    }).bind(this));
    this.$accountSubmit.bind("click", (function(){
        this.updateHash(this.$accountText.val());
        return;
    }).bind(this));
  },

  updateHash: function(text){
    window.location.hash = text;
    return;
  },

  onHashChange: function(){
    this.$images.empty();

    var hash = this.$.param.fragment();
    //var hash = (window.content.location.hash || "").replace(/^#/, "");
    this.$accountText.val(hash);
    if (! hash) {
      return;
    }

    var splittedHash = hash.split("/");
    var account = splittedHash[0];

    var page = parseInt(splittedHash[1]);
    if (isNaN(page)) {
      page = 0;
    }

    this.client.accountImages(account, page, (function(data, textStatus, jqXHR){
      var result = data.data;

      if (page === 1) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: "#" + account
        }).append($("<img />", {
          src: "img/mono-tab-left.svg",
          alt: "left"
        }))));
      } else if (page >= 1) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: "#" + account + "/" + (page - 1).toString()
        }).append($("<img />", {
          src: "img/mono-tab-left.svg",
          alt: "left"
        }))));
      }

      for (var i = 0; i < result.length; i++) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: (isSmartPhone() ?
                 this.makeThumbnailLink(result[i].link, "h") :
                 result[i].link),
          class: this.swipeboxClass
        }).append($("<img />", {
          src: this.makeThumbnailLink(result[i].link, "b"),
          alt: result[i].title || result[i].id,
          width: "100%"
        }))));
      }

      $("." + this.swipeboxClass).swipebox();

      this.$images.append($("<div />", {
        class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
      }).append($("<a />", {
        href: "#" + account + "/" + (page + 1).toString()
      }).append($("<img />", {
        src: "img/mono-tab-right.svg",
        alt: "right"
      }))));
    }).bind(this));
  },

  makeThumbnailLink: function(url, suffix){
    return url.replace(/\/([^.]+)(\.[^\/]*)$/, "/$1" + suffix + "$2");
  }
};

// http://www.kens-web.com/2011/11/1344
function isSmartPhone(){
  return ((navigator.userAgent.indexOf('iPhone') > 0 &&
           navigator.userAgent.indexOf('iPad') === -1) ||
          navigator.userAgent.indexOf('iPod') > 0 ||
          navigator.userAgent.indexOf('Android') > 0);
}
<commit_msg>Do not show next button if currently no image is displayed<commit_after>var imgurClient = {
  $: null,
  base_url: null,
  client_id: null,

  init: function($, base_url, client_id){
    this.$ = $;
    this.base_url = base_url;
    this.client_id = client_id;
  },

  accountImages: function(account, page, done, fail, always){
    this._ajaxRequest("account/" + account + "/images/" + page.toString(),
                      {},
                      done, fail, always);
  },

  _ajaxRequest: function(endpoint, data, done, fail, always){
    this.$.ajax({
      url: this.base_url + endpoint,
      dataType: "json",
      headers: {
        "Authorization": "Client-ID " + this.client_id
      }
    }).
      done(done || function(){}).
      fail(fail || function(){}).
      always(always || function(){});
  }
}

var imgurViewer = {
  client: imgurClient,
  $: null,
  $images: null,
  $accountText: null,
  $accountSubmit: null,

  swipeboxClass: "swipebox-image",

  init: function($, base_url, client_id,
                 imagesId, accountTextId, accountSubmitId){
    this.$ = $;
    this.client.init($, base_url, client_id);
    this.$images = this.$("#" + imagesId);
    this.$accountSubmit = this.$("#" + accountSubmitId);
    this.$accountText = this.$("#" + accountTextId);

    if (! this.$images) {
      // TODO: Notify dom Error
      return;
    }

    this.$(window).bind("hashchange", this.onHashChange.bind(this));
    this.onHashChange();

    this.$accountText.bind("keypress", (function(e){
      if (e.keyCode === 13) {
        this.updateHash(this.$accountText.val());
        return;
      }
    }).bind(this));
    this.$accountSubmit.bind("click", (function(){
        this.updateHash(this.$accountText.val());
        return;
    }).bind(this));
  },

  updateHash: function(text){
    window.location.hash = text;
    return;
  },

  onHashChange: function(){
    this.$images.empty();

    var hash = this.$.param.fragment();
    //var hash = (window.content.location.hash || "").replace(/^#/, "");
    this.$accountText.val(hash);
    if (! hash) {
      return;
    }

    var splittedHash = hash.split("/");
    var account = splittedHash[0];

    var page = parseInt(splittedHash[1]);
    if (isNaN(page)) {
      page = 0;
    }

    this.client.accountImages(account, page, (function(data, textStatus, jqXHR){
      var result = data.data;

      if (page === 1) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: "#" + account
        }).append($("<img />", {
          src: "img/mono-tab-left.svg",
          alt: "left"
        }))));
      } else if (page >= 1) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: "#" + account + "/" + (page - 1).toString()
        }).append($("<img />", {
          src: "img/mono-tab-left.svg",
          alt: "left"
        }))));
      }

      for (var i = 0; i < result.length; i++) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: (isSmartPhone() ?
                 this.makeThumbnailLink(result[i].link, "h") :
                 result[i].link),
          class: this.swipeboxClass
        }).append($("<img />", {
          src: this.makeThumbnailLink(result[i].link, "b"),
          alt: result[i].title || result[i].id,
          width: "100%"
        }))));
      }

      if (result.length > 0) {
        this.$images.append($("<div />", {
          class: "col-1-4 mobile-col-1-3 imgur-viewer-image"
        }).append($("<a />", {
          href: "#" + account + "/" + (page + 1).toString()
        }).append($("<img />", {
          src: "img/mono-tab-right.svg",
          alt: "right"
        }))));
      }

      $("." + this.swipeboxClass).swipebox();

    }).bind(this));
  },

  makeThumbnailLink: function(url, suffix){
    return url.replace(/\/([^.]+)(\.[^\/]*)$/, "/$1" + suffix + "$2");
  }
};

// http://www.kens-web.com/2011/11/1344
function isSmartPhone(){
  return ((navigator.userAgent.indexOf('iPhone') > 0 &&
           navigator.userAgent.indexOf('iPad') === -1) ||
          navigator.userAgent.indexOf('iPod') > 0 ||
          navigator.userAgent.indexOf('Android') > 0);
}
<|endoftext|>