<commit_before>/* eslint eqeqeq: [0, "smart"] */

var app = angular.module('slideApp', ['ngAnimate']);

/**
 * gameModes - Save game configuraions
 *
 * @return {object} this instance of serviec.
 */
app.service('gameModes', function () {
  this.modes = {
    beginner: {
      row: 3,
      column: 3,
      elasped: 240000
    },
    amateur: {
      row: 4,
      column: 4,
      elasped: 3600000
    },
    semipro: {
      row: 5,
      column: 5,
      elasped: 7200000
    },
    professional: {
      row: 6,
      column: 6,
      elasped: 10800000
    },
    legendary: {
      row: 8,
      column: 8,
      elasped: 14400000
    }
  };

  this.defaultMode = Object.keys(this.modes)[0];

  this.get = function (mode) {
    if (mode) {
      return this.modes[mode];
    }
    return this.modes[this.defaultMode];
  };
});

app.controller('slideCtrl', function ($scope, $interval, Game, gameModes,
  save) {
  var game;
  var delay = 1000;
  var timerInstance;

  /**
   * closeevent - fires when tab / winow is closed.
   *
   */
  function closeevent() {
    var data = {
      model: $scope.game.grid,
      history: $scope.game.history,
      time: $scope.game.elasped,
      mode: $scope.game.mode
    };
    save.set(data);
  }

  $scope.init = function () {
    var savedGame = save.get();
    if (savedGame) {
      $scope.newGame(savedGame.mode, savedGame.model,
          savedGame.history, savedGame.time);
    } else {
      $scope.newGame();
    }
  };

  $scope.game = {};
  $scope.game.history = [];
  $scope.game.modes = gameModes.modes;

  /**
   * newGame - Define new game.
   */
  $scope.newGame = function (mode, model, history, time) {
    var type = gameModes.get(mode);
    $scope.game.solved = false;
    $scope.game.mode = mode || gameModes.defaultMode;
    $scope.game.history = history || [];
    $scope.game.elasped = time || type.elasped;
    game = new Game(type.row, type.column);
    $scope.game.grid = model || game.newGame();
    if (timerInstance) {
      $interval.cancel(timerInstance);
    }
    timerInstance = $interval(function () {
      $scope.game.elasped -= delay;
    }, delay);
    // saves the first time data.
    closeevent();
  };

  /**
   * move - move the paddle
   *
   * @param  {number} x description
   * @param  {number} y description
   */
  $scope.move = function (x, y) {
    game.move($scope.game.grid, x, y).then(function (resp) {
      $scope.game.grid = resp.model;
      $scope.game.history.push(resp);
    });
  };

  /**
   * solve - autosolve the game
   */
  $scope.solve = function () {
    $scope.game.grid = game.solve();
  };

  /**
   * sosolveButton - set visibility of solve button
   */
  // $scope.solveButtonStatus = function () {
  //   return Object.keys($scope.game.grid).length;
  // };

  /**
   * Keep watching the model.
   */
  $scope.$watch('game.grid', function () {
    if (_.isEqual($scope.game.grid, game.solve())) {
      $scope.game.solved = true;
      save.remove();
      $interval.cancel(timerInstance);
    }
  });

  /**
   * On window close
   */
  window.onbeforeunload = closeevent;
});

app.factory('Game', function ($q) {
  /**
   * shuffleArray - Shuffle an array in random order
   *
   * @param  {array} arr an array
   * @return {array}     shuffled array
   */
  function shuffleArray(arr) {
    var p = arr.slice();
    p.sort(function () {
      return 0.5 - Math.random();
    });
    return p;
  }

  /**
   * newGame - Desing new game, make model
   *
   * @return {object}  model
   */
  function newGame() {
    var elem;
    var solvedArray;
    for (;;) {
      elem = this.shuffleArray(this.elements);
      if (this.isSolvable(elem)) {
        solvedArray = elem;
        break;
      }
    }

    return this.makeModel(solvedArray);
  }

  /**
   * solve - solve the game
   *
   * @return {object} solved mode
   */
  function solve() {
    var arr = angular.copy(this.elements);
    var removedItem = arr.shift();
    arr.push(removedItem);
    this.moves = 0;
    return this.makeModel(arr);
  }

  /**
   * makeModel - make model for the matrix
   *
   * @param  {type} arr an arrry to deign a model
   * @return {type}     model, matrix sliced in collection.
   */
  function makeModel(arr) {
    var model = {};
    var elem;
    var x = this.x;
    var y = this.y;
    var j = 0;
    var i;
    for (i = 0; i < x * y; i += y) {
      elem = arr.slice();
      model[j] = elem.splice(i, y);
      j += 1;
    }
    this.currentModel = model;
    return model;
  }

  /**
   * pivot - it is the blank tile
   *
   * @param  {object} model  description
   * @param  {number} anchor element of which you find to location, here 0.
   * @return {object}        pos in x & y.
   */
  function pivot(model, anchor) {
    var pos = _.reduce(model, function (result, value, key) {
      var k = value.indexOf(anchor);
      if (k > -1) {
        result.x = parseInt(key, 10);
        result.y = k;
      }
      return result;
    }, {});
    return pos;
  }

  /**
   * directions - calculate the possible direction to move tile.
   *
   * @param  {number} x x co-ordindate
   * @param  {number} y y co-ordindate
   * @return {object}   description , up, down, left, right
   */
  function directions(x, y) {
    var dir = {};
    // down
    if (x + 1 < this.x) {
      dir.down = {
        x: x + 1,
        y: y
      };
    }

    // right
    if (y + 1 < this.y) {
      dir.right = {
        x: x,
        y: y + 1
      };
    }

    // up
    if (x - 1 >= 0) {
      dir.up = {
        x: x - 1,
        y: y
      };
    }

    // left
    if (y - 1 >= 0) {
      dir.left = {
        x: x,
        y: y - 1
      };
    }
    return dir;
  }

  /**
   * checkMove - check that an element can be move or not, cross check with
   * directions object.
   *
   * @param  {object} dirs directions up, down, left, right
   * @param  {number} x    x co-ordindate
   * @param  {type} y    y co-ordindate
   * @return {object}    result witth staus, direction, and postion.
   */
  function checkMove(dirs, x, y) {
    return _.reduce(dirs, function (result, value, key) {
      if (value.x == x && value.y == y) {
        result.status = true;
        result.direaction = key;
        result.pos = value;
      }
      return result;
    }, {});
  }

  /**
   * move - make the move,
   *
   * @param  {object} model
   * @param  {number} x     x co-ordindate
   * @param  {number} y     x co-ordindate
   * @return {promise}
   */
  function move(model, x, y) {
    var deferred = $q.defer();
    var piv = this.pivot(model, 0);
    var dirs = this.directions(piv.x, piv.y);
    var mov = this.checkMove(dirs, x, y);
    var temp;
    if (mov.status) {
      // Swaping the values in model
      temp = model[x][y];
      model[x][y] = model[piv.x][piv.y];
      model[piv.x][piv.y] = temp;
      this.moves += 1;
      this.currentModel = model;
      deferred.resolve({
        model: model,
        new: mov,
        old: piv,
        value: temp
      });
    } else {
      deferred.reject('Can not move here');
    }
    return deferred.promise;
  }

  /**
   * isSolvable - count number of inversion
   *
   * @return {boolean}  true/false
   */
  function isSolvable(arr) {
    // counting empty location from bottom.
    var arrReverse = angular.copy(arr).reverse();
    var arryInv = angular.copy(arr);
    var inversionCount;
    var emptyLoc = Math.ceil((arrReverse.indexOf(0) + 1) / this.y);
    // Removing 0.
    arryInv.splice(arryInv.indexOf(0), 1);
    inversionCount = arryInv.reduce(function (result, value, key, self) {
      return self.slice(key).filter(function (b) {
        return b < value;
      }).map(function (b) {
        return [value, b];
      }).concat(result);
    }, []).length;

    // width is odd
    if (this.x % 2 === 1) {
      return inversionCount % 2 === 0;
    }
    // width is even && inversion count is odd
    if (inversionCount % 2 === 1) {
      // then emptyLoc from bottom would be even
      return (emptyLoc % 2 === 0);
    }
    // width is even && inversion count is even then emptyLoc would be odd
    return (emptyLoc % 2 === 1);
  }

  /**
   * Board - constructor
   *
   * @param  {number} x number of rows
   * @param  {type} y number of columns
   */
  function Board(x, y) {
    this.x = x;
    this.y = y;
    this.elements = _.range(x * y);
    this.newGame = newGame;
    this.currentModel = {};
    this.shuffleArray = shuffleArray;
    this.makeModel = makeModel;
    this.pivot = pivot;
    this.directions = directions;
    this.move = move;
    this.checkMove = checkMove;
    this.solve = solve;
    this.moves = 0;
    this.isSolvable = isSolvable;
  }

  return Board;
});

/**
 * save - save and get data form localStorage
 *
 * @return {type}  description
 */
app.service('save', function () {
  /**
   * set - set data in localStorage
   *
   * @param  {object} obj object which it to be inserted.
   */
  function set(obj) {
    localStorage.setItem(this.id, JSON.stringify(obj));
  }

  /**
   * remove - delete data from localStorage
   */
  function remove() {
    localStorage.removeItem(this.id);
  }

  /**
   * get - get data form localStorage
   *
   * @return {object}  saved data from localStorage
   */
  function get() {
    return JSON.parse(localStorage.getItem(this.id));
  }

  this.id = 'slideGame';
  this.set = set;
  this.get = get;
  this.remove = remove;
});

app.service('inversion', function () {

});
<commit_msg>chore: minor fixes<commit_after>/* eslint eqeqeq: [0, "smart"] */

var app = angular.module('slideApp', ['ngAnimate']);

/**
 * gameModes - Save game configuraions
 *
 * @return {object} this instance of serviec.
 */
app.service('gameModes', function () {
  this.modes = {
    beginner: {
      row: 3,
      column: 3,
      elasped: 240000
    },
    amateur: {
      row: 4,
      column: 4,
      elasped: 3600000
    },
    semipro: {
      row: 5,
      column: 5,
      elasped: 7200000
    },
    professional: {
      row: 6,
      column: 6,
      elasped: 10800000
    },
    legendary: {
      row: 8,
      column: 8,
      elasped: 14400000
    }
  };

  this.defaultMode = Object.keys(this.modes)[0];

  this.get = function (mode) {
    if (mode) {
      return this.modes[mode];
    }
    return this.modes[this.defaultMode];
  };
});

app.controller('slideCtrl', function ($scope, $interval, Game, gameModes,
  save) {
  var game;
  var delay = 1000;
  var timerInstance;

  /**
   * closeevent - fires when tab / winow is closed.
   *
   */
  function closeevent() {
    var data = {
      model: $scope.game.grid,
      history: $scope.game.history,
      time: $scope.game.elasped,
      mode: $scope.game.mode
    };
    save.set(data);
  }

  $scope.init = function () {
    var savedGame = save.get();
    if (savedGame) {
      $scope.newGame(savedGame.mode, savedGame.model,
          savedGame.history, savedGame.time);
    } else {
      $scope.newGame();
    }
  };

  $scope.game = {};
  $scope.game.history = [];
  $scope.game.modes = gameModes.modes;

  /**
   * newGame - Define new game.
   */
  $scope.newGame = function (mode, model, history, time) {
    var type = gameModes.get(mode);
    $scope.game.solved = false;
    $scope.game.mode = mode || gameModes.defaultMode;
    $scope.game.history = history || [];
    $scope.game.elasped = time || type.elasped;
    game = new Game(type.row, type.column);
    $scope.game.grid = model || game.newGame();
    if (timerInstance) {
      $interval.cancel(timerInstance);
    }
    timerInstance = $interval(function () {
      $scope.game.elasped -= delay;
    }, delay);
    // saves the first time data.
    closeevent();
  };

  /**
   * move - move the paddle
   *
   * @param  {number} x description
   * @param  {number} y description
   */
  $scope.move = function (x, y) {
    game.move($scope.game.grid, x, y).then(function (resp) {
      $scope.game.grid = resp.model;
      $scope.game.history.push(resp);
    });
  };

  /**
   * solve - autosolve the game
   */
  $scope.solve = function () {
    $scope.game.grid = game.solve();
  };

  /**
   * Keep watching the model.
   */
  $scope.$watch('game.grid', function () {
    if (_.isEqual($scope.game.grid, game.solve())) {
      $scope.game.solved = true;
      save.remove();
      $interval.cancel(timerInstance);
    }
  });

  /**
   * On window close
   */
  window.onbeforeunload = closeevent;
});

app.factory('Game', function ($q) {
  /**
   * shuffleArray - Shuffle an array in random order
   *
   * @param  {array} arr an array
   * @return {array}     shuffled array
   */
  function shuffleArray(arr) {
    var p = arr.slice();
    p.sort(function () {
      return 0.5 - Math.random();
    });
    return p;
  }

  /**
   * newGame - Desing new game, make model
   *
   * @return {object}  model
   */
  function newGame() {
    var elem;
    var solvedArray;
    for (;;) {
      elem = this.shuffleArray(this.elements);
      if (this.isSolvable(elem)) {
        solvedArray = elem;
        break;
      }
    }

    return this.makeModel(solvedArray);
  }

  /**
   * solve - solve the game
   *
   * @return {object} solved mode
   */
  function solve() {
    var arr = angular.copy(this.elements);
    var removedItem = arr.shift();
    arr.push(removedItem);
    this.moves = 0;
    return this.makeModel(arr);
  }

  /**
   * makeModel - make model for the matrix
   *
   * @param  {type} arr an arrry to deign a model
   * @return {type}     model, matrix sliced in collection.
   */
  function makeModel(arr) {
    var model = {};
    var elem;
    var x = this.x;
    var y = this.y;
    var j = 0;
    var i;
    for (i = 0; i < x * y; i += y) {
      elem = arr.slice();
      model[j] = elem.splice(i, y);
      j += 1;
    }
    this.currentModel = model;
    return model;
  }

  /**
   * pivot - it is the blank tile
   *
   * @param  {object} model  description
   * @param  {number} anchor element of which you find to location, here 0.
   * @return {object}        pos in x & y.
   */
  function pivot(model, anchor) {
    var pos = _.reduce(model, function (result, value, key) {
      var k = value.indexOf(anchor);
      if (k > -1) {
        result.x = parseInt(key, 10);
        result.y = k;
      }
      return result;
    }, {});
    return pos;
  }

  /**
   * directions - calculate the possible direction to move tile.
   *
   * @param  {number} x x co-ordindate
   * @param  {number} y y co-ordindate
   * @return {object}   description , up, down, left, right
   */
  function directions(x, y) {
    var dir = {};
    // down
    if (x + 1 < this.x) {
      dir.down = {
        x: x + 1,
        y: y
      };
    }

    // right
    if (y + 1 < this.y) {
      dir.right = {
        x: x,
        y: y + 1
      };
    }

    // up
    if (x - 1 >= 0) {
      dir.up = {
        x: x - 1,
        y: y
      };
    }

    // left
    if (y - 1 >= 0) {
      dir.left = {
        x: x,
        y: y - 1
      };
    }
    return dir;
  }

  /**
   * checkMove - check that an element can be move or not, cross check with
   * directions object.
   *
   * @param  {object} dirs directions up, down, left, right
   * @param  {number} x    x co-ordindate
   * @param  {type} y    y co-ordindate
   * @return {object}    result witth staus, direction, and postion.
   */
  function checkMove(dirs, x, y) {
    return _.reduce(dirs, function (result, value, key) {
      if (value.x == x && value.y == y) {
        result.status = true;
        result.direaction = key;
        result.pos = value;
      }
      return result;
    }, {});
  }

  /**
   * move - make the move,
   *
   * @param  {object} model
   * @param  {number} x     x co-ordindate
   * @param  {number} y     x co-ordindate
   * @return {promise}
   */
  function move(model, x, y) {
    var deferred = $q.defer();
    var piv = this.pivot(model, 0);
    var dirs = this.directions(piv.x, piv.y);
    var mov = this.checkMove(dirs, x, y);
    var temp;
    if (mov.status) {
      // Swaping the values in model
      temp = model[x][y];
      model[x][y] = model[piv.x][piv.y];
      model[piv.x][piv.y] = temp;
      this.moves += 1;
      this.currentModel = model;
      deferred.resolve({
        model: model,
        new: mov,
        old: piv,
        value: temp
      });
    } else {
      deferred.reject('Can not move here');
    }
    return deferred.promise;
  }

  /**
   * isSolvable - count number of inversion
   *
   * @return {boolean}  true/false
   */
  function isSolvable(arr) {
    // counting empty location from bottom.
    var arrReverse = angular.copy(arr).reverse();
    var arryInv = angular.copy(arr);
    var inversionCount;
    var emptyLoc = Math.ceil((arrReverse.indexOf(0) + 1) / this.y);
    // Removing 0.
    arryInv.splice(arryInv.indexOf(0), 1);
    inversionCount = arryInv.reduce(function (result, value, key, self) {
      return self.slice(key).filter(function (b) {
        return b < value;
      }).map(function (b) {
        return [value, b];
      }).concat(result);
    }, []).length;

    // width is odd
    if (this.x % 2 === 1) {
      return inversionCount % 2 === 0;
    }
    // width is even && inversion count is odd
    if (inversionCount % 2 === 1) {
      // then emptyLoc from bottom would be even
      return (emptyLoc % 2 === 0);
    }
    // width is even && inversion count is even then emptyLoc would be odd
    return (emptyLoc % 2 === 1);
  }

  /**
   * Board - constructor
   *
   * @param  {number} x number of rows
   * @param  {type} y number of columns
   */
  function Board(x, y) {
    this.x = x;
    this.y = y;
    this.elements = _.range(x * y);
    this.newGame = newGame;
    this.currentModel = {};
    this.shuffleArray = shuffleArray;
    this.makeModel = makeModel;
    this.pivot = pivot;
    this.directions = directions;
    this.move = move;
    this.checkMove = checkMove;
    this.solve = solve;
    this.moves = 0;
    this.isSolvable = isSolvable;
  }

  return Board;
});

/**
 * save - save and get data form localStorage
 *
 * @return {type}  description
 */
app.service('save', function () {
  /**
   * set - set data in localStorage
   *
   * @param  {object} obj object which it to be inserted.
   */
  function set(obj) {
    localStorage.setItem(this.id, JSON.stringify(obj));
  }

  /**
   * remove - delete data from localStorage
   */
  function remove() {
    localStorage.removeItem(this.id);
  }

  /**
   * get - get data form localStorage
   *
   * @return {object}  saved data from localStorage
   */
  function get() {
    return JSON.parse(localStorage.getItem(this.id));
  }

  this.id = 'slideGame';
  this.set = set;
  this.get = get;
  this.remove = remove;
});
<|endoftext|>


<commit_before>// Load in dependencies
var async = require('async');
var assert = require('assert');
var Layout = require('layout');
var EngineSmith = require('./smiths/engine.smith.js');
var CanvasSmith = require('./smiths/canvas.smith.js');

// Specify defaults
var engineDefault = 'pixelsmith';
var algorithmDefault = 'top-down';

/**
 * Spritesmith generation function
 * @param {Object} params Parameters for spritesmith
 * @param {String[]} [params.src] Images to generate into sprite sheet
 * @param {String} [params.engine="pixelsmith"] Engine to use (phantomjs, canvas, gm, pngsmith,
      or user-defined via Spritesmith.addEngine)
 * @param {String} [params.algorithm="top-down"] Algorithm to pack images with (top-down
      or user-defined via Spritesmith.addAlgorithm)
 * @param {Number} [params.padding] Padding to use between images
 * @param {Mixed} [params.exportOpts] Options to pass through to engine for export
 * @param {Function} callback Function that receives compiled spritesheet and map
 * @returns {Mixed} callback[0] err If an error was encountered, this will be returned to callback
 * @returns {Object} callback[1] result Result object of spritesmith
 * @returns {String} callback[1].image Binary string representation of image
 * @returns {Object} callback[1].coordinates Map from file name to an object containing x, y, height,
      and width information about the source image
 */
function Spritesmith(params, callback) {
  var retObj = {};
  var files = params.src;
  var engineName = params.engine || engineDefault;
  var engine = engineName;
  var algorithmName = params.algorithm || algorithmDefault;

  // If the engine is a `require` path, attempt to load it
  if (typeof engineName === 'string') {
    // Attempt to resolve the engine to verify it is installed at all
    try {
      require.resolve(engineName);
    } catch (err) {
      console.error('Attempted to find spritesmith engine "' + engineName + '" but could not.');
      console.error('Please verify you have installed "' + engineName + '" and saved it to your `package.json`');
      console.error('');
      console.error('    npm install ' + engineName);
      console.error('');
      throw err;
    }

    // Attempt to load the engine
    try {
      engine = require(engineName);
    } catch (err) {
      console.error('Attempted to load spritesmith engine "' + engineName + '" but could not.');
      console.error('Please verify you have installed its dependencies. Documentation should be available at ');
      console.error('');
      // TODO: Consider using pkg.repository
      console.error('    https://npm.im/' + encodeURIComponent(engineName));
      console.error('');
      throw err;
    }
  }

  // If there is a set parameter for the engine, use it
  if (engine.set) {
    var engineOpts = params.engineOpts || {};
    engine.set(engineOpts);
  }

  // Create our smiths
  var engineSmith = new EngineSmith(engine);
  var layer = new Layout(algorithmName, params.algorithmOpts);
  var padding = params.padding || 0;
  var exportOpts = params.exportOpts || {};
  var packedObj;

  // In a waterfall fashion
  async.waterfall([
    function grabImages (cb) {
      // Map the files into their image counterparts
      engineSmith.createImages(files, cb);
    },
    // Then, add the images to our canvas (dry run)
    function smithAddFiles (images, cb) {
      images.forEach(function (img) {
        // Save the non-padded properties as meta data
        var width = img.width;
        var height = img.height;
        var meta = {img: img, actualWidth: width, actualHeight: height};

        // Add the item with padding to our layer
        layer.addItem({
          width: width + padding,
          height: height + padding,
          meta: meta
        });
      });

      // Callback with nothing
      cb(null);
    },
    // Then, output the coordinates
    function smithOutputCoordinates (cb) {
      // Export and saved packedObj for later
      packedObj = layer['export']();

      // Extract the coordinates
      var coordinates = {};
      var packedItems = packedObj.items;
      packedItems.forEach(function (item) {
        var meta = item.meta;
        var img = meta.img;
        var name = img._filepath;
        coordinates[name] = {
          x: item.x,
          y: item.y,
          width: meta.actualWidth,
          height: meta.actualHeight
        };
      });

      // Save the coordinates
      retObj.coordinates = coordinates;

      // Continue
      cb(null);
    },
    // Then, generate a canvas
    function generateCanvas (cb) {
      // Grab and fallback the width/height
      var width = Math.max(packedObj.width || 0, 0);
      var height = Math.max(packedObj.height || 0, 0);

      // If there are items
      var itemsExist = packedObj.items.length;
      if (itemsExist) {
        // Remove the last item's padding
        width -= padding;
        height -= padding;
      }

      // Export the total width and height of the generated canvas
      retObj.properties = {
        width: width,
        height: height
      };

      // If there are items, generate the canvas
      if (itemsExist) {
        engine.createCanvas(width, height, cb);
      } else {
      // Otherwise, skip over potential errors/CPU
        cb(null, '');
      }
    },
    // Then, export the canvas
    function exportCanvas (canvas, cb) {
      // If there is no canvas, callback with an empty string
      var items = packedObj.items;
      if (!canvas) {
        return cb(null, '');
      }

      // Create a CanvasSmithy
      var canvasSmith = new CanvasSmith(canvas);

      // Add the images onto canvasSmith
      try {
        canvasSmith.addImages(items);
      } catch (err) {
        return cb(err);
      }

      // Export our canvas
      canvasSmith['export'](exportOpts, cb);
    },
    function saveImageToRetObj (imgStr, cb) {
      // Save the image to the retObj
      retObj.image = imgStr;

      // Callback
      cb(null);
    },
    function smithCallbackData (cb) {
      // Callback with the return object
      cb(null, retObj);
    }
  ], callback);
}

// Add the smiths to Spritesmith
Spritesmith.EngineSmith = EngineSmith;
Spritesmith.Layout = Layout;
Spritesmith.CanvasSmith = CanvasSmith;

// Export Spritesmith
module.exports = Spritesmith;
<commit_msg>Adjusted save info<commit_after>// Load in dependencies
var async = require('async');
var assert = require('assert');
var Layout = require('layout');
var EngineSmith = require('./smiths/engine.smith.js');
var CanvasSmith = require('./smiths/canvas.smith.js');

// Specify defaults
var engineDefault = 'pixelsmith';
var algorithmDefault = 'top-down';

/**
 * Spritesmith generation function
 * @param {Object} params Parameters for spritesmith
 * @param {String[]} [params.src] Images to generate into sprite sheet
 * @param {String} [params.engine="pixelsmith"] Engine to use (phantomjs, canvas, gm, pngsmith,
      or user-defined via Spritesmith.addEngine)
 * @param {String} [params.algorithm="top-down"] Algorithm to pack images with (top-down
      or user-defined via Spritesmith.addAlgorithm)
 * @param {Number} [params.padding] Padding to use between images
 * @param {Mixed} [params.exportOpts] Options to pass through to engine for export
 * @param {Function} callback Function that receives compiled spritesheet and map
 * @returns {Mixed} callback[0] err If an error was encountered, this will be returned to callback
 * @returns {Object} callback[1] result Result object of spritesmith
 * @returns {String} callback[1].image Binary string representation of image
 * @returns {Object} callback[1].coordinates Map from file name to an object containing x, y, height,
      and width information about the source image
 */
function Spritesmith(params, callback) {
  var retObj = {};
  var files = params.src;
  var engineName = params.engine || engineDefault;
  var engine = engineName;
  var algorithmName = params.algorithm || algorithmDefault;

  // If the engine is a `require` path, attempt to load it
  if (typeof engineName === 'string') {
    // Attempt to resolve the engine to verify it is installed at all
    try {
      require.resolve(engineName);
    } catch (err) {
      console.error('Attempted to find spritesmith engine "' + engineName + '" but could not.');
      console.error('Please verify you have installed "' + engineName + '" and saved it to your `package.json`');
      console.error('');
      console.error('    npm install ' + engineName + ' --save-dev');
      console.error('');
      throw err;
    }

    // Attempt to load the engine
    try {
      engine = require(engineName);
    } catch (err) {
      console.error('Attempted to load spritesmith engine "' + engineName + '" but could not.');
      console.error('Please verify you have installed its dependencies. Documentation should be available at ');
      console.error('');
      // TODO: Consider using pkg.repository
      console.error('    https://npm.im/' + encodeURIComponent(engineName));
      console.error('');
      throw err;
    }
  }

  // If there is a set parameter for the engine, use it
  if (engine.set) {
    var engineOpts = params.engineOpts || {};
    engine.set(engineOpts);
  }

  // Create our smiths
  var engineSmith = new EngineSmith(engine);
  var layer = new Layout(algorithmName, params.algorithmOpts);
  var padding = params.padding || 0;
  var exportOpts = params.exportOpts || {};
  var packedObj;

  // In a waterfall fashion
  async.waterfall([
    function grabImages (cb) {
      // Map the files into their image counterparts
      engineSmith.createImages(files, cb);
    },
    // Then, add the images to our canvas (dry run)
    function smithAddFiles (images, cb) {
      images.forEach(function (img) {
        // Save the non-padded properties as meta data
        var width = img.width;
        var height = img.height;
        var meta = {img: img, actualWidth: width, actualHeight: height};

        // Add the item with padding to our layer
        layer.addItem({
          width: width + padding,
          height: height + padding,
          meta: meta
        });
      });

      // Callback with nothing
      cb(null);
    },
    // Then, output the coordinates
    function smithOutputCoordinates (cb) {
      // Export and saved packedObj for later
      packedObj = layer['export']();

      // Extract the coordinates
      var coordinates = {};
      var packedItems = packedObj.items;
      packedItems.forEach(function (item) {
        var meta = item.meta;
        var img = meta.img;
        var name = img._filepath;
        coordinates[name] = {
          x: item.x,
          y: item.y,
          width: meta.actualWidth,
          height: meta.actualHeight
        };
      });

      // Save the coordinates
      retObj.coordinates = coordinates;

      // Continue
      cb(null);
    },
    // Then, generate a canvas
    function generateCanvas (cb) {
      // Grab and fallback the width/height
      var width = Math.max(packedObj.width || 0, 0);
      var height = Math.max(packedObj.height || 0, 0);

      // If there are items
      var itemsExist = packedObj.items.length;
      if (itemsExist) {
        // Remove the last item's padding
        width -= padding;
        height -= padding;
      }

      // Export the total width and height of the generated canvas
      retObj.properties = {
        width: width,
        height: height
      };

      // If there are items, generate the canvas
      if (itemsExist) {
        engine.createCanvas(width, height, cb);
      } else {
      // Otherwise, skip over potential errors/CPU
        cb(null, '');
      }
    },
    // Then, export the canvas
    function exportCanvas (canvas, cb) {
      // If there is no canvas, callback with an empty string
      var items = packedObj.items;
      if (!canvas) {
        return cb(null, '');
      }

      // Create a CanvasSmithy
      var canvasSmith = new CanvasSmith(canvas);

      // Add the images onto canvasSmith
      try {
        canvasSmith.addImages(items);
      } catch (err) {
        return cb(err);
      }

      // Export our canvas
      canvasSmith['export'](exportOpts, cb);
    },
    function saveImageToRetObj (imgStr, cb) {
      // Save the image to the retObj
      retObj.image = imgStr;

      // Callback
      cb(null);
    },
    function smithCallbackData (cb) {
      // Callback with the return object
      cb(null, retObj);
    }
  ], callback);
}

// Add the smiths to Spritesmith
Spritesmith.EngineSmith = EngineSmith;
Spritesmith.Layout = Layout;
Spritesmith.CanvasSmith = CanvasSmith;

// Export Spritesmith
module.exports = Spritesmith;
<|endoftext|>


<commit_before>'use strict'

const spawn = require('cross-spawn-promise')

function updateExecutableMissingException (err, logger) {
  if (logger && err.name === 'ENOENT') {
    const isFakeroot = err.syscall === 'spawn fakeroot'
    const isDpkg = !isFakeroot && err.syscall === 'spawn dpkg'

    if (isFakeroot || isDpkg) {
      const installer = process.platform === 'darwin' ? 'brew' : 'apt-get'
      const pkg = isFakeroot ? 'fakeroot' : 'dpkg'

      err.message = `Your system is missing the fakeroot package. Try, e.g. '${installer} install ${pkg}'`
    }
  }
}

/**
 * Spawn a child process.
 */
module.exports = function (cmd, args, logger) {
  if (logger) logger(`Executing command ${cmd} ${args.join(' ')}`)

  return spawn(cmd, args)
    .then(stdout => stdout.toString())
    .catch(err => {
      updateExecutableMissingException(err, logger)

      throw new Error(`Error executing command (${err.message || err}):\n${cmd} ${args.join(' ')}\n${err.stderr.toString()}`)
    })
}
<commit_msg>Better variable name<commit_after>'use strict'

const spawn = require('cross-spawn-promise')

function updateExecutableMissingException (err, updateError) {
  if (updateError && err.name === 'ENOENT') {
    const isFakeroot = err.syscall === 'spawn fakeroot'
    const isDpkg = !isFakeroot && err.syscall === 'spawn dpkg'

    if (isFakeroot || isDpkg) {
      const installer = process.platform === 'darwin' ? 'brew' : 'apt-get'
      const pkg = isFakeroot ? 'fakeroot' : 'dpkg'

      err.message = `Your system is missing the fakeroot package. Try, e.g. '${installer} install ${pkg}'`
    }
  }
}

/**
 * Spawn a child process and make the error message more human friendly, if possible.
 */
module.exports = function (cmd, args, logger) {
  if (logger) logger(`Executing command ${cmd} ${args.join(' ')}`)

  return spawn(cmd, args)
    .then(stdout => stdout.toString())
    .catch(err => {
      updateExecutableMissingException(err, !!logger)

      throw new Error(`Error executing command (${err.message || err}):\n${cmd} ${args.join(' ')}\n${err.stderr.toString()}`)
    })
}
<|endoftext|>


<commit_before>export const topState = {
  currentState: '',
  actions: {
    pull(payload) {
      this.currentState = 'pull';
    },

    trigger(payload) {
      this.currentState = 'trigger';
    },

    loading(payload) {
      this.currentState = 'loading';
    },

    loaded(payload) {
      this.currentState = 'loaded';
    }
  }
};

export const bottomState = {
  currentState: '',
  actions: {
    pull(payload) {
      this.currentState = 'pull';
    },

    trigger(payload) {
      this.currentState = 'trigger';
    },

    loading(payload) {
      this.currentState = 'loading';
    },

    loaded(payload) {
      this.currentState = 'loaded';
    }
  }
}

<commit_msg>Update<commit_after>export const topState = {
  state: '',
  actions: {
    pull(payload) {
      this.state = 'pull';
    },

    trigger(payload) {
      this.state = 'trigger';
    },

    loading(payload) {
      this.state = 'loading';
    },

    loaded(payload) {
      this.state = 'loaded';
    }
  }
};

export const bottomState = {
  state: '',
  actions: {
    pull(payload) {
      this.state = 'pull';
    },

    trigger(payload) {
      this.state = 'trigger';
    },

    loading(payload) {
      this.state = 'loading';
    },

    loaded(payload) {
      this.currentState = 'loaded';
    }
  }
}

<|endoftext|>


<commit_before>// Copyright © 2013 Steelbreeze Limited.
//
// state.js is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// returns the only element of an array that satisfies a specified condition; throws an exception if zero or more than one such elements exist
Array.prototype.single = function( predicate )
{
	var results = predicate ? this.filter( predicate ) : this;

	if( results.length === 1 )
	{
		return results[ 0 ];
	}

	throw new Error( "Cannot return zero or more than one elements" );
};

// returns the only element of an array that satisfies a specified condition; throws an exception if more than one such elements exist
Array.prototype.singleOrUndefined = function( predicate )
{
	var results = predicate ? this.filter( predicate ) : this;

	if( results.length === 1 )
	{
		return results[ 0 ];
	}

	if( results.length > 1 )
	{
		throw new Error( "Cannot return more than one elements" );
	}
};

// returns the a random element of an array that satisfies a specified condition; throws an exception if more than one such elements exist
Array.prototype.randomOrUndefined = function( predicate )
{
	var results = predicate ? this.filter( predicate ) : this;

	if( results.length !== 0 )
	{
		return results[ ( results.length - 1 ) * Math.random() ];
	}
};

// else guard for use in choice and junction pseudo states
var Else = function() { return false; };

var PseudoStateKind =
{
	Choice: { Name: "junction", IsInitial: false, IsHistory: false, GetCompletion: function( completions ) { var result = completions.randomOrUndefined( function( c ) { return c.Guard(); } ); if( !result ) { result = completions.single( function ( c ) { return c === Else; } ); } return result; } },
	DeepHistory: { Name: "deepHistory", IsInitial: true, IsHistory: true, GetCompletion: function( completions ) { return completions.single(); } },
	EntryPoint: { Name: "entryPoint", IsInitial: true, IsHistory: false, GetCompletion: function( completions ) { return completions.single(); } },
	ExitPoint: { Name: "exitPoint", IsInitial: false, IsHistory: false, GetCompletion: function( completions ) { return completions.single( function( c ) { return c.Guard(); } ); } },
	Initial: { Name: "initial", IsInitial: true, IsHistory: false, GetCompletion: function( completions ) { return completions.single(); } },
	Junction: { Name: "junction", IsInitial: false, IsHistory: false, GetCompletion: function( completions ) { var result = completions.singleOrUndefined( function( c ) { return c.Guard(); } ); if( !result ) { result = completions.single( function ( c ) { return c === Else; } ); } return result; } },
	ShallowHistory: { Name: "shallowHistory", IsInitial: true, IsHistory: true, GetCompletion: function( completions ) { return completions.single(); } }
};

// region constructor
function Region( name, parent )
{
	if( parent )
	{
		console.assert( parent instanceof State, "parent must be a State" );

		parent.Regions.push( this );
	}

	this.Name = name;
	this.Parent = parent;
	this.IsActive = false;
	this.Current = null;
	this.Vertices = [];
}

Region.prototype =
{
	// true if a region is 'complete' (the current state is a final state)
	IsComplete : function()
	{
		return this.Current instanceof FinalState;
	},

	// initialises a region for use or subsiquent reuse
	Initialise : function( deepHistory )
	{
		if( !deepHistory )
		{
			deepHistory = false;
		}

		this.BeginEnter();

		var vertex = ( ( deepHistory || this.Initial.Kind.IsHistory ) && ( this.Current !== null ) ) ? this.Current : this.Initial;

		vertex.Initialise( deepHistory || this.Kind === PseudoStateKind.DeepHistory );
	},

	Process : function( message )
	{
		if( this.Current !== null )
		{
			this.Current.Process( message );
		}
	},

	Reset : function()
	{
		this.Vertices.forEach( function( vertex ) { vertex.Reset(); } );
		this.IsActive = false;
		this.Current = null; // TODO: remove the property rather than set null (or init to null)
	},

	OnExit : function()
	{
		if( this.Current !== null && this.Current.IsActive === true )
		{
			this.Current.OnExit();
		}

		console.log( "Leave: " + this.toString() );

		this.IsActive = false;
	},

	BeginEnter : function()
	{
		if( this.IsActive === true )
		{
			this.OnExit();
		}

		console.log( "Enter: " + this );

		this.IsActive = true;
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Name : this.Name;
	}
};

function PseudoState( kind, parent )
{
	// use a state's default region if a state was provided
	if( parent instanceof State )
	{
		parent = parent.DefaultRegion();
	}

	// validate parameters
//	console.assert( kind in PseudoStateKind, "kind must be a PseudoStateKind" ) // TODO: get something like this working
	console.assert( parent instanceof Region, "parent must be a Region" );

	this.Kind = kind;
	this.Parent = parent;
	this.Completions = [];

	// set the parent region's initial pseudo state value
	if( kind.IsInitial === true )
	{
		if( parent.Initial )
		{
			throw new Error( "Regions can have at most 1 initial pseudo state" );
		}

		parent.Initial = this;
	}

	// add the pseudo state to its parent
	parent.Vertices.push( this );
}

PseudoState.prototype =
{
	Initialise: function()
	{
		this.BeginEnter();
		this.EndEnter( false );
	},

	Reset : function()
	{
	},

	OnExit: function()
	{
		console.log( "Leave: " + this );
	},

	BeginEnter: function()
	{
		console.log( "Enter: " + this );
	},

	EndEnter : function( deepHistory )
	{
		this.Kind.GetCompletion( this.Completions ).Traverse( deepHistory );
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Kind.Name : this.Kind.Name;
	}
};

function State( name, parent )
{
	if( parent )
	{
		if( parent instanceof State )
		{
			parent = parent.DefaultRegion();
		}

		console.assert( parent instanceof Region, "parent must be a Region" );

		parent.Vertices.push( this );
	}

	this.Name = name;
	this.Parent = parent;
	this.IsActive = false;
	this.Entry = [];
	this.Exit = [];
	this.Regions = [];
	this.Completions = [];
	this.Transitions = [];
}

State.prototype =
{
	IsSimple : function()
	{
		return this.Regions.length === 0;
	},

	DefaultRegion: function()
	{
		if( !this.defaultRegion )
		{
			this.defaultRegion = new Region( "default", this );
		}

		return this.defaultRegion;
	},

	Initialise: function()
	{
		this.BeginEnter();
		this.EndEnter( false );
	},

	Process : function( message )
	{
		var transition = this.Transitions.singleOrUndefined( function( t ) { return t.Guard( message ); } );
		var processed = transition ? true : false;

		if( processed === true )
		{
			transition.Traverse( message );
		}
		else
		{
			if( this.IsSimple() === false )
			{
				for( var i = 0; i < this.Regions.length; i++ )
				{
					if( this.Regions[ i ].IsActive === true )
					{
						processed |= this.Regions[ i ].Process( message );
					}
				}
			}
		}

		return processed;
	},

	Reset : function()
	{
		this.Regions.forEach( function( region ) { region.Reset(); } );

		this.IsActive = false;
	},

	OnExit : function()
	{
		this.Regions.forEach( function( region ) { if( region.IsActive ) { region.OnExit(); } } );

		this.Exit.forEach( function( action ) { action(); } );

		console.log( "Leave: " + this.toString() );

		this.IsActive = false;
	},

	BeginEnter : function()
	{
		if( this.IsActive === true )
		{
			this.OnExit();
		}

		console.log( "Enter: " + this.toString() );

		this.IsActive = true;

		if( this.Parent )
		{
			this.Parent.Current = this;
		}

		this.Entry.forEach( function( action ) { action(); } );
	},

	EndEnter : function( deepHistory )
	{
		this.Regions.forEach( function( region ) { region.Initialise( deepHistory ); } );

		if( this.Completions.length > 0 )
		{
			if( this.IsSimple() || this.Regions.every( function( region ) { return region.IsComplete(); } ) )
			{
				var completion = this.Completions.singleOrUndefined( function( c ) { return c.Guard(); } );

				if( completion )
				{
					completion( deepHistory );
				}
			}
		}
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Name : this.Name;
	}
};

function FinalState( name, parent )
{
	if( parent instanceof State )
	{
		parent = parent.DefaultRegion();
	}

	console.assert( parent instanceof Region, "parent must be a Region" );

	this.Name = name;
	this.Parent = parent;
	this.IsActive = false;

	parent.Vertices.push( this );
}

FinalState.prototype =
{
	Initialise: function()
	{
		this.BeginEnter();
		this.EndEnter( false );
	},

	Process : function( message )
	{
	},

	Reset : function()
	{
		this.IsActive = false;
	},

	BeginEnter : function()
	{
		if( this.IsActive === true )
		{
			this.OnExit();
		}

		console.log( "Enter: " + this.toString() );

		this.IsActive = true;

		if( this.Parent )
		{
			this.Parent.Current = this;
		}
	},

	EndEnter : function( deepHistory )
	{
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Name : this.Name;
	}
};

function Completion( source, target, guard )
{
	this.Guard = guard ? guard : function() { return true; };
	this.Path = Path( source, target );
	this.Effect = [];

	source.Completions.push( this );
}

Completion.prototype =
{
	Traverse: function( deepHistory )
	{
		this.Path.Exit.forEach( function( action ) { action(); } );

		this.Effect.forEach( function( action ) { action(); } );

		this.Path.Enter.forEach( function( action ) { action(); } );

		if( this.Path.Complete )
			this.Path.Complete( deepHistory );
	}
};

function Transition( source, target, guard )
{
	console.assert( source instanceof State, "Source of a transition must be a State" );

	this.Guard = guard ? guard : function( message ) { return true; };
	this.Path = Path( source, target );
	this.Effect = [];

	source.Transitions.push( this );
}

Transition.prototype =
{
	Traverse: function( message )
	{
		this.Path.Exit.forEach( function( action ) { action(); } );

		this.Effect.forEach( function( action ) { action( message ); } );

		this.Path.Enter.forEach( function( action ) { action(); } );

		if( this.Path.Complete )
			this.Path.Complete( false );
	}
};

// the path to take between vertices
function Path( source, target )
{
	var path = { Exit: [], Enter: [] };

	if( target !== null )
	{
		// get the ancestors of the source and target vertices
		var sourceAncestors = Ancestors( source );
		var targetAncestors = Ancestors( target );

		// iterate over all common ancestors
		for( var i = 0; sourceAncestors[ i ] === targetAncestors[ i ]; i++ );

		// special case for leaving pseudo states where source and target vertices are in different regions
		if( source instanceof PseudoState && ( sourceAncestors[ i ] != source ) )
		{
			path.Exit.push( function() { source.OnExit(); } );
		}

		// leave the source ancestry (exit cascades)
		( function( s ) { path.Exit.push( function() { s.OnExit(); } ); } )( sourceAncestors[ i ] );

		// enter the target ancestry (beginEnter does not cascade)
		for( ; i < targetAncestors.length; i++ )
		{
			( function( t ) { path.Enter.push( function() { t.BeginEnter(); } ); } )( targetAncestors[ i ] );
		}

		// complete entry (endEnter cascades to any child regions)
		path.Complete = function() { target.EndEnter(); };
	}

	return path;
}

// returns the ancstors of a node
function Ancestors( node )
{
	if( node.Parent )
	{
		return Ancestors( node.Parent ).concat( node );
	}
	else
	{
		return [ node ];
	}
}

// exports for node.js
if( typeof exports != 'undefined' )
{
	exports.Completion = Completion;
	exports.Else = Else;
	exports.FinalState = FinalState;
	exports.Region = Region;
	exports.PseudoState = PseudoState;
	exports.State = State;
	exports.PseudoStateKind = PseudoStateKind;
	exports.Transition = Transition;
}
<commit_msg>Bug fix & trivial performance<commit_after>// Copyright © 2013 Steelbreeze Limited.
//
// state.js is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// returns the only element of an array that satisfies a specified condition; throws an exception if zero or more than one such elements exist
Array.prototype.single = function( predicate )
{
	var results = predicate ? this.filter( predicate ) : this;

	if( results.length === 1 )
	{
		return results[ 0 ];
	}

	throw new Error( "Cannot return zero or more than one elements" );
};

// returns the only element of an array that satisfies a specified condition; throws an exception if more than one such elements exist
Array.prototype.singleOrUndefined = function( predicate )
{
	var results = predicate ? this.filter( predicate ) : this;

	if( results.length === 1 )
	{
		return results[ 0 ];
	}

	if( results.length > 1 )
	{
		throw new Error( "Cannot return more than one elements" );
	}
};

// returns the a random element of an array that satisfies a specified condition; throws an exception if more than one such elements exist
Array.prototype.randomOrUndefined = function( predicate )
{
	var results = predicate ? this.filter( predicate ) : this;

	if( results.length !== 0 )
	{
		return results[ ( results.length - 1 ) * Math.random() ];
	}
};

// else guard for use in choice and junction pseudo states
var Else = function() { return false; };

var PseudoStateKind =
{
	Choice: { Name: "junction", IsInitial: false, IsHistory: false, GetCompletion: function( completions ) { var result = completions.randomOrUndefined( function( c ) { return c.Guard(); } ); if( !result ) { result = completions.single( function ( c ) { return c === Else; } ); } return result; } },
	DeepHistory: { Name: "deepHistory", IsInitial: true, IsHistory: true, GetCompletion: function( completions ) { return completions.single(); } },
	EntryPoint: { Name: "entryPoint", IsInitial: true, IsHistory: false, GetCompletion: function( completions ) { return completions.single(); } },
	ExitPoint: { Name: "exitPoint", IsInitial: false, IsHistory: false, GetCompletion: function( completions ) { return completions.single( function( c ) { return c.Guard(); } ); } },
	Initial: { Name: "initial", IsInitial: true, IsHistory: false, GetCompletion: function( completions ) { return completions.single(); } },
	Junction: { Name: "junction", IsInitial: false, IsHistory: false, GetCompletion: function( completions ) { var result = completions.singleOrUndefined( function( c ) { return c.Guard(); } ); if( !result ) { result = completions.single( function ( c ) { return c === Else; } ); } return result; } },
	ShallowHistory: { Name: "shallowHistory", IsInitial: true, IsHistory: true, GetCompletion: function( completions ) { return completions.single(); } }
};

// region constructor
function Region( name, parent )
{
	if( parent )
	{
		console.assert( parent instanceof State, "parent must be a State" );

		parent.Regions.push( this );
	}

	this.Name = name;
	this.Parent = parent;
	this.IsActive = false;
	this.Current = null;
	this.Vertices = [];
}

Region.prototype =
{
	// true if a region is 'complete' (the current state is a final state)
	IsComplete : function()
	{
		return this.Current instanceof FinalState;
	},

	// initialises a region for use or subsiquent reuse
	Initialise : function( deepHistory )
	{
		if( !deepHistory )
		{
			deepHistory = false;
		}

		this.BeginEnter();

		var vertex = ( ( deepHistory || this.Initial.Kind.IsHistory ) && ( this.Current !== null ) ) ? this.Current : this.Initial;

		vertex.Initialise( deepHistory || this.Initial.Kind === PseudoStateKind.DeepHistory );
	},

	Process : function( message )
	{
		if( this.Current !== null )
		{
			this.Current.Process( message );
		}
	},

	Reset : function()
	{
		this.Vertices.forEach( function( vertex ) { vertex.Reset(); } );
		this.IsActive = false;
		this.Current = null; // TODO: remove the property rather than set null (or init to null)
	},

	OnExit : function()
	{
		if( this.Current !== null && this.Current.IsActive === true )
		{
			this.Current.OnExit();
		}

		console.log( "Leave: " + this.toString() );

		this.IsActive = false;
	},

	BeginEnter : function()
	{
		if( this.IsActive === true )
		{
			this.OnExit();
		}

		console.log( "Enter: " + this );

		this.IsActive = true;
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Name : this.Name;
	}
};

function PseudoState( kind, parent )
{
	// use a state's default region if a state was provided
	if( parent instanceof State )
	{
		parent = parent.DefaultRegion();
	}

	// validate parameters
//	console.assert( kind in PseudoStateKind, "kind must be a PseudoStateKind" ) // TODO: get something like this working
	console.assert( parent instanceof Region, "parent must be a Region" );

	this.Kind = kind;
	this.Parent = parent;
	this.Completions = [];

	// set the parent region's initial pseudo state value
	if( kind.IsInitial === true )
	{
		if( parent.Initial )
		{
			throw new Error( "Regions can have at most 1 initial pseudo state" );
		}

		parent.Initial = this;
	}

	// add the pseudo state to its parent
	parent.Vertices.push( this );
}

PseudoState.prototype =
{
	Initialise: function()
	{
		this.BeginEnter();
		this.EndEnter( false );
	},

	Reset : function()
	{
	},

	OnExit: function()
	{
		console.log( "Leave: " + this );
	},

	BeginEnter: function()
	{
		console.log( "Enter: " + this );
	},

	EndEnter : function( deepHistory )
	{
		this.Kind.GetCompletion( this.Completions ).Traverse( deepHistory );
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Kind.Name : this.Kind.Name;
	}
};

function State( name, parent )
{
	if( parent )
	{
		if( parent instanceof State )
		{
			parent = parent.DefaultRegion();
		}

		console.assert( parent instanceof Region, "parent must be a Region" );

		parent.Vertices.push( this );
	}

	this.Name = name;
	this.Parent = parent;
	this.IsActive = false;
	this.Entry = [];
	this.Exit = [];
	this.Regions = [];
	this.Completions = [];
	this.Transitions = [];
}

State.prototype =
{
	IsSimple : function()
	{
		return this.Regions.length === 0;
	},

	DefaultRegion: function()
	{
		if( !this.defaultRegion )
		{
			this.defaultRegion = new Region( "default", this );
		}

		return this.defaultRegion;
	},

	Initialise: function()
	{
		this.BeginEnter();
		this.EndEnter( false );
	},

	Process : function( message )
	{
		var transition = this.Transitions.singleOrUndefined( function( t ) { return t.Guard( message ); } );
		var processed = transition ? true : false;

		if( processed === true )
		{
			transition.Traverse( message );
		}
		else
		{
			if( this.IsSimple() === false )
			{
				for( var i = 0; i < this.Regions.length; i++ )
				{
					if( this.Regions[ i ].IsActive === true )
					{
						processed |= this.Regions[ i ].Process( message );
					}
				}
			}
		}

		return processed;
	},

	Reset : function()
	{
		this.Regions.forEach( function( region ) { region.Reset(); } );

		this.IsActive = false;
	},

	OnExit : function()
	{
		this.Regions.forEach( function( region ) { if( region.IsActive ) { region.OnExit(); } } );

		this.Exit.forEach( function( action ) { action(); } );

		console.log( "Leave: " + this.toString() );

		this.IsActive = false;
	},

	BeginEnter : function()
	{
		if( this.IsActive === true )
		{
			this.OnExit();
		}

		console.log( "Enter: " + this.toString() );

		this.IsActive = true;

		if( this.Parent )
		{
			this.Parent.Current = this;
		}

		this.Entry.forEach( function( action ) { action(); } );
	},

	EndEnter : function( deepHistory )
	{
		this.Regions.forEach( function( region ) { region.Initialise( deepHistory ); } );

		if( this.Completions.length > 0 )
		{
			if( this.IsSimple() || this.Regions.every( function( region ) { return region.IsComplete(); } ) )
			{
				var completion = this.Completions.singleOrUndefined( function( c ) { return c.Guard(); } );

				if( completion )
				{
					completion( deepHistory );
				}
			}
		}
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Name : this.Name;
	}
};

function FinalState( name, parent )
{
	if( parent instanceof State )
	{
		parent = parent.DefaultRegion();
	}

	console.assert( parent instanceof Region, "parent must be a Region" );

	this.Name = name;
	this.Parent = parent;
	this.IsActive = false;

	parent.Vertices.push( this );
}

FinalState.prototype =
{
	Initialise: function()
	{
		this.BeginEnter();
		this.EndEnter( false );
	},

	Process : function( message )
	{
	},

	Reset : function()
	{
		this.IsActive = false;
	},

	BeginEnter : function()
	{
		if( this.IsActive === true )
		{
			this.OnExit();
		}

		console.log( "Enter: " + this.toString() );

		this.IsActive = true;

		if( this.Parent )
		{
			this.Parent.Current = this;
		}
	},

	EndEnter : function( deepHistory )
	{
	},

	toString: function()
	{
		return this.Parent ? this.Parent.toString() + "." + this.Name : this.Name;
	}
};

function Completion( source, target, guard )
{
	this.Guard = guard ? guard : function() { return true; };
	this.Path = Path( source, target );
	this.Effect = [];

	source.Completions.push( this );
}

Completion.prototype =
{
	Traverse: function( deepHistory )
	{
		this.Path.Exit.forEach( function( action ) { action(); } );

		this.Effect.forEach( function( action ) { action(); } );

		this.Path.Enter.forEach( function( action ) { action(); } );

		if( this.Path.Complete )
			this.Path.Complete( deepHistory );
	}
};

function Transition( source, target, guard )
{
	console.assert( source instanceof State, "Source of a transition must be a State" );

	this.Guard = guard ? guard : function( message ) { return true; };
	this.Path = Path( source, target );
	this.Effect = [];

	source.Transitions.push( this );
}

Transition.prototype =
{
	Traverse: function( message )
	{
		this.Path.Exit.forEach( function( action ) { action(); } );

		this.Effect.forEach( function( action ) { action( message ); } );

		this.Path.Enter.forEach( function( action ) { action(); } );

		if( this.Path.Complete )
			this.Path.Complete( false );
	}
};

// the path to take between vertices
function Path( source, target )
{
	var path = { Exit: [], Enter: [] };

	if( target !== null )
	{
		// get the ancestors of the source and target vertices
		var sourceAncestors = Ancestors( source );
		var targetAncestors = Ancestors( target );

		// iterate over all common ancestors
		for( var i = 0; sourceAncestors[ i ] === targetAncestors[ i ]; i++ );

		// special case for leaving pseudo states where source and target vertices are in different regions
		if( source instanceof PseudoState && ( sourceAncestors[ i ] !== source ) )
		{
			path.Exit.push( function() { source.OnExit(); } );
		}

		// leave the source ancestry (exit cascades)
		( function( s ) { path.Exit.push( function() { s.OnExit(); } ); } )( sourceAncestors[ i ] );

		// enter the target ancestry (beginEnter does not cascade)
		for( ; i < targetAncestors.length; i++ )
		{
			( function( t ) { path.Enter.push( function() { t.BeginEnter(); } ); } )( targetAncestors[ i ] );
		}

		// complete entry (endEnter cascades to any child regions)
		path.Complete = function() { target.EndEnter(); };
	}

	return path;
}

// returns the ancstors of a node
function Ancestors( node )
{
	if( node.Parent )
	{
		return Ancestors( node.Parent ).concat( node );
	}
	else
	{
		return [ node ];
	}
}

// exports for node.js
if( typeof exports !== 'undefined' )
{
	exports.Completion = Completion;
	exports.Else = Else;
	exports.FinalState = FinalState;
	exports.Region = Region;
	exports.PseudoState = PseudoState;
	exports.State = State;
	exports.PseudoStateKind = PseudoStateKind;
	exports.Transition = Transition;
}
<|endoftext|>


<commit_before>import React     from 'react'
import thunk     from 'redux-thunk'
import { merge, reduce } from 'lodash'
import {
  compose,
  createStore,
  combineReducers,
  applyMiddleware
} from 'redux'
import { Provider } from 'react-redux'
import { persistState } from 'react-devtools/lib/persistState'

export default class Store {

  static defaults = {
    devTools: true,
    reducers: {},
    middleware: [
      thunk
    ],
    createMiddleware(middleware, options) {
      // middleware.push(new ApiClient(options))
      return middleware
    },
    composers: [],
    createComposers(x) { return x }
  }

  constructor(opts) {
    this.options = merge({}, Store.defaults, opts)
    this.reducers = this.options.reducers
    this.middleware = this.options.middleware
    this.composers = this.options.composers
  }

  addReducer(name, reducer) {
    this.reducers[name] = reducer
  }

  removeReducer(name) {
    delete this.reducers[name]
  }

  getInitialState() {
    if (SERVER) {
      return {}
    }
    return window[this.options.rootVar]
  }

  finalize(req, router = null) {
    const initialState = this.getInitialState()
    const middleware = this.options.createMiddleware(this.middleware, { req })
    const reducer = combineReducers({ ...this.reducers })

    this.composers.push(applyMiddleware(...middleware))

    if (CLIENT && this.options.devTools && window.devToolsExtension) {
      this.composers.push(
        window.devToolsExtension(),
        persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
      )
    }

    const composers = this.options.createComposers(this.composers)
    const finalCreateStore = compose(...composers)(createStore)
    return finalCreateStore(reducer, initialState)
  }

  getWrappedComponent(store, instance) {
    return <Provider store={store}>{instance}</Provider>
  }

  fetchComponentData(store, components, params) {
    const needs = reduce(components, (prev, current) => {
      let nested = []
      if (current.WrappedComponent && current.WrappedComponent.need !== current.need) {
        nested = current.WrappedComponent.need
      }
      return (current.need || [])
        .concat(nested)
        .concat(prev)
    }, [])
    return sequence(needs, need => store.dispatch(need(params, store.getState())))
  }
}

function sequence(items, consumer) {
  const results = []
  const runner = () => {
    const item = items.shift()
    if (item) {
      return consumer(item)
        .then((result) => {
          results.push(result)
        })
        .then(runner);
    }
    return Promise.resolve(results)
  }
  return runner();
}
<commit_msg>whitespace<commit_after>import React     from 'react'
import thunk     from 'redux-thunk'
import { merge, reduce } from 'lodash'
import { Provider } from 'react-redux'
import { persistState } from 'react-devtools/lib/persistState'
import {
  compose,
  createStore,
  combineReducers,
  applyMiddleware
} from 'redux'

export default class Store {

  static defaults = {
    devTools: true,
    reducers: {},
    middleware: [
      thunk
    ],
    createMiddleware(middleware, options) {
      // middleware.push(new ApiClient(options))
      return middleware
    },
    composers: [],
    createComposers(x) { return x }
  }

  constructor(opts) {
    this.options = merge({}, Store.defaults, opts)
    this.reducers = this.options.reducers
    this.middleware = this.options.middleware
    this.composers = this.options.composers
  }

  addReducer(name, reducer) {
    this.reducers[name] = reducer
  }

  removeReducer(name) {
    delete this.reducers[name]
  }

  getInitialState() {
    if (SERVER) {
      return {}
    }
    return window[this.options.rootVar]
  }

  finalize(req, router = null) {
    const initialState = this.getInitialState()
    const middleware = this.options.createMiddleware(this.middleware, { req })
    const reducer = combineReducers({ ...this.reducers })

    this.composers.push(applyMiddleware(...middleware))

    if (CLIENT && this.options.devTools && window.devToolsExtension) {
      this.composers.push(
        window.devToolsExtension(),
        persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
      )
    }

    const composers = this.options.createComposers(this.composers)
    const finalCreateStore = compose(...composers)(createStore)
    return finalCreateStore(reducer, initialState)
  }

  getWrappedComponent(store, instance) {
    return <Provider store={store}>{instance}</Provider>
  }

  fetchComponentData(store, components, params) {
    const needs = reduce(components, (prev, current) => {
      let nested = []
      if (current.WrappedComponent && current.WrappedComponent.need !== current.need) {
        nested = current.WrappedComponent.need
      }
      return (current.need || [])
        .concat(nested)
        .concat(prev)
    }, [])
    return sequence(needs, need => store.dispatch(need(params, store.getState())))
  }
}

function sequence(items, consumer) {
  const results = []
  const runner = () => {
    const item = items.shift()
    if (item) {
      return consumer(item)
        .then((result) => {
          results.push(result)
        })
        .then(runner);
    }
    return Promise.resolve(results)
  }
  return runner();
}
<|endoftext|>


<commit_before>
superagent = require("superagent");

var Store = {
  callbacks: {
    adding: [],
    change: [],
    fetch:  [],
    error:  [],
    removing: [],
    remove: [],
  },
  fetchAll: function() {
    var store = this;
    superagent
      .get("/list.json")
      .end(function(err, res) {
        if (res.ok) {
          store.dispatch("fetch", res.body);
        } else {
          store.dispatch("error", "ほしいものリストの取得に失敗しました");
        }
      });
  },
  create: function(url) {
    var store = this;
    store.dispatch("adding");
    superagent
      .post("/add")
      .type("form")
      .send({url: url})
      .end(function(err, res) {
        if (res.ok) {
          store.dispatch("change");
        } else {
          store.dispatch("error", "ほしいものリストの追加に失敗しました: " + url);
        }
      });
  },
  remove: function(id) {
    var store = this;
    store.dispatch("removing", id);
    superagent
      .post("/remove")
      .type("form")
      .send({id: id})
      .end(function(err, res) {
        if (res.ok) {
          store.dispatch("remove", id);
        } else {
          store.dispatch("chane");
          store.dispatch("error", "削除に失敗しました");
        }
      });
  },
  on: function(name, callback) {
    if (!this.callbacks[name]) {
      return;
    }
    this.callbacks[name].push(callback);
  },
  dispatch: function(name, arg) {
    var callbacks = this.callbacks[name];
    if (!callbacks) { return }
    callbacks.forEach(function(callback) {
      callback(arg);
    });
  },
};

module.exports = Store;
<commit_msg>Fix typo<commit_after>
superagent = require("superagent");

var Store = {
  callbacks: {
    adding: [],
    change: [],
    fetch:  [],
    error:  [],
    removing: [],
    remove: [],
  },
  fetchAll: function() {
    var store = this;
    superagent
      .get("/list.json")
      .end(function(err, res) {
        if (res.ok) {
          store.dispatch("fetch", res.body);
        } else {
          store.dispatch("error", "ほしいものリストの取得に失敗しました");
        }
      });
  },
  create: function(url) {
    var store = this;
    store.dispatch("adding");
    superagent
      .post("/add")
      .type("form")
      .send({url: url})
      .end(function(err, res) {
        if (res.ok) {
          store.dispatch("change");
        } else {
          store.dispatch("error", "ほしいものリストの追加に失敗しました: " + url);
        }
      });
  },
  remove: function(id) {
    var store = this;
    store.dispatch("removing", id);
    superagent
      .post("/remove")
      .type("form")
      .send({id: id})
      .end(function(err, res) {
        if (res.ok) {
          store.dispatch("remove", id);
        } else {
          store.dispatch("change");
          store.dispatch("error", "削除に失敗しました");
        }
      });
  },
  on: function(name, callback) {
    if (!this.callbacks[name]) {
      return;
    }
    this.callbacks[name].push(callback);
  },
  dispatch: function(name, arg) {
    var callbacks = this.callbacks[name];
    if (!callbacks) { return }
    callbacks.forEach(function(callback) {
      callback(arg);
    });
  },
};

module.exports = Store;
<|endoftext|>


<commit_before>import { createStore } from 'redux';

import Parser from './parser';
import Placeholder from './ast/placeholder';
import { add, sub, mul, div } from './operations';
import { traverseNode, deepEqual, findNode, compare } from './ast/node-utils';
import params from './params';
import Selection from './ui/selection';

const parser = new Parser();

const initialState = {
    steps: [{
        math: params.start ? parser.parse(params.start) : parser.parse('2x+5=10'),
    }],
    currentIndex: 0,
    activeIndex: 0,
    goal: params.end ? parser.parse(params.end) : parser.parse('x=5/2')
};

const reducer = (state = initialState, action) => {
    const currentStep = state.steps[state.currentIndex];
    const newMath = currentStep.userInput ? currentStep.userInput.math.clone() : currentStep.math.clone();
    const lastStep = state.steps[state.steps.length - 1];
    const previousSteps = state.steps.slice(0, state.currentIndex);
    let maxId = 0;

    switch (action.type) {
        case 'SELECT_STEP':
            return {
                ...state,
                activeIndex: action.index,
            };
        case 'SELECT_MATH':
            return {
                ...state,
                steps: [
                    ...state.steps.slice(0, state.currentIndex),
                    {
                        ...currentStep,
                        selections: action.selections,
                    },
                    ...state.steps.slice(state.currentIndex + 1)
                ],
            };
        case 'SIMPLE_OPERATION':
            // TODO: have two modes... when we're in insertion mode any keystroke get's appended to the current insertionText
            // TODO: we need to keep track of the operation we're using during the insertion mode so we can insert parens appropriately

            // TODO: reduce for tree traversal
            traverseNode(newMath, node => maxId = Math.max(maxId, node.id));

            if (currentStep.cursor) {
                traverseNode(newMath, node => {
                    if (node.type === 'Placeholder') {
                        node.text += action.operator;
                    }
                });

                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            math: newMath,
                            maxId: currentStep.maxId,
                            cursor: true,
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            }

            if (currentStep.math.root.type === 'Equation') {
                const op = {
                    '+': add,
                    '-': sub,
                    '*': mul,
                    '/': div
                }[action.operator];
                const placeholder = new Placeholder();
                newMath.root = op(newMath.root, placeholder);
            }

            return {
                ...state,
                currentIndex: state.currentIndex + 1,
                activeIndex: state.activeIndex + 1,
                steps: [
                    ...state.steps.slice(0, state.currentIndex + 1),
                    {
                        math: newMath,
                        maxId: maxId,
                        action: {
                            type: 'INSERT',
                            operation: action.operator,
                            value: null
                        },
                    },
                ],
            };
        case 'SHOW_CURSOR':
            return {
                ...state,
                steps: [
                    ...state.steps.slice(0, state.currentIndex),
                    {
                        ...currentStep,
                        cursor: true,
                    },
                    ...state.steps.slice(state.currentIndex + 1)
                ]
            };
        case 'INSERT_NUMBER':
            traverseNode(newMath, node => {
                if (node.type === 'Placeholder') {
                    node.text += action.number;
                }
            });

            if (currentStep.userInput) {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            userInput: {
                                ...currentStep.userInput,
                                math: newMath,
                                incorrect: false,
                            },
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            } else {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            math: newMath,
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            }
        case 'BACKSPACE':
            traverseNode(newMath, node => {
                if (node.type === 'Placeholder') {
                    if (node.text === '') {
                        // TODO: think about how to undo the operation
                    } else {
                        node.text = node.text.slice(0, node.text.length - 1);
                    }
                }
            });

            if (currentStep.userInput) {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            userInput: {
                                ...currentStep.userInput,
                                math: newMath,
                                incorrect: false,
                            },
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            } else {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            math: newMath,
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            }
        case 'ACCEPT_STEP':
            let value = null;
            traverseNode(newMath, node => {
                if (node.type === 'Placeholder') {
                    // TODO: try/catch and provide feedback if math isn't valid
                    value = parser.parse(node.text).root;
                    node.parent.replace(node, value);
                }
            });

            if (currentStep.userInput) {
                const selections = currentStep.selections;
                const {transform} = currentStep.userInput;

                const newNewMath = currentStep.math.clone();

                const newSelections = selections.map(selection => {
                    const first = findNode(newNewMath, selection.first.id);
                    const last = findNode(newNewMath, selection.last.id);
                    return new Selection(first, last);
                });

                if (compare(newMath.root.left, newMath.root.right)) {
                    if (transform.canTransform(newSelections)) {
                        transform.doTransform(newSelections, newMath.root.right.clone());
                    }

                    return {
                        ...state,
                        steps: [
                            ...previousSteps,
                            {
                                selections: [],
                                math: currentStep.math.clone(),
                                action: {
                                    type: 'TRANSFORM',
                                    transform: transform,
                                    selections: selections,
                                },
                            },
                            {
                                math: newNewMath,
                                selections: [],
                                active: true,
                            },
                        ],
                        activeIndex: state.activeIndex + 1,
                        currentIndex: state.currentIndex + 1,
                    };
                } else {
                    return {
                        ...state,
                        steps: [
                            ...state.steps.slice(0, state.currentIndex),
                            {
                                ...currentStep,
                                userInput: {
                                    ...currentStep.userInput,
                                    incorrect: true,
                                },
                            },
                            ...state.steps.slice(state.currentIndex + 1)
                        ],
                    };
                }
            } else {
                return {
                    ...state,
                    steps: [
                        ...previousSteps.slice(0, previousSteps.length - 1),
                        {
                            ...previousSteps[previousSteps.length - 1],
                            action: {
                                ...lastStep.action,
                                value: value.clone(),
                                maxId: currentStep.maxId,
                            },
                        },
                        {
                            ...currentStep,
                            math: newMath,
                            cursor: null,
                            maxId: Infinity,
                        },
                    ],
                };
            }
        case 'ADD_STEP':
            return {
                ...state,
                steps: [
                    ...previousSteps,
                    {
                        math: currentStep.math.clone(),
                        action: {
                            type: 'TRANSFORM',
                            transform: action.transform,
                            selections: currentStep.selections,
                        },
                    },
                    {
                        math: action.math,
                    },
                ],
                activeIndex: state.activeIndex + 1,
                currentIndex: state.currentIndex + 1,
            };
        case 'CHECK_ANSWER':
            const finished = deepEqual(state.goal, currentStep.math);

            const jsonifyAction = function(action) {
                if (action) {
                    const json = { type: action.type };

                    if (json.type === 'TRANSFORM') {
                        json.transform = action.transform.label;
                        json.selections = action.selections.map(selection => selection.toExpression().toString());
                    } else if (json.type === 'INSERT') {
                        json.operation = action.operation;
                        json.value = JSON.stringify(action.value);
                    }

                    return json;
                } else {
                    return null;
                }
            };

            if (finished) {
                $.ajax({
                    url: 'http://localhost:3000/api/steps',
                    method: 'post',
                    data: {
                        steps: state.steps.map(step => {
                            return {
                                math: JSON.stringify(step.math),
                                action: jsonifyAction(step.action)
                            }
                        })
                    }
                }).then(res => {
                    console.log(res);
                });
            }

            return {
                ...state,
                finished: finished,
            };
        case 'GET_USER_INPUT':
            const selection = action.selections[0];
            const math = parser.parse('a=b');
            const left = selection.toExpression();
            const right = new Placeholder();
            math.root.left = left;
            left.parent = math.root;
            math.root.right = right;
            right.parent = math.root;

            return {
                ...state,
                steps: [
                    ...state.steps.slice(0, state.currentIndex),
                    {
                        ...currentStep,
                        userInput: {
                            transform: action.transform,
                            value: '',
                            math: math,
                        },
                    },
                    ...state.steps.slice(state.currentIndex + 1)
                ],
            };
        case 'UNDO':
            return {
                ...state,
                currentIndex: Math.max(0, state.currentIndex - 1),
                activeIndex: Math.max(0, state.currentIndex - 1),
            };
        case 'REDO':
            return {
                ...state,
                currentIndex: Math.min(state.currentIndex + 1, state.steps.length - 1),
                activeIndex: Math.min(state.currentIndex + 1, state.steps.length - 1),
            };
        default:
            return state;
    }
};

const store = createStore(reducer);

export { store as default };
<commit_msg>serialize selections correclty<commit_after>import { createStore } from 'redux';

import Parser from './parser';
import Placeholder from './ast/placeholder';
import { add, sub, mul, div } from './operations';
import { traverseNode, deepEqual, findNode, compare } from './ast/node-utils';
import params from './params';
import Selection from './ui/selection';

const parser = new Parser();

const initialState = {
    steps: [{
        math: params.start ? parser.parse(params.start) : parser.parse('2x+5=10'),
    }],
    currentIndex: 0,
    activeIndex: 0,
    goal: params.end ? parser.parse(params.end) : parser.parse('x=5/2')
};

const reducer = (state = initialState, action) => {
    const currentStep = state.steps[state.currentIndex];
    const newMath = currentStep.userInput ? currentStep.userInput.math.clone() : currentStep.math.clone();
    const lastStep = state.steps[state.steps.length - 1];
    const previousSteps = state.steps.slice(0, state.currentIndex);
    let maxId = 0;

    switch (action.type) {
        case 'SELECT_STEP':
            return {
                ...state,
                activeIndex: action.index,
            };
        case 'SELECT_MATH':
            return {
                ...state,
                steps: [
                    ...state.steps.slice(0, state.currentIndex),
                    {
                        ...currentStep,
                        selections: action.selections,
                    },
                    ...state.steps.slice(state.currentIndex + 1)
                ],
            };
        case 'SIMPLE_OPERATION':
            // TODO: have two modes... when we're in insertion mode any keystroke get's appended to the current insertionText
            // TODO: we need to keep track of the operation we're using during the insertion mode so we can insert parens appropriately

            // TODO: reduce for tree traversal
            traverseNode(newMath, node => maxId = Math.max(maxId, node.id));

            if (currentStep.cursor) {
                traverseNode(newMath, node => {
                    if (node.type === 'Placeholder') {
                        node.text += action.operator;
                    }
                });

                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            math: newMath,
                            maxId: currentStep.maxId,
                            cursor: true,
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            }

            if (currentStep.math.root.type === 'Equation') {
                const op = {
                    '+': add,
                    '-': sub,
                    '*': mul,
                    '/': div
                }[action.operator];
                const placeholder = new Placeholder();
                newMath.root = op(newMath.root, placeholder);
            }

            return {
                ...state,
                currentIndex: state.currentIndex + 1,
                activeIndex: state.activeIndex + 1,
                steps: [
                    ...state.steps.slice(0, state.currentIndex + 1),
                    {
                        math: newMath,
                        maxId: maxId,
                        action: {
                            type: 'INSERT',
                            operation: action.operator,
                            value: null
                        },
                    },
                ],
            };
        case 'SHOW_CURSOR':
            return {
                ...state,
                steps: [
                    ...state.steps.slice(0, state.currentIndex),
                    {
                        ...currentStep,
                        cursor: true,
                    },
                    ...state.steps.slice(state.currentIndex + 1)
                ]
            };
        case 'INSERT_NUMBER':
            traverseNode(newMath, node => {
                if (node.type === 'Placeholder') {
                    node.text += action.number;
                }
            });

            if (currentStep.userInput) {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            userInput: {
                                ...currentStep.userInput,
                                math: newMath,
                                incorrect: false,
                            },
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            } else {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            math: newMath,
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            }
        case 'BACKSPACE':
            traverseNode(newMath, node => {
                if (node.type === 'Placeholder') {
                    if (node.text === '') {
                        // TODO: think about how to undo the operation
                    } else {
                        node.text = node.text.slice(0, node.text.length - 1);
                    }
                }
            });

            if (currentStep.userInput) {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            userInput: {
                                ...currentStep.userInput,
                                math: newMath,
                                incorrect: false,
                            },
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            } else {
                return {
                    ...state,
                    steps: [
                        ...state.steps.slice(0, state.currentIndex),
                        {
                            ...currentStep,
                            math: newMath,
                        },
                        ...state.steps.slice(state.currentIndex + 1)
                    ],
                };
            }
        case 'ACCEPT_STEP':
            let value = null;
            traverseNode(newMath, node => {
                if (node.type === 'Placeholder') {
                    // TODO: try/catch and provide feedback if math isn't valid
                    value = parser.parse(node.text).root;
                    node.parent.replace(node, value);
                }
            });

            if (currentStep.userInput) {
                const selections = currentStep.selections;
                const {transform} = currentStep.userInput;

                const newNewMath = currentStep.math.clone();

                const newSelections = selections.map(selection => {
                    const first = findNode(newNewMath, selection.first.id);
                    const last = findNode(newNewMath, selection.last.id);
                    return new Selection(first, last);
                });

                if (compare(newMath.root.left, newMath.root.right)) {
                    if (transform.canTransform(newSelections)) {
                        transform.doTransform(newSelections, newMath.root.right.clone());
                    }

                    return {
                        ...state,
                        steps: [
                            ...previousSteps,
                            {
                                selections: [],
                                math: currentStep.math.clone(),
                                action: {
                                    type: 'TRANSFORM',
                                    transform: transform,
                                    selections: selections,
                                },
                            },
                            {
                                math: newNewMath,
                                selections: [],
                                active: true,
                            },
                        ],
                        activeIndex: state.activeIndex + 1,
                        currentIndex: state.currentIndex + 1,
                    };
                } else {
                    return {
                        ...state,
                        steps: [
                            ...state.steps.slice(0, state.currentIndex),
                            {
                                ...currentStep,
                                userInput: {
                                    ...currentStep.userInput,
                                    incorrect: true,
                                },
                            },
                            ...state.steps.slice(state.currentIndex + 1)
                        ],
                    };
                }
            } else {
                return {
                    ...state,
                    steps: [
                        ...previousSteps.slice(0, previousSteps.length - 1),
                        {
                            ...previousSteps[previousSteps.length - 1],
                            action: {
                                ...lastStep.action,
                                value: value.clone(),
                                maxId: currentStep.maxId,
                            },
                        },
                        {
                            ...currentStep,
                            math: newMath,
                            cursor: null,
                            maxId: Infinity,
                        },
                    ],
                };
            }
        case 'ADD_STEP':
            return {
                ...state,
                steps: [
                    ...previousSteps,
                    {
                        math: currentStep.math.clone(),
                        action: {
                            type: 'TRANSFORM',
                            transform: action.transform,
                            selections: currentStep.selections,
                        },
                    },
                    {
                        math: action.math,
                    },
                ],
                activeIndex: state.activeIndex + 1,
                currentIndex: state.currentIndex + 1,
            };
        case 'CHECK_ANSWER':
            const finished = deepEqual(state.goal, currentStep.math);

            const jsonifyAction = function(action) {
                if (action) {
                    const json = { type: action.type };

                    if (json.type === 'TRANSFORM') {
                        json.transform = action.transform.label;
                        json.selections = action.selections.map(
                            selection => JSON.stringify(selection.toExpression()));
                    } else if (json.type === 'INSERT') {
                        json.operation = action.operation;
                        json.value = JSON.stringify(action.value);
                    }

                    return json;
                } else {
                    return null;
                }
            };

            if (finished) {
                $.ajax({
                    url: 'http://localhost:3000/api/steps',
                    method: 'post',
                    data: {
                        steps: state.steps.map(step => {
                            return {
                                math: JSON.stringify(step.math),
                                action: jsonifyAction(step.action)
                            }
                        })
                    }
                }).then(res => {
                    console.log(res);
                });
            }

            return {
                ...state,
                finished: finished,
            };
        case 'GET_USER_INPUT':
            const selection = action.selections[0];
            const math = parser.parse('a=b');
            const left = selection.toExpression();
            const right = new Placeholder();
            math.root.left = left;
            left.parent = math.root;
            math.root.right = right;
            right.parent = math.root;

            return {
                ...state,
                steps: [
                    ...state.steps.slice(0, state.currentIndex),
                    {
                        ...currentStep,
                        userInput: {
                            transform: action.transform,
                            value: '',
                            math: math,
                        },
                    },
                    ...state.steps.slice(state.currentIndex + 1)
                ],
            };
        case 'UNDO':
            return {
                ...state,
                currentIndex: Math.max(0, state.currentIndex - 1),
                activeIndex: Math.max(0, state.currentIndex - 1),
            };
        case 'REDO':
            return {
                ...state,
                currentIndex: Math.min(state.currentIndex + 1, state.steps.length - 1),
                activeIndex: Math.min(state.currentIndex + 1, state.steps.length - 1),
            };
        default:
            return state;
    }
};

const store = createStore(reducer);

export { store as default };
<|endoftext|>


<commit_before>import {createStore} from 'redux';
import reducer from './reducer';

export default function makeStore() {
    return createStore(reducer);
}<commit_msg>'documentation'<commit_after>import {createStore} from 'redux';
import reducer from './reducer';

/* Creates a Redux store with a reducer consisting of three actions
* */
export default function makeStore() {
    return createStore(reducer);
}<|endoftext|>


<commit_before>udefine(['eventmap', 'performance'], function(EventMap, performance) {

  var Timer = function(interval) {
    EventMap.mixin(this, Timer.prototype);

    var self = this;

    this.interval = interval || 1000;
    this.startTime = -1;

    this.active = false;
    this.paused = false;

    var oldTicks = 0;

    this.tick = function(currentTime) {
      if (!self.active || self.paused) {
        return;
      }

      if (interval <= 0) {
        return;
      }

      self.trigger('tick', currentTime);

      if ((currentTime - self.startTime - self.interval) > oldTicks) {
        oldTicks = currentTime;
        self.trigger('interval');
      }
    };
  };

  Timer.prototype.start = function() {
    this.active = true;
    this.paused = false;

    this.startTime = performance.now();

    this.trigger('start');
  };

  Timer.prototype.pause = function() {
    this.paused = true;

    this.trigger('pause');
  };

  Timer.prototype.unpause = function() {
    this.paused = false;

    this.trigger('unpause');
  };

  Timer.prototype.stop = function() {
    this.paused = false;
    this.active = false;

    this.trigger('stop');
  };

  return Timer;

});
<commit_msg>:fire: Update Timer to ES6<commit_after>'use strict';

import EventMap from 'eventmap';
import performance from 'performance';

class Timer extends EventMap {
  constructor(interval) {
    this.interval = interval || 1000;
    this.startTime = -1;

    this.active = false;
    this.paused = false;

    var oldTicks = 0;

    this.tick = (currentTime) => {
      if (!this.active || this.paused) {
        return;
      }

      if (interval <= 0) {
        return;
      }

      this.trigger('tick', currentTime);

      if ((currentTime - this.startTime - this.interval) > oldTicks) {
        oldTicks = currentTime;
        this.trigger('interval');
      }
    };
  }

  start() {
    this.active = true;
    this.paused = false;

    this.startTime = performance.now();

    this.trigger('start');
  }

  pause() {
    this.paused = true;

    this.trigger('pause');
  }

  unpause() {
    this.paused = false;

    this.trigger('unpause');
  }

  stop() {
    this.paused = false;
    this.active = false;

    this.trigger('stop');
  }
}

export default Timer;<|endoftext|>


<commit_before>/*
'use strict'

function Token() {
  this.expiresIn = 7 * 24 * 60 * 60 // a week
  this.secret = 'no_secret'
}

const O = new Token()
module.exports.O = O
  */<commit_msg>obsolete token.js removed<commit_after><|endoftext|>