<commit_before>function bulkOperationsController($scope, context, $stateParams, $state, $i18next, $uibModalInstance, $timeout, activityType, entityName,
                                  MultipleSelectService, UsersService, SettingServices, PermissionsService, NotifyingService) {

    $scope.selectedItems = MultipleSelectService.getSelected();
    $scope.activityType = activityType;
    $scope.entityName = entityName;
    UsersService.getAll().then(allUsers => $scope.people = allUsers);

    $scope.statusMap = SettingServices.getStatusList();
    $scope.statuses = $scope.statusMap[$scope.entityName.substring(0, $scope.entityName.length - 1)];

    $scope.select = function (selected) {
        $scope.selected = selected;
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };

    $scope.bulkUpdate = function (type, value) {
        if(!value)return;

        let idsArray = $scope.selectedItems.map(entity => entity._id);
        let changedBulkObject = {
            update: {},
            ids: idsArray
        };
        changedBulkObject.update[type] = value;

        MultipleSelectService.bulkUpdate(changedBulkObject, $scope.entityName)
            .then(result => {
                for(let i = 0; i < $scope.selectedItems.length; i++){
                    let entity = result.find(entity => entity._id === $scope.selectedItems[i]._id);
                    if(typeof entity.due === 'string')entity.due = new Date(entity.due);
                    entity = _.pick(entity, ['status', 'assign', 'due', 'tags', 'recycled']);
                    Object.assign($scope.selectedItems[i], entity);
                }
                if(changedBulkObject.update.delete){
                    refreshState();
                }
                MultipleSelectService.setSelectedList($scope.selectedItems);
                NotifyingService.notify('refreshAfterOperation');
                $uibModalInstance.dismiss('cancel');
            });
    };

    function refreshState(){
        let currentState = $state.current.name;
        $state.go(currentState, {
            entity: 'all',
        }, {
            reload: true
        });
    }

    //------------------------------------------------//
    //----------------------DUE----------------------//

    $scope.selectedDue = {};
    $scope.setDueDate = 'setDueDate';
    $scope.dueDateErrorMessage = 'couldNotSetPreviousTime';
    $scope.duePlaceholder = $scope.setDueDate;

    $scope.dueOptions = {
        dateFormat: 'dd.mm.yy',
        onSelect: function() {
            $scope.dateCheck();
        }
    };

    $scope.dateCheck = function(){
        let nowTime = new Date();
        if($scope.selectedDue.date < nowTime){
            $scope.duePlaceholder = $scope.dueDateErrorMessage;
            $scope.selectedDue.date = '';
        } else {
            $scope.bulkUpdate('due', $scope.selectedDue.date);
        }
    };

  //------------------------------------------------//
  //----------------------TAGS----------------------//

  $scope.tags = [];
  $scope.addTagClicked = function () {
      $scope.tagInputVisible = true;
      $timeout(function () {
          let element = angular.element('#addTag .ui-select-toggle')[0];
          element.click();
      }, 0);
  };

    $scope.addTag = function (tag) {
      if (!$scope.selected) $scope.selected = [];

      if (!$scope.selected.find(selectedTag => selectedTag === tag)) {
          $scope.selected.push(tag);
      }
      $scope.tagInputVisible = false;
  };

  $scope.removeTag = function (tag) {
      $scope.selected = _($scope.selected).without(tag);
  };

  $scope.onOpenClose = function (isOpen) {
      $scope.tagInputVisible = !isOpen;
  };

  switch (activityType) {
      case 'status':
          $scope.title = `${$i18next('setStatus')}`;
          break;
      case 'watch':
          $scope.title = `${$i18next('setWatchers')}`;
          break;
      case 'assign':
          $scope.title = `${$i18next('assign')}`;
          break;
      case 'due':
          $scope.title = `${$i18next('setDueDate')}`;
          break;
      case 'tag':
          $scope.title = `${$i18next('addTags')}`;
          break;
      case 'delete':
          $scope.title = `${$i18next('Delete')}`;
          break;
  }
}
<commit_msg>fixed bulk updating due without clicking update button https://github.com/linnovate/root/issues/1200<commit_after>function bulkOperationsController($scope, context, $stateParams, $state, $i18next, $uibModalInstance, $timeout, activityType, entityName,
                                  MultipleSelectService, UsersService, SettingServices, PermissionsService, NotifyingService) {

    $scope.selectedItems = MultipleSelectService.getSelected();
    $scope.activityType = activityType;
    $scope.entityName = entityName;
    UsersService.getAll().then(allUsers => $scope.people = allUsers);

    $scope.statusMap = SettingServices.getStatusList();
    $scope.statuses = $scope.statusMap[$scope.entityName.substring(0, $scope.entityName.length - 1)];

    $scope.select = function (selected) {
        $scope.selected = selected;
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };

    $scope.bulkUpdate = function (type, value) {
        if(!value)return;

        let idsArray = $scope.selectedItems.map(entity => entity._id);
        let changedBulkObject = {
            update: {},
            ids: idsArray
        };
        changedBulkObject.update[type] = value;

        MultipleSelectService.bulkUpdate(changedBulkObject, $scope.entityName)
            .then(result => {
                for(let i = 0; i < $scope.selectedItems.length; i++){
                    let entity = result.find(entity => entity._id === $scope.selectedItems[i]._id);
                    if(typeof entity.due === 'string')entity.due = new Date(entity.due);
                    entity = _.pick(entity, ['status', 'assign', 'due', 'tags', 'recycled']);
                    Object.assign($scope.selectedItems[i], entity);
                }
                if(changedBulkObject.update.delete){
                    refreshState();
                }
                MultipleSelectService.setSelectedList($scope.selectedItems);
                NotifyingService.notify('refreshAfterOperation');
                $uibModalInstance.dismiss('cancel');
            });
    };

    function refreshState(){
        let currentState = $state.current.name;
        $state.go(currentState, {
            entity: 'all',
        }, {
            reload: true
        });
    }

    //------------------------------------------------//
    //----------------------DUE----------------------//

    $scope.selectedDue = {};
    $scope.setDueDate = 'setDueDate';
    $scope.dueDateErrorMessage = 'couldNotSetPreviousTime';
    $scope.duePlaceholder = $scope.setDueDate;

    $scope.dueOptions = {
        dateFormat: 'dd.mm.yy'
    };

    $scope.dateCheck = function(){
        let nowTime = new Date();
        if($scope.selectedDue.date < nowTime){
            $scope.duePlaceholder = $scope.dueDateErrorMessage;
            $scope.selectedDue.date = '';
        } else {
            $scope.bulkUpdate('due', $scope.selectedDue.date);
        }
    };

  //------------------------------------------------//
  //----------------------TAGS----------------------//

  $scope.tags = [];
  $scope.addTagClicked = function () {
      $scope.tagInputVisible = true;
      $timeout(function () {
          let element = angular.element('#addTag .ui-select-toggle')[0];
          element.click();
      }, 0);
  };

    $scope.addTag = function (tag) {
      if (!$scope.selected) $scope.selected = [];

      if (!$scope.selected.find(selectedTag => selectedTag === tag)) {
          $scope.selected.push(tag);
      }
      $scope.tagInputVisible = false;
  };

  $scope.removeTag = function (tag) {
      $scope.selected = _($scope.selected).without(tag);
  };

  $scope.onOpenClose = function (isOpen) {
      $scope.tagInputVisible = !isOpen;
  };

  switch (activityType) {
      case 'status':
          $scope.title = `${$i18next('setStatus')}`;
          break;
      case 'watch':
          $scope.title = `${$i18next('setWatchers')}`;
          break;
      case 'assign':
          $scope.title = `${$i18next('assign')}`;
          break;
      case 'due':
          $scope.title = `${$i18next('setDueDate')}`;
          break;
      case 'tag':
          $scope.title = `${$i18next('addTags')}`;
          break;
      case 'delete':
          $scope.title = `${$i18next('Delete')}`;
          break;
  }
}
<|endoftext|>


<commit_before>function bulkOperationsController($scope, context, $stateParams, $state, $i18next, $uibModalInstance, $timeout, activityType, entityName,
                                  MultipleSelectService, UsersService, SettingServices, PermissionsService, NotifyingService) {

    $scope.selectedItems = MultipleSelectService.getSelected();

    $scope.selectedArrays = MultipleSelectService.getSelectedEntityArrays();
    $scope.selectedTypes = Object.keys($scope.selectedArrays);

    $scope.selected = {};
    $scope.activityType = activityType;
    $scope.entityName = entityName;
    UsersService.getAll().then(allUsers => {
        $scope.people = allUsers;
        $scope.getUsedWatchers();
    });
    UsersService.getMe().then( me => $scope.me = me);

  $scope.statusMap = SettingServices.getStatusList();
    $scope.statuses = $scope.statusMap[$scope.entityName.substring(0, $scope.entityName.length - 1)];

    $scope.select = function (selected) {
        $scope.selected = selected;
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };

    $scope.bulkUpdateEvery = (type, value) => {
        $scope.selectedTypes.forEach( entityName => {
            let entityArray = $scope.selectedArrays[entityName];
            if(entityArray.length)$scope.bulkUpdate(type, value, entityArray, entityName + 's');
        })
    };

    $scope.bulkUpdate = function (type, value, selectedArray = $scope.selectedItems, entityName = $scope.entityName) {
        if(!value)return;

        let idsArray = selectedArray.map(entity => entity._id);
        let changedBulkObject = {
            update: {},
            ids: idsArray
        };
        changedBulkObject.update[type] = value;

        MultipleSelectService.bulkUpdate(changedBulkObject, entityName)
            .then(result => {
                for(let i = 0; i < selectedArray.length; i++){
                    let entity = result.find(entity => entity._id === selectedArray[i]._id);
                    if(!entity)continue;

                    if(typeof entity.due === 'string')entity.due = new Date(entity.due);
                    entity = _.pick(entity, [
                      'status', 'watchers', 'assign',
                      'due', 'startDate', 'endDate', 'startTime', 'endTime', 'allDay',
                      'tags', 'recycled']);
                    Object.assign(selectedArray[i], entity);
                }
                if(changedBulkObject.update.delete){
                    refreshState();
                }
                MultipleSelectService.setSelectedList(selectedArray);
                NotifyingService.notify('refreshAfterOperation');
                $uibModalInstance.dismiss('cancel');
            });
    };

    $scope.updateComplex = function(){
        let arraysOfTypes = $scope.selectedTypes;
        let updateObject = $scope.selectedWatchers.filter( bulkObject => !bulkObject.remove);

        for (let i = 0; i < arraysOfTypes.length; i++){
            let entityArray = $scope.selectedArrays[arraysOfTypes[i]];
            if(!entityArray.length)continue;

            let updateIds = updateObject.map( bulkObject => bulkObject._id);
            let updatePermissions = updateObject.map( bulkObject => {
                return {
                    'id': bulkObject._id,
                    'level': bulkObject.permissions
                }
            });

            let removeObject = $scope.selectedWatchers.filter( bulkObject => bulkObject.remove);
            let removedIds = removeObject.map( bulkObject => bulkObject._id);

            let idsArray = entityArray.map(entity => entity._id);
            let changedBulkObject = {
                ids: idsArray
            };

            if(updateIds.length){
                changedBulkObject.update = {};
                changedBulkObject.update.watchers = updateIds;
                changedBulkObject.update.permissions = updatePermissions;
            }
            if(removeObject.length){
                changedBulkObject.remove = {};
                changedBulkObject.remove.watchers = removedIds;
            }

            MultipleSelectService.bulkUpdate(changedBulkObject, arraysOfTypes[i] + 's')
                .then(result => {
                    for(let i = 0; i < $scope.selectedItems.length; i++){
                        let entity = result.find(entity => entity._id === $scope.selectedItems[i]._id);
                        if(typeof entity.due === 'string')entity.due = new Date(entity.due);
                        entity = _.pick(entity, ['status', 'watchers', 'assign', 'due', 'tags', 'recycled']);
                        Object.assign($scope.selectedItems[i], entity);
                    }
                    if(changedBulkObject.update.delete){
                        refreshState();
                    }
                    MultipleSelectService.setSelectedList($scope.selectedItems);
                    NotifyingService.notify('refreshAfterOperation');
                    $uibModalInstance.dismiss('cancel');
                });
        }
    };

    function refreshState(){
        let currentState = $state.current.name;
        $state.go(currentState, {
            entity: 'all',
        }, {
            reload: true
        });
    }

    //--------------------------------------------------//
    //----------------------watchers----------------------//

    $scope.usedWatchers = [];
    $scope.unusedWatchers = [];
    $scope.selectedWatchers = {};

    $scope.getUsedWatchers = function(){
        let usedIds = getIdsArray($scope.selectedItems[0].watchers);

        for(let selectedItem of $scope.selectedItems){
            usedIds = _.intersection(getIdsArray(selectedItem.watchers), usedIds)
        }

        $scope.usedWatchers = usedIds.map( id => _.find($scope.people, { '_id': id}));
        $scope.getUnusedWatchers();
        $scope.transformToBulkObjects($scope.usedWatchers);
    };

    $scope.getUnusedWatchers = function(){
        let unUsedIds = _.difference(getIdsArray($scope.people), getIdsArray($scope.usedWatchers));
        $scope.unusedWatchers = unUsedIds.map( id =>  _.find($scope.people, { '_id': id}));
    };

    $scope.transformToBulkObjects = function(array){
        $scope.selectedWatchers = array.map( object =>  createBulkWatcher(object._id, $scope.userPermissionStatus(object), false, true));
    };

    function createBulkWatcher(id, perms, remove, primary){
        return {
            '_id': id,
            'permissions': perms,
            'remove': remove,
            'primary': primary
        }
    }

     $scope.getBulkWatchersClass = function(member){
        let bulkObject = $scope.selectedWatchers.find( watcher => watcher._id === member._id);
        return bulkObject.permissions
    };

    function getIdsArray(objArray){
        return objArray.map(obj => obj._id);
    }

    $scope.userPermissionStatus = function(member){
        if(member) return PermissionsService.getUnifiedPerms(member, $scope.selectedItems);
        return 'No Permissions';
    };

    function changePerms(member, newPerms){
        let bulkWatcher = $scope.selectedWatchers.find( watcher => watcher._id === member._id);
        bulkWatcher.permissions = newPerms;
    }

    $scope.setEditor = function(user){return changePerms(user, 'editor')};
    $scope.setCommenter = function(user){return changePerms(user, 'commenter');};
    $scope.setViewer = function(user){return changePerms(user, 'viewer')};

    $scope.addMember = function(member){
        $scope.usedWatchers.push(member);

        let bulkWatcher = createBulkWatcher(member._id,  $scope.userPermissionStatus(member), false, false);
        $scope.selectedWatchers.push(bulkWatcher);
    };

    $scope.removeMember = function(member){
        $scope.usedWatchers = _.reject($scope.usedWatchers,  {'_id': member._id});

        let bulkWatcher = $scope.selectedWatchers.find( watcher => watcher._id === member._id);
        if(bulkWatcher.primary){
            bulkWatcher.remove = true;
        } else {
            $scope.selectedWatchers = _.reject($scope.selectedWatchers,  {'_id': member._id});
        }
    };

    $scope.selfTest = function(member){
        member.selfTest = $scope.me._id === member._id;
        return member;
    };

    $scope.triggerSelect = function() {
        $scope.showSelect = !$scope.showSelect;
        if ($scope.showSelect) {
            $scope.animate = false;
        }
    };

    //------------------------------------------------//
    //----------------------DUE----------------------//

    $scope.selectedDue = {};
    $scope.setDueDate = 'setDueDate';
    $scope.dueDateErrorMessage = 'couldNotSetPreviousTime';
    $scope.duePlaceholder = $scope.setDueDate;
    $scope.enableSetDueDate = false;

    $scope.dueOptions = {
        onSelect: function() {
            $scope.entityDateCheck();
        },
        dateFormat: 'dd.mm.yy'
    };

    $scope.dateCheck = function(){
        if($scope.entityName !== 'discussions'){
          if(!$scope.entityDateCheck()){
            $scope.duePlaceholder = $scope.dueDateErrorMessage;
            $scope.selectedDue.date = '';
            return;
          } else {
            $scope.bulkUpdate('due', $scope.selectedDue.date);
          }
        } else {
          if($scope.setDiscussionDue()){
            $scope.bulkUpdate('due', $scope.selectedDue);
          }
        }
    };

    $scope.setDiscussionDue = function(){
      let selectedDue = $scope.selectedDue;
      $scope.enableSetDueDate = (
        (selectedDue.startDate && selectedDue.endDate && selectedDue.startTime && selectedDue.endTime)
        || (selectedDue.startDate && selectedDue.allDay)
      );
      return $scope.enableSetDueDate;
    };

    $scope.entityDateCheck = function(){
        let nowTime = new Date();
        $scope.enableSetDueDate = !($scope.selectedDue.date < nowTime);

        return $scope.enableSetDueDate;
    };

  //------------------------------------------------//
  //----------------------TAGS----------------------//

  $scope.usedTags = [];
  $scope.usedTagsFiltered;
  $scope.removedTags = [];
  $scope.tags = [];

  $scope.getlastInputText = function(val){
    $scope.lastTagInput = val;
  };

  $scope.addLastInputTextToTag = function(query){
    if(query.length && !$scope.usedTags.find( tagObj => tagObj.tag === query)){
        $scope.addTag(query);
    }
  };

  getUsedTags();
  filterUsedTags();

  function getUsedTags(){
    let usedTags = $scope.selectedItems[0].tags;

    for(let selectedItem of $scope.selectedItems){
      usedTags = _.intersection(selectedItem.tags, usedTags)
    }

    $scope.usedTags = usedTags.map( tag => tagsToBulkObjects(tag, false, true))
  }

  function filterUsedTags(){
    $scope.usedTagsFiltered = $scope.usedTags.filter( tag => !tag.remove);
  }

  function tagsToBulkObjects(tag, remove, primary){
    return {
      'tag': tag,
      'primary': primary,
      'remove': remove
    }
  }

  $scope.addTagClicked = function () {
      $scope.tagInputVisible = true;
      $timeout(function () {
          let element = angular.element('#addTag .ui-select-toggle')[0];
          element.click();
      }, 0);
  };

    $scope.addTag = function (tag) {
      $scope.usedTags.push(tagsToBulkObjects(tag, false, false));

      $scope.tagInputVisible = false;
      filterUsedTags();
  };

  $scope.removeTag = function (tagObj) {
    let bulkTag = $scope.usedTags.find( obj => obj.tag === tagObj.tag);

    if(bulkTag.primary){
      bulkTag.remove = true;
    } else {
      $scope.usedTags = _.reject($scope.usedTags,  {'tag': tagObj.tag});
    }
    filterUsedTags();
  };

  $scope.tagUpdate = function(){
    let updateObject = $scope.usedTags.filter( bulkObject => !bulkObject.remove);
    let updateIds = updateObject.map( bulkObject => bulkObject.tag);

    let removeObject = $scope.usedTags.filter( bulkObject => bulkObject.remove);
    let removedIds = removeObject.map( bulkObject => bulkObject.tag);

    let idsArray = $scope.selectedItems.map(entity => entity._id);
    let changedBulkObject = {
      ids: idsArray
    };

    if(updateIds.length){
      changedBulkObject.update = {};
      changedBulkObject.update.tags = updateIds;
    }
    if(removeObject.length){
      changedBulkObject.remove = {};
      changedBulkObject.remove.tags = removedIds;
    }

    MultipleSelectService.bulkUpdate(changedBulkObject, $scope.entityName)
      .then(result => {
        for(let i = 0; i < $scope.selectedItems.length; i++){
          let entity = result.find(entity => entity._id === $scope.selectedItems[i]._id);
          if(typeof entity.due === 'string')entity.due = new Date(entity.due);
          entity = _.pick(entity, ['status', 'watchers', 'assign', 'due', 'tags', 'recycled']);
          Object.assign($scope.selectedItems[i], entity);
        }
        if(changedBulkObject.update.delete){
          refreshState();
        }
        MultipleSelectService.setSelectedList($scope.selectedItems);
        NotifyingService.notify('refreshAfterOperation');
        $uibModalInstance.dismiss('cancel');
      });
  }

  $scope.onOpenClose = function (isOpen) {
      $scope.tagInputVisible = !isOpen;
  };

  switch (activityType) {
      case 'status':
          $scope.title = `${$i18next('setStatus')}`;
          break;
      case 'watchers':
          $scope.title = `${$i18next('setWatchers')}`;
          break;
      case 'assign':
          $scope.title = `${$i18next('assign')}`;
          break;
      case 'due':
          $scope.title = `${$i18next('setDueDate')}`;
          break;
      case 'tag':
          $scope.title = `${$i18next('addTags')}`;
          break;
      case 'delete':
          $scope.title = `${$i18next('delete')}`;
          break;
  }
}
<commit_msg>fixed bug with not showing user in the list of selected watchers in bulk watchers<commit_after>function bulkOperationsController($scope, context, $stateParams, $state, $i18next, $uibModalInstance, $timeout, activityType, entityName,
                                  MultipleSelectService, UsersService, SettingServices, PermissionsService, NotifyingService) {

    $scope.selectedItems = MultipleSelectService.getSelected();

    $scope.selectedArrays = MultipleSelectService.getSelectedEntityArrays();
    $scope.selectedTypes = Object.keys($scope.selectedArrays);

    $scope.selected = {};
    $scope.activityType = activityType;
    $scope.entityName = entityName;
    UsersService.getAll().then(allUsers => {
        $scope.people = allUsers;
        $scope.getUsedWatchers();
    });
    UsersService.getMe().then( me => $scope.me = me);

  $scope.statusMap = SettingServices.getStatusList();
    $scope.statuses = $scope.statusMap[$scope.entityName.substring(0, $scope.entityName.length - 1)];

    $scope.select = function (selected) {
        $scope.selected = selected;
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };

    $scope.bulkUpdateEvery = (type, value) => {
        $scope.selectedTypes.forEach( entityName => {
            let entityArray = $scope.selectedArrays[entityName];
            if(entityArray.length)$scope.bulkUpdate(type, value, entityArray, entityName + 's');
        })
    };

    $scope.bulkUpdate = function (type, value, selectedArray = $scope.selectedItems, entityName = $scope.entityName) {
        if(!value)return;

        let idsArray = selectedArray.map(entity => entity._id);
        let changedBulkObject = {
            update: {},
            ids: idsArray
        };
        changedBulkObject.update[type] = value;

        MultipleSelectService.bulkUpdate(changedBulkObject, entityName)
            .then(result => {
                for(let i = 0; i < selectedArray.length; i++){
                    let entity = result.find(entity => entity._id === selectedArray[i]._id);
                    if(!entity)continue;

                    if(typeof entity.due === 'string')entity.due = new Date(entity.due);
                    entity = _.pick(entity, [
                      'status', 'watchers', 'assign',
                      'due', 'startDate', 'endDate', 'startTime', 'endTime', 'allDay',
                      'tags', 'recycled']);
                    Object.assign(selectedArray[i], entity);
                }
                if(changedBulkObject.update.delete){
                    refreshState();
                }
                MultipleSelectService.setSelectedList(selectedArray);
                NotifyingService.notify('refreshAfterOperation');
                $uibModalInstance.dismiss('cancel');
            });
    };

    $scope.updateComplex = function(){
        let arraysOfTypes = $scope.selectedTypes;
        let updateObject = $scope.selectedWatchers.filter( bulkObject => !bulkObject.remove);

        for (let i = 0; i < arraysOfTypes.length; i++){
            let entityArray = $scope.selectedArrays[arraysOfTypes[i]];
            if(!entityArray.length)continue;

            let updateIds = updateObject.map( bulkObject => bulkObject._id);
            let updatePermissions = updateObject.map( bulkObject => {
                return {
                    'id': bulkObject._id,
                    'level': bulkObject.permissions
                }
            });

            let removeObject = $scope.selectedWatchers.filter( bulkObject => bulkObject.remove);
            let removedIds = removeObject.map( bulkObject => bulkObject._id);

            let idsArray = entityArray.map(entity => entity._id);
            let changedBulkObject = {
                ids: idsArray
            };

            if(updateIds.length){
                changedBulkObject.update = {};
                changedBulkObject.update.watchers = updateIds;
                changedBulkObject.update.permissions = updatePermissions;
            }
            if(removeObject.length){
                changedBulkObject.remove = {};
                changedBulkObject.remove.watchers = removedIds;
            }

            MultipleSelectService.bulkUpdate(changedBulkObject, arraysOfTypes[i] + 's')
                .then(result => {
                    for(let i = 0; i < $scope.selectedItems.length; i++){
                        let entity = result.find(entity => entity._id === $scope.selectedItems[i]._id);
                        if(typeof entity.due === 'string')entity.due = new Date(entity.due);
                        entity = _.pick(entity, ['status', 'watchers', 'assign', 'due', 'tags', 'recycled']);
                        Object.assign($scope.selectedItems[i], entity);
                    }
                    if(changedBulkObject.update.delete){
                        refreshState();
                    }
                    MultipleSelectService.setSelectedList($scope.selectedItems);
                    NotifyingService.notify('refreshAfterOperation');
                    $uibModalInstance.dismiss('cancel');
                });
        }
    };

    function refreshState(){
        let currentState = $state.current.name;
        $state.go(currentState, {
            entity: 'all',
        }, {
            reload: true
        });
    }

    //--------------------------------------------------//
    //----------------------watchers----------------------//

    $scope.usedWatchers = [];
    $scope.unusedWatchers = [];
    $scope.selectedWatchers = [];

    $scope.getUsedWatchers = function(){
        let usedIds = getIdsArray($scope.selectedItems[0].watchers);

        for(let selectedItem of $scope.selectedItems){
            usedIds = _.intersection(getIdsArray(selectedItem.watchers), usedIds)
        }

        $scope.usedWatchers = usedIds.map( id => _.find($scope.people, { '_id': id}));
        $scope.getUnusedWatchers();
        $scope.transformToBulkObjects($scope.usedWatchers);
    };

    $scope.getUnusedWatchers = function(){
        let unUsedIds = _.difference(getIdsArray($scope.people), getIdsArray($scope.usedWatchers));
        $scope.unusedWatchers = unUsedIds.map( id =>  _.find($scope.people, { '_id': id}));
    };

    $scope.transformToBulkObjects = function(array){
        $scope.selectedWatchers = array.map( object =>  createBulkWatcher(object._id, $scope.userPermissionStatus(object), false, true));
    };

    function createBulkWatcher(id, perms, remove, primary){
        return {
            '_id': id,
            'permissions': perms,
            'remove': remove,
            'primary': primary
        }
    }

     $scope.getBulkWatchersClass = function(member){
        let bulkObject = $scope.selectedWatchers.find( watcher => watcher._id === member._id);
        return bulkObject.permissions
    };

    function getIdsArray(objArray){
        return objArray.map(obj => obj._id || obj);
    }

    $scope.userPermissionStatus = function(member){
        if(member) return PermissionsService.getUnifiedPerms(member, $scope.selectedItems);
        return 'No Permissions';
    };

    function changePerms(member, newPerms){
        let bulkWatcher = $scope.selectedWatchers.find( watcher => watcher._id === member._id);
        bulkWatcher.permissions = newPerms;
    }

    $scope.setEditor = function(user){return changePerms(user, 'editor')};
    $scope.setCommenter = function(user){return changePerms(user, 'commenter');};
    $scope.setViewer = function(user){return changePerms(user, 'viewer')};

    $scope.addMember = function(member){
        $scope.usedWatchers.push(member);

        let bulkWatcher = createBulkWatcher(member._id,  $scope.userPermissionStatus(member), false, false);
        $scope.selectedWatchers.push(bulkWatcher);
    };

    $scope.removeMember = function(member){
        $scope.usedWatchers = _.reject($scope.usedWatchers,  {'_id': member._id});

        let bulkWatcher = $scope.selectedWatchers.find( watcher => watcher._id === member._id);
        if(bulkWatcher.primary){
            bulkWatcher.remove = true;
        } else {
            $scope.selectedWatchers = _.reject($scope.selectedWatchers,  {'_id': member._id});
        }
    };

    $scope.selfTest = function(member){
        member.selfTest = $scope.me._id === member._id;
        return member;
    };

    $scope.triggerSelect = function() {
        $scope.showSelect = !$scope.showSelect;
        if ($scope.showSelect) {
            $scope.animate = false;
        }
    };

    //------------------------------------------------//
    //----------------------DUE----------------------//

    $scope.selectedDue = {};
    $scope.setDueDate = 'setDueDate';
    $scope.dueDateErrorMessage = 'couldNotSetPreviousTime';
    $scope.duePlaceholder = $scope.setDueDate;
    $scope.enableSetDueDate = false;

    $scope.dueOptions = {
        onSelect: function() {
            $scope.entityDateCheck();
        },
        dateFormat: 'dd.mm.yy'
    };

    $scope.dateCheck = function(){
        if($scope.entityName !== 'discussions'){
          if(!$scope.entityDateCheck()){
            $scope.duePlaceholder = $scope.dueDateErrorMessage;
            $scope.selectedDue.date = '';
            return;
          } else {
            $scope.bulkUpdate('due', $scope.selectedDue.date);
          }
        } else {
          if($scope.setDiscussionDue()){
            $scope.bulkUpdate('due', $scope.selectedDue);
          }
        }
    };

    $scope.setDiscussionDue = function(){
      let selectedDue = $scope.selectedDue;
      $scope.enableSetDueDate = (
        (selectedDue.startDate && selectedDue.endDate && selectedDue.startTime && selectedDue.endTime)
        || (selectedDue.startDate && selectedDue.allDay)
      );
      return $scope.enableSetDueDate;
    };

    $scope.entityDateCheck = function(){
        let nowTime = new Date();
        $scope.enableSetDueDate = !($scope.selectedDue.date < nowTime);

        return $scope.enableSetDueDate;
    };

  //------------------------------------------------//
  //----------------------TAGS----------------------//

  $scope.usedTags = [];
  $scope.usedTagsFiltered;
  $scope.removedTags = [];
  $scope.tags = [];

  $scope.getlastInputText = function(val){
    $scope.lastTagInput = val;
  };

  $scope.addLastInputTextToTag = function(query){
    if(query.length && !$scope.usedTags.find( tagObj => tagObj.tag === query)){
        $scope.addTag(query);
    }
  };

  getUsedTags();
  filterUsedTags();

  function getUsedTags(){
    let usedTags = $scope.selectedItems[0].tags;

    for(let selectedItem of $scope.selectedItems){
      usedTags = _.intersection(selectedItem.tags, usedTags)
    }

    $scope.usedTags = usedTags.map( tag => tagsToBulkObjects(tag, false, true))
  }

  function filterUsedTags(){
    $scope.usedTagsFiltered = $scope.usedTags.filter( tag => !tag.remove);
  }

  function tagsToBulkObjects(tag, remove, primary){
    return {
      'tag': tag,
      'primary': primary,
      'remove': remove
    }
  }

  $scope.addTagClicked = function () {
      $scope.tagInputVisible = true;
      $timeout(function () {
          let element = angular.element('#addTag .ui-select-toggle')[0];
          element.click();
      }, 0);
  };

    $scope.addTag = function (tag) {
      $scope.usedTags.push(tagsToBulkObjects(tag, false, false));

      $scope.tagInputVisible = false;
      filterUsedTags();
  };

  $scope.removeTag = function (tagObj) {
    let bulkTag = $scope.usedTags.find( obj => obj.tag === tagObj.tag);

    if(bulkTag.primary){
      bulkTag.remove = true;
    } else {
      $scope.usedTags = _.reject($scope.usedTags,  {'tag': tagObj.tag});
    }
    filterUsedTags();
  };

  $scope.tagUpdate = function(){
    let updateObject = $scope.usedTags.filter( bulkObject => !bulkObject.remove);
    let updateIds = updateObject.map( bulkObject => bulkObject.tag);

    let removeObject = $scope.usedTags.filter( bulkObject => bulkObject.remove);
    let removedIds = removeObject.map( bulkObject => bulkObject.tag);

    let idsArray = $scope.selectedItems.map(entity => entity._id);
    let changedBulkObject = {
      ids: idsArray
    };

    if(updateIds.length){
      changedBulkObject.update = {};
      changedBulkObject.update.tags = updateIds;
    }
    if(removeObject.length){
      changedBulkObject.remove = {};
      changedBulkObject.remove.tags = removedIds;
    }

    MultipleSelectService.bulkUpdate(changedBulkObject, $scope.entityName)
      .then(result => {
        for(let i = 0; i < $scope.selectedItems.length; i++){
          let entity = result.find(entity => entity._id === $scope.selectedItems[i]._id);
          if(typeof entity.due === 'string')entity.due = new Date(entity.due);
          entity = _.pick(entity, ['status', 'watchers', 'assign', 'due', 'tags', 'recycled']);
          Object.assign($scope.selectedItems[i], entity);
        }
        if(changedBulkObject.update.delete){
          refreshState();
        }
        MultipleSelectService.setSelectedList($scope.selectedItems);
        NotifyingService.notify('refreshAfterOperation');
        $uibModalInstance.dismiss('cancel');
      });
  }

  $scope.onOpenClose = function (isOpen) {
      $scope.tagInputVisible = !isOpen;
  };

  switch (activityType) {
      case 'status':
          $scope.title = `${$i18next('setStatus')}`;
          break;
      case 'watchers':
          $scope.title = `${$i18next('setWatchers')}`;
          break;
      case 'assign':
          $scope.title = `${$i18next('assign')}`;
          break;
      case 'due':
          $scope.title = `${$i18next('setDueDate')}`;
          break;
      case 'tag':
          $scope.title = `${$i18next('addTags')}`;
          break;
      case 'delete':
          $scope.title = `${$i18next('delete')}`;
          break;
  }
}
<|endoftext|>


<commit_before>/*
A Station object represents a participant in a
contest. It has a callsign, a current frequency,
a morse keyer, and a set of methods that are
called to simulate station behavior, such as
calling CQ. It also has other configuration to
make the station unique, such as a contest exchange.

Frequencies are expressed as an offset from a
base frequency. The units are hertz.
*/

var Station = function(callSign, mode) {
  // Station configuration
  this.callSign = callSign;
  this.mode = mode;
  this.state = "idle";

  // Station state (may change during contest)
  this.frequency = 0;
  this.exchange = "5nn 3";
  this.rfGain = 0.5;
  this.dupes = [];
  this.currentDoublers = {};
  this.cqCounter = 0;

  this.keyer = new Keyer(this.callSign);
  this.msgCompleteCallback = null;  // invoked when message send complete
  this.inactivityCallback = null; // used for, e.g. calling cq if no answer

  this.cqRepeatDelay = 2500;  // in ms
};

Station.prototype.init = function(context, audioSink) {
  this.context = context;
  this.audioSink = audioSink;

  this.rfGainControl = context.createGain();
  this.rfGainControl.gain.setValueAtTime(this.rfGain, context.currentTime);
  this.rfGainControl.connect(audioSink);
  this.keyer.init(context, this.rfGainControl);
};

Station.prototype.setFrequency = function(frequency) {
  this.frequency = frequency;
  //console.log("Station  " + this.callSign + " set to freq "  + frequency);
};

Station.prototype.getFrequency = function() {
  return this.frequency;
};

Station.prototype.setExchange = function(exchange) {
  this.exchange = exchange;
};

Station.prototype.getCallsign = function() {
  return this.callSign;
};

Station.prototype.setCallsign = function(callSign) {
  this.callSign = callSign;
};

Station.prototype.setRfGain = function(gain) {
  this.rfGain = gain;
};

Station.prototype.setMode = function(mode) {
  this.mode = mode;
};

Station.prototype.mute = function() {
  this.rfGainControl.gain.value = 0.0;
};

Station.prototype.unMute = function() {
  this.rfGainControl.gain.value = this.rfGain;
};

Station.prototype.stop = function() {
  console.log("Clearing inactivity timeout "  + this.inactivityCallback + " for station " + this.callSign);
  clearTimeout(this.inactivityCallback);
  console.log("Clearing pending action timeout "  + this.pendingAction + " for station " + this.callSign);
  clearTimeout(this.pendingAction);
  this.keyer.stop();
};

Station.prototype.ifNothingHappens = function(fn, delay) {
  this.pendingAction = setTimeout(fn, delay);
  console.log("scheduled " + fn + "to happen in " + delay + "milliseconds");
}

Station.prototype.getOpDelay = function() {
  // Return a random delay between 0 and 1000 milliseconds
  ret = Math.random() * 1000.0;
  console.log("opDelay is " + ret);
  return ret;
}

/*
 Send a CQ
 */
Station.prototype.callCq = function(repeat) {
  var self = this;
  var isRepeat = repeat || false;
  //console.log("callCq for " + this.callSign);
  if (!(self.state == "idle" || self.state == "listening_after_cq" || self.state == "wait_after_tu")) {
    return;
  }
  this.msgCompleteCallback = function() {
    self.state = "listening_after_cq";
    self.cqCounter++;
    // Set a timeout that fires if no one calls us - call CQ again
    self.inactivityCallback = setTimeout(function() {self.callCq(true)}, self.cqRepeatDelay);
    //console.log("set inactivity callback " + self.inactivityCallback);
  }
  self.state = "calling_cq";
  if (!isRepeat) {
    self.cqCounter = 0;
  } else {
    console.log("Station " + self.callSign + " sending cq number " + self.cqCounter);
  }
  this.keyer.send("cq test " + this.callSign + " " + this.callSign, this.msgCompleteCallback);
};

/*
 Send the same text every <repeat> seconds. If msgCount is provided, the
 message is sent <msgCount> times.
*/
Station.prototype.sendRepeated = function(message, repeatDelay, msgCount) {
  var self = this;
  if (msgCount === undefined) {
    self.msgCounter = 5;
  } else {
    self.msgCounter = msgCount;
  }
  this.msgCompleteCallback = function() {
    console.log("Station " + self.callSign + " msgCounter is " + self.msgCounter);
    if (self.msgCounter > 0) {
      self.msgCounter--;
      self.inactivityCallback = setTimeout(function() {self.keyer.send(message, self.msgCompleteCallback)}, repeatDelay);
    }
  }
  this.keyer.send(message,   this.msgCompleteCallback);
};

/*
 Cancel repeated message. Allows currently sending message to complete.
*/
Station.prototype.cancelRepeated = function() {
  this.msgCounter = 0;
}

/*
 Send the contest exchange
 */
Station.prototype.sendExchange = function() {
  this.send(this.exchange);
};

/*
 Send my callsign
 */
Station.prototype.sendCallSign = function() {
  this.send(this.callSign);
};

/*
 Send TU + Callsign
 */
Station.prototype.sendTU = function() {
  this.send("tu " + this.callSign);
};

Station.prototype.isCallsign = function(s) {
  return (/^[0-9a-zA-Z\/]+$/).test(s);
}

Station.prototype.isCq = function(s) {
  //m = s.match(/^cq *test *([0-9a-zA-Z\/]+)$/i);
  m = s.match(/cq *test *(([0-9a-zA-Z\/]+) *)+/i);
  return (m != null);
}

Station.prototype.isMyReportSP = function(s) {
  re = new RegExp("^ *" + this.callSign + "..*[1-5][1-9n][1-9n]$", "i");
  m = re.exec(s);
  console.log("isMyReportSP: " + (m != null));
  return m != null;
}

Station.prototype.isMyReportRun = function(s) {
  re = new RegExp("^..*[1-5][1-9n][1-9n]$", "i");
  m = re.exec(s);
  console.log("isMyReportRun: " + (m != null));
  return m != null;
}

Station.prototype.isTu = function(s) {
  return (/^.*tu.*$/i).test(s);
}

Station.prototype.isFillRequest = function(s) {
  //if (/^ *\? *$/i).test(s) {
  if (/^.*agn.*$/i.test(s)) {
    return true;
  }
  if (/^\?$/.test(s)) {
    return true;
  }
  return false;
}

Station.prototype.respond = function(msg) {
  // Send a response to a message, with a short random delay.
  var self = this;
  console.log("RESPOND");
  setTimeout(function() {self.keyer.send(msg)}, self.getOpDelay());
}

Station.prototype.handleMessageBeginRun = function(message, fromCall) {
  console.log("handleMessageBeginRun: " + this.callSign + " handling " + message + " while in state " + this.state);
  if (this.keyer.isSending()) {
    console.log("Station " + fromCall + " doubled with sending station " + this.callSign);
    this.currentDoublers[fromCall] = true;
    return;
  } else {
    console.log("Station " + this.callSign + " keyer is NOT sending");
  }
  switch (this.state) {
    case "calling_cq":
    case "listening_after_cq":
    case "wait_after_tu":
      // Clear the inactivity timeout so we don't CQ on top of the caller
      clearTimeout(this.inactivityCallback);
  }
};

Station.prototype.handleMessageBeginSearchAndPounce = function(message, fromCall) {
  console.log("handleMessageBeginSearchAndPounce: " + this.callSign + " handling " + message);
};

Station.prototype.handleMessageEndRun = function(message, fromCall) {
  var self = this;
  console.log("handleMessageEndRun: " + this.callSign + " handling " + message + ", state is " + this.state);
  if (this.keyer.isSending()) {
    // The other station's double started and ended while we were still
    // sending. Remove that station from the doublers list since we would
    // have never heard them. Ignoring QSK for now.
    delete this.currentDoublers[fromCall];
    console.log("total double from " + fromCall);
    return;
  } else if (!jQuery.isEmptyObject(this.currentDoublers)) {
    // The other station's double started while we were transmitting, but
    // ended after we started listening. Send a "?".
    console.log("Partial double from " + fromCall);
    this.currentDoublers = {};
    this.keyer.send("?");
    return;
  }
  switch (this.state) {
    case "listening_after_cq":
    case "wait_after_tu":
      if (this.isCallsign(message)) {
        this.state = "sending_report";
        console.log("Canceling activityTimeout " + this.inactivityCallback);
        clearTimeout(this.inactivityCallback);
        self.respond(message +  "5nn 3");
        //setTimeout(function() {self.keyer.send(message + " 5nn 3")}, self.getOpDelay());
        self.state = "wait_my_report";
      }
      break;
    case "wait_my_report":
      if (this.isMyReportRun(message)) {
        this.keyer.send("tu " + this.callSign, function() {
          self.state = "wait_after_tu";
          // Set a timeout that fires if no one calls us - call CQ
          self.inactivityCallback = setTimeout(function() {self.callCq()}, self.cqRepeatDelay);
        });
      }
      break;
    case "double":
      this.keyer.send("?", function() {
        self.state = "listening_after_cq";
        self.inactivityCallback = setTimeout(function() {self.callCq()}, self.cqRepeatDelay);
      });
      break;
  }
};

Station.prototype.handleMessageEndSearchAndPounce = function(message, fromCall) {
  console.log("handleMessageEndSearchAndPounce: " + this.callSign + " handling " + message);
  var self = this;
  switch (this.state) {
    case "idle":
      if (this.isCq(message)) {
        if ($.inArray(fromCall, this.dupes) != -1) {
          console.log("Station " + self.callSign + " heard CQ from " + fromCall + " but is a dupe");
        } else {
         setTimeout(function() {self.keyer.send(self.callSign)}, self.getOpDelay());
         this.state = "wait_my_report";
       }
      }
      break;
    case "wait_my_report":
      if (this.isMyReportSP(message)) {
        setTimeout(function() {self.keyer.send(fromCall + " 5NN")}, self.getOpDelay());
        this.state = "wait_confirm";
      } else {
        console.log("not my report");
        this.state = "wait_other_qso_to_end";
      }
      break;
    case "wait_confirm":
      if (this.isTu(message)) {
        this.dupes.push(fromCall);
        this.state = "idle"
      } else if (this.isFillRequest(message)) {
        setTimeout(function() {self.keyer.send(fromCall + " 5NN"), self.getOpDelay()});
        this.state = "wait_confirm";
      }
      break;
    case "wait_other_qso_to_end":
      if (this.isTu(message)) {
        console.log(this.dupes);
        if (!$.inArray(fromCall, this.dupes) != -1) {
         setTimeout(function() {self.keyer.send(self.callSign)}, self.getOpDelay());
         this.state = "wait_my_report";
       }
      }
  }
};

Station.prototype.handleMessageBegin = function(message, fromCall) {
  if (this.mode == "run") {
    this.handleMessageBeginRun(message, fromCall);
  } else {
    this.handleMessageBeginSearchAndPounce(message, fromCall);
  }
};

Station.prototype.handleMessageEnd = function(message, fromCall) {
  if (this.keyer.isSending()) {
    console.log("NOT handling message end from " + fromCall + " because station " + this.callSign + " keyer is sending");
    // What to do here? If the message is a "total double" we want to ignore it
    // But if it was a partial double (meaning: the sender started while we
    // were transmitting but we stopped transmitting before they were finished)
    // we want to send a "?"
  }
  if (this.mode == "run") {
    this.handleMessageEndRun(message, fromCall);
  } else {
    this.handleMessageEndSearchAndPounce(message, fromCall);
  }
};
<commit_msg>Update design notes for station.js<commit_after>/*
A Station object represents a participant in a
contest. It has a callsign, a current frequency,
a morse keyer, and a set of methods that are
called to simulate station behavior, such as
calling CQ. It also has other configuration to
make the station unique, such as a contest exchange.

Specific properties:

** callSign: the callsign of the station.

** mode: "run", "sp", or "manual"

A station in "sp" (search and pounce) mode will respond to a cq
if it falls within a certain range of its current frequency.
A station in "sp" mode will change its own frequency, in
order to find stations calling cq.

A station in "run" mode will call cq repeatedly, and will respond
to any station that is within a certain range of the runner's
frequency and responds to the cq. A station in run mode may choose
to change its frequency if it deems necessary, due to lack
of responses or too much activity nearby.

A station in "manual" mode is being operated by the human
running the simulator, and takes no actions on its own.

** frequency: the current frequency of the station.

Changing the frequency of the station affects which stations
it will interact with, and, if the station's audio is being
monitored, affects what is audible.

Frequencies are expressed as an offset from a
base frequency. The units are hertz.
*/

var Station = function(callSign, mode) {
  // Station configuration
  this.callSign = callSign;
  this.mode = mode;

  // Station state (may change during contest)
  this.state = "idle";
  this.frequency = 0;
  this.exchange = "5nn 3";
  this.rfGain = 0.5;
  this.dupes = [];
  this.currentDoublers = {};
  this.cqCounter = 0;

  this.keyer = new Keyer(this.callSign);
  this.msgCompleteCallback = null;  // invoked when message send complete
  this.inactivityCallback = null; // used for, e.g. calling cq if no answer

  this.cqRepeatDelay = 2500;  // in ms
};

Station.prototype.init = function(context, audioSink) {
  this.context = context;
  this.audioSink = audioSink;

  this.rfGainControl = context.createGain();
  this.rfGainControl.gain.setValueAtTime(this.rfGain, context.currentTime);
  this.rfGainControl.connect(audioSink);
  this.keyer.init(context, this.rfGainControl);
};

Station.prototype.setFrequency = function(frequency) {
  this.frequency = frequency;
  console.log("Station  " + this.callSign + " set to freq "  + frequency);
};

Station.prototype.getFrequency = function() {
  return this.frequency;
};

Station.prototype.setExchange = function(exchange) {
  this.exchange = exchange;
};

Station.prototype.getCallsign = function() {
  return this.callSign;
};

Station.prototype.setCallsign = function(callSign) {
  this.callSign = callSign;
};

Station.prototype.setRfGain = function(gain) {
  this.rfGain = gain;
};

Station.prototype.setMode = function(mode) {
  this.mode = mode;
};

Station.prototype.mute = function() {
  this.rfGainControl.gain.value = 0.0;
};

Station.prototype.unMute = function() {
  this.rfGainControl.gain.value = this.rfGain;
};

Station.prototype.stop = function() {
  console.log("Clearing inactivity timeout "  + this.inactivityCallback + " for station " + this.callSign);
  clearTimeout(this.inactivityCallback);
  console.log("Clearing pending action timeout "  + this.pendingAction + " for station " + this.callSign);
  clearTimeout(this.pendingAction);
  this.keyer.stop();
};

Station.prototype.ifNothingHappens = function(fn, delay) {
  this.pendingAction = setTimeout(fn, delay);
  console.log("scheduled " + fn + "to happen in " + delay + "milliseconds");
}

Station.prototype.getOpDelay = function() {
  // Return a random delay between 0 and 1000 milliseconds
  ret = Math.random() * 1000.0;
  console.log("opDelay is " + ret);
  return ret;
}

/*
 Send a CQ
 */
Station.prototype.callCq = function(repeat) {
  var self = this;
  var isRepeat = repeat || false;
  //console.log("callCq for " + this.callSign);
  if (!(self.state == "idle" || self.state == "listening_after_cq" || self.state == "wait_after_tu")) {
    return;
  }
  this.msgCompleteCallback = function() {
    self.state = "listening_after_cq";
    self.cqCounter++;
    // Set a timeout that fires if no one calls us - call CQ again
    self.inactivityCallback = setTimeout(function() {self.callCq(true)}, self.cqRepeatDelay);
    //console.log("set inactivity callback " + self.inactivityCallback);
  }
  self.state = "calling_cq";
  if (!isRepeat) {
    self.cqCounter = 0;
  } else {
    console.log("Station " + self.callSign + " sending cq number " + self.cqCounter);
  }
  this.keyer.send("cq test " + this.callSign + " " + this.callSign, this.msgCompleteCallback);
};

/*
 Send the same text every <repeat> seconds. If msgCount is provided, the
 message is sent <msgCount> times.
*/
Station.prototype.sendRepeated = function(message, repeatDelay, msgCount) {
  var self = this;
  if (msgCount === undefined) {
    self.msgCounter = 5;
  } else {
    self.msgCounter = msgCount;
  }
  this.msgCompleteCallback = function() {
    console.log("Station " + self.callSign + " msgCounter is " + self.msgCounter);
    if (self.msgCounter > 0) {
      self.msgCounter--;
      self.inactivityCallback = setTimeout(function() {self.keyer.send(message, self.msgCompleteCallback)}, repeatDelay);
    }
  }
  this.keyer.send(message,   this.msgCompleteCallback);
};

/*
 Cancel repeated message. Allows currently sending message to complete.
*/
Station.prototype.cancelRepeated = function() {
  this.msgCounter = 0;
}

/*
 Send the contest exchange
 */
Station.prototype.sendExchange = function() {
  this.send(this.exchange);
};

/*
 Send my callsign
 */
Station.prototype.sendCallSign = function() {
  this.send(this.callSign);
};

/*
 Send TU + Callsign
 */
Station.prototype.sendTU = function() {
  this.send("tu " + this.callSign);
};

Station.prototype.isCallsign = function(s) {
  return (/^[0-9a-zA-Z\/]+$/).test(s);
}

Station.prototype.isCq = function(s) {
  //m = s.match(/^cq *test *([0-9a-zA-Z\/]+)$/i);
  m = s.match(/cq *test *(([0-9a-zA-Z\/]+) *)+/i);
  return (m != null);
}

Station.prototype.isMyReportSP = function(s) {
  re = new RegExp("^ *" + this.callSign + "..*[1-5][1-9n][1-9n]$", "i");
  m = re.exec(s);
  console.log("isMyReportSP: " + (m != null));
  return m != null;
}

Station.prototype.isMyReportRun = function(s) {
  re = new RegExp("^..*[1-5][1-9n][1-9n]$", "i");
  m = re.exec(s);
  console.log("isMyReportRun: " + (m != null));
  return m != null;
}

Station.prototype.isTu = function(s) {
  return (/^.*tu.*$/i).test(s);
}

Station.prototype.isFillRequest = function(s) {
  //if (/^ *\? *$/i).test(s) {
  if (/^.*agn.*$/i.test(s)) {
    return true;
  }
  if (/^\?$/.test(s)) {
    return true;
  }
  return false;
}

Station.prototype.respond = function(msg) {
  // Send a response to a message, with a short random delay.
  var self = this;
  console.log("RESPOND");
  setTimeout(function() {self.keyer.send(msg)}, self.getOpDelay());
}

Station.prototype.handleMessageBeginRun = function(message, fromCall) {
  console.log("handleMessageBeginRun: " + this.callSign + " handling " + message + " while in state " + this.state);
  if (this.keyer.isSending()) {
    console.log("Station " + fromCall + " doubled with sending station " + this.callSign);
    this.currentDoublers[fromCall] = true;
    return;
  } else {
    console.log("Station " + this.callSign + " keyer is NOT sending");
  }
  switch (this.state) {
    case "calling_cq":
    case "listening_after_cq":
    case "wait_after_tu":
      // Clear the inactivity timeout so we don't CQ on top of the caller
      clearTimeout(this.inactivityCallback);
  }
};

Station.prototype.handleMessageBeginSearchAndPounce = function(message, fromCall) {
  console.log("handleMessageBeginSearchAndPounce: " + this.callSign + " handling " + message);
};

Station.prototype.handleMessageEndRun = function(message, fromCall) {
  var self = this;
  console.log("handleMessageEndRun: " + this.callSign + " handling " + message + ", state is " + this.state);
  if (this.keyer.isSending()) {
    // The other station's double started and ended while we were still
    // sending. Remove that station from the doublers list since we would
    // have never heard them. Ignoring QSK for now.
    delete this.currentDoublers[fromCall];
    console.log("total double from " + fromCall);
    return;
  } else if (!jQuery.isEmptyObject(this.currentDoublers)) {
    // The other station's double started while we were transmitting, but
    // ended after we started listening. Send a "?".
    console.log("Partial double from " + fromCall);
    this.currentDoublers = {};
    this.keyer.send("?");
    return;
  }
  switch (this.state) {
    case "listening_after_cq":
    case "wait_after_tu":
      if (this.isCallsign(message)) {
        this.state = "sending_report";
        console.log("Canceling activityTimeout " + this.inactivityCallback);
        clearTimeout(this.inactivityCallback);
        self.respond(message +  "5nn 3");
        //setTimeout(function() {self.keyer.send(message + " 5nn 3")}, self.getOpDelay());
        self.state = "wait_my_report";
      }
      break;
    case "wait_my_report":
      if (this.isMyReportRun(message)) {
        this.keyer.send("tu " + this.callSign, function() {
          self.state = "wait_after_tu";
          // Set a timeout that fires if no one calls us - call CQ
          self.inactivityCallback = setTimeout(function() {self.callCq()}, self.cqRepeatDelay);
        });
      }
      break;
    case "double":
      this.keyer.send("?", function() {
        self.state = "listening_after_cq";
        self.inactivityCallback = setTimeout(function() {self.callCq()}, self.cqRepeatDelay);
      });
      break;
  }
};

Station.prototype.handleMessageEndSearchAndPounce = function(message, fromCall) {
  console.log("handleMessageEndSearchAndPounce: " + this.callSign + " handling " + message);
  var self = this;
  switch (this.state) {
    case "idle":
      if (this.isCq(message)) {
        if ($.inArray(fromCall, this.dupes) != -1) {
          console.log("Station " + self.callSign + " heard CQ from " + fromCall + " but is a dupe");
        } else {
         setTimeout(function() {self.keyer.send(self.callSign)}, self.getOpDelay());
         this.state = "wait_my_report";
       }
      }
      break;
    case "wait_my_report":
      if (this.isMyReportSP(message)) {
        setTimeout(function() {self.keyer.send(fromCall + " 5NN")}, self.getOpDelay());
        this.state = "wait_confirm";
      } else {
        console.log("not my report");
        this.state = "wait_other_qso_to_end";
      }
      break;
    case "wait_confirm":
      if (this.isTu(message)) {
        this.dupes.push(fromCall);
        this.state = "idle"
      } else if (this.isFillRequest(message)) {
        setTimeout(function() {self.keyer.send(fromCall + " 5NN"), self.getOpDelay()});
        this.state = "wait_confirm";
      }
      break;
    case "wait_other_qso_to_end":
      if (this.isTu(message)) {
        console.log(this.dupes);
        if (!$.inArray(fromCall, this.dupes) != -1) {
         setTimeout(function() {self.keyer.send(self.callSign)}, self.getOpDelay());
         this.state = "wait_my_report";
       }
      }
  }
};

Station.prototype.handleMessageBegin = function(message, fromCall) {
  if (this.mode == "run") {
    this.handleMessageBeginRun(message, fromCall);
  } else {
    this.handleMessageBeginSearchAndPounce(message, fromCall);
  }
};

Station.prototype.handleMessageEnd = function(message, fromCall) {
  if (this.keyer.isSending()) {
    console.log("NOT handling message end from " + fromCall + " because station " + this.callSign + " keyer is sending");
    // What to do here? If the message is a "total double" we want to ignore it
    // But if it was a partial double (meaning: the sender started while we
    // were transmitting but we stopped transmitting before they were finished)
    // we want to send a "?"
  }
  if (this.mode == "run") {
    this.handleMessageEndRun(message, fromCall);
  } else {
    this.handleMessageEndSearchAndPounce(message, fromCall);
  }
};
<|endoftext|>


<commit_before>#!/usr/bin/env node

var hogan = require('hogan.js'),
    fs    = require('fs');

var layout, pages;

// compile layout template
layout = fs.readFileSync(__dirname + '/../templates/layout.mustache', 'utf-8');
layout = hogan.compile(layout, { sectionTags: [{o:'_i', c:'i'}] });

// retrieve pages
pages = fs.readdirSync(__dirname + '/../templates/pages');

// iterate over pages
pages.forEach(function (name) {

  page = hogan.compile(page)
  page = layout.render(context, {
    body: page
  })

  fs.writeFileSync(__dirname + '/../' + name.replace(/mustache$/, 'html'), page, 'utf-8')
})
<commit_msg>will work on docs later<commit_after><|endoftext|>


<commit_before>const assert = require('assert');
const request = require('./_shared.js').request;
const shouldLogIn = require('./_shared.js').shouldLogIn;
const testAdmin = require('./_shared.js').testAdmin;
const testTranslator = require('./_shared.js').testTranslator;
const testUpdateTranslatorDescription = require('./_shared.js').testUpdateTranslatorDescription;

describe('Update user API', () => {

  shouldLogIn(testAdmin);

  it('should not update user description (no data)', (done) => {
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send('123')
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. Invalid data");
          done();
        }
      )
  });

  it('should not update user description (no id)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    delete userDescription['id'];
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. No id");
          done();
        }
      )
  });

  it('should not update user description (no description)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    delete userDescription['description'];
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. No description");
          done();
        }
      )
  });

  it('should not update user description (invalid id)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    userDescription['id'] = {obj: 'wrong_id'};
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. Invalid id");
          done();
        }
      )
  });

  it('should not update user description (invalid description)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    userDescription['description'] = {obj: 'wrong_description'};
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. Invalid description");
          done();
        }
      )
  });

  it('should update user description', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.equal(res.body.user.description, testUpdateTranslatorDescription.description);
          assert.equal(res.body.success, true);
          done();
        }
      )
  });
});
<commit_msg>Removed useless data<commit_after>const assert = require('assert');
const request = require('./_shared.js').request;
const shouldLogIn = require('./_shared.js').shouldLogIn;
const testAdmin = require('./_shared.js').testAdmin;
const testUpdateTranslatorDescription = require('./_shared.js').testUpdateTranslatorDescription;

describe('Update user API', () => {

  shouldLogIn(testAdmin);

  it('should not update user description (no data)', (done) => {
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send('123')
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. Invalid data");
          done();
        }
      )
  });

  it('should not update user description (no id)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    delete userDescription['id'];
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. No id");
          done();
        }
      )
  });

  it('should not update user description (no description)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    delete userDescription['description'];
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. No description");
          done();
        }
      )
  });

  it('should not update user description (invalid id)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    userDescription['id'] = {obj: 'wrong_id'};
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. Invalid id");
          done();
        }
      )
  });

  it('should not update user description (invalid description)', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    userDescription['description'] = {obj: 'wrong_description'};
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.notEqual(res.body.success, true);
          assert.equal(res.body.message, "Can't update translator description. Invalid description");
          done();
        }
      )
  });

  it('should update user description', (done) => {
    let userDescription = Object.assign({}, testUpdateTranslatorDescription);
    request.post('/api/updateUserDescription')
      .set('Authorization', 'Bearer ' + testAdmin.token)
      .send({userNewDescription: userDescription})
      .end(
        (err, res) => {
          assert.equal(res.body.user.description, testUpdateTranslatorDescription.description);
          assert.equal(res.body.success, true);
          done();
        }
      )
  });
});
<|endoftext|>


<commit_before>describe('variable', function() {

    beforeEach(function() {
        ne.util.variable.reset({});
    });

    it('저장된 전역 변수를 안전하게 참조할 수 있다', function() {
        ne.util.variable.reset({
            a: 1,
            b: 2,
            c: 'stop',
            d: {
                a: '2'
            }
        });

        expect(ne.util.variable.get('a')).toBe(1);
        expect(ne.util.variable.get('d.a')).toBe('2');
        expect(ne.util.variable.get('cc')).toBeUndefined();
        expect(ne.util.variable.get('woeijrwoijr.we.qwem1;123;')).toBeUndefined();
    });

    it('저장되는 변수는 참조형이기 때문에 주의를 요한다', function() {
        var arr = [1, 2],
            obj = {};

        ne.util.variable.set('mydata.arr', arr);

        arr.push(3);

        expect(ne.util.variable.get('mydata.arr').length).toBe(3);

        ne.util.variable.set('mydata.obj', obj);

        // 참조형이므로 직접 수정도 가능하다
        obj.test = '123';

        expect(ne.util.variable.get('mydata.obj.test')).toBe('123');
    });

    it('get() 메서드를 인자 없이 사용할 경우 undefined를 반환한다', function() {
        ne.util.variable.set('myval', '123');

        expect(ne.util.variable.get()).toBe(undefined);
    });

    it('전역변수를 한 곳에 안전하게 저장할 수 있다', function() {
        ne.util.variable.set('msg.loginerror', '로그인 중 문제가 발생했습니다. 잠시 후 다시 시도하세요');

        expect(ne.util.variable.get('msg.loginerror')).toBe('로그인 중 문제가 발생했습니다. 잠시 후 다시 시도하세요');
    });

    it('전역변수를 설정할 때 key:value의 객체 배열을 넘겨 한번에 여러 변수를 설정할 수 있다', function() {
        ne.util.variable.set('msg.error', '오류가 발생했습니다. 잠시 후 다시 시도하세요');

        ne.util.variable.set({
            'msg.error': '오류 발생',
            'msg.test': '메시지 테스트'
        });

        expect(ne.util.variable.get('msg.error')).toBe('오류 발생');
        expect(ne.util.variable.get('msg.test')).toBe('메시지 테스트');
    });

    it('이미 있는 전역변수를 다시 설정하면 덮어쓴다', function() {
        ne.util.variable.set('myval', 'test');

        expect(ne.util.variable.get('myval')).toBe('test');

        ne.util.variable.set('myval', 'good');

        expect(ne.util.variable.get('myval')).not.toBe('test');
    });

    it('null의 저장은 값이 있는 것으로 본다 (undefined만이 값이 없는 것으로 본다)', function() {
        ne.util.variable.set('msg.myval', null);

        ne.util.variable.set('msg.myval2', undefined);

        expect(ne.util.variable.get('msg.myval')).toBe(null);
        expect(ne.util.variable.get('msg.myval2')).toBeUndefined();
        expect(ne.util.variable.get('msg.myval2') === ne.util.variable.get('msg.ewrewrer')).toBe(true);
    });

    it('set으로 undefined를 할당하는 행위는 무시된다', function() {
        ne.util.variable.set('msg.test', undefined);

        expect(ne.util.variable.get('msg.test')).toBe(undefined);
    });

    it('전역변수가 복잡하더라도 변수를 정확히 설정한다', function() {

        ne.util.variable.reset({
            msg: {
                error: '오류가 발생했습니다. 잠시 후 다시 시도하세요',
                loginerror: '로그인 중 문제가 발생했습니다. 잠시 후 다시 시도하세요',
                common: {
                    user: {
                        need: '을(를) 다시 입력하세요'
                    },
                    admin: {
                        need: '을(를) 다시 입력해 주세요'
                    }
                }
            },
            url: {
                'login_url': 'http://www.naver.com',
                'detail_url': {
                    'product': 'http://www.naver.com/product/test'
                }
            }
        });

        ne.util.variable.set('msg.common.user.no_need', '을(를) 다시 입력할 필요가 없습니다');

        ne.util.variable.set({
            'url.detail_url.product': 'http://www.google.com',
            'msg.common.admin': '이보게 젊은이'
        });

        expect(ne.util.variable.get('msg.common.admin')).toBe('이보게 젊은이');
        expect(ne.util.variable.get('msg.common.user.no_need', '을(를) 다시 입력할 필요가 없습니다'));
        expect(ne.util.variable.get('url.detail_url.product')).toBe('http://www.google.com');
        expect(ne.util.variable.get('msg.common.fire')).toBeUndefined();
        // msg.common.admin 항목은 덮어써진다.
        expect(ne.util.variable.get('msg.common.admin.need')).toBe(undefined);
    });

    it('reset() 메서드를 통해 전역변수 공간을 한번에 덮어쓸 수 있다', function() {

        expect(function() {
            ne.util.variable.reset(3);
        }).toThrowError('variable: 전역변수 공간은 object 형태의 데이터로만 설정이 가능합니다.');

        expect(function() {
            ne.util.variable.reset(function() {});
        }).toThrowError('variable: 전역변수 공간은 object 형태의 데이터로만 설정이 가능합니다.');

        expect(function() {
            ne.util.variable.reset([]);
        }).toThrowError('variable: 전역변수 공간은 object 형태의 데이터로만 설정이 가능합니다.');

        var mySetting = {
            a: '123',
            b: '456',
            c: ['a', 'b']
        };

        ne.util.variable.reset(mySetting);

        expect(ne.util.variable.get('a')).toBe('123');
        expect(ne.util.variable.get('b')).toBe('456');
        expect(ne.util.variable.get('c').length).toBe(2);
    });

});
<commit_msg>delete test suite for deleted module Variable<commit_after><|endoftext|>


<commit_before>describe("State", function() {
	"use strict";

	var customStoreData = {};
	var customStore = function(initVal, key, factorykey) {
		var prefix = this.prefix || '';
		factorykey = factorykey ? (factorykey + '.') : '';
		key = prefix + factorykey + key;
		var prop = function(valNew) {
			if (valNew) {
				customStoreData[key] = valNew;
			} else {
				return customStoreData[key];
			}
		};
		if (!(key in customStoreData))
			prop(initVal);
		return prop;
	};

	it("Single instance", function() {
		var state = md.State.create({
			isEditing: false,
			test: 'Foo'
		});
		expect(state.isEditing()).to.be.false;
		expect(state.test()).to.be.equal('Foo');
		state.isEditing(true);
		state.test('Bar');
		expect(state.isEditing()).to.be.true;
		expect(state.test()).to.be.equal('Bar');
	});

	it("Accepts fuctions initialized with m.prop()", function() {
		var _isDone = m.prop(false);
		var _test = m.prop('Foo');
		var state = md.State.create({
			isDone: _isDone,
			test: _test
		});
		expect(state.isDone()).to.be.false;
		expect(state.test()).to.be.equal('Foo');
		state.isDone(true);
		state.test('Bar');
		expect(state.isDone()).to.be.true;
		expect(state.test()).to.be.equal('Bar');
	});

	it("`toJson()` method", function() {
		var _isDone = m.prop(false);
		var _test = m.prop('Foo');
		var state = md.State.create({
			isDone: _isDone,
			test: _test
		});
		var json = state.toJson();
		expect(json.isDone).to.be.false;
		expect(json.test).to.be.equal('Foo');
	});

	it("Custom store / prop (non factory)", function() {
		var state = md.State.create({
			name: 'Foo',
			age: 25
		}, {
			store: customStore
		});
		expect(state.name()).to.equal(customStoreData.name).and.to.equal('Foo');
		expect(state.age()).to.equal(customStoreData.age).and.to.equal(25);
	});

	it("Custom store / prop with prefix (non factory)", function() {
		var state = md.State.create({
			name: 'Foo',
			age: 25
		}, {
			prefix: 'pref.',
			store: customStore
		});
		expect(state.name()).to.equal(customStoreData['pref.name']).and.to.equal('Foo');
		expect(state.age()).to.equal(customStoreData['pref.age']).and.to.equal(25);
	});

	it("Custom store / prop with prefix (factory)", function() {
		var stateFactory = new md.State({
			name: 'Foo',
			age: 25
		}, {
			prefix: 'pref.',
			store: customStore
		});
		var stateA = stateFactory.set('a');
		expect(stateA.name()).to.equal(customStoreData['pref.a.name']).and.to.equal('Foo');
		expect(stateA.age()).to.equal(customStoreData['pref.a.age']).and.to.equal(25);
	});

});<commit_msg>Updated test suite for state class<commit_after>describe("State", function() {
	"use strict";

	var customStoreData = {};
	var customStore = function(initVal, key, factorykey) {
		var prefix = this.prefix || '';
		factorykey = factorykey ? (factorykey + '.') : '';
		key = prefix + factorykey + key;
		var prop = function(valNew) {
			if (arguments.length) {
				customStoreData[key] = valNew;
			} else {
				return customStoreData[key];
			}
		};
		if (!(key in customStoreData))
			prop(initVal);
		return prop;
	};

	it("Single instance", function() {
		var state = md.State.create({
			isEditing: false,
			test: 'Foo'
		});
		expect(state.isEditing()).to.be.false;
		expect(state.test()).to.be.equal('Foo');
		state.isEditing(true);
		state.test('Bar');
		expect(state.isEditing()).to.be.true;
		expect(state.test()).to.be.equal('Bar');
	});

	it("Accepts fuctions initialized with m.prop()", function() {
		var _isDone = m.prop(false);
		var _test = m.prop('Foo');
		var state = md.State.create({
			isDone: _isDone,
			test: _test
		});
		expect(state.isDone()).to.be.false;
		expect(state.test()).to.be.equal('Foo');
		state.isDone(true);
		state.test('Bar');
		expect(state.isDone()).to.be.true;
		expect(state.test()).to.be.equal('Bar');
	});

	it("`toJson()` method", function() {
		var _isDone = m.prop(false);
		var _test = m.prop('Foo');
		var state = md.State.create({
			isDone: _isDone,
			test: _test
		});
		var json = state.toJson();
		expect(json.isDone).to.be.false;
		expect(json.test).to.be.equal('Foo');
	});

	it("Custom store / prop (non factory)", function() {
		var state = md.State.create({
			name: 'Foo',
			age: 25,
			active: false
		}, {
			store: customStore
		});
		expect(state.name()).to.equal(customStoreData.name).and.to.equal('Foo');
		expect(state.age()).to.equal(customStoreData.age).and.to.equal(25);
		expect(state.active()).to.equal(customStoreData.active).and.to.equal(false);
	});

	it("Custom store / prop with prefix (non factory)", function() {
		var state = md.State.create({
			name: 'Foo',
			age: 25
		}, {
			prefix: 'pref.',
			store: customStore
		});
		expect(state.name()).to.equal(customStoreData['pref.name']).and.to.equal('Foo');
		expect(state.age()).to.equal(customStoreData['pref.age']).and.to.equal(25);
	});

	it("Custom store / prop with prefix (factory)", function() {
		var stateFactory = new md.State({
			name: 'Foo',
			age: 25
		}, {
			prefix: 'pref.',
			store: customStore
		});
		var stateA = stateFactory.set('a');
		expect(stateA.name()).to.equal(customStoreData['pref.a.name']).and.to.equal('Foo');
		expect(stateA.age()).to.equal(customStoreData['pref.a.age']).and.to.equal(25);
	});

});<|endoftext|>


<commit_before>'use strict';

function FloatingText(width, text, type, scene, current_color) {
    /*   __   __        __  ___  __        __  ___  __   __
        /  ` /  \ |\ | /__`  |  |__) |  | /  `  |  /  \ |__)
        \__, \__/ | \| .__/  |  |  \ \__/ \__,  |  \__/ |  \ */
    this.width         = width;
    this.type          = type;
    this.scene         = scene;
    this.object3D      = new THREE.Object3D();
    this.current_color = COLOR_DAY_PRESENT;
    this.default_color = COLOR_DAY_PRESENT;

    // Default value.
    this.normal_depth  = 1;

    if (is_defined(current_color)) {
        this.current_color = current_color;
        this.default_color = current_color;
    }
    // Convert to the correct color type.
    if (is_list(this.current_color)) {
        var temp = this.current_color;
        if (this.is_2d_text) {
            this.current_color = temp[COLOR_STRING_INDEX];
            this.default_color = temp[COLOR_STRING_INDEX];
        } else {
            this.current_color = temp[COLOR_HEX_INDEX];
            this.default_color = temp[COLOR_HEX_INDEX];
        }
    }

    //l('Current color ' + this.current_color);
    //l('Default color ' + this.current_color);

    if (this.type == TYPE_INPUT_PASSWORD) {
        this.text = '';
        for (var c = 0; c < text.length; c++) {
            this.text += '*';
        }
        this.hidden_text = text;
    } else {
        this.text = text;
    }

    // Call the child initialize functions.
    this.initialize();

    // Gets called from child functions.
    this.final_initialize = function() {
        switch (this.type) {
        case TYPE_BUTTON:
        case TYPE_CHECK_BOX:
            this.maintain_engage_when_tabbed_to = false;
            this.engable = false;
            break;
        case TYPE_CONSTANT_TEXT:
            this.engable = false;
        }
    };

    /*   ___            __  ___    __        __
        |__  |  | |\ | /  `  |  | /  \ |\ | /__`
        |    \__/ | \| \__,  |  | \__/ | \| .__/ */

    // This is just an alternative name to the function update_color.
    this.set_color = function(color) {
        this.update_color(color);
    };

    // Gets called in constructor so defining this function first.
    this.update_color = function(color) {
        if (is_list(color)) {
            var color_to_set = null;
            if (this.is_2d_text) {
                color_to_set = color[COLOR_STRING_INDEX];
            } else {
                color_to_set = color[COLOR_HEX_INDEX];
            }
            this.current_color = color_to_set;
            this._update_color();
        } else {
            this.current_color = color;
            this._update_color();
        }
    };

    this.update_text = function(text) {
        // TODO : This is currently assuming this function never gets called on a Password field.
        if (this.text !== text) {
            this.text = text;
            this._update_text();
        }
    };

    this.get_text = function() {
        if (this.type === TYPE_INPUT_PASSWORD) {
            return this.hidden_text;
        }
        return this.text;
    };

    this._add_character = function(character) {
        this.update_text(this.text + character);
    };

    this._pop_character = function() {
        this.update_text(this.text.slice(0, -1));
    };

    this.parse_keycode = function(event) {
        var keycode = event.keyCode;

        if (keycode === KEY_CODE_DELETE) {
            if (this.text.length > 0) {
                this._pop_character();
                if (this.type === TYPE_INPUT_PASSWORD) {
                    this.hidden_text = this.hidden_text.slice(0, -1);
                }
                MANAGER_AUDIO.play_typing_sound();
            }
        } else if (event.key.length === 1) {
            if (this.type === TYPE_INPUT_PASSWORD) {
                this.hidden_text += event.key;
                this._add_character('*');
            } else if (this.type === TYPE_INPUT_REGULAR) {
                this._add_character(event.key);
            }
            MANAGER_AUDIO.play_typing_sound();
        }
    };

    this.get_position = function() {
        return this.object3D.position;
    };

    this.update_position_and_look_at_origin = function(position_vector) {
        this.object3D.position.x = position_vector.x;
        this.object3D.position.y = position_vector.y;
        this.object3D.position.z = position_vector.z;
        this.normal = new THREE.Vector3(0 - position_vector.x, 0, 0 - position_vector.z);
        this.normal.normalize();
    };

    this.update_position = function(position_vector) {
        if (is_defined(this.normal_depth) && is_defined(this.normal)) {
            this.object3D.position.x = position_vector.x + this.normal.x * this.normal_depth;
            this.object3D.position.y = position_vector.y + this.normal.y * this.normal_depth;
            this.object3D.position.z = position_vector.z + this.normal.z * this.normal_depth;

            this.x_without_normal = this.object3D.position.x - this.normal.x * this.normal_depth;
            this.y_without_normal = this.object3D.position.y - this.normal.y * this.normal_depth;
            this.z_without_normal = this.object3D.position.z - this.normal.z * this.normal_depth;
        } else {
            this.object3D.position.x = position_vector.x;
            this.object3D.position.y = position_vector.y;
            this.object3D.position.z = position_vector.z;

            this.x_without_normal = this.object3D.position.x;
            this.y_without_normal = this.object3D.position.y;
            this.z_without_normal = this.object3D.position.z;
        }
    };

    this.update_position_with_offset_xyz = function(x, y, z) {
        l(this.x_without_normal);
        l(this.y_without_normal);
        l(this.z_without_normal);
        if (is_defined(this.normal_depth) && is_defined(this.normal)) {
            this.object3D.position.x = this.x_without_normal + x + this.normal.x * this.normal_depth;
            this.object3D.position.y = this.y_without_normal + y + this.normal.y * this.normal_depth;
            this.object3D.position.z = this.z_without_normal + z + this.normal.z * this.normal_depth;
        } else {
            this.object3D.position.x = this.x_without_normal + x;
            this.object3D.position.y = this.y_without_normal + y;
            this.object3D.position.z = this.z_without_normal + z;
        }
    };

    this.set_normal_depth = function(depth) {
        if (depth <= 0) {
            this.normal_depth = 1;
        } else {
            this.normal_depth = depth;
        }
    };

    this.update_normal = function(normal) {
        this.normal = new THREE.Vector3(normal.x, normal.y, normal.z);
        this.normal.normalize();
        this.object3D.lookAt(new THREE.Vector3(this.object3D.position.x + this.normal.x * 100, this.object3D.position.y + this.normal.y * 100, this.object3D.position.z + this.normal.z * 100));
    };

    this.update_position_and_normal = function(position_vector, normal) {
        this.update_normal(normal);
        this.update_position(position_vector);
    };

    this.delete_self = function() {
        // TODO : Implement this function!!!
    };

    this.clear = function() {
        this._update_text('');
    };

    /* __  ___      ___  ___     __                  __   ___  __
      /__`  |   /\   |  |__     /  ` |__|  /\  |\ | / _` |__  /__`
      .__/  |  /~~\  |  |___    \__, |  | /~~\ | \| \__> |___ .__/ */
    this.state_change_look_at = function(being_looked_at) {
        if (being_looked_at) {
            this.update_color(COLOR_HIGHLIGHT);
        } else {
            this.update_color(this.default_color);
        }
    };

    this.state_change_engage = function(being_engaged_with) {
        if (being_engaged_with) {
            if (this.type !== TYPE_BUTTON && this.type !== TYPE_CHECK_BOX) {
                CURRENT_PLAYER.engage();
            } else {
                this.being_engaged_with = false;
                CURRENT_PLAYER.disengage();
            }
        } else {
            CURRENT_PLAYER.disengage();
        }
    };
}<commit_msg>general updating 16<commit_after>'use strict';

function FloatingText(width, text, type, scene, current_color) {
    /*   __   __        __  ___  __        __  ___  __   __
        /  ` /  \ |\ | /__`  |  |__) |  | /  `  |  /  \ |__)
        \__, \__/ | \| .__/  |  |  \ \__/ \__,  |  \__/ |  \ */
    this.width         = width;
    this.type          = type;
    this.scene         = scene;
    this.object3D      = new THREE.Object3D();
    this.current_color = COLOR_DAY_PRESENT;
    this.default_color = COLOR_DAY_PRESENT;

    // Default value.
    this.normal_depth  = 1;

    if (is_defined(current_color)) {
        this.current_color = current_color;
        this.default_color = current_color;
    }
    // Convert to the correct color type.
    if (is_list(this.current_color)) {
        var temp = this.current_color;
        if (this.is_2d_text) {
            this.current_color = temp[COLOR_STRING_INDEX];
            this.default_color = temp[COLOR_STRING_INDEX];
        } else {
            this.current_color = temp[COLOR_HEX_INDEX];
            this.default_color = temp[COLOR_HEX_INDEX];
        }
    }

    //l('Current color ' + this.current_color);
    //l('Default color ' + this.current_color);

    if (this.type == TYPE_INPUT_PASSWORD) {
        this.text = '';
        for (var c = 0; c < text.length; c++) {
            this.text += '*';
        }
        this.hidden_text = text;
    } else {
        this.text = text;
    }

    // Call the child initialize functions.
    this.initialize();

    // Gets called from child functions.
    this.final_initialize = function() {
        switch (this.type) {
        case TYPE_BUTTON:
        case TYPE_CHECK_BOX:
            this.maintain_engage_when_tabbed_to = false;
            this.engable = false;
            break;
        case TYPE_CONSTANT_TEXT:
            this.engable = false;
        }
    };

    /*   ___            __  ___    __        __
        |__  |  | |\ | /  `  |  | /  \ |\ | /__`
        |    \__/ | \| \__,  |  | \__/ | \| .__/ */

    // This is just an alternative name to the function update_color.
    this.set_color = function(color) {
        this.update_color(color);
    };

    // Gets called in constructor so defining this function first.
    this.update_color = function(color) {
        if (is_list(color)) {
            var color_to_set = null;
            if (this.is_2d_text) {
                color_to_set = color[COLOR_STRING_INDEX];
            } else {
                color_to_set = color[COLOR_HEX_INDEX];
            }
            this.current_color = color_to_set;
            this._update_color();
        } else {
            this.current_color = color;
            this._update_color();
        }
    };

    this.update_text = function(text) {
        // TODO : This is currently assuming this function never gets called on a Password field.
        if (this.text !== text) {
            this.text = text;
            this._update_text();
        }
    };

    this.get_text = function() {
        if (this.type === TYPE_INPUT_PASSWORD) {
            return this.hidden_text;
        }
        return this.text;
    };

    this._add_character = function(character) {
        this.update_text(this.text + character);
    };

    this._pop_character = function() {
        this.update_text(this.text.slice(0, -1));
    };

    this.parse_keycode = function(event) {
        var keycode = event.keyCode;

        if (keycode === KEY_CODE_DELETE) {
            if (this.text.length > 0) {
                this._pop_character();
                if (this.type === TYPE_INPUT_PASSWORD) {
                    this.hidden_text = this.hidden_text.slice(0, -1);
                }
                MANAGER_AUDIO.play_typing_sound();
            }
        } else if (event.key.length === 1) {
            if (this.type === TYPE_INPUT_PASSWORD) {
                this.hidden_text += event.key;
                this._add_character('*');
            } else if (this.type === TYPE_INPUT_REGULAR) {
                this._add_character(event.key);
            }
            MANAGER_AUDIO.play_typing_sound();
        }
    };

    this.get_position = function() {
        return this.object3D.position;
    };

    this.update_position_and_look_at_origin = function(position_vector) {
        this.object3D.position.x = position_vector.x;
        this.object3D.position.y = position_vector.y;
        this.object3D.position.z = position_vector.z;
        this.normal = new THREE.Vector3(0 - position_vector.x, 0, 0 - position_vector.z);
        this.normal.normalize();
    };

    this.update_position = function(position_vector) {
        // TODO : optimize

        if (is_defined(this.normal_depth) && is_defined(this.normal)) {
            this.object3D.position.x = position_vector.x + this.normal.x * this.normal_depth;
            this.object3D.position.y = position_vector.y + this.normal.y * this.normal_depth;
            this.object3D.position.z = position_vector.z + this.normal.z * this.normal_depth;

            this.x_without_normal = this.object3D.position.x - this.normal.x * this.normal_depth;
            this.y_without_normal = this.object3D.position.y - this.normal.y * this.normal_depth;
            this.z_without_normal = this.object3D.position.z - this.normal.z * this.normal_depth;
        } else {
            this.object3D.position.x = position_vector.x;
            this.object3D.position.y = position_vector.y;
            this.object3D.position.z = position_vector.z;

            this.x_without_normal = this.object3D.position.x;
            this.y_without_normal = this.object3D.position.y;
            this.z_without_normal = this.object3D.position.z;
        }
    };

    this.update_position_with_offset_xyz = function(x, y, z) {
        // TODO : optimize

        l(this.x_without_normal);
        l(this.y_without_normal);
        l(this.z_without_normal);
        if (is_defined(this.normal_depth) && is_defined(this.normal)) {
            this.object3D.position.x = this.x_without_normal + x + this.normal.x * this.normal_depth;
            this.object3D.position.y = this.y_without_normal + y + this.normal.y * this.normal_depth;
            this.object3D.position.z = this.z_without_normal + z + this.normal.z * this.normal_depth;

            this.x_without_normal = this.object3D.position.x - this.normal.x * this.normal_depth;
            this.y_without_normal = this.object3D.position.y - this.normal.y * this.normal_depth;
            this.z_without_normal = this.object3D.position.z - this.normal.z * this.normal_depth;
        } else {
            this.object3D.position.x = this.x_without_normal + x;
            this.object3D.position.y = this.y_without_normal + y;
            this.object3D.position.z = this.z_without_normal + z;

            this.x_without_normal = this.object3D.position.x;
            this.y_without_normal = this.object3D.position.y;
            this.z_without_normal = this.object3D.position.z;
        }
    };

    this.set_normal_depth = function(depth) {
        if (depth <= 0) {
            this.normal_depth = 1;
        } else {
            this.normal_depth = depth;
        }
    };

    this.update_normal = function(normal) {
        this.normal = new THREE.Vector3(normal.x, normal.y, normal.z);
        this.normal.normalize();
        this.object3D.lookAt(new THREE.Vector3(this.object3D.position.x + this.normal.x * 100, this.object3D.position.y + this.normal.y * 100, this.object3D.position.z + this.normal.z * 100));
    };

    this.update_position_and_normal = function(position_vector, normal) {
        this.update_normal(normal);
        this.update_position(position_vector);
    };

    this.delete_self = function() {
        // TODO : Implement this function!!!
    };

    this.clear = function() {
        this._update_text('');
    };

    /* __  ___      ___  ___     __                  __   ___  __
      /__`  |   /\   |  |__     /  ` |__|  /\  |\ | / _` |__  /__`
      .__/  |  /~~\  |  |___    \__, |  | /~~\ | \| \__> |___ .__/ */
    this.state_change_look_at = function(being_looked_at) {
        if (being_looked_at) {
            this.update_color(COLOR_HIGHLIGHT);
        } else {
            this.update_color(this.default_color);
        }
    };

    this.state_change_engage = function(being_engaged_with) {
        if (being_engaged_with) {
            if (this.type !== TYPE_BUTTON && this.type !== TYPE_CHECK_BOX) {
                CURRENT_PLAYER.engage();
            } else {
                this.being_engaged_with = false;
                CURRENT_PLAYER.disengage();
            }
        } else {
            CURRENT_PLAYER.disengage();
        }
    };
}<|endoftext|>