<commit_before>//= require import

/* global miqSparkleOn miqSparkleOff showSuccessMessage showErrorMessage showWarningMessage clearMessages */

var Automate = {
  getAndRenderAutomateJson: function(importFileUploadId, message) {
    $('.hidden-import-file-upload-id').val(importFileUploadId);

    $.getJSON("automate_json?import_file_upload_id=" + importFileUploadId)
      .done(function(rows_json) {
        Automate.addDomainOptions(rows_json);
        Automate.setupInitialTree(rows_json);

        $('select.importing-domains').change(function() {
          Automate.importingDomainsChangeHandler(rows_json);
        });

        $('#import_file_upload_id').val(importFileUploadId);
        $('.import-data').show();
        $('.import-or-export').hide();
        showSuccessMessage(JSON.parse(message).message);
      })
      .fail(function(failedMessage) {
        var messageData = JSON.parse(failedMessage.responseText);

        if (messageData.level == 'warning') {
          showWarningMessage(messageData.message);
        } else {
          showErrorMessage(messageData.message);
        }
      });
  },

  renderGitImport: function(branches, tags, gitRepoId, messages) {
    clearMessages();
    message = JSON.parse(messages).message;
    messageLevel = JSON.parse(messages).level;

    if (messageLevel === "error") {
      showErrorMessage(message);
    } else {
      $('.hidden-git-repo-id').val(gitRepoId);
      $('.git-import-data').show();
      $('.import-or-export').hide();
      if (messageLevel === "warning") {
        showWarningMessage(message);
      } else {
        showSuccessMessage(message);
      }

      var addToDropDown = function(identifier, child) {
        $('select.git-' + identifier).append(
          $('<option>', {
            value: child,
            text: child
          })
        );
      };

      $.each(JSON.parse(branches), function(index, child) {
        addToDropDown('branches', child);
      });
      $.each(JSON.parse(tags), function(index, child) {
        addToDropDown('tags', child);
      });

      $('select.git-branches').selectpicker('refresh');
      $('select.git-tags').selectpicker('refresh');
    }

    miqSparkleOff();
  },

  addDomainOptions: function(domains) {
    $('select.importing-domains').empty();

    $.each(domains, function(_index, child) {
      $('select.importing-domains').append(
        $('<option>', {
          value: child.key,
          text:  child.text
        })
      );
    });

    $('select.importing-domains').selectpicker('refresh');
  },

  setupInitialTree: function(domains) {
    $('.domain-tree').treeview({
      data:              domains[0].nodes,
      levels:            1,
      showCheckbox:      true,
      showImage:         true,
      hierarchicalCheck: true,
      expandIcon:        'fa fa-fw fa-chevron-right',
      collapseIcon:      'fa fa-fw fa-chevron-down'
    });
  },

  importingDomainsChangeHandler: function(domains) {
    $.each(domains, function(_index, child) {
      if ($('select.importing-domains').val() === child.key) {
        $('.domain-tree').treeview({
          data:              child.nodes,
          levels:            1,
          showCheckbox:      true,
          showImage:         true,
          hierarchicalCheck: true,
          expandIcon:        'fa fa-fw fa-chevron-right',
          collapseIcon:      'fa fa-fw fa-chevron-down'
        });
      }
    });

    $('#toggle-all').prop('checked', false);
  },

  setUpAutomateImportClickHandlers: function() {
    var tearDownGitImportOptions = function() {
      $('select.git-branches').find('option').remove().end();
      $('select.git-tags').find('option').remove().end();
      $('select.git-branches').selectpicker('refresh');
      $('select.git-tags').selectpicker('refresh');

      $('.import-or-export').show();
      $('.git-import-data').hide();
      $('#git-url-import').prop('disabled', null);
    };

    $('.import-commit').click(function(event) {
      event.preventDefault();
      miqSparkleOn();
      clearMessages();

      var postData = $('#import-form').serialize();
      postData += '&';

      var treeName = $('.domain-tree').attr('name');
      var serialized = $.param($('.domain-tree').treeview(true).getChecked().map(function (node) {
        return {name: treeName, value: node.key}
      }));
      postData = postData.concat(serialized);

      $.post('import_automate_datastore', postData, function(data) {
        var flashMessage = JSON.parse(data)[0];
        if (flashMessage.level == 'error') {
          showErrorMessage(flashMessage.message);
        } else {
          showSuccessMessage(flashMessage.message);
        }

        miqSparkleOff();
      });
    });

    Automate.setUpGitRefreshClickHandlers();

    $('.git-retreive-datastore').click(function() {
      miqSparkleOn();
      $('.git-import-submit').prop('disabled', true);
    });

    $('.git-import-submit').click(function(event) {
      event.preventDefault();
      miqSparkleOn();
      clearMessages();

      $.post('import_via_git', $('#git-branch-tag-form').serialize(), function(data) {
        var flashMessage = data[0];
        if (flashMessage.level == 'error') {
          showErrorMessage(flashMessage.message);
        } else {
          showSuccessMessage(flashMessage.message);
        }

        tearDownGitImportOptions();

        miqSparkleOff();
      }, 'json');
    });

    $('.import-back').click(function(event) {
      event.preventDefault();
      miqSparkleOn();
      clearMessages();

      $('.domain-tree').empty();

      $.post('cancel_import', $('#import-form').serialize(), function(data) {
        var flashMessage = JSON.parse(data)[0];
        showSuccessMessage(flashMessage.message);

        $('.import-or-export').show();
        $('.import-data').hide();
        miqSparkleOff();
      });
    });

    $('.git-import-cancel').click(function(event) {
      event.preventDefault();
      clearMessages();
      tearDownGitImportOptions();
      showSuccessMessage('Import cancelled');
    });

    $('#toggle-all').on('change', function() {
      $('#toggle-all').prop('checked') ? $('.domain-tree').treeview(true).checkAll() : $('.domain-tree').treeview(true).uncheckAll();
    });
  },

  setUpDefaultGitBranchOrTagValue: function() {
    $('.git-branch-or-tag').val($('select.git-branches').val());
  },

  setUpGitRefreshClickHandlers: function() {
    var toggleSubmitButton = function() {
      if ($('.git-branch-or-tag').val() !== "") {
        $('.git-import-submit').prop('disabled', false);
      } else {
        $('.git-import-submit').prop('disabled', true);
      }
    };

    $('.git-branch-or-tag-select').on('change', function(event) {
      event.preventDefault();
      if ($(event.currentTarget).val() === "Branch") {
        $('.git-branch-group').show();
        $('.git-tag-group').hide();
        $('.git-branch-or-tag').val($('select.git-branches').val());
      } else if ($(event.currentTarget).val() === "Tag") {
        $('.git-branch-group').hide();
        $('.git-tag-group').show();
        $('.git-branch-or-tag').val($('select.git-tags').val());
      }
      toggleSubmitButton();
    });

    $('select.git-branches').on('change', function(event) {
      $('.git-branch-or-tag').val($('select.git-branches').val());
      toggleSubmitButton();
    });

    $('select.git-tags').on('change', function(event) {
      $('.git-branch-or-tag').val($('select.git-tags').val());
      toggleSubmitButton();
    });
  }
};
<commit_msg>Remove if statement in favor of variable declaration<commit_after>//= require import

/* global miqSparkleOn miqSparkleOff showSuccessMessage showErrorMessage showWarningMessage clearMessages */

var Automate = {
  getAndRenderAutomateJson: function(importFileUploadId, message) {
    $('.hidden-import-file-upload-id').val(importFileUploadId);

    $.getJSON("automate_json?import_file_upload_id=" + importFileUploadId)
      .done(function(rows_json) {
        Automate.addDomainOptions(rows_json);
        Automate.setupInitialTree(rows_json);

        $('select.importing-domains').change(function() {
          Automate.importingDomainsChangeHandler(rows_json);
        });

        $('#import_file_upload_id').val(importFileUploadId);
        $('.import-data').show();
        $('.import-or-export').hide();
        showSuccessMessage(JSON.parse(message).message);
      })
      .fail(function(failedMessage) {
        var messageData = JSON.parse(failedMessage.responseText);

        if (messageData.level == 'warning') {
          showWarningMessage(messageData.message);
        } else {
          showErrorMessage(messageData.message);
        }
      });
  },

  renderGitImport: function(branches, tags, gitRepoId, messages) {
    clearMessages();
    message = JSON.parse(messages).message;
    messageLevel = JSON.parse(messages).level;

    if (messageLevel === "error") {
      showErrorMessage(message);
    } else {
      $('.hidden-git-repo-id').val(gitRepoId);
      $('.git-import-data').show();
      $('.import-or-export').hide();
      if (messageLevel === "warning") {
        showWarningMessage(message);
      } else {
        showSuccessMessage(message);
      }

      var addToDropDown = function(identifier, child) {
        $('select.git-' + identifier).append(
          $('<option>', {
            value: child,
            text: child
          })
        );
      };

      $.each(JSON.parse(branches), function(index, child) {
        addToDropDown('branches', child);
      });
      $.each(JSON.parse(tags), function(index, child) {
        addToDropDown('tags', child);
      });

      $('select.git-branches').selectpicker('refresh');
      $('select.git-tags').selectpicker('refresh');
    }

    miqSparkleOff();
  },

  addDomainOptions: function(domains) {
    $('select.importing-domains').empty();

    $.each(domains, function(_index, child) {
      $('select.importing-domains').append(
        $('<option>', {
          value: child.key,
          text:  child.text
        })
      );
    });

    $('select.importing-domains').selectpicker('refresh');
  },

  setupInitialTree: function(domains) {
    $('.domain-tree').treeview({
      data:              domains[0].nodes,
      levels:            1,
      showCheckbox:      true,
      showImage:         true,
      hierarchicalCheck: true,
      expandIcon:        'fa fa-fw fa-chevron-right',
      collapseIcon:      'fa fa-fw fa-chevron-down'
    });
  },

  importingDomainsChangeHandler: function(domains) {
    $.each(domains, function(_index, child) {
      if ($('select.importing-domains').val() === child.key) {
        $('.domain-tree').treeview({
          data:              child.nodes,
          levels:            1,
          showCheckbox:      true,
          showImage:         true,
          hierarchicalCheck: true,
          expandIcon:        'fa fa-fw fa-chevron-right',
          collapseIcon:      'fa fa-fw fa-chevron-down'
        });
      }
    });

    $('#toggle-all').prop('checked', false);
  },

  setUpAutomateImportClickHandlers: function() {
    var tearDownGitImportOptions = function() {
      $('select.git-branches').find('option').remove().end();
      $('select.git-tags').find('option').remove().end();
      $('select.git-branches').selectpicker('refresh');
      $('select.git-tags').selectpicker('refresh');

      $('.import-or-export').show();
      $('.git-import-data').hide();
      $('#git-url-import').prop('disabled', null);
    };

    $('.import-commit').click(function(event) {
      event.preventDefault();
      miqSparkleOn();
      clearMessages();

      var postData = $('#import-form').serialize();
      postData += '&';

      var treeName = $('.domain-tree').attr('name');
      var serialized = $.param($('.domain-tree').treeview(true).getChecked().map(function (node) {
        return {name: treeName, value: node.key}
      }));
      postData = postData.concat(serialized);

      $.post('import_automate_datastore', postData, function(data) {
        var flashMessage = JSON.parse(data)[0];
        if (flashMessage.level == 'error') {
          showErrorMessage(flashMessage.message);
        } else {
          showSuccessMessage(flashMessage.message);
        }

        miqSparkleOff();
      });
    });

    Automate.setUpGitRefreshClickHandlers();

    $('.git-retreive-datastore').click(function() {
      miqSparkleOn();
      $('.git-import-submit').prop('disabled', true);
    });

    $('.git-import-submit').click(function(event) {
      event.preventDefault();
      miqSparkleOn();
      clearMessages();

      $.post('import_via_git', $('#git-branch-tag-form').serialize(), function(data) {
        var flashMessage = data[0];
        if (flashMessage.level == 'error') {
          showErrorMessage(flashMessage.message);
        } else {
          showSuccessMessage(flashMessage.message);
        }

        tearDownGitImportOptions();

        miqSparkleOff();
      }, 'json');
    });

    $('.import-back').click(function(event) {
      event.preventDefault();
      miqSparkleOn();
      clearMessages();

      $('.domain-tree').empty();

      $.post('cancel_import', $('#import-form').serialize(), function(data) {
        var flashMessage = JSON.parse(data)[0];
        showSuccessMessage(flashMessage.message);

        $('.import-or-export').show();
        $('.import-data').hide();
        miqSparkleOff();
      });
    });

    $('.git-import-cancel').click(function(event) {
      event.preventDefault();
      clearMessages();
      tearDownGitImportOptions();
      showSuccessMessage('Import cancelled');
    });

    $('#toggle-all').on('change', function() {
      $('#toggle-all').prop('checked') ? $('.domain-tree').treeview(true).checkAll() : $('.domain-tree').treeview(true).uncheckAll();
    });
  },

  setUpDefaultGitBranchOrTagValue: function() {
    $('.git-branch-or-tag').val($('select.git-branches').val());
  },

  setUpGitRefreshClickHandlers: function() {
    var toggleSubmitButton = function() {
      var branchOrTagSelected = $('.git-branch-or-tag').val() === '';
      $('.git-import-submit').prop('disabled', branchOrTagSelected);
    };

    $('.git-branch-or-tag-select').on('change', function(event) {
      event.preventDefault();
      if ($(event.currentTarget).val() === "Branch") {
        $('.git-branch-group').show();
        $('.git-tag-group').hide();
        $('.git-branch-or-tag').val($('select.git-branches').val());
      } else if ($(event.currentTarget).val() === "Tag") {
        $('.git-branch-group').hide();
        $('.git-tag-group').show();
        $('.git-branch-or-tag').val($('select.git-tags').val());
      }
      toggleSubmitButton();
    });

    $('select.git-branches').on('change', function(event) {
      $('.git-branch-or-tag').val($('select.git-branches').val());
      toggleSubmitButton();
    });

    $('select.git-tags').on('change', function(event) {
      $('.git-branch-or-tag').val($('select.git-tags').val());
      toggleSubmitButton();
    });
  }
};
<|endoftext|>


<commit_before>/** internal
 *  class Base
 *
 *  Base class for [[Environment]] and [[Index]].
 *
 *
 *  ##### INCLUDES
 *
 *  - [[Engines]]
 *  - [[Mime]]
 *  - [[Processing]]
 *  - [[Paths]]
 **/

'use strict';

// stdlib
var fs      = require('fs');
var path    = require('path');
var crypto  = require('crypto');

// 3rd-party
var _     = require('underscore');
var async = require('async');

// internal
var getter          = require('./common').getter;
var prop            = require('./common').prop;
var mixin           = require('./common').mixin;
var isAbsolute      = require('./common').isAbsolute;
var VERSION         = require('./version');
var AssetAttributes = require('./asset_attributes');
var StaticAsset     = require('./assets/static');
var ProcessedAsset  = require('./assets/processed');
var BundledAsset    = require('./assets/bundled');

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Base()
 **/
var Base = module.exports = function Base() {};

mixin(Base.prototype, require('./helpers/paths'));
mixin(Base.prototype, require('./helpers/mime'));
mixin(Base.prototype, require('./helpers/processing'));
mixin(Base.prototype, require('./helpers/engines'));

// helper that defines property which fires expireIndex()
// each time it gets changed
function attr_with_expire_index(name, value) {
  var __name__ = '__' + name + '__';

  // set underlying value
  prop(Base.prototype, __name__, value, {writable: true});

  // provide getters/setter
  Object.defineProperty(Base.prototype, name, {
    get: function () {
      return this[__name__];
    },
    set: function (val) {
      this.expireIndex();
      this[__name__] = val;
    }
  });
}

/**
 *  Base#digestAlgorithm -> String
 *
 *  Digest algorithm: `sha1` or `md5`.
 *  See Node manual on `crypto` module.
 *
 *  Default: `md5`.
 **/
attr_with_expire_index('digestAlgorithm', 'md5');

/**
 *  Base#version -> String
 *
 *  Environment version.
 *
 *      environment.version = '2.0'
 **/
attr_with_expire_index('version', '');

/**
 *  Base#digest -> crypto.Hash
 *
 *  Returns a `crypto.Hash` instance for the `Environment`.
 *
 *  This value serves two purposes. If two `Environment`s have the
 *  same digest value they can be treated as equal. This is more
 *  useful for comparing environment states between processes rather
 *  than in the same. Two equal `Environment`s can share the same
 *  cached assets.
 *
 *  The value also provides a seed digest for all `Asset` digests.
 *  Any change in the environment digest will affect all of its assets.
 **/
getter(Base.prototype, 'digest', function () {
  // Do not cache, so the caller can safely mutate it with `.update`
  var digest = crypto.createHash(this.digestAlgorithm);

  // Mixin Mincer release version and custom environment version.
  // So any new releases will affect all your assets.
  digest.update(VERSION,      'utf8');
  digest.update(this.version, 'utf8');

  return digest;
});

// helper that overrides defined method `name` with wrapper that expireIndex()
// and calls `func` when provided before calling original method
function func_proxy_with_expire_index(name, func) {
  var orig = Base.prototype[name];

  Base.prototype[name] = function () {
    this.expireIndex();
    if (func) {
      func.apply(this, arguments);
    }
    orig.apply(this, arguments);
  };
}

//
// override [[Paths]] mixin methods
//

func_proxy_with_expire_index('prependPath');
func_proxy_with_expire_index('appendPath');
func_proxy_with_expire_index('clearPaths');

//
// override [[Mime]] mixin methods
//

func_proxy_with_expire_index('registerMimeType', function (mimeType, ext) {
  this.__trail__.extensions.append(ext);
});

//
// override [[Engines]] mixin methods
//

func_proxy_with_expire_index('registerEngine', function (ext, klass) {
  this.addEngineToTrail(ext, klass);
});

//
// override [[Processing]] mixin methods
//

func_proxy_with_expire_index('registerPreprocessor');
func_proxy_with_expire_index('registerPostprocessor');
func_proxy_with_expire_index('registerBundleProcessor');
func_proxy_with_expire_index('unregisterPreprocessor');
func_proxy_with_expire_index('unregisterPostprocessor');
func_proxy_with_expire_index('unregisterBundleProcessor');

/**
 *  Base#resolve(logicalPath[, options = {}[, fn]]) -> String
 *  - logicalPath (String)
 *  - options (Object)
 *  - fn (Function)
 *
 *  Finds the expanded real path for a given logical path by
 *  searching the environment's paths.
 *
 *      env.resolve("application.js")
 *      # => "/path/to/app/javascripts/application.js.coffee"
 *
 *  An Error with `code = 'FileNotFound'` is raised if the file does not exist.
 **/
Base.prototype.resolve = function (logicalPath, options, fn) {
  var err, resolved, args;

  if (fn) {
    args      = this.attributesFor(logicalPath).searchPaths;
    resolved  = this.__trail__.find(args, options, fn);
  } else {
    resolved = this.resolve(logicalPath, options, function (pathname) {
      return pathname;
    });

    if (!resolved) {
      err = new Error("couldn't find file '" + logicalPath + "'");
      err.code = 'FileNotFound';
      throw err;
    }
  }

  return resolved;
};

/**
 *  Base#entries(pathname) -> Array
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#entries`. Works like `fs.readdirSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.entries = function (pathname) {
  return this.__trail__.entries(pathname);
};

/**
 *  Base#stat(pathname) -> fs.Stats
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#stat`. Works like `fs.statSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.stat = function (pathname) {
  return this.__trail__.stat(pathname);
};

/**
 *  Base#getFileDigest(pathname) -> String
 *  - pathname (String)
 *
 *  Read and compute digest of filename.
 *  Subclasses may cache this method.
 **/
Base.prototype.getFileDigest = function (pathname) {
  var stat = this.stat(pathname);

  if (stat && stat.isFile()) {
    // If its a file, digest the contents
    return this.digest.update(fs.readFileSync(pathname)).digest('hex');
  } else if (stat && stat.isDirectory()) {
    // If its a directive, digest the list of filenames
    return this.digest.update(this.entries(pathname).join(',')).digest('hex');
  }
};

/** internal
 *  Base#attributesFor(pathname) -> AssetAttributes
 *  - pathname (String)
 *
 *  Returns a `AssetAttributes` for `pathname`
 **/
Base.prototype.attributesFor = function (pathname) {
  return new AssetAttributes(this, pathname);
};

/** internal
 *  Base#contentTypeOf(pathname) -> String
 *  - pathname (String)
 *
 *  Returns content type of `pathname`
 **/
Base.prototype.contentTypeOf = function (pathname) {
  return this.attributesFor(pathname).contentType;
};

/**
 *  Base#findAsset(pathname[, options = {}]) -> Asset
 *  - pathname (String)
 *  - options (Object)
 *
 *  Find asset by logical path or expanded path.
 **/
Base.prototype.findAsset = function (pathname, options) {
  var logical_path = pathname;

  if (isAbsolute(pathname)) {
    if (!this.stat(pathname)) {
      return;
    }

    logical_path = this.attributesFor(pathname).logicalPath;
  } else {
    try {
      pathname = this.resolve(logical_path);
    } catch (err) {
      if ('FileNotFound' === err.code) {
        return null;
      }

      throw err;
    }
  }

  return this.buildAsset(logical_path, pathname, options);
};

/**
 *  Base#precompile(*files[, callback]) -> Void
 *
 *  Helper to make sure that given list of `files` were compiled.
 *  Similar to [[Manifest#compile]], but does not write anything to disk.
 *
 *      environment.precompile("app.js", function (err, assets) {
 *        assets[0]; // - compiled asset of 'app.js'
 *      });
 *
 *  Needed when you want to render HTML with some JavaScript injected right
 *  into your page (e.g. single-page offline documentation) and your template
 *  engine does not support asynchronous helpers (e.g. Jade requires helpers
 *  to be synchronous).
 **/
Base.prototype.precompile = function () {
  var self     = this,
      files    = _.flatten(arguments),
      callback = _.isFunction(_.last(files)) ? files.pop() : function () {},
      paths    = [];

  this.environment.eachLogicalPath(files, function (pathname) {
    paths.push(pathname);
  });

  paths = _.union(paths, _.select(files, isAbsolute));
  async.map(paths, function (pathname, next) {
    var asset = self.findAsset(pathname);

    if (!asset) {
      next(new Error("Can not find asset '" + pathname + "'"));
      return;
    }

    asset.compile(next);
  }, callback);
};

/**
 *  Base#eachEntry(root, iterator) -> Void
 *  - root (String)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found file or directory in alphabetical order:
 *
 *      env.eachEntry('/some/path', function (entry) {
 *        console.log(entry);
 *      });
 *      // -> "/some/path/a"
 *      // -> "/some/path/a/b.txt"
 *      // -> "/some/path/a/c.txt"
 *      // -> "/some/path/b.txt"
 **/
Base.prototype.eachEntry = function (root, iterator) {
  var self = this, paths = [];

  this.entries(root).forEach(function (filename) {
    var pathname  = path.join(root, filename);

    paths.push(pathname);

    if (self.stat(pathname).isDirectory()) {
      self.eachEntry(pathname, function (subpath) {
        paths.push(subpath);
      });
    }
  });

  paths.sort().forEach(iterator);
};

/**
 *  Base#eachFile(iterator) -> Void
 *  - iterator (Function)
 *
 *  Calls `iterator` for each file found within all registered paths.
 **/
Base.prototype.eachFile = function (iterator) {
  var self = this;

  this.paths.forEach(function (root) {
    self.eachEntry(root, function (pathname) {
      if (!self.stat(pathname).isDirectory()) {
        iterator(pathname);
      }
    });
  });
};

// Returns true if there were no filters, or `filename` matches at least one
function matches_filter(filters, filename) {
  if (0 === filters.length) {
    return true;
  }

  return _.any(filters, function (filter) {
    if (_.isRegExp(filter)) {
      return filter.test(filename);
    } else if (_.isFunction(filter)) {
      return filter(filename);
    } else {
      return (new RegExp(filter.replace(/\*\*|\*|\?/, function (m) {
        switch (m[0]) {
          case "**":  return ".*?";
          case "*":   return "[^/]+";
          case "?":   return "[^/]?";
          default:    return m[0];
        }
      }))).test(filename);
    }
  });
}

// Returns logicalPath for `filename` if it mtches given filters
function logical_path_for_filename(self, filters, filename) {
  var logical_path = self.attributesFor(filename).logicalPath;

  if (matches_filter(filters, logical_path)) {
    return logical_path;
  }

  // If filename is an index file, retest with alias
  if ('index' === path.basename(filename).split('.').shift()) {
    logical_path = logical_path.replace(/\/index\./, '.');
    if (matches_filter(filters, logical_path)) {
      return logical_path;
    }
  }
}

/**
 *  Base#eachLogicalPath(*filters, iterator) -> Void
 *  - filters (Mixed)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found logical path (once per unique path) that
 *  matches at least one of the given filters.
 *
 *  Each filter might be a `String`, `RegExp` or a `Function`.
 **/
Base.prototype.eachLogicalPath = function () {
  var self = this,
      filters = _.flatten(arguments),
      iterator = filters.pop(),
      files = {};

  this.eachFile(function (filename) {
    var logical_path = logical_path_for_filename(self, filters, filename);
    if (logical_path && !files[logical_path]) {
      iterator(logical_path);
      files[logical_path] = true;
    }
  });
};

// circular call  protection helper.
// keeps array of required pathnames until the function
// that originated protection finishes it's execution
var circular_calls = null;
function circular_call_protection(pathname, callback) {
  var reset   = (null === circular_calls),
      calls   = circular_calls || (circular_calls = []),
      result  = null,
      error   = null;

  if (0 <= calls.indexOf(pathname)) {
    if (reset) { circular_calls = null; }
    throw new Error("Circular dependency detected: " + pathname +
                    " has already been required");
  }

  calls.push(pathname);

  try {
    result = callback();
  } catch (err) {
    error = err;
  }

  if (reset) {
    circular_calls = null;
  }

  if (error) {
    throw error;
  }

  return result;
}

// creates instance of [[StaticAsset]], [[BundledAsset]] or [[ProcessedAsset]]
Base.prototype.buildAsset = function (logicalPath, pathname, options) {
  var self = this;

  options = options || {};

  // If there are any processors to run on the pathname, use
  // `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.

  if (0 === this.attributesFor(pathname).processors.length) {
    return new StaticAsset(this.index, logicalPath, pathname);
  }

  if (options.bundle) {
    return new BundledAsset(this.index, logicalPath, pathname);
  }

  return circular_call_protection(pathname, function () {
    return new ProcessedAsset(self.index, logicalPath, pathname);
  });
};

// Returns cache key for given `pathname` based on options
Base.prototype.cacheKeyFor = function (pathname, options) {
  return pathname + String(options.bundle ? 1 : 0);
};
<commit_msg>Fix Base#precompile()<commit_after>/** internal
 *  class Base
 *
 *  Base class for [[Environment]] and [[Index]].
 *
 *
 *  ##### INCLUDES
 *
 *  - [[Engines]]
 *  - [[Mime]]
 *  - [[Processing]]
 *  - [[Paths]]
 **/

'use strict';

// stdlib
var fs      = require('fs');
var path    = require('path');
var crypto  = require('crypto');

// 3rd-party
var _     = require('underscore');
var async = require('async');

// internal
var getter          = require('./common').getter;
var prop            = require('./common').prop;
var mixin           = require('./common').mixin;
var isAbsolute      = require('./common').isAbsolute;
var VERSION         = require('./version');
var AssetAttributes = require('./asset_attributes');
var StaticAsset     = require('./assets/static');
var ProcessedAsset  = require('./assets/processed');
var BundledAsset    = require('./assets/bundled');

////////////////////////////////////////////////////////////////////////////////

/**
 *  new Base()
 **/
var Base = module.exports = function Base() {};

mixin(Base.prototype, require('./helpers/paths'));
mixin(Base.prototype, require('./helpers/mime'));
mixin(Base.prototype, require('./helpers/processing'));
mixin(Base.prototype, require('./helpers/engines'));

// helper that defines property which fires expireIndex()
// each time it gets changed
function attr_with_expire_index(name, value) {
  var __name__ = '__' + name + '__';

  // set underlying value
  prop(Base.prototype, __name__, value, {writable: true});

  // provide getters/setter
  Object.defineProperty(Base.prototype, name, {
    get: function () {
      return this[__name__];
    },
    set: function (val) {
      this.expireIndex();
      this[__name__] = val;
    }
  });
}

/**
 *  Base#digestAlgorithm -> String
 *
 *  Digest algorithm: `sha1` or `md5`.
 *  See Node manual on `crypto` module.
 *
 *  Default: `md5`.
 **/
attr_with_expire_index('digestAlgorithm', 'md5');

/**
 *  Base#version -> String
 *
 *  Environment version.
 *
 *      environment.version = '2.0'
 **/
attr_with_expire_index('version', '');

/**
 *  Base#digest -> crypto.Hash
 *
 *  Returns a `crypto.Hash` instance for the `Environment`.
 *
 *  This value serves two purposes. If two `Environment`s have the
 *  same digest value they can be treated as equal. This is more
 *  useful for comparing environment states between processes rather
 *  than in the same. Two equal `Environment`s can share the same
 *  cached assets.
 *
 *  The value also provides a seed digest for all `Asset` digests.
 *  Any change in the environment digest will affect all of its assets.
 **/
getter(Base.prototype, 'digest', function () {
  // Do not cache, so the caller can safely mutate it with `.update`
  var digest = crypto.createHash(this.digestAlgorithm);

  // Mixin Mincer release version and custom environment version.
  // So any new releases will affect all your assets.
  digest.update(VERSION,      'utf8');
  digest.update(this.version, 'utf8');

  return digest;
});

// helper that overrides defined method `name` with wrapper that expireIndex()
// and calls `func` when provided before calling original method
function func_proxy_with_expire_index(name, func) {
  var orig = Base.prototype[name];

  Base.prototype[name] = function () {
    this.expireIndex();
    if (func) {
      func.apply(this, arguments);
    }
    orig.apply(this, arguments);
  };
}

//
// override [[Paths]] mixin methods
//

func_proxy_with_expire_index('prependPath');
func_proxy_with_expire_index('appendPath');
func_proxy_with_expire_index('clearPaths');

//
// override [[Mime]] mixin methods
//

func_proxy_with_expire_index('registerMimeType', function (mimeType, ext) {
  this.__trail__.extensions.append(ext);
});

//
// override [[Engines]] mixin methods
//

func_proxy_with_expire_index('registerEngine', function (ext, klass) {
  this.addEngineToTrail(ext, klass);
});

//
// override [[Processing]] mixin methods
//

func_proxy_with_expire_index('registerPreprocessor');
func_proxy_with_expire_index('registerPostprocessor');
func_proxy_with_expire_index('registerBundleProcessor');
func_proxy_with_expire_index('unregisterPreprocessor');
func_proxy_with_expire_index('unregisterPostprocessor');
func_proxy_with_expire_index('unregisterBundleProcessor');

/**
 *  Base#resolve(logicalPath[, options = {}[, fn]]) -> String
 *  - logicalPath (String)
 *  - options (Object)
 *  - fn (Function)
 *
 *  Finds the expanded real path for a given logical path by
 *  searching the environment's paths.
 *
 *      env.resolve("application.js")
 *      # => "/path/to/app/javascripts/application.js.coffee"
 *
 *  An Error with `code = 'FileNotFound'` is raised if the file does not exist.
 **/
Base.prototype.resolve = function (logicalPath, options, fn) {
  var err, resolved, args;

  if (fn) {
    args      = this.attributesFor(logicalPath).searchPaths;
    resolved  = this.__trail__.find(args, options, fn);
  } else {
    resolved = this.resolve(logicalPath, options, function (pathname) {
      return pathname;
    });

    if (!resolved) {
      err = new Error("couldn't find file '" + logicalPath + "'");
      err.code = 'FileNotFound';
      throw err;
    }
  }

  return resolved;
};

/**
 *  Base#entries(pathname) -> Array
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#entries`. Works like `fs.readdirSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.entries = function (pathname) {
  return this.__trail__.entries(pathname);
};

/**
 *  Base#stat(pathname) -> fs.Stats
 *  - pathname (String)
 *
 *  Proxy to `Hike.Trail#stat`. Works like `fs.statSync`.
 *  Subclasses may cache this method.
 **/
Base.prototype.stat = function (pathname) {
  return this.__trail__.stat(pathname);
};

/**
 *  Base#getFileDigest(pathname) -> String
 *  - pathname (String)
 *
 *  Read and compute digest of filename.
 *  Subclasses may cache this method.
 **/
Base.prototype.getFileDigest = function (pathname) {
  var stat = this.stat(pathname);

  if (stat && stat.isFile()) {
    // If its a file, digest the contents
    return this.digest.update(fs.readFileSync(pathname)).digest('hex');
  } else if (stat && stat.isDirectory()) {
    // If its a directive, digest the list of filenames
    return this.digest.update(this.entries(pathname).join(',')).digest('hex');
  }
};

/** internal
 *  Base#attributesFor(pathname) -> AssetAttributes
 *  - pathname (String)
 *
 *  Returns a `AssetAttributes` for `pathname`
 **/
Base.prototype.attributesFor = function (pathname) {
  return new AssetAttributes(this, pathname);
};

/** internal
 *  Base#contentTypeOf(pathname) -> String
 *  - pathname (String)
 *
 *  Returns content type of `pathname`
 **/
Base.prototype.contentTypeOf = function (pathname) {
  return this.attributesFor(pathname).contentType;
};

/**
 *  Base#findAsset(pathname[, options = {}]) -> Asset
 *  - pathname (String)
 *  - options (Object)
 *
 *  Find asset by logical path or expanded path.
 **/
Base.prototype.findAsset = function (pathname, options) {
  var logical_path = pathname;

  if (isAbsolute(pathname)) {
    if (!this.stat(pathname)) {
      return;
    }

    logical_path = this.attributesFor(pathname).logicalPath;
  } else {
    try {
      pathname = this.resolve(logical_path);
    } catch (err) {
      if ('FileNotFound' === err.code) {
        return null;
      }

      throw err;
    }
  }

  return this.buildAsset(logical_path, pathname, options);
};

/**
 *  Base#precompile(*files[, callback]) -> Void
 *
 *  Helper to make sure that given list of `files` were compiled.
 *  Similar to [[Manifest#compile]], but does not write anything to disk.
 *
 *      environment.precompile("app.js", function (err, assets) {
 *        assets[0]; // - compiled asset of 'app.js'
 *      });
 *
 *  Needed when you want to render HTML with some JavaScript injected right
 *  into your page (e.g. single-page offline documentation) and your template
 *  engine does not support asynchronous helpers (e.g. Jade requires helpers
 *  to be synchronous).
 **/
Base.prototype.precompile = function () {
  var self     = this,
      files    = _.flatten(arguments),
      callback = _.isFunction(_.last(files)) ? files.pop() : function () {},
      paths    = [];

  this.eachLogicalPath(files, function (pathname) {
    paths.push(pathname);
  });

  paths = _.union(paths, _.select(files, isAbsolute));
  async.map(paths, function (pathname, next) {
    var asset = self.findAsset(pathname);

    if (!asset) {
      next(new Error("Can not find asset '" + pathname + "'"));
      return;
    }

    asset.compile(next);
  }, callback);
};

/**
 *  Base#eachEntry(root, iterator) -> Void
 *  - root (String)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found file or directory in alphabetical order:
 *
 *      env.eachEntry('/some/path', function (entry) {
 *        console.log(entry);
 *      });
 *      // -> "/some/path/a"
 *      // -> "/some/path/a/b.txt"
 *      // -> "/some/path/a/c.txt"
 *      // -> "/some/path/b.txt"
 **/
Base.prototype.eachEntry = function (root, iterator) {
  var self = this, paths = [];

  this.entries(root).forEach(function (filename) {
    var pathname  = path.join(root, filename);

    paths.push(pathname);

    if (self.stat(pathname).isDirectory()) {
      self.eachEntry(pathname, function (subpath) {
        paths.push(subpath);
      });
    }
  });

  paths.sort().forEach(iterator);
};

/**
 *  Base#eachFile(iterator) -> Void
 *  - iterator (Function)
 *
 *  Calls `iterator` for each file found within all registered paths.
 **/
Base.prototype.eachFile = function (iterator) {
  var self = this;

  this.paths.forEach(function (root) {
    self.eachEntry(root, function (pathname) {
      if (!self.stat(pathname).isDirectory()) {
        iterator(pathname);
      }
    });
  });
};

// Returns true if there were no filters, or `filename` matches at least one
function matches_filter(filters, filename) {
  if (0 === filters.length) {
    return true;
  }

  return _.any(filters, function (filter) {
    if (_.isRegExp(filter)) {
      return filter.test(filename);
    } else if (_.isFunction(filter)) {
      return filter(filename);
    } else {
      return (new RegExp(filter.replace(/\*\*|\*|\?/, function (m) {
        switch (m[0]) {
          case "**":  return ".*?";
          case "*":   return "[^/]+";
          case "?":   return "[^/]?";
          default:    return m[0];
        }
      }))).test(filename);
    }
  });
}

// Returns logicalPath for `filename` if it mtches given filters
function logical_path_for_filename(self, filters, filename) {
  var logical_path = self.attributesFor(filename).logicalPath;

  if (matches_filter(filters, logical_path)) {
    return logical_path;
  }

  // If filename is an index file, retest with alias
  if ('index' === path.basename(filename).split('.').shift()) {
    logical_path = logical_path.replace(/\/index\./, '.');
    if (matches_filter(filters, logical_path)) {
      return logical_path;
    }
  }
}

/**
 *  Base#eachLogicalPath(*filters, iterator) -> Void
 *  - filters (Mixed)
 *  - iterator (Function)
 *
 *  Calls `iterator` on each found logical path (once per unique path) that
 *  matches at least one of the given filters.
 *
 *  Each filter might be a `String`, `RegExp` or a `Function`.
 **/
Base.prototype.eachLogicalPath = function () {
  var self = this,
      filters = _.flatten(arguments),
      iterator = filters.pop(),
      files = {};

  this.eachFile(function (filename) {
    var logical_path = logical_path_for_filename(self, filters, filename);
    if (logical_path && !files[logical_path]) {
      iterator(logical_path);
      files[logical_path] = true;
    }
  });
};

// circular call  protection helper.
// keeps array of required pathnames until the function
// that originated protection finishes it's execution
var circular_calls = null;
function circular_call_protection(pathname, callback) {
  var reset   = (null === circular_calls),
      calls   = circular_calls || (circular_calls = []),
      result  = null,
      error   = null;

  if (0 <= calls.indexOf(pathname)) {
    if (reset) { circular_calls = null; }
    throw new Error("Circular dependency detected: " + pathname +
                    " has already been required");
  }

  calls.push(pathname);

  try {
    result = callback();
  } catch (err) {
    error = err;
  }

  if (reset) {
    circular_calls = null;
  }

  if (error) {
    throw error;
  }

  return result;
}

// creates instance of [[StaticAsset]], [[BundledAsset]] or [[ProcessedAsset]]
Base.prototype.buildAsset = function (logicalPath, pathname, options) {
  var self = this;

  options = options || {};

  // If there are any processors to run on the pathname, use
  // `BundledAsset`. Otherwise use `StaticAsset` and treat is as binary.

  if (0 === this.attributesFor(pathname).processors.length) {
    return new StaticAsset(this.index, logicalPath, pathname);
  }

  if (options.bundle) {
    return new BundledAsset(this.index, logicalPath, pathname);
  }

  return circular_call_protection(pathname, function () {
    return new ProcessedAsset(self.index, logicalPath, pathname);
  });
};

// Returns cache key for given `pathname` based on options
Base.prototype.cacheKeyFor = function (pathname, options) {
  return pathname + String(options.bundle ? 1 : 0);
};
<|endoftext|>


<commit_before>/**
 * 4x4 matrix.
 * @constructor
 * @this {Matrix}
 */
function Matrix(){
    /** @type {Array.<number>} */
    this.m = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
/**
 * Compare matrix with self for equality.
 * @method
 * @param {Matrix} matrix
 * @return {boolean}
 */
Matrix.prototype.equals = function(matrix){
    for (var i = 0, len = this.m.length; i < len; i++){
        if (this.m[i] !== matrix.m[i]){
            return false;
        }
    }
    return true;
};
/**
 * Add matrix to self.
 * @method
 * @param {Matrix} matrix
 * @return {Matrix}
 */
Matrix.prototype.add = function(matrix){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.m.length; i < len; i++){
        new_matrix.m[i] = this.m[i] + matrix.m[i];
    }
    return new_matrix;
};
/**
 * Subtract matrix from self.
 * @method
 * @param {Matrix} matrix
 * @return {Matrix}
 */
Matrix.prototype.subtract = function(matrix){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.m.length; i < len; i++){
        this.m[i] = this.m[i] - matrix.m[i];
    }
    return new_matrix;
};
/**
 * Multiply self by scalar.
 * @method
 * @param {number} scalar
 * @return {Matrix}
 */
Matrix.prototype.multiplyScalar = function(scalar){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.m.length; i < len; i++){
        this.m[i] = this.m[i] * scalar;
    }
    return new_matrix;
};
/**
 * Multiply self by matrix.
 * @method
 * @param {Matrix} matrix
 * @return {Matrix}
 */
Matrix.prototype.multiply = function(matrix){
    var new_matrix = new Matrix();
    new_matrix.m[0] = (this.m[0] * matrix.m[0]) + (this.m[1] * matrix.m[4]) + (this.m[2] * matrix.m[8]) + (this.m[3] * matrix.m[12]);
    new_matrix.m[1] = (this.m[0] * matrix.m[1]) + (this.m[1] * matrix.m[5]) + (this.m[2] * matrix.m[9]) + (this.m[3] * matrix.m[13]);
    new_matrix.m[2] = (this.m[0] * matrix.m[2]) + (this.m[1] * matrix.m[6]) + (this.m[2] * matrix.m[10]) + (this.m[3] * matrix.m[14]);
    new_matrix.m[3] = (this.m[0] * matrix.m[3]) + (this.m[1] * matrix.m[7]) + (this.m[2] * matrix.m[11]) + (this.m[3] * matrix.m[15]);
    new_matrix.m[4] = (this.m[4] * matrix.m[0]) + (this.m[5] * matrix.m[4]) + (this.m[6] * matrix.m[8]) + (this.m[7] * matrix.m[12]);
    new_matrix.m[5] = (this.m[4] * matrix.m[1]) + (this.m[5] * matrix.m[5]) + (this.m[6] * matrix.m[9]) + (this.m[7] * matrix.m[13]);
    new_matrix.m[6] = (this.m[4] * matrix.m[2]) + (this.m[5] * matrix.m[6]) + (this.m[6] * matrix.m[10]) + (this.m[7] * matrix.m[14]);
    new_matrix.m[7] = (this.m[4] * matrix.m[3]) + (this.m[5] * matrix.m[7]) + (this.m[6] * matrix.m[11]) + (this.m[7] * matrix.m[15]);
    new_matrix.m[8] = (this.m[8] * matrix.m[0]) + (this.m[9] * matrix.m[4]) + (this.m[10] * matrix.m[8]) + (this.m[11] * matrix.m[12]);
    new_matrix.m[9] = (this.m[8] * matrix.m[1]) + (this.m[9] * matrix.m[5]) + (this.m[10] * matrix.m[9]) + (this.m[11] * matrix.m[13]);
    new_matrix.m[10] = (this.m[8] * matrix.m[2]) + (this.m[9] * matrix.m[6]) + (this.m[10] * matrix.m[10]) + (this.m[11] * matrix.m[14]);
    new_matrix.m[11] = (this.m[8] * matrix.m[3]) + (this.m[9] * matrix.m[7]) + (this.m[10] * matrix.m[11]) + (this.m[11] * matrix.m[15]);
    new_matrix.m[12] = (this.m[12] * matrix.m[0]) + (this.m[13] * matrix.m[4]) + (this.m[14] * matrix.m[8]) + (this.m[15] * matrix.m[12]);
    new_matrix.m[13] = (this.m[12] * matrix.m[1]) + (this.m[13] * matrix.m[5]) + (this.m[14] * matrix.m[9]) + (this.m[15] * matrix.m[13]);
    new_matrix.m[14] = (this.m[12] * matrix.m[2]) + (this.m[13] * matrix.m[6]) + (this.m[14] * matrix.m[10]) + (this.m[15] * matrix.m[14]);
    new_matrix.m[15] = (this.m[12] * matrix.m[3]) + (this.m[13] * matrix.m[7]) + (this.m[14] * matrix.m[11]) + (this.m[15] * matrix.m[15]);
    return new_matrix;
};
/**
 * Negate self.
 * @method
 * @param {number} scalar
 * @return {Matrix}
 */
Matrix.prototype.negate = function(matrix){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.matrix.length; i < len; i++){
        this.matrix.m[i] = -this.matrix.m[i];
    }
    return new_matrix;
};
/**
 * Transpose self.
 * @method
 * @param {number} scalar
 * @return {Matrix}
 */
Matrix.prototype.transpose = function(){
    var new_matrix = new Matrix();
    new_matrix.m[0] = this.m[0];
    new_matrix.m[1] = this.m[4];
    new_matrix.m[2] = this.m[8];
    new_matrix.m[3] = this.m[12];
    new_matrix.m[4] = this.m[1];
    new_matrix.m[5] = this.m[5];
    new_matrix.m[6] = this.m[9];
    new_matrix.m[7] = this.m[13];
    new_matrix.m[8] = this.m[2];
    new_matrix.m[9] = this.m[6];
    new_matrix.m[10] = this.m[10];
    new_matrix.m[11] = this.m[14];
    new_matrix.m[12] = this.m[3];
    new_matrix.m[13] = this.m[7];
    new_matrix.m[14] = this.m[11];
    new_matrix.m[15] = this.m[15];
    return new_matrix;
};

/**
 * Constructs a rotation matrix, rotating by theta around the x-axis
 * @method
 * @static
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationX = function(theta){
    var rotation_matrix = new Matrix();
    var cos = Math.cos(theta);
    var sin = Math.sin(theta);
    rotation_matrix.m[0] = 1;
    rotation_matrix.m[5] = cos;
    rotation_matrix.m[6] = -sin;
    rotation_matrix.m[9] = sin;
    rotation_matrix.m[10] = cos;
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix, rotating by theta around the y-axis
 * @method
 * @static
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationY = function(theta){
    var rotation_matrix = new Matrix();
    var cos = Math.cos(theta);
    var sin = Math.sin(theta);
    rotation_matrix.m[0] = cos;
    rotation_matrix.m[2] = sin;
    rotation_matrix.m[5] = 1;
    rotation_matrix.m[8] = -sin;
    rotation_matrix.m[10] = cos;
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix, rotating by theta around the z-axis
 * @method
 * @static
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationZ = function(theta){
    var rotation_matrix = new Matrix();
    var cos = Math.cos(theta);
    var sin = Math.sin(theta);
    rotation_matrix.m[0] = cos;
    rotation_matrix.m[1] = -sin;
    rotation_matrix.m[4] = sin;
    rotation_matrix.m[5] = cos;
    rotation_matrix.m[10] = 1;
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix, rotating by theta around the axis
 * @method
 * @static
 * @param {Vector} axis
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationAxis = function(axis, theta){
    var rotation_matrix = new Matrix();
    var u = axis.normalize();
    var sin = Math.sin(theta);
    var cos = Math.cos(theta);
    var cos1 = 1-cos;
    var ux = u.x;
    var uy = u.y;
    var uz = u.z;
    var xy = ux * uy;
    var xz = ux * uz;
    var yz = uy * uz;
    rotation_matrix.m[0] = cos + ((ux*ux)*cos1);
    rotation_matrix.m[1] = (xy*cos1) - (uz*sin);
    rotation_matrix.m[2] = (xz*cos1)+(uy*sin);
    rotation_matrix.m[4] = (xy*cos1)+(uz*sin);
    rotation_matrix.m[5] = cos+((uy*uy)*cos1);
    rotation_matrix.m[6] = (yz*cos1)-(ux*sin);
    rotation_matrix.m[8] = (xz*cos1)-(uy*sin);
    rotation_matrix.m[9] = (yz*cos1)+(ux*sin);
    rotation_matrix.m[10] = cos + ((uz*uz)*cos1);
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix from pitch, yaw, and roll
 * @method
 * @static
 * @param {number} pitch
 * @param {number} yaw
 * @param {number} roll
 * @return {Matrix}
 */
Matrix.rotation = function(pitch, yaw, roll){
    return Matrix.rotationX(roll).multiply(Matrix.rotationZ(yaw)).multiply(Matrix.rotationY(pitch));
};
/**
 * Constructs a scaling matrix from x, y, and z scalars
 * @method
 * @static
 * @param {number} xscale
 * @param {number} yscale
 * @param {number} zscale
 * @return {Matrix}
 */
Matrix.translation = function(xscale, yscale, zscale){
    var translation_matrix = Matrix.identity();
    translation_matrix.m[0] = xscale;
    translation_matrix.m[5] = yscale;
    translation_matrix.m[1] = zscale;
    return translation_matrix;
};
/**
 * Constructs a translation matrix from x, y, and z distances
 * @method
 * @static
 * @param {number} xtrans
 * @param {number} ytrans
 * @param {number} ztrans
 * @return {Matrix}
 */
Matrix.translation = function(xtrans, ytrans, ztrans){
    var translation_matrix = Matrix.identity();
    translation_matrix.m[12] = xtrans;
    translation_matrix.m[13] = ytrans;
    translation_matrix.m[14] = ztrans;
    return translation_matrix;
};
/**
 * Constructs a scaling matrix from x, y, and z scale
 * @method
 * @static
 * @param {number} xtrans
 * @param {number} ytrans
 * @param {number} ztrans
 * @return {Matrix}
 */
Matrix.scale = function(xscale, yscale, zscale){
    var scaling_matrix = new Matrix();
    scaling_matrix.m[0] = xscale;
    scaling_matrix.m[5] = yscale;
    scaling_matrix.m[10] = zscale;
    scaling_matrix.m[15] = 1;
    return scaling_matrix;
};
/**
 * Constructs an identity matrix
 * @method
 * @static
 * @return {Matrix}
 */
Matrix.identity = function(){
    var identity = new Matrix();
    identity.m[0] = 1;
    identity.m[5] = 1;
    identity.m[10] = 1;
    identity.m[15] = 1;
    return identity;
};
/**
 * Constructs a zero matrix
 * @method
 * @static
 * @return {Matrix}
 */
Matrix.zero = function(){
    return new Matrix();
};
/**
 * Constructs a new matrix from an array
 * @method
 * @static
 * @return {Matrix}
 */
Matrix.fromArray = function(arr){
    var new_matrix = new Matrix();
    for (var i = 0; i < 16; i++){
        new_matrix.m[i] = arr[i];
    }
    return new_matrix;
};

module.exports = Matrix;<commit_msg>Remove duplicate translation method<commit_after>/**
 * 4x4 matrix.
 * @constructor
 * @this {Matrix}
 */
function Matrix(){
    /** @type {Array.<number>} */
    this.m = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
/**
 * Compare matrix with self for equality.
 * @method
 * @param {Matrix} matrix
 * @return {boolean}
 */
Matrix.prototype.equals = function(matrix){
    for (var i = 0, len = this.m.length; i < len; i++){
        if (this.m[i] !== matrix.m[i]){
            return false;
        }
    }
    return true;
};
/**
 * Add matrix to self.
 * @method
 * @param {Matrix} matrix
 * @return {Matrix}
 */
Matrix.prototype.add = function(matrix){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.m.length; i < len; i++){
        new_matrix.m[i] = this.m[i] + matrix.m[i];
    }
    return new_matrix;
};
/**
 * Subtract matrix from self.
 * @method
 * @param {Matrix} matrix
 * @return {Matrix}
 */
Matrix.prototype.subtract = function(matrix){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.m.length; i < len; i++){
        this.m[i] = this.m[i] - matrix.m[i];
    }
    return new_matrix;
};
/**
 * Multiply self by scalar.
 * @method
 * @param {number} scalar
 * @return {Matrix}
 */
Matrix.prototype.multiplyScalar = function(scalar){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.m.length; i < len; i++){
        this.m[i] = this.m[i] * scalar;
    }
    return new_matrix;
};
/**
 * Multiply self by matrix.
 * @method
 * @param {Matrix} matrix
 * @return {Matrix}
 */
Matrix.prototype.multiply = function(matrix){
    var new_matrix = new Matrix();
    new_matrix.m[0] = (this.m[0] * matrix.m[0]) + (this.m[1] * matrix.m[4]) + (this.m[2] * matrix.m[8]) + (this.m[3] * matrix.m[12]);
    new_matrix.m[1] = (this.m[0] * matrix.m[1]) + (this.m[1] * matrix.m[5]) + (this.m[2] * matrix.m[9]) + (this.m[3] * matrix.m[13]);
    new_matrix.m[2] = (this.m[0] * matrix.m[2]) + (this.m[1] * matrix.m[6]) + (this.m[2] * matrix.m[10]) + (this.m[3] * matrix.m[14]);
    new_matrix.m[3] = (this.m[0] * matrix.m[3]) + (this.m[1] * matrix.m[7]) + (this.m[2] * matrix.m[11]) + (this.m[3] * matrix.m[15]);
    new_matrix.m[4] = (this.m[4] * matrix.m[0]) + (this.m[5] * matrix.m[4]) + (this.m[6] * matrix.m[8]) + (this.m[7] * matrix.m[12]);
    new_matrix.m[5] = (this.m[4] * matrix.m[1]) + (this.m[5] * matrix.m[5]) + (this.m[6] * matrix.m[9]) + (this.m[7] * matrix.m[13]);
    new_matrix.m[6] = (this.m[4] * matrix.m[2]) + (this.m[5] * matrix.m[6]) + (this.m[6] * matrix.m[10]) + (this.m[7] * matrix.m[14]);
    new_matrix.m[7] = (this.m[4] * matrix.m[3]) + (this.m[5] * matrix.m[7]) + (this.m[6] * matrix.m[11]) + (this.m[7] * matrix.m[15]);
    new_matrix.m[8] = (this.m[8] * matrix.m[0]) + (this.m[9] * matrix.m[4]) + (this.m[10] * matrix.m[8]) + (this.m[11] * matrix.m[12]);
    new_matrix.m[9] = (this.m[8] * matrix.m[1]) + (this.m[9] * matrix.m[5]) + (this.m[10] * matrix.m[9]) + (this.m[11] * matrix.m[13]);
    new_matrix.m[10] = (this.m[8] * matrix.m[2]) + (this.m[9] * matrix.m[6]) + (this.m[10] * matrix.m[10]) + (this.m[11] * matrix.m[14]);
    new_matrix.m[11] = (this.m[8] * matrix.m[3]) + (this.m[9] * matrix.m[7]) + (this.m[10] * matrix.m[11]) + (this.m[11] * matrix.m[15]);
    new_matrix.m[12] = (this.m[12] * matrix.m[0]) + (this.m[13] * matrix.m[4]) + (this.m[14] * matrix.m[8]) + (this.m[15] * matrix.m[12]);
    new_matrix.m[13] = (this.m[12] * matrix.m[1]) + (this.m[13] * matrix.m[5]) + (this.m[14] * matrix.m[9]) + (this.m[15] * matrix.m[13]);
    new_matrix.m[14] = (this.m[12] * matrix.m[2]) + (this.m[13] * matrix.m[6]) + (this.m[14] * matrix.m[10]) + (this.m[15] * matrix.m[14]);
    new_matrix.m[15] = (this.m[12] * matrix.m[3]) + (this.m[13] * matrix.m[7]) + (this.m[14] * matrix.m[11]) + (this.m[15] * matrix.m[15]);
    return new_matrix;
};
/**
 * Negate self.
 * @method
 * @param {number} scalar
 * @return {Matrix}
 */
Matrix.prototype.negate = function(matrix){
    var new_matrix = new Matrix();
    for (var i = 0, len = this.matrix.length; i < len; i++){
        this.matrix.m[i] = -this.matrix.m[i];
    }
    return new_matrix;
};
/**
 * Transpose self.
 * @method
 * @param {number} scalar
 * @return {Matrix}
 */
Matrix.prototype.transpose = function(){
    var new_matrix = new Matrix();
    new_matrix.m[0] = this.m[0];
    new_matrix.m[1] = this.m[4];
    new_matrix.m[2] = this.m[8];
    new_matrix.m[3] = this.m[12];
    new_matrix.m[4] = this.m[1];
    new_matrix.m[5] = this.m[5];
    new_matrix.m[6] = this.m[9];
    new_matrix.m[7] = this.m[13];
    new_matrix.m[8] = this.m[2];
    new_matrix.m[9] = this.m[6];
    new_matrix.m[10] = this.m[10];
    new_matrix.m[11] = this.m[14];
    new_matrix.m[12] = this.m[3];
    new_matrix.m[13] = this.m[7];
    new_matrix.m[14] = this.m[11];
    new_matrix.m[15] = this.m[15];
    return new_matrix;
};

/**
 * Constructs a rotation matrix, rotating by theta around the x-axis
 * @method
 * @static
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationX = function(theta){
    var rotation_matrix = new Matrix();
    var cos = Math.cos(theta);
    var sin = Math.sin(theta);
    rotation_matrix.m[0] = 1;
    rotation_matrix.m[5] = cos;
    rotation_matrix.m[6] = -sin;
    rotation_matrix.m[9] = sin;
    rotation_matrix.m[10] = cos;
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix, rotating by theta around the y-axis
 * @method
 * @static
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationY = function(theta){
    var rotation_matrix = new Matrix();
    var cos = Math.cos(theta);
    var sin = Math.sin(theta);
    rotation_matrix.m[0] = cos;
    rotation_matrix.m[2] = sin;
    rotation_matrix.m[5] = 1;
    rotation_matrix.m[8] = -sin;
    rotation_matrix.m[10] = cos;
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix, rotating by theta around the z-axis
 * @method
 * @static
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationZ = function(theta){
    var rotation_matrix = new Matrix();
    var cos = Math.cos(theta);
    var sin = Math.sin(theta);
    rotation_matrix.m[0] = cos;
    rotation_matrix.m[1] = -sin;
    rotation_matrix.m[4] = sin;
    rotation_matrix.m[5] = cos;
    rotation_matrix.m[10] = 1;
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix, rotating by theta around the axis
 * @method
 * @static
 * @param {Vector} axis
 * @param {number} theta
 * @return {Matrix}
 */
Matrix.rotationAxis = function(axis, theta){
    var rotation_matrix = new Matrix();
    var u = axis.normalize();
    var sin = Math.sin(theta);
    var cos = Math.cos(theta);
    var cos1 = 1-cos;
    var ux = u.x;
    var uy = u.y;
    var uz = u.z;
    var xy = ux * uy;
    var xz = ux * uz;
    var yz = uy * uz;
    rotation_matrix.m[0] = cos + ((ux*ux)*cos1);
    rotation_matrix.m[1] = (xy*cos1) - (uz*sin);
    rotation_matrix.m[2] = (xz*cos1)+(uy*sin);
    rotation_matrix.m[4] = (xy*cos1)+(uz*sin);
    rotation_matrix.m[5] = cos+((uy*uy)*cos1);
    rotation_matrix.m[6] = (yz*cos1)-(ux*sin);
    rotation_matrix.m[8] = (xz*cos1)-(uy*sin);
    rotation_matrix.m[9] = (yz*cos1)+(ux*sin);
    rotation_matrix.m[10] = cos + ((uz*uz)*cos1);
    rotation_matrix.m[15] = 1;
    return rotation_matrix;
};
/**
 * Constructs a rotation matrix from pitch, yaw, and roll
 * @method
 * @static
 * @param {number} pitch
 * @param {number} yaw
 * @param {number} roll
 * @return {Matrix}
 */
Matrix.rotation = function(pitch, yaw, roll){
    return Matrix.rotationX(roll).multiply(Matrix.rotationZ(yaw)).multiply(Matrix.rotationY(pitch));
};
/**
 * Constructs a translation matrix from x, y, and z distances
 * @method
 * @static
 * @param {number} xtrans
 * @param {number} ytrans
 * @param {number} ztrans
 * @return {Matrix}
 */
Matrix.translation = function(xtrans, ytrans, ztrans){
    var translation_matrix = Matrix.identity();
    translation_matrix.m[12] = xtrans;
    translation_matrix.m[13] = ytrans;
    translation_matrix.m[14] = ztrans;
    return translation_matrix;
};
/**
 * Constructs a scaling matrix from x, y, and z scale
 * @method
 * @static
 * @param {number} xtrans
 * @param {number} ytrans
 * @param {number} ztrans
 * @return {Matrix}
 */
Matrix.scale = function(xscale, yscale, zscale){
    var scaling_matrix = new Matrix();
    scaling_matrix.m[0] = xscale;
    scaling_matrix.m[5] = yscale;
    scaling_matrix.m[10] = zscale;
    scaling_matrix.m[15] = 1;
    return scaling_matrix;
};
/**
 * Constructs an identity matrix
 * @method
 * @static
 * @return {Matrix}
 */
Matrix.identity = function(){
    var identity = new Matrix();
    identity.m[0] = 1;
    identity.m[5] = 1;
    identity.m[10] = 1;
    identity.m[15] = 1;
    return identity;
};
/**
 * Constructs a zero matrix
 * @method
 * @static
 * @return {Matrix}
 */
Matrix.zero = function(){
    return new Matrix();
};
/**
 * Constructs a new matrix from an array
 * @method
 * @static
 * @return {Matrix}
 */
Matrix.fromArray = function(arr){
    var new_matrix = new Matrix();
    for (var i = 0; i < 16; i++){
        new_matrix.m[i] = arr[i];
    }
    return new_matrix;
};

module.exports = Matrix;<|endoftext|>


<commit_before>angular.module('opal.controllers').controller(
    'EpisodeListCtrl', function($scope, $q, $http, $cookieStore,
                                $location, $routeParams,
                                $modal,
                                Episode, schema, episodes, options,
                                profile,
                                episodeVisibility, viewDischarged){

        var version = window.version;
        // if($cookieStore.get('opal.seenVersion') !=  version){
        //     $('#changelogTooltip').tooltip('show');
        //     $('#changelog').on('hidden.bs.modal', function(){
        //         $cookieStore.put('opal.seenVersion', version)
        //         $('#changelogTooltip').tooltip('hide');
        //     });
        // }

        $scope.state = 'normal';

	    $scope.rix = 0; // row index
	    $scope.cix = 0; // column index
	    $scope.iix = 0; // item index

	    $scope.mouseRix = -1; // index of row mouse is currently over
	    $scope.mouseCix = -1; // index of column mouse is currently over
        $scope._ =  _;

	    $scope.query = {hospital_number: '', name: ''};

        if(!$routeParams.tag){
            var tag =  $cookieStore.get('opal.currentTag') || 'mine';
            $location.path("/list/" + tag);
            return
        }
        $scope.currentTag = $routeParams.tag;

        if(!$routeParams.subtag){
            $scope.currentSubTag = 'all';
        }else{
            $scope.currentSubTag = $routeParams.subtag;
        }
        $scope.columns = schema.columns;

        $scope.profile = profile;
        $scope.tag_display = options.tag_display;

        $scope.episode_lookup = {};
        _.each(episodes,  function(e){
            if(e.tagging){         // Shouldn't be needed but occasionally happens in migration breakage
                _.each(_.keys(e.tagging[0]), function(tag_name){
                    if(tag_name && !_.has($scope.episode_lookup, tag_name)){
                        $scope.episode_lookup[tag_name] = [];
                    };
                    $scope.episode_lookup[tag_name].push(e.id);
                })
                    }
        });

	    function getVisibleEpisodes() {
		    var visibleEpisodes = [];
            var episode_list;

            if($scope.episode_lookup){
                if($scope.currentSubTag == 'all'){
                    episode_list = $scope.episode_lookup[$scope.currentTag];
                }else{
                    episode_list = $scope.episode_lookup[$scope.currentSubTag];
                }
            }else{
                episode_list = [];
            }

            visibleEpisodes = _.map(
                _.filter(episode_list, function(id){
                    return episodeVisibility(episodes[id], $scope, viewDischarged)
                }),
                function(id){
                    return episodes[id];
                })
		    visibleEpisodes.sort(compareEpisodes);
		    return visibleEpisodes;
	    };

	    $scope.rows = getVisibleEpisodes();

	    function compareEpisodes(p1, p2) {
		    return p1.compare(p2);
	    };

        $scope.jumpToTag = function(tag){
            if(_.contains(_.keys(options.tag_hierarchy), tag)){
                $location.path('/list/'+tag)
            }else{

                for(var prop in options.tag_hierarchy){
                    if(options.tag_hierarchy.hasOwnProperty(prop)){
                        if(_.contains(_.values(options.tag_hierarchy[prop]), tag)){
                            $location.path('/list/'+ prop + '/' + tag)
                        }
                    }
                }

            }
        }

        $scope.otherTags = function(item){
            return _.filter(_.keys(item.makeCopy()), function(tag){
                return item[tag];
            });
        }

	    $scope.$watch('currentTag', function() {
		    $cookieStore.put('opal.currentTag', $scope.currentTag);
            if($scope.currentTag != $routeParams.tag){
                $scope.state = 'reloading'
            }
            $location.path('/list/' +  $scope.currentTag);
	    });

	    $scope.$watch('currentSubTag', function(){
		    $cookieStore.put('opal.currentSubTag', $scope.currentSubTag);
            if($scope.currentSubTag == 'all'){
                if($routeParams.subtag && $scope.currentSubTag != $routeParams.subtag){
                    $scope.state = 'reloading'
                }
                $location.path('/list/' +  $scope.currentTag);
            }else{
                if($scope.currentSubTag != $routeParams.subtag){
                    $scope.state = 'reloading'
                }
                $location.path('/list/' +  $scope.currentTag + '/' +  $scope.currentSubTag);
            }
	    });

        $scope.showSubtags = function(withsubtags){
            var show =  _.contains(withsubtags, $scope.currentTag);
            return show
        };

	    $scope.$watch('query.hospital_number', function() {
		    $scope.rows = getVisibleEpisodes();
	    });

	    $scope.$watch('query.name', function() {
		    $scope.rows = getVisibleEpisodes();
	    });

	    $scope.$on('keydown', function(event, e) {
		    if ($scope.state == 'normal') {
			    switch (e.keyCode) {
			    case 37: // left
				    goLeft();
				    break;
			    case 39: // right
				    goRight();
				    break;
			    case 38: // up
				    goUp();
				    break;
			    case 40: // down
				    goDown();
				    break;
			    case 13: // enter
			    case 113: // F2
				    $scope.editItem($scope.rix, $scope.cix, $scope.iix);
                    e.preventDefault();
				    break;
			    case 8: // backspace
				    e.preventDefault();
			    case 46: // delete
				    $scope.deleteItem($scope.rix, $scope.cix, $scope.iix);
				    break;
			    case 191: // question mark
				    if(e.shiftKey){
					    showKeyboardShortcuts();
				    }
				    break;
                case 78: // n
                    $scope.addEpisode();
                    e.preventDefault();
                    break;
			    };
		    };
	    });

	    function getColumnName(cix) {
		    return $scope.columns[cix].name;
	    };

	    function getRowIxFromEpisodeId(episodeId) {
		    for (var rix = 0; rix < $scope.rows.length; rix++) {
			    if ($scope.rows[rix].id == episodeId) {
				    return rix;
			    }
		    };
		    return -1;
	    };

	    function getEpisode(rix) {
		    return $scope.rows[rix];
	    };

	    $scope.print = function() {
		    window.print();
	    };

	    $scope.selectItem = function(rix, cix, iix) {
		    $scope.rix = rix;
		    $scope.cix = cix;
		    $scope.iix = iix;
	    };

	    $scope.focusOnQuery = function() {
		    $scope.selectItem(-1, -1, -1);
		    $scope.state = 'search';
	    };

	    $scope.blurOnQuery = function() {
		    if ($scope.rix == -1) {
			    $scope.selectItem(0, 0, 0);
		    };
		    $scope.state = 'normal';
	    };

	    $scope.addEpisode = function() {
            if(profile.readonly){
                return null;
            };
		    var hospitalNumberModal;

		    $scope.state = 'modal';

		    hospitalNumberModal = $modal.open({
			    templateUrl: '/templates/modals/hospital_number.html/',
			    controller: 'HospitalNumberCtrl',
                resolve: {
                    schema: function(){ return schema },
                    options: function(){ return options },
                    tags: function(){ return {tag: $scope.currentTag,
                                              subtag: $scope.currentSubTag}}
                }
		    }).result.then(
                function(episode) {
		            // User has either retrieved an existing episode or created a new one,
		            // or has cancelled the process at some point.
		            //
		            // This ensures that the relevant episode is added to the table and
		            // selected.
		            var rowIx;
		            $scope.state = 'normal';
		            if (episode) {
			            episodes[episode.id] = episode;
                        if($scope.episode_lookup[$scope.currentTag] === undefined){
                            $scope.episode_lookup[$scope.currentTag] = [];
                        }
                        $scope.episode_lookup[$scope.currentTag].push(episode.id);
                        if($scope.currentSubTag != 'all'){
                            $scope.episode_lookup[$scope.currentSubTag].push(episode.id)
                        }
			            $scope.rows = getVisibleEpisodes();
			            rowIx = getRowIxFromEpisodeId(episode.id);
			            $scope.selectItem(rowIx, 0, 0);
		            };
	            },
                function(reason){
                    // The modal has been dismissed. Practically speaking this means
                    // that the Angular UI called dismiss rather than our cancel()
                    // method on the OPAL controller. We just need to re-set in order
                    // to re-enable keybard listeners.
                    $scope.state = 'normal';
                });
	    };

	    $scope.dischargeEpisode = function(rix, event) {
		    var modal;
		    var episode = getEpisode(rix);

            if(profile.readonly){
                return null;
            };

		    // This is required to prevent the page reloading
		    event.preventDefault();

		    $scope.state = 'modal';

		    modal = $modal.open({
			    templateUrl: '/templates/modals/discharge_episode.html/',
			    controller: 'DischargeEpisodeCtrl',
			    resolve: {
				    episode: function() { return episode; },
				    currentTag: function() { return $scope.currentTag; },
                    currentSubTag: function() { return $scope.currentSubTag; }
			    }
		    }).result.then(function(result) {
			    $scope.state = 'normal';

			    if (result == 'discharged') {
				    $scope.rows = getVisibleEpisodes();
				    $scope.selectItem(0, $scope.cix, 0);
			    };
		    });
	    };

        // TODO: Test This!
        $scope.removeFromMine = function(rix, event){
            if(profile.readonly){
                return null;
            };

            event.preventDefault();

            var modal;
            var episode = getEpisode(rix);
            var location = episode.location[0];
            editing = location.makeCopy();
            // TODO:
            // URGENT: Make this use new tagging
            delete editing.tagging.mine;
            location.save(editing).then(function(result){
                $scope.rows = getVisibleEpisodes();
            })

        };

	    $scope.editItem = function(rix, cix, iix) {
		    var modal;
		    var columnName = getColumnName(cix);
		    var episode = getEpisode(rix);
		    var item;

            if(profile.readonly){
                return null;
            };

		    if (iix == episode.getNumberOfItems(columnName)) {
			    item = episode.newItem(columnName, {schema: schema});
		    } else {
			    item = episode.getItem(columnName, iix);
		    };

		    $scope.selectItem(rix, cix, iix);
		    $scope.state = 'modal';

		    modal = $modal.open({
			    templateUrl: '/templates/modals/' + columnName + '.html/',
			    controller: 'EditItemCtrl',
			    resolve: {
				    item: function() { return item; },
				    options: function() { return options; },
                    episode: function() { return episode; }
			    }
		    });

		    modal.result.then(function(result) {
			    $scope.state = 'normal';

			    if (columnName == 'location') {
				    // User may have removed current tag
				    $scope.rows = getVisibleEpisodes();
				    $scope.selectItem(getRowIxFromEpisodeId(episode.id), $scope.cix, 0);
			    }

			    if (result == 'save-and-add-another') {
				    $scope.editItem(rix, cix, episode.getNumberOfItems(columnName));
			    };
		    }, function(){
                $scope.state = 'normal';
            });
	    };

	    $scope.deleteItem = function(rix, cix, iix) {
		    var modal;
		    var columnName = getColumnName(cix);
		    var episode = getEpisode(rix);
		    var item = episode.getItem(columnName, iix);

            if(profile.readonly){
                return null;
            };

            if (schema.isReadOnly(columnName)) {
                // Cannont delete readonly columns
                return;
            }

		    if (schema.isSingleton(columnName)) {
			    // Cannot delete singleton
			    return;
		    }

		    if (!angular.isDefined(item)) {
			    // Cannot delete 'Add'
			    return;
		    }

		    $scope.state = 'modal'
		    modal = $modal.open({
			    templateUrl: '/templates/modals/delete_item_confirmation.html/',
			    controller: 'DeleteItemConfirmationCtrl',
			    resolve: {
				    item: function() { return item; }
			    }
		    });

		    modal.result.then(function(result) {
			    $scope.state = 'normal';
		    });
	    };

	    $scope.mouseEnter = function(rix, cix) {
		    $scope.mouseRix = rix;
		    $scope.mouseCix = cix;
	    }

	    $scope.mouseLeave = function() {
		    $scope.mouseRix = -1;
		    $scope.mouseCix = -1;
	    }

        function showKeyboardShortcuts(){
		    // TODO fix this
            $('#keyboard-shortcuts').modal();
        };

	    function goLeft() {
		    if ($scope.cix > 0) {
			    $scope.cix--;
			    $scope.iix = 0;
		    };
	    };

	    function goRight() {
		    if ($scope.cix < $scope.columns.length - 1) {
			    $scope.cix++;
			    $scope.iix = 0;
		    };
	    };

	    function goUp() {
		    var episode;
		    var columnName = getColumnName($scope.cix);

		    if ($scope.iix > 0) {
			    $scope.iix--;
		    } else if ($scope.rix > 0) {
			    $scope.rix--;
			    if (schema.isSingleton(columnName)) {
				    $scope.iix = 0;
			    } else {
				    episode = getEpisode($scope.rix);
				    $scope.iix = episode.getNumberOfItems(columnName);
			    };
		    };
	    };

	    function goDown() {
		    var episode = getEpisode($scope.rix);
		    var columnName = getColumnName($scope.cix);

		    if (!schema.isSingleton(columnName) &&
		        ($scope.iix < episode.getNumberOfItems(columnName))) {
			    $scope.iix++;
		    } else if ($scope.rix < $scope.rows.length - 1) {
			    $scope.rix++;
			    $scope.iix = 0;
		    };
	    };
    });
<commit_msg>Name of the column from which we edit teams has changed. refs openhealthcare/elcid#338<commit_after>angular.module('opal.controllers').controller(
    'EpisodeListCtrl', function($scope, $q, $http, $cookieStore,
                                $location, $routeParams,
                                $modal,
                                Episode, schema, episodes, options,
                                profile,
                                episodeVisibility, viewDischarged){

        var version = window.version;
        // if($cookieStore.get('opal.seenVersion') !=  version){
        //     $('#changelogTooltip').tooltip('show');
        //     $('#changelog').on('hidden.bs.modal', function(){
        //         $cookieStore.put('opal.seenVersion', version)
        //         $('#changelogTooltip').tooltip('hide');
        //     });
        // }

        $scope.state = 'normal';

	    $scope.rix = 0; // row index
	    $scope.cix = 0; // column index
	    $scope.iix = 0; // item index

	    $scope.mouseRix = -1; // index of row mouse is currently over
	    $scope.mouseCix = -1; // index of column mouse is currently over
        $scope._ =  _;

	    $scope.query = {hospital_number: '', name: ''};

        if(!$routeParams.tag){
            var tag =  $cookieStore.get('opal.currentTag') || 'mine';
            $location.path("/list/" + tag);
            return
        }
        $scope.currentTag = $routeParams.tag;

        if(!$routeParams.subtag){
            $scope.currentSubTag = 'all';
        }else{
            $scope.currentSubTag = $routeParams.subtag;
        }
        $scope.columns = schema.columns;

        $scope.profile = profile;
        $scope.tag_display = options.tag_display;

        $scope.episode_lookup = {};
        _.each(episodes,  function(e){
            if(e.tagging){         // Shouldn't be needed but occasionally happens in migration breakage
                _.each(_.keys(e.tagging[0]), function(tag_name){
                    if(tag_name && !_.has($scope.episode_lookup, tag_name)){
                        $scope.episode_lookup[tag_name] = [];
                    };
                    $scope.episode_lookup[tag_name].push(e.id);
                })
                    }
        });

	    function getVisibleEpisodes() {
		    var visibleEpisodes = [];
            var episode_list;

            if($scope.episode_lookup){
                if($scope.currentSubTag == 'all'){
                    episode_list = $scope.episode_lookup[$scope.currentTag];
                }else{
                    episode_list = $scope.episode_lookup[$scope.currentSubTag];
                }
            }else{
                episode_list = [];
            }

            visibleEpisodes = _.map(
                _.filter(episode_list, function(id){
                    return episodeVisibility(episodes[id], $scope, viewDischarged)
                }),
                function(id){
                    return episodes[id];
                })
		    visibleEpisodes.sort(compareEpisodes);
		    return visibleEpisodes;
	    };

	    $scope.rows = getVisibleEpisodes();

	    function compareEpisodes(p1, p2) {
		    return p1.compare(p2);
	    };

        $scope.jumpToTag = function(tag){
            if(_.contains(_.keys(options.tag_hierarchy), tag)){
                $location.path('/list/'+tag)
            }else{

                for(var prop in options.tag_hierarchy){
                    if(options.tag_hierarchy.hasOwnProperty(prop)){
                        if(_.contains(_.values(options.tag_hierarchy[prop]), tag)){
                            $location.path('/list/'+ prop + '/' + tag)
                        }
                    }
                }

            }
        }

        $scope.otherTags = function(item){
            return _.filter(_.keys(item.makeCopy()), function(tag){
                return item[tag];
            });
        }

	    $scope.$watch('currentTag', function() {
		    $cookieStore.put('opal.currentTag', $scope.currentTag);
            if($scope.currentTag != $routeParams.tag){
                $scope.state = 'reloading'
            }
            $location.path('/list/' +  $scope.currentTag);
	    });

	    $scope.$watch('currentSubTag', function(){
		    $cookieStore.put('opal.currentSubTag', $scope.currentSubTag);
            if($scope.currentSubTag == 'all'){
                if($routeParams.subtag && $scope.currentSubTag != $routeParams.subtag){
                    $scope.state = 'reloading'
                }
                $location.path('/list/' +  $scope.currentTag);
            }else{
                if($scope.currentSubTag != $routeParams.subtag){
                    $scope.state = 'reloading'
                }
                $location.path('/list/' +  $scope.currentTag + '/' +  $scope.currentSubTag);
            }
	    });

        $scope.showSubtags = function(withsubtags){
            var show =  _.contains(withsubtags, $scope.currentTag);
            return show
        };

	    $scope.$watch('query.hospital_number', function() {
		    $scope.rows = getVisibleEpisodes();
	    });

	    $scope.$watch('query.name', function() {
		    $scope.rows = getVisibleEpisodes();
	    });

	    $scope.$on('keydown', function(event, e) {
		    if ($scope.state == 'normal') {
			    switch (e.keyCode) {
			    case 37: // left
				    goLeft();
				    break;
			    case 39: // right
				    goRight();
				    break;
			    case 38: // up
				    goUp();
				    break;
			    case 40: // down
				    goDown();
				    break;
			    case 13: // enter
			    case 113: // F2
				    $scope.editItem($scope.rix, $scope.cix, $scope.iix);
                    e.preventDefault();
				    break;
			    case 8: // backspace
				    e.preventDefault();
			    case 46: // delete
				    $scope.deleteItem($scope.rix, $scope.cix, $scope.iix);
				    break;
			    case 191: // question mark
				    if(e.shiftKey){
					    showKeyboardShortcuts();
				    }
				    break;
                case 78: // n
                    $scope.addEpisode();
                    e.preventDefault();
                    break;
			    };
		    };
	    });

	    function getColumnName(cix) {
		    return $scope.columns[cix].name;
	    };

	    function getRowIxFromEpisodeId(episodeId) {
		    for (var rix = 0; rix < $scope.rows.length; rix++) {
			    if ($scope.rows[rix].id == episodeId) {
				    return rix;
			    }
		    };
		    return -1;
	    };

	    function getEpisode(rix) {
		    return $scope.rows[rix];
	    };

	    $scope.print = function() {
		    window.print();
	    };

	    $scope.selectItem = function(rix, cix, iix) {
		    $scope.rix = rix;
		    $scope.cix = cix;
		    $scope.iix = iix;
	    };

	    $scope.focusOnQuery = function() {
		    $scope.selectItem(-1, -1, -1);
		    $scope.state = 'search';
	    };

	    $scope.blurOnQuery = function() {
		    if ($scope.rix == -1) {
			    $scope.selectItem(0, 0, 0);
		    };
		    $scope.state = 'normal';
	    };

	    $scope.addEpisode = function() {
            if(profile.readonly){
                return null;
            };
		    var hospitalNumberModal;

		    $scope.state = 'modal';

		    hospitalNumberModal = $modal.open({
			    templateUrl: '/templates/modals/hospital_number.html/',
			    controller: 'HospitalNumberCtrl',
                resolve: {
                    schema: function(){ return schema },
                    options: function(){ return options },
                    tags: function(){ return {tag: $scope.currentTag,
                                              subtag: $scope.currentSubTag}}
                }
		    }).result.then(
                function(episode) {
		            // User has either retrieved an existing episode or created a new one,
		            // or has cancelled the process at some point.
		            //
		            // This ensures that the relevant episode is added to the table and
		            // selected.
		            var rowIx;
		            $scope.state = 'normal';
		            if (episode) {
			            episodes[episode.id] = episode;
                        if($scope.episode_lookup[$scope.currentTag] === undefined){
                            $scope.episode_lookup[$scope.currentTag] = [];
                        }
                        $scope.episode_lookup[$scope.currentTag].push(episode.id);
                        if($scope.currentSubTag != 'all'){
                            $scope.episode_lookup[$scope.currentSubTag].push(episode.id)
                        }
			            $scope.rows = getVisibleEpisodes();
			            rowIx = getRowIxFromEpisodeId(episode.id);
			            $scope.selectItem(rowIx, 0, 0);
		            };
	            },
                function(reason){
                    // The modal has been dismissed. Practically speaking this means
                    // that the Angular UI called dismiss rather than our cancel()
                    // method on the OPAL controller. We just need to re-set in order
                    // to re-enable keybard listeners.
                    $scope.state = 'normal';
                });
	    };

	    $scope.dischargeEpisode = function(rix, event) {
		    var modal;
		    var episode = getEpisode(rix);

            if(profile.readonly){
                return null;
            };

		    // This is required to prevent the page reloading
		    event.preventDefault();

		    $scope.state = 'modal';

		    modal = $modal.open({
			    templateUrl: '/templates/modals/discharge_episode.html/',
			    controller: 'DischargeEpisodeCtrl',
			    resolve: {
				    episode: function() { return episode; },
				    currentTag: function() { return $scope.currentTag; },
                    currentSubTag: function() { return $scope.currentSubTag; }
			    }
		    }).result.then(function(result) {
			    $scope.state = 'normal';

			    if (result == 'discharged') {
				    $scope.rows = getVisibleEpisodes();
				    $scope.selectItem(0, $scope.cix, 0);
			    };
		    });
	    };

        // TODO: Test This!
        $scope.removeFromMine = function(rix, event){
            if(profile.readonly){
                return null;
            };

            event.preventDefault();

            var modal;
            var episode = getEpisode(rix);
            var location = episode.location[0];
            editing = location.makeCopy();
            // TODO:
            // URGENT: Make this use new tagging
            delete editing.tagging.mine;
            location.save(editing).then(function(result){
                $scope.rows = getVisibleEpisodes();
            })

        };

	    $scope.editItem = function(rix, cix, iix) {
		    var modal;
		    var columnName = getColumnName(cix);
		    var episode = getEpisode(rix);
		    var item;

            if(profile.readonly){
                return null;
            };

		    if (iix == episode.getNumberOfItems(columnName)) {
			    item = episode.newItem(columnName, {schema: schema});
		    } else {
			    item = episode.getItem(columnName, iix);
		    };

		    $scope.selectItem(rix, cix, iix);
		    $scope.state = 'modal';

		    modal = $modal.open({
			    templateUrl: '/templates/modals/' + columnName + '.html/',
			    controller: 'EditItemCtrl',
			    resolve: {
				    item: function() { return item; },
				    options: function() { return options; },
                    episode: function() { return episode; }
			    }
		    });

		    modal.result.then(function(result) {
			    $scope.state = 'normal';

			    if (columnName == 'tagging') {
				    // User may have removed current tag
				    $scope.rows = getVisibleEpisodes();
				    $scope.selectItem(getRowIxFromEpisodeId(episode.id), $scope.cix, 0);
			    }

			    if (result == 'save-and-add-another') {
				    $scope.editItem(rix, cix, episode.getNumberOfItems(columnName));
			    };
		    }, function(){
                $scope.state = 'normal';
            });
	    };

	    $scope.deleteItem = function(rix, cix, iix) {
		    var modal;
		    var columnName = getColumnName(cix);
		    var episode = getEpisode(rix);
		    var item = episode.getItem(columnName, iix);

            if(profile.readonly){
                return null;
            };

            if (schema.isReadOnly(columnName)) {
                // Cannont delete readonly columns
                return;
            }

		    if (schema.isSingleton(columnName)) {
			    // Cannot delete singleton
			    return;
		    }

		    if (!angular.isDefined(item)) {
			    // Cannot delete 'Add'
			    return;
		    }

		    $scope.state = 'modal'
		    modal = $modal.open({
			    templateUrl: '/templates/modals/delete_item_confirmation.html/',
			    controller: 'DeleteItemConfirmationCtrl',
			    resolve: {
				    item: function() { return item; }
			    }
		    });

		    modal.result.then(function(result) {
			    $scope.state = 'normal';
		    });
	    };

	    $scope.mouseEnter = function(rix, cix) {
		    $scope.mouseRix = rix;
		    $scope.mouseCix = cix;
	    }

	    $scope.mouseLeave = function() {
		    $scope.mouseRix = -1;
		    $scope.mouseCix = -1;
	    }

        function showKeyboardShortcuts(){
		    // TODO fix this
            $('#keyboard-shortcuts').modal();
        };

	    function goLeft() {
		    if ($scope.cix > 0) {
			    $scope.cix--;
			    $scope.iix = 0;
		    };
	    };

	    function goRight() {
		    if ($scope.cix < $scope.columns.length - 1) {
			    $scope.cix++;
			    $scope.iix = 0;
		    };
	    };

	    function goUp() {
		    var episode;
		    var columnName = getColumnName($scope.cix);

		    if ($scope.iix > 0) {
			    $scope.iix--;
		    } else if ($scope.rix > 0) {
			    $scope.rix--;
			    if (schema.isSingleton(columnName)) {
				    $scope.iix = 0;
			    } else {
				    episode = getEpisode($scope.rix);
				    $scope.iix = episode.getNumberOfItems(columnName);
			    };
		    };
	    };

	    function goDown() {
		    var episode = getEpisode($scope.rix);
		    var columnName = getColumnName($scope.cix);

		    if (!schema.isSingleton(columnName) &&
		        ($scope.iix < episode.getNumberOfItems(columnName))) {
			    $scope.iix++;
		    } else if ($scope.rix < $scope.rows.length - 1) {
			    $scope.rix++;
			    $scope.iix = 0;
		    };
	    };
    });
<|endoftext|>