<commit_before>var Indexing = (function() {
  exports = {};

  function arraysIdentical(a, b) {
    var i = a.length;
    if (!b || i != b.length) return false;
    while (i--) {
      if(a[i] && a[i].constructor === Array) {
        if(!arraysIdentical(a[i], b[i])) return false;
        continue;
      }
      if (a[i] !== b[i]) return false;
    }
    return true;
  }

  exports.arraysIdentical = arraysIdentical;

  function indexOfArray(haystack, needle) {
    var result = -1;
    for(var haystackIx = 0, haystackLen = haystack.length; haystackIx < haystackLen; haystackIx++) {
      var cur = haystack[haystackIx];
      if(arraysIdentical(cur, needle)) {
        result = haystackIx;
        break;
      }
    }
    return result;
  }

  function applyTableDiff(table, adds, removes) {
    for(var remIx = 0, remLen = removes.length; remIx < remLen; remIx++) {
      var rem = removes[remIx];
      var foundIx = indexOfArray(table, rem);
      if(foundIx !== -1) {
        table.splice(foundIx, 1);
      }
    }
    for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
      var add = adds[addIx];
      var foundIx = indexOfArray(table, add);
      if(foundIx !== -1) continue;
      table.push(add);
    }
  }

  function Indexer() {
    this.tables = {};
    this.indexes = {};
    this.tableToIndex = {};
  }

  Indexer.prototype = {
    clear: function() {
      var final = {};
      for(var table in this.tables) {
        this.handleDiff(table, [], this.tables[table]);
      }
      return {changes: final};
    },
    load: function(pickle) {
      var diffs = {};
      for(var table in pickle) {
        this.handleDiff(table, pickle[table]);
      }
    },
    handleDiff: function(table, adds, removes) {
      var safeAdds = adds || [];
      var safeRemoves = removes || [];
      var indexes = this.tableToIndex[table] || [];
      for(var ix = 0, len = indexes.length; ix < len; ix++) {
        var cur = indexes[ix];
        cur.index = cur.indexer(cur.index, safeAdds, safeRemoves);
      }
      if(!this.tables[table]) {
        this.tables[table] = [];
      }
      applyTableDiff(this.tables[table], safeAdds, safeRemoves);
    },
    dumpMapDiffs: function() {
      var final = {};
      for(var table in this.tables) {
        final[table] = {inserted: this.tables[table], removed: []};
      }
      return {changes: final};
    },
    handleMapDiffs: function(diffs) {
      for(var table in diffs) {
        var diff = diffs[table];
        if(diff.inserted.length || diff.removed.length) {
          this.handleDiff(table, diff.inserted, diff.removed);
        }
      }
    },
    handleDiffs: function(diffs) {
      var diffTables = {};
      var adds = {};
      var removes = {};
      for(var diffIx = 0, diffLen = diffs.length; diffIx < diffLen; diffIx++) {
        var cur = diffs[diffIx];
        var table = cur[0];
        var action = cur[1];
        var fact = cur[2];
        diffTables[table] = true;
        if(action === "inserted") {
          if(!adds[table]) { adds[table] = []; }
          adds[table].push(fact);
        } else {
          if(!removes[table]) { removes[table] = []; }
          removes[table].push(fact);
        }
      }
      for(var table in diffTables) {
        this.handleDiff(table, adds[table], removes[table]);
      }
    },
    addIndex: function(name, table, indexer) {
      var index = {index: {}, indexer: indexer, table: table};
      this.indexes[name] = index;
      if(!this.tableToIndex[table]) {
        this.tableToIndex[table] = [];
      }
      this.tableToIndex[table].push(index);
      if(this.tables[table]) {
        index.index = index.indexer(index.index, this.tables[table], []);
      }
    },
    index: function(name) {
      if(this.indexes[name]) {
        return this.indexes[name].index;
      }
      return null;
    },
    facts: function(name) {
      return this.tables[name] || [];
    },
    first: function(name) {
      return this.facts(name)[0];
    }
  };

  exports.Indexer = Indexer;

  var create = {
    lookup: function(keyIxes) {
      var valueIx = keyIxes.pop();
      return function(cur, adds, removes) {
        var cursor;
        outer: for(var remIx = 0, remLen = removes.length; remIx < remLen; remIx++) {
          var rem = removes[remIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            cursor = cursor[rem[keyIxes[ix]]];
            if(!cursor) continue outer;
          }
          delete cursor[rem[keyIxes[keyIxes.length - 1]]];
        }
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          if(valueIx !== false) {
            cursor[add[keyIxes[keyIxes.length - 1]]] = add[valueIx];
          } else {
            cursor[add[keyIxes[keyIxes.length - 1]]] = add;
          }
        }
        return cur;
      }
    },
    collector: function(keyIxes) {
      return function(cur, adds, removes) {
        var cursor;
        outer: for(var remIx = 0, remLen = removes.length; remIx < remLen; remIx++) {
          var rem = removes[remIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            cursor = cursor[rem[keyIxes[ix]]];
            if(!cursor) continue outer;
          }

          cursor[rem[keyIxes[keyIxes.length - 1]]] = cursor[rem[keyIxes[keyIxes.length - 1]]].filter(function(potential) {
            return !arraysIdentical(rem, potential);
          });
        }
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          next = cursor[add[keyIxes[keyIxes.length - 1]]];
          if(!next) {
            next = cursor[add[keyIxes[keyIxes.length - 1]]] = [];
          }
          next.push(add);
        }
        return cur;
      }
    },
    //OPTS:
    // {
    //   keys: [], //keys to index on
    // }
    latestLookup: function(opts) {
      var keyIxes = opts.keys;
      var valueIx = keyIxes.pop();
      return function(cur, adds, removes) {
        var cursor;
        //in a latest scenario, we never remove so we only need to worry about
        //adds
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          var finalKey = add[keyIxes[keyIxes.length - 1]];
          if(valueIx !== false) {
            //in the case where we only store the value, we just assume that later things
            //will be later. This may or may not be what you want.
            cursor[finalKey] = add[valueIx];
          } else {
            var finalValue = cursor[finalKey];
            //if the added value's transaction time is not later, then there's nothing to do here.
            if(finalValue && finalValue[0] >= add[0]) continue;
            cursor[finalKey] = add;
          }
        }
        return cur;
      }
    },
    //OPTS:
    // {
    //   keys: [], //keys to index on
    //   uniqueness: [] //keys that determine uniqueness when testing for latest
    // }
    latestCollector: function(opts) {
      var keyIxes = opts.keys;
      var uniques = opts.uniqueness;
      return function(cur, adds, removes) {
        var cursor;
        //in a latest scenario, we never remove so we only need to worry about
        //adds
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          next = cursor[add[keyIxes[keyIxes.length - 1]]];
          if(!next) {
            next = cursor[add[keyIxes[keyIxes.length - 1]]] = [];
          }
          if(next.length) {
            var found = false;
            //look through and determine if there is something with the same uniqueness
            //that is older than the current value being inserted
            filter: for(var filterIx = 0, filterLen = next.length; filterIx < filterLen; filterIx++) {
              var nextItem = next[filterIx];
              for(var uniqueIx = 0, uniqueLen = uniques.length; uniqueIx < uniqueLen; uniqueIx++) {
                var uniqueKey = uniques[uniqueIx];
                if(nextItem[uniqueKey] !== add[uniqueKey]) {
                  continue filter;
                }
              }
              if(nextItem[0] < add[0]) {
                found = true;
                next[filterIx] = add;
              }
            }
            if(!found) {
              next.push(add);
            }
          } else {
            next.push(add);
          }
        }
        return cur;
      }
    },
  };

  exports.create = create;

  return exports;
})();
<commit_msg>add indexOnly to mark tables that you don't want to keep every fact for<commit_after>var Indexing = (function() {
  exports = {};

  function arraysIdentical(a, b) {
    var i = a.length;
    if (!b || i != b.length) return false;
    while (i--) {
      if(a[i] && a[i].constructor === Array) {
        if(!arraysIdentical(a[i], b[i])) return false;
        continue;
      }
      if (a[i] !== b[i]) return false;
    }
    return true;
  }

  exports.arraysIdentical = arraysIdentical;

  function indexOfArray(haystack, needle) {
    var result = -1;
    for(var haystackIx = 0, haystackLen = haystack.length; haystackIx < haystackLen; haystackIx++) {
      var cur = haystack[haystackIx];
      if(arraysIdentical(cur, needle)) {
        result = haystackIx;
        break;
      }
    }
    return result;
  }

  function applyTableDiff(table, adds, removes) {
    for(var remIx = 0, remLen = removes.length; remIx < remLen; remIx++) {
      var rem = removes[remIx];
      var foundIx = indexOfArray(table, rem);
      if(foundIx !== -1) {
        table.splice(foundIx, 1);
      }
    }
    for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
      var add = adds[addIx];
      var foundIx = indexOfArray(table, add);
      if(foundIx !== -1) continue;
      table.push(add);
    }
  }

  function Indexer() {
    this.tables = {};
    this.indexes = {};
    this.tableToIndex = {};
  }

  Indexer.prototype = {
    clear: function() {
      var final = {};
      for(var table in this.tables) {
        this.handleDiff(table, [], this.tables[table]);
      }
      return {changes: final};
    },
    load: function(pickle) {
      var diffs = {};
      for(var table in pickle) {
        this.handleDiff(table, pickle[table]);
      }
    },
    handleDiff: function(table, adds, removes) {
      var safeAdds = adds || [];
      var safeRemoves = removes || [];
      var indexes = this.tableToIndex[table] || [];
      for(var ix = 0, len = indexes.length; ix < len; ix++) {
        var cur = indexes[ix];
        cur.index = cur.indexer(cur.index, safeAdds, safeRemoves);
      }
      if(this.tables[table] === undefined) {
        this.tables[table] = [];
      }
      if(this.tables[table] !== false) {
        applyTableDiff(this.tables[table], safeAdds, safeRemoves);
      }
    },
    indexOnly: function(table) {
      this.tables[table] = false;
    },
    dumpMapDiffs: function() {
      var final = {};
      for(var table in this.tables) {
        final[table] = {inserted: this.tables[table], removed: []};
      }
      return {changes: final};
    },
    handleMapDiffs: function(diffs) {
      for(var table in diffs) {
        var diff = diffs[table];
        if(diff.inserted.length || diff.removed.length) {
          this.handleDiff(table, diff.inserted, diff.removed);
        }
      }
    },
    handleDiffs: function(diffs) {
      var diffTables = {};
      var adds = {};
      var removes = {};
      for(var diffIx = 0, diffLen = diffs.length; diffIx < diffLen; diffIx++) {
        var cur = diffs[diffIx];
        var table = cur[0];
        var action = cur[1];
        var fact = cur[2];
        diffTables[table] = true;
        if(action === "inserted") {
          if(!adds[table]) { adds[table] = []; }
          adds[table].push(fact);
        } else {
          if(!removes[table]) { removes[table] = []; }
          removes[table].push(fact);
        }
      }
      for(var table in diffTables) {
        this.handleDiff(table, adds[table], removes[table]);
      }
    },
    addIndex: function(name, table, indexer) {
      var index = {index: {}, indexer: indexer, table: table};
      this.indexes[name] = index;
      if(!this.tableToIndex[table]) {
        this.tableToIndex[table] = [];
      }
      this.tableToIndex[table].push(index);
      if(this.tables[table]) {
        index.index = index.indexer(index.index, this.tables[table], []);
      }
    },
    index: function(name) {
      if(this.indexes[name]) {
        return this.indexes[name].index;
      }
      return null;
    },
    facts: function(name) {
      return this.tables[name] || [];
    },
    first: function(name) {
      return this.facts(name)[0];
    }
  };

  exports.Indexer = Indexer;

  var create = {
    lookup: function(keyIxes) {
      var valueIx = keyIxes.pop();
      return function(cur, adds, removes) {
        var cursor;
        outer: for(var remIx = 0, remLen = removes.length; remIx < remLen; remIx++) {
          var rem = removes[remIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            cursor = cursor[rem[keyIxes[ix]]];
            if(!cursor) continue outer;
          }
          delete cursor[rem[keyIxes[keyIxes.length - 1]]];
        }
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          if(valueIx !== false) {
            cursor[add[keyIxes[keyIxes.length - 1]]] = add[valueIx];
          } else {
            cursor[add[keyIxes[keyIxes.length - 1]]] = add;
          }
        }
        return cur;
      }
    },
    collector: function(keyIxes) {
      return function(cur, adds, removes) {
        var cursor;
        outer: for(var remIx = 0, remLen = removes.length; remIx < remLen; remIx++) {
          var rem = removes[remIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            cursor = cursor[rem[keyIxes[ix]]];
            if(!cursor) continue outer;
          }

          cursor[rem[keyIxes[keyIxes.length - 1]]] = cursor[rem[keyIxes[keyIxes.length - 1]]].filter(function(potential) {
            return !arraysIdentical(rem, potential);
          });
        }
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          next = cursor[add[keyIxes[keyIxes.length - 1]]];
          if(!next) {
            next = cursor[add[keyIxes[keyIxes.length - 1]]] = [];
          }
          next.push(add);
        }
        return cur;
      }
    },
    //OPTS:
    // {
    //   keys: [], //keys to index on
    // }
    latestLookup: function(opts) {
      var keyIxes = opts.keys;
      var valueIx = keyIxes.pop();
      return function(cur, adds, removes) {
        var cursor;
        //in a latest scenario, we never remove so we only need to worry about
        //adds
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          var finalKey = add[keyIxes[keyIxes.length - 1]];
          if(valueIx !== false) {
            //in the case where we only store the value, we just assume that later things
            //will be later. This may or may not be what you want.
            cursor[finalKey] = add[valueIx];
          } else {
            var finalValue = cursor[finalKey];
            //if the added value's transaction time is not later, then there's nothing to do here.
            if(finalValue && finalValue[0] >= add[0]) continue;
            cursor[finalKey] = add;
          }
        }
        return cur;
      }
    },
    //OPTS:
    // {
    //   keys: [], //keys to index on
    //   uniqueness: [] //keys that determine uniqueness when testing for latest
    // }
    latestCollector: function(opts) {
      var keyIxes = opts.keys;
      var uniques = opts.uniqueness;
      return function(cur, adds, removes) {
        var cursor;
        //in a latest scenario, we never remove so we only need to worry about
        //adds
        for(var addIx = 0, addLen = adds.length; addIx < addLen; addIx++) {
          var add = adds[addIx];
          cursor = cur;
          for(var ix = 0, keyLen = keyIxes.length - 1; ix < keyLen; ix++) {
            var next = cursor[add[keyIxes[ix]]];
            if(!next) {
              next = cursor[add[keyIxes[ix]]] = {};
            }
            cursor = next;
          }
          next = cursor[add[keyIxes[keyIxes.length - 1]]];
          if(!next) {
            next = cursor[add[keyIxes[keyIxes.length - 1]]] = [];
          }
          if(next.length) {
            var found = false;
            //look through and determine if there is something with the same uniqueness
            //that is older than the current value being inserted
            filter: for(var filterIx = 0, filterLen = next.length; filterIx < filterLen; filterIx++) {
              var nextItem = next[filterIx];
              for(var uniqueIx = 0, uniqueLen = uniques.length; uniqueIx < uniqueLen; uniqueIx++) {
                var uniqueKey = uniques[uniqueIx];
                if(nextItem[uniqueKey] !== add[uniqueKey]) {
                  continue filter;
                }
              }
              if(nextItem[0] < add[0]) {
                found = true;
                next[filterIx] = add;
              }
            }
            if(!found) {
              next.push(add);
            }
          } else {
            next.push(add);
          }
        }
        return cur;
      }
    },
  };

  exports.create = create;

  return exports;
})();
<|endoftext|>


<commit_before>//TODO: Split into multiple files

var HomeController = function ($http) {
    var ctrl = this;
    this.greeting = 'nothing yet';

    $http.get('/hello/World')
        .success(function (data) {
            ctrl.greeting = data.greeting;
        });
};

var AuthController = function ($rootScope, $http, $location) {
    var auth = this;
    this.credentials = {};
    this.error = false;
    this.errorMessage = 'Falscher Benutzername oder falsches Passwort!';
    this.username = 'Kevin';

    var authenticate = function (credentials, callback) {
        var headers = credentials ? {
            authorization: "Basic " + btoa(credentials.username + ":" + credentials.password)
        } : {}; //add headers if we were called using credentials

        $http.get('auth/status', {headers: headers}).success(function (data) {
            if (data.name) {
                $rootScope.authenticated = true;
                auth.username = data.name;
            } else {
                $rootScope.authenticated = false;
                auth.username = '--';
            }
            callback && callback($rootScope.authenticated); //only call if we actually have a callback
        }).error(function () {
            $rootScope.authenticated = false;
            callback && callback(false);
        });
    };

    this.login = function () {
        authenticate(auth.credentials,
            function (authSuccess) {
                auth.error = !authSuccess;
                if (authSuccess) {
                    if ($rootScope.returnto != null) {
                        $location.path($rootScope.returnto);
                        $rootScope.returnto = null;
                    } else {
                        $location.path('/');
                    }
                } else {
                    $location.path('/login');
                }
            }
        );
    };

    this.logout = function () {
        $http.post('logout', {}).success(function () {
            $rootScope.authenticated = false;
            auth.username = '-';
            $location.path('/');
        }).error(function () {
            $rootScope.authenticated = false;
        });
    };

    this.register = function () {
        $rootScope.authenticated = false;
        $http.post('auth/register', auth.credentials).success(function() {
            auth.login();
        }).error(function(data) {
            auth.error = true;
            auth.errorMessage = data.errorMessage;
        });
    };

    authenticate();
};

var RegisterController = function() {

};

var LoginController = function ($stateParams, $rootScope, $location) {
    this.errRedirect = $stateParams.errRedirect;

    this.back = function() {
        if ($rootScope.returnto != null) {
            $location.path($rootScope.returnto);
            $rootScope.returnto = null;
        } else {
            $location.path('/');
        }
    }
};

var TeacherDetailController = function ($http, $stateParams) {
    var ctrl = this;
    this.teacher = {name: '...', abbreviation: '....'};
    this.fields = {};
    this.rows = [];

    $http.get('/api/field/by/teacher/' + $stateParams.id)
        .success(function (data) {
            ctrl.teacher = data.teacher;
            ctrl.fields = data;
            var rowsCount = Math.ceil(data / 5);
            var columnsCount = 5;
            var possibleRows = [5, 4, 3];

            for (var j = 0; j < possibleRows.length; j++) {
                var possibleRow = possibleRows[j];
                console.info(ctrl.fields.count % possibleRow);
                if ((ctrl.fields.count % possibleRow) == 0) {
                    console.info(possibleRow + "--pR");
                    rowsCount = possibleRow;
                    columnsCount = ctrl.fields.count / rowsCount;
                    break;
                }
            }

            var i = 0;
            for (var rowId = 0; rowId < rowsCount; rowId++) {
                var row = [];
                for (var columnId = 0; columnId < columnsCount; columnId++) {
                    row[columnId] = ctrl.fields.fields[i];
                    i++;
                }
                console.info(row);
                ctrl.rows[rowId] = row;
            }
            console.info(rowsCount + " -- " + columnsCount);
            console.info(data);
            console.info(ctrl.rows);
        });
};

var TeacherListController = function ($http) {
    var ctrl = this;
    this.teachers = {};

    $http.get('/api/teacher/list')
        .success(function (data) {
            ctrl.teachers = data;
        });
};

var tingoApp = angular.module('tingo', ['ui.router']);

tingoApp.config(function ($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $httpProvider) {
    $urlRouterProvider.otherwise("/");
    $urlRouterProvider.when('/teachers', '/teachers/list');

    $stateProvider
        .state('home', {
            url: '/',
            templateUrl: 'partials/home.html',
            controller: 'HomeController',
            controllerAs: 'homeCtrl',
            data: {
                no_auth: true
            }
        })
        .state('login', {
            url: '/login?errRedirect',
            templateUrl: 'partials/login.html',
            controller: 'LoginController',
            controllerAs: 'loginCtrl',
            data: {
                no_auth: true
            }
        })
        .state('register', {
            url: '/register',
            templateUrl: 'partials/register.html',
            controller: 'RegisterController',
            controllerAs: 'regCtrl',
            data: {
                no_auth: true
            }
        })
        .state('teachers', {
            url: '/teachers',
            abstract: true,
            template: '<ui-view/>'
        })
        .state('teachers.list', {
            url: '/list',
            templateUrl: 'partials/teacher-list.html',
            controller: 'TeacherListController',
            controllerAs: 'listCtrl'
        })
        .state('teachers.detail', {
            url: '/detail/:id',
            templateUrl: 'partials/teacher-detail.html',
            controller: 'TeacherDetailController',
            controllerAs: 'detailCtrl'
        });

    //Prevent Spring Security from displaying auth dialog, we control authentication ourselves!
    $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
});

tingoApp.run(function ($rootScope, $state, $location) {
    $rootScope.authenticated = false;
    $rootScope.returnto = null;
    $rootScope.$on('$stateChangeStart', function (e, to) {
        if ((!to.data || !to.data.no_auth) && !$rootScope.authenticated) {
            e.preventDefault();
            $state.go('login', {errRedirect: true});
            $rootScope.returnto = $location.path();
        }
    })
});

tingoApp.controller('HomeController', HomeController);
tingoApp.controller('AuthController', AuthController);
tingoApp.controller('TeacherDetailController', TeacherDetailController);
tingoApp.controller('TeacherListController', TeacherListController);
tingoApp.controller('LoginController', LoginController);
tingoApp.controller('RegisterController', RegisterController);
<commit_msg>Fix dropdown in navbar not working<commit_after>//TODO: Split into multiple files

var HomeController = function ($http) {
    var ctrl = this;
    this.greeting = 'nothing yet';

    $http.get('/hello/World')
        .success(function (data) {
            ctrl.greeting = data.greeting;
        });
};

var AuthController = function ($rootScope, $http, $location) {
    var auth = this;
    this.credentials = {};
    this.error = false;
    this.errorMessage = 'Falscher Benutzername oder falsches Passwort!';
    this.username = 'Kevin';

    var authenticate = function (credentials, callback) {
        var headers = credentials ? {
            authorization: "Basic " + btoa(credentials.username + ":" + credentials.password)
        } : {}; //add headers if we were called using credentials

        $http.get('auth/status', {headers: headers}).success(function (data) {
            if (data.name) {
                $rootScope.authenticated = true;
                auth.username = data.name;
            } else {
                $rootScope.authenticated = false;
                auth.username = '--';
            }
            callback && callback($rootScope.authenticated); //only call if we actually have a callback
        }).error(function () {
            $rootScope.authenticated = false;
            callback && callback(false);
        });
    };

    this.login = function () {
        authenticate(auth.credentials,
            function (authSuccess) {
                auth.error = !authSuccess;
                if (authSuccess) {
                    if ($rootScope.returnto != null) {
                        $location.path($rootScope.returnto);
                        $rootScope.returnto = null;
                    } else {
                        $location.path('/');
                    }
                } else {
                    $location.path('/login');
                }
            }
        );
    };

    this.logout = function () {
        $http.post('logout', {}).success(function () {
            $rootScope.authenticated = false;
            auth.username = '-';
            $location.path('/');
        }).error(function () {
            $rootScope.authenticated = false;
        });
    };

    this.register = function () {
        $rootScope.authenticated = false;
        $http.post('auth/register', auth.credentials).success(function() {
            auth.login();
        }).error(function(data) {
            auth.error = true;
            auth.errorMessage = data.errorMessage;
        });
    };

    authenticate();
};

var RegisterController = function() {

};

var LoginController = function ($stateParams, $rootScope, $location) {
    this.errRedirect = $stateParams.errRedirect;

    this.back = function() {
        if ($rootScope.returnto != null) {
            $location.path($rootScope.returnto);
            $rootScope.returnto = null;
        } else {
            $location.path('/');
        }
    }
};

var TeacherDetailController = function ($http, $stateParams) {
    var ctrl = this;
    this.teacher = {name: '...', abbreviation: '....'};
    this.fields = {};
    this.rows = [];

    $http.get('/api/field/by/teacher/' + $stateParams.id)
        .success(function (data) {
            ctrl.teacher = data.teacher;
            ctrl.fields = data;
            var rowsCount = Math.ceil(data / 5);
            var columnsCount = 5;
            var possibleRows = [5, 4, 3];

            for (var j = 0; j < possibleRows.length; j++) {
                var possibleRow = possibleRows[j];
                console.info(ctrl.fields.count % possibleRow);
                if ((ctrl.fields.count % possibleRow) == 0) {
                    console.info(possibleRow + "--pR");
                    rowsCount = possibleRow;
                    columnsCount = ctrl.fields.count / rowsCount;
                    break;
                }
            }

            var i = 0;
            for (var rowId = 0; rowId < rowsCount; rowId++) {
                var row = [];
                for (var columnId = 0; columnId < columnsCount; columnId++) {
                    row[columnId] = ctrl.fields.fields[i];
                    i++;
                }
                console.info(row);
                ctrl.rows[rowId] = row;
            }
            console.info(rowsCount + " -- " + columnsCount);
            console.info(data);
            console.info(ctrl.rows);
        });
};

var TeacherListController = function ($http) {
    var ctrl = this;
    this.teachers = {};

    $http.get('/api/teacher/list')
        .success(function (data) {
            ctrl.teachers = data;
        });
};

var tingoApp = angular.module('tingo', ['ui.router', 'ui.bootstrap']);

tingoApp.config(function ($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $httpProvider) {
    $urlRouterProvider.otherwise("/");
    $urlRouterProvider.when('/teachers', '/teachers/list');

    $stateProvider
        .state('home', {
            url: '/',
            templateUrl: 'partials/home.html',
            controller: 'HomeController',
            controllerAs: 'homeCtrl',
            data: {
                no_auth: true
            }
        })
        .state('login', {
            url: '/login?errRedirect',
            templateUrl: 'partials/login.html',
            controller: 'LoginController',
            controllerAs: 'loginCtrl',
            data: {
                no_auth: true
            }
        })
        .state('register', {
            url: '/register',
            templateUrl: 'partials/register.html',
            controller: 'RegisterController',
            controllerAs: 'regCtrl',
            data: {
                no_auth: true
            }
        })
        .state('teachers', {
            url: '/teachers',
            abstract: true,
            template: '<ui-view/>'
        })
        .state('teachers.list', {
            url: '/list',
            templateUrl: 'partials/teacher-list.html',
            controller: 'TeacherListController',
            controllerAs: 'listCtrl'
        })
        .state('teachers.detail', {
            url: '/detail/:id',
            templateUrl: 'partials/teacher-detail.html',
            controller: 'TeacherDetailController',
            controllerAs: 'detailCtrl'
        });

    //Prevent Spring Security from displaying auth dialog, we control authentication ourselves!
    $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
});

tingoApp.run(function ($rootScope, $state, $location) {
    $rootScope.authenticated = false;
    $rootScope.returnto = null;
    $rootScope.$on('$stateChangeStart', function (e, to) {
        if ((!to.data || !to.data.no_auth) && !$rootScope.authenticated) {
            e.preventDefault();
            $state.go('login', {errRedirect: true});
            $rootScope.returnto = $location.path();
        }
    })
});

tingoApp.controller('HomeController', HomeController);
tingoApp.controller('AuthController', AuthController);
tingoApp.controller('TeacherDetailController', TeacherDetailController);
tingoApp.controller('TeacherListController', TeacherListController);
tingoApp.controller('LoginController', LoginController);
tingoApp.controller('RegisterController', RegisterController);
<|endoftext|>


<commit_before>'use strict';

const execSync = require('child_process').execSync;
const fs = require('fs');
const path = require('path');

/**
 * Use Yarn if available, it's much faster than the npm client.
 * Return the version of yarn installed on the system, null if yarn is not available.
 */
function getYarnVersionIfAvailable() {
    let yarnVersion;
    try {
        // execSync returns a Buffer -> convert to string
        yarnVersion = (execSync('yarn --version', {
            stdio: [0, 'pipe', 'ignore'],
        }).toString() || '').trim();
    } catch (error) {
        return null;
    }
    return yarnVersion;
}

function installDevDependencies() {
    console.log('Adding dev dependencies for the project...');

    const devDependenciesJsonPath = path.resolve('devDependencies.json');
    const devDependencies = JSON.parse(fs.readFileSync(devDependenciesJsonPath));
    let depsToInstall = [];

    for (const depName in devDependencies) {
        const depVersion = devDependencies[depName];
        const depToInstall = `${depName}@${depVersion}`;
        depsToInstall.push(depToInstall);
    }

    depsToInstall = depsToInstall.join(' ');
    console.log(`Adding ${depsToInstall}...`);
    if (getYarnVersionIfAvailable()) {
        execSync(`yarn add ${depsToInstall} -D`, {stdio: 'inherit'});
    } else {
        execSync(`npm install ${depsToInstall} --save`);
    }
    console.log("Deleting devDependencies.json...");
    execSync(`rm  ${devDependenciesJsonPath}`);
}

//"resolutions": { "moment-timezone/moment": "2.19.0" }

function updatePackageJson() {
    const fileName = "package.json";
    const packageFile = path.resolve(fileName);
    let file = require(packageFile);
    //these are the scripts that will be added to package.json
    console.log(`Adding scripts for web to package.json`);
    file.scripts['web'] = "react-scripts start";
    file.scripts["build"] = "react-scripts build";
    file.scripts["test-web"] = "react-scripts test --env=jsdom";
    file.scripts["eject"] = "react-scripts eject";
    file.scripts["electron"] = "react-scripts start & electron .";
    console.log(`Adding entry point for electron`);
    file['main'] = "index.electron.js";
    file['resolutions'] = {"moment-timezone/moment": "2.19.0"};
    fs.writeFileSync(fileName, JSON.stringify(file));
}

function removeExtraFiles() {
    execSync(`rm -rf devDependencies.json`);
}

installDevDependencies();
updatePackageJson();
removeExtraFiles();

<commit_msg>remove obselete code<commit_after>'use strict';

const execSync = require('child_process').execSync;
const fs = require('fs');
const path = require('path');

/**
 * Use Yarn if available, it's much faster than the npm client.
 * Return the version of yarn installed on the system, null if yarn is not available.
 */
function getYarnVersionIfAvailable() {
    let yarnVersion;
    try {
        // execSync returns a Buffer -> convert to string
        yarnVersion = (execSync('yarn --version', {
            stdio: [0, 'pipe', 'ignore'],
        }).toString() || '').trim();
    } catch (error) {
        return null;
    }
    return yarnVersion;
}

function installDevDependencies() {
    console.log('Adding dev dependencies for the project...');

    const devDependenciesJsonPath = path.resolve('devDependencies.json');
    const devDependencies = JSON.parse(fs.readFileSync(devDependenciesJsonPath));
    let depsToInstall = [];

    for (const depName in devDependencies) {
        const depVersion = devDependencies[depName];
        const depToInstall = `${depName}@${depVersion}`;
        depsToInstall.push(depToInstall);
    }

    depsToInstall = depsToInstall.join(' ');
    console.log(`Adding ${depsToInstall}...`);
    if (getYarnVersionIfAvailable()) {
        execSync(`yarn add ${depsToInstall} -D`, {stdio: 'inherit'});
    } else {
        execSync(`npm install ${depsToInstall} --save`);
    }
    console.log("Deleting devDependencies.json...");
    execSync(`rm  ${devDependenciesJsonPath}`);
}

//"resolutions": { "moment-timezone/moment": "2.19.0" }

function updatePackageJson() {
    const fileName = "package.json";
    const packageFile = path.resolve(fileName);
    let file = require(packageFile);
    //these are the scripts that will be added to package.json
    console.log(`Adding scripts for web to package.json`);
    file.scripts['web'] = "react-scripts start";
    file.scripts["build"] = "react-scripts build";
    file.scripts["test-web"] = "react-scripts test --env=jsdom";
    file.scripts["eject"] = "react-scripts eject";
    file.scripts["electron"] = "react-scripts start & electron .";
    console.log(`Adding entry point for electron`);
    file['main'] = "index.electron.js";
    file['resolutions'] = {"moment-timezone/moment": "2.19.0"};
    fs.writeFileSync(fileName, JSON.stringify(file));
}

installDevDependencies();
updatePackageJson();

<|endoftext|>


<commit_before>/**
 * Created by lidavidm on 6/6/17.
 */

const CELL_SIZE = 1;
const PLAYER_COLORS = [0xFF704B, 0x9010B9, 0x005DD0, 0x00B553];
const PLANET_COLOR = 0x888888;

class FrameAnimation {
    constructor(frames, update, draw) {
        this.frames = frames;
        this.update = update;
        this.draw = draw;
    }
}

class HaliteVisualizer {
    constructor(replay) {
        this.replay = replay;
        this.frame = 0;
        this.substep = 0;
        this.application = new PIXI.Application(
            800, 100 + 800 * (this.replay.height / this.replay.width));

        this.scale = 800 / Math.max(replay.width, replay.height);

        this.planetContainer = new PIXI.Graphics();
        this.shipContainer = new PIXI.Graphics();
        this.container = new PIXI.Container();
        this.container.position.set(0, 100);
        this.container.addChild(this.planetContainer, this.shipContainer);

        this.statsDisplay = new PIXI.Graphics();

        this.currentTurnDisplay = new PIXI.Text("Frame");

        this.application.stage.addChild(this.container);
        this.application.stage.addChild(this.statsDisplay);
        this.application.stage.addChild(this.currentTurnDisplay);

        this.timer = null;

        this.animationQueue = [];
    }

    get currentSubstep() {
        return this.replay.frames[this.frame][this.substep];
    }

    get currentStatistics() {
        let substep = this.currentSubstep;
        let planets = { "unowned": 0 };
        let ships = {};
        let total_ships = 0;

        for (let planet of substep.planets) {
            if (planet.owner !== null) {
                if (typeof planets[planet.owner] === "undefined") {
                    planets[planet.owner] = 0;
                }
                planets[planet.owner]++;
            }
            else {
                planets["unowned"]++;
            }
        }

        for (let ship of substep.ships) {
            if (typeof ships[ship.owner] === "undefined") {
                ships[ship.owner] = 0;
            }
            ships[ship.owner]++;
            total_ships++;
        }

        return {
            "planets": planets,
            "ships": ships,
            "total_ships": total_ships,
        }
    }

    attach(containerEl) {
        $(containerEl).append(this.application.view);

        this.draw();
    }

    play() {
        if (this.timer) return;

        this.timer = window.setInterval(() => {
            for (let i = 0; i < 3; i++) {
                this.substep++;
                if (this.substep >= this.replay.frames[this.frame].length) {
                    this.substep = 0;
                    this.frame++;
                }

                if (this.frame >= this.replay.frames.length) {
                    this.pause();
                    this.frame = this.replay.frames.length - 1;
                    this.substep = this.replay.frames[this.frame].length - 1;
                    break;
                }

                this.update();
            }
        }, 1000/30);

        this.application.ticker.add((dt) => {
            this.draw(dt);
        });
    }

    pause() {
        if (!this.timer) return;

        window.clearInterval(this.timer);
        this.timer = null;
    }

    /**
     *
     * @param container
     * @param x
     * @param y
     * @param color
     * @param health_factor
     */
    drawCell(container, x, y, color, health_factor) {
        const side = CELL_SIZE * this.scale;
        x = x * side;
        y = y * side;
        container.lineStyle(0);
        container.beginFill(color, 0.5);
        container.drawRect(x, y, side, side);
        container.endFill();
        container.beginFill(color, 1);
        container.drawRect(
            x + health_factor * side,
            y + health_factor * side,
            (1 - 2*health_factor) * side,
            (1 - 2*health_factor) * side);
        container.endFill();
    }

    drawPlanet(planet) {
        let planetBase = this.replay.planets[planet.id];

        const side = CELL_SIZE * this.scale;

        this.planetContainer.lineStyle(0);
        for (let dx = -planetBase.r; dx <= planetBase.r; dx++) {
            for (let dy = -planetBase.r; dy <= planetBase.r; dy++) {
                if (dx*dx + dy*dy <= planetBase.r*planetBase.r) {
                    const health_factor = 0.5 *
                        (planetBase.production - planet.remaining_production) / planetBase.production;
                    this.drawCell(
                        this.planetContainer,
                        dx + planetBase.x,
                        dy + planetBase.y,
                        planet.owner === null ? PLANET_COLOR : PLAYER_COLORS[planet.owner],
                        health_factor,
                    );
                }
            }
        }

        const center_x = side * planetBase.x;
        const center_y = side * (planetBase.y + 0.5);

        const health_factor = planet.health / planetBase.health;
        const health_bar = health_factor * side * (planetBase.r - 1);
        this.planetContainer.beginFill(0xFF0000);
        this.planetContainer.lineStyle(2, 0x000000);
        this.planetContainer.drawRect(center_x, center_y - health_bar, side, 2 * health_bar);
        this.planetContainer.endFill();
    }

    drawShip(ship) {
        const side = CELL_SIZE * this.scale;
        const max_ship_health = this.replay.constants.MAX_SHIP_HEALTH;
        const health_factor = 0.1 + 0.3 * (max_ship_health - ship.health) / max_ship_health;

        const x = side * ship.x;
        const y = side * ship.y;

        this.drawCell(this.shipContainer, ship.x, ship.y, PLAYER_COLORS[ship.owner], health_factor);

        const dock_turns = this.replay.constants.DOCK_TURNS;

        if (ship.docking.status !== "undocked") {
            let progress = ship.docking.status === "docked" ? dock_turns : dock_turns - ship.docking.turns_left;

            const planetId = ship.docking.planet_id;
            const planetBase = this.replay.planets[planetId];

            const planetX = side * (planetBase.x + 0.5);
            const planetY = side * (planetBase.y + 0.5);

            const cx = x + 0.5*side;
            const cy = y + 0.5*side;

            const dx = planetX - cx;
            const dy = planetY - cy;

            this.shipContainer.beginFill(PLAYER_COLORS[ship.owner]);
            this.shipContainer.lineStyle(2, 0xFFFFFF, 1);
            if (ship.docking.status === "undocking") {
                // TODO:
            }
            else {
                progress /= dock_turns;
                this.shipContainer.moveTo(cx, cy);
                this.shipContainer.lineTo(cx + progress*dx, cy + progress*dy);
            }
            this.shipContainer.endFill();
        }
    }

    update() {
        if (this.currentSubstep.events) {
            for (let event of this.currentSubstep.events) {
                if (event.event === "destroyed") {
                    let draw = (frame) => {
                        const width = CELL_SIZE * this.scale;
                        const height = CELL_SIZE * this.scale;

                        const x = width * event.x;
                        const y = width * event.y;

                        this.shipContainer.beginFill(0xFFA500, frame / 24);
                        this.shipContainer.lineStyle(0);
                        this.shipContainer.drawRect(x, y, width, height);
                        this.shipContainer.endFill();
                    };
                    if (event.radius > 0) {
                        let r = event.radius;
                        draw = (frame) => {
                            const side = CELL_SIZE * this.scale;
                            this.planetContainer.lineStyle(0);
                            for (let dx = -r; dx <= r; dx++) {
                                for (let dy = -r; dy <= r; dy++) {
                                    if (dx*dx + dy*dy <= r*r) {
                                        const distance = (48 - frame) / 24;
                                        const x = side * (distance * dx + event.x);
                                        const y = side * (distance * dy + event.y);

                                        this.planetContainer.beginFill(0xFFA500, (frame / 48) * (1 / (1 + dx*dx + dy*dy)));
                                        this.planetContainer.drawRect(x, y, side, side);
                                        this.planetContainer.endFill();
                                    }
                                }
                            }
                        };
                    }

                    this.animationQueue.push(new FrameAnimation(
                        48, () => {}, draw,
                    ));
                }
                else if (event.event === "attack") {
                    this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;

                            const x = side * (event.x + 0.5);
                            const y = side * (event.y + 0.5);

                            this.shipContainer.lineStyle(2, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.drawCircle(x, y, side * this.replay.constants.WEAPON_RADIUS);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else if (event.event === "spawned") {
                     this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;
                            const planetX = side * (event.planet_x + 0.5);
                            const planetY = side * (event.planet_y + 0.5);
                            const ship_x = side * (event.x + 0.5);
                            const ship_y = side * (event.y + 0.5);
                            this.shipContainer.lineStyle(3, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.moveTo(planetX, planetY);
                            this.shipContainer.lineTo(ship_x, ship_y);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else {
                    console.log(event);
                }
            }
        }
    }

    draw(dt=0) {
        this.planetContainer.clear();
        this.shipContainer.clear();

        for (let planet of this.currentSubstep.planets) {
            this.drawPlanet(planet);
        }

        for (let ship of this.currentSubstep.ships) {
            this.drawShip(ship);
        }

        this.drawStats();

        // dt comes from Pixi ticker, and the unit is essentially frames
        this.animationQueue = this.animationQueue.filter((anim) => {
            anim.draw(anim.frames);
            anim.frames -= dt;
            return anim.frames > 0;
        });
    }

    drawStats() {
        let stats = this.currentStatistics;

        let x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.ships[player] || 0) / stats.total_ships;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 0, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        this.statsDisplay.beginFill(0x000000);
        this.statsDisplay.drawRect(0, 40, 800, 10);
        this.statsDisplay.endFill();
        x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.planets[player] || 0) / this.replay.planets.length;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 50, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        const width = 800 * (stats.planets["unowned"] || 0) / this.replay.planets.length;
        this.statsDisplay.beginFill(PLANET_COLOR);
        this.statsDisplay.drawRect(x, 50, width, 40);
        this.statsDisplay.endFill();
        this.statsDisplay.drawRect(0, 90, 800, 10);

        this.currentTurnDisplay.text = `Frame ${this.frame}.${this.substep}`;
    }
}

class HaliteVisualizerControls {
    constructor(replay) {
        this.visualizer = new HaliteVisualizer(replay);
    }

    attach(el) {
        $(el).empty();

        this.visualizer.attach(el);

        this.visualizer.play();
    }
}

function setupUpload() {
    const dragPopup = $(`<div class="halite-dnd-popup">`).appendTo(document.body);
    dragPopup.append($("
Drop to visualize
"));

    const visualizerEl = $("
").appendTo(document.body);

    document.body.addEventListener("dragenter", (e) => {
        e.stopPropagation();
        e.preventDefault();

        dragPopup.removeClass("hidden");
    });
    document.body.addEventListener("dragover", (e) => {
        e.stopPropagation();
        e.preventDefault();
    });
    document.body.addEventListener("dragexit", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");
    });

    document.body.addEventListener("drop", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");

        let dt = e.dataTransfer;
        let files = dt.files;

        let reader = new FileReader();
        reader.onload = function(e) {
            const replay = msgpack.decode(new Uint8Array(e.target.result));

            console.log(replay);

            let visualizer = new HaliteVisualizerControls(replay);
            visualizer.attach(visualizerEl);
        };
        reader.readAsArrayBuffer(files[0]);
    });
}

function setup() {
    setupUpload();
}
<commit_msg>Visualize undocking progress as well<commit_after>/**
 * Created by lidavidm on 6/6/17.
 */

const CELL_SIZE = 1;
const PLAYER_COLORS = [0xFF704B, 0x9010B9, 0x005DD0, 0x00B553];
const PLANET_COLOR = 0x888888;

class FrameAnimation {
    constructor(frames, update, draw) {
        this.frames = frames;
        this.update = update;
        this.draw = draw;
    }
}

class HaliteVisualizer {
    constructor(replay) {
        this.replay = replay;
        this.frame = 0;
        this.substep = 0;
        this.application = new PIXI.Application(
            800, 100 + 800 * (this.replay.height / this.replay.width));

        this.scale = 800 / Math.max(replay.width, replay.height);

        this.planetContainer = new PIXI.Graphics();
        this.shipContainer = new PIXI.Graphics();
        this.container = new PIXI.Container();
        this.container.position.set(0, 100);
        this.container.addChild(this.planetContainer, this.shipContainer);

        this.statsDisplay = new PIXI.Graphics();

        this.currentTurnDisplay = new PIXI.Text("Frame");

        this.application.stage.addChild(this.container);
        this.application.stage.addChild(this.statsDisplay);
        this.application.stage.addChild(this.currentTurnDisplay);

        this.timer = null;

        this.animationQueue = [];
    }

    get currentSubstep() {
        return this.replay.frames[this.frame][this.substep];
    }

    get currentStatistics() {
        let substep = this.currentSubstep;
        let planets = { "unowned": 0 };
        let ships = {};
        let total_ships = 0;

        for (let planet of substep.planets) {
            if (planet.owner !== null) {
                if (typeof planets[planet.owner] === "undefined") {
                    planets[planet.owner] = 0;
                }
                planets[planet.owner]++;
            }
            else {
                planets["unowned"]++;
            }
        }

        for (let ship of substep.ships) {
            if (typeof ships[ship.owner] === "undefined") {
                ships[ship.owner] = 0;
            }
            ships[ship.owner]++;
            total_ships++;
        }

        return {
            "planets": planets,
            "ships": ships,
            "total_ships": total_ships,
        }
    }

    attach(containerEl) {
        $(containerEl).append(this.application.view);

        this.draw();
    }

    play() {
        if (this.timer) return;

        this.timer = window.setInterval(() => {
            for (let i = 0; i < 3; i++) {
                this.substep++;
                if (this.substep >= this.replay.frames[this.frame].length) {
                    this.substep = 0;
                    this.frame++;
                }

                if (this.frame >= this.replay.frames.length) {
                    this.pause();
                    this.frame = this.replay.frames.length - 1;
                    this.substep = this.replay.frames[this.frame].length - 1;
                    break;
                }

                this.update();
            }
        }, 1000/30);

        this.application.ticker.add((dt) => {
            this.draw(dt);
        });
    }

    pause() {
        if (!this.timer) return;

        window.clearInterval(this.timer);
        this.timer = null;
    }

    /**
     *
     * @param container
     * @param x
     * @param y
     * @param color
     * @param health_factor
     */
    drawCell(container, x, y, color, health_factor) {
        const side = CELL_SIZE * this.scale;
        x = x * side;
        y = y * side;
        container.lineStyle(0);
        container.beginFill(color, 0.5);
        container.drawRect(x, y, side, side);
        container.endFill();
        container.beginFill(color, 1);
        container.drawRect(
            x + health_factor * side,
            y + health_factor * side,
            (1 - 2*health_factor) * side,
            (1 - 2*health_factor) * side);
        container.endFill();
    }

    drawPlanet(planet) {
        let planetBase = this.replay.planets[planet.id];

        const side = CELL_SIZE * this.scale;

        this.planetContainer.lineStyle(0);
        for (let dx = -planetBase.r; dx <= planetBase.r; dx++) {
            for (let dy = -planetBase.r; dy <= planetBase.r; dy++) {
                if (dx*dx + dy*dy <= planetBase.r*planetBase.r) {
                    const health_factor = 0.5 *
                        (planetBase.production - planet.remaining_production) / planetBase.production;
                    this.drawCell(
                        this.planetContainer,
                        dx + planetBase.x,
                        dy + planetBase.y,
                        planet.owner === null ? PLANET_COLOR : PLAYER_COLORS[planet.owner],
                        health_factor,
                    );
                }
            }
        }

        const center_x = side * planetBase.x;
        const center_y = side * (planetBase.y + 0.5);

        const health_factor = planet.health / planetBase.health;
        const health_bar = health_factor * side * (planetBase.r - 1);
        this.planetContainer.beginFill(0xFF0000);
        this.planetContainer.lineStyle(2, 0x000000);
        this.planetContainer.drawRect(center_x, center_y - health_bar, side, 2 * health_bar);
        this.planetContainer.endFill();
    }

    drawShip(ship) {
        const side = CELL_SIZE * this.scale;
        const max_ship_health = this.replay.constants.MAX_SHIP_HEALTH;
        const health_factor = 0.1 + 0.3 * (max_ship_health - ship.health) / max_ship_health;

        const x = side * ship.x;
        const y = side * ship.y;

        this.drawCell(this.shipContainer, ship.x, ship.y, PLAYER_COLORS[ship.owner], health_factor);

        const dock_turns = this.replay.constants.DOCK_TURNS;

        if (ship.docking.status !== "undocked") {
            let progress = ship.docking.status === "docked" ? dock_turns : dock_turns - ship.docking.turns_left;
            if (ship.docking.status === "undocking") {
                progress = ship.docking.turns_left / dock_turns;
            }
            else {
                progress /= dock_turns;
            }

            const planetId = ship.docking.planet_id;
            const planetBase = this.replay.planets[planetId];

            const planetX = side * (planetBase.x + 0.5);
            const planetY = side * (planetBase.y + 0.5);

            const cx = x + 0.5*side;
            const cy = y + 0.5*side;

            const dx = planetX - cx;
            const dy = planetY - cy;

            this.shipContainer.beginFill(PLAYER_COLORS[ship.owner]);
            this.shipContainer.lineStyle(2, 0xFFFFFF, 1);
            this.shipContainer.moveTo(cx, cy);
            this.shipContainer.lineTo(cx + progress*dx, cy + progress*dy);
            this.shipContainer.endFill();
        }
    }

    update() {
        if (this.currentSubstep.events) {
            for (let event of this.currentSubstep.events) {
                if (event.event === "destroyed") {
                    let draw = (frame) => {
                        const width = CELL_SIZE * this.scale;
                        const height = CELL_SIZE * this.scale;

                        const x = width * event.x;
                        const y = width * event.y;

                        this.shipContainer.beginFill(0xFFA500, frame / 24);
                        this.shipContainer.lineStyle(0);
                        this.shipContainer.drawRect(x, y, width, height);
                        this.shipContainer.endFill();
                    };
                    if (event.radius > 0) {
                        let r = event.radius;
                        draw = (frame) => {
                            const side = CELL_SIZE * this.scale;
                            this.planetContainer.lineStyle(0);
                            for (let dx = -r; dx <= r; dx++) {
                                for (let dy = -r; dy <= r; dy++) {
                                    if (dx*dx + dy*dy <= r*r) {
                                        const distance = (48 - frame) / 24;
                                        const x = side * (distance * dx + event.x);
                                        const y = side * (distance * dy + event.y);

                                        this.planetContainer.beginFill(0xFFA500, (frame / 48) * (1 / (1 + dx*dx + dy*dy)));
                                        this.planetContainer.drawRect(x, y, side, side);
                                        this.planetContainer.endFill();
                                    }
                                }
                            }
                        };
                    }

                    this.animationQueue.push(new FrameAnimation(
                        48, () => {}, draw,
                    ));
                }
                else if (event.event === "attack") {
                    this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;

                            const x = side * (event.x + 0.5);
                            const y = side * (event.y + 0.5);

                            this.shipContainer.lineStyle(2, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.drawCircle(x, y, side * this.replay.constants.WEAPON_RADIUS);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else if (event.event === "spawned") {
                     this.animationQueue.push(new FrameAnimation(
                        24,
                        () => {
                        },
                        (frame) => {
                            const side = CELL_SIZE * this.scale;
                            const planetX = side * (event.planet_x + 0.5);
                            const planetY = side * (event.planet_y + 0.5);
                            const ship_x = side * (event.x + 0.5);
                            const ship_y = side * (event.y + 0.5);
                            this.shipContainer.lineStyle(3, PLAYER_COLORS[event.entity.owner], 0.5 * frame / 24);
                            this.shipContainer.moveTo(planetX, planetY);
                            this.shipContainer.lineTo(ship_x, ship_y);
                            this.shipContainer.endFill();
                        },
                    ));
                }
                else {
                    console.log(event);
                }
            }
        }
    }

    draw(dt=0) {
        this.planetContainer.clear();
        this.shipContainer.clear();

        for (let planet of this.currentSubstep.planets) {
            this.drawPlanet(planet);
        }

        for (let ship of this.currentSubstep.ships) {
            this.drawShip(ship);
        }

        this.drawStats();

        // dt comes from Pixi ticker, and the unit is essentially frames
        this.animationQueue = this.animationQueue.filter((anim) => {
            anim.draw(anim.frames);
            anim.frames -= dt;
            return anim.frames > 0;
        });
    }

    drawStats() {
        let stats = this.currentStatistics;

        let x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.ships[player] || 0) / stats.total_ships;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 0, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        this.statsDisplay.beginFill(0x000000);
        this.statsDisplay.drawRect(0, 40, 800, 10);
        this.statsDisplay.endFill();
        x = 0;
        for (let player = 0; player < this.replay.num_players; player++) {
            const width = 800 * (stats.planets[player] || 0) / this.replay.planets.length;
            this.statsDisplay.beginFill(PLAYER_COLORS[player]);
            this.statsDisplay.drawRect(x, 50, width, 40);
            this.statsDisplay.endFill();
            x += width;
        }
        const width = 800 * (stats.planets["unowned"] || 0) / this.replay.planets.length;
        this.statsDisplay.beginFill(PLANET_COLOR);
        this.statsDisplay.drawRect(x, 50, width, 40);
        this.statsDisplay.endFill();
        this.statsDisplay.drawRect(0, 90, 800, 10);

        this.currentTurnDisplay.text = `Frame ${this.frame}.${this.substep}`;
    }
}

class HaliteVisualizerControls {
    constructor(replay) {
        this.visualizer = new HaliteVisualizer(replay);
    }

    attach(el) {
        $(el).empty();

        this.visualizer.attach(el);

        this.visualizer.play();
    }
}

function setupUpload() {
    const dragPopup = $(`<div class="halite-dnd-popup">`).appendTo(document.body);
    dragPopup.append($("
Drop to visualize
"));

    const visualizerEl = $("
").appendTo(document.body);

    document.body.addEventListener("dragenter", (e) => {
        e.stopPropagation();
        e.preventDefault();

        dragPopup.removeClass("hidden");
    });
    document.body.addEventListener("dragover", (e) => {
        e.stopPropagation();
        e.preventDefault();
    });
    document.body.addEventListener("dragexit", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");
    });

    document.body.addEventListener("drop", (e) => {
        e.stopPropagation();
        e.preventDefault();
        dragPopup.addClass("hidden");

        let dt = e.dataTransfer;
        let files = dt.files;

        let reader = new FileReader();
        reader.onload = function(e) {
            const replay = msgpack.decode(new Uint8Array(e.target.result));

            console.log(replay);

            let visualizer = new HaliteVisualizerControls(replay);
            visualizer.attach(visualizerEl);
        };
        reader.readAsArrayBuffer(files[0]);
    });
}

function setup() {
    setupUpload();
}
<|endoftext|>


<commit_before>; (function () {
    var vueForm = {};
    vueForm.install = function (Vue) {

        function closest(elem, selector) {
            var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector;
            while (elem) {
                if (matchesSelector.call(elem, selector)) {
                    return elem;
                } else {
                    elem = elem.parentElement;
                }
            }
            return null;
        }

        function removeClassWithPrefix(el, prefix) {
            var classes = el.className.split(" ").filter(function(c) {
                return c.lastIndexOf(prefix, 0) !== 0;
            });
            el.className = (classes.join(" ")).trim();
        }

        var emailRegExp = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/,
            urlRegExp = /^(http\:\/\/|https\:\/\/)(.{4,})$/,
            dirtyClass = 'vf-dirty',
            pristineClass = 'vf-pristine',
            validClass = 'vf-valid',
            invalidClass = 'vf-invalid',
            submittedClass = 'vf-submitted',
            attrs = [
                'type',
                'required',
                'pattern',
                'multiple',
                'minlength',
                'maxlength',
                'min',
                'max',
                'custom-validator'
            ],
            attrsWithValue = [
                'minlength',
                'maxlength',
                'min',
                'max',
                'pattern'
            ];

        var validators = {
            required: function (value) {
                if (Vue.util.isArray(value)) {
                    return !!value.length;
                }
                return !!value;
            },
            email: function (value, multiple) {
                if (typeof value === 'undefined') {
                    return true;
                }
                if (!value.trim()) {
                    return true;
                }
                return emailRegExp.test(value);
            },
            number: function (value) {
                return !isNaN(value);
            },
            url: function (value) {
                return urlRegExp.test(value);
            },
            minlength: function (value, length) {
                return value.length >= length;
            },
            maxlength: function (value, length) {
                return length >= value.length;
            },
            pattern: function (value, pattern) {
                var patternRegExp = new RegExp('^' + pattern + '$');
                return patternRegExp.test(value);
            },
            min: function (value, min) {
                return value >= min;
            },
            max: function (value, max) {
                return max >= value;
            }
        };

        // check if an attribute exists, static or binding.
        // if it is a binding, watch it and re-validate on change
        function checkAttribute($this, attribute) {
            var vueFormCtrl = $this._vueFormCtrl;
            var binding = Vue.util.getBindAttr($this.el, attribute);
            if (binding) {
                $this.vm.$watch(binding, function (value, oldValue) {
                    vueFormCtrl[attribute] = value;
                    if (attribute === 'type') {
                        delete vueFormCtrl.validators[oldValue];
                        vueFormCtrl.validators[value] = validators[value];
                    } else {
                        vueFormCtrl.validators[attribute] = validators[attribute];
                        if (value === false || typeof value === 'undefined') {
                            vueFormCtrl.validators[attribute] = false;
                        }
                    }
                    if($this._vueForm) {
                        vueFormCtrl.validate($this._value);
                    } else {
                        // this is for when an input is inside a v-if
                        // and will not be inserted into the dom for
                        // some time
                        Vue.nextTick(function () {
                            Vue.nextTick(function () {
                                vueFormCtrl.validate($this._value);
                            });
                        });
                    }
                }, { immediate: true });
            }
            var staticAttr = $this.el.getAttribute(attribute);
            if (staticAttr !== null) {
                vueFormCtrl[attribute] = staticAttr || true;
                if (attribute === 'type') {
                    vueFormCtrl.validators[staticAttr] = validators[staticAttr];
                } else if (attribute === 'custom-validator') {
                    vueFormCtrl.validators[attribute] = $this.vm[staticAttr];
                } else {
                    vueFormCtrl.validators[attribute] = validators[attribute];
                }
            }

        }

        Vue.directive('form', {
            id: 'form',
            priority: 10001,
            bind: function () {
                var  el = this.el,
                    formName = el.getAttribute('name'),
                    vm = this.vm,
                    self = this,
                    controls = {};

                el.noValidate = true;

                var state = this._state = {
                    $name: formName,
                    $dirty: false,
                    $pristine: true,
                    $valid: true,
                    $invalid: false,
                    $submitted: false,
                    $error: {}
                };

                // set inital state
                vm.$set(formName, state);
                Vue.util.addClass(el, pristineClass);
                Vue.util.addClass(el, validClass);

                var vueForm = this.el._vueForm = {
                    name: formName,
                    state: state,
                    addControl: function (ctrl) {
                        controls[ctrl.name] = ctrl;
                    },
                    removeControl: function (ctrl) {
                        this.removeError(ctrl.name);
                        delete controls[ctrl.name];
                        this.checkValidity();
                    },
                    setData: function (key, data) {
                        vm.$set(formName + '.' + key, data);
                    },
                    removeError: function (key) {
                        state.$error[key] = false;
                        delete state.$error[key];
                    },
                    checkValidity: function () {
                        var isValid = true;
                        Object.keys(controls).forEach(function (ctrl) {
                            if (controls[ctrl].state.$invalid) {
                                isValid = false;
                            }
                        });
                        this.setValidity(isValid);
                    },
                    setValidity: function (isValid) {
                        state.$valid = isValid;
                        state.$invalid = !isValid;
                        if (isValid) {
                            Vue.util.addClass(el, validClass);
                            Vue.util.removeClass(el, invalidClass);
                            removeClassWithPrefix(el, invalidClass + '-');
                        } else {
                            Vue.util.removeClass(el, validClass);
                            Vue.util.addClass(el, invalidClass);
                        }
                    },
                    setDirty: function () {
                        state.$dirty = true;
                        state.$pristine = false;
                        Vue.util.addClass(el, dirtyClass);
                        Vue.util.removeClass(el, pristineClass);
                    },
                    setPristine: function () {
                        state.$dirty = false;
                        state.$pristine = true;
                        Object.keys(controls).forEach(function (ctrl) {
                            controls[ctrl].setPristine();
                        });
                        vueForm.setSubmitted(false);
                        Vue.util.removeClass(el, dirtyClass);
                        Vue.util.addClass(el, pristineClass);
                    },
                    setSubmitted: function (isSubmitted) {
                        state.$submitted = isSubmitted;
                    }
                };

                this._submitEvent = function () {
                    vueForm.setSubmitted(true);
                };
                Vue.util.on(el, 'submit', this._submitEvent);
            },
            update: function () {

            },
            unbind: function () {
                Vue.util.off(this.el, 'submit', this._submitEvent);
                delete this.el._vueForm;
            }
        });

        Vue.directive('formCtrl', {
            id: 'formCtrl',
            priority: 10000,
            bind: function () {
                var inputName = this.el.getAttribute('name'),
                    vModel = this.el.getAttribute('v-model'),
                    vm = this.vm,
                    el = this.el,
                    self = this,
                    vueForm;

                if (!inputName) {
                    console.warn('Name attribute must be populated');
                    return;
                }

                var state = self._state = {
                    $name: inputName,
                    $dirty: false,
                    $pristine: true,
                    $valid: true,
                    $invalid: false,
                    $error: {}
                };

                var vueFormCtrl = el._vueFormCtrl = self._vueFormCtrl = {
                    el: el,
                    name: inputName,
                    state: state,
                    setVadility: function (key, isValid) {
                        var vueForm = self._vueForm;
                        state.$valid = isValid;
                        state.$invalid = !isValid;
                        if (isValid) {
                            vueForm.setData(inputName + '.$error.' + key, false);
                            delete state.$error[key];
                            vueForm.removeError(inputName);
                            Vue.util.addClass(el, validClass);
                            Vue.util.removeClass(el, invalidClass);
                            removeClassWithPrefix(el, invalidClass + '-');
                        } else {
                            vueForm.setData(inputName + '.$error.' + key, true);
                            vueForm.setData('$error.' + inputName, state);
                            Vue.util.removeClass(el, validClass);
                            Vue.util.addClass(el, invalidClass);
                            Vue.util.addClass(el, invalidClass + '-' + key);
                        }
                        vueForm.checkValidity();
                    },
                    setDirty: function () {
                        state.$dirty = true;
                        state.$pristine = false;
                        self._vueForm.setDirty();
                        Vue.util.addClass(el, dirtyClass);
                        Vue.util.removeClass(el, pristineClass);
                    },
                    setPristine: function () {
                        state.$dirty = false;
                        state.$pristine = true;
                        Vue.util.removeClass(el, dirtyClass);
                        Vue.util.addClass(el, pristineClass);
                    },
                    validators: {},
                    error: {},
                    validate: function (value) {
                        var isValid = true,
                            self = this;

                        Object.keys(this.validators).forEach(function (validator) {
                            var args = [value];

                            if (self.validators[validator] === false) {
                                self.setVadility(validator, true);
                                return;
                            }

                            if (!self.validators[validator]) {
                                return;
                            }

                            if (validator === 'email') {
                                args.push(self.multiple);
                            } else if (attrsWithValue.indexOf(validator) !== -1) {
                                args.push(self[validator]);
                            }

                            if (!self.validators[validator].apply(this, args)) {
                                isValid = false;
                                self.setVadility(validator, false);
                            } else {
                                self.setVadility(validator, true);
                            }

                        });

                        return isValid;
                    }
                };

                // add to validators depending on element attributes
                attrs.forEach(function (attr) {
                    checkAttribute(self, attr);
                });

                // find parent form
                var form;
                if(el.form) {
                    init(el.form._vueForm);
                } else {
                    // this is either a non form element node
                    // or a detached node (inside v-if)
                    form = closest(el, 'form[name]');
                    if(form && form._vueForm) {
                        init(form._vueForm);
                    } else {
                        // must be detached
                        Vue.nextTick(function () {
                            form = el.form || closest(el, 'form[name]');
                            init(form._vueForm);
                        });
                    }
                }

                function init(vueForm) {
                    if(!vueForm) {
                        return;
                    }
                    self._vueForm = vueForm;

                    // register the form control
                    vueForm.addControl(vueFormCtrl);

                    // set inital state
                    vueForm.setData(inputName, state);
                    Vue.util.addClass(el, pristineClass);
                    Vue.util.addClass(el, validClass);

                    var first = true;
                    if (vModel) {
                        self.vm.$watch(vModel, function (value, oldValue) {
                            if (!first) {
                                vueFormCtrl.setDirty();
                            }
                            first = false;
                            vueFormCtrl.validate(value);
                            self._value = value;
                        }, { immediate: true });
                    }

                };

            },
            update: function (value, oldValue) {
                //if (typeof oldValue !== 'undefined') {
                this._vueFormCtrl.setDirty();
                //}
                this._vueFormCtrl.validate(value);
                this._value = value;
            },
            unbind: function () {
                this._vueForm.removeControl(this._vueFormCtrl);
                delete this.el._vueFormCtrl;
            }
        });

    }

if (typeof exports == "object") {
    module.exports = vueForm;
} else if (typeof define == "function" && define.amd) {
    define([], function () { return vueForm });
} else if (window.Vue) {
    window.vueForm = vueForm;
    Vue.use(vueForm);
}

})();<commit_msg>Add / remove submitted classes<commit_after>; (function () {
    var vueForm = {};
    vueForm.install = function (Vue) {

        function closest(elem, selector) {
            var matchesSelector = elem.matches || elem.webkitMatchesSelector || elem.mozMatchesSelector || elem.msMatchesSelector;
            while (elem) {
                if (matchesSelector.call(elem, selector)) {
                    return elem;
                } else {
                    elem = elem.parentElement;
                }
            }
            return null;
        }

        function removeClassWithPrefix(el, prefix) {
            var classes = el.className.split(" ").filter(function(c) {
                return c.lastIndexOf(prefix, 0) !== 0;
            });
            el.className = (classes.join(" ")).trim();
        }

        var emailRegExp = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/,
            urlRegExp = /^(http\:\/\/|https\:\/\/)(.{4,})$/,
            dirtyClass = 'vf-dirty',
            pristineClass = 'vf-pristine',
            validClass = 'vf-valid',
            invalidClass = 'vf-invalid',
            submittedClass = 'vf-submitted',
            attrs = [
                'type',
                'required',
                'pattern',
                'multiple',
                'minlength',
                'maxlength',
                'min',
                'max',
                'custom-validator'
            ],
            attrsWithValue = [
                'minlength',
                'maxlength',
                'min',
                'max',
                'pattern'
            ];

        var validators = {
            required: function (value) {
                if (Vue.util.isArray(value)) {
                    return !!value.length;
                }
                return !!value;
            },
            email: function (value, multiple) {
                if (typeof value === 'undefined') {
                    return true;
                }
                if (!value.trim()) {
                    return true;
                }
                return emailRegExp.test(value);
            },
            number: function (value) {
                return !isNaN(value);
            },
            url: function (value) {
                return urlRegExp.test(value);
            },
            minlength: function (value, length) {
                return value.length >= length;
            },
            maxlength: function (value, length) {
                return length >= value.length;
            },
            pattern: function (value, pattern) {
                var patternRegExp = new RegExp('^' + pattern + '$');
                return patternRegExp.test(value);
            },
            min: function (value, min) {
                return value >= min;
            },
            max: function (value, max) {
                return max >= value;
            }
        };

        // check if an attribute exists, static or binding.
        // if it is a binding, watch it and re-validate on change
        function checkAttribute($this, attribute) {
            var vueFormCtrl = $this._vueFormCtrl;
            var binding = Vue.util.getBindAttr($this.el, attribute);
            if (binding) {
                $this.vm.$watch(binding, function (value, oldValue) {
                    vueFormCtrl[attribute] = value;
                    if (attribute === 'type') {
                        delete vueFormCtrl.validators[oldValue];
                        vueFormCtrl.validators[value] = validators[value];
                    } else {
                        vueFormCtrl.validators[attribute] = validators[attribute];
                        if (value === false || typeof value === 'undefined') {
                            vueFormCtrl.validators[attribute] = false;
                        }
                    }
                    if($this._vueForm) {
                        vueFormCtrl.validate($this._value);
                    } else {
                        // this is for when an input is inside a v-if
                        // and will not be inserted into the dom for
                        // some time
                        Vue.nextTick(function () {
                            Vue.nextTick(function () {
                                vueFormCtrl.validate($this._value);
                            });
                        });
                    }
                }, { immediate: true });
            }
            var staticAttr = $this.el.getAttribute(attribute);
            if (staticAttr !== null) {
                vueFormCtrl[attribute] = staticAttr || true;
                if (attribute === 'type') {
                    vueFormCtrl.validators[staticAttr] = validators[staticAttr];
                } else if (attribute === 'custom-validator') {
                    vueFormCtrl.validators[attribute] = $this.vm[staticAttr];
                } else {
                    vueFormCtrl.validators[attribute] = validators[attribute];
                }
            }

        }

        Vue.directive('form', {
            id: 'form',
            priority: 10001,
            bind: function () {
                var  el = this.el,
                    formName = el.getAttribute('name'),
                    vm = this.vm,
                    self = this,
                    controls = {};

                el.noValidate = true;

                var state = this._state = {
                    $name: formName,
                    $dirty: false,
                    $pristine: true,
                    $valid: true,
                    $invalid: false,
                    $submitted: false,
                    $error: {}
                };

                // set inital state
                vm.$set(formName, state);
                Vue.util.addClass(el, pristineClass);
                Vue.util.addClass(el, validClass);

                var vueForm = this.el._vueForm = {
                    name: formName,
                    state: state,
                    addControl: function (ctrl) {
                        controls[ctrl.name] = ctrl;
                    },
                    removeControl: function (ctrl) {
                        this.removeError(ctrl.name);
                        delete controls[ctrl.name];
                        this.checkValidity();
                    },
                    setData: function (key, data) {
                        vm.$set(formName + '.' + key, data);
                    },
                    removeError: function (key) {
                        state.$error[key] = false;
                        delete state.$error[key];
                    },
                    checkValidity: function () {
                        var isValid = true;
                        Object.keys(controls).forEach(function (ctrl) {
                            if (controls[ctrl].state.$invalid) {
                                isValid = false;
                            }
                        });
                        this.setValidity(isValid);
                    },
                    setValidity: function (isValid) {
                        state.$valid = isValid;
                        state.$invalid = !isValid;
                        if (isValid) {
                            Vue.util.addClass(el, validClass);
                            Vue.util.removeClass(el, invalidClass);
                            removeClassWithPrefix(el, invalidClass + '-');
                        } else {
                            Vue.util.removeClass(el, validClass);
                            Vue.util.addClass(el, invalidClass);
                        }
                    },
                    setDirty: function () {
                        state.$dirty = true;
                        state.$pristine = false;
                        Vue.util.addClass(el, dirtyClass);
                        Vue.util.removeClass(el, pristineClass);
                    },
                    setPristine: function () {
                        state.$dirty = false;
                        state.$pristine = true;
                        Object.keys(controls).forEach(function (ctrl) {
                            controls[ctrl].setPristine();
                        });
                        vueForm.setSubmitted(false);
                        Vue.util.removeClass(el, dirtyClass);
                        Vue.util.addClass(el, pristineClass);
                    },
                    setSubmitted: function (isSubmitted) {
                        state.$submitted = isSubmitted;
                        if(isSubmitted) {
                            Vue.util.addClass(el, submittedClass);
                        } else {
                            Vue.util.removeClass(el, submittedClass);
                        }
                    }
                };

                this._submitEvent = function () {
                    vueForm.setSubmitted(true);
                };
                Vue.util.on(el, 'submit', this._submitEvent);
            },
            update: function () {

            },
            unbind: function () {
                Vue.util.off(this.el, 'submit', this._submitEvent);
                delete this.el._vueForm;
            }
        });

        Vue.directive('formCtrl', {
            id: 'formCtrl',
            priority: 10000,
            bind: function () {
                var inputName = this.el.getAttribute('name'),
                    vModel = this.el.getAttribute('v-model'),
                    vm = this.vm,
                    el = this.el,
                    self = this,
                    vueForm;

                if (!inputName) {
                    console.warn('Name attribute must be populated');
                    return;
                }

                var state = self._state = {
                    $name: inputName,
                    $dirty: false,
                    $pristine: true,
                    $valid: true,
                    $invalid: false,
                    $error: {}
                };

                var vueFormCtrl = el._vueFormCtrl = self._vueFormCtrl = {
                    el: el,
                    name: inputName,
                    state: state,
                    setVadility: function (key, isValid) {
                        var vueForm = self._vueForm;
                        state.$valid = isValid;
                        state.$invalid = !isValid;
                        if (isValid) {
                            vueForm.setData(inputName + '.$error.' + key, false);
                            delete state.$error[key];
                            vueForm.removeError(inputName);
                            Vue.util.addClass(el, validClass);
                            Vue.util.removeClass(el, invalidClass);
                            removeClassWithPrefix(el, invalidClass + '-');
                        } else {
                            vueForm.setData(inputName + '.$error.' + key, true);
                            vueForm.setData('$error.' + inputName, state);
                            Vue.util.removeClass(el, validClass);
                            Vue.util.addClass(el, invalidClass);
                            Vue.util.addClass(el, invalidClass + '-' + key);
                        }
                        vueForm.checkValidity();
                    },
                    setDirty: function () {
                        state.$dirty = true;
                        state.$pristine = false;
                        self._vueForm.setDirty();
                        Vue.util.addClass(el, dirtyClass);
                        Vue.util.removeClass(el, pristineClass);
                    },
                    setPristine: function () {
                        state.$dirty = false;
                        state.$pristine = true;
                        Vue.util.removeClass(el, dirtyClass);
                        Vue.util.addClass(el, pristineClass);
                    },
                    validators: {},
                    error: {},
                    validate: function (value) {
                        var isValid = true,
                            self = this;

                        Object.keys(this.validators).forEach(function (validator) {
                            var args = [value];

                            if (self.validators[validator] === false) {
                                self.setVadility(validator, true);
                                return;
                            }

                            if (!self.validators[validator]) {
                                return;
                            }

                            if (validator === 'email') {
                                args.push(self.multiple);
                            } else if (attrsWithValue.indexOf(validator) !== -1) {
                                args.push(self[validator]);
                            }

                            if (!self.validators[validator].apply(this, args)) {
                                isValid = false;
                                self.setVadility(validator, false);
                            } else {
                                self.setVadility(validator, true);
                            }

                        });

                        return isValid;
                    }
                };

                // add to validators depending on element attributes
                attrs.forEach(function (attr) {
                    checkAttribute(self, attr);
                });

                // find parent form
                var form;
                if(el.form) {
                    init(el.form._vueForm);
                } else {
                    // this is either a non form element node
                    // or a detached node (inside v-if)
                    form = closest(el, 'form[name]');
                    if(form && form._vueForm) {
                        init(form._vueForm);
                    } else {
                        // must be detached
                        Vue.nextTick(function () {
                            form = el.form || closest(el, 'form[name]');
                            init(form._vueForm);
                        });
                    }
                }

                function init(vueForm) {
                    if(!vueForm) {
                        return;
                    }
                    self._vueForm = vueForm;

                    // register the form control
                    vueForm.addControl(vueFormCtrl);

                    // set inital state
                    vueForm.setData(inputName, state);
                    Vue.util.addClass(el, pristineClass);
                    Vue.util.addClass(el, validClass);

                    var first = true;
                    if (vModel) {
                        self.vm.$watch(vModel, function (value, oldValue) {
                            if (!first) {
                                vueFormCtrl.setDirty();
                            }
                            first = false;
                            vueFormCtrl.validate(value);
                            self._value = value;
                        }, { immediate: true });
                    }

                };

            },
            update: function (value, oldValue) {
                //if (typeof oldValue !== 'undefined') {
                this._vueFormCtrl.setDirty();
                //}
                this._vueFormCtrl.validate(value);
                this._value = value;
            },
            unbind: function () {
                this._vueForm.removeControl(this._vueFormCtrl);
                delete this.el._vueFormCtrl;
            }
        });

    }

if (typeof exports == "object") {
    module.exports = vueForm;
} else if (typeof define == "function" && define.amd) {
    define([], function () { return vueForm });
} else if (window.Vue) {
    window.vueForm = vueForm;
    Vue.use(vueForm);
}

})();<|endoftext|>