<commit_before>angular.module('Directory.items.controllers', ['Directory.loader', 'Directory.user', 'Directory.items.models', 'Directory.entities.models', 'Directory.people.models', 'prxSearch', 'Directory.storage', 'ngRoute', 'ngSanitize'])
.controller('ItemsCtrl', [ '$scope', 'Item', 'Loader', 'Me', 'Storage', function ItemsCtrl($scope, Item, Loader, Me, Storage) {

  $scope.Storage = Storage;

  Me.authenticated(function (data) {
    if ($scope.collectionId) {
      $scope.items = Loader.page(Item.query(), 'Items');
    }
  });

  $scope.startUpload = function() {
    var newFiles = [];
    var newImages = [];
    $scope.$emit('filesAdded', newFiles);
  }

}])
.controller('ItemCtrl', ['$scope', '$timeout', '$q', '$modal', 'Item', 'Loader', 'Me', '$routeParams', 'Collection', 'Entity', '$location', 'SearchResults', 'Storage', '$window', function ItemCtrl($scope, $timeout, $q, $modal, Item, Loader, Me, $routeParams, Collection, Entity, $location, SearchResults, Storage, $window) {

  $scope.Storage = Storage;

  $scope.storageModal = $modal({template: '/assets/items/storage.html', persist: false, show: false, backdrop: 'static', scope: $scope});

  if ($routeParams.id) {
    Loader.page(Item.get({collectionId:$routeParams.collectionId, id: $routeParams.id}), Collection.get({id:$routeParams.collectionId}), Collection.query(), 'Item-v2/'+$routeParams.id, $scope);
  }

  SearchResults.setCurrentIndex({id:$routeParams.id});
  $scope.nextItem = SearchResults.getItem(SearchResults.currentIndex + 1);
  $scope.previousItem = SearchResults.getItem(SearchResults.currentIndex - 1);
  $scope.searchResults = SearchResults;

  $scope.transcriptExpanded = false;

  $scope.isFileProcessing = function(file) {
    var item = $scope.item;
    var user = $scope.currentUser;
    return (user && item && user.canEdit(item) && (file.transcript == null));
  };

  $scope.status = "uploading";
  $scope.upgradeMessage = false;

  $scope.statusNotification = function(file) {
    $scope.taskStatus(file);
    var statusHTML = "
Status: ";
    if ($scope.status == "uploading") {
      statusHTML += "uploading";
    } else if ($scope.status == "upload_failed") {
      statusHTML += "UPLOAD FAILED - please try uploading again or contact us if uploads keep failing.";
    } else if ($scope.status == "started") {
      statusHTML += "transcript processing.

The first two minutes of your transcription will be ready momentarily. ";
      if ($scope.upgradeMessage) {
        statusHTML += "Upgrade your plan for full transcripts.
";
      } else {
        statusHTML += "The rest will process in real time (a 30-minute file will take at least 30 minutes). We'll email you when it's ready.
";
      }
    } else if ($scope.status == "ts_failed") {
      statusHTML += "TRANSCRIPTION FAILED - please email us for support at edison@popuparchive.com";
    }
    console.log(statusHTML);
    return statusHTML;
  };

  $scope.taskStatus = function(file) {
    if (!file) { return false; }
    console.log(file.tasks);
    var upload, start, full;
    for (var i=0; i<file.tasks.length; i++) {
      var task = file.tasks[i];
      if (task.type == "upload") {
        upload = task.status;
      } else if (task.identifier == "ts_start") {
        start = task.status;
      } else if (task.identifier == "ts_all" || task.identifier == "ts_paid") {
        full = task.status;
      }
    }
    if (upload == "failed") {
      $scope.status = "upload_failed";
    } else if (start == "failed" || full == "failed"){
      $scope.status = "ts_failed";
    } else if (start == "working" || start == "created") {
      $scope.status = "started";
    } else if (start == "complete" && full != "complete") {
      $scope.status = "full";
    } else if (full == "complete") {
      $scope.status = "finished";
    }
    if (!full) {
      $scope.upgradeMessage = true;
    }
    console.log(upload, start, full, $scope.status);

  };

  $scope.allowEditButton = function(file) {
    var item = $scope.item;
    var found = true;
    if (file.duration != null && file.duration <= 120 ) {
      var found = false;
    }
    else {
      for(var i = 0; i < file.tasks.length; i++) {
        task = file.tasks[i];
        if ((task.identifier == 'ts_all' || task.identifier == 'ts_paid') && (task.status== 'complete')) {
          var found = false;
          break;
        }
      }
    }
    return found;
  }

  $scope.toggleTranscript = function () {
    $scope.transcriptExpanded = !$scope.transcriptExpanded;
  };

  $scope.transcriptClass = function () {
    if ($scope.transcriptExpanded) {
      return "expanded";
    }
    return "collapsed";
  };

  $scope.itemStorage = function() {
    $q.when($scope.storageModal).then( function (modalEl) {
      modalEl.modal('show');
    });
  };

  $scope.clearEntities = function() {
    $scope.item.entities.forEach(function(entity) {
      if (entity.isConfirmed === false) {
    	  $scope.deleteEntity(entity);
      }
    });
  };

  $scope.deleteEntity = function(entity) {
    var e = new Entity(entity);
    e.itemId = $scope.item.id;
    e.deleting = true;
    e.delete().then(function() {
      $scope.item.entities.splice($scope.item.entities.indexOf(entity), 1);
    });
  };

  $scope.confirmEntity = function(entity) {
    // console.log('confirmEntity', entity);
    entity.itemId = $scope.item.id;
    entity.isConfirmed = true;
    var entity = new Entity(entity);
    entity.update();
  };

  $scope.deleteItem = function () {
    if (confirm("Are you sure you want to delete the item " + $scope.item.title +"? \n\n This cannot be undone." )){
      $scope.item.delete().then(function () {
        $timeout(function(){ $scope.$broadcast('datasetChanged')}, 100);
        $location.path('/collections/' + $scope.collection.id);
      })
    }
  };

  $scope.encodeText = function (text) {
    return encodeURIComponent(text);
  };

  $scope.callEditor = function() {
    $scope.$broadcast('CallEditor');
    $scope.editTable = true;
  };

  $scope.callSave = function() {
    $scope.$broadcast('CallSave');
    $scope.editTable = false;
  }

  $scope.my_path= $window.location.protocol + "//" + $window.location.host;
}])
.controller('ItemStorageCtrl', [ '$scope', 'Item', 'Loader', 'Me', function ItemsCtrl($scope, Item, Loader, Me) {

  function pad(number) {
    if (number < 10) {
      return "0" + number;
    }
    return number;
  }

  $scope.durationString = function (secs) {
    var d = new Date(secs * 1000);

    return pad(d.getUTCHours()) + ":" + pad(d.getUTCMinutes()) + ":" + pad(d.getUTCSeconds());
  };
}])

.controller('ItemFormCtrl', ['$window', '$cookies', '$scope', '$http', '$q', '$timeout', '$route', '$routeParams', '$modal', 'Me', 'Loader', 'Alert', 'Collection', 'Item', 'Contribution', 'ImageFile', function FilesCtrl($window, $cookies, $scope, $http, $q, $timeout, $route, $routeParams, $modal, Me, Loader, Alert, Collection, Item, Contribution, ImageFile) {

  $scope.$watch('item', function (is) {
    if (!angular.isUndefined(is) && (is.id > 0) && angular.isUndefined(is.adoptToCollection)) {
      is.adoptToCollection = is.collectionId;
    }
  });

  $scope.selectedCollection = null;

  $scope.$watch('item.collectionId', function (cid) {
    $scope.setSelectedCollection();
  })

  $scope.$watch('item.adoptToCollection', function (cid) {
    $scope.setSelectedCollection();
  })

  $scope.setSelectedCollection = function () {
    if (angular.isUndefined($scope.item))
      return;

    var collectionId = $scope.item.adoptToCollection || $scope.item.collectionId;

    if (collectionId && (collectionId > 0) && (!$scope.selectedCollection || (collectionId != $scope.selectedCollection.id))) {
      for (var i=0; i < $scope.collections.length; i++) {
        if ($scope.collections[i].id == collectionId) {
          $scope.selectedCollection = $scope.collections[i];
          break;
        }
      }
    }
  };

  if ($scope.item && $scope.item.id) {
    $scope.item.adoptToCollection = $scope.item.collectionId;
  }

  $scope.submit = function () {
    // console.log('ItemFormCtrl submit: ', $scope.item);
    var saveItem = $scope.item;
    this.item = $scope.initializeItem(true);
    $scope.clear();

    var uploadFiles = saveItem.files;
    saveItem.files = [];
    var uploadImageFiles = saveItem.images;
    saveItem.images = [];

    var audioFiles = saveItem.audioFiles;
    var contributions = saveItem.contributions;

    Collection.get(saveItem.collectionId).then(function (collection) {
      if (angular.isArray(collection.items)) {
        collection.items.push(saveItem);
      }
    });

    if (saveItem.id) {

      saveItem.update().then(function (data) {
        // reset tags
        saveItem.tagList2Tags();
        $scope.addRemoteImageFile(saveItem, $scope.urlForImage);
        $scope.uploadImageFiles(saveItem, uploadImageFiles);
        $scope.uploadAudioFiles(saveItem, uploadFiles);
        $scope.updateAudioFiles(saveItem, audioFiles);
        $scope.updateContributions(saveItem, contributions);
        delete $scope.item;
        // console.log('scope after update', $scope);
        // $scope.item = saveItem;
        // if ($scope.item != $scope.$parent.item) {
        //   angular.copy($scope.item, $scope.$parent.item);
        // }
      });
    } else {
      saveItem.create().then(function (data) {
        // reset tags
        saveItem.tagList2Tags();
        $scope.addRemoteImageFile(saveItem, $scope.urlForImage);
        $scope.uploadImageFiles(saveItem, uploadImageFiles);
        $scope.uploadAudioFiles(saveItem, uploadFiles);
        $scope.updateAudioFiles(saveItem, audioFiles);
        $scope.updateContributions(saveItem, contributions);
        $timeout(function(){ $scope.$broadcast('datasetChanged')}, 750);
        delete $scope.item;
        // console.log('scope after create', $scope);
      });
    }

  };

  $scope.addRemoteImageFile = function (saveItem, imageUrl){
    if (!$scope.urlForImage)
      return;
    new ImageFile({remoteFileUrl: imageUrl, itemId: saveItem.id} ).create();
    $scope.item.images.push({ name: 'name', remoteFileUrl: imageUrl, size: ''});
    console.log("url link", $scope.urlForImage);
    $scope.urlForImage = "";
  };

  $scope.clear = function() {
    $scope.hideUploadModal();
  }

  // used by the upload-button callback when new files are selected
  $scope.setFiles = function(event) {
    element = angular.element(event.target);

    $scope.$apply(function($scope) {

      var newFiles = element[0].files;

      // default title to first file if not already set
      if (!$scope.item.title || $scope.item.title == "") {
        $scope.item.title = newFiles[0].name;
      }

      if (!$scope.item.files) {
        $scope.item.files = [];
      }

      // add files to the item
      angular.forEach(newFiles, function (file) {
        $scope.item.files.push(file);
      });

      element[0].value = "";

    });
  };

  $scope.setImageFiles = function(event) {
    element = angular.element(event.target);

    $scope.$apply(function($scope) {

      var newImageFiles = element[0].files;
      // console.log('image files',element[0].files);

      if (!$scope.item.images) {
        $scope.item.images = [];
      }

      // add image files to the item
      angular.forEach(newImageFiles, function (file) {
        $scope.item.images.push(file);
      });

      element[0].value = "";

    });
  };

  $scope.removeAudioFile = function(file) {
    if (file.id && (file.id > 0)) {
      file._delete = true;
    } else {
      $scope.item.files.splice($scope.item.files.indexOf(file), 1);
    }
  }

  $scope.removeImageFile = function(imageFile) {
    if (imageFile.id && (imageFile.id > 0)) {
      imageFile._delete = true;
    } else {
      $scope.item.images.splice($scope.item.images.indexOf(imageFile), 1);
    }
  }

  $scope.addContribution = function () {
    var c = new Contribution();
    if (!$scope.item.contributions) {
      $scope.item.contributions = [];
    }
    $scope.item.contributions.push(c);
  }

  $scope.deleteContribution = function(contribution) {
    // mark it to delete later
    if (contribution.id && (contribution.id > 0)) {
      contribution._delete = true;
    } else {
      $scope.item.contributions.splice($scope.item.contributions.indexOf(contribution), 1);
    }
  }

  $scope.updateContributions = function(item, contributions) {
    item.contributions = contributions;
    item.updateContributions();
  };

  $scope.updateAudioFiles = function(item, audioFiles) {
    item.audioFiles = audioFiles;
    item.updateAudioFiles();
  };

  $scope.tagSelect = function() {
    return {
      placeholder: 'Tags...',
      width: '284px',
      tags: [],
      initSelection: function (element, callback) {
        callback($scope.item.getTagList());
      }
    }
  };

  $scope.languageSelect = function() {
    return {
      placeholder: 'Language...',
      width: '220px',
      data: Item.languages,
      initSelection: function (element, callback) {
        callback(element.val());
      }
    }
  };

  // the ajax version, maybe?
  // $scope.languageSelect = function() {
  //   return {
  //     placeholder: 'Language...',
  //     width: '220px',
  //     ajax: {
  //       url: '/languages.json',
  //       results: function (data) {
  //         return {results: data};
  //       }
  //     },
  //     initSelection: function (element, callback) {
  //       callback($scope.item.language);
  //     }
  //   }
  // };

  $scope.roleSelect = {
    placeholder:'Role...',
    width: '160px'
  };

  $scope.peopleSelect = {
    placeholder: 'Name...',
    width: '240px',
    minimumInputLength: 2,
    quietMillis: 100,
    formatSelection: function (person) { return person.name; },
    formatResult: function (result, container, query, escapeMarkup) {
      var markup=[];
      $window.Select2.util.markMatch(result.name, query.term, markup, escapeMarkup);
      return markup.join("");
    },
    createSearchChoice: function (term, data) {
      if ($(data).filter(function() {
        return this.name.toUpperCase().localeCompare(term.toUpperCase()) === 0;
      }).length === 0) {
        return { id: 'new', name: term };
      }
    },
    initSelection: function (element, callback) {
      var scope = angular.element(element).scope();
      callback(scope.contribution.person);
    },
    ajax: {
      url: function (self, term, page, context) {
        return '/api/collections/' + ($routeParams.collectionId || $scope.item.collectionId) + '/people';
      },
      data: function (term, page) { return { q: term }; },
      results: function (data, page) { return { results: data }; }
    }
  }

}]);
<commit_msg>Removed the for loop from the allowEditButton function and changed it to check the status instead<commit_after>angular.module('Directory.items.controllers', ['Directory.loader', 'Directory.user', 'Directory.items.models', 'Directory.entities.models', 'Directory.people.models', 'prxSearch', 'Directory.storage', 'ngRoute', 'ngSanitize'])
.controller('ItemsCtrl', [ '$scope', 'Item', 'Loader', 'Me', 'Storage', function ItemsCtrl($scope, Item, Loader, Me, Storage) {

  $scope.Storage = Storage;

  Me.authenticated(function (data) {
    if ($scope.collectionId) {
      $scope.items = Loader.page(Item.query(), 'Items');
    }
  });

  $scope.startUpload = function() {
    var newFiles = [];
    var newImages = [];
    $scope.$emit('filesAdded', newFiles);
  }

}])
.controller('ItemCtrl', ['$scope', '$timeout', '$q', '$modal', 'Item', 'Loader', 'Me', '$routeParams', 'Collection', 'Entity', '$location', 'SearchResults', 'Storage', '$window', function ItemCtrl($scope, $timeout, $q, $modal, Item, Loader, Me, $routeParams, Collection, Entity, $location, SearchResults, Storage, $window) {

  $scope.Storage = Storage;

  $scope.storageModal = $modal({template: '/assets/items/storage.html', persist: false, show: false, backdrop: 'static', scope: $scope});

  if ($routeParams.id) {
    Loader.page(Item.get({collectionId:$routeParams.collectionId, id: $routeParams.id}), Collection.get({id:$routeParams.collectionId}), Collection.query(), 'Item-v2/'+$routeParams.id, $scope);
  }

  SearchResults.setCurrentIndex({id:$routeParams.id});
  $scope.nextItem = SearchResults.getItem(SearchResults.currentIndex + 1);
  $scope.previousItem = SearchResults.getItem(SearchResults.currentIndex - 1);
  $scope.searchResults = SearchResults;

  $scope.transcriptExpanded = false;

  $scope.isFileProcessing = function(file) {
    var item = $scope.item;
    var user = $scope.currentUser;
    return (user && item && user.canEdit(item) && (file.transcript == null));
  };

  $scope.status = "uploading";
  $scope.upgradeMessage = false;

  $scope.statusNotification = function(file) {
    $scope.taskStatus(file);
    var statusHTML = "
Status: ";
    if ($scope.status == "uploading") {
      statusHTML += "uploading";
    } else if ($scope.status == "upload_failed") {
      statusHTML += "UPLOAD FAILED - please try uploading again or contact us if uploads keep failing.";
    } else if ($scope.status == "started") {
      statusHTML += "transcript processing.

The first two minutes of your transcription will be ready momentarily. ";
      if ($scope.upgradeMessage) {
        statusHTML += "Upgrade your plan for full transcripts.
";
      } else {
        statusHTML += "The rest will process in real time (a 30-minute file will take at least 30 minutes). We'll email you when it's ready.
";
      }
    } else if ($scope.status == "ts_failed") {
      statusHTML += "TRANSCRIPTION FAILED - please email us for support at edison@popuparchive.com";
    }
    console.log(statusHTML);
    return statusHTML;
  };

  $scope.taskStatus = function(file) {
    if (!file) { return false; }
    console.log(file.tasks);
    var upload, start, full;
    for (var i=0; i<file.tasks.length; i++) {
      var task = file.tasks[i];
      if (task.type == "upload") {
        upload = task.status;
      } else if (task.identifier == "ts_start") {
        start = task.status;
      } else if (task.identifier == "ts_all" || task.identifier == "ts_paid") {
        full = task.status;
      }
    }
    if (upload == "failed") {
      $scope.status = "upload_failed";
    } else if (start == "failed" || full == "failed"){
      $scope.status = "ts_failed";
    } else if (start == "working" || start == "created") {
      $scope.status = "started";
    } else if (start == "complete" && full != "complete") {
      $scope.status = "full";
    } else if (full == "complete") {
      $scope.status = "finished";
    }
    if (!full) {
      $scope.upgradeMessage = true;
    }
    console.log(upload, start, full, $scope.status);

  };

  $scope.allowEditButton = function(file) {
    var item = $scope.item;
    var found = true;
    if (file.duration != null && file.duration <= 120 ) {
      var found = false;
    }
    else {
      if ($scope.status == "finished") {
          var found = false;
      }
    }
    return found;
  }

  $scope.toggleTranscript = function () {
    $scope.transcriptExpanded = !$scope.transcriptExpanded;
  };

  $scope.transcriptClass = function () {
    if ($scope.transcriptExpanded) {
      return "expanded";
    }
    return "collapsed";
  };

  $scope.itemStorage = function() {
    $q.when($scope.storageModal).then( function (modalEl) {
      modalEl.modal('show');
    });
  };

  $scope.clearEntities = function() {
    $scope.item.entities.forEach(function(entity) {
      if (entity.isConfirmed === false) {
    	  $scope.deleteEntity(entity);
      }
    });
  };

  $scope.deleteEntity = function(entity) {
    var e = new Entity(entity);
    e.itemId = $scope.item.id;
    e.deleting = true;
    e.delete().then(function() {
      $scope.item.entities.splice($scope.item.entities.indexOf(entity), 1);
    });
  };

  $scope.confirmEntity = function(entity) {
    // console.log('confirmEntity', entity);
    entity.itemId = $scope.item.id;
    entity.isConfirmed = true;
    var entity = new Entity(entity);
    entity.update();
  };

  $scope.deleteItem = function () {
    if (confirm("Are you sure you want to delete the item " + $scope.item.title +"? \n\n This cannot be undone." )){
      $scope.item.delete().then(function () {
        $timeout(function(){ $scope.$broadcast('datasetChanged')}, 100);
        $location.path('/collections/' + $scope.collection.id);
      })
    }
  };

  $scope.encodeText = function (text) {
    return encodeURIComponent(text);
  };

  $scope.callEditor = function() {
    $scope.$broadcast('CallEditor');
    $scope.editTable = true;
  };

  $scope.callSave = function() {
    $scope.$broadcast('CallSave');
    $scope.editTable = false;
  }

  $scope.my_path= $window.location.protocol + "//" + $window.location.host;
}])
.controller('ItemStorageCtrl', [ '$scope', 'Item', 'Loader', 'Me', function ItemsCtrl($scope, Item, Loader, Me) {

  function pad(number) {
    if (number < 10) {
      return "0" + number;
    }
    return number;
  }

  $scope.durationString = function (secs) {
    var d = new Date(secs * 1000);

    return pad(d.getUTCHours()) + ":" + pad(d.getUTCMinutes()) + ":" + pad(d.getUTCSeconds());
  };
}])

.controller('ItemFormCtrl', ['$window', '$cookies', '$scope', '$http', '$q', '$timeout', '$route', '$routeParams', '$modal', 'Me', 'Loader', 'Alert', 'Collection', 'Item', 'Contribution', 'ImageFile', function FilesCtrl($window, $cookies, $scope, $http, $q, $timeout, $route, $routeParams, $modal, Me, Loader, Alert, Collection, Item, Contribution, ImageFile) {

  $scope.$watch('item', function (is) {
    if (!angular.isUndefined(is) && (is.id > 0) && angular.isUndefined(is.adoptToCollection)) {
      is.adoptToCollection = is.collectionId;
    }
  });

  $scope.selectedCollection = null;

  $scope.$watch('item.collectionId', function (cid) {
    $scope.setSelectedCollection();
  })

  $scope.$watch('item.adoptToCollection', function (cid) {
    $scope.setSelectedCollection();
  })

  $scope.setSelectedCollection = function () {
    if (angular.isUndefined($scope.item))
      return;

    var collectionId = $scope.item.adoptToCollection || $scope.item.collectionId;

    if (collectionId && (collectionId > 0) && (!$scope.selectedCollection || (collectionId != $scope.selectedCollection.id))) {
      for (var i=0; i < $scope.collections.length; i++) {
        if ($scope.collections[i].id == collectionId) {
          $scope.selectedCollection = $scope.collections[i];
          break;
        }
      }
    }
  };

  if ($scope.item && $scope.item.id) {
    $scope.item.adoptToCollection = $scope.item.collectionId;
  }

  $scope.submit = function () {
    // console.log('ItemFormCtrl submit: ', $scope.item);
    var saveItem = $scope.item;
    this.item = $scope.initializeItem(true);
    $scope.clear();

    var uploadFiles = saveItem.files;
    saveItem.files = [];
    var uploadImageFiles = saveItem.images;
    saveItem.images = [];

    var audioFiles = saveItem.audioFiles;
    var contributions = saveItem.contributions;

    Collection.get(saveItem.collectionId).then(function (collection) {
      if (angular.isArray(collection.items)) {
        collection.items.push(saveItem);
      }
    });

    if (saveItem.id) {

      saveItem.update().then(function (data) {
        // reset tags
        saveItem.tagList2Tags();
        $scope.addRemoteImageFile(saveItem, $scope.urlForImage);
        $scope.uploadImageFiles(saveItem, uploadImageFiles);
        $scope.uploadAudioFiles(saveItem, uploadFiles);
        $scope.updateAudioFiles(saveItem, audioFiles);
        $scope.updateContributions(saveItem, contributions);
        delete $scope.item;
        // console.log('scope after update', $scope);
        // $scope.item = saveItem;
        // if ($scope.item != $scope.$parent.item) {
        //   angular.copy($scope.item, $scope.$parent.item);
        // }
      });
    } else {
      saveItem.create().then(function (data) {
        // reset tags
        saveItem.tagList2Tags();
        $scope.addRemoteImageFile(saveItem, $scope.urlForImage);
        $scope.uploadImageFiles(saveItem, uploadImageFiles);
        $scope.uploadAudioFiles(saveItem, uploadFiles);
        $scope.updateAudioFiles(saveItem, audioFiles);
        $scope.updateContributions(saveItem, contributions);
        $timeout(function(){ $scope.$broadcast('datasetChanged')}, 750);
        delete $scope.item;
        // console.log('scope after create', $scope);
      });
    }

  };

  $scope.addRemoteImageFile = function (saveItem, imageUrl){
    if (!$scope.urlForImage)
      return;
    new ImageFile({remoteFileUrl: imageUrl, itemId: saveItem.id} ).create();
    $scope.item.images.push({ name: 'name', remoteFileUrl: imageUrl, size: ''});
    console.log("url link", $scope.urlForImage);
    $scope.urlForImage = "";
  };

  $scope.clear = function() {
    $scope.hideUploadModal();
  }

  // used by the upload-button callback when new files are selected
  $scope.setFiles = function(event) {
    element = angular.element(event.target);

    $scope.$apply(function($scope) {

      var newFiles = element[0].files;

      // default title to first file if not already set
      if (!$scope.item.title || $scope.item.title == "") {
        $scope.item.title = newFiles[0].name;
      }

      if (!$scope.item.files) {
        $scope.item.files = [];
      }

      // add files to the item
      angular.forEach(newFiles, function (file) {
        $scope.item.files.push(file);
      });

      element[0].value = "";

    });
  };

  $scope.setImageFiles = function(event) {
    element = angular.element(event.target);

    $scope.$apply(function($scope) {

      var newImageFiles = element[0].files;
      // console.log('image files',element[0].files);

      if (!$scope.item.images) {
        $scope.item.images = [];
      }

      // add image files to the item
      angular.forEach(newImageFiles, function (file) {
        $scope.item.images.push(file);
      });

      element[0].value = "";

    });
  };

  $scope.removeAudioFile = function(file) {
    if (file.id && (file.id > 0)) {
      file._delete = true;
    } else {
      $scope.item.files.splice($scope.item.files.indexOf(file), 1);
    }
  }

  $scope.removeImageFile = function(imageFile) {
    if (imageFile.id && (imageFile.id > 0)) {
      imageFile._delete = true;
    } else {
      $scope.item.images.splice($scope.item.images.indexOf(imageFile), 1);
    }
  }

  $scope.addContribution = function () {
    var c = new Contribution();
    if (!$scope.item.contributions) {
      $scope.item.contributions = [];
    }
    $scope.item.contributions.push(c);
  }

  $scope.deleteContribution = function(contribution) {
    // mark it to delete later
    if (contribution.id && (contribution.id > 0)) {
      contribution._delete = true;
    } else {
      $scope.item.contributions.splice($scope.item.contributions.indexOf(contribution), 1);
    }
  }

  $scope.updateContributions = function(item, contributions) {
    item.contributions = contributions;
    item.updateContributions();
  };

  $scope.updateAudioFiles = function(item, audioFiles) {
    item.audioFiles = audioFiles;
    item.updateAudioFiles();
  };

  $scope.tagSelect = function() {
    return {
      placeholder: 'Tags...',
      width: '284px',
      tags: [],
      initSelection: function (element, callback) {
        callback($scope.item.getTagList());
      }
    }
  };

  $scope.languageSelect = function() {
    return {
      placeholder: 'Language...',
      width: '220px',
      data: Item.languages,
      initSelection: function (element, callback) {
        callback(element.val());
      }
    }
  };

  // the ajax version, maybe?
  // $scope.languageSelect = function() {
  //   return {
  //     placeholder: 'Language...',
  //     width: '220px',
  //     ajax: {
  //       url: '/languages.json',
  //       results: function (data) {
  //         return {results: data};
  //       }
  //     },
  //     initSelection: function (element, callback) {
  //       callback($scope.item.language);
  //     }
  //   }
  // };

  $scope.roleSelect = {
    placeholder:'Role...',
    width: '160px'
  };

  $scope.peopleSelect = {
    placeholder: 'Name...',
    width: '240px',
    minimumInputLength: 2,
    quietMillis: 100,
    formatSelection: function (person) { return person.name; },
    formatResult: function (result, container, query, escapeMarkup) {
      var markup=[];
      $window.Select2.util.markMatch(result.name, query.term, markup, escapeMarkup);
      return markup.join("");
    },
    createSearchChoice: function (term, data) {
      if ($(data).filter(function() {
        return this.name.toUpperCase().localeCompare(term.toUpperCase()) === 0;
      }).length === 0) {
        return { id: 'new', name: term };
      }
    },
    initSelection: function (element, callback) {
      var scope = angular.element(element).scope();
      callback(scope.contribution.person);
    },
    ajax: {
      url: function (self, term, page, context) {
        return '/api/collections/' + ($routeParams.collectionId || $scope.item.collectionId) + '/people';
      },
      data: function (term, page) { return { q: term }; },
      results: function (data, page) { return { results: data }; }
    }
  }

}]);
<|endoftext|>


<commit_before>var util = require('../');
var should = require('should');
var path = require('path');
require('mocha');

describe('PluginError()', function(){
  it('should print the plugin name in toString', function(done){
    var err = new util.PluginError('test', 'something broke');
    err.toString().indexOf('test').should.not.equal(-1);
    done();
  });

  it('should not include the stack by default in toString', function(done){
    var err = new util.PluginError('test', 'something broke');
    // just check that there are no 'at' lines
    err.toString().indexOf('at').should.equal(-1);
    done();
  });

  it('should include the stack when specified in toString', function(done){
    var err = new util.PluginError('test', 'something broke', {stack: "at huh", showStack: true});
    // just check that there are 'at' lines
    err.toString().indexOf('at').should.not.equal(-1);
    done();
  });

  it('should take arguments as one object', function(done){
    var err = new util.PluginError({
      plugin: 'test',
      message: 'something broke'
    });
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    done();
  });

  it('should take arguments as plugin name and one object', function(done){
    var err = new util.PluginError('test', {
      message: 'something broke'
    });
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    done();
  });

  it('should take arguments as plugin name and message', function(done){
    var err = new util.PluginError('test', 'something broke');
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    done();
  });

  it('should take arguments as plugin name, message, and one object', function(done){
    var err = new util.PluginError('test', 'something broke', {showStack: true});
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    err.showStack.should.equal(true);
    done();
  });

  it('should take arguments as plugin name, error, and one object', function(done){
    var realErr = new Error('something broke');
    var err = new util.PluginError('test', realErr, {showStack: true});
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    err.showStack.should.equal(true);
    done();
  });

});<commit_msg>Added basic test for fileName override on Error<commit_after>var util = require('../');
var should = require('should');
var path = require('path');
require('mocha');

describe('PluginError()', function(){
  it('should print the plugin name in toString', function(done){
    var err = new util.PluginError('test', 'something broke');
    err.toString().indexOf('test').should.not.equal(-1);
    done();
  });

  it('should not include the stack by default in toString', function(done){
    var err = new util.PluginError('test', 'something broke');
    // just check that there are no 'at' lines
    err.toString().indexOf('at').should.equal(-1);
    done();
  });

  it('should include the stack when specified in toString', function(done){
    var err = new util.PluginError('test', 'something broke', {stack: "at huh", showStack: true});
    // just check that there are 'at' lines
    err.toString().indexOf('at').should.not.equal(-1);
    done();
  });

  it('should take arguments as one object', function(done){
    var err = new util.PluginError({
      plugin: 'test',
      message: 'something broke'
    });
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    done();
  });

  it('should take arguments as plugin name and one object', function(done){
    var err = new util.PluginError('test', {
      message: 'something broke'
    });
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    done();
  });

  it('should take arguments as plugin name and message', function(done){
    var err = new util.PluginError('test', 'something broke');
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    done();
  });

  it('should take arguments as plugin name, message, and one object', function(done){
    var err = new util.PluginError('test', 'something broke', {showStack: true});
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    err.showStack.should.equal(true);
    done();
  });

  it('should take arguments as plugin name, error, and one object', function(done){
    var realErr = new Error('something broke');
    realErr.fileName = 'original.js';
    var err = new util.PluginError('test', realErr, {showStack: true, fileName: 'override.js'});
    err.plugin.should.equal('test');
    err.message.should.equal('something broke');
    err.fileName.should.equal('override.js');
    err.showStack.should.equal(true);
    done();
  });

});<|endoftext|>


<commit_before>/**
 * Creates a SimMatrix object that includes the similarity matrix and
 * phylogenetic tree.
 *
 * @param <Pancore> args.pancore The Pancore object
 * @param <GenomeTable> args.table The GenomeTable object
 */
var constructSimMatrix = function constructSimMatrix(args) {
    /*************** Private variables ***************/

    // Parameters
    var pancore = args.pancore,
        table = args.table;

    // UI variables
    var margin = {top: 20, right: 0, bottom: 200, left: 200},
        width = 500,
        height = 500;

    // D3 vars
    var svg,
        transitionDuration = 1000,
        x = d3.scale.ordinal().rangeBands([0, width]),
        z = d3.scale.linear().domain([0, 1]).clamp(true),
        tooltip;

    // Constructor fields
    var metadata = {},
        order = [],
        similarities = {},
        dirty = false,
        clustered,
        newick;

    var $matrixTab = $('a[href="#sim_matrix_wrapper"]'),
        $graphSelector = $('#sim_graph'),
        $clusterBtn = $("#cluster-matrix-btn");

    var that = {};

    /*************** Private methods ***************/

    /**
     * Initializes the SimMatrix
     */
    function init() {
        // calculate similarity and update on tab switch
        $matrixTab.on('shown.bs.tab', function tabSwitchAction() {
            calculateSimilarity();
            that.update();
        });

        // cluster matrix button
        $clusterBtn.click(that.clusterMatrix);

        // decluster matrix button
        $('#decluster-matrix').click(function declusterAction() {
            that.setOrder(table.getOrder());
        });

        $('#use-cluster-order').click(that.useClusterOrder);

        // Dummy newick value chosen randomly
        var dummyNewick = "((((A:0.2,B:0.2):0.1,C:0.3):0.4,(F:0.4,D:0.4):0.3):0.3,E:1.0)";
        that.drawTree(dummyNewick, 500);

        that.redraw(true);
    }

    /**
     * Shows the info panel with info about the similarities
     *
     * @param <Number> d.key The bioprojectid associated with the square
     */
    function showInfoPanel(d) {
        var col = d.key,
            row = d3.select(this.parentNode).datum().key,
            pos = $(this).position(),
            tooltipHtml = "
";
        tooltipHtml += "

Name	Genome size

";
        tooltipHtml += "
" + metadata[row].name + "	" + d3.format(",")(metadata[row].size) + " peptides
";
        tooltipHtml += "
" + metadata[col].name + "	" + d3.format(",")(metadata[col].size) + " peptides
";
        tooltipHtml += "
Similarity: " + d3.format(",.2%")(similarities[row][col]) + "
"
        tooltipHtml += "

";
        tooltip.html(tooltipHtml)
            .style("top", (pos.top + x.rangeBand() + 15) + "px")
            .style("left", (pos.left + 15) + "px")
            .style("visibility", "visible");
    }

    /**
     * Hides the info panel
     */
    function hideInfoPanel() {
        tooltip.style("visibility", "hidden");
    }

    /**
     * Requests a similarity calculation to the webserver if dirty
     */
    function calculateSimilarity() {
        if (dirty) {
            pancore.requestSimilarityCalculation();
        }
    }

    /*************** Public methods ***************/

    /**
     * Process new similarity data
     *
     * @param <Boolean> fullMatrix Is this a full matrix, or just a single row?
     * @param <SimObject> data New similarity data
     */
    that.addSimilarityData = function addSimilarityData(fullMatrix, data) {
        var id,
            id2,
            row;

        if (fullMatrix) {
            similarities = data;
        } else {
            id = data.id;
            row = data.row;
            for (id2 in similarities) {
                similarities[id2][id] = row[id2];
            }
            similarities[id] = row;
        }

        dirty = true;
        that.update();
    };

    /**
     * Use the clusterd order as the order for the table and the graph
     */
    that.useClusterOrder = function useClusterOrder() {
        pancore.updateOrder({
            'order' : order,
            'start' : 0,
            'stop' : order.length - 1
        });
        $('#reorder-header').addClass('hidden');
    };

    /**
     * Changes the order of the table based on the given order
     *
     * @param <Array> orderData The new order we want to set
     */
    that.setOrder = function setOrder(orderData) {
        order = orderData;
        that.setClustered(false);
        dirty = true;
        that.update();
    };

    /**
     * Redraws the entire matrix
     *
     * @param <Boolean> force Force the redraw, even if it's not the active tab
     */
    that.redraw = function redraw(force) {
        // Check if we are currently active pane
        if (!force && !that.isActiveTab()) {
            return;
        }

        dirty = true;

        $("#sim_matrix").html('');
        svg = d3.select("#sim_matrix").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        svg.append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height)
            .attr("fill", "#eeeeee");

        // create the tooltip
        tooltip = d3.select("#sim_matrix")
          .append("div")
            .attr("class", "tip")
            .style("position", "absolute")
            .style("top", "0px")
            .style("left", "0px")
            .style("z-index", "10")
            .style("visibility", "hidden");

        that.update();
    };

    /**
     * Updates the SimMatrix
     */
    that.update = function update() {
        // Check if we are currently active pane and dirty
        if (!that.isActiveTab()) {
            return;
        }
        if (!dirty) {
            return;
        }

        var dataArray = d3.entries(similarities);
        x.domain(order);

        // 50px is the max size of a square
        var minWidth = d3.min([width, 50 * order.length]);
        x.rangeBands([0, minWidth]);
        $('#cluster-div').css('height', minWidth + 30 + "px");
        d3.select(".background")
            .attr("width", minWidth)
            .attr("height", minWidth);

        var rows = svg.selectAll(".row")
            .data(dataArray, function (d) { return d.key; });

        var rowsEnter = rows.enter()
            .append("g")
                .attr("class", "row");
        rowsEnter.append("text")
                .attr("x", -6)
                .attr("y", x.rangeBand() / 2)
                .attr("dy", ".32em")
                .attr("text-anchor", "end")
                .text(function (d) { return metadata[d.key].abbreviation; });

        rows.each(function (d) {
            var cells = d3.select(this).selectAll(".cell")
                .data(d3.entries(d.value), function (d) { return d.key; });
            cells.enter().append("rect")
                .attr("class", "cell")
                .attr("x", 0)
                .attr("width", 0)
                .attr("height", 0)
                .style("fill-opacity", 0)
                .style("fill", "white")
                .on("mouseover", showInfoPanel)
                .on("mouseout", hideInfoPanel);

            cells.transition()
                .duration(transitionDuration)
                .attr("x", function (d) { return x(d.key); })
                .attr("width", x.rangeBand())
                .attr("height", x.rangeBand())
                .style("fill-opacity", function (d) { return z(d.value * d.value); })
                .style("fill", function (d) { return (d.value !== -1) ? "steelblue" : "white"; });

            cells.exit().remove();
        });

        // draw grid lines on top of the cells
        rowsEnter.append("line")
            .attr("x2", width)
            .attr("stroke", "#ffffff");

        rows.transition()
            .duration(transitionDuration)
            .attr("transform", function (d) { return "translate(0," + x(d.key) + ")"; });

        rows.selectAll("text")
            .transition()
                .duration(transitionDuration)
                .attr('y', x.rangeBand() / 2);

        rows.exit().remove();

        var columns = svg.selectAll(".column")
            .data(dataArray, function (d) { return d.key; });

        var colsEnter = columns.enter().append("g")
            .attr("class", "column");
        colsEnter.append("text")
            .attr("x", minWidth + 6)
            .attr("y", -x.rangeBand() / 2)
            .attr("dy", ".32em")
            .attr("text-anchor", "start")
            .text(function (d) { return metadata[d.key].abbreviation; });
        colsEnter.append("line")
            .attr("x1", width)
            .attr("stroke", "#ffffff");

        columns.transition()
            .duration(transitionDuration)
            .attr("transform", function (d) { return "translate(" + x(d.key) + ")rotate(90)"; });

        columns.selectAll("text")
            .transition()
                .duration(transitionDuration)
                .attr("x", minWidth + 6)
                .attr('y', -x.rangeBand() / 2);

        columns.exit().remove();

        dirty = false;
    };

    /**
     * Resets the state of the matrix
     */
    that.clearAllData = function clearAllData() {
        dirty = true;
        that.setClustered(false);
        similarities = {};
        order = [];
        metadata = {};
        newick = "";
        that.redraw();
    };

    /**
     * Draws a phylogenetic tree based on a newick string
     *
     * @param <String> n The tree in newick format
     * @param <Number> height Optional height of the tree
     */
    that.drawTree = function drawTree(n, height) {
        newick = n;
        $("#sim_graph").html("");
        if (height === undefined) {
            height = d3.min([500, 50 * order.length]);
        }
        d3.phylogram.build('#sim_graph', Newick.parse(n), {width: 180, height: height, skipLabels: true});
    };

    /**
     * Requests the clustering of the matrix to the pancore object
     */
    that.clusterMatrix = function clusterMatrix() {
        pancore.requestClustering();
    };

    /**
     * Adds a genome to the matrix.
     *
     * @param <Genome> genome The genome object we want to add
     */
    that.addGenome = function addGenome(genome) {
        var id = genome.bioproject_id,
            name = genome.name,
            size = genome.peptides,
            abbreviation = genome.abbreviation,
            i;

        // add the data to the lists
        metadata[id] = {
            'name' : name,
            'size' : size,
            'abbreviation' : abbreviation
        };
        order.push(id);

        similarities[id] = {};
        for (i in similarities) {
            similarities[id][i] = -1;
            similarities[i][id] = -1;
        }

        that.setClustered(false);
        dirty = true;
        if (that.isActiveTab()) {
            delay(function () {
                calculateSimilarity();
                that.update();
            }, 1000);
        }
    };

    /**
     * Remove data from the matrix
     *
     * @param <Number> id The bioprojectId of the genome to remove
     */
    that.removeGenome = function removeGenome(id) {
        var id2;

        delete metadata[id];
        delete similarities[id];
        for (id2 in similarities) {
            delete similarities[id2][id];
        }
        order.splice(order.indexOf(id), 1);

        that.setClustered(false);
        dirty = true;
        that.update();
    };

    /**
     * Call this method to indicate that the current view is or isn't clustered.
     * This will enable or disable the clustering buttons.
     *
     * @param <Boolean> c is the matrix currently clustered?
     */
    that.setClustered = function setClustered(c) {
        // Check if value differs, if we don't do this we call fadeTo too many times
        if (c !== clustered) {
            if (!c) {
                $graphSelector.fadeTo('normal', 0.2);
                $clusterBtn.show();
                $('#reorder-header').addClass('hidden');
            } else {
                $graphSelector.fadeTo('fast', 1);
                $clusterBtn.hide();
                $('#reorder-header').removeClass('hidden');
            }
            clustered = c;
        }
    };

    /**
     * Returns true if the matrix tab is currently visible to the user
     */
    that.isActiveTab = function isActiveTab() {
        return $matrixTab.parent().hasClass("active");
    };

    /**
     * TODO
     */
    that.getDataAsCsv = function getDataAsCsv() {
        var csvString = ",",
            tempArray = [],
            i;

        for (i = 0; i < order.length; i++) {
            tempArray.push('"' + metadata[order[i]].name + '"');
        }
        csvString += tempArray.join(',') + "\n";

        for (i = 0; i < order.length; i++) {
            tempArray = [];
            tempArray.push('"' + metadata[order[i]].name + '"');
            tempArray.push.apply(tempArray, matrix[i]);
            csvString += tempArray.join(',') + "\n";
        }

        return csvString;
    };

    // Initialize the object
    init();

    return that;
};
<commit_msg>fix tooltip overflow<commit_after>/**
 * Creates a SimMatrix object that includes the similarity matrix and
 * phylogenetic tree.
 *
 * @param <Pancore> args.pancore The Pancore object
 * @param <GenomeTable> args.table The GenomeTable object
 */
var constructSimMatrix = function constructSimMatrix(args) {
    /*************** Private variables ***************/

    // Parameters
    var pancore = args.pancore,
        table = args.table;

    // UI variables
    var margin = {top: 20, right: 0, bottom: 200, left: 200},
        width = 500,
        height = 500;

    // D3 vars
    var svg,
        transitionDuration = 1000,
        x = d3.scale.ordinal().rangeBands([0, width]),
        z = d3.scale.linear().domain([0, 1]).clamp(true),
        tooltip;

    // Constructor fields
    var metadata = {},
        order = [],
        similarities = {},
        dirty = false,
        clustered,
        newick;

    var $matrixTab = $('a[href="#sim_matrix_wrapper"]'),
        $graphSelector = $('#sim_graph'),
        $clusterBtn = $("#cluster-matrix-btn");

    var that = {};

    /*************** Private methods ***************/

    /**
     * Initializes the SimMatrix
     */
    function init() {
        // calculate similarity and update on tab switch
        $matrixTab.on('shown.bs.tab', function tabSwitchAction() {
            calculateSimilarity();
            that.update();
        });

        // cluster matrix button
        $clusterBtn.click(that.clusterMatrix);

        // decluster matrix button
        $('#decluster-matrix').click(function declusterAction() {
            that.setOrder(table.getOrder());
        });

        $('#use-cluster-order').click(that.useClusterOrder);

        // Dummy newick value chosen randomly
        var dummyNewick = "((((A:0.2,B:0.2):0.1,C:0.3):0.4,(F:0.4,D:0.4):0.3):0.3,E:1.0)";
        that.drawTree(dummyNewick, 500);

        that.redraw(true);
    }

    /**
     * Shows the info panel with info about the similarities
     *
     * @param <Number> d.key The bioprojectid associated with the square
     */
    function showInfoPanel(d) {
        var col = d.key,
            row = d3.select(this.parentNode).datum().key,
            pos = $(this).offset(),
            tooltipHtml = "
";
        tooltipHtml += "

Name	Genome size

";
        tooltipHtml += "
" + metadata[row].name + "	" + d3.format(",")(metadata[row].size) + " peptides
";
        tooltipHtml += "
" + metadata[col].name + "	" + d3.format(",")(metadata[col].size) + " peptides
";
        tooltipHtml += "
Similarity: " + d3.format(",.2%")(similarities[row][col]) + "
"
        tooltipHtml += "

";
        tooltip.html(tooltipHtml)
            .style("top", (pos.top + x.rangeBand() + 5) + "px")
            .style("left", (pos.left + x.rangeBand() + 5) + "px")
            .style("visibility", "visible");
    }

    /**
     * Hides the info panel
     */
    function hideInfoPanel() {
        tooltip.style("visibility", "hidden");
    }

    /**
     * Requests a similarity calculation to the webserver if dirty
     */
    function calculateSimilarity() {
        if (dirty) {
            pancore.requestSimilarityCalculation();
        }
    }

    /*************** Public methods ***************/

    /**
     * Process new similarity data
     *
     * @param <Boolean> fullMatrix Is this a full matrix, or just a single row?
     * @param <SimObject> data New similarity data
     */
    that.addSimilarityData = function addSimilarityData(fullMatrix, data) {
        var id,
            id2,
            row;

        if (fullMatrix) {
            similarities = data;
        } else {
            id = data.id;
            row = data.row;
            for (id2 in similarities) {
                similarities[id2][id] = row[id2];
            }
            similarities[id] = row;
        }

        dirty = true;
        that.update();
    };

    /**
     * Use the clusterd order as the order for the table and the graph
     */
    that.useClusterOrder = function useClusterOrder() {
        pancore.updateOrder({
            'order' : order,
            'start' : 0,
            'stop' : order.length - 1
        });
        $('#reorder-header').addClass('hidden');
    };

    /**
     * Changes the order of the table based on the given order
     *
     * @param <Array> orderData The new order we want to set
     */
    that.setOrder = function setOrder(orderData) {
        order = orderData;
        that.setClustered(false);
        dirty = true;
        that.update();
    };

    /**
     * Redraws the entire matrix
     *
     * @param <Boolean> force Force the redraw, even if it's not the active tab
     */
    that.redraw = function redraw(force) {
        // Check if we are currently active pane
        if (!force && !that.isActiveTab()) {
            return;
        }

        dirty = true;

        $("#sim_matrix").html('');
        svg = d3.select("#sim_matrix").append("svg")
            .attr("width", width + margin.left + margin.right)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        svg.append("rect")
            .attr("class", "background")
            .attr("width", width)
            .attr("height", height)
            .attr("fill", "#eeeeee");

        // create the tooltip
        tooltip = d3.select("body")
          .append("div")
            .attr("class", "tip")
            .style("position", "absolute")
            .style("top", "0px")
            .style("left", "0px")
            .style("z-index", "10")
            .style("visibility", "hidden");

        that.update();
    };

    /**
     * Updates the SimMatrix
     */
    that.update = function update() {
        // Check if we are currently active pane and dirty
        if (!that.isActiveTab()) {
            return;
        }
        if (!dirty) {
            return;
        }

        var dataArray = d3.entries(similarities);
        x.domain(order);

        // 50px is the max size of a square
        var minWidth = d3.min([width, 50 * order.length]);
        x.rangeBands([0, minWidth]);
        $('#cluster-div').css('height', minWidth + 30 + "px");
        d3.select(".background")
            .attr("width", minWidth)
            .attr("height", minWidth);

        var rows = svg.selectAll(".row")
            .data(dataArray, function (d) { return d.key; });

        var rowsEnter = rows.enter()
            .append("g")
                .attr("class", "row");
        rowsEnter.append("text")
                .attr("x", -6)
                .attr("y", x.rangeBand() / 2)
                .attr("dy", ".32em")
                .attr("text-anchor", "end")
                .text(function (d) { return metadata[d.key].abbreviation; });

        rows.each(function (d) {
            var cells = d3.select(this).selectAll(".cell")
                .data(d3.entries(d.value), function (d) { return d.key; });
            cells.enter().append("rect")
                .attr("class", "cell")
                .attr("x", 0)
                .attr("width", 0)
                .attr("height", 0)
                .style("fill-opacity", 0)
                .style("fill", "white")
                .on("mouseover", showInfoPanel)
                .on("mouseout", hideInfoPanel);

            cells.transition()
                .duration(transitionDuration)
                .attr("x", function (d) { return x(d.key); })
                .attr("width", x.rangeBand())
                .attr("height", x.rangeBand())
                .style("fill-opacity", function (d) { return z(d.value * d.value); })
                .style("fill", function (d) { return (d.value !== -1) ? "steelblue" : "white"; });

            cells.exit().remove();
        });

        // draw grid lines on top of the cells
        rowsEnter.append("line")
            .attr("x2", width)
            .attr("stroke", "#ffffff");

        rows.transition()
            .duration(transitionDuration)
            .attr("transform", function (d) { return "translate(0," + x(d.key) + ")"; });

        rows.selectAll("text")
            .transition()
                .duration(transitionDuration)
                .attr('y', x.rangeBand() / 2);

        rows.exit().remove();

        var columns = svg.selectAll(".column")
            .data(dataArray, function (d) { return d.key; });

        var colsEnter = columns.enter().append("g")
            .attr("class", "column");
        colsEnter.append("text")
            .attr("x", minWidth + 6)
            .attr("y", -x.rangeBand() / 2)
            .attr("dy", ".32em")
            .attr("text-anchor", "start")
            .text(function (d) { return metadata[d.key].abbreviation; });
        colsEnter.append("line")
            .attr("x1", width)
            .attr("stroke", "#ffffff");

        columns.transition()
            .duration(transitionDuration)
            .attr("transform", function (d) { return "translate(" + x(d.key) + ")rotate(90)"; });

        columns.selectAll("text")
            .transition()
                .duration(transitionDuration)
                .attr("x", minWidth + 6)
                .attr('y', -x.rangeBand() / 2);

        columns.exit().remove();

        dirty = false;
    };

    /**
     * Resets the state of the matrix
     */
    that.clearAllData = function clearAllData() {
        dirty = true;
        that.setClustered(false);
        similarities = {};
        order = [];
        metadata = {};
        newick = "";
        that.redraw();
    };

    /**
     * Draws a phylogenetic tree based on a newick string
     *
     * @param <String> n The tree in newick format
     * @param <Number> height Optional height of the tree
     */
    that.drawTree = function drawTree(n, height) {
        newick = n;
        $("#sim_graph").html("");
        if (height === undefined) {
            height = d3.min([500, 50 * order.length]);
        }
        d3.phylogram.build('#sim_graph', Newick.parse(n), {width: 180, height: height, skipLabels: true});
    };

    /**
     * Requests the clustering of the matrix to the pancore object
     */
    that.clusterMatrix = function clusterMatrix() {
        pancore.requestClustering();
    };

    /**
     * Adds a genome to the matrix.
     *
     * @param <Genome> genome The genome object we want to add
     */
    that.addGenome = function addGenome(genome) {
        var id = genome.bioproject_id,
            name = genome.name,
            size = genome.peptides,
            abbreviation = genome.abbreviation,
            i;

        // add the data to the lists
        metadata[id] = {
            'name' : name,
            'size' : size,
            'abbreviation' : abbreviation
        };
        order.push(id);

        similarities[id] = {};
        for (i in similarities) {
            similarities[id][i] = -1;
            similarities[i][id] = -1;
        }

        that.setClustered(false);
        dirty = true;
        if (that.isActiveTab()) {
            delay(function () {
                calculateSimilarity();
                that.update();
            }, 1000);
        }
    };

    /**
     * Remove data from the matrix
     *
     * @param <Number> id The bioprojectId of the genome to remove
     */
    that.removeGenome = function removeGenome(id) {
        var id2;

        delete metadata[id];
        delete similarities[id];
        for (id2 in similarities) {
            delete similarities[id2][id];
        }
        order.splice(order.indexOf(id), 1);

        that.setClustered(false);
        dirty = true;
        that.update();
    };

    /**
     * Call this method to indicate that the current view is or isn't clustered.
     * This will enable or disable the clustering buttons.
     *
     * @param <Boolean> c is the matrix currently clustered?
     */
    that.setClustered = function setClustered(c) {
        // Check if value differs, if we don't do this we call fadeTo too many times
        if (c !== clustered) {
            if (!c) {
                $graphSelector.fadeTo('normal', 0.2);
                $clusterBtn.show();
                $('#reorder-header').addClass('hidden');
            } else {
                $graphSelector.fadeTo('fast', 1);
                $clusterBtn.hide();
                $('#reorder-header').removeClass('hidden');
            }
            clustered = c;
        }
    };

    /**
     * Returns true if the matrix tab is currently visible to the user
     */
    that.isActiveTab = function isActiveTab() {
        return $matrixTab.parent().hasClass("active");
    };

    /**
     * TODO
     */
    that.getDataAsCsv = function getDataAsCsv() {
        var csvString = ",",
            tempArray = [],
            i;

        for (i = 0; i < order.length; i++) {
            tempArray.push('"' + metadata[order[i]].name + '"');
        }
        csvString += tempArray.join(',') + "\n";

        for (i = 0; i < order.length; i++) {
            tempArray = [];
            tempArray.push('"' + metadata[order[i]].name + '"');
            tempArray.push.apply(tempArray, matrix[i]);
            csvString += tempArray.join(',') + "\n";
        }

        return csvString;
    };

    // Initialize the object
    init();

    return that;
};
<|endoftext|>


<commit_before>require('moonstone');

/**
* Contains the declaration for the {@link module:moonstone/ExpandableListItem~ExpandableListItem} kind.
* @module moonstone/ExpandableListItem
*/

var
	kind = require('enyo/kind'),
	Control = require('enyo/Control'),
	Group = require('enyo/Group'),
	options = require('enyo/options');

var
	Spotlight = require('spotlight');

var
	ExpandableListDrawer = require('../ExpandableListDrawer'),
	Item = require('../Item'),
	Marquee = require('../Marquee'),
	MarqueeText = Marquee.Text,
	ExpandableListItemAccessibilitySupport = require('./ExpandableListItemAccessibilitySupport');

/**
* {@link module:moonstone/ExpandableListItem~ExpandableListItem}, which extends {@link module:moonstone/Item~Item}, displays a header
* while also allowing additional content to be stored in an {@link module:enyo/Drawer~Drawer}. When
* the header is selected, the drawer opens below. To close the drawer, tap on the
* header text or navigate (via 5-way) back to the top of the drawer.
*
* The control's child components may be of any kind; by default, they are
* instances of `moon.Item`.
*
* ```
* 		{kind: 'moon.ExpandableListItem', content: 'A Countries', components: [
* 			{content: 'Algeria'},
* 			{content: 'Argentina'},
* 			{content: 'Australia'}
* 		]},
* 		{kind: 'moon.ExpandableListItem', content: 'B Countries', components: [
* 			{content: 'Belgium'},
* 			{content: 'Bolivia'},
* 			{content: 'Brazil'}
* 		]}
*
* When multiple ExpandableListItems are used in a group, only one may be open at
* a given time.
*
* ```
* 		{kind: 'enyo.Group', highlander: true, components: [
* 			{kind: 'moon.ExpandableListItem',  open: true,
* 			content: 'This is a grouped ExpandableListItem', components: [
* 				{content: 'Item One'},
* 				{content: 'Item Two'}
* 			]},
* 			{kind: 'moon.ExpandableListItem',
* 				content: 'This is another grouped ExpandableListItem', components: [
* 					{content: 'Item Three'},
* 					{content: 'Item Four'}
* 				]
* 			},
* 			{kind: 'moon.ExpandableListItem',
* 				content: 'This is yet another grouped ExpandableListItem', components: [
* 					{content: 'Item Five'},
* 					{content: 'Item Six'}
* 				]
* 			}
* 		]}
* ```
*
* @class ExpandableListItem
* @extends module:enyo/Control~Control
* @ui
* @public
*/
module.exports = kind(
	/** @lends module:moonstone/ExpandableListItem~ExpandableListItem.prototype */ {

	/**
	* @private
	*/
	name: 'moon.ExpandableListItem',

	/**
	* @private
	*/
	kind: Control,

	/**
	* @private
	*/
	mixins: options.accessibility ? [ExpandableListItemAccessibilitySupport] : null,

	/**
	* @private
	* @lends module:moonstone/ExpandableListItem~ExpandableListItem.prototype
	*/
	published: {

		/**
		* If `true`, the drawer automatically closes when the user navigates to the top of the
		* control; if `false`, the user must select/tap the header to close the drawer.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		autoCollapse: false,

		/**
		* If `true`, the drawer is expanded, showing this item's contents. Use this property
		* (rather than [active]{@link module:moonstone/ExpandableListItem~ExpandableListItem#active}) to set the item's
		* initial state.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		open: false,

		/**
		* Boolean that reflects the value of the [open]{@link module:moonstone/ExpandableListItem~ExpandableListItem#open}
		* property; it is used to support the {@link module:enyo/Group~Group} API for grouping a set of
		* ExpandableListItems in which only one is expanded at a time. Note that the `open`
		* property (not the `active` property) controls the initial state of the picker.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		active: false,

		/**
		* If `true`, the user is prevented from moving {@glossary Spotlight} past the bottom
		* of the drawer (when open) using 5-way controls.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		lockBottom: false,

		/**
		* If `true`, item is shown as disabled and does not generate tap events.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		disabled: false
	},

	/**
	* @private
	*/
	classes: 'moon-expandable-list-item',

	/**
	* @private
	*/
	spotlight: false,

	/**
	* @private
	*/
	defaultKind: Item,

	/**
	* @private
	*/
	handlers: {
		onSpotlightDown: 'spotlightDown',
		onDrawerAnimationEnd: 'drawerAnimationEnd'
	},

	/**
	* @private
	*/
	components: [
		// headerContainer required to avoid bad scrollWidth returned in RTL for certain text
		// widths (webkit bug)
		{name: 'headerContainer', kind: Item, classes: 'moon-expandable-list-item-header moon-expandable-picker-header moon-expandable-list-header', onSpotlightFocus: 'headerFocus', ontap: 'expandContract', components: [
			{name: 'header', kind: MarqueeText, accessibilityDisabled: true}
		]},
		{name: 'drawer', kind: ExpandableListDrawer, resizeContainer:false, classes: 'moon-expandable-list-item-client', components: [
			{name: 'client', kind: Group, tag: null}
		]}
	],

	/**
	* @private
	*/
	bindings: [
		{from: '.allowHtml', to: '.$.header.allowHtml'},
		{from: '.disabled', to: '.$.headerContainer.disabled'}
	],

	/**
	* @private
	*/
	create: function () {
		Control.prototype.create.apply(this, arguments);
		this.openChanged();
		this.setActive(this.open);
		this.disabledChanged();
	},

	/**
	* Facade for header content.
	*
	* @private
	*/
	contentChanged: function () {
		this.$.header.setContent(this.getContent());
	},

	/**
	* Facade for drawer.
	*
	* @private
	*/
	openChanged: function () {
		var open = this.getOpen();
		this.addRemoveClass('open', open);
		this.$.drawer.setOpen(open);
		this.$.drawer.spotlightDisabled = !open;
		this.setActive(open);
	},

	/**
	* @private
	*/
	disabledChanged: function () {
		var disabled = this.getDisabled();

		this.addRemoveClass('disabled', disabled);
		if (disabled) {
			this.setActive(false);
		}
	},

	/**
	* @fires module:enyo/GroupItem~GroupItem#onActivate
	* @private
	*/
	activeChanged: function () {
		this.bubble('onActivate', {allowHighlanderDeactivate:true});
		this.setOpen(this.active);
	},

	/**
	* If closed, opens drawer and highlights first spottable child.
	*
	* @private
	*/
	expandContract: function (inSender, inEvent) {
		this.toggleActive();

		if (this.getActive() && !Spotlight.getPointerMode()) {
			Spotlight.spot(Spotlight.getFirstChild(this.$.drawer));
		}
	},

	/**
	* @private
	*/
	toggleActive: function () {
		if (this.getOpen()) {
			this.setActive(false);
		} else {
			this.setActive(true);
		}
	},

	/**
	* If drawer is currently open, and event was sent via keypress (i.e., it has a direction),
	* process header focus.
	*
	* @fires module:moonstone/Scroller~Scroller#onRequestScrollIntoView
	* @private
	*/
	headerFocus: function (inSender, inEvent) {
		var direction = inEvent && inEvent.dir;

		if (this.getOpen() && this.getAutoCollapse() && direction === 'UP') {
			this.setActive(false);
		}

		if (inEvent.originator === this.$.header) {
			this.bubble('onRequestScrollIntoView');
		}
	},

	/**
	* Check for the last item in the client area, and prevent 5-way focus movement below it,
	* per UX specs.
	*
	* @private
	*/
	spotlightDown: function (inSender, inEvent) {
		var children = Spotlight.getChildren(this.$.client);
		if (this.getLockBottom() && this.getOpen() && children.length && inEvent.originator == children[children.length - 1]) {
			return true;
		}
	},

	/**
	* @fires module:moonstone/Scroller~Scroller#onRequestScrollIntoView
	* @private
	*/
	drawerAnimationEnd: function () {
		this.bubble('onRequestScrollIntoView', {scrollInPointerMode: true});
		return true;
	}
});
<commit_msg>ENYO-1231-inconsistent behavior of spotlight on auto collapse<commit_after>require('moonstone');

/**
* Contains the declaration for the {@link module:moonstone/ExpandableListItem~ExpandableListItem} kind.
* @module moonstone/ExpandableListItem
*/

var
	kind = require('enyo/kind'),
	Control = require('enyo/Control'),
	Group = require('enyo/Group'),
	options = require('enyo/options');

var
	Spotlight = require('spotlight');

var
	ExpandableListDrawer = require('../ExpandableListDrawer'),
	Item = require('../Item'),
	Marquee = require('../Marquee'),
	MarqueeText = Marquee.Text,
	ExpandableListItemAccessibilitySupport = require('./ExpandableListItemAccessibilitySupport');

/**
* {@link module:moonstone/ExpandableListItem~ExpandableListItem}, which extends {@link module:moonstone/Item~Item}, displays a header
* while also allowing additional content to be stored in an {@link module:enyo/Drawer~Drawer}. When
* the header is selected, the drawer opens below. To close the drawer, tap on the
* header text or navigate (via 5-way) back to the top of the drawer.
*
* The control's child components may be of any kind; by default, they are
* instances of `moon.Item`.
*
* ```
* 		{kind: 'moon.ExpandableListItem', content: 'A Countries', components: [
* 			{content: 'Algeria'},
* 			{content: 'Argentina'},
* 			{content: 'Australia'}
* 		]},
* 		{kind: 'moon.ExpandableListItem', content: 'B Countries', components: [
* 			{content: 'Belgium'},
* 			{content: 'Bolivia'},
* 			{content: 'Brazil'}
* 		]}
*
* When multiple ExpandableListItems are used in a group, only one may be open at
* a given time.
*
* ```
* 		{kind: 'enyo.Group', highlander: true, components: [
* 			{kind: 'moon.ExpandableListItem',  open: true,
* 			content: 'This is a grouped ExpandableListItem', components: [
* 				{content: 'Item One'},
* 				{content: 'Item Two'}
* 			]},
* 			{kind: 'moon.ExpandableListItem',
* 				content: 'This is another grouped ExpandableListItem', components: [
* 					{content: 'Item Three'},
* 					{content: 'Item Four'}
* 				]
* 			},
* 			{kind: 'moon.ExpandableListItem',
* 				content: 'This is yet another grouped ExpandableListItem', components: [
* 					{content: 'Item Five'},
* 					{content: 'Item Six'}
* 				]
* 			}
* 		]}
* ```
*
* @class ExpandableListItem
* @extends module:enyo/Control~Control
* @ui
* @public
*/
module.exports = kind(
	/** @lends module:moonstone/ExpandableListItem~ExpandableListItem.prototype */ {

	/**
	* @private
	*/
	name: 'moon.ExpandableListItem',

	/**
	* @private
	*/
	kind: Control,

	/**
	* @private
	*/
	mixins: options.accessibility ? [ExpandableListItemAccessibilitySupport] : null,

	/**
	* @private
	* @lends module:moonstone/ExpandableListItem~ExpandableListItem.prototype
	*/
	published: {

		/**
		* If `true`, the drawer automatically closes when the user navigates to the top of the
		* control; if `false`, the user must select/tap the header to close the drawer.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		autoCollapse: false,

		/**
		* If `true`, the drawer is expanded, showing this item's contents. Use this property
		* (rather than [active]{@link module:moonstone/ExpandableListItem~ExpandableListItem#active}) to set the item's
		* initial state.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		open: false,

		/**
		* Boolean that reflects the value of the [open]{@link module:moonstone/ExpandableListItem~ExpandableListItem#open}
		* property; it is used to support the {@link module:enyo/Group~Group} API for grouping a set of
		* ExpandableListItems in which only one is expanded at a time. Note that the `open`
		* property (not the `active` property) controls the initial state of the picker.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		active: false,

		/**
		* If `true`, the user is prevented from moving {@glossary Spotlight} past the bottom
		* of the drawer (when open) using 5-way controls.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		lockBottom: false,

		/**
		* If `true`, item is shown as disabled and does not generate tap events.
		*
		* @type {Boolean}
		* @default false
		* @public
		*/
		disabled: false
	},

	/**
	* @private
	*/
	classes: 'moon-expandable-list-item',

	/**
	* @private
	*/
	spotlight: false,

	/**
	* @private
	*/
	defaultKind: Item,

	/**
	* @private
	*/
	handlers: {
		onSpotlightUp: 'spotlightUp',
		onSpotlightDown: 'spotlightDown',
		onDrawerAnimationEnd: 'drawerAnimationEnd'
	},

	/**
	* @private
	*/
	components: [
		// headerContainer required to avoid bad scrollWidth returned in RTL for certain text
		// widths (webkit bug)
		{name: 'headerContainer', kind: Item, classes: 'moon-expandable-list-item-header moon-expandable-picker-header moon-expandable-list-header', onSpotlightFocus: 'headerFocus', ontap: 'expandContract', components: [
			{name: 'header', kind: MarqueeText, accessibilityDisabled: true}
		]},
		{name: 'drawer', kind: ExpandableListDrawer, resizeContainer:false, classes: 'moon-expandable-list-item-client', components: [
			{name: 'client', kind: Group, tag: null}
		]}
	],

	/**
	* @private
	*/
	bindings: [
		{from: '.allowHtml', to: '.$.header.allowHtml'},
		{from: '.disabled', to: '.$.headerContainer.disabled'}
	],

	/**
	* @private
	*/
	create: function () {
		Control.prototype.create.apply(this, arguments);
		this.openChanged();
		this.setActive(this.open);
		this.disabledChanged();
	},

	/**
	* Facade for header content.
	*
	* @private
	*/
	contentChanged: function () {
		this.$.header.setContent(this.getContent());
	},

	/**
	* Facade for drawer.
	*
	* @private
	*/
	openChanged: function () {
		var open = this.getOpen();
		this.addRemoveClass('open', open);
		this.$.drawer.setOpen(open);
		this.$.drawer.spotlightDisabled = !open;
		this.setActive(open);
	},

	/**
	* @private
	*/
	disabledChanged: function () {
		var disabled = this.getDisabled();

		this.addRemoveClass('disabled', disabled);
		if (disabled) {
			this.setActive(false);
		}
	},

	/**
	* @fires module:enyo/GroupItem~GroupItem#onActivate
	* @private
	*/
	activeChanged: function () {
		this.bubble('onActivate', {allowHighlanderDeactivate:true});
		this.setOpen(this.active);
	},

	/**
	* If closed, opens drawer and highlights first spottable child.
	*
	* @private
	*/
	expandContract: function (inSender, inEvent) {
		this.toggleActive();

		if (this.getActive() && !Spotlight.getPointerMode()) {
			Spotlight.spot(Spotlight.getFirstChild(this.$.drawer));
		}
	},

	/**
	* @private
	*/
	toggleActive: function () {
		if (this.getOpen()) {
			this.setActive(false);
		} else {
			this.setActive(true);
		}
	},

	/**
	* If drawer is currently open, and event was sent via keypress (i.e., it has a direction),
	* process header focus.
	*
	* @fires module:moonstone/Scroller~Scroller#onRequestScrollIntoView
	* @private
	*/
	headerFocus: function (inSender, inEvent) {
		var direction = inEvent && inEvent.dir;

		if (this.getOpen() && this.getAutoCollapse() && direction === 'UP') {
			this.setActive(false);
		}

		if (inEvent.originator === this.$.header) {
			this.bubble('onRequestScrollIntoView');
		}
	},

	/**
	* Check for the first item in the client area, and close the list
	* if the focus moves out of the drawer and auto-collapse is true
	*
	* @private
	*/
	spotlightUp: function(inSender, inEvent) {
		var children = Spotlight.getChildren(this.$.client);
		if (children.length && inEvent.originator == children[0] && this.getAutoCollapse() && this.getOpen()) {
			this.setActive(false);
		}
	},

	/**
	* Check for the last item in the client area, and prevent 5-way focus movement below it,
	* per UX specs.
	*
	* @private
	*/
	spotlightDown: function (inSender, inEvent) {
		var children = Spotlight.getChildren(this.$.client);
		if (this.getLockBottom() && this.getOpen() && children.length && inEvent.originator == children[children.length - 1]) {
			return true;
		}
	},

	/**
	* @fires module:moonstone/Scroller~Scroller#onRequestScrollIntoView
	* @private
	*/
	drawerAnimationEnd: function () {
		this.bubble('onRequestScrollIntoView', {scrollInPointerMode: true});
		return true;
	}
});
<|endoftext|>


<commit_before>Hummingbird.ChatRoute = Ember.Route.extend({
  pingInterval: null,

  model: function() {
    return [];
  },

  afterModel: function() {
    Hummingbird.TitleManager.setTitle("Chat");
  },

  getOnlineUsers: function() {
    var self = this;
    ic.ajax({
      url: "/chat/ping",
      type: 'POST'
    }).then(function(onlineUsers) {
      self.set('controller.onlineUsers', onlineUsers);
    });
  },

  activate: function() {
    var self = this;
    MessageBus.subscribe("/chat", function(message) {
      self.get('controller').send("recvMessage", message);
    });
    self.getOnlineUsers();
    self.set('pingInterval', setInterval(self.getOnlineUsers.bind(self), 20000));
  },

  deactivate: function() {
    MessageBus.unsubscribe("/chat");
    if (self.get('pingInterval')) {
      clearInterval(self.get('pingInterval'));
      self.set('pingInterval', null);
    }
  }
});
<commit_msg>Chat route deactivate bugfix.<commit_after>Hummingbird.ChatRoute = Ember.Route.extend({
  pingInterval: null,

  model: function() {
    return [];
  },

  afterModel: function() {
    Hummingbird.TitleManager.setTitle("Chat");
  },

  getOnlineUsers: function() {
    var self = this;
    ic.ajax({
      url: "/chat/ping",
      type: 'POST'
    }).then(function(onlineUsers) {
      self.set('controller.onlineUsers', onlineUsers);
    });
  },

  activate: function() {
    var self = this;
    MessageBus.subscribe("/chat", function(message) {
      self.get('controller').send("recvMessage", message);
    });
    self.getOnlineUsers();
    self.set('pingInterval', setInterval(self.getOnlineUsers.bind(self), 20000));
  },

  deactivate: function() {
    MessageBus.unsubscribe("/chat");
    if (this.get('pingInterval')) {
      clearInterval(this.get('pingInterval'));
      this.set('pingInterval', null);
    }
  }
});
<|endoftext|>


<commit_before>Action = Class.create({

	initialize:function(){
		this.options = Object.extend({
			name:'',
			src:'',
			text:'',
			title:'',
			hasAccessKey:false,
			accessKey:'',
			callbackCode:'',
			callback:Prototype.emptyFunction,
			displayAction:false,
			prepareModal:false,
			formId:undefined,
			formCode:undefined
			}, arguments[0] || { });
		this.context = Object.extend({
			selection:true,
			dir:false,
			recycle:false,
			behaviour:'hidden',
			actionBar:false,
			actionBarGroup:'default',
			contextMenu:false,
			infoPanel:false
			}, arguments[1] || { });

		this.selectionContext = Object.extend({
			dir:false,
			file:true,
			recycle:false,
			behaviour:'disabled',
			allowedMimes:$A([]),
			unique:true,
			multipleOnly:false
			}, arguments[2] || { });
		this.rightsContext = Object.extend({
			noUser:true,
			userLogged:true,
			guestLogged:false,
			read:true,
			write:false,
			adminOnly:false
			}, arguments[3] || { });

		this.elements = new Array();
		this.contextHidden = false;
		this.deny = false;
	},

	apply: function(){
		if(this.deny) return;
		if(this.options.prepareModal){
			modal.prepareHeader(this.options.title, ajxpResourcesFolder+'/images/crystal/actions/16/'+this.options.src);
		}
		window.actionArguments = $A([]);
		if(arguments[0]) window.actionArguments = $A(arguments[0]);
		if(this.options.callbackCode) this.options.callbackCode.evalScripts();
		window.actionArguments = null;
	},

	fireContextChange: function(){
		if(arguments.length < 4) return;
		var usersEnabled = arguments[0]
		var crtUser = arguments[1];
		var crtIsRecycle = arguments[2];
		var crtDisplayMode = arguments[3];
		var rightsContext = this.rightsContext;
		if(!rightsContext.noUser && !usersEnabled){
			return this.hideForContext();
		}
		if((rightsContext.userLogged == 'only' && crtUser == null) ||
			(rightsContext.guestLogged && rightsContext.guestLogged=='hidden' & crtUser!=null && crtUser.id=='guest')){
			return this.hideForContext();
		}
		if(rightsContext.userLogged == 'hidden' && crtUser != null && !(crtUser.id=='guest' && rightsContext.guestLogged && rightsContext.guestLogged=='show') ){
			return this.hideForContext();
		}
		if(rightsContext.adminOnly && (crtUser == null || crtUser.id != 'admin')){
			return this.hideForContext();
		}
		if(rightsContext.read && crtUser != null && !crtUser.canRead()){
			return this.hideForContext();
		}
		if(rightsContext.write && crtUser != null && !crtUser.canWrite()){
			return this.hideForContext();
		}
		if(this.context.recycle){
			if(this.context.recycle == 'only' && !crtIsRecycle){
				return this.hideForContext();
			}
			if(this.context.recycle == 'hidden' && crtIsRecycle){
				return this.hideForContext();
			}
		}
		if(this.options.displayAction && this.options.displayAction == crtDisplayMode){
			return this.hideForContext();
		}
		this.showForContext();

	},

	fireSelectionChange: function(){
		if(arguments.length < 1
			|| this.contextHidden
			|| !this.context.selection) {
			return;
		}
		var userSelection = arguments[0];
		var bSelection = false;
		if(userSelection != null)
		{
			bSelection = !userSelection.isEmpty();
			var bUnique = userSelection.isUnique();
			var bFile = userSelection.hasFile();
			var bDir = userSelection.hasDir();
			var bRecycle = userSelection.isRecycle();
		}
		var selectionContext = this.selectionContext;
		if(selectionContext.allowedMimes.size()){
			if(selectionContext.behaviour == 'hidden') this.hide();
			else this.disable();
		}
		if(selectionContext.unique && !bUnique){
			return this.disable();
		}
		if((selectionContext.file || selectionContext.dir) && !bFile && !bDir){
			return this.disable();
		}
		if((selectionContext.dir && !selectionContext.file && bFile)
			|| (!selectionContext.dir && selectionContext.file && bDir)){
			return this.disable();
		}
		if(!selectionContext.recycle && bRecycle){
			return this.disable();
		}
		if(selectionContext.allowedMimes.size() && userSelection && !userSelection.hasMime(selectionContext.allowedMimes)){
			if(selectionContext.behaviour == 'hidden') return this.hide();
			else return this.disable();
		}
		this.show();
		this.enable();

	},

	createFromXML:function(xmlNode){
		this.options.name = xmlNode.getAttribute('name');
		for(var i=0; i<xmlNode.childNodes.length;i++){
			var node = xmlNode.childNodes[i];
			if(node.tagName == "processing"){
				for(var j=0; j<node.childNodes.length; j++){
					var processNode = node.childNodes[j];
					if(processNode.nodeName == "clientForm"){
						this.options.formId = processNode.getAttribute("id");
						this.options.formCode = processNode.firstChild.nodeValue;
						this.insertForm();
					}else if(processNode.nodeName == "clientCallback" && processNode.firstChild){
						this.options.callbackCode = '';
						if(processNode.getAttribute('prepareModal') && processNode.getAttribute('prepareModal') == "true"){
							this.options.prepareModal = true;
						}
						if(processNode.getAttribute('displayModeButton') && processNode.getAttribute('displayModeButton') != ''){
							this.options.displayAction = processNode.getAttribute('displayModeButton');
						}
					}
				}
			}else if(node.tagName == "gui"){
				this.options.text = MessageHash[node.getAttribute('text')];
				this.options.title = MessageHash[node.getAttribute('title')];
				this.options.src = node.getAttribute('src');
				if(node.getAttribute('hasAccessKey') && node.getAttribute('hasAccessKey') == "true"){
					this.options.accessKey = node.getAttribute('accessKey');
					this.options.hasAccessKey = true;
				}
				for(var j=0; j<node.childNodes.length;j++){
					if(node.childNodes[j].tagName == "context"){
						this.attributesToObject(this.context, node.childNodes[j]);
					}
					else if(node.childNodes[j].tagName == "selectionContext"){
						this.attributesToObject(this.selectionContext, node.childNodes[j]);
					}
				}

			}else if(node.tagName == "rightsContext"){
				this.attributesToObject(this.rightsContext, node);
			}
		}
		if(!this.options.hasAccessKey) return;
		if(this.options.accessKey == ''
			|| !MessageHash[this.options.accessKey]
			|| this.options.text.indexOf(MessageHash[this.options.accessKey]) == -1)
		{
			this.options.accessKey == this.options.text.charAt(0);
		}else{
			this.options.accessKey = MessageHash[this.options.accessKey];
		}
	},

	toActionBar:function(){
		var button = new Element('a', {
			href:this.options.name,
			id:this.options.name +'_button'
		}).observe('click', function(e){
			Event.stop(e);
			this.apply();
		}.bind(this));
		var imgPath = 'client/images/crystal/actions/22/'+this.options.src;
		var img = new Element('img', {
			src:imgPath,
			width:22,
			height:22,
			border:0,
			align:'absmiddle',
			alt:this.options.title,
			title:this.options.title
		});
		var titleSpan = new Element('span');
		button.insert(img).insert(new Element('br')).insert(titleSpan.update(this.getKeyedText()));
		this.elements.push(button);
		return button;
	},

	toInfoPanel:function(){
		return this.options;
	},

	toContextMenu:function(){
		return this.options;
	},

	hideForContext: function(){
		this.hide();
		this.contextHidden = true;
	},

	showForContext: function(){
		this.show();
		this.contextHidden = false;
	},

	hide: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = true;
		this.elements.each(function(elem){
			elem.hide();
		});
	},

	show: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = false;
		this.elements.each(function(elem){
			elem.show();
		});
	},

	disable: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = true;
		this.elements.each(function(elem){
			elem.addClassName('disabled');
		});
	},

	enable: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = false;
		this.elements.each(function(elem){
			elem.removeClassName('disabled');
		});
	},

	remove: function(){
		// Remove all elements and forms from html
		this.elements.each(function(el){
			$(el).remove();
		});
		if(this.options.formId && $('all_forms').select('[id="'+this.options.formId+'"]').length){
			$('all_forms').select('[id="'+this.options.formId+'"]')[0].remove();
		}
	},

	getKeyedText: function(){
		var displayString = this.options.text;
		if(!this.options.hasAccessKey) return displayString;
		var accessKey = this.options.accessKey;
		returnString = displayString.substring(0,displayString.indexOf(accessKey));
		returnString += ''+accessKey+'';
		returnString += displayString.substring(displayString.indexOf(accessKey)+1, displayString.length);
		return returnString;
	},

	insertForm: function(){
		if(!this.options.formCode || !this.options.formId) return;
		if($('all_forms').select('[id="'+this.options.formId+'"]').length) return;
		$('all_forms').insert(this.options.formCode);
	},

	attributesToObject: function(object, node){
		Object.keys(object).each(function(key){
			if(node.getAttribute(key)){
				value = node.getAttribute(key);
				if(value == 'true') value = true;
				else if(value == 'false') value = false;
				if(key == 'allowedMimes'){
					if(value && value.split(',').length){
						value = $A(value.split(','));
					}else{
						value = $A([]);
					}
				}
				this[key] = value;
			}
		}.bind(object));
	}

});<commit_msg>Use nodeName instead of tagName<commit_after>Action = Class.create({

	initialize:function(){
		this.options = Object.extend({
			name:'',
			src:'',
			text:'',
			title:'',
			hasAccessKey:false,
			accessKey:'',
			callbackCode:'',
			callback:Prototype.emptyFunction,
			displayAction:false,
			prepareModal:false,
			formId:undefined,
			formCode:undefined
			}, arguments[0] || { });
		this.context = Object.extend({
			selection:true,
			dir:false,
			recycle:false,
			behaviour:'hidden',
			actionBar:false,
			actionBarGroup:'default',
			contextMenu:false,
			infoPanel:false
			}, arguments[1] || { });

		this.selectionContext = Object.extend({
			dir:false,
			file:true,
			recycle:false,
			behaviour:'disabled',
			allowedMimes:$A([]),
			unique:true,
			multipleOnly:false
			}, arguments[2] || { });
		this.rightsContext = Object.extend({
			noUser:true,
			userLogged:true,
			guestLogged:false,
			read:true,
			write:false,
			adminOnly:false
			}, arguments[3] || { });

		this.elements = new Array();
		this.contextHidden = false;
		this.deny = false;
	},

	apply: function(){
		if(this.deny) return;
		if(this.options.prepareModal){
			modal.prepareHeader(this.options.title, ajxpResourcesFolder+'/images/crystal/actions/16/'+this.options.src);
		}
		window.actionArguments = $A([]);
		if(arguments[0]) window.actionArguments = $A(arguments[0]);
		if(this.options.callbackCode) this.options.callbackCode.evalScripts();
		window.actionArguments = null;
	},

	fireContextChange: function(){
		if(arguments.length < 4) return;
		var usersEnabled = arguments[0]
		var crtUser = arguments[1];
		var crtIsRecycle = arguments[2];
		var crtDisplayMode = arguments[3];
		var rightsContext = this.rightsContext;
		if(!rightsContext.noUser && !usersEnabled){
			return this.hideForContext();
		}
		if((rightsContext.userLogged == 'only' && crtUser == null) ||
			(rightsContext.guestLogged && rightsContext.guestLogged=='hidden' & crtUser!=null && crtUser.id=='guest')){
			return this.hideForContext();
		}
		if(rightsContext.userLogged == 'hidden' && crtUser != null && !(crtUser.id=='guest' && rightsContext.guestLogged && rightsContext.guestLogged=='show') ){
			return this.hideForContext();
		}
		if(rightsContext.adminOnly && (crtUser == null || crtUser.id != 'admin')){
			return this.hideForContext();
		}
		if(rightsContext.read && crtUser != null && !crtUser.canRead()){
			return this.hideForContext();
		}
		if(rightsContext.write && crtUser != null && !crtUser.canWrite()){
			return this.hideForContext();
		}
		if(this.context.recycle){
			if(this.context.recycle == 'only' && !crtIsRecycle){
				return this.hideForContext();
			}
			if(this.context.recycle == 'hidden' && crtIsRecycle){
				return this.hideForContext();
			}
		}
		if(this.options.displayAction && this.options.displayAction == crtDisplayMode){
			return this.hideForContext();
		}
		this.showForContext();

	},

	fireSelectionChange: function(){
		if(arguments.length < 1
			|| this.contextHidden
			|| !this.context.selection) {
			return;
		}
		var userSelection = arguments[0];
		var bSelection = false;
		if(userSelection != null)
		{
			bSelection = !userSelection.isEmpty();
			var bUnique = userSelection.isUnique();
			var bFile = userSelection.hasFile();
			var bDir = userSelection.hasDir();
			var bRecycle = userSelection.isRecycle();
		}
		var selectionContext = this.selectionContext;
		if(selectionContext.allowedMimes.size()){
			if(selectionContext.behaviour == 'hidden') this.hide();
			else this.disable();
		}
		if(selectionContext.unique && !bUnique){
			return this.disable();
		}
		if((selectionContext.file || selectionContext.dir) && !bFile && !bDir){
			return this.disable();
		}
		if((selectionContext.dir && !selectionContext.file && bFile)
			|| (!selectionContext.dir && selectionContext.file && bDir)){
			return this.disable();
		}
		if(!selectionContext.recycle && bRecycle){
			return this.disable();
		}
		if(selectionContext.allowedMimes.size() && userSelection && !userSelection.hasMime(selectionContext.allowedMimes)){
			if(selectionContext.behaviour == 'hidden') return this.hide();
			else return this.disable();
		}
		this.show();
		this.enable();

	},

	createFromXML:function(xmlNode){
		this.options.name = xmlNode.getAttribute('name');
		for(var i=0; i<xmlNode.childNodes.length;i++){
			var node = xmlNode.childNodes[i];
			if(node.nodeName == "processing"){
				for(var j=0; j<node.childNodes.length; j++){
					var processNode = node.childNodes[j];
					if(processNode.nodeName == "clientForm"){
						this.options.formId = processNode.getAttribute("id");
						this.options.formCode = processNode.firstChild.nodeValue;
						this.insertForm();
					}else if(processNode.nodeName == "clientCallback" && processNode.firstChild){
						this.options.callbackCode = '';
						if(processNode.getAttribute('prepareModal') && processNode.getAttribute('prepareModal') == "true"){
							this.options.prepareModal = true;
						}
						if(processNode.getAttribute('displayModeButton') && processNode.getAttribute('displayModeButton') != ''){
							this.options.displayAction = processNode.getAttribute('displayModeButton');
						}
					}
				}
			}else if(node.nodeName == "gui"){
				this.options.text = MessageHash[node.getAttribute('text')];
				this.options.title = MessageHash[node.getAttribute('title')];
				this.options.src = node.getAttribute('src');
				if(node.getAttribute('hasAccessKey') && node.getAttribute('hasAccessKey') == "true"){
					this.options.accessKey = node.getAttribute('accessKey');
					this.options.hasAccessKey = true;
				}
				for(var j=0; j<node.childNodes.length;j++){
					if(node.childNodes[j].nodeName == "context"){
						this.attributesToObject(this.context, node.childNodes[j]);
					}
					else if(node.childNodes[j].nodeName == "selectionContext"){
						this.attributesToObject(this.selectionContext, node.childNodes[j]);
					}
				}

			}else if(node.nodeName == "rightsContext"){
				this.attributesToObject(this.rightsContext, node);
			}
		}
		if(!this.options.hasAccessKey) return;
		if(this.options.accessKey == ''
			|| !MessageHash[this.options.accessKey]
			|| this.options.text.indexOf(MessageHash[this.options.accessKey]) == -1)
		{
			this.options.accessKey == this.options.text.charAt(0);
		}else{
			this.options.accessKey = MessageHash[this.options.accessKey];
		}
	},

	toActionBar:function(){
		var button = new Element('a', {
			href:this.options.name,
			id:this.options.name +'_button'
		}).observe('click', function(e){
			Event.stop(e);
			this.apply();
		}.bind(this));
		var imgPath = 'client/images/crystal/actions/22/'+this.options.src;
		var img = new Element('img', {
			src:imgPath,
			width:22,
			height:22,
			border:0,
			align:'absmiddle',
			alt:this.options.title,
			title:this.options.title
		});
		var titleSpan = new Element('span');
		button.insert(img).insert(new Element('br')).insert(titleSpan.update(this.getKeyedText()));
		this.elements.push(button);
		return button;
	},

	toInfoPanel:function(){
		return this.options;
	},

	toContextMenu:function(){
		return this.options;
	},

	hideForContext: function(){
		this.hide();
		this.contextHidden = true;
	},

	showForContext: function(){
		this.show();
		this.contextHidden = false;
	},

	hide: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = true;
		this.elements.each(function(elem){
			elem.hide();
		});
	},

	show: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = false;
		this.elements.each(function(elem){
			elem.show();
		});
	},

	disable: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = true;
		this.elements.each(function(elem){
			elem.addClassName('disabled');
		});
	},

	enable: function(){
		if(this.elements.size() > 0 || (!this.context.actionBar && this.context.infoPanel)) this.deny = false;
		this.elements.each(function(elem){
			elem.removeClassName('disabled');
		});
	},

	remove: function(){
		// Remove all elements and forms from html
		this.elements.each(function(el){
			$(el).remove();
		});
		if(this.options.formId && $('all_forms').select('[id="'+this.options.formId+'"]').length){
			$('all_forms').select('[id="'+this.options.formId+'"]')[0].remove();
		}
	},

	getKeyedText: function(){
		var displayString = this.options.text;
		if(!this.options.hasAccessKey) return displayString;
		var accessKey = this.options.accessKey;
		returnString = displayString.substring(0,displayString.indexOf(accessKey));
		returnString += ''+accessKey+'';
		returnString += displayString.substring(displayString.indexOf(accessKey)+1, displayString.length);
		return returnString;
	},

	insertForm: function(){
		if(!this.options.formCode || !this.options.formId) return;
		if($('all_forms').select('[id="'+this.options.formId+'"]').length) return;
		$('all_forms').insert(this.options.formCode);
	},

	attributesToObject: function(object, node){
		Object.keys(object).each(function(key){
			if(node.getAttribute(key)){
				value = node.getAttribute(key);
				if(value == 'true') value = true;
				else if(value == 'false') value = false;
				if(key == 'allowedMimes'){
					if(value && value.split(',').length){
						value = $A(value.split(','));
					}else{
						value = $A([]);
					}
				}
				this[key] = value;
			}
		}.bind(object));
	}

});<|endoftext|>