<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

define(["ceci-cards", "ceci-utils"], function(Ceci, Utils) {
  "use strict";

  /**
   *
   * Executes a callback with a new server-generated UUID
   * If only a callback is specified (first arg), it's called with no context,
   * otherwise the context is passed so one can reference "this"
   *
   * It's likely over-complicated, but I (wex) am writing code while tired,
   * hence my sense of obligation to make excuses in multi-line comments.
   *
   */
  function getUuid(contextOrCallback, callback) {
    Utils.get('/store/uuid', function(data) {
      if (!callback){
        if (typeof contextOrCallback === 'function'){
          callback = contextOrCallback;
          contextOrCallback = null;
        }
      }

      var context = contextOrCallback;

      if (callback){
        if (context){
          callback.call(context, data);
        }
        else {
          callback(data);
        }
      }
      else{
        throw "No callback specified for getUuid(); nothing to do!";
      }
    });
  }

  var onLoadListeners = [];

  var App = function(params) {

    if (Array.isArray(params.defaultChannels)) {
      Ceci.defaultChannels = params.defaultChannels.slice();
    }

    //TODO: This is faking the app "knowing" about cards, when we refactor, this should go
    if (params.onCardChange){
      Ceci.onCardChange(params.onCardChange);
    }
    if (params.onCardAdded){
      Ceci.onCardAdded(params.onCardAdded);
    }

    this.componentAddedCallback = typeof params.onComponentAdded === 'function' ? params.onComponentAdded : function(){};

    this.container = params.container;

    // generate a unique id that increments per tag ('moz-button-1', 'moz-button-2', etc.)
    this.tagids = {};
    this.generateTagId = function (tagName) {
      if (!this.tagids[tagName]){
        this.tagids[tagName] = 0;
      }
      return tagName + '-' + String(++this.tagids[tagName]);
    };

    /*
     * Here we clone each card and go two levels down, then construct listeners
     *

     *

     *      <app-* >...
     *

     *

     *      <app-* >...
     *

     *

     *      <app-* >...
     *

     *

     */
    this.serialize = function(){
      return this.getPortableAppTree().innerHTML;
    };

    this.addComponent = function (tagName, callback) {
      var component = document.createElement(tagName);
      component.setAttribute('id', this.generateTagId(tagName));

      var t = this;

      if (typeof callback === 'function') {
        callback(component);
      }
      Ceci.convertElement(component, function(){
        t.componentAddedCallback(component);
      }, true);
      Ceci.fireChangeEvent();
    };

    this.addCard = function (){
      var card = Ceci.createCard(this.container);
      return card;
    };

    this.duplicateCard = function (card) {
      var cardClone = card.cloneNode(true);
      this.getPortableCardTree(cardClone);
      Ceci.addCard(this.container, cardClone, this.componentAddedCallback);
      return cardClone;
    };

    this.getPortableCardTree = function (card) {

      function cleanAttributes (element, params) {
        params = params ? params : {};
        var allowedAttributes = params.keep;
        var reject = params.reject;
        var rejectEvents = params.rejectEvents;
        var rejectAll = false;

        //default reject everything
        if (typeof rejectEvents === 'undefined' && typeof allowedAttributes === 'undefined'){
          rejectAll = true;
        }

        if (typeof reject === 'string') {
          reject = [reject];
        }

        if (typeof allowedAttributes === 'string') {
          allowedAttributes = [allowedAttributes];
        }

        if (reject) {
          reject.forEach(function(name) {
            element.removeAttribute(name);
          });
        }

        Array.prototype.forEach.call(element.attributes, function (attr) {
          if (rejectAll) {
            element.removeAttribute(attr.name);
          }
          else {
            // Reject all except for allowed attributes
            if (allowedAttributes) {
              if (allowedAttributes.indexOf(attr.name) === -1) {
                element.removeAttribute(attr.name);
              }
            }
            else {
              // Or if allowed isn't set, reject /on.+/
              if (rejectEvents) {
                if (attr.name.indexOf('on') === 0 && attr.name.length != 2) {
                  element.removeAttribute(attr.name);
                }
              }
            }
          }
        });
        return element;
      }

      cleanAttributes(card, {keep: 'class', reject: ['id', 'style']});

      function cleanAppChildren (node) {
        if (node.localName.indexOf('app-') !== 0 && ['broadcast', 'listen'].indexOf(node.localName) === -1) {
          node.parentNode.removeChild(node);
        }
        cleanAttributes(node, {rejectEvents: true});
        if (node.children.length > 0) {
          Array.prototype.forEach.call(node.children, cleanAppChildren);
        }
      }

      Array.prototype.forEach.call(card.children, function (section) {
        Array.prototype.forEach.call(section.children, cleanAppChildren);
      });

      function cleanSection (sectionContainer) {
        var listenElements = Array.prototype.slice.call(sectionContainer.querySelectorAll('listen'));
        var broadcastElements = Array.prototype.slice.call(sectionContainer.querySelectorAll('broadcast'));
        var subscriptionElements = listenElements.concat(broadcastElements);

        subscriptionElements.forEach(function (subscriptionElement) {
          subscriptionElement.innerHTML = '';
          if (subscriptionElement.parentNode.localName.indexOf('app-') !== 0) {
            subscriptionElement.parentNode.removeChild(subscriptionElement);
          }
        });

        function cleanComponentElement (componentElement) {
          if (componentElement.localName.indexOf('app-') === 0 || ['broadcast', 'listen'].indexOf(componentElement.localName) > -1){
            Array.prototype.slice.call(componentElement.children).forEach(cleanComponentElement);
          }
          else {
            componentElement.parentNode.removeChild(componentElement);
          }
        }

        Array.prototype.slice.call(sectionContainer.children).forEach(cleanComponentElement);
      }

      ['.fixed-top', '.phone-canvas', '.fixed-bottom'].forEach(function (sectionSelector) {
        cleanSection(card.querySelector(sectionSelector));
      });

      return card;
    };

    this.getPortableAppTree = function () {
      var appContainerClone = this.container.cloneNode(true);
      var cards = appContainerClone.querySelectorAll('.ceci-card');

      Array.prototype.forEach.call(cards, this.getPortableCardTree);

      return appContainerClone;
    };

    // This is clearly a hack, we should just be doing new App(...),
    // but it's too much work to make that right right now.
    this.clear = function () {
      var t = this;
      getUuid(this, function(id){
        t.id = id;
        Ceci.clearCards();
        this.addCard();
      });
    };

    var init = function(id){
      var t = this;
      this.id = id;

      Ceci.load.call(t, function (components) {
        Ceci.convertContainer(t.container, function (element){
          element.setAttribute('id', t.generateTagId(element.tagName.toLowerCase()));
          t.componentAddedCallback(element);
        });

        // run any plugins to be run when the app is finished loading
        Ceci._plugins.onload.forEach(function(plugin) {
          plugin();
        });

        if (typeof params.onload === 'function'){
          params.onload.call(t, components);
        }

        Ceci.currentCard = t.container.querySelector('.ceci-card');
        onLoadListeners.forEach(function(listener) {
          listener(components);
        });
      });
    };

    if (params.id){
      init(params.id);
    } else {
      getUuid(this, init);
    }
  };

  App.onload = function(listener) {
    onLoadListeners.push(listener);
  };

  App.getUuid = getUuid;

  Ceci.App = App;

  return Ceci;
});
<commit_msg>Proxying removeCard in the app, ensuring that we always have one card for the app<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

define(["ceci-cards", "ceci-utils"], function(Ceci, Utils) {
  "use strict";

  /**
   *
   * Executes a callback with a new server-generated UUID
   * If only a callback is specified (first arg), it's called with no context,
   * otherwise the context is passed so one can reference "this"
   *
   * It's likely over-complicated, but I (wex) am writing code while tired,
   * hence my sense of obligation to make excuses in multi-line comments.
   *
   */
  function getUuid(contextOrCallback, callback) {
    Utils.get('/store/uuid', function(data) {
      if (!callback){
        if (typeof contextOrCallback === 'function'){
          callback = contextOrCallback;
          contextOrCallback = null;
        }
      }

      var context = contextOrCallback;

      if (callback){
        if (context){
          callback.call(context, data);
        }
        else {
          callback(data);
        }
      }
      else{
        throw "No callback specified for getUuid(); nothing to do!";
      }
    });
  }

  var onLoadListeners = [];

  var App = function(params) {

    if (Array.isArray(params.defaultChannels)) {
      Ceci.defaultChannels = params.defaultChannels.slice();
    }

    //TODO: This is faking the app "knowing" about cards, when we refactor, this should go
    if (params.onCardChange){
      Ceci.onCardChange(params.onCardChange);
    }
    if (params.onCardAdded){
      Ceci.onCardAdded(params.onCardAdded);
    }

    this.componentAddedCallback = typeof params.onComponentAdded === 'function' ? params.onComponentAdded : function(){};

    this.container = params.container;

    // generate a unique id that increments per tag ('moz-button-1', 'moz-button-2', etc.)
    this.tagids = {};
    this.generateTagId = function (tagName) {
      if (!this.tagids[tagName]){
        this.tagids[tagName] = 0;
      }
      return tagName + '-' + String(++this.tagids[tagName]);
    };

    /*
     * Here we clone each card and go two levels down, then construct listeners
     *

     *

     *      <app-* >...
     *

     *

     *      <app-* >...
     *

     *

     *      <app-* >...
     *

     *

     */
    this.serialize = function(){
      return this.getPortableAppTree().innerHTML;
    };

    this.addComponent = function (tagName, callback) {
      var component = document.createElement(tagName);
      component.setAttribute('id', this.generateTagId(tagName));

      var t = this;

      if (typeof callback === 'function') {
        callback(component);
      }
      Ceci.convertElement(component, function(){
        t.componentAddedCallback(component);
      }, true);
      Ceci.fireChangeEvent();
    };

    this.addCard = function (){
      var card = Ceci.createCard(this.container);
      return card;
    };

    this.duplicateCard = function (card) {
      var cardClone = card.cloneNode(true);
      this.getPortableCardTree(cardClone);
      Ceci.addCard(this.container, cardClone, this.componentAddedCallback);
      return cardClone;
    };

    this.removeCard = function(card){
      Ceci.removeCard(card, true);
      if (Ceci.cardCount() === 0){
        this.addCard();
      }
      Ceci.fireChangeEvent();
    };

    this.getPortableCardTree = function (card) {

      function cleanAttributes (element, params) {
        params = params ? params : {};
        var allowedAttributes = params.keep;
        var reject = params.reject;
        var rejectEvents = params.rejectEvents;
        var rejectAll = false;

        //default reject everything
        if (typeof rejectEvents === 'undefined' && typeof allowedAttributes === 'undefined'){
          rejectAll = true;
        }

        if (typeof reject === 'string') {
          reject = [reject];
        }

        if (typeof allowedAttributes === 'string') {
          allowedAttributes = [allowedAttributes];
        }

        if (reject) {
          reject.forEach(function(name) {
            element.removeAttribute(name);
          });
        }

        Array.prototype.forEach.call(element.attributes, function (attr) {
          if (rejectAll) {
            element.removeAttribute(attr.name);
          }
          else {
            // Reject all except for allowed attributes
            if (allowedAttributes) {
              if (allowedAttributes.indexOf(attr.name) === -1) {
                element.removeAttribute(attr.name);
              }
            }
            else {
              // Or if allowed isn't set, reject /on.+/
              if (rejectEvents) {
                if (attr.name.indexOf('on') === 0 && attr.name.length != 2) {
                  element.removeAttribute(attr.name);
                }
              }
            }
          }
        });
        return element;
      }

      cleanAttributes(card, {keep: 'class', reject: ['id', 'style']});

      function cleanAppChildren (node) {
        if (node.localName.indexOf('app-') !== 0 && ['broadcast', 'listen'].indexOf(node.localName) === -1) {
          node.parentNode.removeChild(node);
        }
        cleanAttributes(node, {rejectEvents: true});
        if (node.children.length > 0) {
          Array.prototype.forEach.call(node.children, cleanAppChildren);
        }
      }

      Array.prototype.forEach.call(card.children, function (section) {
        Array.prototype.forEach.call(section.children, cleanAppChildren);
      });

      function cleanSection (sectionContainer) {
        var listenElements = Array.prototype.slice.call(sectionContainer.querySelectorAll('listen'));
        var broadcastElements = Array.prototype.slice.call(sectionContainer.querySelectorAll('broadcast'));
        var subscriptionElements = listenElements.concat(broadcastElements);

        subscriptionElements.forEach(function (subscriptionElement) {
          subscriptionElement.innerHTML = '';
          if (subscriptionElement.parentNode.localName.indexOf('app-') !== 0) {
            subscriptionElement.parentNode.removeChild(subscriptionElement);
          }
        });

        function cleanComponentElement (componentElement) {
          if (componentElement.localName.indexOf('app-') === 0 || ['broadcast', 'listen'].indexOf(componentElement.localName) > -1){
            Array.prototype.slice.call(componentElement.children).forEach(cleanComponentElement);
          }
          else {
            componentElement.parentNode.removeChild(componentElement);
          }
        }

        Array.prototype.slice.call(sectionContainer.children).forEach(cleanComponentElement);
      }

      ['.fixed-top', '.phone-canvas', '.fixed-bottom'].forEach(function (sectionSelector) {
        cleanSection(card.querySelector(sectionSelector));
      });

      return card;
    };

    this.getPortableAppTree = function () {
      var appContainerClone = this.container.cloneNode(true);
      var cards = appContainerClone.querySelectorAll('.ceci-card');

      Array.prototype.forEach.call(cards, this.getPortableCardTree);

      return appContainerClone;
    };

    // This is clearly a hack, we should just be doing new App(...),
    // but it's too much work to make that right right now.
    this.clear = function () {
      var t = this;
      getUuid(this, function(id){
        t.id = id;
        Ceci.clearCards();
        this.addCard();
      });
    };

    var init = function(id){
      var t = this;
      this.id = id;

      Ceci.load.call(t, function (components) {
        Ceci.convertContainer(t.container, function (element){
          element.setAttribute('id', t.generateTagId(element.tagName.toLowerCase()));
          t.componentAddedCallback(element);
        });

        // run any plugins to be run when the app is finished loading
        Ceci._plugins.onload.forEach(function(plugin) {
          plugin();
        });

        if (typeof params.onload === 'function'){
          params.onload.call(t, components);
        }

        Ceci.currentCard = t.container.querySelector('.ceci-card');
        onLoadListeners.forEach(function(listener) {
          listener(components);
        });
      });
    };

    if (params.id){
      init(params.id);
    } else {
      getUuid(this, init);
    }
  };

  App.onload = function(listener) {
    onLoadListeners.push(listener);
  };

  App.getUuid = getUuid;

  Ceci.App = App;

  return Ceci;
});
<|endoftext|>


<commit_before>angular.module('opal.controllers').controller(
    'PatientListCtrl', function($scope, $q, $http, $cookieStore,
                                $location, $routeParams,
                                $modal, $rootScope, $window,
                                growl, Flow, Item, Episode,
                                episodedata, metadata, profile, episodeVisibility){

        $scope.ready = false;
        var version = window.version;
        if(episodedata.status == 'error'){
            if($cookieStore.get('opal.lastPatientList')){
                $cookieStore.remove('opal.lastPatientList');
                $location.path('/list/')
                return
            }
            $window.location.href = '/404';
            return
        }else{
            $scope.episodes = episodedata.data;
            $rootScope.state = 'normal';
            $scope.url = $location.url();
            $scope.metadata = metadata;
            $scope.listView = true;

            $scope.num_episodes = _.keys($scope.episodes).length;

  	        $scope.rix = 0; // row index
            $scope._ =  _;

  	        $scope.query = {
              hospital_number: '', first_name: '', surname: '', ward: '', bed: ''
            };
            $scope.$location = $location;
            $scope.path_base = '/list/';
            $scope.profile = profile;

            $cookieStore.put('opal.lastPatientList', $routeParams.slug);
            var tags = $routeParams.slug.split('-')
            $scope.currentTag = tags[0];
            $scope.currentSubTag = tags.length == 2 ? tags[1] : "";
            $scope.tag_display = metadata.tag_display;
            var pertinantTag = $scope.currentSubTag || $scope.currentTag;
        }

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

            visibleEpisodes = _.filter($scope.episodes, function(episode){
                return episodeVisibility(episode, $scope);
            });

    		    visibleEpisodes.sort(compareEpisodes);

            if($scope.rows && visibleEpisodes.length){
              var episodePresent = _.any(visibleEpisodes, function(x){
                  return x.id === $scope.episode.id;
              });

              if(!episodePresent){
                $scope.select_episode(visibleEpisodes[0], 0);
              }
            }
    		    return visibleEpisodes;
	    };

	    $scope.rows = $scope.getVisibleEpisodes();
        $scope.episode = $scope.rows[0];

        $scope.ready = true;

        //
        // This is used to be callable we can pass to
        // the table row iterator in the spreadsheet template.
        //
        $scope.isSelectedEpisode = function(episode){
            return episode === $scope.episode;
        }

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

      $scope.editTags = function(){
          $rootScope.state = 'modal';
          $scope.open_modal(
            'EditTeamsCtrl',
            '/templates/modals/edit_teams.html',
            {episode: $scope.episode}
          ).then(function(){
              if(!$scope.episode.hasTag(pertinantTag)){
                delete $scope.episodes[$scope.episode.id];
                $scope.rows = _.filter($scope.rows, function(e){
                    return e.id !== $scope.episode.id;
                });
              }
              $rootScope.state = 'normal';
              $scope.episode = $scope.rows[0];
          });
      };

      $scope.$on('change', function(event, episode) {
          episode = new Episode(episode);
          if($scope.episodes[episode.id]){
              $scope.episodes[episode.id] = episode;
              var rix = getRowIxFromEpisodeId(episode.id);
              if(rix != -1){
                  $scope.rows[rix] = episode;
              }
          }
      });

        $scope.jumpToTag = function(tag){
            if(_.contains(_.keys(metadata.tag_hierarchy), tag)){
                $location.path($scope.path_base + tag)
            }else{
                for(var prop in metadata.tag_hierarchy){
                    if(metadata.tag_hierarchy.hasOwnProperty(prop)){
                        if(_.contains(_.values(metadata.tag_hierarchy[prop]), tag)){
                            $location.path($scope.path_base + prop + '-' + tag)
                        }
                    }
                }
            };
        }

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

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

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

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

	    $scope.$on('keydown', function(event, e) {
		    if ($rootScope.state == 'normal') {
			    switch (e.keyCode) {
                case 191: // question mark
                    if(e.shiftKey){
                        $scope.keyboard_shortcuts();
                    }
                    break;
                case 13:
                    if(profile.can_see_pid()){
                        $location.url($scope.episode.link);
                    }
                    break;
    			case 38: // up
    				goUp();
    				break;
    			case 40: // down
    				goDown();
    				break;
                case 78: // n
                    $scope.addEpisode();
    		    }
            }
	    });

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

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

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

	    $scope.focusOnQuery = function() {
		    $rootScope.state = 'search';
	    };

	    $scope.blurOnQuery = function() {
		    $rootScope.state = 'normal';
	    };

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

            var enter = Flow.enter(
                {
                    current_tags: {
                        tag: $scope.currentTag,
                        subtag: $scope.currentSubTag
                    }
                }
            );

            $rootScope.state = 'modal';
            enter.then(
                function(resolved) {
		            // We have either retrieved an existing episode or created a new one,
                    // rendered a new modal for which we are waiting,
		            // or has cancelled the process at some point.

                    var return_to_normal = function(episode){
                    	// This ensures that the relevant episode is added to the table and
		                // selected.
		                var rowIx;
		                $rootScope.state = 'normal';
  		                if (episode && episode != 'cancel') {
                            //
                            // Occasionally the addPatient modal will add an episode to a list we're
                            // not currently on. So we check to see if they're tagged to this list.
                            //
                            if(episode.tagging[0][$scope.currentTag]){
                                if(!$scope.currentSubTag || episode.tagging[0][$scope.currentSubTag]){
  			                        $scope.episodes[episode.id] = episode;
  			                        $scope.rows = $scope.getVisibleEpisodes();
  			                        rowIx = getRowIxFromEpisodeId(episode.id);
                                    $scope.num_episodes += 1;
                                }
                            }
                            var msg = episode.demographics[0].first_name + " " + episode.demographics[0].surname;
                            var newTags = _.intersection(_.keys(episode.tagging[0]), _.keys(metadata.tags));
                            var newTag;

                            if(newTags.length > 1){
                              var tagObjs = _.filter(metadata.tags, function(t){ return _.contains(newTags, t.name); });
                              if($scope.currentSubTag.length){
                                newTag = _.findWhere(tagObjs, {parent_tag: $scope.currentTag}).name;
                              }
                              else{
                                newTag = _.findWhere(tagObjs, {name: $scope.currentTag}).name;
                              }
                            }
                            else{
                              newTag = newTags[0];
                            }
                            msg += " added to the " + metadata.tags[newTag].display_name + " list";
                            growl.success(msg);

  		                }
                    };
                    if(resolved && resolved.then){ // OMG - it's a promise!
                        resolved.then(
                            function(r){ return_to_normal(r) },
                            function(r){ return_to_normal(r) }
                        );
                    }else{
                        return_to_normal(resolved);
                    }
	            },
                function(reason){
                    // The modal has been dismissed. Practically speaking this means
                    // that the Angular UI called dismiss rather than our cancel()
                    // method on the OPAL controller. We just need to re-set in order
                    // to re-enable keybard listeners.
                    $rootScope.state = 'normal';
                });
	    };

        $scope.removeFromList = function(episode){
          delete $scope.episodes[episode.id];
          $scope.rows = $scope.getVisibleEpisodes();
          $scope.num_episodes -= 1;
          $scope.episode = $scope.rows[0];
        }

        $scope._post_discharge = function(result, episode){
    			$rootScope.state = 'normal';
    			if (result == 'discharged' | result == 'moved') {
            $scope.removeFromList(episode);
    			};
        };

	    $scope.dischargeEpisode = function(episode) {
            if(profile.readonly){ return null; };

		    $rootScope.state = 'modal';
            var exit = Flow.exit(episode,
                {
                    current_tags: {
                        tag   : $scope.currentTag,
                        subtag: $scope.currentSubTag
                    },
                }
            );

            exit.then(function(result) {
                //
                // Sometimes our Flow will open another modal - we wait for that
                // to close before firing the Post discharge hooks - this avoids the list
                // scope from trapping keystrokes etc
                //
                if(result && result.then){
                    result.then(function(r){
                      $scope._post_discharge(r, episode);
                    });
                }else{
                    $scope._post_discharge(result, episode);
                }
		    });
	    };

      $scope.removeFromMine = function(episode){
          if(profile.readonly){
              return null;
          }

          var modal;
          var tagging = episode.tagging[0];
          editing = tagging.makeCopy();
          editing.mine = false;
          tagging.save(editing).then(function(){
              $scope.removeFromList(episode);
          });

      };

      $scope.newNamedItem = function(episode, name) {
          return episode.recordEditor.newItem(name);
      };

      $scope.is_tag_visible_in_list = function(tag){
          return _.contains(metadata.tag_visible_in_list, tag);
      };

      $scope.editNamedItem  = function(episode, name, iix) {
          var reset_state = function(result){
              if (name == 'tagging') {
                  // User may have removed current tag
                  $scope.rows = $scope.getVisibleEpisodes();
              }
              var item = _.last(episode[name]);

              if (episode[name].sort){
                  episode.sortColumn(item.columnName, item.sort);
              }
          };

          episode.recordEditor.editItem(name, iix).then(function(result){
              reset_state(result);
          });
      };

	    function goUp() {
		    var episode;
            if ($scope.rix > 0) {
			    $scope.rix--;
				$scope.episode = getEpisode($scope.rix);
		    };
	    };

	    function goDown() {
		    var episode = getEpisode($scope.rix);
            if ($scope.rix < $scope.rows.length - 1) {
			    $scope.rix++;
                $scope.episode = $scope.rows[$scope.rix];
		    };
	    };

        $scope.select_episode = function(episode, rix){
            $scope.episode = episode;
            $scope.rix = rix;
        }

        $scope.keyboard_shortcuts = function(){
            $modal.open({
                controller: "KeyBoardShortcutsCtrl",
                templateUrl: 'list_keyboard_shortcuts.html'
            })
        }
    });
<commit_msg>Don't watch for change in PatientLists now that we no longer ever emit it from collaborative<commit_after>angular.module('opal.controllers').controller(
    'PatientListCtrl', function($scope, $q, $http, $cookieStore,
                                $location, $routeParams,
                                $modal, $rootScope, $window,
                                growl, Flow, Item, Episode,
                                episodedata, metadata, profile, episodeVisibility){

        $scope.ready = false;
        var version = window.version;
        if(episodedata.status == 'error'){
            if($cookieStore.get('opal.lastPatientList')){
                $cookieStore.remove('opal.lastPatientList');
                $location.path('/list/')
                return
            }
            $window.location.href = '/404';
            return
        }else{
            $scope.episodes = episodedata.data;
            $rootScope.state = 'normal';
            $scope.url = $location.url();
            $scope.metadata = metadata;
            $scope.listView = true;

            $scope.num_episodes = _.keys($scope.episodes).length;

  	        $scope.rix = 0; // row index
            $scope._ =  _;

  	        $scope.query = {
              hospital_number: '', first_name: '', surname: '', ward: '', bed: ''
            };
            $scope.$location = $location;
            $scope.path_base = '/list/';
            $scope.profile = profile;

            $cookieStore.put('opal.lastPatientList', $routeParams.slug);
            var tags = $routeParams.slug.split('-')
            $scope.currentTag = tags[0];
            $scope.currentSubTag = tags.length == 2 ? tags[1] : "";
            $scope.tag_display = metadata.tag_display;
            var pertinantTag = $scope.currentSubTag || $scope.currentTag;
        }

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

            visibleEpisodes = _.filter($scope.episodes, function(episode){
                return episodeVisibility(episode, $scope);
            });

    		    visibleEpisodes.sort(compareEpisodes);

            if($scope.rows && visibleEpisodes.length){
              var episodePresent = _.any(visibleEpisodes, function(x){
                  return x.id === $scope.episode.id;
              });

              if(!episodePresent){
                $scope.select_episode(visibleEpisodes[0], 0);
              }
            }
    		    return visibleEpisodes;
	    };

	    $scope.rows = $scope.getVisibleEpisodes();
        $scope.episode = $scope.rows[0];

        $scope.ready = true;

        //
        // This is used to be callable we can pass to
        // the table row iterator in the spreadsheet template.
        //
        $scope.isSelectedEpisode = function(episode){
            return episode === $scope.episode;
        }

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

      $scope.editTags = function(){
          $rootScope.state = 'modal';
          $scope.open_modal(
            'EditTeamsCtrl',
            '/templates/modals/edit_teams.html',
            {episode: $scope.episode}
          ).then(function(){
              if(!$scope.episode.hasTag(pertinantTag)){
                delete $scope.episodes[$scope.episode.id];
                $scope.rows = _.filter($scope.rows, function(e){
                    return e.id !== $scope.episode.id;
                });
              }
              $rootScope.state = 'normal';
              $scope.episode = $scope.rows[0];
          });
      };

        $scope.jumpToTag = function(tag){
            if(_.contains(_.keys(metadata.tag_hierarchy), tag)){
                $location.path($scope.path_base + tag)
            }else{
                for(var prop in metadata.tag_hierarchy){
                    if(metadata.tag_hierarchy.hasOwnProperty(prop)){
                        if(_.contains(_.values(metadata.tag_hierarchy[prop]), tag)){
                            $location.path($scope.path_base + prop + '-' + tag)
                        }
                    }
                }
            };
        }

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

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

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

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

	    $scope.$on('keydown', function(event, e) {
		    if ($rootScope.state == 'normal') {
			    switch (e.keyCode) {
                case 191: // question mark
                    if(e.shiftKey){
                        $scope.keyboard_shortcuts();
                    }
                    break;
                case 13:
                    if(profile.can_see_pid()){
                        $location.url($scope.episode.link);
                    }
                    break;
    			case 38: // up
    				goUp();
    				break;
    			case 40: // down
    				goDown();
    				break;
                case 78: // n
                    $scope.addEpisode();
    		    }
            }
	    });

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

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

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

	    $scope.focusOnQuery = function() {
		    $rootScope.state = 'search';
	    };

	    $scope.blurOnQuery = function() {
		    $rootScope.state = 'normal';
	    };

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

            var enter = Flow.enter(
                {
                    current_tags: {
                        tag: $scope.currentTag,
                        subtag: $scope.currentSubTag
                    }
                }
            );

            $rootScope.state = 'modal';
            enter.then(
                function(resolved) {
		            // We have either retrieved an existing episode or created a new one,
                    // rendered a new modal for which we are waiting,
		            // or has cancelled the process at some point.

                    var return_to_normal = function(episode){
                    	// This ensures that the relevant episode is added to the table and
		                // selected.
		                var rowIx;
		                $rootScope.state = 'normal';
  		                if (episode && episode != 'cancel') {
                            //
                            // Occasionally the addPatient modal will add an episode to a list we're
                            // not currently on. So we check to see if they're tagged to this list.
                            //
                            if(episode.tagging[0][$scope.currentTag]){
                                if(!$scope.currentSubTag || episode.tagging[0][$scope.currentSubTag]){
  			                        $scope.episodes[episode.id] = episode;
  			                        $scope.rows = $scope.getVisibleEpisodes();
  			                        rowIx = getRowIxFromEpisodeId(episode.id);
                                    $scope.num_episodes += 1;
                                }
                            }
                            var msg = episode.demographics[0].first_name + " " + episode.demographics[0].surname;
                            var newTags = _.intersection(_.keys(episode.tagging[0]), _.keys(metadata.tags));
                            var newTag;

                            if(newTags.length > 1){
                              var tagObjs = _.filter(metadata.tags, function(t){ return _.contains(newTags, t.name); });
                              if($scope.currentSubTag.length){
                                newTag = _.findWhere(tagObjs, {parent_tag: $scope.currentTag}).name;
                              }
                              else{
                                newTag = _.findWhere(tagObjs, {name: $scope.currentTag}).name;
                              }
                            }
                            else{
                              newTag = newTags[0];
                            }
                            msg += " added to the " + metadata.tags[newTag].display_name + " list";
                            growl.success(msg);

  		                }
                    };
                    if(resolved && resolved.then){ // OMG - it's a promise!
                        resolved.then(
                            function(r){ return_to_normal(r) },
                            function(r){ return_to_normal(r) }
                        );
                    }else{
                        return_to_normal(resolved);
                    }
	            },
                function(reason){
                    // The modal has been dismissed. Practically speaking this means
                    // that the Angular UI called dismiss rather than our cancel()
                    // method on the OPAL controller. We just need to re-set in order
                    // to re-enable keybard listeners.
                    $rootScope.state = 'normal';
                });
	    };

        $scope.removeFromList = function(episode){
          delete $scope.episodes[episode.id];
          $scope.rows = $scope.getVisibleEpisodes();
          $scope.num_episodes -= 1;
          $scope.episode = $scope.rows[0];
        }

        $scope._post_discharge = function(result, episode){
    			$rootScope.state = 'normal';
    			if (result == 'discharged' | result == 'moved') {
            $scope.removeFromList(episode);
    			};
        };

	    $scope.dischargeEpisode = function(episode) {
            if(profile.readonly){ return null; };

		    $rootScope.state = 'modal';
            var exit = Flow.exit(episode,
                {
                    current_tags: {
                        tag   : $scope.currentTag,
                        subtag: $scope.currentSubTag
                    },
                }
            );

            exit.then(function(result) {
                //
                // Sometimes our Flow will open another modal - we wait for that
                // to close before firing the Post discharge hooks - this avoids the list
                // scope from trapping keystrokes etc
                //
                if(result && result.then){
                    result.then(function(r){
                      $scope._post_discharge(r, episode);
                    });
                }else{
                    $scope._post_discharge(result, episode);
                }
		    });
	    };

      $scope.removeFromMine = function(episode){
          if(profile.readonly){
              return null;
          }

          var modal;
          var tagging = episode.tagging[0];
          editing = tagging.makeCopy();
          editing.mine = false;
          tagging.save(editing).then(function(){
              $scope.removeFromList(episode);
          });

      };

      $scope.newNamedItem = function(episode, name) {
          return episode.recordEditor.newItem(name);
      };

      $scope.is_tag_visible_in_list = function(tag){
          return _.contains(metadata.tag_visible_in_list, tag);
      };

      $scope.editNamedItem  = function(episode, name, iix) {
          var reset_state = function(result){
              if (name == 'tagging') {
                  // User may have removed current tag
                  $scope.rows = $scope.getVisibleEpisodes();
              }
              var item = _.last(episode[name]);

              if (episode[name].sort){
                  episode.sortColumn(item.columnName, item.sort);
              }
          };

          episode.recordEditor.editItem(name, iix).then(function(result){
              reset_state(result);
          });
      };

	    function goUp() {
		    var episode;
            if ($scope.rix > 0) {
			    $scope.rix--;
				$scope.episode = getEpisode($scope.rix);
		    };
	    };

	    function goDown() {
		    var episode = getEpisode($scope.rix);
            if ($scope.rix < $scope.rows.length - 1) {
			    $scope.rix++;
                $scope.episode = $scope.rows[$scope.rix];
		    };
	    };

        $scope.select_episode = function(episode, rix){
            $scope.episode = episode;
            $scope.rix = rix;
        }

        $scope.keyboard_shortcuts = function(){
            $modal.open({
                controller: "KeyBoardShortcutsCtrl",
                templateUrl: 'list_keyboard_shortcuts.html'
            })
        }
    });
<|endoftext|>


<commit_before>const fbbkJson = require('fbbk-json');

const ExtendedJSON = function () {
};

const extractMiddleString = function (str) {
  if (!str) {
    return '';
  }

  return str.substring(str.indexOf('"') + 1, str.lastIndexOf('"'));
};

const replaceShellStuff = function (str, regex, extendedJsonVersion) {
  const matches = str.match(regex);
  if (matches) {
    for (let i = 0; i < matches.length; i += 1) {
      str = str.replace(matches[i], `{${extendedJsonVersion}:"${extractMiddleString(matches[i])}"}`);
    }
  }

  return str;
};

// supporting shell commands for ObjectID and ISODate, https://docs.mongodb.com/manual/reference/mongodb-extended-json/
const convertToExtendedJson = function (str) {
  if (!str || Object.prototype.toString.call(str) !== '[object String]') {
    return;
  }

  // support shell stuff
  // replace objectID variations with $oid
  str = replaceShellStuff(str, /objectid\("[A-Z0-9]*"\)/gmi, '$oid');

  // replace ISODate|date variations with $date
  str = replaceShellStuff(str, /isodate\("[A-Z0-9- :.]*"\)|date\("[A-Z0-9- :.]*"\)|newdate\("[A-Z0-9- :.]*"\)|newisodate\("[A-Z0-9 -:.]*"\)/gmi, '$date');

  return str;
};

ExtendedJSON.prototype = {
  convertAndCheckJSON(json) {
    if (!json) return {};
    if (json.match(/[^\s"']+|"([^"]*)"|'([^']*)'/gm)) json = json.match(/[^\s"']+|"([^"]*)"|'([^']*)'/gm).join('');
    let result = {};
    try {
      if (!json.startsWith('{') && !json.startsWith('[')) {
        json = `{${json}`;
      }

      if ((!json.endsWith('}') && !json.endsWith(']')) ||
        (json.split('\{').length - 1) > (json.split('\}').length - 1)) {
        json = `${json}}`;
      }

      json = convertToExtendedJson(json);
      result = fbbkJson.parse(json);
    } catch (err) {
      result.ERROR = err.message;
    }

    return result;
  }
};

export default new ExtendedJSON();
<commit_msg>#407 querying<commit_after>const fbbkJson = require('fbbk-json');

const ExtendedJSON = function () {
};

const extractMiddleString = function (str) {
  if (!str) {
    return '';
  }

  return str.substring(str.indexOf('"') + 1, str.lastIndexOf('"'));
};

const replaceShellStuff = function (str, regex, extendedJsonVersion) {
  const matches = str.match(regex);
  if (matches) {
    for (let i = 0; i < matches.length; i += 1) {
      str = str.replace(matches[i], `{${extendedJsonVersion}:"${extractMiddleString(matches[i])}"}`);
    }
  }

  return str;
};

// supporting shell commands for ObjectID and ISODate, https://docs.mongodb.com/manual/reference/mongodb-extended-json/
const convertToExtendedJson = function (str) {
  if (!str || Object.prototype.toString.call(str) !== '[object String]') {
    return;
  }

  // support shell stuff
  // replace objectID variations with $oid
  str = replaceShellStuff(str, /objectid\("[A-Z0-9]*"\)/gmi, '$oid');

  // replace ISODate|date variations with $date
  str = replaceShellStuff(str, /isodate\("[A-Z0-9- :.]*"\)|date\("[A-Z0-9- :.]*"\)|newdate\("[A-Z0-9- :.]*"\)|newisodate\("[A-Z0-9 -:.]*"\)/gmi, '$date');

  return str;
};

ExtendedJSON.prototype = {
  convertAndCheckJSON(json) {
    if (!json) return {};
    if (json.match(/[^\s"']+|"([^"]*)"|'([^']*)'/gm)) json = json.match(/[^\s"']+|"([^"]*)"|'([^']*)'/gm).join('');
    let result = {};
    try {
      if (!json.startsWith('{') && !json.startsWith('[')) json = `{${json}`;

      if ((!json.endsWith('}') && !json.endsWith(']')) ||
        (json.split('\{').length - 1) > (json.split('\}').length - 1)) {
        json = `${json}}`;
      }

      json = convertToExtendedJson(json);
      result = fbbkJson.parse(json);
    } catch (err) {
      result.ERROR = err.message;
    }

    return result;
  }
};

export default new ExtendedJSON();
<|endoftext|>


<commit_before>'use strict';

const inherit = require('inherit');
const async = require('async');
const tough = require('tough-cookie');
const Cookie = tough.Cookie;

var languagePreset = {
	language: {
		japan: 'ja',
		english: 'en'
	}
}

var Injector = {

	__constructor: function(subdomains, cookies) {
		if (cookies instanceof Array)
		  cookies = cookies.map(Cookie.parse);
		else
		  cookies = [Cookie.parse(cookies)];

		this.cookies = cookies;
		this.tasks = [];
		this.subdomains = subdomains || ['/'];
		this.domain = 'dmm.com';
	},

	revokeRegionRestriction: function() {
		var targetCookie = {key: 'ckcy', value: 1};
		var self = this;
		this.tasks.push(function(done) {
			self.cookies = self.cookies.filter(function(cookie) {
				return cookie.key != targetCookie.key;
			})

			self.cookies = self.cookies.concat(generateCookies(targetCookie, [self.domain], self.subdomains));
			done();
		});

		return this;
	},

	language: function(language) {
		language = language || languagePreset.japan;
		var targetCookie = {key: 'cklg', value: language};
		var self = this;
		this.tasks.push(function(done) {
			self.cookies = self.cookies.filter(function(cookie) {
				return cookie.key != targetCookie.key;
			})

			self.cookies = self.cookies.concat(generateCookies(targetCookie, [self.domain], self.subdomains));
			done();
		});

		return this;
	},

	end: function(done) {
		var self = this;
		async.series(this.tasks, function(error) {
			done(error, self.cookies);
		});
	}
}

function generateCookies(keyVal, domains, paths) {
	var cookies = [];
	domains.forEach(function(domain) {
		paths.forEach(function(path) {
			var options = keyVal;
			options.domain = domain;
			options.path = path;

			var cookie = new Cookie(options);
			cookies.push(cookie);
		})
	})

	return cookies;
}

module.exports = exports = inherit(Injector, languagePreset);
<commit_msg>[injector] log revokeRegionRestriction() and language()<commit_after>'use strict';

const inherit = require('inherit');
const async = require('async');
const appLog = require('winston').loggers.get('app');
const tough = require('tough-cookie');
const Cookie = tough.Cookie;

var languagePreset = {
	language: {
		japan: 'ja',
		english: 'en'
	}
}

var Injector = {

	__constructor: function(subdomains, cookies) {
		appLog.verbose('parse cookie');
		if (cookies instanceof Array)
		  cookies = cookies.map(Cookie.parse);
		else
		  cookies = [Cookie.parse(cookies)];
		appLog.debug(cookies);

		this.cookies = cookies;
		this.tasks = [];
		this.subdomains = subdomains || ['/'];
		this.domain = 'dmm.com';
	},

	revokeRegionRestriction: function() {
		var targetCookie = {key: 'ckcy', value: 1};
		var self = this;
		appLog.verbose('push async task');
		this.tasks.push(function(done) {
			appLog.verbose('remove cookies that has value ' + targetCookie.key);
			self.cookies = self.cookies.filter(function(cookie) {
				return cookie.key != targetCookie.key;
			})
			appLog.debug(self.cookies);

			appLog.verbose('merge generated cookies');
			self.cookies = self.cookies.concat(generateCookies(targetCookie, [self.domain], self.subdomains));
			appLog.debug(self.cookies);
			done();
		});

		return this;
	},

	language: function(language) {
		language = language || languagePreset.japan;
		var targetCookie = {key: 'cklg', value: language};
		var self = this;
		appLog.verbose('push async task');
		this.tasks.push(function(done) {
			appLog.verbose('remove cookies that has value ' + targetCookie.key);
			self.cookies = self.cookies.filter(function(cookie) {
				return cookie.key != targetCookie.key;
			})
			appLog.debug(self.cookies);

			appLog.verbose('merge generated cookies');
			self.cookies = self.cookies.concat(generateCookies(targetCookie, [self.domain], self.subdomains));
			appLog.debug(self.cookies);
			done();
		});

		return this;
	},

	end: function(done) {
		appLog.info("start queued cookie injector's tasks");
		var self = this;
		async.series(this.tasks, function(error) {
			done(error, self.cookies);
		});
	}
}

function generateCookies(keyVal, domains, paths) {
	appLog.verbose('create cookies');
	appLog.debug(keyVal);
	appLog.debug(domains);
	appLog.debug(paths);

	var cookies = [];
	domains.forEach(function(domain) {
		paths.forEach(function(path) {
			var options = keyVal;
			options.domain = domain;
			options.path = path;

			var cookie = new Cookie(options);
			cookies.push(cookie);
		})
	})

	appLog.debug(cookies);
	return cookies;
}

module.exports = exports = inherit(Injector, languagePreset);
<|endoftext|>


<commit_before>var _ = require('underscore');
var express = require('express');
var request = require('request');
var handlebars = require('handlebars');
var hbs = require('hbs');
var sendgrid = require('sendgrid');
var keys = require('./keys.js');

var app = express();

hbs.registerHelper('foreach', function(context, options) {
    var ret = "";
    for(var prop in context)
    {
        ret = ret + options.fn({
			property:prop, value:context[prop]
        });
    }
    return ret;
});

app.configure('development', function() {
	app.use(express.bodyParser());
	app.use(express.errorHandler());
	app.locals.pretty = true;

	app.set("view engine", 'tmpl');
	app.set("view options", { layout: false });

	app.engine('tmpl', require('hbs').__express);

	app.use(express.static(__dirname + '/public'));
});

app.get('/', function(req, res){
	if(req.query.token){
		//show page with selection of groups
		//maybe should be client-side js for loading the groups?
	} else {
		//send them a facebook auth screen - this could be done on the client side, too
	}
	res.send(__dirname + '/public/index.html');
});

app.get('/selection', function(req, res){
	if(req.query.token && req.query.fbid){
		request({json: true, url:'https://graph.facebook.com/me', qs:{'access_token':req.query.token}}, function(error, response, body){
			if(!error && response.statusCode == 200){
				var user_info = body;
				request({json: true, url:'https://graph.facebook.com/me/accounts', qs:{'access_token':req.query.token}}, function(error, response, body){
					if(!error && response.statusCode == 200){
						var data = {
							name: user_info.first_name,
							accessToken: req.query.token,
							pages: body.data
						};

						res.render('pages', data, function(err, html){
							if(err){
								console.log(err);
							} else {
								res.send(html);
							}
						});
					}
				});
			}
		});
	} else {
		res.redirect('/');
	}
});

app.get('/:pageid', function(req, res){
	if(req.query.token){
		request({json: true, url:'https://graph.facebook.com/me/accounts', qs:{'access_token':req.query.token}}, function(error, response, body){
			if(!error && response.statusCode == 200){
				var page;
				_.each(body.data, function(element){
					if(element.id == req.params.pageid){
						page = element;
					}
				});
				res.redirect('/' + page.id + '?pageToken=' + page.access_token);
			}
		});
	} else if(req.query.pageToken) {
		request({json: true, url:'https://graph.facebook.com/' + req.params.pageid, qs:{'access_token':req.query.pageToken}}, function(error, response, body){
			if(!error && response.statusCode == 200){
				var page = body;
				request({json: true, url:'https://graph.facebook.com/' + req.params.pageid + '/feed', qs:{'access_token':req.query.pageToken}}, function(error, response, body){
					var item = body.feed.data[0];
					request({method: 'post', url:'http://access.alchemyapi.com/calls/text/TextGetTextSentiment', body:{apikey:keys.ALCHEMY, text:item.message, outputMode:'json'}}}, function(error, response, body){
						var sentiment = {
							type: body.docSentiment.type,
							value: body.docSentiment.score
						};
						var viewData = {
							post: item,
							sentiment: sentiment,
							page: page,
							name: 'Random User'
						};
						res.render('page', viewData, function(err, html){
							if(err){
								console.log(err);
							} else {
								res.send(html);
							}
						});
					});
				});
			}
		});
	} else {
		res.redirect('/');
	}
});

app.post('/:pageid', function(req, res){
	if(req.query.token){

	}
});

app.get('/:pageid/:postid', function(req, res){
	res.send('Nope');
});

app.listen(80);<commit_msg>extra bracket<commit_after>var _ = require('underscore');
var express = require('express');
var request = require('request');
var handlebars = require('handlebars');
var hbs = require('hbs');
var sendgrid = require('sendgrid');
var keys = require('./keys.js');

var app = express();

hbs.registerHelper('foreach', function(context, options) {
    var ret = "";
    for(var prop in context)
    {
        ret = ret + options.fn({
			property:prop, value:context[prop]
        });
    }
    return ret;
});

app.configure('development', function() {
	app.use(express.bodyParser());
	app.use(express.errorHandler());
	app.locals.pretty = true;

	app.set("view engine", 'tmpl');
	app.set("view options", { layout: false });

	app.engine('tmpl', require('hbs').__express);

	app.use(express.static(__dirname + '/public'));
});

app.get('/', function(req, res){
	if(req.query.token){
		//show page with selection of groups
		//maybe should be client-side js for loading the groups?
	} else {
		//send them a facebook auth screen - this could be done on the client side, too
	}
	res.send(__dirname + '/public/index.html');
});

app.get('/selection', function(req, res){
	if(req.query.token && req.query.fbid){
		request({json: true, url:'https://graph.facebook.com/me', qs:{'access_token':req.query.token}}, function(error, response, body){
			if(!error && response.statusCode == 200){
				var user_info = body;
				request({json: true, url:'https://graph.facebook.com/me/accounts', qs:{'access_token':req.query.token}}, function(error, response, body){
					if(!error && response.statusCode == 200){
						var data = {
							name: user_info.first_name,
							accessToken: req.query.token,
							pages: body.data
						};

						res.render('pages', data, function(err, html){
							if(err){
								console.log(err);
							} else {
								res.send(html);
							}
						});
					}
				});
			}
		});
	} else {
		res.redirect('/');
	}
});

app.get('/:pageid', function(req, res){
	if(req.query.token){
		request({json: true, url:'https://graph.facebook.com/me/accounts', qs:{'access_token':req.query.token}}, function(error, response, body){
			if(!error && response.statusCode == 200){
				var page;
				_.each(body.data, function(element){
					if(element.id == req.params.pageid){
						page = element;
					}
				});
				res.redirect('/' + page.id + '?pageToken=' + page.access_token);
			}
		});
	} else if(req.query.pageToken) {
		request({json: true, url:'https://graph.facebook.com/' + req.params.pageid, qs:{'access_token':req.query.pageToken}}, function(error, response, body){
			if(!error && response.statusCode == 200){
				var page = body;
				request({json: true, url:'https://graph.facebook.com/' + req.params.pageid + '/feed', qs:{'access_token':req.query.pageToken}}, function(error, response, body){
					var item = body.feed.data[0];
					request({method: 'post', url:'http://access.alchemyapi.com/calls/text/TextGetTextSentiment', body:{apikey:keys.ALCHEMY, text:item.message, outputMode:'json'}}, function(error, response, body){
						var sentiment = {
							type: body.docSentiment.type,
							value: body.docSentiment.score
						};
						var viewData = {
							post: item,
							sentiment: sentiment,
							page: page,
							name: 'Random User'
						};
						res.render('page', viewData, function(err, html){
							if(err){
								console.log(err);
							} else {
								res.send(html);
							}
						});
					});
				});
			}
		});
	} else {
		res.redirect('/');
	}
});

app.post('/:pageid', function(req, res){
	if(req.query.token){

	}
});

app.get('/:pageid/:postid', function(req, res){
	res.send('Nope');
});

app.listen(80);<|endoftext|>


<commit_before>/* globals describe, it, expect, expectObservable, expectSubscriptions, hot, cold */
var Rx = require('../../dist/cjs/Rx');
var Observable = Rx.Observable;
var Promise = require('promise');

describe('Observable.prototype.expand()', function () {
  it('should map and recursively flatten', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^           !   ';
    var e2shape =  '---(z|)         ';
    var expected = 'a--b--c--d--(e|)';
    /*
      expectation explanation: (conjunction junction?) ...

      since `cold('---(z|)')` emits `x + x` and completes on frame 30
      but the next "expanded" return value is synchronously subscribed to in
      that same frame, it stacks like so:

      a
      ---(b|)
         ---(c|)
            ---(d|)
               ---(e|)      (...which flattens into:)
      a--b--c--d--(e|)
    */

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should map and recursively flatten, and handle event raised error', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^        !   ';
    var e2shape =  '---(z|)      ';
    var expected = 'a--b--c--(d#)';

    var result = e1.expand(function (x) {
      if (x === 8) {
        return cold('#');
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should map and recursively flatten, and propagate error thrown from projection', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^        !   ';
    var e2shape =  '---(z|)      ';
    var expected = 'a--b--c--(d#)';

    var result = e1.expand(function (x) {
      if (x === 8) {
        throw 'error';
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow unsubscribing early', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var unsub =    '       !  ';
    var e1subs =   '^      !  ';
    var e2shape =  '---(z|)   ';
    var expected = 'a--b--c-  ';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result, unsub).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow concurrent expansions', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('a-a|              ', values);
    var e1subs =   '^             !   ';
    var e2shape =  '---(z|)           ';
    var expected = 'a-ab-bc-cd-de-(e|)';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow configuring the concurrency limit parameter to 1', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
      u: 10,
      v: 20,  // u + u
      x: 40,  // v + v
      y: 80,  // x + x
      z: 160, // y + y
    };
    var e1 =   hot('a-u|', values);
    var e2shape =  '---(z|)';
    //                 ---(z|)
    //                    ---(z|)
    //                       ---(z|)
    //                          ---(z|)
    //                             ---(z|)
    //                                ---(z|)
    //                                   ---(z|)
    // Notice how for each column, there is at most 1 `-` character.
    var e1subs =   '^                       !    ';
    var expected = 'a--u--b--v--c--x--d--y--(ez|)';
    var concurrencyLimit = 1;

    var result = e1.expand(function (x) {
      if (x === 16 || x === 160) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, concurrencyLimit);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow configuring the concurrency limit parameter to 2', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      u: 10,
      v: 20,  // u + u
      x: 40,  // v + v
    };
    var e1 =   hot('a---au|', values);
    var e2shape =  '------(z|)';
    //                  ------(z|)
    //                    ------(z|)
    //                        ------(z|)
    //                          ------(z|)
    //                              ------(z|)
    //                                ------(z|)
    // Notice how for each column, there is at most 2 `-` characters.
    var e1subs =   '^                     !   ';
    var expected = 'a---a-u---b-b---v-(cc)(x|)';
    var concurrencyLimit = 2;

    var result = e1.expand(function (x) {
      if (x === 4 || x === 40) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, concurrencyLimit);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should ignore concurrency limit if it is not passed', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
      u: 10,
      v: 20,  // u + u
      x: 40,  // v + v
      y: 80,  // x + x
      z: 160, // y + y
    };
    var e1 =   hot('a-u|              ', values);
    var e1subs =   '^             !   ';
    var e2shape =  '---(z|)           ';
    var expected = 'a-ub-vc-xd-ye-(z|)';
    var concurrencyLimit = 100;

    var result = e1.expand(function (x) {
      if (x === 16 || x === 160) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, concurrencyLimit);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should map and recursively flatten with scalars', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '(^!)';
    var expected = '(abcde|)';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return Observable.of(x + x); // scalar
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should recursively flatten promises', function (done) {
    var expected = [1, 2, 4, 8, 16];
    Observable.of(1)
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }
        return Promise.resolve(x + x);
      })
      .subscribe(function (x) {
        expect(x).toBe(expected.shift());
      }, null, function () {
        expect(expected.length).toBe(0);
        done();
      });
  });

  it('should recursively flatten Arrays', function (done) {
    var expected = [1, 2, 4, 8, 16];
    Observable.of(1)
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }
        return [x + x];
      })
      .subscribe(function (x) {
        expect(x).toBe(expected.shift());
      }, null, function () {
        expect(expected.length).toBe(0);
        done();
      });
  });

  it('should recursively flatten lowercase-o observables', function (done) {
    var expected = [1, 2, 4, 8, 16];

    Observable.of(1)
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }

        var ish = {
          subscribe: function (observer) {
            observer.next(x + x);
            observer.complete();
          }
        };

        ish[Symbol.observable] = function () { return this; };
        return ish;
      })
      .subscribe(function (x) {
        expect(x).toBe(expected.shift());
      }, null, function () {
        expect(expected.length).toBe(0);
        done();
      });
  });

  it('should work when passing undefined for the optional arguments', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^           !   ';
    var e2shape =  '---(z|)         ';
    var expected = 'a--b--c--d--(e|)';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, undefined, undefined);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });
});
<commit_msg>test(expand): add test against breaking unsubscription chain<commit_after>/* globals describe, it, expect, expectObservable, expectSubscriptions, hot, cold */
var Rx = require('../../dist/cjs/Rx');
var Observable = Rx.Observable;
var Promise = require('promise');

describe('Observable.prototype.expand()', function () {
  it('should map and recursively flatten', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^           !   ';
    var e2shape =  '---(z|)         ';
    var expected = 'a--b--c--d--(e|)';
    /*
      expectation explanation: (conjunction junction?) ...

      since `cold('---(z|)')` emits `x + x` and completes on frame 30
      but the next "expanded" return value is synchronously subscribed to in
      that same frame, it stacks like so:

      a
      ---(b|)
         ---(c|)
            ---(d|)
               ---(e|)      (...which flattens into:)
      a--b--c--d--(e|)
    */

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should map and recursively flatten, and handle event raised error', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^        !   ';
    var e2shape =  '---(z|)      ';
    var expected = 'a--b--c--(d#)';

    var result = e1.expand(function (x) {
      if (x === 8) {
        return cold('#');
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should map and recursively flatten, and propagate error thrown from projection', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^        !   ';
    var e2shape =  '---(z|)      ';
    var expected = 'a--b--c--(d#)';

    var result = e1.expand(function (x) {
      if (x === 8) {
        throw 'error';
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow unsubscribing early', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var unsub =    '       !  ';
    var e1subs =   '^      !  ';
    var e2shape =  '---(z|)   ';
    var expected = 'a--b--c-  ';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result, unsub).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should not break unsubscription chains when result is unsubscribed explicitly', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^      !  ';
    var e2shape =  '---(z|)   ';
    var expected = 'a--b--c-  ';
    var unsub =    '       !  ';

    var result = e1
      .mergeMap(function (x) { return Observable.of(x); })
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }
        return cold(e2shape, { z: x + x });
      })
      .mergeMap(function (x) { return Observable.of(x); });

    expectObservable(result, unsub).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow concurrent expansions', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('a-a|              ', values);
    var e1subs =   '^             !   ';
    var e2shape =  '---(z|)           ';
    var expected = 'a-ab-bc-cd-de-(e|)';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow configuring the concurrency limit parameter to 1', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
      u: 10,
      v: 20,  // u + u
      x: 40,  // v + v
      y: 80,  // x + x
      z: 160, // y + y
    };
    var e1 =   hot('a-u|', values);
    var e2shape =  '---(z|)';
    //                 ---(z|)
    //                    ---(z|)
    //                       ---(z|)
    //                          ---(z|)
    //                             ---(z|)
    //                                ---(z|)
    //                                   ---(z|)
    // Notice how for each column, there is at most 1 `-` character.
    var e1subs =   '^                       !    ';
    var expected = 'a--u--b--v--c--x--d--y--(ez|)';
    var concurrencyLimit = 1;

    var result = e1.expand(function (x) {
      if (x === 16 || x === 160) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, concurrencyLimit);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should allow configuring the concurrency limit parameter to 2', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      u: 10,
      v: 20,  // u + u
      x: 40,  // v + v
    };
    var e1 =   hot('a---au|', values);
    var e2shape =  '------(z|)';
    //                  ------(z|)
    //                    ------(z|)
    //                        ------(z|)
    //                          ------(z|)
    //                              ------(z|)
    //                                ------(z|)
    // Notice how for each column, there is at most 2 `-` characters.
    var e1subs =   '^                     !   ';
    var expected = 'a---a-u---b-b---v-(cc)(x|)';
    var concurrencyLimit = 2;

    var result = e1.expand(function (x) {
      if (x === 4 || x === 40) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, concurrencyLimit);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should ignore concurrency limit if it is not passed', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
      u: 10,
      v: 20,  // u + u
      x: 40,  // v + v
      y: 80,  // x + x
      z: 160, // y + y
    };
    var e1 =   hot('a-u|              ', values);
    var e1subs =   '^             !   ';
    var e2shape =  '---(z|)           ';
    var expected = 'a-ub-vc-xd-ye-(z|)';
    var concurrencyLimit = 100;

    var result = e1.expand(function (x) {
      if (x === 16 || x === 160) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, concurrencyLimit);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should map and recursively flatten with scalars', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '(^!)';
    var expected = '(abcde|)';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return Observable.of(x + x); // scalar
    });

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });

  it('should recursively flatten promises', function (done) {
    var expected = [1, 2, 4, 8, 16];
    Observable.of(1)
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }
        return Promise.resolve(x + x);
      })
      .subscribe(function (x) {
        expect(x).toBe(expected.shift());
      }, null, function () {
        expect(expected.length).toBe(0);
        done();
      });
  });

  it('should recursively flatten Arrays', function (done) {
    var expected = [1, 2, 4, 8, 16];
    Observable.of(1)
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }
        return [x + x];
      })
      .subscribe(function (x) {
        expect(x).toBe(expected.shift());
      }, null, function () {
        expect(expected.length).toBe(0);
        done();
      });
  });

  it('should recursively flatten lowercase-o observables', function (done) {
    var expected = [1, 2, 4, 8, 16];

    Observable.of(1)
      .expand(function (x) {
        if (x === 16) {
          return Observable.empty();
        }

        var ish = {
          subscribe: function (observer) {
            observer.next(x + x);
            observer.complete();
          }
        };

        ish[Symbol.observable] = function () { return this; };
        return ish;
      })
      .subscribe(function (x) {
        expect(x).toBe(expected.shift());
      }, null, function () {
        expect(expected.length).toBe(0);
        done();
      });
  });

  it('should work when passing undefined for the optional arguments', function () {
    var values = {
      a: 1,
      b: 1 + 1, // a + a,
      c: 2 + 2, // b + b,
      d: 4 + 4, // c + c,
      e: 8 + 8, // d + d
    };
    var e1 =   hot('(a|)', values);
    var e1subs =   '^           !   ';
    var e2shape =  '---(z|)         ';
    var expected = 'a--b--c--d--(e|)';

    var result = e1.expand(function (x) {
      if (x === 16) {
        return Observable.empty();
      }
      return cold(e2shape, { z: x + x });
    }, undefined, undefined);

    expectObservable(result).toBe(expected, values);
    expectSubscriptions(e1.subscriptions).toBe(e1subs);
  });
});
<|endoftext|>


<commit_before>import React from 'react';

import Analyzer from 'Parser/Core/Analyzer';
import Enemies from 'Parser/Core/Modules/Enemies';
import Combatants from 'Parser/Core/Modules/Combatants';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import { formatNumber, formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Wrapper from 'common/Wrapper';
import SpellLink from 'common/SpellLink';

import { UNSTABLE_AFFLICTION_DEBUFF_IDS } from '../../Constants';
import getDamageBonus from '../WarlockCore/getDamageBonus';

const AFFECTED_ABILITIES = [
  SPELLS.AGONY.id,
  SPELLS.CORRUPTION_DEBUFF.id,
  SPELLS.SIPHON_LIFE_TALENT.id,
  ...UNSTABLE_AFFLICTION_DEBUFF_IDS,
];

const MALEFIC_GRASP_DAMAGE_BONUS = 0.25;

class MaleficGrasp extends Analyzer {
  static dependencies = {
    enemies: Enemies,
    combatants: Combatants,
  };

  totalBonusDmg = 0;
  agonyBonusDmg = 0;
  corruptionBonusDmg = 0;
  siphonLifeBonusDmg = 0;
  unstableAfflictionBonusDmg = 0;

  totalTicks = 0;
  buffedTicks = 0;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.MALEFIC_GRASP_TALENT.id);
  }

  on_byPlayer_damage(event) {
    const spellId = event.ability.guid;
    if (!AFFECTED_ABILITIES.includes(spellId)) {
      return;
    }

    const target = this.enemies.getEntity(event);
    if (!target) {
      return;
    }

    const drained = target.hasBuff(SPELLS.DRAIN_SOUL.id, event.timestamp);
    if (AFFECTED_ABILITIES.includes(spellId)) {
      this.totalTicks += 1;
      if (drained) {
        this.buffedTicks += 1;
      }
    }

    if (drained) {
      const bonus = getDamageBonus(event, MALEFIC_GRASP_DAMAGE_BONUS);
      this.totalBonusDmg += bonus;

      switch (spellId) {
        case SPELLS.AGONY.id:
          this.agonyBonusDmg += bonus;
          break;
        case SPELLS.CORRUPTION_DEBUFF.id:
          this.corruptionBonusDmg += bonus;
          break;
        case SPELLS.SIPHON_LIFE_TALENT.id:
          this.siphonLifeBonusDmg += bonus;
          break;
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_1.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_2.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_3.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_4.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_5.id:
          this.unstableAfflictionBonusDmg += bonus;
          break;
        default:
          break;
      }
    }
  }

  get unbuffedTicks() {
    return this.totalTicks - this.buffedTicks;
  }

  get suggestionThresholds() {
    return {
      actual: (this.unbuffedTicks / this.totalTicks) || 1,
      isGreaterThan: {
        // TODO
        minor: 0.15,
        average: 0.2,
        major: 0.25,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(<Wrapper>Your <SpellLink id={SPELLS.UNSTABLE_AFFLICTION_CAST.id}/> aren't buffed enough by <SpellLink id={SPELLS.DRAIN_SOUL.id}/>. You should try to Drain your Unstable Afflictions as much as you can.</Wrapper>)
          .icon(SPELLS.MALEFIC_GRASP_TALENT.icon)
          .actual(`${formatPercentage(actual)}% unbuffed Unstable Affliction ticks.`)
          .recommended(`< ${formatPercentage(recommended)}% is recommended`);
      });
  }

  statistic() {
    const buffedTicksPercentage = (this.buffedTicks / this.totalTicks) || 1;
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.MALEFIC_GRASP_TALENT.id} />}
        value={`${formatPercentage(buffedTicksPercentage)} %`}
        label="UA ticks buffed by Malefic Grasp"
        tooltip={`Your Malefic Grasp talent contributed ${formatNumber(this.totalBonusDmg)} total damage (${formatPercentage(this.owner.getPercentageOfTotalDamageDone(this.totalBonusDmg))} %).

          ${this.agonyBonusDmg > 0 ? `

${formatNumber(this.agonyBonusDmg)} bonus Agony damage.

          ` : ''}

          ${this.corruptionBonusDmg > 0 ? `

${formatNumber(this.corruptionBonusDmg)} bonus Corruption damage.

          ` : ''}

          ${this.siphonLifeBonusDmg > 0 ? `

${formatNumber(this.siphonLifeBonusDmg)} bonus Siphon Life damage.

          ` : ''}

          ${this.unstableAfflictionBonusDmg > 0 ? `

${formatNumber(this.unstableAfflictionBonusDmg)} bonus Unstable Affliction damage.

          ` : ''}

        `}
      />
    );
  }

  statisticOrder = STATISTIC_ORDER.OPTIONAL(0);
}

export default MaleficGrasp;
<commit_msg>Woops<commit_after>import React from 'react';

import Analyzer from 'Parser/Core/Analyzer';
import Enemies from 'Parser/Core/Modules/Enemies';
import Combatants from 'Parser/Core/Modules/Combatants';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import { formatNumber, formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Wrapper from 'common/Wrapper';
import SpellLink from 'common/SpellLink';

import { UNSTABLE_AFFLICTION_DEBUFF_IDS } from '../../Constants';
import getDamageBonus from '../WarlockCore/getDamageBonus';

const AFFECTED_ABILITIES = [
  SPELLS.AGONY.id,
  SPELLS.CORRUPTION_DEBUFF.id,
  SPELLS.SIPHON_LIFE_TALENT.id,
  ...UNSTABLE_AFFLICTION_DEBUFF_IDS,
];

const MALEFIC_GRASP_DAMAGE_BONUS = 0.25;

class MaleficGrasp extends Analyzer {
  static dependencies = {
    enemies: Enemies,
    combatants: Combatants,
  };

  totalBonusDmg = 0;
  agonyBonusDmg = 0;
  corruptionBonusDmg = 0;
  siphonLifeBonusDmg = 0;
  unstableAfflictionBonusDmg = 0;

  totalTicks = 0;
  buffedTicks = 0;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.MALEFIC_GRASP_TALENT.id);
  }

  on_byPlayer_damage(event) {
    const spellId = event.ability.guid;
    if (!AFFECTED_ABILITIES.includes(spellId)) {
      return;
    }

    const target = this.enemies.getEntity(event);
    if (!target) {
      return;
    }

    const drained = target.hasBuff(SPELLS.DRAIN_SOUL.id, event.timestamp);
    if (UNSTABLE_AFFLICTION_DEBUFF_IDS.includes(spellId)) {
      this.totalTicks += 1;
      if (drained) {
        this.buffedTicks += 1;
      }
    }

    if (drained) {
      const bonus = getDamageBonus(event, MALEFIC_GRASP_DAMAGE_BONUS);
      this.totalBonusDmg += bonus;

      switch (spellId) {
        case SPELLS.AGONY.id:
          this.agonyBonusDmg += bonus;
          break;
        case SPELLS.CORRUPTION_DEBUFF.id:
          this.corruptionBonusDmg += bonus;
          break;
        case SPELLS.SIPHON_LIFE_TALENT.id:
          this.siphonLifeBonusDmg += bonus;
          break;
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_1.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_2.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_3.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_4.id:
        case SPELLS.UNSTABLE_AFFLICTION_DEBUFF_5.id:
          this.unstableAfflictionBonusDmg += bonus;
          break;
        default:
          break;
      }
    }
  }

  get unbuffedTicks() {
    return this.totalTicks - this.buffedTicks;
  }

  get suggestionThresholds() {
    return {
      actual: (this.unbuffedTicks / this.totalTicks) || 1,
      isGreaterThan: {
        // TODO
        minor: 0.15,
        average: 0.2,
        major: 0.25,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.suggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(<Wrapper>Your <SpellLink id={SPELLS.UNSTABLE_AFFLICTION_CAST.id}/> aren't buffed enough by <SpellLink id={SPELLS.DRAIN_SOUL.id}/>. You should try to Drain your Unstable Afflictions as much as you can.</Wrapper>)
          .icon(SPELLS.MALEFIC_GRASP_TALENT.icon)
          .actual(`${formatPercentage(actual)}% unbuffed Unstable Affliction ticks.`)
          .recommended(`< ${formatPercentage(recommended)}% is recommended`);
      });
  }

  statistic() {
    const buffedTicksPercentage = (this.buffedTicks / this.totalTicks) || 1;
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.MALEFIC_GRASP_TALENT.id} />}
        value={`${formatPercentage(buffedTicksPercentage)} %`}
        label="UA ticks buffed by Malefic Grasp"
        tooltip={`Your Malefic Grasp talent contributed ${formatNumber(this.totalBonusDmg)} total damage (${formatPercentage(this.owner.getPercentageOfTotalDamageDone(this.totalBonusDmg))} %).

          ${this.agonyBonusDmg > 0 ? `

${formatNumber(this.agonyBonusDmg)} bonus Agony damage.

          ` : ''}

          ${this.corruptionBonusDmg > 0 ? `

${formatNumber(this.corruptionBonusDmg)} bonus Corruption damage.

          ` : ''}

          ${this.siphonLifeBonusDmg > 0 ? `

${formatNumber(this.siphonLifeBonusDmg)} bonus Siphon Life damage.

          ` : ''}

          ${this.unstableAfflictionBonusDmg > 0 ? `

${formatNumber(this.unstableAfflictionBonusDmg)} bonus Unstable Affliction damage.

          ` : ''}

        `}
      />
    );
  }

  statisticOrder = STATISTIC_ORDER.OPTIONAL(0);
}

export default MaleficGrasp;
<|endoftext|>


<commit_before>(function (window, angular, undefined) { 'use strict';

    if (typeof angular == 'undefined') {
        return;
    }

    var displayMessage = function (message) {
        var body = angular.element('body'), status;

        if (body.children('.notification').length > 0)
            body.children('.notification').remove();

        var text = '';

        if (typeof message.text === 'object') {
            if (Object.keys(message.text).length > 0) {
                var firstKey = Object.keys(message.text)[0];

                if ((firstKey === 'error' || firstKey === 'message') && typeof message.text[firstKey] === 'string') {
                    text = message.text[firstKey];
                } else {
                    text = firstKey + ' ' + message.text[firstKey];
                }
            } else {
                text = 'No details available!';
            }
        } else {
            text = message.text
        }

        switch (message.type) {
            case 'danger':
                status = 'Error!';
                break;

            case 'success':
                status = 'Success!';
                break;

            case 'warning':
                status = 'Warning!';
                break;

            default:
                status = 'Message:';
                break
        }

        var div = angular.element('

', { class: 'notification ' + message.type })
            .html('' + status + ' ' + text + '.')
            .on('click', function () {
                div.fadeOut(200);
            });

        body.append(div);
        var interval = setInterval(function () { div.fadeOut({ duration: 1000, complete: function () { clearInterval(interval); } }) }, 4000);
    };

    angular.module('ngToast', [])
        .provider('toast', [function () {
            this.$get =[function() {
                return {
                    show: displayMessage,
                    error: function (message) { displayMessage({ type: 'danger', text: message }) },
                    warning: function (message) { displayMessage({ type: 'warning', text: message }) },
                    info: function (message) { displayMessage({ type: 'info', text: message }) },
                    success: function (message) { displayMessage({ type: 'success', text: message }) }
                }
            }]
        }]);
})(window, window.angular);<commit_msg>[added] catch exceptions on processing API response<commit_after>(function (window, angular, undefined) { 'use strict';

    if (typeof angular == 'undefined') {
        return;
    }

    var displayMessage = function (message) {
        var body = angular.element('body'), status;

        if (body.children('.notification').length > 0)
            body.children('.notification').remove();

        var text = '';

        try {
            if (typeof message.text === 'object') {
                if (Object.keys(message.text).length > 0) {
                    var firstKey = Object.keys(message.text)[0];

                    if ((firstKey === 'error' || firstKey === 'message') && typeof message.text[firstKey] === 'string') {
                        text = message.text[firstKey];
                    } else {
                        text = firstKey + ' ' + message.text[firstKey];
                    }
                } else {
                    text = 'No details available!';
                }
            } else {
                text = message.text
            }
        } catch (e) {
            text = 'No details available!'
        }

        switch (message.type) {
            case 'danger':
                status = 'Error!';
                break;

            case 'success':
                status = 'Success!';
                break;

            case 'warning':
                status = 'Warning!';
                break;

            default:
                status = 'Message:';
                break
        }

        var div = angular.element('

', { class: 'notification ' + message.type })
            .html('' + status + ' ' + text + '.')
            .on('click', function () {
                div.fadeOut(200);
            });

        body.append(div);
        var interval = setInterval(function () { div.fadeOut({ duration: 1000, complete: function () { clearInterval(interval); } }) }, 4000);
    };

    angular.module('ngToast', [])
        .provider('toast', [function () {
            this.$get =[function() {
                return {
                    show: displayMessage,
                    error: function (message) { displayMessage({ type: 'danger', text: message }) },
                    warning: function (message) { displayMessage({ type: 'warning', text: message }) },
                    info: function (message) { displayMessage({ type: 'info', text: message }) },
                    success: function (message) { displayMessage({ type: 'success', text: message }) }
                }
            }]
        }]);
})(window, window.angular);<|endoftext|>