<commit_before>'use strict';

angular.module('myApp.emotions', ['ngRoute'])

  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/emotions', {
      templateUrl: 'emotions/emotions.html',
      controller: 'EmotionsCtrl'
    });
  }])

  /**
   * The Emotion plane controller handles the interactions within the emotion plane.
   * The emotion plane allows a user to click somewhere in a 2d-plane with four emotions in each corner,
   * the application will then create a playlist based on the distance to each emotion.
   */
  .controller('EmotionsCtrl', ['$scope', 'ResourcesService', 'SongRequestService', 'EmotionsService','$timeout',
    function ($scope, ResourcesService, SongRequestService, EmotionsService,$timeout) {
      var DISPLAY_SONGS = false;

      /**
       * Set the size of the selection image
       */
      var setImageSize = function(){
        var CSS_plane = $('.plane');
        $scope.imgwidth = CSS_plane.outerWidth() * 1/(100/($scope.feature_variance*2));
        $scope.imgheight = CSS_plane.outerHeight() * 1/(100/($scope.feature_variance*2));
      };

      /**
       * Calculate the min-max values of each feature based on the distance between the click and the corners
       * @param x_coord The x-coordinate inside the plane.
       * @param y_coord The y-coordinate inside the plane.
       * @param plane_width The total width of the plane
       * @param plane_height The total height of the plane
       * @returns {Array} An array with each feature's min and max value calculated
       */
      var calcFeatures = function (x_coord, y_coord, plane_width, plane_height) {
        var feature_list = [];

        /* Scale the coordinate system to be between -1 and 1 */
        var x = 2 * ((x_coord / plane_width) - 0.5);
        var y = 2 * (0.5 - (y_coord / plane_height));

        /* Interpolate the features based on the distance to each emotion
         See http://www.speech.kth.se/prod/publications/files/1344.pdf for the algorithm used */
        for (var i = 0; i < $scope.emotions[0].features.length; i++) {
          var tr_max = $scope.topright.features[i].maxvalue;
          var tl_max = $scope.topleft.features[i].maxvalue;
          var bl_max = $scope.bottomleft.features[i].maxvalue;
          var br_max = $scope.bottomright.features[i].maxvalue;

          var tr_min = $scope.topright.features[i].minvalue;
          var tl_min = $scope.topleft.features[i].minvalue;
          var bl_min = $scope.bottomleft.features[i].minvalue;
          var br_min = $scope.bottomright.features[i].minvalue;

          var max_val = Math.round((0.25 * y * (x * (tr_max - tl_max - br_max + bl_max) + tr_max + tl_max - br_max - bl_max))
            + (0.25 * ((x * (tr_max - tl_max + br_max - bl_max)) + tr_max + tl_max + br_max + bl_max)));
          max_val = Math.min($scope.feature_variance + max_val, 100); // Limit max to 100

          var min_val = Math.round(0.25 * y * (x * (tr_min - tl_min - br_min + bl_min)
            + tr_min + tl_min - br_min - bl_min) + 0.25 * (x * (tr_min - tl_min + br_min - bl_min) + tr_min + tl_min + br_min + bl_min));
          min_val = Math.max(min_val - $scope.feature_variance, 0); // Limit min to 0

          feature_list.push({
            "feature": {"id": $scope.emotions[0].features[i].feature},
            "minvalue": min_val,
            "maxvalue": max_val
          });
        }
        return feature_list;
      };

      /**
       * Returns the inner width of the plane
       * @returns {*|jQuery}
       */
      var getPlaneWidth = function(){
        return $('.plane').innerWidth();
      };

      /**
       * Returns the inner height of the plane
       * @returns {*|jQuery}
       */
      var getPlaneHeight = function(){
        return $('.plane').innerHeight();
      };

      /**
       * Return where on the plane's x-axis (width) the selection marker is
       * @returns {number} 0 for the furthest left and 1 for furthest right
       */
      var getSelectionXPercent = function(){
        return ($scope.imgleft-$('.plane').offset().left)/getPlaneWidth();
      };

      /**
       * Return where on the plane's y-axis (height) the selection marker is
       * @returns {number} 0 for the furthest top and 1 for furthest bottom
       */
      var getSelectionYPercent = function(){
        return ($scope.imgtop-$('.plane').offset().top)/getPlaneHeight();
      };

      /**
       * Adds the input songs to the scope for display
       * @param songs The songs to display
       */
      var displaySongs = function (songs) {
        $scope.songs = [];
        var songPositions = $scope.songPosition;
        for (var i = 0; i < songs.length; i++) {
          if (songPositions.length > 0) {
            var random = Math.floor(Math.random() * (songPositions.length)); // Random a number between 0 and the amount of remaining positions
            var pos = songPositions[random];
            songPositions.splice(random, 1);

            $scope.songs.push({left: pos.left, top: pos.top, songID: songs[i].songID});
          }
        }
      };

      /**
       * Calculate positions around the selected position to display songs
       * @param middle_x The x position of the click
       * @param middle_y The y position of the click
       * @param plane_width The width of the plane
       * @param plane_height The height of the plane
       * @param offset The offset for the plane
       * @returns {Array} An array of coordinates around the selected position
       */
      var calcSongPositions = function (middle_x, middle_y, plane_width, plane_height, offset) {
        var positions = [];
        var xScale = plane_width / 30;
        var yScale = plane_height / 30;

        for (var i = -2; i < 3; i++) {
          for (var j = -2; j < 3; j++) {
            var left = middle_x + i * xScale;
            var top = middle_y + j * yScale;
            var check_left = left - offset.left;
            var check_top = top - offset.top;

            if (check_left >= 0 && check_left <= plane_width && check_top >= 0 && check_top <= plane_height) {
              positions.push({left: left, top: top});
            }
          }
        }
        return positions;
      };

      /**
       * planeClick is called whenever a user clicks on the 2d-plane
       * @param event Event
       */
      $scope.planeClick = function (event) {
        var CSS_plane = $('.plane');
        setImageSize();
        var plane_width = CSS_plane.outerWidth();
        var plane_height = CSS_plane.outerHeight();

        /* Calculate the location of the selection image */
        $scope.imgleft = event.pageX - ($scope.imgwidth / 2);
        $scope.imgtop = event.pageY - ($scope.imgheight / 2);

        /* Calculate the features required to request for music */
        $scope.songPosition = calcSongPositions($scope.imgleft, $scope.imgtop, plane_width, plane_height, CSS_plane.offset());

        var feature_list = calcFeatures(event.offsetX, event.offsetY, plane_width, plane_height);
        if (DISPLAY_SONGS) {
          SongRequestService.playMatchingSongs(feature_list, displaySongs);
        } else {
          SongRequestService.playMatchingSongs(feature_list);
        }
        $scope.feature_list = feature_list;

        // Save the current variables used for the click
        EmotionsService.saveClick($scope.imgleft, $scope.imgtop, $scope.imgwidth, $scope.imgheight, $scope.feature_list, getSelectionXPercent(), getSelectionYPercent());
      };

      /**
       * Updates the image position and size and saves it.
       */
      var updateWindow = function (){
        setImageSize();
        var CSS_plane = $('.plane');
        $scope.imgleft = CSS_plane.offset().left + getPlaneWidth()*EmotionsService.getSavedValues().selection_img_x_percent;
        $scope.imgtop = CSS_plane.offset().top + getPlaneHeight()*EmotionsService.getSavedValues().selection_img_y_percent;
        EmotionsService.saveClick($scope.imgleft, $scope.imgtop, $scope.imgwidth, $scope.imgheight, $scope.feature_list, getSelectionXPercent(), getSelectionYPercent());
      };

      /**
       * Initialize the image position and size.
       */
      var initWindow = function(){
        setImageSize();
        var CSS_plane = $('.plane');
        $scope.imgleft = CSS_plane.offset().left + getPlaneWidth()*EmotionsService.getSavedValues().selection_img_x_percent;

        /*
         For some arcane reason the top offset is 33.28 pixels smaller when the page is first loaded compared to after a click,
         so we'll apply this ugly workaround for now. TODO: Fix this strange behaviour
         */
        $scope.imgtop = 33.28 + CSS_plane.offset().top + getPlaneHeight()*EmotionsService.getSavedValues().selection_img_y_percent;
      };

      /**
       * If the user has previously clicked somewhere, load the values for the previous click
       */
      var loadValues = function () {
        var prevValues = EmotionsService.getSavedValues();
        $scope.imgwidth = prevValues.imgwidth;
        $scope.imgheight = prevValues.imgheight;
        $scope.imgleft = prevValues.imgleft;
        $scope.imgtop = prevValues.imgtop;
        $scope.feature_list = prevValues.feature_list;

        if(getSelectionXPercent() != prevValues.selection_img_x_percent || getSelectionYPercent() != prevValues.selection_img_y_percent){
          initWindow();
        }

      };

      /**
       * Initializes the handling of window resizing
       */
      var initWindowHandling = function(){
        var oldWidth = getPlaneWidth();

        $(window).on('resize.doResize', function () {
          var newWidth = getPlaneWidth();
          var updateStuffTimer;

          if (newWidth !== oldWidth) {
            $timeout.cancel(updateStuffTimer);
          }

          updateStuffTimer = $timeout(function() {
            updateWindow();
            oldWidth = newWidth;
          }, 100);
        });

        $scope.$on('$destroy',function (){
          $(window).off('resize.doResize'); // remove the handler added earlier
        });
      };

      /* Controller body starts here */
      $scope.feature_variance = 5;

      // Get labels, initialize select boxes and load existing values
      ResourcesService.Labels.emotions().$promise.then(function (data) {
        $scope.emotions = data;
        loadValues(data);
        $scope.topleft = data[0];
        $scope.bottomright = data[1];
        $scope.topright = data[2];
        $scope.bottomleft = data[3];
      }, function (err) {
        throw "No emotions were returned by query: " + err;
      });

      // Handle window resizing
      initWindowHandling();

    }]);<commit_msg>Reduced size of selection image.<commit_after>'use strict';

angular.module('myApp.emotions', ['ngRoute'])

  .config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/emotions', {
      templateUrl: 'emotions/emotions.html',
      controller: 'EmotionsCtrl'
    });
  }])

  /**
   * The Emotion plane controller handles the interactions within the emotion plane.
   * The emotion plane allows a user to click somewhere in a 2d-plane with four emotions in each corner,
   * the application will then create a playlist based on the distance to each emotion.
   */
  .controller('EmotionsCtrl', ['$scope', 'ResourcesService', 'SongRequestService', 'EmotionsService','$timeout',
    function ($scope, ResourcesService, SongRequestService, EmotionsService,$timeout) {
      var DISPLAY_SONGS = false;

      /**
       * Set the size of the selection image
       */
      var setImageSize = function(){
        var CSS_plane = $('.plane');
        $scope.imgwidth = CSS_plane.outerWidth() * 1/(100/($scope.feature_variance));
        $scope.imgheight = CSS_plane.outerHeight() * 1/(100/($scope.feature_variance));
      };

      /**
       * Calculate the min-max values of each feature based on the distance between the click and the corners
       * @param x_coord The x-coordinate inside the plane.
       * @param y_coord The y-coordinate inside the plane.
       * @param plane_width The total width of the plane
       * @param plane_height The total height of the plane
       * @returns {Array} An array with each feature's min and max value calculated
       */
      var calcFeatures = function (x_coord, y_coord, plane_width, plane_height) {
        var feature_list = [];

        /* Scale the coordinate system to be between -1 and 1 */
        var x = 2 * ((x_coord / plane_width) - 0.5);
        var y = 2 * (0.5 - (y_coord / plane_height));

        /* Interpolate the features based on the distance to each emotion
         See http://www.speech.kth.se/prod/publications/files/1344.pdf for the algorithm used */
        for (var i = 0; i < $scope.emotions[0].features.length; i++) {
          var tr_max = $scope.topright.features[i].maxvalue;
          var tl_max = $scope.topleft.features[i].maxvalue;
          var bl_max = $scope.bottomleft.features[i].maxvalue;
          var br_max = $scope.bottomright.features[i].maxvalue;

          var tr_min = $scope.topright.features[i].minvalue;
          var tl_min = $scope.topleft.features[i].minvalue;
          var bl_min = $scope.bottomleft.features[i].minvalue;
          var br_min = $scope.bottomright.features[i].minvalue;

          var max_val = Math.round((0.25 * y * (x * (tr_max - tl_max - br_max + bl_max) + tr_max + tl_max - br_max - bl_max))
            + (0.25 * ((x * (tr_max - tl_max + br_max - bl_max)) + tr_max + tl_max + br_max + bl_max)));
          max_val = Math.min($scope.feature_variance + max_val, 100); // Limit max to 100

          var min_val = Math.round(0.25 * y * (x * (tr_min - tl_min - br_min + bl_min)
            + tr_min + tl_min - br_min - bl_min) + 0.25 * (x * (tr_min - tl_min + br_min - bl_min) + tr_min + tl_min + br_min + bl_min));
          min_val = Math.max(min_val - $scope.feature_variance, 0); // Limit min to 0

          feature_list.push({
            "feature": {"id": $scope.emotions[0].features[i].feature},
            "minvalue": min_val,
            "maxvalue": max_val
          });
        }
        return feature_list;
      };

      /**
       * Returns the inner width of the plane
       * @returns {*|jQuery}
       */
      var getPlaneWidth = function(){
        return $('.plane').innerWidth();
      };

      /**
       * Returns the inner height of the plane
       * @returns {*|jQuery}
       */
      var getPlaneHeight = function(){
        return $('.plane').innerHeight();
      };

      /**
       * Return where on the plane's x-axis (width) the selection marker is
       * @returns {number} 0 for the furthest left and 1 for furthest right
       */
      var getSelectionXPercent = function(){
        return ($scope.imgleft-$('.plane').offset().left)/getPlaneWidth();
      };

      /**
       * Return where on the plane's y-axis (height) the selection marker is
       * @returns {number} 0 for the furthest top and 1 for furthest bottom
       */
      var getSelectionYPercent = function(){
        return ($scope.imgtop-$('.plane').offset().top)/getPlaneHeight();
      };

      /**
       * Adds the input songs to the scope for display
       * @param songs The songs to display
       */
      var displaySongs = function (songs) {
        $scope.songs = [];
        var songPositions = $scope.songPosition;
        for (var i = 0; i < songs.length; i++) {
          if (songPositions.length > 0) {
            var random = Math.floor(Math.random() * (songPositions.length)); // Random a number between 0 and the amount of remaining positions
            var pos = songPositions[random];
            songPositions.splice(random, 1);

            $scope.songs.push({left: pos.left, top: pos.top, songID: songs[i].songID});
          }
        }
      };

      /**
       * Calculate positions around the selected position to display songs
       * @param middle_x The x position of the click
       * @param middle_y The y position of the click
       * @param plane_width The width of the plane
       * @param plane_height The height of the plane
       * @param offset The offset for the plane
       * @returns {Array} An array of coordinates around the selected position
       */
      var calcSongPositions = function (middle_x, middle_y, plane_width, plane_height, offset) {
        var positions = [];
        var xScale = plane_width / 30;
        var yScale = plane_height / 30;

        for (var i = -2; i < 3; i++) {
          for (var j = -2; j < 3; j++) {
            var left = middle_x + i * xScale;
            var top = middle_y + j * yScale;
            var check_left = left - offset.left;
            var check_top = top - offset.top;

            if (check_left >= 0 && check_left <= plane_width && check_top >= 0 && check_top <= plane_height) {
              positions.push({left: left, top: top});
            }
          }
        }
        return positions;
      };

      /**
       * planeClick is called whenever a user clicks on the 2d-plane
       * @param event Event
       */
      $scope.planeClick = function (event) {
        var CSS_plane = $('.plane');
        setImageSize();
        var plane_width = CSS_plane.outerWidth();
        var plane_height = CSS_plane.outerHeight();

        /* Calculate the location of the selection image */
        $scope.imgleft = event.pageX - ($scope.imgwidth / 2);
        $scope.imgtop = event.pageY - ($scope.imgheight / 2);

        /* Calculate the features required to request for music */
        $scope.songPosition = calcSongPositions($scope.imgleft, $scope.imgtop, plane_width, plane_height, CSS_plane.offset());

        var feature_list = calcFeatures(event.offsetX, event.offsetY, plane_width, plane_height);
        if (DISPLAY_SONGS) {
          SongRequestService.playMatchingSongs(feature_list, displaySongs);
        } else {
          SongRequestService.playMatchingSongs(feature_list);
        }
        $scope.feature_list = feature_list;

        // Save the current variables used for the click
        EmotionsService.saveClick($scope.imgleft, $scope.imgtop, $scope.imgwidth, $scope.imgheight, $scope.feature_list, getSelectionXPercent(), getSelectionYPercent());
      };

      /**
       * Updates the image position and size and saves it.
       */
      var updateWindow = function (){
        setImageSize();
        var CSS_plane = $('.plane');
        $scope.imgleft = CSS_plane.offset().left + getPlaneWidth()*EmotionsService.getSavedValues().selection_img_x_percent;
        $scope.imgtop = CSS_plane.offset().top + getPlaneHeight()*EmotionsService.getSavedValues().selection_img_y_percent;
        EmotionsService.saveClick($scope.imgleft, $scope.imgtop, $scope.imgwidth, $scope.imgheight, $scope.feature_list, getSelectionXPercent(), getSelectionYPercent());
      };

      /**
       * Initialize the image position and size.
       */
      var initWindow = function(){
        setImageSize();
        var CSS_plane = $('.plane');
        $scope.imgleft = CSS_plane.offset().left + getPlaneWidth()*EmotionsService.getSavedValues().selection_img_x_percent;

        /*
         For some arcane reason the top offset is 33.28 pixels smaller when the page is first loaded compared to after a click,
         so we'll apply this ugly workaround for now. TODO: Fix this strange behaviour
         */
        $scope.imgtop = 33.28 + CSS_plane.offset().top + getPlaneHeight()*EmotionsService.getSavedValues().selection_img_y_percent;
      };

      /**
       * If the user has previously clicked somewhere, load the values for the previous click
       */
      var loadValues = function () {
        var prevValues = EmotionsService.getSavedValues();
        $scope.imgwidth = prevValues.imgwidth;
        $scope.imgheight = prevValues.imgheight;
        $scope.imgleft = prevValues.imgleft;
        $scope.imgtop = prevValues.imgtop;
        $scope.feature_list = prevValues.feature_list;

        if(getSelectionXPercent() != prevValues.selection_img_x_percent || getSelectionYPercent() != prevValues.selection_img_y_percent){
          initWindow();
        }
      };

      /**
       * Initializes the handling of window resizing
       */
      var initWindowHandling = function(){
        var oldWidth = getPlaneWidth();

        $(window).on('resize.doResize', function () {
          var newWidth = getPlaneWidth();
          var updateStuffTimer;

          if (newWidth !== oldWidth) {
            $timeout.cancel(updateStuffTimer);
          }

          updateStuffTimer = $timeout(function() {
            updateWindow();
            oldWidth = newWidth;
          }, 100);
        });

        $scope.$on('$destroy',function (){
          $(window).off('resize.doResize'); // remove the handler added earlier
        });
      };

      /* Controller body starts here */
      $scope.feature_variance = 5;

      // Get labels, initialize select boxes and load existing values
      ResourcesService.Labels.emotions().$promise.then(function (data) {
        $scope.emotions = data;
        loadValues(data);
        $scope.topleft = data[0];
        $scope.bottomright = data[1];
        $scope.topright = data[2];
        $scope.bottomleft = data[3];
      }, function (err) {
        throw "No emotions were returned by query: " + err;
      });

      // Handle window resizing
      initWindowHandling();

    }]);<|endoftext|>


<commit_before>/**
 * Model module
 * @module model
 */

'use strict';

var fake = require('./fake'); //Fake data
var localforage = require('localforage'); //Browser data storage
var pubSub = require('pubsub-js');

var configMap = {
  anonId: 'a0', //Special ID per anonymous person
  defaultAvatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg'
};
var stateMap = {
  anonUser: null,
  chatee: null,
  cidSerial: 0,
  isConnected: false,
  peopleCidMap: {},
  peopleDb: localforage.createInstance({
    name: 'people'
  }),
  currentUser: null
};
var isFakeData = true;

/* Exported variables hoisting, used also in internal functions before their formal declaration */
var people, chat, init;

/**
 * PRIVATE FUNCTIONS
 */

 /**
  * Prototype for a 'person' object
  */
var personProto = {
  getIsUser: function() {
    return this.cid === stateMap.currentUser.cid;
  },
  getIsAnon: function() {
    return this.cid === stateMap.anonUser.cid;
  }
};

/**
 * Creates the 'person' object and stores it in the db
 * @param  {object} personMap The person map
 * @return {object} person The created person
 */
var _makePerson = function(personMap) {
  var db = stateMap.peopleDb;
  var cid = personMap.cid;
  var id = personMap.id;
  var name = personMap.name;
  var avatar = personMap.avatar;
  var role = personMap.role;

  // 'cid === undefined' instead of '!cid' because if cid is zero '!cid' returns 1
  if( cid === undefined || name === undefined ) {
    throw 'Client id and name are required';
  }

  var person = Object.create(personProto);
  person.cid = cid;
  person.name = name;
  person.avatar = avatar;
  person.role = role;

  if(id) {
    person.id = id;
  }

  stateMap.peopleCidMap[cid] = person;

  db.setItem(cid, person, function(error) {
    if(error) {
      console.error(error);
    }
  });

  return person;
};

var _makeCid = function() {
  return 'c' + String(stateMap.cidSerial++);
};

/**
 * Refresh the 'peopleDb' when a new people list is received
 * @param  {array} peopleList Array of people returned from back-end
 */
var _updateList = function(peopleList) {
  var currentUser = people.getCurrentUser();
  var isChateeonline = false;

  people.clearDb();

  peopleList.forEach(function(person) {
    if(!person.name) {
      return;
    }

    if(currentUser && currentUser.id === person._id) {
      currentUser.avatar = person.avatar;
      return;
    }

    var clientPerson = _makePerson({
      cid: person._id,
      id: person._id,
      name: person.name,
      avatar: person.avatar,
      role: person.role
    });

    //Update any changes about the chatee (logged in or updated avatar)
    if(stateMap.chatee && stateMap.chatee.id === person._id) {
      isChateeonline = true;
      stateMap.chatee = clientPerson;
    }
  });

  if(stateMap.chatee && !isChateeonline) {
    chat.setChatee('');
  }

  return stateMap.peopleDb;
};

/**
 * EVENT HANDLERS
 */

/**
 * EVENTS PUBLISHED BY THE MODEL
 * -----------------------------
 * 	* setChatee: when a new chatee is set
 * 	* listChange: when the list of online people changes
 * 	* updateChat: when a new msg is set or received
 * 	* login: when login is completed
 * 	* logout: when logout is completed
 */

/**
 * Updates the current user information and publishes an 'login' event when
 * backend sends confirmation and data for the user login
 * @param  {object} user User data returned from the backend
 */
var _completeLogin = function(userList) {
  var user = userList[0];
  stateMap.currentUser.cid = user._id;
  stateMap.currentUser.id = user._id;
  stateMap.currentUser.name = user.name;
  stateMap.currentUser.avatar = user.avatar;

  stateMap.peopleCidMap[user._id] = user;

  console.log('Hello ' + user.name + ' !');

  chat.join();

  pubSub.publish('login', stateMap.currentUser);
};

/**
 * Updates 'peopleDb' and publish 'listChange' event
 * @param  {array} peopleList New peopleList returned from backend
 */
var _publishListChange = function(peopleList) {
  _updateList(peopleList);
  pubSub.publish('listChange', {
    newPeopleDb: people.getDb(),
    chatee: chat.getChatee()
  });
};

/**
 * Publishes the 'updateChat' event with relevant data for the view
 * @param  {object} msg Object map with data about the msg
 * @example _publishUpdateChat({
 *   destId: stateMap.chatee.id,
 *   destName: stateMap.chatee.name,
 *   senderId: stateMap.currentUser.id,
 *   msgText: msgText
 * })
 */
var _publishUpdateChat = function(msg) {
  console.log('New msg from backend: %o', msg);
  var sender = people.getByCid(msg.senderId);
  var isSenderUser = false;

  if(sender) {
    isSenderUser = sender.getIsUser();
  }

  //If user is not chatting with anyone or someone else wrote to us we set a new chatee
  if(
    !stateMap.chatee ||
    ( !isSenderUser && (msg.senderId !== stateMap.chatee.id) )
  ) {
    chat.setChatee(msg.senderId);
  }

  pubSub.publish('updateChat', {
    destId: msg.destId,
    destName: msg.destName,
    msgText: msg.msgText,
    isSenderUser: isSenderUser,
    sender: sender
  });
};

/**
 * PUBLIC FUNCTIONS
 */

/**
 * Object with methods relevant to 'people' such as peopleDb, log-in/out etc.
 * @type {Object}
 */
people = {
  clearDb: function() {
    var currentUser = stateMap.currentUser;

    stateMap.peopleDb.clear(function(err) {
      if(err) {
        console.error('Error: clearDb.');
      }

      stateMap.peopleDb.setItem(currentUser.cid, currentUser, function(err) {
        if(err) {
          console.error('Error. setItem in clearDb');
        }
      });
    });

    stateMap.peopleCidMap = {};
    stateMap.peopleCidMap[currentUser.cid] = currentUser;

  },

  getByCid: function(cid) {
    return stateMap.peopleCidMap[cid];
  },

  getCurrentUser: function() {
    return stateMap.currentUser;
  },

  getDb: function() {
    return stateMap.peopleCidMap;
  },

  login: function(userName) {
    var sio = isFakeData? fake.mockSio : console.error('No Sio');

    stateMap.currentUser = _makePerson({
      cid: _makeCid(),
      name: userName,
      avatar: configMap.defaultAvatar
    });

    sio.on('userupdate', _completeLogin);

    sio.emit('addUser', {
      cid: stateMap.currentUser.cid,
      name: stateMap.currentUser.name,
      avatar: stateMap.currentUser.avatar
    });
  },

  logout: function() {
    var user = stateMap.currentUser;

    chat.leave();

    stateMap.currentUser = stateMap.anonUser;
    people.clearDb();
    pubSub.publish('logout', user);

    return true;
  }
};

/**
 * Object with methods relevant to the chat such as join, set/get chatee, sendMsg
 * @type {Object}
 */
chat = {
  /**
   * Gets the current chatee
   * @return {object}
   */
  getChatee: function() {
    return stateMap.chatee;
  },

  leave: function() {
    var sio = fake.mockSio;

    stateMap.chatee = null;
    stateMap.isConnected = false;

    if(sio) {
      sio.emit('leaveChat');
    }
  },

  join: function() {
    var sio = fake.mockSio;

    if(stateMap.isConnected) {
      return false;
    }

    if(stateMap.currentUser.getIsAnon()) {
      console.warn('User must be signed in before joining chat');
      return false;
    }

    sio.on('listChange', _publishListChange);
    sio.on('updateChat', _publishUpdateChat);
    stateMap.isConnected = true;

    return true;
  },

  sendMsg: function(msgText) {
    var msg;
    var sio = fake.mockSio;

    //Abort sending a msg if there is no connection or user/chatee is not set
    if( !sio || !(stateMap.currentUser && stateMap.chatee) ) {
      return false;
    }

    msg = {
      destId: stateMap.chatee.id,
      destName: stateMap.chatee.name,
      senderId: stateMap.currentUser.id,
      msgText: msgText
    };

    _publishUpdateChat(msg);
    sio.emit('updateChat', msg);

    return true;
  },

  setChatee: function(personId) {
    var newChatee = stateMap.peopleCidMap[personId];

    if(newChatee) {
      if( stateMap.chatee && (stateMap.chatee.id === newChatee.id) ) {
        return false;
      }
    } else {
      newChatee = '';
    }

    pubSub.publish('setChatee', {
      oldChatee: stateMap.chatee,
      newChatee: newChatee
    });
    stateMap.chatee = newChatee;

    return true;
  },

  /**
   * Updates the avatar in the back-end
   * @param  {object} avatarMap Object map
   * @example updateAvatar({
   *   personId: {string},
   *   avatar: {string}
   * })
   */
  updateAvatar: function(avatarMap) {
    var sio = fake.mockSio;
    if(sio) {
      sio.emit('updateAvatar', avatarMap);
    }
  }
};

init = function() {
  stateMap.anonUser = _makePerson({
    cid: configMap.anonId,
    id: configMap.anonId,
    name: 'Anonymous',
    avatar: configMap.defaultAvatar
  });
  stateMap.currentUser = stateMap.anonUser;

  if(isFakeData) {
    //Init fake Socket IO
    fake.init();
  }

  //DEVELOPMENT ONLY
  window.chat = chat;
  window.people = people;
};

module.exports = {
  /** Object with methods relevant to the chat such as join, set/get chatee, sendMsg */
  chat: chat,
  /** Init the model */
  init: init,
  /** Object with methods relevant to 'people' such as peopleDb, log-in/out etc. */
  people: people
};
<commit_msg>Throw errors instead of console.error<commit_after>/**
 * Model module
 * @module model
 */

'use strict';

var fake = require('./fake'); //Fake data
var localforage = require('localforage'); //Browser data storage
var pubSub = require('pubsub-js');

var configMap = {
  anonId: 'a0', //Special ID per anonymous person
  defaultAvatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/adellecharles/128.jpg'
};
var stateMap = {
  anonUser: null,
  chatee: null,
  cidSerial: 0,
  isConnected: false,
  peopleCidMap: {},
  peopleDb: localforage.createInstance({
    name: 'people'
  }),
  currentUser: null
};
var isFakeData = true;

/* Exported variables hoisting, used also in internal functions before their formal declaration */
var people, chat, init;

/**
 * PRIVATE FUNCTIONS
 */

 /**
  * Prototype for a 'person' object
  */
var personProto = {
  getIsUser: function() {
    return this.cid === stateMap.currentUser.cid;
  },
  getIsAnon: function() {
    return this.cid === stateMap.anonUser.cid;
  }
};

/**
 * Creates the 'person' object and stores it in the db
 * @param  {object} personMap The person map
 * @return {object} person The created person
 */
var _makePerson = function(personMap) {
  var db = stateMap.peopleDb;
  var cid = personMap.cid;
  var id = personMap.id;
  var name = personMap.name;
  var avatar = personMap.avatar;
  var role = personMap.role;

  // 'cid === undefined' instead of '!cid' because if cid is zero '!cid' returns 1
  if( cid === undefined || name === undefined ) {
    throw new Error('Client id and name are required');
  }

  var person = Object.create(personProto);
  person.cid = cid;
  person.name = name;
  person.avatar = avatar;
  person.role = role;

  if(id) {
    person.id = id;
  }

  stateMap.peopleCidMap[cid] = person;

  db.setItem(cid, person, function(error) {
    if(error) {
      throw error;
    }
  });

  return person;
};

var _makeCid = function() {
  return 'c' + String(stateMap.cidSerial++);
};

/**
 * Refresh the 'peopleDb' when a new people list is received
 * @param  {array} peopleList Array of people returned from back-end
 */
var _updateList = function(peopleList) {
  var currentUser = people.getCurrentUser();
  var isChateeonline = false;

  people.clearDb();

  peopleList.forEach(function(person) {
    if(!person.name) {
      return;
    }

    if(currentUser && currentUser.id === person._id) {
      currentUser.avatar = person.avatar;
      return;
    }

    var clientPerson = _makePerson({
      cid: person._id,
      id: person._id,
      name: person.name,
      avatar: person.avatar,
      role: person.role
    });

    //Update any changes about the chatee (logged in or updated avatar)
    if(stateMap.chatee && stateMap.chatee.id === person._id) {
      isChateeonline = true;
      stateMap.chatee = clientPerson;
    }
  });

  if(stateMap.chatee && !isChateeonline) {
    chat.setChatee('');
  }

  return stateMap.peopleDb;
};

/**
 * EVENT HANDLERS
 */

/**
 * EVENTS PUBLISHED BY THE MODEL
 * -----------------------------
 * 	* setChatee: when a new chatee is set
 * 	* listChange: when the list of online people changes
 * 	* updateChat: when a new msg is set or received
 * 	* login: when login is completed
 * 	* logout: when logout is completed
 */

/**
 * Updates the current user information and publishes an 'login' event when
 * backend sends confirmation and data for the user login
 * @param  {object} user User data returned from the backend
 */
var _completeLogin = function(userList) {
  var user = userList[0];
  stateMap.currentUser.cid = user._id;
  stateMap.currentUser.id = user._id;
  stateMap.currentUser.name = user.name;
  stateMap.currentUser.avatar = user.avatar;

  stateMap.peopleCidMap[user._id] = user;

  console.log('Hello ' + user.name + ' !');

  chat.join();

  pubSub.publish('login', stateMap.currentUser);
};

/**
 * Updates 'peopleDb' and publish 'listChange' event
 * @param  {array} peopleList New peopleList returned from backend
 */
var _publishListChange = function(peopleList) {
  _updateList(peopleList);
  pubSub.publish('listChange', {
    newPeopleDb: people.getDb(),
    chatee: chat.getChatee()
  });
};

/**
 * Publishes the 'updateChat' event with relevant data for the view
 * @param  {object} msg Object map with data about the msg
 * @example _publishUpdateChat({
 *   destId: stateMap.chatee.id,
 *   destName: stateMap.chatee.name,
 *   senderId: stateMap.currentUser.id,
 *   msgText: msgText
 * })
 */
var _publishUpdateChat = function(msg) {
  console.log('New msg from backend: %o', msg);
  var sender = people.getByCid(msg.senderId);
  var isSenderUser = false;

  if(sender) {
    isSenderUser = sender.getIsUser();
  }

  //If user is not chatting with anyone or someone else wrote to us we set a new chatee
  if(
    !stateMap.chatee ||
    ( !isSenderUser && (msg.senderId !== stateMap.chatee.id) )
  ) {
    chat.setChatee(msg.senderId);
  }

  pubSub.publish('updateChat', {
    destId: msg.destId,
    destName: msg.destName,
    msgText: msg.msgText,
    isSenderUser: isSenderUser,
    sender: sender
  });
};

/**
 * PUBLIC FUNCTIONS
 */

/**
 * Object with methods relevant to 'people' such as peopleDb, log-in/out etc.
 * @type {Object}
 */
people = {
  clearDb: function() {
    var currentUser = stateMap.currentUser;

    stateMap.peopleDb.clear(function(err) {
      if(err) {
        throw err;
      }

      stateMap.peopleDb.setItem(currentUser.cid, currentUser, function(err) {
        if(err) {
          throw err;
        }
      });
    });

    stateMap.peopleCidMap = {};
    stateMap.peopleCidMap[currentUser.cid] = currentUser;

  },

  getByCid: function(cid) {
    return stateMap.peopleCidMap[cid];
  },

  getCurrentUser: function() {
    return stateMap.currentUser;
  },

  getDb: function() {
    return stateMap.peopleCidMap;
  },

  login: function(userName) {
    var sio = fake.mockSio;

    if(!sio) {
      throw new Error('No Socket.IO');
    }

    stateMap.currentUser = _makePerson({
      cid: _makeCid(),
      name: userName,
      avatar: configMap.defaultAvatar
    });

    sio.on('userupdate', _completeLogin);

    sio.emit('addUser', {
      cid: stateMap.currentUser.cid,
      name: stateMap.currentUser.name,
      avatar: stateMap.currentUser.avatar
    });
  },

  logout: function() {
    var user = stateMap.currentUser;

    chat.leave();

    stateMap.currentUser = stateMap.anonUser;
    people.clearDb();
    pubSub.publish('logout', user);

    return true;
  }
};

/**
 * Object with methods relevant to the chat such as join, set/get chatee, sendMsg
 * @type {Object}
 */
chat = {
  /**
   * Gets the current chatee
   * @return {object}
   */
  getChatee: function() {
    return stateMap.chatee;
  },

  leave: function() {
    var sio = fake.mockSio;

    stateMap.chatee = null;
    stateMap.isConnected = false;

    if(sio) {
      sio.emit('leaveChat');
    }
  },

  join: function() {
    var sio = fake.mockSio;

    if(stateMap.isConnected) {
      return false;
    }

    if(stateMap.currentUser.getIsAnon()) {
      console.warn('User must be signed in before joining chat');
      return false;
    }

    sio.on('listChange', _publishListChange);
    sio.on('updateChat', _publishUpdateChat);
    stateMap.isConnected = true;

    return true;
  },

  sendMsg: function(msgText) {
    var msg;
    var sio = fake.mockSio;

    //Abort sending a msg if there is no connection or user/chatee is not set
    if( !sio || !(stateMap.currentUser && stateMap.chatee) ) {
      return false;
    }

    msg = {
      destId: stateMap.chatee.id,
      destName: stateMap.chatee.name,
      senderId: stateMap.currentUser.id,
      msgText: msgText
    };

    _publishUpdateChat(msg);
    sio.emit('updateChat', msg);

    return true;
  },

  setChatee: function(personId) {
    var newChatee = stateMap.peopleCidMap[personId];

    if(newChatee) {
      if( stateMap.chatee && (stateMap.chatee.id === newChatee.id) ) {
        return false;
      }
    } else {
      newChatee = '';
    }

    pubSub.publish('setChatee', {
      oldChatee: stateMap.chatee,
      newChatee: newChatee
    });
    stateMap.chatee = newChatee;

    return true;
  },

  /**
   * Updates the avatar in the back-end
   * @param  {object} avatarMap Object map
   * @example updateAvatar({
   *   personId: {string},
   *   avatar: {string}
   * })
   */
  updateAvatar: function(avatarMap) {
    var sio = fake.mockSio;
    if(sio) {
      sio.emit('updateAvatar', avatarMap);
    }
  }
};

init = function() {
  stateMap.anonUser = _makePerson({
    cid: configMap.anonId,
    id: configMap.anonId,
    name: 'Anonymous',
    avatar: configMap.defaultAvatar
  });
  stateMap.currentUser = stateMap.anonUser;

  if(isFakeData) {
    //Init fake Socket IO
    fake.init();
  }

  //DEVELOPMENT ONLY
  window.chat = chat;
  window.people = people;
};

module.exports = {
  /** Object with methods relevant to the chat such as join, set/get chatee, sendMsg */
  chat: chat,
  /** Init the model */
  init: init,
  /** Object with methods relevant to 'people' such as peopleDb, log-in/out etc. */
  people: people
};
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import StatusBar from './components/StatusBar'

function mapStateToProps(state) {
  return {
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({}, dispatch)
}

class HomeScreenPage extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (

      <StatusBar hideBackToHomeLink={true} />

Hello, Blockstack

Forum

To Do List

Guild

Ongaku Ryoho

Casa

OpenBazaar

    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(HomeScreenPage)
<commit_msg>Add more app links<commit_after>import React, { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import StatusBar from './components/StatusBar'

function mapStateToProps(state) {
  return {
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({}, dispatch)
}

class HomeScreenPage extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    return (

      <StatusBar hideBackToHomeLink={true} />

Hello, Blockstack

Forum

To Do List

Guild

Ongaku Ryoho

Casa

OpenBazaar

    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(HomeScreenPage)
<|endoftext|>


<commit_before>"use strict";

angular.module('arethusa.sg').service('sg', [
  'state',
  'configurator',
  '$cacheFactory',
  'plugins',
  'notifier',
  function(state, configurator, $cacheFactory, plugins, notifier) {
    var self = this;
    this.name = 'sg';

    var retriever;
    this.labelAs = "long";
    this.defineAncestors = true;

    var sgCache = $cacheFactory('sg', { number:  100 });

    this.defaultConf = {
      displayName: "SG"
    };

    function configure() {
      configurator.getConfAndDelegate(self, ['labels']);
      retriever = configurator.getRetriever(self.conf.retriever);
    }

    function SgTemplate() {
      var self = this;

      this.morph = {};
      this.ancestors = [];
      this.definingAttrs = [];
      this.menu = {};
      this.hasChanged = true;
      this.isSgTemplate = true;
      this.markChange = function() {
        self.hasChanged = true;
      };
    }

    function grammarReset(grammar) {
      arethusaUtil.empty(grammar.ancestors);
      // We have to redefine this property - it's untouchable now
      // that it's cached!
      grammar.definingAttrs = [];
    }

    function sgFromStateComplete(sg) {
      return sg && sg.isSgTemplate;
    }

    function sgFromRetriever(sg) {
      return sg && sg.ancestors && !sg.isTemplate;
    }

    function createNewSgObject(token) {
      var grammar = new SgTemplate();
      var morph = token.morphology || {};
      grammar.string = token.string;
      checkAndUpdateGrammar(morph, grammar);
      return grammar;
    }

    function createInternalState() {
      // 3 possibilites here:
      //
      // 1 When we have seen this token set before, we don't need to do anything
      //   and can take the info we already created earlier in time.
      // 2 We have not seen the tokens, but they have info from the retrieved
      //   document. In such a case we build a new SgTemplate and add the sg
      //   ancestors from the token to it.
      // 3 We have to start from scratch.
      return arethusaUtil.inject({}, state.tokens, function(memo, id, token) {
        var grammar;
        var fromState = token.sg;
        if (sgFromStateComplete(fromState)) {
          grammar = fromState;
        } else {
          grammar = createNewSgObject(token);
          if (sgFromRetriever(fromState)) {
            addAncestorsFromState(fromState, grammar);
          }
        }
        memo[id] = grammar;
      });
    }

    function addAncestorsFromState(sg, grammar) {
      var ancestors = sg.ancestors;
      var menu = grammar.menu;
      angular.forEach(ancestors, function(ancestor, i) {
        if (menu) {
          // The || menu.nested party is very hacky, but effective.
          // It can appear in menus that show a morph preselection -
          // I guess... Check a word with a dative proper e.g.
          var expandedAncestor = menu[ancestor] || menu.nested;
          if (!expandedAncestor) {
            notifier.error("Failed to parse SG annotation (" + ancestors + ")");
            notifier.warning("You might be using an incompatible version of the SG tagset");
            menu = undefined;
            return;
          }
          grammar.ancestors.push(expandedAncestor);
          menu = expandedAncestor.nested;
        }
      });
    }

    var hint = "Please select a morphological form first!";

    this.currentGrammar = function() {
      return arethusaUtil.inject({}, state.selectedTokens, function(memo, id, event) {
        var morph = state.tokens[id].morphology;
        var grammar = self.grammar[id];
        if (grammar) {
          if (morph && morph.attributes) {
            delete grammar.hint;
            checkAndUpdateGrammar(morph, grammar);
          } else {
            grammarReset(grammar);
            grammar.morph = {};
            grammar.hint = hint;
          }
          memo[id] = grammar;
        }
      });
    };

    function morphHasChanged(a, b) {
      return !angular.equals(a, b);
    }

    function checkAndUpdateGrammar(morph, grammar) {
      if (morphHasChanged(grammar.morph, morph.attributes)) {
        // Need to empty first, otherwise we might mix attributes
        // of various part of speech types together!
        arethusaUtil.empty(grammar.morph);
        angular.extend(grammar.morph, morph.attributes);
        updateGrammar(self.labels, grammar);
      }
      return grammar;
    }

    function updateGrammar(labels, grammar) {
      grammarReset(grammar);
      findDefiningAttributes(self.labels, grammar, grammar.definingAttrs);
      extractMenu(grammar);
      cacheUpdateProcess(grammar);
      grammar.markChange();
    }

    function cacheUpdateProcess(grammar) {
      var key = cacheKey(grammar);
      if (!sgCache.get(key)) {
        sgCache.put(key, grammar.definingAttrs);
      }
    }

    function cacheKey(grammar) {
      return arethusaUtil.inject([], grammar.morph, function(memo, k, v) {
        memo.push(k + '-' + v);
      }).sort().join('|');
    }

    function findDefiningAttributes(labels, grammar, target) {
      var cached = sgCache.get(cacheKey(grammar));
      if (cached) {
        arethusaUtil.pushAll(target, cached);
      } else {
        arethusaUtil.inject(target, labels, function(memo, label, val) {
          var dep = val.dependency;
          if (dep) {
            var morph = grammar.morph;
            var nextLevel;
            angular.forEach(dep, function(depVal, depCat) {
              if (dependencyMet(morph[depCat], depVal)) {
                val = angular.copy(val);
                memo.push(val);
                nextLevel = val.nested;
                if (nextLevel) {
                  angular.forEach(nextLevel, function(nestedMenu, nestedLabel) {
                    if (nestedMenu.nestedDependency) {
                      var nextNestedLevel = [];
                      findDefiningAttributes(nestedMenu.nested, grammar, nextNestedLevel);
                      nestedMenu.nested = { nested: nextNestedLevel.pop() };
                    }
                  });
                  findDefiningAttributes(nextLevel, grammar, target);
                }
              }
            });
          }
        });
      }
    }

    function dependencyMet(morphValue, depValue) {
      if (angular.isDefined(morphValue)) {
        return morphValue === depValue || depValue === "*";
      }
    }

    // We already captured the defining attributes at this point - they
    // are all stored as full objects with their full nested structure.
    // The menu we want to present to the user is therefore the last one
    // in this array structure.
    function extractMenu(grammar) {
      var attrs = grammar.definingAttrs;
      // Could be that this array is empty!
      var lastAttr = attrs[attrs.length - 1] || {};
      grammar.menu = lastAttr.nested;
    }

    function propagateToState() {
      angular.forEach(self.grammar, function(val, id) {
        state.tokens[id].sg = val;
      });
    }

    this.requestGrammar = function(sections, callback) {
      retriever.getData(sections, callback);
    };

    this.canEdit = function() {
      return self.mode === "editor";
    };

    state.on('tokenAdded', function(event, token) {
      self.grammar[token.id] = createNewSgObject(token);
    });

    state.on('tokenRemoved', function(event, token) {
      delete self.grammar[token.id];
    });

    this.init = function() {
      plugins.doAfter('morph', function() {
        configure();
        self.grammar = createInternalState();
        self.readerRequested = false;
        propagateToState();
      });
    };
  }
]);
<commit_msg>Even better error message in sg<commit_after>"use strict";

angular.module('arethusa.sg').service('sg', [
  'state',
  'configurator',
  '$cacheFactory',
  'plugins',
  'notifier',
  function(state, configurator, $cacheFactory, plugins, notifier) {
    var self = this;
    this.name = 'sg';

    var retriever;
    this.labelAs = "long";
    this.defineAncestors = true;

    var sgCache = $cacheFactory('sg', { number:  100 });

    this.defaultConf = {
      displayName: "SG"
    };

    function configure() {
      configurator.getConfAndDelegate(self, ['labels']);
      retriever = configurator.getRetriever(self.conf.retriever);
    }

    function SgTemplate() {
      var self = this;

      this.morph = {};
      this.ancestors = [];
      this.definingAttrs = [];
      this.menu = {};
      this.hasChanged = true;
      this.isSgTemplate = true;
      this.markChange = function() {
        self.hasChanged = true;
      };
    }

    function grammarReset(grammar) {
      arethusaUtil.empty(grammar.ancestors);
      // We have to redefine this property - it's untouchable now
      // that it's cached!
      grammar.definingAttrs = [];
    }

    function sgFromStateComplete(sg) {
      return sg && sg.isSgTemplate;
    }

    function sgFromRetriever(sg) {
      return sg && sg.ancestors && !sg.isTemplate;
    }

    function createNewSgObject(token) {
      var grammar = new SgTemplate();
      var morph = token.morphology || {};
      grammar.string = token.string;
      checkAndUpdateGrammar(morph, grammar);
      return grammar;
    }

    function createInternalState() {
      // 3 possibilites here:
      //
      // 1 When we have seen this token set before, we don't need to do anything
      //   and can take the info we already created earlier in time.
      // 2 We have not seen the tokens, but they have info from the retrieved
      //   document. In such a case we build a new SgTemplate and add the sg
      //   ancestors from the token to it.
      // 3 We have to start from scratch.
      return arethusaUtil.inject({}, state.tokens, function(memo, id, token) {
        var grammar;
        var fromState = token.sg;
        if (sgFromStateComplete(fromState)) {
          grammar = fromState;
        } else {
          grammar = createNewSgObject(token);
          if (sgFromRetriever(fromState)) {
            addAncestorsFromState(fromState, grammar);
          }
        }
        memo[id] = grammar;
      });
    }

    function addAncestorsFromState(sg, grammar) {
      var ancestors = sg.ancestors;
      var menu = grammar.menu;
      angular.forEach(ancestors, function(ancestor, i) {
        if (menu) {
          // The || menu.nested party is very hacky, but effective.
          // It can appear in menus that show a morph preselection -
          // I guess... Check a word with a dative proper e.g.
          var expandedAncestor = menu[ancestor] || menu.nested;
          if (!expandedAncestor) {
            notifier.error("Failed to parse SG annotation (" + ancestors + " at " + ancestor + ")");
            notifier.warning("You might be using an incompatible version of the SG tagset");
            menu = undefined;
            return;
          }
          grammar.ancestors.push(expandedAncestor);
          menu = expandedAncestor.nested;
        }
      });
    }

    var hint = "Please select a morphological form first!";

    this.currentGrammar = function() {
      return arethusaUtil.inject({}, state.selectedTokens, function(memo, id, event) {
        var morph = state.tokens[id].morphology;
        var grammar = self.grammar[id];
        if (grammar) {
          if (morph && morph.attributes) {
            delete grammar.hint;
            checkAndUpdateGrammar(morph, grammar);
          } else {
            grammarReset(grammar);
            grammar.morph = {};
            grammar.hint = hint;
          }
          memo[id] = grammar;
        }
      });
    };

    function morphHasChanged(a, b) {
      return !angular.equals(a, b);
    }

    function checkAndUpdateGrammar(morph, grammar) {
      if (morphHasChanged(grammar.morph, morph.attributes)) {
        // Need to empty first, otherwise we might mix attributes
        // of various part of speech types together!
        arethusaUtil.empty(grammar.morph);
        angular.extend(grammar.morph, morph.attributes);
        updateGrammar(self.labels, grammar);
      }
      return grammar;
    }

    function updateGrammar(labels, grammar) {
      grammarReset(grammar);
      findDefiningAttributes(self.labels, grammar, grammar.definingAttrs);
      extractMenu(grammar);
      cacheUpdateProcess(grammar);
      grammar.markChange();
    }

    function cacheUpdateProcess(grammar) {
      var key = cacheKey(grammar);
      if (!sgCache.get(key)) {
        sgCache.put(key, grammar.definingAttrs);
      }
    }

    function cacheKey(grammar) {
      return arethusaUtil.inject([], grammar.morph, function(memo, k, v) {
        memo.push(k + '-' + v);
      }).sort().join('|');
    }

    function findDefiningAttributes(labels, grammar, target) {
      var cached = sgCache.get(cacheKey(grammar));
      if (cached) {
        arethusaUtil.pushAll(target, cached);
      } else {
        arethusaUtil.inject(target, labels, function(memo, label, val) {
          var dep = val.dependency;
          if (dep) {
            var morph = grammar.morph;
            var nextLevel;
            angular.forEach(dep, function(depVal, depCat) {
              if (dependencyMet(morph[depCat], depVal)) {
                val = angular.copy(val);
                memo.push(val);
                nextLevel = val.nested;
                if (nextLevel) {
                  angular.forEach(nextLevel, function(nestedMenu, nestedLabel) {
                    if (nestedMenu.nestedDependency) {
                      var nextNestedLevel = [];
                      findDefiningAttributes(nestedMenu.nested, grammar, nextNestedLevel);
                      nestedMenu.nested = { nested: nextNestedLevel.pop() };
                    }
                  });
                  findDefiningAttributes(nextLevel, grammar, target);
                }
              }
            });
          }
        });
      }
    }

    function dependencyMet(morphValue, depValue) {
      if (angular.isDefined(morphValue)) {
        return morphValue === depValue || depValue === "*";
      }
    }

    // We already captured the defining attributes at this point - they
    // are all stored as full objects with their full nested structure.
    // The menu we want to present to the user is therefore the last one
    // in this array structure.
    function extractMenu(grammar) {
      var attrs = grammar.definingAttrs;
      // Could be that this array is empty!
      var lastAttr = attrs[attrs.length - 1] || {};
      grammar.menu = lastAttr.nested;
    }

    function propagateToState() {
      angular.forEach(self.grammar, function(val, id) {
        state.tokens[id].sg = val;
      });
    }

    this.requestGrammar = function(sections, callback) {
      retriever.getData(sections, callback);
    };

    this.canEdit = function() {
      return self.mode === "editor";
    };

    state.on('tokenAdded', function(event, token) {
      self.grammar[token.id] = createNewSgObject(token);
    });

    state.on('tokenRemoved', function(event, token) {
      delete self.grammar[token.id];
    });

    this.init = function() {
      plugins.doAfter('morph', function() {
        configure();
        self.grammar = createInternalState();
        self.readerRequested = false;
        propagateToState();
      });
    };
  }
]);
<|endoftext|>


<commit_before>/* Fibonacci Heap
 * Copyright 2015 330k
 * https://github.com/330k/
 *
 * This software is released under the MIT License.
 * http://opensource.org/licenses/mit-license.php
 */
function fibonacci_heap(){
	var _top = null;
	var _size = 0;
	var _mergeList = function(i, j){
		if(i === null) return j;
		if(j === null) return i;

		var iNext = i.next;
		i.next = j.next;
		i.next.prev = i;
		j.next = iNext;
		j.next.prev = j;

		return i.p > j.p ? i : j;
	};

	var enqueue = function(priority, value){
		var newnode = {
			p: priority,
			v: value,
			marked: false,
			rank: 0,
			next: null,
			prev: null,
			firstchild: null
		};
		newnode.next = newnode;
		newnode.prev = newnode;

		_top = _mergeList(_top, newnode);
		_size++;
	};
	var dequeue = function(){
		var result = _top.v;
		_size--;

		if(_top.next === _top){
			_top = null;
		}else{
			_top.prev.next = _top.next;
			_top.next.prev = _top.prev;
			_top = _top.next;
		}

		_top = _mergeList(_top, result.firstchild);

		if(_top === null){
			return result;
		}

		var ranks = [];
		var roots = [];

		var curr = _top;
		do{
			roots.push(curr);
			curr = curr.next;
		} while(curr !== _top);

		for(var i = 0; i < roots.length; i++){
			curr = roots[i];
			while(true){
				if(ranks[curr.rank] === undefined){
					ranks[curr.rank] = curr;
					break;
				}
				var other = ranks[curr.rank];
				ranks[curr.rank] = undefined;

				var min, max;
				if(curr.p < other.p){
					min = curr;
					max = other;
				}else{
					min = other;
					max = curr;
				}

				min.next.prev = min.prev;
				min.prev.next = min.next;

				min.next = min.prev = min;
				max.firstchild = _mergeList(max.firstchild, min);

				min.marked = false;
				max.rank++;

				curr = max;
			}
			if(curr.p > _top.p){
				_top = curr;
			}
		}

		return result;
	};
	var top = function(){
		return _top.v;
	};
	var size = function(){
		return _size;
	};

	return {
		name: 'Fibonacci Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
<commit_msg>midofy dequeue<commit_after>/* Fibonacci Heap
 * Copyright 2015 330k
 * https://github.com/330k/
 *
 * This software is released under the MIT License.
 * http://opensource.org/licenses/mit-license.php
 */
function fibonacci_heap(){
	var _top = null;
	var _size = 0;
	var _mergeList = function(i, j){
		if(i === null) return j;
		if(j === null) return i;

		var iNext = i.next;
		i.next = j.next;
		i.next.prev = i;
		j.next = iNext;
		j.next.prev = j;

		return i.p > j.p ? i : j;
	};

	var enqueue = function(priority, value){
		var newnode = {
			p: priority,
			v: value,
			marked: false,
			rank: 0,
			next: null,
			prev: null,
			firstchild: null
		};
		newnode.next = newnode;
		newnode.prev = newnode;

		_top = _mergeList(_top, newnode);
		_size++;
	};
	var dequeue = function(){
		var result = _top;
		_size--;

		if(_top.next === _top){
			_top = null;
		}else{
			_top.prev.next = _top.next;
			_top.next.prev = _top.prev;
			_top = _top.next;
		}

		_top = _mergeList(_top, result.firstchild);

		if(_top === null){
			return result.v;
		}

		var ranks = [];
		var roots = [];

		var curr = _top;
		do{
			roots.push(curr);
			curr = curr.next;
		} while(curr !== _top);

		for(var i = 0; i < roots.length; i++){
			curr = roots[i];
			while(true){
				if(ranks[curr.rank] === undefined){
					ranks[curr.rank] = curr;
					break;
				}
				var other = ranks[curr.rank];
				ranks[curr.rank] = undefined;

				var min, max;
				if(curr.p < other.p){
					min = curr;
					max = other;
				}else{
					min = other;
					max = curr;
				}

				min.next.prev = min.prev;
				min.prev.next = min.next;

				min.next = min.prev = min;
				max.firstchild = _mergeList(max.firstchild, min);

				min.marked = false;
				max.rank++;

				curr = max;
			}
			if(curr.p > _top.p){
				_top = curr;
			}
		}

		return result.v;
	};
	var top = function(){
		return _top.v;
	};
	var size = function(){
		return _size;
	};

	return {
		name: 'Fibonacci Heap',
		enqueue: enqueue,
		dequeue: dequeue,
		top: top,
		size: size
	};
}
<|endoftext|>


<commit_before>define(['knockout'], function(ko) {
    'use strict';

    /**
     * Símbolo utilizado para representar a sentença vazia.
     * @const
     */
    var EPSILON = String.fromCharCode(0x3B5);

    /**
     * Símbolo para separar o lado esquerdo do lado direito no formalismo.
     * @const
     */
    var ARROW = String.fromCharCode(0x279C);

    /**
     * Representação de uma única regra de produção dentro de uma gramática.
     *
     * @class
     */
    function ProductionRule() {
        this.init.apply(this, arguments);
    }

    ProductionRule.prototype = {

        /**
         * @constructs
         *
         * @param {Grammar} grammar Gramática à qual a regra de produção pertence.
         */
        init: function(grammar) {
            this.grammar = grammar;

            this.leftSide  = ko.observable('');
            this.rightSide = ko.observableArray([]);
        },

        /**
         * Adiciona a sentenca vazia na lista de possíveis produções da regra.
         */
        addEmptySentence: function() {
            this.rightSide.push(EPSILON);
        },

        /**
         * Gera a representação do formalismo desta regra.
         */
        toFormalismString: function() {
            var l = this.leftSide(),
                r = this.rightSide().join(' | ');

            if (l && r) {
                return l + ' ' + ARROW + ' ' + r;
            }

            return '';
        },

        /**
         * Verifica se a definição da regra de produção está completa (todas as informações inseridas).
         *
         * @return boolean Se a regra de produção está completamente definida.
         */
        isCompleted: function() {
            return ((this.leftSide().length > 0) && (this.rightSide().length > 0));
        },

        // TODO: doc
        isUnrestricted: function() {
            // TODO
        },

        // TODO: doc
        isContextSensitive: function() {
            // TODO
        },

        // TODO: doc
        isContextFree: function() {
            // TODO
        },

        // TODO: doc
        isRegular: function() {
            // Lado esquerdo deve ser um e apenas um não terminal
            if (this.grammar.nonTerminalSymbols().indexOf(this.leftSide()) === -1) {
                return false;
            }

            // Lado direito pode ser um terminal ou um terminal seguido de um não terminal
            var right = this.rightSide();
            for (var i = 0, l = right.length; i < l; ++i) {
                if (this.grammar.terminalSymbols().indexOf(right[i]) === -1) {
                    // TODO: verificar se pode ser um terminal seguido de não terminal
                    return false;
                }
            }

            return true;
        }

    };

    return ProductionRule;
});
<commit_msg>Verificação para regras do tipo regular<commit_after>define(['knockout'], function(ko) {
    'use strict';

    /**
     * Símbolo utilizado para representar a sentença vazia.
     * @const
     */
    var EPSILON = String.fromCharCode(0x3B5);

    /**
     * Símbolo para separar o lado esquerdo do lado direito no formalismo.
     * @const
     */
    var ARROW = String.fromCharCode(0x279C);

    /**
     * Representação de uma única regra de produção dentro de uma gramática.
     *
     * @class
     */
    function ProductionRule() {
        this.init.apply(this, arguments);
    }

    ProductionRule.prototype = {

        /**
         * @constructs
         *
         * @param {Grammar} grammar Gramática à qual a regra de produção pertence.
         */
        init: function(grammar) {
            this.grammar = grammar;

            this.leftSide  = ko.observable('');
            this.rightSide = ko.observableArray([]);
        },

        /**
         * Adiciona a sentenca vazia na lista de possíveis produções da regra.
         */
        addEmptySentence: function() {
            this.rightSide.push(EPSILON);
        },

        /**
         * Gera a representação do formalismo desta regra.
         */
        toFormalismString: function() {
            var l = this.leftSide(),
                r = this.rightSide().join(' | ');

            if (l && r) {
                return l + ' ' + ARROW + ' ' + r;
            }

            return '';
        },

        /**
         * Verifica se a definição da regra de produção está completa (todas as informações inseridas).
         *
         * @return boolean Se a regra de produção está completamente definida.
         */
        isCompleted: function() {
            return ((this.leftSide().length > 0) && (this.rightSide().length > 0));
        },

        // TODO: doc
        isUnrestricted: function() {
            // TODO
        },

        // TODO: doc
        isContextSensitive: function() {
            // TODO
        },

        // TODO: doc
        isContextFree: function() {
            // TODO
        },

        /**
         * Verifica se a regra de produção obedece todas as restrições necessárias para pertencer a uma gramática do
         * tipo regular. Para isso, a regra deve obedecer a duas restrições:
         *
         *     * O lado esquerdo deve ser um e apenas um símbolo não terminal.
         *     * O lado direito pode ser apenas:
         *         * Um símbolo terminal
         *         * Um símbolo terminal seguido de um símbolo não terminal
         *         * Sentença vazia
         *
         * @returns {Boolean} Se a regra obedece a todas as restrições do tipo regular.
         */
        isRegular: function() {
            var right = this.rightSide();
            var nt    = this.grammar.nonTerminalSymbols();
            var t     = this.grammar.terminalSymbols();

            // Conjunto de todas as combinações de terminal + não terminal
            var tnt = [];
            for (var i = 0, lt = t.length; i < lt; ++i) {
                for (var j = 0, lnt = nt.length; j < lnt; ++j) {
                    tnt.push(t[i] + nt[j]);
                }
            }

            // Lado esquerdo deve ser um e apenas um não terminal
            if (nt.indexOf(this.leftSide()) === -1) {
                return false;
            }

            // Lado direito pode ser um terminal ou um terminal seguido de um não terminal
            for (var i = 0, l = right.length; i < l; ++i) {
                if (t.indexOf(right[i]) === -1 && tnt.indexOf(right[i]) === -1) {
                    return false;
                }
            }

            return true;
        }

    };

    return ProductionRule;
});
<|endoftext|>


<commit_before>const ytdl = require('ytdl-core');
const Discord = require('discord.js');
var servers = {};
//const streamOptions = { seek: 0, volume: 1 };

//
/*if(!servers[message.guild.id]) servers[message.guild.id] ={ //makes sure that there is a queue value for that server
    queue: []
  }
  var server = servers[message.guild.id]
  if(args[0].startsWith("http")){ //checks if its a link or not
    message.reply("Adding "+args[0]);
    server.queue.push(args[0]);
  }*/
 /* if(!message.guild.voiceConnection) message.member.voiceChannel.join().then(function(connection){ //joins the vc
    play(connection, message);
  })*/
  //

  function play(connection, message){
    var server = servers[message.guild.id];
    server.dispatcher = connection.playStream(ytdl(server.queue[0], {filter: "audioonly"}));
    server.queue.shift();
    server.dispatcher.on("end", function(){
      if(server.queue[0]) play(connection, message);
      else connection.disconnect();
      message.channel.send("O som acabou... Por favor informe outro link para continuar escutando música.")
    });
  }

//id canal musica = 375842517566095360
module.exports = (client, message, args, connection) => {

    if(!args[0]) return message.channel.send("Por favor, informe um **link** correto."); //Makes sure that theres a name/link
    if(!message.member.voiceChannel) return message.reply("Entre em um **canal de áudio** primeiro para usar o comando."); //Makes sure it can join a voice chat with that person
    if(!servers[message.guild.id]) servers[message.guild.id] ={ //makes sure that there is a queue value for that server
      queue: []
    }
    var server = servers[message.guild.id]
    if(args[0].startsWith("http")){ //checks if its a link or not
      message.reply("Adicionando a fila "+args[0]);
      server.queue.push(`${args.join(' ')}`);
    }

    //let channel = client.channels.get('375842517566095360');
    /*if ((channel) && (args[1]))
    {
      channel.join()
      .then(connection =>
      { */
        /*message.reply('A música no **Youtube** escolhida foi iniciada com sucesso! :musical_note:').then(msg => {
          msg.delete(60000) });
          //const stream = ytdl(`${args.join(' ')}`, { filter: 'audioonly' });
          const stream = ytdl(ytAudioQueue.first, { filter: 'audioonly' });
          const dispatcher = connection.playStream(stream);  */

       /*   play(connection, message);

      })
    .catch(console.log);
    }*/
    if(!message.guild.voiceConnection) message.member.voiceChannel.join().then(function(connection){ //joins the vc
        play(connection, message);
      })
    message.delete(60000);
};<commit_msg><commit_after>const ytdl = require('ytdl-core');
const Discord = require('discord.js');
var servers = {};
//const streamOptions = { seek: 0, volume: 1 };

//
/*if(!servers[message.guild.id]) servers[message.guild.id] ={ //makes sure that there is a queue value for that server
    queue: []
  }
  var server = servers[message.guild.id]
  if(args[0].startsWith("http")){ //checks if its a link or not
    message.reply("Adding "+args[0]);
    server.queue.push(args[0]);
  }*/
 /* if(!message.guild.voiceConnection) message.member.voiceChannel.join().then(function(connection){ //joins the vc
    play(connection, message);
  })*/
  //

  function play(connection, message){
    var server = servers[message.guild.id];
    server.dispatcher = connection.playStream(ytdl(server.queue[0], {filter: "audioonly"}));
    server.queue.shift();
    server.dispatcher.on("end", function(){
      if(server.queue[0]) play(connection, message);
      else connection.disconnect();
      message.channel.send("A música anterior acabou... Por favor informe outro link para continuar escutando música.").then(msg => {
        msg.delete(60000) });
    });
  }

//id canal musica = 375842517566095360
module.exports = (client, message, args, connection) => {

    if(!args[0]) return message.channel.send("Por favor, informe um **link** correto.").then(msg => {
        msg.delete(60000) });; //Makes sure that theres a name/link
    if(!message.member.voiceChannel) return message.reply("Entre em um **canal de áudio** para usar o comando.").then(msg => {
        msg.delete(60000) });; //Makes sure it can join a voice chat with that person
    if(!servers[message.guild.id]) servers[message.guild.id] ={ //makes sure that there is a queue value for that server
      queue: []
    }
    var server = servers[message.guild.id]
    if(args[0].startsWith("http")){ //checks if its a link or not
      message.reply("Adicionou uma nova música para tocar: "+args[0]).then(msg => {
        msg.delete(60000) });;
      server.queue.push(`${args.join(' ')}`);
    }

    //let channel = client.channels.get('375842517566095360');
    /*if ((channel) && (args[1]))
    {
      channel.join()
      .then(connection =>
      { */
        /*message.reply('A música no **Youtube** escolhida foi iniciada com sucesso! :musical_note:').then(msg => {
          msg.delete(60000) });
          //const stream = ytdl(`${args.join(' ')}`, { filter: 'audioonly' });
          const stream = ytdl(ytAudioQueue.first, { filter: 'audioonly' });
          const dispatcher = connection.playStream(stream);  */

       /*   play(connection, message);

      })
    .catch(console.log);
    }*/

    //if(!message.guild.voiceConnection) message.member.voiceChannel.join().then(function(connection){ //joins the vc
    let channel = client.channels.get('375842517566095360');
    if (channel)
    { channel.join()
      .then(connection =>
      {
        play(connection, message);
      })
    }
    message.delete(60000);
};<|endoftext|>


<commit_before>import Icon from '../Icon';
import React from 'react';
import classNames from 'classnames';
import styles from './styles.css';

function ActionButton(props) {
    return (
        <div
            className={classNames(styles.wrapper, {
                [styles[props.size]]: props.size,
                [props.className]: props.className
            })}
            onClick={props.onClick}
        >
            <Icon
                className={classNames(styles.icon, {
                    [props.className]: props.className
                })}
                icon={props.icon}
                size="standard"
            />
        </div>
    );
}

ActionButton.propTypes = {
    className: React.PropTypes.string,
    icon: React.PropTypes.string,
    onClick: React.PropTypes.func,
    size: React.PropTypes.oneOf(['standard', 'main'])
};

export default ActionButton;
<commit_msg>Remove Classname<commit_after>import Icon from '../Icon';
import React from 'react';
import classNames from 'classnames';
import styles from './styles.css';

function ActionButton(props) {
    return (
        <div
            className={classNames(styles.wrapper, {
                [styles[props.size]]: props.size,
                [props.className]: props.className
            })}
            onClick={props.onClick}
        >
            <Icon
                className={styles.icon}
                icon={props.icon}
                size="standard"
            />
        </div>
    );
}

ActionButton.propTypes = {
    className: React.PropTypes.string,
    icon: React.PropTypes.string,
    onClick: React.PropTypes.func,
    size: React.PropTypes.oneOf(['standard', 'main'])
};

export default ActionButton;
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012 Expression organization is undefined on line 4, column 61 in Templates/Licenses/license-gpl30.txt.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * LayerSelector
 * A generic component to retrieve the layers
 * @author Meine Toonen
 */
Ext.define ("viewer.components.LayerSelector",{
    extend: "viewer.components.Component",
    popupWin:null,
    layerList : null,
    layerArray : null,
    combobox : null,
    div: null,
    config: {
        viewerController: new Object(),
        restriction : null,
        layers:null
    },
    constructor: function (conf){
        this.initConfig(conf);
        var layers = Ext.create('Ext.data.Store', {
            fields: ['id', 'title','layer'],
            data : []
        });

        this.combobox = Ext.create('Ext.form.ComboBox', {
            fieldLabel: 'Kies kaartlaag',
            store: layers,
            queryMode: 'local',
            displayField: 'title',
            valueField: 'layer',
            listeners :{
                change:{
                    fn: this.changed,
                    scope: this
                }
            },
            renderTo: this.div
        });
        this.addEvents(viewer.viewercontroller.controller.Event.ON_LAYERSELECTOR_CHANGE);
        var requestPath= actionBeans["layerlist"];
        var requestParams = {};
        // TODO make layerselector so, that the layerselector can use a filtered list of layers
        requestParams[this.restriction]= true;
        requestParams["appId"]= appId;
        if(this.layers != null){
            requestParams["layers"]= this.layers;
            requestParams["hasConfiguredLayers"]= true;
        }

        var me = this;
        Ext.Ajax.request({
            url: requestPath,
            params: requestParams,
            success: function ( result, request ) {
                me.layerList = Ext.JSON.decode(result.responseText);
                me.initLayers();
            },
            failure: function(a,b,c) {
                Ext.MessageBox.alert("Foutmelding", "Er is een onbekende fout opgetreden waardoor de lijst met kaartlagen niet kan worden weergegeven");
            }
        });

        return this;
    },
    initLayers : function (){
        this.layerArray = new Array();
        var store = this.combobox.getStore();
        for (var i = 0 ; i < this.layerList.length ;i++){
            var l = this.layerList[i];
            l.title = l.titleAlias || l.title ;
            l.layer = l;
            store.add(l);
        }

    },
    changed :function (combobox,item,previousSelected){
        this.fireEvent(viewer.viewercontroller.controller.Event.ON_LAYERSELECTOR_CHANGE,item,previousSelected,this);
    },
    getValue : function (){
        return this.combobox.getValue();
    },
    getSelectedAppLayer : function (){
        var layerObj = this.getValue();
        var layer = this.viewerController.getApplayer(layerObj.serviceId, layerObj.name);
        return layer;
    },
    getExtComponents: function() {
        return [ this.combobox.getId() ];
    }
});<commit_msg>LayerSelector shows only visible layers<commit_after>/*
 * Copyright (C) 2012 Expression organization is undefined on line 4, column 61 in Templates/Licenses/license-gpl30.txt.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 * LayerSelector
 * A generic component to retrieve the layers
 * @author Meine Toonen
 */
Ext.define ("viewer.components.LayerSelector",{
    extend: "viewer.components.Component",
    popupWin:null,
    layerList : null,
    layerArray : null,
    combobox : null,
    div: null,
    config: {
        viewerController: new Object(),
        restriction : null,
        layers:null
    },
    constructor: function (conf){
        this.initConfig(conf);
        var layers = Ext.create('Ext.data.Store', {
            fields: ['id', 'title','layer'],
            data : []
        });

        this.combobox = Ext.create('Ext.form.ComboBox', {
            fieldLabel: 'Kies kaartlaag',
            store: layers,
            queryMode: 'local',
            displayField: 'title',
            valueField: 'layer',
            listeners :{
                change:{
                    fn: this.changed,
                    scope: this
                }
            },
            renderTo: this.div
        });
        this.addEvents(viewer.viewercontroller.controller.Event.ON_LAYERSELECTOR_CHANGE);
        var requestPath= actionBeans["layerlist"];
        var requestParams = {};
        // TODO make layerselector so, that the layerselector can use a filtered list of layers
        requestParams[this.restriction]= true;
        requestParams["appId"]= appId;
        if(this.layers != null){
            requestParams["layers"]= this.layers;
            requestParams["hasConfiguredLayers"]= true;
        }

        var me = this;
        Ext.Ajax.request({
            url: requestPath,
            params: requestParams,
            success: function ( result, request ) {
                me.layerList = Ext.JSON.decode(result.responseText);
                me.initLayers();
            },
            failure: function(a,b,c) {
                Ext.MessageBox.alert("Foutmelding", "Er is een onbekende fout opgetreden waardoor de lijst met kaartlagen niet kan worden weergegeven");
            }
        });
        this.viewerController.mapComponent.getMap().registerEvent(viewer.viewercontroller.controller.Event.ON_LAYER_VISIBILITY_CHANGED, this.layerVisibilityChanged, this);
        return this;
    },
    initLayers : function (){
        this.layerArray = new Array();
        var visibleLayers = this.viewerController.getVisibleLayerIds();
        var store = this.combobox.getStore();
        store.removeAll();
        if(this.layerList != null){
            for (var i = 0 ; i < this.layerList.length ;i++){
                var l = this.layerList[i];
                for ( var j = 0 ; j < visibleLayers.length ;j++){

                    var index = visibleLayers[j].indexOf("_");
                    var service = visibleLayers[j].substring(0,index);
                    var layername = visibleLayers[j].substring(index+1);

                    if (service == l.serviceId && layername == l.name){
                        l.title = l.titleAlias || l.title ;
                        l.layer = l;
                        store.add(l);
                    }
                }
            }
        }
    },
    changed :function (combobox,item,previousSelected){
        this.fireEvent(viewer.viewercontroller.controller.Event.ON_LAYERSELECTOR_CHANGE,item,previousSelected,this);
    },
    getValue : function (){
        return this.combobox.getValue();
    },
    getSelectedAppLayer : function (){
        var layerObj = this.getValue();
        var layer = this.viewerController.getApplayer(layerObj.serviceId, layerObj.name);
        return layer;
    },
    getExtComponents: function() {
        return [ this.combobox.getId() ];
    },

    layerVisibilityChanged : function (map,object){
        this.initLayers();
    }
});<|endoftext|>