<commit_before>'use strict';

var $ = require('jquery');
var App = require('../../app');
var Backbone = require('backbone');
var Mousetrap = require('mousetrap');
var NUSMods = require('../../nusmods');
var SelectView = require('./SelectView');
var BookmarksView = require('./BookmarksView');
var _ = require('underscore');
var analytics = require('../../analytics');
var attachFastClick = require('fastclick');
var corsify = require('../../cors/corsify');
var themePicker = require('../themes/themePicker');
var nusmoderator = require('nusmoderator');
require('bootstrap/alert');
require('qTip2');

module.exports = Backbone.View.extend({
  el: 'body',

  events: {
    'click a[href]:not([data-bypass])': 'hijackLinks',
    'mouseup a[href]:not([data-bypass])' : 'removeFocus',
  },

  hijackLinks: function (event) {
    /* jshint maxlen: 140 */
    // Ref: https://github.com/backbone-boilerplate/backbone-boilerplate/blob/85723839dbab6787d69eedcbbea05e1d59960eff/app/app.js#L52

    // Do not hijack if modifier key was pressed when the event fired.
    if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
      return;
    }

    // Get the absolute anchor href.
    var $link = $(event.currentTarget);
    var href = { prop: $link.prop('href'), attr: $link.attr('href') };
    // Get the absolute root.
    var root = location.protocol + '//' + location.host + '/';

    // Ensure the root is part of the anchor href, meaning it's relative.
    if (href.prop.slice(0, root.length) === root) {
      // Stop the default event to ensure the link will not cause a page
      // refresh.
      event.preventDefault();

      // `Backbone.history.navigate` is sufficient for all Routers and will
      // trigger the correct events. The Router's internal `navigate` method
      // calls this anyways.  The fragment is sliced from the root.
      this.navigateWithScrollTop(href.attr, true);
    }
  },

  removeFocus: function(event) {
    $(event.currentTarget).blur();
  },

  initialize: function () {
    $.ajaxSetup({
      cache: true
    });

    // [Override](http://craigsworks.com/projects/qtip2/tutorials/advanced/#override)
    // default tooltip settings.
    $.fn.qtip.defaults.position.my = 'bottom center';
    $.fn.qtip.defaults.position.at = 'top center';
    $.fn.qtip.defaults.position.viewport = true;
    $.fn.qtip.defaults.show.solo = true;
    $.fn.qtip.defaults.style.classes = 'qtip-bootstrap';

    NUSMods.getLastModified().then(function (lastModified) {
      $('#correct-as-at').text((new Date(lastModified)).toString().slice(0, 21));
    });

    App.selectRegion.show(new SelectView());

    $('.container').removeClass('hidden');

    Mousetrap.bind('/', function(ev) {
      analytics.track('Search', 'Keyboard', 'Main Search');
      $('#s2id_autogen2').focus();
      ev.preventDefault();
      return false;
    });

    var keyboardNavigationMappings = {
      t: '/timetable',
      m: '/modules',
      p: '/preferences',
      v: '/venues',
      f: '/venueavailability',
      n: '/news',
      a: '/apps',
      '?': '/help'
    };

    var that = this;

    _.each(keyboardNavigationMappings, function (value, key) {
      Mousetrap.bind(key, function () {
        analytics.track('Navigation', 'Keyboard', value.slice(1));
        that.navigateWithScrollTop(value, true);
      });
    });

    var modulePageRegex = /^\/modules\/[^\/]{6,10}/;

    var keyboardAnchorMappings = {
      c: '#cors',
      s: '#schedule',
      e: '#prerequisites',
      r: '#reviews'
    };

    _.each(keyboardAnchorMappings, function (value, key) {
      Mousetrap.bind(key, function () {
        var section = value.slice(1);
        analytics.track('Module ' + section, 'Visit - Keyboard', section);
        if (modulePageRegex.test(window.location.pathname)) {
          location.hash = value;
        }
      });
    });

    Mousetrap.bind(['x'], function () {
      var newMode = themePicker.toggleMode();
      analytics.track('event', 'Mode', 'Change mode using keyboard', newMode);
      if (modulePageRegex.test(window.location.pathname) && window.DISQUS) {
        window.DISQUS.reset({
          reload: true,
          config: function () {
            /* jshint camelcase: false */
            this.page.identifier = window.disqus_identifier;
            this.page.title = window.disqus_title;
            this.page.url = window.disqus_url;
          }
        });
      }
    });

    Mousetrap.bind(['left', 'right'], function (e) {
      var newTheme = themePicker.selectNextTheme(e.keyCode === 37 ? 'Left' : 'Right');
      analytics.track('Theme', 'Change theme using keyboard', newTheme, e.keyCode === 37 ? 1 : 0);
      return false;
    });

    $('.nm-bookmark-button').qtip({
      content: '

',
      hide: {
        fixed: true,
        delay: 300
      },
      events: {
        show: function() {
          analytics.track('Bookmarks', 'Display bookmark');
          App.request('getBookmarks', function (modules) {
            var modulesList = [];
            _.each(modules, function (module) {
              modulesList.push({'module': module});
            });
            var bookmarksCollection = new Backbone.Collection(modulesList);
            var bookmarksView = new BookmarksView({collection: bookmarksCollection});
            App.bookmarksRegion.show(bookmarksView);
          });
        }
      }
    });

    // For sidebar navigations
    $('.navbar-toggle').click(function (e) {
      e.preventDefault();
      $('body').toggleClass('js-toggled');
    });

    $(window).resize(function () {
      $('body').removeClass('js-toggled');
    });

    attachFastClick(document.body);

    (function () {
      $('.cors-round-text').html(corsify.determineRound(Date.now()));
      $('.cors-round-container').addClass('animated bounceInUp shown');
      // $('.js-nm-contest-alert').addClass('animated bounceInDown nm-force-show');
      var week = nusmoderator.academicCalendar.currentAcadWeek(new Date());
      var thisWeekText = 'AY20' + week.year + ', ' + week.sem + ', ';
      if (week.type !== 'Instructional'){ // hide 'Instructional'
        thisWeekText += week.type;
      }
      thisWeekText += ' Week';
      if (week.num > 0) { // do not show the week number if there is only one week, eg. recess
        thisWeekText += ' ' + week.num;
      }
      $('.js-nm-current-week-text').text(thisWeekText);
    })();
  },

  navigateWithScrollTop: function (location, trigger) {
    $('body').removeClass('js-toggled');
    Backbone.history.navigate(location, {trigger: trigger});
    // Hack: Scroll to top of page after navigation
    setTimeout(function () {
      window.scrollTo(0, 0);
    }, 0);
  }
});
<commit_msg>Deactivate CORS reminder for AY15/16 Sem 2<commit_after>'use strict';

var $ = require('jquery');
var App = require('../../app');
var Backbone = require('backbone');
var Mousetrap = require('mousetrap');
var NUSMods = require('../../nusmods');
var SelectView = require('./SelectView');
var BookmarksView = require('./BookmarksView');
var _ = require('underscore');
var analytics = require('../../analytics');
var attachFastClick = require('fastclick');
// var corsify = require('../../cors/corsify');
var themePicker = require('../themes/themePicker');
var nusmoderator = require('nusmoderator');
require('bootstrap/alert');
require('qTip2');

module.exports = Backbone.View.extend({
  el: 'body',

  events: {
    'click a[href]:not([data-bypass])': 'hijackLinks',
    'mouseup a[href]:not([data-bypass])' : 'removeFocus',
  },

  hijackLinks: function (event) {
    /* jshint maxlen: 140 */
    // Ref: https://github.com/backbone-boilerplate/backbone-boilerplate/blob/85723839dbab6787d69eedcbbea05e1d59960eff/app/app.js#L52

    // Do not hijack if modifier key was pressed when the event fired.
    if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
      return;
    }

    // Get the absolute anchor href.
    var $link = $(event.currentTarget);
    var href = { prop: $link.prop('href'), attr: $link.attr('href') };
    // Get the absolute root.
    var root = location.protocol + '//' + location.host + '/';

    // Ensure the root is part of the anchor href, meaning it's relative.
    if (href.prop.slice(0, root.length) === root) {
      // Stop the default event to ensure the link will not cause a page
      // refresh.
      event.preventDefault();

      // `Backbone.history.navigate` is sufficient for all Routers and will
      // trigger the correct events. The Router's internal `navigate` method
      // calls this anyways.  The fragment is sliced from the root.
      this.navigateWithScrollTop(href.attr, true);
    }
  },

  removeFocus: function(event) {
    $(event.currentTarget).blur();
  },

  initialize: function () {
    $.ajaxSetup({
      cache: true
    });

    // [Override](http://craigsworks.com/projects/qtip2/tutorials/advanced/#override)
    // default tooltip settings.
    $.fn.qtip.defaults.position.my = 'bottom center';
    $.fn.qtip.defaults.position.at = 'top center';
    $.fn.qtip.defaults.position.viewport = true;
    $.fn.qtip.defaults.show.solo = true;
    $.fn.qtip.defaults.style.classes = 'qtip-bootstrap';

    NUSMods.getLastModified().then(function (lastModified) {
      $('#correct-as-at').text((new Date(lastModified)).toString().slice(0, 21));
    });

    App.selectRegion.show(new SelectView());

    $('.container').removeClass('hidden');

    Mousetrap.bind('/', function(ev) {
      analytics.track('Search', 'Keyboard', 'Main Search');
      $('#s2id_autogen2').focus();
      ev.preventDefault();
      return false;
    });

    var keyboardNavigationMappings = {
      t: '/timetable',
      m: '/modules',
      p: '/preferences',
      v: '/venues',
      f: '/venueavailability',
      n: '/news',
      a: '/apps',
      '?': '/help'
    };

    var that = this;

    _.each(keyboardNavigationMappings, function (value, key) {
      Mousetrap.bind(key, function () {
        analytics.track('Navigation', 'Keyboard', value.slice(1));
        that.navigateWithScrollTop(value, true);
      });
    });

    var modulePageRegex = /^\/modules\/[^\/]{6,10}/;

    var keyboardAnchorMappings = {
      c: '#cors',
      s: '#schedule',
      e: '#prerequisites',
      r: '#reviews'
    };

    _.each(keyboardAnchorMappings, function (value, key) {
      Mousetrap.bind(key, function () {
        var section = value.slice(1);
        analytics.track('Module ' + section, 'Visit - Keyboard', section);
        if (modulePageRegex.test(window.location.pathname)) {
          location.hash = value;
        }
      });
    });

    Mousetrap.bind(['x'], function () {
      var newMode = themePicker.toggleMode();
      analytics.track('event', 'Mode', 'Change mode using keyboard', newMode);
      if (modulePageRegex.test(window.location.pathname) && window.DISQUS) {
        window.DISQUS.reset({
          reload: true,
          config: function () {
            /* jshint camelcase: false */
            this.page.identifier = window.disqus_identifier;
            this.page.title = window.disqus_title;
            this.page.url = window.disqus_url;
          }
        });
      }
    });

    Mousetrap.bind(['left', 'right'], function (e) {
      var newTheme = themePicker.selectNextTheme(e.keyCode === 37 ? 'Left' : 'Right');
      analytics.track('Theme', 'Change theme using keyboard', newTheme, e.keyCode === 37 ? 1 : 0);
      return false;
    });

    $('.nm-bookmark-button').qtip({
      content: '

',
      hide: {
        fixed: true,
        delay: 300
      },
      events: {
        show: function() {
          analytics.track('Bookmarks', 'Display bookmark');
          App.request('getBookmarks', function (modules) {
            var modulesList = [];
            _.each(modules, function (module) {
              modulesList.push({'module': module});
            });
            var bookmarksCollection = new Backbone.Collection(modulesList);
            var bookmarksView = new BookmarksView({collection: bookmarksCollection});
            App.bookmarksRegion.show(bookmarksView);
          });
        }
      }
    });

    // For sidebar navigations
    $('.navbar-toggle').click(function (e) {
      e.preventDefault();
      $('body').toggleClass('js-toggled');
    });

    $(window).resize(function () {
      $('body').removeClass('js-toggled');
    });

    attachFastClick(document.body);

    (function () {
      // $('.cors-round-text').html(corsify.determineRound(Date.now()));
      // $('.cors-round-container').addClass('animated bounceInUp shown');
      // $('.js-nm-contest-alert').addClass('animated bounceInDown nm-force-show');
      var week = nusmoderator.academicCalendar.currentAcadWeek(new Date());
      var thisWeekText = 'AY20' + week.year + ', ' + week.sem + ', ';
      if (week.type !== 'Instructional'){ // hide 'Instructional'
        thisWeekText += week.type;
      }
      thisWeekText += ' Week';
      if (week.num > 0) { // do not show the week number if there is only one week, eg. recess
        thisWeekText += ' ' + week.num;
      }
      $('.js-nm-current-week-text').text(thisWeekText);
    })();
  },

  navigateWithScrollTop: function (location, trigger) {
    $('body').removeClass('js-toggled');
    Backbone.history.navigate(location, {trigger: trigger});
    // Hack: Scroll to top of page after navigation
    setTimeout(function () {
      window.scrollTo(0, 0);
    }, 0);
  }
});
<|endoftext|>


<commit_before>import m from 'mithril';
import ClipboardJS from 'clipboard';

// The area of the game UI consisting of game UI controls and status messages
class DashboardComponent {

  oninit({ attrs: { game, session } }) {
    this.game = game;
    this.session = session;
  }

  // Prepare game players by creating new players (if necessary) and deciding
  // which player has the starting move
  setPlayers(gameType) {
    if (this.game.players.length > 0) {
      // Reset new games before choosing number of players (no need to reset
      // the very first game)
      this.game.resetGame();
    }
    this.game.setPlayers(gameType);
  }

  startGame(newStartingPlayer) {
    this.game.startGame({
      startingPlayer: newStartingPlayer
    });
  }

  endGame(roomCode) {
    if (roomCode) {
      // The local player ID and room code will be automatically passed by the
      // session.emit() function
      this.session.emit('end-game');
    } else {
      this.game.endGame();
    }
  }

  createNewPlayer() {
    this.session.status = 'newPlayer';
  }

  setNewPlayerName(inputEvent) {
    this.newPlayerName = inputEvent.target.value;
    inputEvent.redraw = false;
  }

  submitNewPlayer(submitEvent, roomCode) {
    submitEvent.preventDefault();
    if (roomCode) {
      this.addNewPlayerToGame(roomCode);
    } else {
      this.startOnlineGame();
    }
  }

  addNewPlayerToGame(roomCode) {
    this.session.status = 'connecting';
    let submittedPlayer = { name: this.newPlayerName, color: 'blue' };
    this.session.emit('add-player', { roomCode, player: submittedPlayer }, ({ game, localPlayer }) => {
      this.game.restoreFromServer({ game, localPlayer });
      m.redraw();
    });
  }

  startOnlineGame() {
    this.session.connect();
    // Construct a placeholder player with the name we entered and the default
    // first player color
    let submittedPlayer = { name: this.newPlayerName, color: 'red' };
    // Request a new room and retrieve the room code returned from the server
    this.session.emit('open-room', { player: submittedPlayer }, ({ roomCode, game, localPlayer }) => {
      this.game.restoreFromServer({ game, localPlayer });
      console.log('new room', roomCode);
      m.route.set(`/room/${roomCode}`);
    });
  }

  requestNewOnlineGame() {
    this.session.emit('request-new-game', { winner: this.game.winner }, ({ localPlayer }) => {
      console.log(this.session.status);
      this.game.requestingPlayer = localPlayer;
      m.redraw();
    });
  }

  configureCopyControl({ dom }) {
    this.shareLinkCopier = new ClipboardJS(dom);
  }

  view({ attrs: { roomCode } }) {
    return m('div#game-dashboard', [
      m('p#game-message',
        // If the current player needs to enter a name
        this.session.status === 'newPlayer' ?
          'Enter your player name:' :
        this.session.status === 'waitingForPlayers' ?
          [
            'Waiting for other player...',
            m('div#share-controls', [
              m('input[type=text]#share-link', {
                value: window.location.href,
                onclick: ({ target }) => target.select()
              }),
              m('button#copy-share-link', {
                'data-clipboard-text': window.location.href,
                oncreate: ({ dom }) => this.configureCopyControl({ dom })
              }, 'Copy')
            ])
          ] :
        this.session.status === 'connecting' ?
          'Connecting to server...' :
        this.session.status === 'roomNotFound' ?
          'This room does not exist.' :
        // If the local player has requested a new game
        this.session.status === 'requestingNewGame' ?
          `Asking ${this.game.getOtherPlayer(this.game.requestingPlayer).name} to play again...` :
        // Inform the other player if a player requests a new game
        this.session.status === 'newGameRequested' ?
          `${this.game.requestingPlayer.name} asks to play again.` :
        // If user has not started any game yet
        this.game.players.length === 0 ?
          'Welcome! How many players?' :
        // If a game is in progress
        this.game.currentPlayer ?
          `${this.game.currentPlayer.name}, your turn!` :
        // If a player wins the game
        this.game.winner ?
          `${this.game.winner.name} wins! Play again?` :
        // If the grid is completely full
        this.game.grid.checkIfFull() ?
          'We\'ll call it a draw! Play again?' :
        // If the user just chose a number of players for the game to be started
        !this.session.socket && this.game.type !== null ?
          'Which player should start first?' :
        // If either player ends the game early
        roomCode && this.game.requestingPlayer ?
          `${this.game.requestingPlayer.name} has ended the game.` :
        // Otherwise, if game was ended manually by the user
        'Game ended. Play again?'
      ),
      // If game is in progress, allow user to end game at any time
      this.game.inProgress ? [
        m('button', { onclick: () => this.endGame(roomCode) }, 'End Game')
      ] :
      // If an online game is not in progress (i.e. it was ended early, or there
      // is a winner/tie), allow the user to play again
      this.session.socket && this.game.players.length === 2 ? m('button', {
        onclick: () => this.requestNewOnlineGame(),
        disabled: this.session.status === 'requestingNewGame'
      }, this.session.status === 'newGameRequested' ? 'Sure!' : this.session.status === 'requestingNewGame' ? 'Pending' : 'Play Again') :
      this.session.status === 'newPlayer' ? [
        m('form', {
          onsubmit: (submitEvent) => this.submitNewPlayer(submitEvent, roomCode)
        }, [
          m('input[type=text][autocomplete=off]#new-player-name', {
            name: 'new-player-name',
            autofocus: true,
            oninput: (inputEvent) => this.setNewPlayerName(inputEvent)
          }),
          m('button[type=submit]', roomCode ? 'Join Game' : 'Start Game')
        ])
      ] :
      !this.session.socket ? [
        // If number of players has been chosen, ask user to choose starting player
        this.game.type !== null ?
          this.game.players.map((player) => {
            return m('button', {
              onclick: () => this.startGame(player)
            }, player.name);
          }) :
          // Select a number of human players
          [
            m('button', {
              onclick: () => this.setPlayers({ gameType: '1P' })
            }, '1 Player'),
            m('button', {
              onclick: () => this.setPlayers({ gameType: '2P' })
            }, '2 Players'),
            m('button', {
              onclick: () => this.createNewPlayer()
            }, 'Online')
          ]
        ] : null
    ]);
  }

}

export default DashboardComponent;
<commit_msg>Require player names to be non-empty<commit_after>import m from 'mithril';
import ClipboardJS from 'clipboard';

// The area of the game UI consisting of game UI controls and status messages
class DashboardComponent {

  oninit({ attrs: { game, session } }) {
    this.game = game;
    this.session = session;
  }

  // Prepare game players by creating new players (if necessary) and deciding
  // which player has the starting move
  setPlayers(gameType) {
    if (this.game.players.length > 0) {
      // Reset new games before choosing number of players (no need to reset
      // the very first game)
      this.game.resetGame();
    }
    this.game.setPlayers(gameType);
  }

  startGame(newStartingPlayer) {
    this.game.startGame({
      startingPlayer: newStartingPlayer
    });
  }

  endGame(roomCode) {
    if (roomCode) {
      // The local player ID and room code will be automatically passed by the
      // session.emit() function
      this.session.emit('end-game');
    } else {
      this.game.endGame();
    }
  }

  createNewPlayer() {
    this.session.status = 'newPlayer';
  }

  setNewPlayerName(inputEvent) {
    this.newPlayerName = inputEvent.target.value;
    inputEvent.redraw = false;
  }

  submitNewPlayer(submitEvent, roomCode) {
    submitEvent.preventDefault();
    if (roomCode) {
      this.addNewPlayerToGame(roomCode);
    } else {
      this.startOnlineGame();
    }
  }

  addNewPlayerToGame(roomCode) {
    this.session.status = 'connecting';
    let submittedPlayer = { name: this.newPlayerName, color: 'blue' };
    this.session.emit('add-player', { roomCode, player: submittedPlayer }, ({ game, localPlayer }) => {
      this.game.restoreFromServer({ game, localPlayer });
      m.redraw();
    });
  }

  startOnlineGame() {
    this.session.connect();
    // Construct a placeholder player with the name we entered and the default
    // first player color
    let submittedPlayer = { name: this.newPlayerName, color: 'red' };
    // Request a new room and retrieve the room code returned from the server
    this.session.emit('open-room', { player: submittedPlayer }, ({ roomCode, game, localPlayer }) => {
      this.game.restoreFromServer({ game, localPlayer });
      console.log('new room', roomCode);
      m.route.set(`/room/${roomCode}`);
    });
  }

  requestNewOnlineGame() {
    this.session.emit('request-new-game', { winner: this.game.winner }, ({ localPlayer }) => {
      console.log(this.session.status);
      this.game.requestingPlayer = localPlayer;
      m.redraw();
    });
  }

  configureCopyControl({ dom }) {
    this.shareLinkCopier = new ClipboardJS(dom);
  }

  view({ attrs: { roomCode } }) {
    return m('div#game-dashboard', [
      m('p#game-message',
        // If the current player needs to enter a name
        this.session.status === 'newPlayer' ?
          'Enter your player name:' :
        this.session.status === 'waitingForPlayers' ?
          [
            'Waiting for other player...',
            m('div#share-controls', [
              m('input[type=text]#share-link', {
                value: window.location.href,
                onclick: ({ target }) => target.select()
              }),
              m('button#copy-share-link', {
                'data-clipboard-text': window.location.href,
                oncreate: ({ dom }) => this.configureCopyControl({ dom })
              }, 'Copy')
            ])
          ] :
        this.session.status === 'connecting' ?
          'Connecting to server...' :
        this.session.status === 'roomNotFound' ?
          'This room does not exist.' :
        // If the local player has requested a new game
        this.session.status === 'requestingNewGame' ?
          `Asking ${this.game.getOtherPlayer(this.game.requestingPlayer).name} to play again...` :
        // Inform the other player if a player requests a new game
        this.session.status === 'newGameRequested' ?
          `${this.game.requestingPlayer.name} asks to play again.` :
        // If user has not started any game yet
        this.game.players.length === 0 ?
          'Welcome! How many players?' :
        // If a game is in progress
        this.game.currentPlayer ?
          `${this.game.currentPlayer.name}, your turn!` :
        // If a player wins the game
        this.game.winner ?
          `${this.game.winner.name} wins! Play again?` :
        // If the grid is completely full
        this.game.grid.checkIfFull() ?
          'We\'ll call it a draw! Play again?' :
        // If the user just chose a number of players for the game to be started
        !this.session.socket && this.game.type !== null ?
          'Which player should start first?' :
        // If either player ends the game early
        roomCode && this.game.requestingPlayer ?
          `${this.game.requestingPlayer.name} has ended the game.` :
        // Otherwise, if game was ended manually by the user
        'Game ended. Play again?'
      ),
      // If game is in progress, allow user to end game at any time
      this.game.inProgress ? [
        m('button', { onclick: () => this.endGame(roomCode) }, 'End Game')
      ] :
      // If an online game is not in progress (i.e. it was ended early, or there
      // is a winner/tie), allow the user to play again
      this.session.socket && this.game.players.length === 2 ? m('button', {
        onclick: () => this.requestNewOnlineGame(),
        disabled: this.session.status === 'requestingNewGame'
      }, this.session.status === 'newGameRequested' ? 'Sure!' : this.session.status === 'requestingNewGame' ? 'Pending' : 'Play Again') :
      this.session.status === 'newPlayer' ? [
        m('form', {
          onsubmit: (submitEvent) => this.submitNewPlayer(submitEvent, roomCode)
        }, [
          m('input[type=text][autocomplete=off]#new-player-name', {
            name: 'new-player-name',
            autofocus: true,
            required: true,
            pattern: '\\S+',
            oninput: (inputEvent) => this.setNewPlayerName(inputEvent)
          }),
          m('button[type=submit]', roomCode ? 'Join Game' : 'Start Game')
        ])
      ] :
      !this.session.socket ? [
        // If number of players has been chosen, ask user to choose starting player
        this.game.type !== null ?
          this.game.players.map((player) => {
            return m('button', {
              onclick: () => this.startGame(player)
            }, player.name);
          }) :
          // Select a number of human players
          [
            m('button', {
              onclick: () => this.setPlayers({ gameType: '1P' })
            }, '1 Player'),
            m('button', {
              onclick: () => this.setPlayers({ gameType: '2P' })
            }, '2 Players'),
            m('button', {
              onclick: () => this.createNewPlayer()
            }, 'Online')
          ]
        ] : null
    ]);
  }

}

export default DashboardComponent;
<|endoftext|>


<commit_before>'use strict';

angular.module('snotes30App')
  .controller('DocumentCtrl', function ($scope, $rootScope, $routeParams, $sce, document, docname, DocumentService) {
    $scope.doc = document;

    function updateDocument() {
      return DocumentService.getByName(docname).then(function (doc) {
        $scope.doc = doc;
      });
    }

    DocumentService.getEditor($scope.doc.editor).then(function (editor) {
      $scope.editor = editor;
      $scope.docurl = $sce.trustAsResourceUrl(editor.url + '/' + $scope.doc.urlname);
    });

    $scope.isShownoter = function () {
      return $scope.doc.meta.shownoters.indexOf($rootScope.currentUser.username) !== -1;
    };

    $scope.addShownoter = function () {
      $scope.doc.customPOST({}, $scope.doc.name + '/contributed').then(updateDocument);
    };

    $scope.delShownoter = function () {
      $scope.doc.customDELETE($scope.doc.name + '/contributed').then(updateDocument);
    };

    $scope.addPodcaster = function (podcaster) {
      $scope.doc.customPOST($scope.newpodcaster, $scope.doc.name + '/podcasters').then(function () { $scope.newpodcaster = null; }).then(updateDocument);
    };

    $scope.sendChatMsg = function () {
      $scope.doc.customPOST($scope.chatmsg, $scope.doc.name + '/chat').then(function () { $scope.chatmsg = null; }).then(getChatMsgs);
    };

    function getChatMsgs() {
      $scope.doc.customGET($scope.doc.name + '/chat').then(function (msgs) {
        $scope.chatmessages = msgs;
      });
    }

    getChatMsgs();
});
<commit_msg>doc-view: load new chat messages<commit_after>'use strict';

angular.module('snotes30App')
  .controller('DocumentCtrl', function ($scope, $rootScope, $routeParams, $sce, $interval, document, docname, DocumentService) {
    $scope.doc = document;

    function updateDocument() {
      return DocumentService.getByName(docname).then(function (doc) {
        $scope.doc = doc;
      });
    }

    DocumentService.getEditor($scope.doc.editor).then(function (editor) {
      $scope.editor = editor;
      $scope.docurl = $sce.trustAsResourceUrl(editor.url + '/' + $scope.doc.urlname);
    });

    $scope.isShownoter = function () {
      return $scope.doc.meta.shownoters.indexOf($rootScope.currentUser.username) !== -1;
    };

    $scope.addShownoter = function () {
      $scope.doc.customPOST({}, $scope.doc.name + '/contributed').then(updateDocument);
    };

    $scope.delShownoter = function () {
      $scope.doc.customDELETE($scope.doc.name + '/contributed').then(updateDocument);
    };

    $scope.addPodcaster = function (podcaster) {
      $scope.doc.customPOST($scope.newpodcaster, $scope.doc.name + '/podcasters').then(function () { $scope.newpodcaster = null; }).then(updateDocument);
    };

    $scope.sendChatMsg = function () {
      $scope.doc.customPOST($scope.chatmsg, $scope.doc.name + '/chat').then(function () { $scope.chatmsg = null; }).then(getChatMsgs);
    };

    function getChatMsgs(since) {
      var params = {};
      if(since) {
        params['since'] = since;
      }
      $scope.doc.customGET($scope.doc.name + '/chat', params).then(function (msgs) {
        if(since) {
          $scope.chatmessages = $scope.chatmessages.concat(msgs);
        } else {
          $scope.chatmessages = msgs;
        }
      });
    }

    getChatMsgs();

    var chatUpdateInt = $interval(function () {
      var since = undefined;
      if($scope.chatmessages && $scope.chatmessages.length != 0) {
        since = $scope.chatmessages[$scope.chatmessages.length - 1].order;
      }
      getChatMsgs(since)
    }, 500);

    $scope.$on('$destroy', function () {
      $interval.cancel(chatUpdateInt);
    });
});
<|endoftext|>


<commit_before>import Ember from 'ember';
import config from '../config/environment';
import { task, timeout } from 'ember-concurrency';

const EMBER_VERSIONS = ['2.7.0, 2.6.2', '2.5.1', '2.4.5', '2.3.2', '2.2.2', '2.1.2', '2.0.3', '1.13.13', '1.12.2'];
const EMBER_DATA_VERSIONS = ['2.7.0, 2.6.2', '2.5.5', '2.4.3', '2.3.3', '2.2.1', '2.1.0', '2.0.1', '1.13.15'];

const VERSION_REGEX = /^\d+.\d+.\d+(-beta\.\d+)?$/;

const CDN_MAP = {
  'ember': {
    library: 'ember.js',
    fileName: 'ember.debug.js'
  },

  'ember-template-compiler': {
    library: 'ember.js',
    fileName: 'ember-template-compiler.js'
  },

  'ember-data': {
    library: 'ember-data.js',
    fileName: 'ember-data.js'
  }
};

const CHANNEL_FILENAME_MAP = {
  'ember': 'ember.debug.js',
  'ember-template-compiler': 'ember-template-compiler.js',
  'ember-data': 'ember-data.js'
};

const CHANNELS = ['canary', 'beta', 'release'];

const POLL_INTERVAL = 10000;

const { computed, RSVP } = Ember;

export default Ember.Service.extend({
  resolveDependencies: function(dependencies) {
    Object.keys(dependencies).forEach((name) => {
      var value = dependencies[name];

      dependencies[name] = this.resolveDependency(name, value);
    });
  },

  resolveAddons: function(addons, dependencies, emberVersion) {
    const taskInstance = this.get('resolveAddonsTask').perform(addons, dependencies, emberVersion);
    return taskInstance.then(() => {
      return RSVP.resolve(taskInstance.value);
    });
  },

  resolveAddonsTask: task(function *(addons, dependencies, emberVersion) {
    let done = false;
    while (!done) {
      let addonPromises = {};
      let addonNames = Object.keys(addons);
      for (let i = 0; i < addonNames.length; ++i) {
        const name = addonNames[i];
        const value = addons[name];
        addonPromises[name] = this.resolveAddon(name, value, emberVersion);
      }
      let hash = yield RSVP.hash(addonPromises);
      let allAddonsLoaded = true;
      for (let j = 0; j < addonNames.length; ++j) {
        const name = addonNames[j];
        const addon = hash[name];
        if (addon.status === 'build_success') {
          dependencies[name] = addon.addon_js;
          dependencies[name+'_css'] = addon.addon_css;
          console.log(`Addon ${name} is loaded...`);
          delete addons[name];
        } else if (addon.status === 'building') {
          console.log(`Addon ${name} is still building...`);
          allAddonsLoaded = false;
        } else if (addon.status === 'build_error') {
          console.error(`Addon ${name} encountered a build error:`);
          console.error(addon.error_log);
          allAddonsLoaded = false;
          throw addon;
        }
      }
      if (allAddonsLoaded) {
        done = true;
      } else {
        yield timeout(POLL_INTERVAL);
      }
    }
    return dependencies;
  }),

  resolveAddon(name, value, emberVersion) {
    return new RSVP.Promise(function(resolve) {
      const url = `${config.addonUrl}?ember_version=${emberVersion}&addon=${name}&addon_version=${value}`;
      resolve(Ember.$.getJSON(url).then(data => RSVP.resolve(data)));
    });
  },

  resolveDependency: function(name, value) {
    switch (name) {
      case 'ember':
      case 'ember-template-compiler':
      case 'ember-data':
        return this.resolveEmberDependency(name, value);

      default:
        return value;
    }
  },

  resolveEmberDependency: function(name, value) {
    if (VERSION_REGEX.test(value)) {
      return this.cdnURL(name, value);
    }

    if (CHANNELS.indexOf(value) !== -1) {
      return this.channelURL(name, value);
    }

    return value;
  },

  channelURL: function(name, channel) {
    var fileName = CHANNEL_FILENAME_MAP[name];

    return `//s3.amazonaws.com/builds.emberjs.com/${channel}/${fileName}`;
  },

  cdnURL: function(name, version) {
    var { library, fileName } = CDN_MAP[name];

    return `//cdnjs.cloudflare.com/ajax/libs/${library}/${version}/${fileName}`;
  },

  emberVersions: computed(function() {
    return [...CHANNELS, ...EMBER_VERSIONS];
  }),

  emberDataVersions: computed(function() {
    return [...CHANNELS, ...EMBER_DATA_VERSIONS];
  })
});
<commit_msg>Fix ember and ember-data versions<commit_after>import Ember from 'ember';
import config from '../config/environment';
import { task, timeout } from 'ember-concurrency';

const EMBER_VERSIONS = ['2.7.0', '2.6.2', '2.5.1', '2.4.5', '2.3.2', '2.2.2', '2.1.2', '2.0.3', '1.13.13', '1.12.2'];
const EMBER_DATA_VERSIONS = ['2.7.0', '2.6.2', '2.5.5', '2.4.3', '2.3.3', '2.2.1', '2.1.0', '2.0.1', '1.13.15'];

const VERSION_REGEX = /^\d+.\d+.\d+(-beta\.\d+)?$/;

const CDN_MAP = {
  'ember': {
    library: 'ember.js',
    fileName: 'ember.debug.js'
  },

  'ember-template-compiler': {
    library: 'ember.js',
    fileName: 'ember-template-compiler.js'
  },

  'ember-data': {
    library: 'ember-data.js',
    fileName: 'ember-data.js'
  }
};

const CHANNEL_FILENAME_MAP = {
  'ember': 'ember.debug.js',
  'ember-template-compiler': 'ember-template-compiler.js',
  'ember-data': 'ember-data.js'
};

const CHANNELS = ['canary', 'beta', 'release'];

const POLL_INTERVAL = 10000;

const { computed, RSVP } = Ember;

export default Ember.Service.extend({
  resolveDependencies: function(dependencies) {
    Object.keys(dependencies).forEach((name) => {
      var value = dependencies[name];

      dependencies[name] = this.resolveDependency(name, value);
    });
  },

  resolveAddons: function(addons, dependencies, emberVersion) {
    const taskInstance = this.get('resolveAddonsTask').perform(addons, dependencies, emberVersion);
    return taskInstance.then(() => {
      return RSVP.resolve(taskInstance.value);
    });
  },

  resolveAddonsTask: task(function *(addons, dependencies, emberVersion) {
    let done = false;
    while (!done) {
      let addonPromises = {};
      let addonNames = Object.keys(addons);
      for (let i = 0; i < addonNames.length; ++i) {
        const name = addonNames[i];
        const value = addons[name];
        addonPromises[name] = this.resolveAddon(name, value, emberVersion);
      }
      let hash = yield RSVP.hash(addonPromises);
      let allAddonsLoaded = true;
      for (let j = 0; j < addonNames.length; ++j) {
        const name = addonNames[j];
        const addon = hash[name];
        if (addon.status === 'build_success') {
          dependencies[name] = addon.addon_js;
          dependencies[name+'_css'] = addon.addon_css;
          console.log(`Addon ${name} is loaded...`);
          delete addons[name];
        } else if (addon.status === 'building') {
          console.log(`Addon ${name} is still building...`);
          allAddonsLoaded = false;
        } else if (addon.status === 'build_error') {
          console.error(`Addon ${name} encountered a build error:`);
          console.error(addon.error_log);
          allAddonsLoaded = false;
          throw addon;
        }
      }
      if (allAddonsLoaded) {
        done = true;
      } else {
        yield timeout(POLL_INTERVAL);
      }
    }
    return dependencies;
  }),

  resolveAddon(name, value, emberVersion) {
    return new RSVP.Promise(function(resolve) {
      const url = `${config.addonUrl}?ember_version=${emberVersion}&addon=${name}&addon_version=${value}`;
      resolve(Ember.$.getJSON(url).then(data => RSVP.resolve(data)));
    });
  },

  resolveDependency: function(name, value) {
    switch (name) {
      case 'ember':
      case 'ember-template-compiler':
      case 'ember-data':
        return this.resolveEmberDependency(name, value);

      default:
        return value;
    }
  },

  resolveEmberDependency: function(name, value) {
    if (VERSION_REGEX.test(value)) {
      return this.cdnURL(name, value);
    }

    if (CHANNELS.indexOf(value) !== -1) {
      return this.channelURL(name, value);
    }

    return value;
  },

  channelURL: function(name, channel) {
    var fileName = CHANNEL_FILENAME_MAP[name];

    return `//s3.amazonaws.com/builds.emberjs.com/${channel}/${fileName}`;
  },

  cdnURL: function(name, version) {
    var { library, fileName } = CDN_MAP[name];

    return `//cdnjs.cloudflare.com/ajax/libs/${library}/${version}/${fileName}`;
  },

  emberVersions: computed(function() {
    return [...CHANNELS, ...EMBER_VERSIONS];
  }),

  emberDataVersions: computed(function() {
    return [...CHANNELS, ...EMBER_DATA_VERSIONS];
  })
});
<|endoftext|>


<commit_before>import React from 'react';
import "./NavBar.css"

class NavBar extends React.Component {
    render() {
        return (

Features

About

Contact

        );
    }
}

export default NavBar;<commit_msg>leave no file behind<commit_after>import React from 'react';
import "./NavBar.css"

class NavBar extends React.Component {
    render() {
        return (

Features

About

Contact

        );
    }
}

export default NavBar;<|endoftext|>


<commit_before>/**
 * Module Dependencies
 */

var mongoose = require('mongoose')
  , Schema   = mongoose.Schema
  , JobReviewSchema = require('./schema.jobReview');

var ObjectIdBase64Conv = require('./util').ObjectIdBase64Conv
  , objectIdToBase64 = ObjectIdBase64Conv.objectIdToBase64
  , base64ToObjectId = ObjectIdBase64Conv.base64ToObjectId;

/**
 * Define job posting schema
 */

var jobPostingSchema = new Schema({
    specifics: {
        jobTitle:     { type: String, required: true }
      , description:  { type: String, required: true }
      , requirements: { type: String, required: true }
      , salary:       { type: String, required: true }
      , application:  { type: String, required: true }
      , companyName:  { type: String, required: true }
      , department:   { type: String }
      , jobType:      { type: String }
    }
  , location: {
        city:         { type: String, required: true }
      , state:        { type: String, required: true }
    }
  , date: {
        postedOn:     { type: String, required: true }
      , endsOn:       { type: String, required: true }
    }
  , media: {
        image:        { type: String }
      , video:        { type: String }
    }
  , meta: {
        id:           { type: String }
  }
  , tags:             [ String ]
  , reviews:          [JobReviewSchema]
  , poster: {type: Schema.Type.ObjectId, ref: 'Employer'}
});

var textSearchIndexFields = {
    "specifics.jobTitle": "text",
    "specifics.description": "text",
    "specifics.requirements": "text",
    "specifics.companyName": "text",
    "specifics.jobType": "text",
    "specifics.location": "text",
    "tags": "text"
},
    textSearchIndexOptions = {
    "name": "text_search_index",
    "weights": {
        "specifics.jobTitle": 4,
        "specifics.description": 1,
        "specifics.requirements": 1,
        "specifics.companyName": 5,
        "specifics.jobType": 4,
        "specifics.location": 4,
        "tags": 6
    }
};

jobPostingSchema.index(textSearchIndexFields, textSearchIndexOptions);

/**
 * Search for job postings based on a keyword. Keyword
 * could be contained in job description OR job title
 * @param keyword {String}
 * @param cb {Function} callback
 * callback(err, [post1, post2...])
 */

jobPostingSchema.static('findByKeyword', function jobSearch(keyword, cb, options){
    var jobPosting = this;

    var limit = 100;

    // Sanity checking
    if (!keyword)
        return cb(new Error("Keyword was not provided"));
    if (typeof keyword !== "string")
        return cb(new Error("Keyword was not a string"));

    if(options) {
        if(options.limit) {
            limit = options.limit;
        }
    }

    jobPosting.find(
        {$text: {$search: keyword }},
        {score : {$meta: "textScore"}}
     )
     .sort({score: {$meta: "textScore"}
     })
     .limit(limit)
     .populate('reviews')
     .exec(cb);
});

jobPostingSchema.static('findByUrlId', function jobSearchUrlId(b64Id, cb) {
    var JobPosting = this,
        _id = null;

    try {
        _id = base64ToObjectId(b64Id);
        return JobPosting.findById(_id, cb);
    } catch(err) {
        err.status = 400;
        cb(err);
    }
});

jobPostingSchema.methods.getReviewByUrlId = function reviewSearchUrlId(reviewB64Id) {
    var jobPosting = this,
        review_id = null;

    try {
        review_id = base64ObjectId(reviewB64Id);
        return jobPosting.reviews.id(review_id);
    } catch(err) {
        return null;
    }
};

jobPostingSchema.pre('save', function beforeSavingJobPost(next) {
  var jobPosting = this;
    if(jobPosting.isNew) {
        jobPosting.meta.id = objectIdToBase64(jobPosting._id);
    }
    next();
});

/**
 * Export schema
 */

exports = module.exports = jobPostingSchema;
<commit_msg>fixed typo for ObjectId Type<commit_after>/**
 * Module Dependencies
 */

var mongoose = require('mongoose')
  , Schema   = mongoose.Schema
  , JobReviewSchema = require('./schema.jobReview');

var ObjectIdBase64Conv = require('./util').ObjectIdBase64Conv
  , objectIdToBase64 = ObjectIdBase64Conv.objectIdToBase64
  , base64ToObjectId = ObjectIdBase64Conv.base64ToObjectId;

/**
 * Define job posting schema
 */

var jobPostingSchema = new Schema({
    specifics: {
        jobTitle:     { type: String, required: true }
      , description:  { type: String, required: true }
      , requirements: { type: String, required: true }
      , salary:       { type: String, required: true }
      , application:  { type: String, required: true }
      , companyName:  { type: String, required: true }
      , department:   { type: String }
      , jobType:      { type: String }
    }
  , location: {
        city:         { type: String, required: true }
      , state:        { type: String, required: true }
    }
  , date: {
        postedOn:     { type: String, required: true }
      , endsOn:       { type: String, required: true }
    }
  , media: {
        image:        { type: String }
      , video:        { type: String }
    }
  , meta: {
        id:           { type: String }
  }
  , tags:             [ String ]
  , reviews:          [JobReviewSchema]
  , poster: {type: Schema.Types.ObjectId, ref: 'Employer'}
});

var textSearchIndexFields = {
    "specifics.jobTitle": "text",
    "specifics.description": "text",
    "specifics.requirements": "text",
    "specifics.companyName": "text",
    "specifics.jobType": "text",
    "specifics.location": "text",
    "tags": "text"
},
    textSearchIndexOptions = {
    "name": "text_search_index",
    "weights": {
        "specifics.jobTitle": 4,
        "specifics.description": 1,
        "specifics.requirements": 1,
        "specifics.companyName": 5,
        "specifics.jobType": 4,
        "specifics.location": 4,
        "tags": 6
    }
};

jobPostingSchema.index(textSearchIndexFields, textSearchIndexOptions);

/**
 * Search for job postings based on a keyword. Keyword
 * could be contained in job description OR job title
 * @param keyword {String}
 * @param cb {Function} callback
 * callback(err, [post1, post2...])
 */

jobPostingSchema.static('findByKeyword', function jobSearch(keyword, cb, options){
    var jobPosting = this;

    var limit = 100;

    // Sanity checking
    if (!keyword)
        return cb(new Error("Keyword was not provided"));
    if (typeof keyword !== "string")
        return cb(new Error("Keyword was not a string"));

    if(options) {
        if(options.limit) {
            limit = options.limit;
        }
    }

    jobPosting.find(
        {$text: {$search: keyword }},
        {score : {$meta: "textScore"}}
     )
     .sort({score: {$meta: "textScore"}
     })
     .limit(limit)
     .populate('reviews')
     .exec(cb);
});

jobPostingSchema.static('findByUrlId', function jobSearchUrlId(b64Id, cb) {
    var JobPosting = this,
        _id = null;

    try {
        _id = base64ToObjectId(b64Id);
        return JobPosting.findById(_id, cb);
    } catch(err) {
        err.status = 400;
        cb(err);
    }
});

jobPostingSchema.methods.getReviewByUrlId = function reviewSearchUrlId(reviewB64Id) {
    var jobPosting = this,
        review_id = null;

    try {
        review_id = base64ObjectId(reviewB64Id);
        return jobPosting.reviews.id(review_id);
    } catch(err) {
        return null;
    }
};

jobPostingSchema.pre('save', function beforeSavingJobPost(next) {
  var jobPosting = this;
    if(jobPosting.isNew) {
        jobPosting.meta.id = objectIdToBase64(jobPosting._id);
    }
    next();
});

/**
 * Export schema
 */

exports = module.exports = jobPostingSchema;
<|endoftext|>


<commit_before>/**
 * TODO: add tests
 */

(function (Handsontable) {
  'use strict';

  function HandsontableFormula() {

    var instance,
        plugin,
        pluginEnabled = false;

    var formulaRenderer = function (instance, TD, row, col, prop, value, cellProperties) {

      // translate coordinates into cellId
      var cellId = plugin.utils.translateCellCoords({row: row, col: col}),
          prevFormula = null,
          formula = null,
          needUpdate = false,
          error, result;

      if (!cellId) {
        return;
      }

      // get cell data
      var item = plugin.matrix.getItem(cellId);

      if (item && item.error) {
        prevFormula = item.formula;
        error = item.error;
        needUpdate = item.needUpdate;

        if (needUpdate) {
          error = null;
        }
      }

      // check if typed formula or cell value should be recalculated
      if ((value && value[0] === '=') || needUpdate) {

        formula = value.substr(1).toUpperCase();

        if (!error || formula !== prevFormula) {

          var currentItem = item;

          if (!currentItem) {

            // define item to rulesJS matrix if not exists
            item = {
              id: cellId,
              formula: formula
            };

            // add item to matrix
            currentItem = plugin.matrix.addItem(item);
          }

          // parse formula
          var newValue = plugin.parse(formula, {row: row, col: col, id: cellId});

          // update item value and error
          plugin.matrix.updateItem(currentItem, {formula: formula, value: newValue.result, error: newValue.error, needUpdate: false});

          error = newValue.error;
          result = newValue.result;

          // update cell value in hot
          value = error || result;
        }
      }

      if (error) {
        // clear cell value
        if (!value) {
        // reset error
          error = null;
        } else {
        // show error
          value = error;
        }
      }

      // change background color
      if (plugin.utils.isSet(error)) {
        TD.style.backgroundColor = 'red';
      } else if (plugin.utils.isSet(result)) {
        TD.style.backgroundColor = 'yellow';
      }

      // apply changes
      textCell.renderer.apply(this, [instance, TD, row, col, prop, value, cellProperties]);
    };

    var afterChange = function (changes, source) {
      if (source === 'edit') {

        var rerender = false;

        changes.forEach(function (item) {

          var row = item[0],
              col = item[1],
              prevValue = item[2],
              value = item[3];

          var cellId = plugin.utils.translateCellCoords({row: row, col: col});

          // if changed value, all references cells should be recalculated
          if (value[0] !== '=' || prevValue !== value) {
            plugin.matrix.removeItem(cellId);

            // get referenced cells
            var deps = plugin.matrix.getDependencies(cellId);

            // update cells
            deps.forEach(function (itemId) {
              plugin.matrix.updateItem(itemId, {needUpdate: true});
            });

            rerender = true;
          }
        });

        if (rerender) {
          instance.render();
        }
      }
    };

    var beforeAutofillInsidePopulate = function (index, direction, data, deltas, iterators, selected) {
      var r = index.row,
          c = index.col,
          value = data[r][c],
          delta = 0,
          rlength = data.length, // rows
          clength = data ? data[0].length : 0; //cols

      if (value[0] === '=') { // formula

        if (['down', 'up'].indexOf(direction) !== -1) {
          delta = rlength * iterators.row;
        } else if (['right', 'left'].indexOf(direction) !== -1) {
          delta = clength * iterators.col;
        }

        return {
          value: plugin.utils.updateFormula(value, direction, delta),
          iterators: iterators
        }

      } else { // other value

        // increment or decrement  values for more than 2 selected cells
        if (rlength >= 2 || clength >= 2) {

          var newValue = plugin.helper.number(value),
              ii,
              start;

          if (plugin.utils.isNumber(newValue)) {

            if (['down', 'up'].indexOf(direction) !== -1) {

              delta = deltas[0][c];

              if (direction === 'down') {
                newValue += (delta * rlength * iterators.row);
              } else {

                ii = (selected.row - r) % rlength;
                start = ii > 0 ? rlength - ii : 0;

                newValue = plugin.helper.number(data[start][c]);

                newValue += (delta * rlength * iterators.row);

                // last element in array -> decrement iterator
                // iterator cannot be less than 1
                if (iterators.row > 1 && (start + 1) === rlength) {
                  iterators.row--;
                }
              }

            } else if (['right', 'left'].indexOf(direction) !== -1) {
              delta = deltas[r][0];

              if (direction === 'right') {
                newValue += (delta * clength * iterators.col);
              } else {

                ii = (selected.col - c) % clength;
                start = ii > 0 ? clength - ii : 0;

                newValue = plugin.helper.number(data[r][start]);

                newValue += (delta * clength * (iterators.col || 1));

                // last element in array -> decrement iterator
                // iterator cannot be less than 1
                if (iterators.col > 1 && (start + 1) === clength) {
                  iterators.col--;
                }
              }
            }

            return {
              value: newValue,
              iterators: iterators
            }

          }
        }

      }

      return {
        value: value,
        iterators: iterators
      };
    };

    var formulaCell = {
      renderer: formulaRenderer,
      editor: Handsontable.editors.TextEditor,
      dataType: 'formula'
    };

    var textCell = {
      renderer: Handsontable.renderers.TextRenderer,
      editor: Handsontable.editors.TextEditor
    };

    this.init = function () {
      pluginEnabled = !!this.getSettings().formulas;

      if (pluginEnabled) {

        instance = this;

        var custom = {
          cellValue: instance.getDataAtCell
        };

        plugin = new ruleJS();
        plugin.init();

        plugin.custom = custom;

        Handsontable.cellTypes['formula'] = formulaCell;
        Handsontable.TextCell.renderer = formulaRenderer;

        Handsontable.hooks.add('afterChange', afterChange);
        Handsontable.hooks.add('beforeAutofillInsidePopulate', beforeAutofillInsidePopulate);
      }
    };
  }

  var htFormula = new HandsontableFormula();

  Handsontable.hooks.add('beforeInit', htFormula.init);

})(Handsontable);
<commit_msg>ctrl+z support<commit_after>/**
 * TODO: add tests
 */

(function (Handsontable) {
  'use strict';

  function HandsontableFormula() {

    var instance,
        plugin,
        pluginEnabled = false;

    var formulaRenderer = function (instance, TD, row, col, prop, value, cellProperties) {

      // translate coordinates into cellId
      var cellId = plugin.utils.translateCellCoords({row: row, col: col}),
          prevFormula = null,
          formula = null,
          needUpdate = false,
          error, result;

      if (!cellId) {
        return;
      }

      // get cell data
      var item = plugin.matrix.getItem(cellId);

      if (item && item.error) {
        prevFormula = item.formula;
        error = item.error;
        needUpdate = item.needUpdate;

        if (needUpdate) {
          error = null;
        }
      }

      // check if typed formula or cell value should be recalculated
      if ((value && value[0] === '=') || needUpdate) {

        formula = value.substr(1).toUpperCase();

        if (!error || formula !== prevFormula) {

          var currentItem = item;

          if (!currentItem) {

            // define item to rulesJS matrix if not exists
            item = {
              id: cellId,
              formula: formula
            };

            // add item to matrix
            currentItem = plugin.matrix.addItem(item);
          }

          // parse formula
          var newValue = plugin.parse(formula, {row: row, col: col, id: cellId});

          // update item value and error
          plugin.matrix.updateItem(currentItem, {formula: formula, value: newValue.result, error: newValue.error, needUpdate: false});

          error = newValue.error;
          result = newValue.result;

          // update cell value in hot
          value = error || result;
        }
      }

      if (error) {
        // clear cell value
        if (!value) {
        // reset error
          error = null;
        } else {
        // show error
          value = error;
        }
      }

      // change background color
      if (plugin.utils.isSet(error)) {
        TD.style.backgroundColor = 'red';
      } else if (plugin.utils.isSet(result)) {
        TD.style.backgroundColor = 'yellow';
      }

      // apply changes
      textCell.renderer.apply(this, [instance, TD, row, col, prop, value, cellProperties]);
    };

    var afterChange = function (changes, source) {
      if (source === 'edit' || source === 'undo') {

        var rerender = false;

        changes.forEach(function (item) {

          var row = item[0],
              col = item[1],
              prevValue = item[2],
              value = item[3];

          var cellId = plugin.utils.translateCellCoords({row: row, col: col});

          // if changed value, all references cells should be recalculated
          if (value[0] !== '=' || prevValue !== value) {
            plugin.matrix.removeItem(cellId);

            // get referenced cells
            var deps = plugin.matrix.getDependencies(cellId);

            // update cells
            deps.forEach(function (itemId) {
              plugin.matrix.updateItem(itemId, {needUpdate: true});
            });

            rerender = true;
          }
        });

        if (rerender) {
          instance.render();
        }
      }
    };

    var beforeAutofillInsidePopulate = function (index, direction, data, deltas, iterators, selected) {
      var r = index.row,
          c = index.col,
          value = data[r][c],
          delta = 0,
          rlength = data.length, // rows
          clength = data ? data[0].length : 0; //cols

      if (value[0] === '=') { // formula

        if (['down', 'up'].indexOf(direction) !== -1) {
          delta = rlength * iterators.row;
        } else if (['right', 'left'].indexOf(direction) !== -1) {
          delta = clength * iterators.col;
        }

        return {
          value: plugin.utils.updateFormula(value, direction, delta),
          iterators: iterators
        }

      } else { // other value

        // increment or decrement  values for more than 2 selected cells
        if (rlength >= 2 || clength >= 2) {

          var newValue = plugin.helper.number(value),
              ii,
              start;

          if (plugin.utils.isNumber(newValue)) {

            if (['down', 'up'].indexOf(direction) !== -1) {

              delta = deltas[0][c];

              if (direction === 'down') {
                newValue += (delta * rlength * iterators.row);
              } else {

                ii = (selected.row - r) % rlength;
                start = ii > 0 ? rlength - ii : 0;

                newValue = plugin.helper.number(data[start][c]);

                newValue += (delta * rlength * iterators.row);

                // last element in array -> decrement iterator
                // iterator cannot be less than 1
                if (iterators.row > 1 && (start + 1) === rlength) {
                  iterators.row--;
                }
              }

            } else if (['right', 'left'].indexOf(direction) !== -1) {
              delta = deltas[r][0];

              if (direction === 'right') {
                newValue += (delta * clength * iterators.col);
              } else {

                ii = (selected.col - c) % clength;
                start = ii > 0 ? clength - ii : 0;

                newValue = plugin.helper.number(data[r][start]);

                newValue += (delta * clength * (iterators.col || 1));

                // last element in array -> decrement iterator
                // iterator cannot be less than 1
                if (iterators.col > 1 && (start + 1) === clength) {
                  iterators.col--;
                }
              }
            }

            return {
              value: newValue,
              iterators: iterators
            }

          }
        }

      }

      return {
        value: value,
        iterators: iterators
      };
    };

    var formulaCell = {
      renderer: formulaRenderer,
      editor: Handsontable.editors.TextEditor,
      dataType: 'formula'
    };

    var textCell = {
      renderer: Handsontable.renderers.TextRenderer,
      editor: Handsontable.editors.TextEditor
    };

    this.init = function () {
      pluginEnabled = !!this.getSettings().formulas;

      if (pluginEnabled) {

        instance = this;

        var custom = {
          cellValue: instance.getDataAtCell
        };

        plugin = new ruleJS();
        plugin.init();

        plugin.custom = custom;

        Handsontable.cellTypes['formula'] = formulaCell;
        Handsontable.TextCell.renderer = formulaRenderer;

        Handsontable.hooks.add('afterChange', afterChange);
        Handsontable.hooks.add('beforeAutofillInsidePopulate', beforeAutofillInsidePopulate);
      }
    };
  }

  var htFormula = new HandsontableFormula();

  Handsontable.hooks.add('beforeInit', htFormula.init);

})(Handsontable);
<|endoftext|>