<commit_before>(function() {

  angular
    .module('app.contentMgmt')
    .controller('ContentMgmtController', ContentMgmtController)
    .controller('CourseMapController',CourseMapController)
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    $( ".accordion1" )
                      .accordion({
                        header: "> div > #chapterHeader",
                        collapsible: true,
                        heightStyle: "content"
                      })
                      .sortable({
                        collapsible: true,
                        axis: "y",
                        handle: "#chapterHeader",
                        stop: function( event, ui ) {
                          // IE doesn't register the blur when sorting
                          // so trigger focusout handlers to remove .ui-state-focus
                          ui.item.children( "#chapterHeader" ).triggerHandler( "focusout" );

                          // Refresh accordion to handle new order
                          $( this ).accordion( "refresh" );
                        }
                      });

                   $( ".accordion2" ).sortable();

                });
            }
            $timeout(function(){
              var cid =  scope.$index;
              if(! scope.chapterAdded){
                $("#text_"+cid).hide();
              }
               $("#text_"+cid).focusout(function(){
                  $(this).hide();
              });
            });

        }
      }
    })
    .directive('toggleButton', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
          var id = scope.mcqObj.qnsID;
          $timeout(function () {

            $("#text_"+id).focusout(function(){
                  $(this).hide();
            });

            if(! scope.qnsAdded) {
              $("#text_"+id).hide();
            }
            angular.forEach(scope.mcqObj.options,function(value,key){
              $("#text_"+id+"_"+key).hide();
              $("#text_"+id+"_"+key).focusout(function(){
                  $(this).hide();
              });
            });

          });
          if (scope.$last === true) {
            $timeout(function () {
              $("#mcq").sortable();
            });
          }
        }
      }
    });

  function ContentMgmtController($http,$scope, $routeParams, $location,$firebaseArray,$mdDialog, $firebaseObject,contentMgmtService) {
	  console.log("ContentMgmtController");
    if($routeParams.qid!=null) {
        var qid = $routeParams.qid;
        $scope.qid = qid;
        $scope.isNewQuestion = false;
        var question = contentMgmtService.getQuestion(qid);
        question.$loaded().then(function() {
          var answer = contentMgmtService.getAnswerKey(qid)
          answer.$loaded().then(function(answerKey){
            if(answerKey && question.qnsType==='mcq') {
              angular.forEach(question.mcq, function(value, key) {
                var ans = answerKey.answer[key];
                value.answer = ans;
              });
            }
            question.qid = question.$id;
            question.cid = $routeParams.cid;
            $scope.qns = question;
          });
        })
        .catch(function(error) {
          console.error("Error:", error);
        });

    }else {
      //"/educator/slides_create/C0"
      var path = $location.$$path;
      path = path.substr(path.indexOf('/educator/'),path.indexOf('_create'));
      var qnsType= path.substr(path.lastIndexOf('/')+1);
      $scope.isNewQuestion = true;
      $scope.qns = {qnsTitle:" ",qnsType:qnsType,cid:$routeParams.cid}
      if(qnsType === "slides"){
        $scope.qns['slides'] = [];
      }else if (qnsType === "video"){
        $scope.qns['qnsDescription'] = "";
        $scope.qns['qnsInstruction'] = "";
        $scope.qns['link'] = "";
      }else if (qnsType === "mcq"){
        $scope.qns['mcq'] = [];
        $scope.qns['hint'] = "";
        $scope.qns['qnsInstruction'] = [];
      }
    }

    $scope.saveQns = function(ev) {
      var confirm = $mdDialog.confirm()
            .title('Would you want to save all changes?')
            .textContent('This question will be saved to what you configured, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Please do it!')
            .cancel('Cancel!');

      $mdDialog.show(confirm).then(function() {
        if($scope.qns.qnsType == "video"){
          contentMgmtService.updateVideoQuestion($scope.qns,$scope.isNewQuestion).then(function(){
            window.location.href = "#/educator/courseMap"
          });
        }else if ($scope.qns.qnsType == "slides") {
          contentMgmtService.updateSlideQuestion($scope.qns,$scope.isNewQuestion).then(function(){
            window.location.href = "#/educator/courseMap"
          });
        }
      });

    }

    $scope.deleteSlide = function(index){
      $scope.qns.slides.splice(index,1);
    }

    $scope.addSlide = function(){
      $scope.qns.slides.push({explanation:" ",imageLink:" "});
    }

    $scope.toggleQns = function(id) {
      $("#text_"+id).toggle();
    }

    $scope.toggleChoice = function(id,index) {
      $("#text_"+id+"_"+index).toggle();
    }

    $scope.addChoice = function(mcq_id) {
      var length = $scope.qns.mcq[mcq_id].options.length;
      $scope.qns.mcq[mcq_id].options.push("Choice "+ (length+1));

      $("#text_"+mcq_id+"_"+length).hide();
    }

    $scope.saveMCQChanges = function(ev) {
    // Appending dialog to document.body to cover sidenav in docs app
      var confirm = $mdDialog.confirm()
            .title('Would you want to save all changes?')
            .textContent('This question will be saved to what you configured, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Please do it!')
            .cancel('Cancel!');

      $mdDialog.show(confirm).then(function() {

        var listToUpdate = [];
        var qns = $scope.qns;
        var mcqList = qns.mcq;
        var qids = $("#mcq").find('strong');
        // updating the question sequence
          for(i=0;i<qids.length;i++) {
              var qid =qids[i].innerText.replace('.','');
              $.each(mcqList,function(index, value){
                if(value.qnsID === qid) {
                  listToUpdate.push(mcqList[index]);
                  return false;
                }
              });
          }
          $scope.qns.mcq = listToUpdate;

          contentMgmtService.updateMCQ($scope.qns,$scope.isNewQuestion).then(function(){
            window.location.reload();
          });
        }, function() {
          // cancel function
        });

    };

    $scope.deleteChoice = function(mcq_id,index){
      $scope.qns.mcq[mcq_id].options.splice(index,1);
    }

    $scope.deleteMcq = function(index){
      $scope.qns.mcq.splice(index,1);
    }

    $scope.addMcq = function() {
      var qnsID = "Q"+($scope.qns.mcq.length+1);
      $scope.qns.mcq.push({options:[],qns:"",qnsID:qnsID});
      $scope.qnsAdded = true;
    }

    $scope.deleteQns = function(ev,cid,qid) {
      var confirm = $mdDialog.confirm()
            .title('Do you really want to DELETE this question?')
            .textContent('This question will deleted, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Delete it now!')
            .cancel('Cancel');

      $mdDialog.show(confirm).then(function() {
        contentMgmtService.deleteQuestion(cid,qid).then(function(){
         window.location.href = "#/educator/courseMap"
         //window.location.reload();
        });
      }, function() {
        // cancel function
      });
    }

    $scope.deleteChap = function(ev,cid) {
      var confirm = $mdDialog.confirm()
            .title('Do you really want to DELETE this question?')
            .textContent('This question will deleted, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Delete it now!')
            .cancel('Cancel');

      $mdDialog.show(confirm).then(function() {
        contentMgmtService.deleteChapter(cid).then(function(){
          window.location.reload();
        });
      }, function() {
        // cancel function
      });
    }
  }

  function CourseMapController($http,$scope, $routeParams,$mdDialog, $location, $firebaseObject, contentMgmtService) {
    $scope.chapTBD = [];
    $scope.qnsTBD = [];
    $scope.chapters = [];
    $scope.qnsTypes = ["Video","Slides","MCQ","Excel","Code"];
    var courseMap = contentMgmtService.getCourseSeq();
    courseMap.$loaded().then(function(){
      var seq = [];
      for(i=0;i<courseMap.length;i++) {
        seq.push(courseMap[i]);
        $scope.chapters.push({cid:courseMap[i].cid,chapterTitle:courseMap[i].chapterTitle});
      }

      $scope.courseMap = seq;
    });

    $scope.showPrompt = function(ev) {
    // Appending dialog to document.body to cover sidenav in docs app
      var parentEl = angular.element(document.body);
       $mdDialog.show({
         parent: parentEl,
         targetEvent: ev,
         template:

          '<md-dialog style="padding:20px">' +
          '
Options to create question:

'+
           '  <md-dialog-content>'+
           '  <md-input-container style="width:500px;height:auto;">'+
           '    Select Chapter to put question in. '+
           '    <md-select ng-model="selectedChapter" required>'+
           '      <md-option ng-repeat="item in chapters" value="{{item.cid}}">'+
           '       {{item.chapterTitle}}' +
           '      '+
           '    </md-option></md-select></md-input-container>
'+
           '  <md-input-container style="width:500px;height:auto;">'+
           '    Select question Type. '+
           '    <md-select ng-model="selectedQnsType" required>'+
           '      <md-option ng-repeat="item in qnsTypes" value="{{item}}">'+
           '       {{item}}' +
           '      '+
           '    </md-option></md-select>'+
           '  </md-input-container>'+
           '  </md-dialog-content>' +
           '  <md-dialog-actions>' +
           '    <md-button ng-click="closeDialog()" class="md-primary">' +
           '      Close' +
           '    </md-button>' +
           '    <md-button ng-click="nextStep()" class="md-primary">' +
           '      Proceed' +
           '    </md-button>' +
           '  </md-dialog-actions>' +
           '</md-dialog>',
         locals: {
           chapters: $scope.chapters,
           qnsTypes:$scope.qnsTypes
         },
         controller: DialogController
      });
      function DialogController($scope, $mdDialog,chapters,qnsTypes) {
        $scope.chapters = chapters;
        $scope.selectedChapter = '';
        $scope.selectedQnsType = '';
        $scope.qnsTypes = qnsTypes;
        $scope.closeDialog = function() {
          $mdDialog.hide();
        }
        $scope.nextStep = function() {
          $scope.selectedQnsType = $scope.selectedQnsType.toLowerCase();

          $location.path('educator/'+$scope.selectedQnsType+'_create/'+$scope.selectedChapter);
          $mdDialog.hide();
        }
      }
    };

    $scope.editQuestion = function(qid,cid) {
      var question = contentMgmtService.getQuestion(qid);
      question.$loaded().then(function() {
        $location.path('educator/'+question.qnsType+'_edit/'+cid+'/'+qid);
      })
      .catch(function(error) {
        console.error("Error:", error);
      });
    }

    $scope.addChapter = function(){
      $scope.courseMap.push({chapterTitle:"",});
      $("#text_"+($scope.courseMap.length-1)).show();
      $scope.chapterAdded = true;
      //window.scrollTo(0,document.body.scrollHeight);
      $('html, body').animate({scrollTop:$(document).height()}, 'slow');
    }

    $scope.saveAllChanges = function(ev) {

      var confirm = $mdDialog.confirm()
            .title('Would you want to save all changes?')
            .textContent('System will be saved to what you configured, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Please do it!')
            .cancel('Cancel!');

      $mdDialog.show(confirm).then(function() {

        var courseSequence = [];
        var chap ={};
        var qlist =[];
        var qns ={};
        $( "div#chapter" ).each(function( index, value ) {
          var c = $(this).find('h2.cid');
          console.log(index + ":" + $(this).attr('id'));
          var cid = c.attr('cid');
          var title = c.text().trim();
          chap['cid'] = cid;
          chap['chapterTitle']=title;
          var qElements = $(this).find('h4.question');

          // all question here
          for(i=0;i<qElements.length;i++) {
            var obj = qElements[i];
            console.log("questionid: "+ obj.id);
            qns['qid']=obj.id;
            qns['qnsTitle']= obj.textContent;
            qns['qnsType']= obj.getAttribute("qnsType");
            qlist.push(qns);
            qns ={};
          }

          chap['qns']=qlist;
          courseSequence.push(chap);
          chap={};
          qlist=[];

        });

        contentMgmtService.deleteQuestionFromCM($scope.qnsTBD);
        contentMgmtService.deleteChapter($scope.chapTBD).then(function(){
          contentMgmtService.updateEntireSeq(courseSequence).then(function() {
            window.location.href = "#/educator/courseMap"
          });
        });

        //$location.path('/educator/courseMap');
      });
    }

    $scope.deleteChapter = function(index,cid){
      $scope.courseMap.splice(index,1);
      $scope.chapTBD.push(cid);
    }

    $scope.deleteQuestion = function(chapterIndex,index,qid){
      $scope.courseMap[chapterIndex].qns.splice(index,1);
      $scope.qnsTBD.push(qid);
    }

    $scope.toggleChapterTextbox = function(id) {
      $("#text_"+id).toggle();
    }

  }

})();
<commit_msg>redirect fix<commit_after>(function() {

  angular
    .module('app.contentMgmt')
    .controller('ContentMgmtController', ContentMgmtController)
    .controller('CourseMapController',CourseMapController)
    .directive('onFinishRender', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
            if (scope.$last === true) {
                $timeout(function () {
                    $( ".accordion1" )
                      .accordion({
                        header: "> div > #chapterHeader",
                        collapsible: true,
                        heightStyle: "content"
                      })
                      .sortable({
                        collapsible: true,
                        axis: "y",
                        handle: "#chapterHeader",
                        stop: function( event, ui ) {
                          // IE doesn't register the blur when sorting
                          // so trigger focusout handlers to remove .ui-state-focus
                          ui.item.children( "#chapterHeader" ).triggerHandler( "focusout" );

                          // Refresh accordion to handle new order
                          $( this ).accordion( "refresh" );
                        }
                      });

                   $( ".accordion2" ).sortable();

                });
            }
            $timeout(function(){
              var cid =  scope.$index;
              if(! scope.chapterAdded){
                $("#text_"+cid).hide();
              }
               $("#text_"+cid).focusout(function(){
                  $(this).hide();
              });
            });

        }
      }
    })
    .directive('toggleButton', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attr) {
          var id = scope.mcqObj.qnsID;
          $timeout(function () {

            $("#text_"+id).focusout(function(){
                  $(this).hide();
            });

            if(! scope.qnsAdded) {
              $("#text_"+id).hide();
            }
            angular.forEach(scope.mcqObj.options,function(value,key){
              $("#text_"+id+"_"+key).hide();
              $("#text_"+id+"_"+key).focusout(function(){
                  $(this).hide();
              });
            });

          });
          if (scope.$last === true) {
            $timeout(function () {
              $("#mcq").sortable();
            });
          }
        }
      }
    });

  function ContentMgmtController($http,$scope, $routeParams, $location,$firebaseArray,$mdDialog, $firebaseObject,contentMgmtService) {
	  console.log("ContentMgmtController");
    if($routeParams.qid!=null) {
        var qid = $routeParams.qid;
        $scope.qid = qid;
        $scope.isNewQuestion = false;
        var question = contentMgmtService.getQuestion(qid);
        question.$loaded().then(function() {
          var answer = contentMgmtService.getAnswerKey(qid)
          answer.$loaded().then(function(answerKey){
            if(answerKey && question.qnsType==='mcq') {
              angular.forEach(question.mcq, function(value, key) {
                var ans = answerKey.answer[key];
                value.answer = ans;
              });
            }
            question.qid = question.$id;
            question.cid = $routeParams.cid;
            $scope.qns = question;
          });
        })
        .catch(function(error) {
          console.error("Error:", error);
        });

    }else {
      //"/educator/slides_create/C0"
      var path = $location.$$path;
      path = path.substr(path.indexOf('/educator/'),path.indexOf('_create'));
      var qnsType= path.substr(path.lastIndexOf('/')+1);
      $scope.isNewQuestion = true;
      $scope.qns = {qnsTitle:" ",qnsType:qnsType,cid:$routeParams.cid}
      if(qnsType === "slides"){
        $scope.qns['slides'] = [];
      }else if (qnsType === "video"){
        $scope.qns['qnsDescription'] = "";
        $scope.qns['qnsInstruction'] = "";
        $scope.qns['link'] = "";
      }else if (qnsType === "mcq"){
        $scope.qns['mcq'] = [];
        $scope.qns['hint'] = "";
        $scope.qns['qnsInstruction'] = [];
      }
    }

    $scope.saveQns = function(ev) {
      var confirm = $mdDialog.confirm()
            .title('Would you want to save all changes?')
            .textContent('This question will be saved to what you configured, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Please do it!')
            .cancel('Cancel!');

      $mdDialog.show(confirm).then(function() {
        if($scope.qns.qnsType == "video"){
          contentMgmtService.updateVideoQuestion($scope.qns,$scope.isNewQuestion).then(function(){
            window.location.href = "#/educator/courseMap"
          });
        }else if ($scope.qns.qnsType == "slides") {
          contentMgmtService.updateSlideQuestion($scope.qns,$scope.isNewQuestion).then(function(){
            window.location.href = "#/educator/courseMap"
          });
        }
      });

    }

    $scope.deleteSlide = function(index){
      $scope.qns.slides.splice(index,1);
    }

    $scope.addSlide = function(){
      $scope.qns.slides.push({explanation:" ",imageLink:" "});
    }

    $scope.toggleQns = function(id) {
      $("#text_"+id).toggle();
    }

    $scope.toggleChoice = function(id,index) {
      $("#text_"+id+"_"+index).toggle();
    }

    $scope.addChoice = function(mcq_id) {
      var length = $scope.qns.mcq[mcq_id].options.length;
      $scope.qns.mcq[mcq_id].options.push("Choice "+ (length+1));

      $("#text_"+mcq_id+"_"+length).hide();
    }

    $scope.saveMCQChanges = function(ev) {
    // Appending dialog to document.body to cover sidenav in docs app
      var confirm = $mdDialog.confirm()
            .title('Would you want to save all changes?')
            .textContent('This question will be saved to what you configured, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Please do it!')
            .cancel('Cancel!');

      $mdDialog.show(confirm).then(function() {

        var listToUpdate = [];
        var qns = $scope.qns;
        var mcqList = qns.mcq;
        var qids = $("#mcq").find('strong');
        // updating the question sequence
          for(i=0;i<qids.length;i++) {
              var qid =qids[i].innerText.replace('.','');
              $.each(mcqList,function(index, value){
                if(value.qnsID === qid) {
                  listToUpdate.push(mcqList[index]);
                  return false;
                }
              });
          }
          $scope.qns.mcq = listToUpdate;

          contentMgmtService.updateMCQ($scope.qns,$scope.isNewQuestion).then(function(){
            window.location.href = "#/educator/courseMap"
          });
        }, function() {
          // cancel function
        });

    };

    $scope.deleteChoice = function(mcq_id,index){
      $scope.qns.mcq[mcq_id].options.splice(index,1);
    }

    $scope.deleteMcq = function(index){
      $scope.qns.mcq.splice(index,1);
    }

    $scope.addMcq = function() {
      var qnsID = "Q"+($scope.qns.mcq.length+1);
      $scope.qns.mcq.push({options:[],qns:"",qnsID:qnsID});
      $scope.qnsAdded = true;
    }

    $scope.deleteQns = function(ev,cid,qid) {
      var confirm = $mdDialog.confirm()
            .title('Do you really want to DELETE this question?')
            .textContent('This question will deleted, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Delete it now!')
            .cancel('Cancel');

      $mdDialog.show(confirm).then(function() {
        contentMgmtService.deleteQuestion(cid,qid).then(function(){
         window.location.href = "#/educator/courseMap"
         //window.location.reload();
        });
      }, function() {
        // cancel function
      });
    }

    $scope.deleteChap = function(ev,cid) {
      var confirm = $mdDialog.confirm()
            .title('Do you really want to DELETE this question?')
            .textContent('This question will deleted, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Delete it now!')
            .cancel('Cancel');

      $mdDialog.show(confirm).then(function() {
        contentMgmtService.deleteChapter(cid).then(function(){
          window.location.reload();
        });
      }, function() {
        // cancel function
      });
    }
  }

  function CourseMapController($http,$scope, $routeParams,$mdDialog, $location, $firebaseObject, contentMgmtService) {
    $scope.chapTBD = [];
    $scope.qnsTBD = [];
    $scope.chapters = [];
    $scope.qnsTypes = ["Video","Slides","MCQ","Excel","Code"];
    var courseMap = contentMgmtService.getCourseSeq();
    courseMap.$loaded().then(function(){
      var seq = [];
      for(i=0;i<courseMap.length;i++) {
        seq.push(courseMap[i]);
        $scope.chapters.push({cid:courseMap[i].cid,chapterTitle:courseMap[i].chapterTitle});
      }

      $scope.courseMap = seq;
    });

    $scope.showPrompt = function(ev) {
    // Appending dialog to document.body to cover sidenav in docs app
      var parentEl = angular.element(document.body);
       $mdDialog.show({
         parent: parentEl,
         targetEvent: ev,
         template:

          '<md-dialog style="padding:20px">' +
          '
Options to create question:

'+
           '  <md-dialog-content>'+
           '  <md-input-container style="width:500px;height:auto;">'+
           '    Select Chapter to put question in. '+
           '    <md-select ng-model="selectedChapter" required>'+
           '      <md-option ng-repeat="item in chapters" value="{{item.cid}}">'+
           '       {{item.chapterTitle}}' +
           '      '+
           '    </md-option></md-select></md-input-container>
'+
           '  <md-input-container style="width:500px;height:auto;">'+
           '    Select question Type. '+
           '    <md-select ng-model="selectedQnsType" required>'+
           '      <md-option ng-repeat="item in qnsTypes" value="{{item}}">'+
           '       {{item}}' +
           '      '+
           '    </md-option></md-select>'+
           '  </md-input-container>'+
           '  </md-dialog-content>' +
           '  <md-dialog-actions>' +
           '    <md-button ng-click="closeDialog()" class="md-primary">' +
           '      Close' +
           '    </md-button>' +
           '    <md-button ng-click="nextStep()" class="md-primary">' +
           '      Proceed' +
           '    </md-button>' +
           '  </md-dialog-actions>' +
           '</md-dialog>',
         locals: {
           chapters: $scope.chapters,
           qnsTypes:$scope.qnsTypes
         },
         controller: DialogController
      });
      function DialogController($scope, $mdDialog,chapters,qnsTypes) {
        $scope.chapters = chapters;
        $scope.selectedChapter = '';
        $scope.selectedQnsType = '';
        $scope.qnsTypes = qnsTypes;
        $scope.closeDialog = function() {
          $mdDialog.hide();
        }
        $scope.nextStep = function() {
          $scope.selectedQnsType = $scope.selectedQnsType.toLowerCase();

          $location.path('educator/'+$scope.selectedQnsType+'_create/'+$scope.selectedChapter);
          $mdDialog.hide();
        }
      }
    };

    $scope.editQuestion = function(qid,cid) {
      var question = contentMgmtService.getQuestion(qid);
      question.$loaded().then(function() {
        $location.path('educator/'+question.qnsType+'_edit/'+cid+'/'+qid);
      })
      .catch(function(error) {
        console.error("Error:", error);
      });
    }

    $scope.addChapter = function(){
      $scope.courseMap.push({chapterTitle:"",});
      $("#text_"+($scope.courseMap.length-1)).show();
      $scope.chapterAdded = true;
      //window.scrollTo(0,document.body.scrollHeight);
      $('html, body').animate({scrollTop:$(document).height()}, 'slow');
    }

    $scope.saveAllChanges = function(ev) {

      var confirm = $mdDialog.confirm()
            .title('Would you want to save all changes?')
            .textContent('System will be saved to what you configured, is it ok to proceed?')
            .targetEvent(ev)
            .ok('Please do it!')
            .cancel('Cancel!');

      $mdDialog.show(confirm).then(function() {

        var courseSequence = [];
        var chap ={};
        var qlist =[];
        var qns ={};
        $( "div#chapter" ).each(function( index, value ) {
          var c = $(this).find('h2.cid');
          console.log(index + ":" + $(this).attr('id'));
          var cid = c.attr('cid');
          var title = c.text().trim();
          chap['cid'] = cid;
          chap['chapterTitle']=title;
          var qElements = $(this).find('h4.question');

          // all question here
          for(i=0;i<qElements.length;i++) {
            var obj = qElements[i];
            console.log("questionid: "+ obj.id);
            qns['qid']=obj.id;
            qns['qnsTitle']= obj.textContent;
            qns['qnsType']= obj.getAttribute("qnsType");
            qlist.push(qns);
            qns ={};
          }

          chap['qns']=qlist;
          courseSequence.push(chap);
          chap={};
          qlist=[];

        });

        contentMgmtService.deleteQuestionFromCM($scope.qnsTBD);
        contentMgmtService.deleteChapter($scope.chapTBD).then(function(){
          contentMgmtService.updateEntireSeq(courseSequence).then(function() {
            window.location.href = "#/educator/courseMap"
          });
        });

        //$location.path('/educator/courseMap');
      });
    }

    $scope.deleteChapter = function(index,cid){
      $scope.courseMap.splice(index,1);
      $scope.chapTBD.push(cid);
    }

    $scope.deleteQuestion = function(chapterIndex,index,qid){
      $scope.courseMap[chapterIndex].qns.splice(index,1);
      $scope.qnsTBD.push(qid);
    }

    $scope.toggleChapterTextbox = function(id) {
      $("#text_"+id).toggle();
    }

  }

})();
<|endoftext|>


<commit_before>// InlineEditor
// =========================================================================================================
// Replaces an element with an editable version of itself
//
// Usage:
//   new InlineEditor(url, element, editor_writer)
//     url            the URL where edited materials will be posted to using the HTTP verb PUT
//     element        the HTML element which contains the information to be replaced with an inline editor
//     editor_writer  a function that creates an inline editor from the element
//
// Events:
//   after_init       passes the element to observers before the editor is created
//   before_init      passes the element and the editor to observers after the editor is created and added to the DOM
//   close            passes the element and the editor to observers just before the editor is hidden and the element restored
//
//
var InlineEditor = (function() {
  var CURRENT_ELEMENT = null;
  var CURRENT_EDITOR = null;
  var observer = new Observer();

  var constructor = function(url, element, editor_writer) {
    element = $(element); if(!element) return;

    element.edit_inline = function() {

      // close any existing editors
      InlineEditor.close();

      // before_init callback
      // TODO: can return false to cancel the edit?
      observer.fire('before_init', element);

      // Create editor
      var editor = editor_writer(element); if(!editor) {return}

      // Hide the view-only element
      // element.hide();
      element.addClassName('in-edit');

      // Insert the editor
      element.insert({'after':editor});

      // Save the contents of the editor...
      editor.save = function(callback) {
        // var params = Form.serialize(editor);
        var params = editor.up('form').serialize();
        FT.xhr(url, 'put', params, {
          onSuccess: function() {
            if(CURRENT_EDITOR == editor) { InlineEditor.close(); }
          },
          onComplete: function(response) {
            if((response.status != 400) && callback) {
              callback();
            }
          }
        });
      }

      // ...on clicking a submit button
      var submit = editor.down('*[type="submit"]');
      if(submit) {
        submit.observe('click', function(event) {
          Event.stop(event);
          editor.save();
        });
      }

      // ...or on hitting the Return key
      editor.observe('keydown', function(e) {
        // window.console.log('kd: ' + e.which);
        if(e.keyCode == Event.KEY_RETURN) {
          Event.stop(e);
          editor.save();
        }
        if(e.keyCode == Event.KEY_UP) {
          observer.fire('up', [e, element, editor]);
        }
        if(e.keyCode == Event.KEY_DOWN) {
          observer.fire('down', [e, element, editor]);
        }
      });

      // after_init callback
      observer.fire('after_init', [element, editor]);

      // Finally, select the first Form element in the editor
      var first_input = editor.down('input, select, textarea');
      try { if(first_input) { first_input.activate(); } } catch(e) {}

      // Remember the row being edited
      CURRENT_ELEMENT = element;
      CURRENT_EDITOR = editor;
    }

    // Edit the row when it is clicked
    element.observe('click', function(event) {

      // Ignore if a link or button was clicked
      if(!Event.findElement(event, 'input, button, a')) {
        element.edit_inline();
      }
    });
  };

  constructor.close = function() {
    if(CURRENT_ELEMENT || CURRENT_EDITOR) {
      observer.fire('close', [CURRENT_ELEMENT, CURRENT_EDITOR]);
      if(CURRENT_ELEMENT) CURRENT_ELEMENT.removeClassName('in-edit');
      if(CURRENT_EDITOR)  CURRENT_EDITOR.remove();
      CURRENT_ELEMENT     = null;
      CURRENT_EDITOR      = null;
    }
  };
  constructor.observe = function(name,func){ observer.observe(name, func); };
  constructor.unobserve = function(name,func){ observer.unobserve(name, func); };

  // Close InlineEditors when the ESC key is pressed
  document.observe('dom:loaded', function() {
    $(document.body).observe('keyup', function(event) {
      if(event.keyCode==Event.KEY_ESC) {
        InlineEditor.close();
      }
    });
  });

  return constructor;
})();<commit_msg>by listening to keydown rather than keyup, InlineEditor doesn't close if you're escaping out of an autocomplete<commit_after>// InlineEditor
// =========================================================================================================
// Replaces an element with an editable version of itself
//
// Usage:
//   new InlineEditor(url, element, editor_writer)
//     url            the URL where edited materials will be posted to using the HTTP verb PUT
//     element        the HTML element which contains the information to be replaced with an inline editor
//     editor_writer  a function that creates an inline editor from the element
//
// Events:
//   after_init       passes the element to observers before the editor is created
//   before_init      passes the element and the editor to observers after the editor is created and added to the DOM
//   close            passes the element and the editor to observers just before the editor is hidden and the element restored
//
//
var InlineEditor = (function() {
  var CURRENT_ELEMENT = null;
  var CURRENT_EDITOR = null;
  var observer = new Observer();

  var constructor = function(url, element, editor_writer) {
    element = $(element); if(!element) return;

    element.edit_inline = function() {

      // close any existing editors
      InlineEditor.close();

      // before_init callback
      // TODO: can return false to cancel the edit?
      observer.fire('before_init', element);

      // Create editor
      var editor = editor_writer(element); if(!editor) {return}

      // Hide the view-only element
      // element.hide();
      element.addClassName('in-edit');

      // Insert the editor
      element.insert({'after':editor});

      // Save the contents of the editor...
      editor.save = function(callback) {
        // var params = Form.serialize(editor);
        var params = editor.up('form').serialize();
        FT.xhr(url, 'put', params, {
          onSuccess: function() {
            if(CURRENT_EDITOR == editor) { InlineEditor.close(); }
          },
          onComplete: function(response) {
            if((response.status != 400) && callback) {
              callback();
            }
          }
        });
      }

      // ...on clicking a submit button
      var submit = editor.down('*[type="submit"]');
      if(submit) {
        submit.observe('click', function(event) {
          Event.stop(event);
          editor.save();
        });
      }

      // ...or on hitting the Return key
      editor.observe('keydown', function(e) {
        // window.console.log('kd: ' + e.which);
        if(e.keyCode == Event.KEY_RETURN) {
          Event.stop(e);
          editor.save();
        }
        if(e.keyCode == Event.KEY_UP) {
          observer.fire('up', [e, element, editor]);
        }
        if(e.keyCode == Event.KEY_DOWN) {
          observer.fire('down', [e, element, editor]);
        }
      });

      // after_init callback
      observer.fire('after_init', [element, editor]);

      // Finally, select the first Form element in the editor
      var first_input = editor.down('input, select, textarea');
      try { if(first_input) { first_input.activate(); } } catch(e) {}

      // Remember the row being edited
      CURRENT_ELEMENT = element;
      CURRENT_EDITOR = editor;
    }

    // Edit the row when it is clicked
    element.observe('click', function(event) {

      // Ignore if a link or button was clicked
      if(!Event.findElement(event, 'input, button, a')) {
        element.edit_inline();
      }
    });
  };

  constructor.close = function() {
    if(CURRENT_ELEMENT || CURRENT_EDITOR) {
      observer.fire('close', [CURRENT_ELEMENT, CURRENT_EDITOR]);
      if(CURRENT_ELEMENT) CURRENT_ELEMENT.removeClassName('in-edit');
      if(CURRENT_EDITOR)  CURRENT_EDITOR.remove();
      CURRENT_ELEMENT     = null;
      CURRENT_EDITOR      = null;
    }
  };
  constructor.observe = function(name,func){ observer.observe(name, func); };
  constructor.unobserve = function(name,func){ observer.unobserve(name, func); };

  // Close InlineEditors when the ESC key is pressed
  document.observe('dom:loaded', function() {
    $(document.body).observe('keydown', function(event) {
      if(event.keyCode==Event.KEY_ESC) {
        InlineEditor.close();
      }
    });
  });

  return constructor;
})();<|endoftext|>


<commit_before>var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
/**
 * Minify and push build to gh-pages branch
 */
gulp.task('deploy', ['build'], function () {
  return gulp.src('./dist/app/**/*')
    /* This is to ensure that we work from Firebase in production */
    .pipe($.if('*.js', $.replace('var localData = true;', 'var localData = false;')))
    .pipe($.if('*.js', $.uglify({ mangle: false }))) // Minify JavaScript
    .pipe($.if('*.js', $.rev()))
    .pipe($.if('*.css', $.cleanCss())) // Minify CSS
    .pipe($.if('*.css', $.rev()))
    .pipe($.revReplace())
    .pipe($.deploy());
});
<commit_msg>gulp fix<commit_after>var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
/**
 * Minify and push build to gh-pages branch
 */
gulp.task('deploy', ['build'], function () {
  return gulp.src('./dist/app/**/*')
    /* This is to ensure that we work from Firebase in production */
    .pipe($.if('*.js', $.replace('var localData = true;', 'var localData = false;')))
    .pipe($.if('*.js', $.uglify({ mangle: false }))) // Minify JavaScript
    .pipe($.if('*.js', $.rev()))
    .pipe($.if('*.css', $.cleanCss())) // Minify CSS
    .pipe($.if('*.css', $.rev()))
    .pipe($.revReplace())
    .pipe($.ghPages());
});
<|endoftext|>


<commit_before>/****************************************************************
 * On any platform including Travis-CI
 ****************************************************************/
var seleniumArgs = {};
var capabilities = [{
    // maxInstances can get overwritten per capability. So if you have an in-house Selenium
    // grid with only 5 firefox instance available you can make sure that not more than
    // 5 instance gets started at a time.
    maxInstances: 5,
    // The following are the default drivers installed with selenium-standalone
    // browserName: 'chrome'
    // browserName: 'firefox' // gecko
    // browserName: 'internet explorer'
    // The following driver is installed with phantomjs-brebuilt
    browserName: 'phantomjs'
}];
/****************************************************************
 * On our Windows environment
 ****************************************************************/
if (/^win/.test(process.platform)) {
    var path = require('path');
    seleniumArgs = {
        // Drivers can be downloaded at http://docs.seleniumhq.org/download/
        javaArgs: [
            // Add Microsoft Edge driver
            '-Dwebdriver.edge.driver=' + path.join(__dirname, './test/bin/MicrosoftWebDriver.exe')
            // Add opera driver
            // '-Dwebdriver.opera.driver=' + path.join(__dirname, './test/bin/operadriver.exe')
        ]
        // For other opts, see https://github.com/vvo/selenium-standalone/blob/master/lib/start.js#L22
        // seleniumArgs: [],
        // version
        // spawnCb
        // drivers
        // basePath
        // javaPath
    };
    capabilities = [
        {
            maxInstances: 1,
            browserName: 'chrome'
        },
        {
            maxInstances: 1,
            browserName: 'firefox',
            // @see https://github.com/vvo/selenium-standalone/issues/237
            // @see http://stackoverflow.com/questions/38125581/how-to-enable-a-firefox-extension-when-testing-with-selenium-webdriverio
            marionette: true
        },
        {
            maxInstances: 1,
            browserName: 'internet explorer'
        },
        {
            maxInstances: 1,
            browserName: 'MicrosoftEdge'
        },
        {
            maxInstances: 1,
            browserName: 'phantomjs',
            // Without the path, phantomJS is not found on Windows
            'phantomjs.binary.path': 'C:\\Program Files (x86)\\PhantomJS\\bin\\phantomjs.EXE'
            // 'phantomjs.binary.path': path.join(__dirname, './node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs.exe')
        }
        /*
        {
            maxInstances: 1,
            browserName: 'operablink'
            operaOptions: {
                // binary: 'C:\\Program Files (x86)\\Opera\\launcher.exe'
                binary: 'C:\\Program Files (x86)\\Opera\\42.0.2393.94\\opera.exe'
            }
        }
         */

    ];
}

exports.config = {
    // =====================
    // Server Configurations
    // =====================
    // Host address of the running Selenium server. This information is usually obsolete as
    // WebdriverIO automatically connects to localhost. Also, if you are using one of the
    // supported cloud services like Sauce Labs, Browserstack, or Testing Bot you don't
    // need to define host and port information because WebdriverIO can figure that out
    // according to your user and key information. However, if you are using a private Selenium
    // backend you should define the host address, port, and path here.
    //
    // host: '0.0.0.0',
    // port: 4444,
    // path: '/wd/hub',
    //
    // =================
    // Service Providers
    // =================
    // WebdriverIO supports Sauce Labs, Browserstack, and Testing Bot (other cloud providers
    // should work too though). These services define specific user and key (or access key)
    // values you need to put in here in order to connect to these services.
    //
    // user: 'webdriverio',
    // key:  'xxxxxxxxxxxxxxxx-xxxxxx-xxxxx-xxxxxxxxx',
    //
    // ==================
    // Specify Test Files
    // ==================
    // Define which test specs should run. The pattern is relative to the directory
    // from which `wdio` was called. Notice that, if you are calling `wdio` from an
    // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
    // directory is where your package.json resides, so `wdio` will be called from there.
    //
    specs: [
        './test/selenium/**/*.js'
    ],
    // Patterns to exclude.
    exclude: [
        // 'path/to/excluded/files'
    ],
    //
    // ============
    // Capabilities
    // ============
    // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
    // time. Depending on the number of capabilities, WebdriverIO launches several test
    // sessions. Within your capabilities you can overwrite the spec and exclude options in
    // order to group specific specs to a specific capability.
    //
    // First, you can define how many instances should be started at the same time. Let's
    // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
    // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
    // files and you set maxInstances to 10, all spec files will get tested at the same time
    // and 30 processes will get spawned. The property handles how many capabilities
    // from the same test should run tests.
    //
    maxInstances: 10,
    //
    // If you have trouble getting all important capabilities together, check out the
    // Sauce Labs platform configurator - a great tool to configure your capabilities:
    // https://docs.saucelabs.com/reference/platforms-configurator
    //
    capabilities: capabilities, // See above
    //
    // When enabled opens a debug port for node-inspector and pauses execution
    // on `debugger` statements. The node-inspector can be attached with:
    // `node-inspector --debug-port 5859 --no-preload`
    // When debugging it is also recommended to change the timeout interval of
    // test runner (eg. jasmineNodeOpts.defaultTimeoutInterval) to a very high
    // value and setting maxInstances to 1.
    // debug: false,
    //
    // Additional list node arguments to use when starting child processes
    // execArgv: null,
    //
    // ===================
    // Test Configurations
    // ===================
    // Define all options that are relevant for the WebdriverIO instance here
    //
    // By default WebdriverIO commands are executed in a synchronous way using
    // the wdio-sync package. If you still want to run your tests in an async way
    // e.g. using promises you can set the sync option to false.
    sync: true,
    //
    // Level of logging verbosity: silent | verbose | command | data | result | error
    logLevel: 'verbose',
    //
    // Enables colors for log output.
    coloredLogs: true,
    //
    // Saves a screenshot to a given path if a command fails.
    screenshotPath: './errorShots/',
    //
    // Set a base URL in order to shorten url command calls. If your url parameter starts
    // with "/", then the base url gets prepended.
    baseUrl: 'http://localhost:3000',
    //
    // Default timeout for all waitFor* commands.
    waitforTimeout: 10000,
    //
    // Default timeout in milliseconds for request
    // if Selenium Grid doesn't send response
    connectionRetryTimeout: 90000,
    //
    // Default request retries count
    connectionRetryCount: 3,
    //
    // Initialize the browser instance with a WebdriverIO plugin. The object should have the
    // plugin name as key and the desired plugin options as properties. Make sure you have
    // the plugin installed before running any tests. The following plugins are currently
    // available:
    // WebdriverCSS: https://github.com/webdriverio/webdrivercss
    // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
    // Browserevent: https://github.com/webdriverio/browserevent
    // plugins: {
    //     webdrivercss: {
    //         screenshotRoot: 'my-shots',
    //         failedComparisonsRoot: 'diffs',
    //         misMatchTolerance: 0.05,
    //         screenWidth: [320,480,640,1024]
    //     },
    //     webdriverrtc: {},
    //     browserevent: {}
    // },
    //
    // Test runner services
    // Services take over a specific job you don't want to take care of. They enhance
    // your test setup with almost no effort. Unlike plugins, they don't add new
    // commands. Instead, they hook themselves up into the test process.
    // services: [],//
    services: ['selenium-standalone'],
    // selenium-standalone configuration
    // @see http://webdriver.io/guide/services/selenium-standalone.html
    // @see https://www.npmjs.com/package/selenium-standalone
    seleniumArgs: seleniumArgs,
    // Framework you want to run your specs with.
    // The following are supported: Mocha, Jasmine, and Cucumber
    // see also: http://webdriver.io/guide/testrunner/frameworks.html
    //
    // Make sure you have the wdio adapter package for the specific framework installed
    // before running any tests.
    framework: 'mocha',
    //
    // Test reporter for stdout.
    // The only one supported by default is 'dot'
    // see also: http://webdriver.io/guide/testrunner/reporters.html
    // reporters: ['dot'],
    //
    // Options to be passed to Mocha.
    // See the full list at http://mochajs.org/
    mochaOpts: {
        ui: 'bdd'
    },
    //
    // =====
    // Hooks
    // =====
    // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
    // it and to build services around it. You can either apply a single function or an array of
    // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
    // resolved to continue.
    //
    // Gets executed once before all workers get launched.
    onPrepare: function (config, capabilities) {
        var app = require('./webapp/server'); // Start the web application
    }
    //
    // Gets executed before test execution begins. At this point you can access all global
    // variables, such as `browser`. It is the perfect place to define custom commands.
    // before: function (capabilities, specs) {
    // },
    //
    // Hook that gets executed before the suite starts
    // beforeSuite: function (suite) {
    // },
    //
    // Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
    // beforeEach in Mocha)
    // beforeHook: function () {
    // },
    //
    // Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
    // afterEach in Mocha)
    // afterHook: function () {
    // },
    //
    // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
    // beforeTest: function (test) {
    // },
    //
    // Runs before a WebdriverIO command gets executed.
    // beforeCommand: function (commandName, args) {
    // },
    //
    // Runs after a WebdriverIO command gets executed
    // afterCommand: function (commandName, args, result, error) {
    // },
    //
    // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
    // afterTest: function (test) {
    // },
    //
    // Hook that gets executed after the suite has ended
    // afterSuite: function (suite) {
    // },
    //
    // Gets executed after all tests are done. You still have access to all global variables from
    // the test.
    // after: function (result, capabilities, specs) {
    // },
    //
    // Gets executed after all workers got shut down and the process is about to exit. It is not
    // possible to defer the end of the process using a promise.
    // onComplete: function(exitCode) {
    // }
};
<commit_msg>Added opera browser configuration.<commit_after>/****************************************************************
 * On any platform including Travis-CI
 ****************************************************************/
var seleniumArgs = {};
var capabilities = [{
    // maxInstances can get overwritten per capability. So if you have an in-house Selenium
    // grid with only 5 firefox instance available you can make sure that not more than
    // 5 instance gets started at a time.
    maxInstances: 5,
    // The following are the default drivers installed with selenium-standalone
    // browserName: 'chrome'
    // browserName: 'firefox' // gecko
    // browserName: 'internet explorer'
    // The following driver is installed with phantomjs-brebuilt
    browserName: 'phantomjs'
}];
/****************************************************************
 * On our Windows environment
 ****************************************************************/
if (/^win/.test(process.platform)) {
    var path = require('path');
    seleniumArgs = {
        // Drivers can be downloaded at http://docs.seleniumhq.org/download/
        javaArgs: [
            // Add Microsoft Edge driver
            '-Dwebdriver.edge.driver=' + path.join(__dirname, './test/bin/MicrosoftWebDriver.exe'),
            // Add opera driver
            // '-Dwebdriver.opera.driver=' + path.join(__dirname, './test/bin/operadriver.exe')
            '-Dwebdriver.opera.driver=' + path.join(__dirname, './node_modules/selenium-standalone/.selenium/chromedriver/2.25-x64-chromedriver')
        ]
        // For other opts, see https://github.com/vvo/selenium-standalone/blob/master/lib/start.js#L22
        // seleniumArgs: [],
        // version
        // spawnCb
        // drivers
        // basePath
        // javaPath
    };
    capabilities = [
        // @see https://medium.com/@jlchereau/how-to-configure-webdrivier-io-with-selenium-standalone-and-additional-browsers-9369d38bc4d1
        {
            maxInstances: 1,
            browserName: 'chrome'
        },
        {
            maxInstances: 1,
            browserName: 'firefox',
            // @see https://github.com/vvo/selenium-standalone/issues/237
            // @see http://stackoverflow.com/questions/38125581/how-to-enable-a-firefox-extension-when-testing-with-selenium-webdriverio
            marionette: true
        },
        {
            maxInstances: 1,
            browserName: 'internet explorer'
        },
        {
            maxInstances: 1,
            browserName: 'MicrosoftEdge'
        },
        {
            maxInstances: 1,
            browserName: 'phantomjs',
            // Without the path, phantomJS is not found on Windows
            'phantomjs.binary.path': 'C:\\Program Files (x86)\\PhantomJS\\bin\\phantomjs.EXE'
            // 'phantomjs.binary.path': path.join(__dirname, './node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs.exe')
        },
        {
            maxInstances: 1,
            browserName: 'operablink',
            chromeOptions: {
                args: [],
                extensions: [],
                // binary: 'C:\\Program Files (x86)\\Opera\\launcher.exe'
                binary: 'C:\\Program Files (x86)\\Opera\\42.0.2393.94\\opera.exe'
            }
        }
    ];
}

exports.config = {
    // =====================
    // Server Configurations
    // =====================
    // Host address of the running Selenium server. This information is usually obsolete as
    // WebdriverIO automatically connects to localhost. Also, if you are using one of the
    // supported cloud services like Sauce Labs, Browserstack, or Testing Bot you don't
    // need to define host and port information because WebdriverIO can figure that out
    // according to your user and key information. However, if you are using a private Selenium
    // backend you should define the host address, port, and path here.
    //
    // host: '0.0.0.0',
    // port: 4444,
    // path: '/wd/hub',
    //
    // =================
    // Service Providers
    // =================
    // WebdriverIO supports Sauce Labs, Browserstack, and Testing Bot (other cloud providers
    // should work too though). These services define specific user and key (or access key)
    // values you need to put in here in order to connect to these services.
    //
    // user: 'webdriverio',
    // key:  'xxxxxxxxxxxxxxxx-xxxxxx-xxxxx-xxxxxxxxx',
    //
    // ==================
    // Specify Test Files
    // ==================
    // Define which test specs should run. The pattern is relative to the directory
    // from which `wdio` was called. Notice that, if you are calling `wdio` from an
    // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
    // directory is where your package.json resides, so `wdio` will be called from there.
    //
    specs: [
        './test/selenium/**/*.js'
    ],
    // Patterns to exclude.
    exclude: [
        // 'path/to/excluded/files'
    ],
    //
    // ============
    // Capabilities
    // ============
    // Define your capabilities here. WebdriverIO can run multiple capabilities at the same
    // time. Depending on the number of capabilities, WebdriverIO launches several test
    // sessions. Within your capabilities you can overwrite the spec and exclude options in
    // order to group specific specs to a specific capability.
    //
    // First, you can define how many instances should be started at the same time. Let's
    // say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
    // set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
    // files and you set maxInstances to 10, all spec files will get tested at the same time
    // and 30 processes will get spawned. The property handles how many capabilities
    // from the same test should run tests.
    //
    maxInstances: 10,
    //
    // If you have trouble getting all important capabilities together, check out the
    // Sauce Labs platform configurator - a great tool to configure your capabilities:
    // https://docs.saucelabs.com/reference/platforms-configurator
    //
    capabilities: capabilities, // See above
    //
    // When enabled opens a debug port for node-inspector and pauses execution
    // on `debugger` statements. The node-inspector can be attached with:
    // `node-inspector --debug-port 5859 --no-preload`
    // When debugging it is also recommended to change the timeout interval of
    // test runner (eg. jasmineNodeOpts.defaultTimeoutInterval) to a very high
    // value and setting maxInstances to 1.
    // debug: false,
    //
    // Additional list node arguments to use when starting child processes
    // execArgv: null,
    //
    // ===================
    // Test Configurations
    // ===================
    // Define all options that are relevant for the WebdriverIO instance here
    //
    // By default WebdriverIO commands are executed in a synchronous way using
    // the wdio-sync package. If you still want to run your tests in an async way
    // e.g. using promises you can set the sync option to false.
    sync: true,
    //
    // Level of logging verbosity: silent | verbose | command | data | result | error
    logLevel: 'verbose',
    //
    // Enables colors for log output.
    coloredLogs: true,
    //
    // Saves a screenshot to a given path if a command fails.
    screenshotPath: './errorShots/',
    //
    // Set a base URL in order to shorten url command calls. If your url parameter starts
    // with "/", then the base url gets prepended.
    baseUrl: 'http://localhost:3000',
    //
    // Default timeout for all waitFor* commands.
    waitforTimeout: 10000,
    //
    // Default timeout in milliseconds for request
    // if Selenium Grid doesn't send response
    connectionRetryTimeout: 90000,
    //
    // Default request retries count
    connectionRetryCount: 3,
    //
    // Initialize the browser instance with a WebdriverIO plugin. The object should have the
    // plugin name as key and the desired plugin options as properties. Make sure you have
    // the plugin installed before running any tests. The following plugins are currently
    // available:
    // WebdriverCSS: https://github.com/webdriverio/webdrivercss
    // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
    // Browserevent: https://github.com/webdriverio/browserevent
    // plugins: {
    //     webdrivercss: {
    //         screenshotRoot: 'my-shots',
    //         failedComparisonsRoot: 'diffs',
    //         misMatchTolerance: 0.05,
    //         screenWidth: [320,480,640,1024]
    //     },
    //     webdriverrtc: {},
    //     browserevent: {}
    // },
    //
    // Test runner services
    // Services take over a specific job you don't want to take care of. They enhance
    // your test setup with almost no effort. Unlike plugins, they don't add new
    // commands. Instead, they hook themselves up into the test process.
    // services: [],//
    services: ['selenium-standalone'],
    // selenium-standalone configuration
    // @see http://webdriver.io/guide/services/selenium-standalone.html
    // @see https://www.npmjs.com/package/selenium-standalone
    seleniumArgs: seleniumArgs,
    // Framework you want to run your specs with.
    // The following are supported: Mocha, Jasmine, and Cucumber
    // see also: http://webdriver.io/guide/testrunner/frameworks.html
    //
    // Make sure you have the wdio adapter package for the specific framework installed
    // before running any tests.
    framework: 'mocha',
    //
    // Test reporter for stdout.
    // The only one supported by default is 'dot'
    // see also: http://webdriver.io/guide/testrunner/reporters.html
    // reporters: ['dot'],
    //
    // Options to be passed to Mocha.
    // See the full list at http://mochajs.org/
    mochaOpts: {
        ui: 'bdd'
    },
    //
    // =====
    // Hooks
    // =====
    // WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
    // it and to build services around it. You can either apply a single function or an array of
    // methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
    // resolved to continue.
    //
    // Gets executed once before all workers get launched.
    onPrepare: function (config, capabilities) {
        var app = require('./webapp/server'); // Start the web application
    }
    //
    // Gets executed before test execution begins. At this point you can access all global
    // variables, such as `browser`. It is the perfect place to define custom commands.
    // before: function (capabilities, specs) {
    // },
    //
    // Hook that gets executed before the suite starts
    // beforeSuite: function (suite) {
    // },
    //
    // Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
    // beforeEach in Mocha)
    // beforeHook: function () {
    // },
    //
    // Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
    // afterEach in Mocha)
    // afterHook: function () {
    // },
    //
    // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
    // beforeTest: function (test) {
    // },
    //
    // Runs before a WebdriverIO command gets executed.
    // beforeCommand: function (commandName, args) {
    // },
    //
    // Runs after a WebdriverIO command gets executed
    // afterCommand: function (commandName, args, result, error) {
    // },
    //
    // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
    // afterTest: function (test) {
    // },
    //
    // Hook that gets executed after the suite has ended
    // afterSuite: function (suite) {
    // },
    //
    // Gets executed after all tests are done. You still have access to all global variables from
    // the test.
    // after: function (result, capabilities, specs) {
    // },
    //
    // Gets executed after all workers got shut down and the process is about to exit. It is not
    // possible to defer the end of the process using a promise.
    // onComplete: function(exitCode) {
    // }
};
<|endoftext|>


<commit_before>import 'dotenv/config';

const bundleId = 'gov.dts.ugrc.utahwvcr';

const buildNumber = 520;

export default {
  name: 'WVC Reporter',
  slug: 'wildlife-vehicle-collision-reporter',
  description: 'A mobile application for reporting and removing animal carcasses.',
  // this needs to be different from the bundleId, otherwise android presents two options for redirect
  // Ref: https://forums.expo.dev/t/app-appears-twice-on-open-with-list-after-google-auth/55659/6?u=agrc
  scheme: bundleId.split('.').pop(),
  facebookScheme: `fb${process.env.FACEBOOK_OAUTH_CLIENT_ID}`,
  facebookAppId: process.env.FACEBOOK_OAUTH_CLIENT_ID,
  facebookDisplayName: 'Utah WVC Reporter',
  githubUrl: 'https://github.com/agrc/roadkill-mobile',
  version: '3.0.0',
  orientation: 'portrait',
  icon: './assets/icon.png',
  splash: {
    image: './assets/splash.png',
    resizeMode: 'contain',
    backgroundColor: '#ffffff',
  },
  updates: {
    fallbackToCacheTimeout: 0,
  },
  assetBundlePatterns: ['**/*'],
  ios: {
    bundleIdentifier: bundleId,
    googleServicesFile: process.env.GOOGLE_SERVICES_IOS,
    buildNumber: buildNumber.toString(),
    supportsTablet: true,
    config: {
      usesNonExemptEncryption: false,
      googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY_IOS,
    },
    infoPlist: {
      NSLocationAlwaysUsageDescription:
        'The app uses your location in the background for tracking routes. *Note* this is only applicable for agency employees or contractors. Background location is not used for public users.',
      NSLocationWhenInUseUsageDescription:
        'The app uses your location to help record the location of the animal that you are reporting.',
    },
  },
  android: {
    package: bundleId,
    googleServicesFile: process.env.GOOGLE_SERVICES_ANDROID,
    versionCode: buildNumber,
    softwareKeyboardLayoutMode: 'pan',
    adaptiveIcon: {
      foregroundImage: './assets/adaptive-icon.png',
      backgroundColor: '#FFFFFF',
    },
    permissions: [
      'ACCESS_FINE_LOCATION',
      'ACCESS_COARSE_LOCATION',
      'FOREGROUND_SERVICE',
      'CAMERA',
      'READ_EXTERNAL_STORAGE',
      'WRITE_EXTERNAL_STORAGE',
    ],
    config: {
      googleMaps: {
        apiKey: process.env.GOOGLE_MAPS_API_KEY_ANDROID,
      },
    },
  },
  // this is only to enable logEvent calls during development in Expo Go
  // ref: https://docs.expo.io/versions/latest/sdk/firebase-analytics/#expo-go-limitations--configuration
  web: {
    config: {
      firebase: {
        apiKey: process.env.GOOGLE_MAPS_API_KEY_ANDROID ?? '',
        measurementId: process.env.FIREBASE_MEASUREMENT_ID ?? 'G-XXXXXXXXXX',
      },
    },
  },
  hooks: {
    postPublish: [
      {
        file: 'sentry-expo/upload-sourcemaps',
        config: {
          organization: 'utah-agrc',
          project: 'roadkill',
          authToken: process.env.SENTRY_AUTH_TOKEN,
        },
      },
    ],
  },
  plugins: ['sentry-expo', 'expo-community-flipper'],
};
<commit_msg>chore: bump build number<commit_after>import 'dotenv/config';

const bundleId = 'gov.dts.ugrc.utahwvcr';

const buildNumber = 521;

export default {
  name: 'WVC Reporter',
  slug: 'wildlife-vehicle-collision-reporter',
  description: 'A mobile application for reporting and removing animal carcasses.',
  // this needs to be different from the bundleId, otherwise android presents two options for redirect
  // Ref: https://forums.expo.dev/t/app-appears-twice-on-open-with-list-after-google-auth/55659/6?u=agrc
  scheme: bundleId.split('.').pop(),
  facebookScheme: `fb${process.env.FACEBOOK_OAUTH_CLIENT_ID}`,
  facebookAppId: process.env.FACEBOOK_OAUTH_CLIENT_ID,
  facebookDisplayName: 'Utah WVC Reporter',
  githubUrl: 'https://github.com/agrc/roadkill-mobile',
  version: '3.0.0',
  orientation: 'portrait',
  icon: './assets/icon.png',
  splash: {
    image: './assets/splash.png',
    resizeMode: 'contain',
    backgroundColor: '#ffffff',
  },
  updates: {
    fallbackToCacheTimeout: 0,
  },
  assetBundlePatterns: ['**/*'],
  ios: {
    bundleIdentifier: bundleId,
    googleServicesFile: process.env.GOOGLE_SERVICES_IOS,
    buildNumber: buildNumber.toString(),
    supportsTablet: true,
    config: {
      usesNonExemptEncryption: false,
      googleMapsApiKey: process.env.GOOGLE_MAPS_API_KEY_IOS,
    },
    infoPlist: {
      NSLocationAlwaysUsageDescription:
        'The app uses your location in the background for tracking routes. *Note* this is only applicable for agency employees or contractors. Background location is not used for public users.',
      NSLocationWhenInUseUsageDescription:
        'The app uses your location to help record the location of the animal that you are reporting.',
    },
  },
  android: {
    package: bundleId,
    googleServicesFile: process.env.GOOGLE_SERVICES_ANDROID,
    versionCode: buildNumber,
    softwareKeyboardLayoutMode: 'pan',
    adaptiveIcon: {
      foregroundImage: './assets/adaptive-icon.png',
      backgroundColor: '#FFFFFF',
    },
    permissions: [
      'ACCESS_FINE_LOCATION',
      'ACCESS_COARSE_LOCATION',
      'FOREGROUND_SERVICE',
      'CAMERA',
      'READ_EXTERNAL_STORAGE',
      'WRITE_EXTERNAL_STORAGE',
    ],
    config: {
      googleMaps: {
        apiKey: process.env.GOOGLE_MAPS_API_KEY_ANDROID,
      },
    },
  },
  // this is only to enable logEvent calls during development in Expo Go
  // ref: https://docs.expo.io/versions/latest/sdk/firebase-analytics/#expo-go-limitations--configuration
  web: {
    config: {
      firebase: {
        apiKey: process.env.GOOGLE_MAPS_API_KEY_ANDROID ?? '',
        measurementId: process.env.FIREBASE_MEASUREMENT_ID ?? 'G-XXXXXXXXXX',
      },
    },
  },
  hooks: {
    postPublish: [
      {
        file: 'sentry-expo/upload-sourcemaps',
        config: {
          organization: 'utah-agrc',
          project: 'roadkill',
          authToken: process.env.SENTRY_AUTH_TOKEN,
        },
      },
    ],
  },
  plugins: ['sentry-expo', 'expo-community-flipper'],
};
<|endoftext|>


<commit_before>/**
 * modules/analytics data store: profiles.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';

// Actions
const FETCH_CREATE_PROFILE = 'FETCH_CREATE_PROFILE';
const FETCH_PROFILES = 'FETCH_PROFILES';
const RECEIVE_CREATE_PROFILE = 'RECEIVE_CREATE_PROFILE';
const RECEIVE_CREATE_PROFILE_FAILED = 'RECEIVE_CREATE_PROFILE_FAILED';
const RECEIVE_PROFILES = 'RECEIVE_PROFILES';
const RECEIVE_PROFILES_COMPLETED = 'RECEIVE_PROFILES_COMPLETED';
const RECEIVE_PROFILES_FAILED = 'RECEIVE_PROFILES_FAILED';
// Values
export const PROFILE_CREATE = 'profile_create';

export const INITIAL_STATE = {
	isFetchingCreateProfile: {},
	isFetchingProfiles: {},
	profiles: {},
};

export const actions = {
	/**
	 * Creates a new Analytics profile.
	 *
	 * Creates a new Analytics profile for an existing Google Analytics
	 * account + property combination.
	 *
	 * @since n.e.x.t
	 *
	 * @param {string} accountID  Google Analytics account ID.
	 * @param {string} propertyID Google Analytics property ID.
	 * @return {Function} Generator function action.
	 */
	*createProfile( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		try {
			const profile = yield actions.fetchCreateProfile( accountID, propertyID );

			yield actions.receiveCreateProfile( { accountID, propertyID, profile } );
			yield Data.dispatch( STORE_NAME ).setProfileID( profile.id );
			return;
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreateProfileFailed( { accountID, error, propertyID } );
		}
	},

	fetchCreateProfile( accountID, propertyID ) {
		return {
			payload: { accountID, propertyID },
			type: FETCH_CREATE_PROFILE,
		};
	},

	fetchProfiles( accountID, propertyID ) {
		return {
			payload: { accountID, propertyID },
			type: FETCH_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics profile ID.
	 * @param {Object} args.profile    Google Analytics profile object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfile( { accountID, propertyID, profile } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( profile, 'profile is required.' );

		return {
			payload: { accountID, propertyID, profile },
			type: RECEIVE_CREATE_PROFILE,
		};
	},

	/**
	 * Logs an error with profile creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics property ID.
	 * @param {Object} args.error      Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfileFailed( { accountID, propertyID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_CREATE_PROFILE_FAILED,
		};
	},

	receiveProfiles( profiles ) {
		invariant( Array.isArray( profiles ), 'profiles must be an array.' );

		return {
			payload: { profiles },
			type: RECEIVE_PROFILES,
		};
	},

	receiveProfilesCompleted( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, propertyID },
			type: RECEIVE_PROFILES_COMPLETED,
		};
	},

	receiveProfilesFailed( { accountID, error, propertyID } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROFILE ]: ( { payload: { accountID, propertyID } } ) => {
		return API.set( 'modules', 'analytics', 'create-profile', {
			accountID,
			propertyID,
		} );
	},
	[ FETCH_PROFILES ]: ( { payload: { accountID, propertyID } } ) => {
		return API.get( 'modules', 'analytics', 'profiles', {
			accountID,
			propertyID,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROFILE: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case FETCH_PROFILES: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROFILE: {
			const { accountID, propertyID, profile } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
				profiles: {
					...state.profiles,
					[ `${ accountID }::${ propertyID }` ]: [
						...( state.profiles[ `${ accountID }::${ propertyID }` ] || [] ),
						profile,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROFILE_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES: {
			const { profiles } = payload;

			return {
				...state,
				profiles: {
					...state.profiles,
					...groupBy( profiles, ( { accountId, webPropertyId } ) => `${ accountId }::${ webPropertyId }` ), // Capitalization rule exception: `accountId` and `webPropertyId` are properties of an API returned value.
				},
			};
		}

		case RECEIVE_PROFILES_COMPLETED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProfiles( accountID, propertyID ) {
		if ( typeof accountID === 'undefined' || typeof propertyID === 'undefined' ) {
			return undefined;
		}
		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingProfiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID );

			// If there are already profiles loaded in state for this request; consider it fulfilled
			// and don't make an API request.
			if ( existingProfiles ) {
				return;
			}

			const profiles = yield actions.fetchProfiles( accountID, propertyID );

			yield actions.receiveProfiles( profiles );

			return actions.receiveProfilesCompleted( accountID, propertyID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveProfilesFailed( { accountID, propertyID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Get all Google Analytics profiles this user account+property has available.
	 *
	 * Returns an array of all profiles.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to fetch profiles for.
	 * @param {string} propertyID The Analytics Property ID to fetch profiles for.
	 * @return {?Array.<Object>} An array of Analytics profiles; `undefined` if not loaded.
	 */
	getProfiles( state, accountID, propertyID ) {
		if ( typeof accountID === 'undefined' || typeof propertyID === 'undefined' ) {
			return undefined;
		}
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		const { profiles } = state;

		return profiles[ `${ accountID }::${ propertyID }` ];
	},

	/**
	 * Check if a profile is being created for an account and property.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to check for profile creation.
	 * @param {string} propertyID The Analytics Property ID to check for profile creation.
	 * @return {boolean} `true` if creating a profile, `false` if not.
	 */
	isDoingCreateProfile( state, accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		const { isFetchingCreateProfile } = state;

		return !! isFetchingCreateProfile[ `${ accountID }::${ propertyID }` ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Bypass getProfiles resolver if IDs are invalid.<commit_after>/**
 * modules/analytics data store: profiles.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { isValidAccountID, isValidPropertyID } from '../util';

// Actions
const FETCH_CREATE_PROFILE = 'FETCH_CREATE_PROFILE';
const FETCH_PROFILES = 'FETCH_PROFILES';
const RECEIVE_CREATE_PROFILE = 'RECEIVE_CREATE_PROFILE';
const RECEIVE_CREATE_PROFILE_FAILED = 'RECEIVE_CREATE_PROFILE_FAILED';
const RECEIVE_PROFILES = 'RECEIVE_PROFILES';
const RECEIVE_PROFILES_COMPLETED = 'RECEIVE_PROFILES_COMPLETED';
const RECEIVE_PROFILES_FAILED = 'RECEIVE_PROFILES_FAILED';
// Values
export const PROFILE_CREATE = 'profile_create';

export const INITIAL_STATE = {
	isFetchingCreateProfile: {},
	isFetchingProfiles: {},
	profiles: {},
};

export const actions = {
	/**
	 * Creates a new Analytics profile.
	 *
	 * Creates a new Analytics profile for an existing Google Analytics
	 * account + property combination.
	 *
	 * @since n.e.x.t
	 *
	 * @param {string} accountID  Google Analytics account ID.
	 * @param {string} propertyID Google Analytics property ID.
	 * @return {Function} Generator function action.
	 */
	*createProfile( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		try {
			const profile = yield actions.fetchCreateProfile( accountID, propertyID );

			yield actions.receiveCreateProfile( { accountID, propertyID, profile } );
			yield Data.dispatch( STORE_NAME ).setProfileID( profile.id );
			return;
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreateProfileFailed( { accountID, error, propertyID } );
		}
	},

	fetchCreateProfile( accountID, propertyID ) {
		return {
			payload: { accountID, propertyID },
			type: FETCH_CREATE_PROFILE,
		};
	},

	fetchProfiles( accountID, propertyID ) {
		return {
			payload: { accountID, propertyID },
			type: FETCH_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics profile ID.
	 * @param {Object} args.profile    Google Analytics profile object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfile( { accountID, propertyID, profile } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( profile, 'profile is required.' );

		return {
			payload: { accountID, propertyID, profile },
			type: RECEIVE_CREATE_PROFILE,
		};
	},

	/**
	 * Logs an error with profile creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args            Argument params.
	 * @param {string} args.accountID  Google Analytics account ID.
	 * @param {string} args.propertyID Google Analytics property ID.
	 * @param {Object} args.error      Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProfileFailed( { accountID, propertyID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_CREATE_PROFILE_FAILED,
		};
	},

	receiveProfiles( profiles ) {
		invariant( Array.isArray( profiles ), 'profiles must be an array.' );

		return {
			payload: { profiles },
			type: RECEIVE_PROFILES,
		};
	},

	receiveProfilesCompleted( accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, propertyID },
			type: RECEIVE_PROFILES_COMPLETED,
		};
	},

	receiveProfilesFailed( { accountID, error, propertyID } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );
		invariant( propertyID, 'propertyID is required.' );

		return {
			payload: { accountID, error, propertyID },
			type: RECEIVE_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROFILE ]: ( { payload: { accountID, propertyID } } ) => {
		return API.set( 'modules', 'analytics', 'create-profile', {
			accountID,
			propertyID,
		} );
	},
	[ FETCH_PROFILES ]: ( { payload: { accountID, propertyID } } ) => {
		return API.get( 'modules', 'analytics', 'profiles', {
			accountID,
			propertyID,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROFILE: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case FETCH_PROFILES: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROFILE: {
			const { accountID, propertyID, profile } = payload;

			return {
				...state,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
				profiles: {
					...state.profiles,
					[ `${ accountID }::${ propertyID }` ]: [
						...( state.profiles[ `${ accountID }::${ propertyID }` ] || [] ),
						profile,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROFILE_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingCreateProfile: {
					...state.isFetchingCreateProfile,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES: {
			const { profiles } = payload;

			return {
				...state,
				profiles: {
					...state.profiles,
					...groupBy( profiles, ( { accountId, webPropertyId } ) => `${ accountId }::${ webPropertyId }` ), // Capitalization rule exception: `accountId` and `webPropertyId` are properties of an API returned value.
				},
			};
		}

		case RECEIVE_PROFILES_COMPLETED: {
			const { accountID, propertyID } = payload;

			return {
				...state,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		case RECEIVE_PROFILES_FAILED: {
			const { accountID, error, propertyID } = payload;

			return {
				...state,
				error,
				isFetchingProfiles: {
					...state.isFetchingProfiles,
					[ `${ accountID }::${ propertyID }` ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProfiles( accountID, propertyID ) {
		if ( ! isValidAccountID( accountID ) || ! isValidPropertyID( propertyID ) ) {
			return;
		}

		try {
			const registry = yield Data.commonActions.getRegistry();

			const existingProfiles = registry.select( STORE_NAME ).getProfiles( accountID, propertyID );

			// If there are already profiles loaded in state for this request; consider it fulfilled
			// and don't make an API request.
			if ( existingProfiles ) {
				return;
			}

			const profiles = yield actions.fetchProfiles( accountID, propertyID );

			yield actions.receiveProfiles( profiles );

			return actions.receiveProfilesCompleted( accountID, propertyID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveProfilesFailed( { accountID, propertyID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Get all Google Analytics profiles this user account+property has available.
	 *
	 * Returns an array of all profiles.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to fetch profiles for.
	 * @param {string} propertyID The Analytics Property ID to fetch profiles for.
	 * @return {?Array.<Object>} An array of Analytics profiles; `undefined` if not loaded.
	 */
	getProfiles( state, accountID, propertyID ) {
		if ( typeof accountID === 'undefined' || typeof propertyID === 'undefined' ) {
			return undefined;
		}
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		const { profiles } = state;

		return profiles[ `${ accountID }::${ propertyID }` ];
	},

	/**
	 * Check if a profile is being created for an account and property.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state      Data store's state.
	 * @param {string} accountID  The Analytics Account ID to check for profile creation.
	 * @param {string} propertyID The Analytics Property ID to check for profile creation.
	 * @return {boolean} `true` if creating a profile, `false` if not.
	 */
	isDoingCreateProfile( state, accountID, propertyID ) {
		invariant( accountID, 'accountID is required.' );
		invariant( propertyID, 'propertyID is required.' );

		const { isFetchingCreateProfile } = state;

		return !! isFetchingCreateProfile[ `${ accountID }::${ propertyID }` ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before>/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
import gulp from 'gulp';
import eslint from 'gulp-eslint';
import config from '../config';

gulp.task('eslint', () => gulp.src(config.files.js)
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError()));
<commit_msg>fix(eslint): add gulp folder<commit_after>/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
import gulp from 'gulp';
import eslint from 'gulp-eslint';
import config from '../config';

gulp.task('eslint', () => gulp.src([].concat(config.files.js, ['./gulp/**/*.js']))
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failAfterError()));
<|endoftext|>


<commit_before>Teikei.module("User", function(User, App, Backbone, Marionette, $, _) {

  User.MenuView = Marionette.View.extend({

    el: "#user",

    ui: {
      signInToggle: "#signin",
      signUpToggle: "#signup",
      currentUserMenuItem: "#current_user",
      participateMenuItem: "#participate",
      newEntryMenuItem: "#new-entry",
      myEntriesMenuItem: "#my-entries"
    },

    events: {
      "click #signin": "toggleAuth",
      "click #signup": "onSignUp",
      "click #add-farm": "addFarm",
      "click #add-depot": "addDepot",
      "click #my-entries": "showEntryList",
      "click #participate-depot": "onParticipateDepot",
      "click #participate-farm": "onParticipateFarm"
    },

    initialize: function() {
      this.bindUIElements();
      if (this.model.get("loggedIn")){
        this.onSignIn();
      }
      this.updateUserName();
      App.vent.on("user:signin:success", this.onSignIn, this);
      App.vent.on("user:logout:success", this.onLogout, this);
    },

    toggleAuth: function(event) {
      event.preventDefault();
      var loggedIn = this.model.get("loggedIn");
      if (!loggedIn) {
        this.trigger("signin:selected");
      }
      else {
        this.trigger("logout:selected");
      }
    },

    addFarm: function(event) {
      event.preventDefault();
      App.vent.trigger("user:add:farm");
    },

    addDepot: function(event) {
      event.preventDefault();
      App.vent.trigger("user:add:depot");
    },

    showEntryList: function() {
      event.preventDefault();
      App.vent.trigger("user:show:entrylist");
    },

    onParticipateDepot: function(event) {
      event.preventDefault();
      App.vent.trigger("participate:for:citizens");
    },

    onParticipateFarm: function(event) {
      event.preventDefault();
      App.vent.trigger("participate:for:farmers");
    },

    onSignUp: function(event) {
      var loggedIn = this.model.get("loggedIn");
      if (!loggedIn) {
        event.preventDefault();
        this.trigger("signup:selected");
      }
    },

    onSignIn: function() {
      this.ui.signInToggle.text("Abmelden");
      this.ui.signInToggle.attr("href", "/users/sign_out");
      this.ui.signUpToggle.text("Konto anpassen");
      this.ui.signUpToggle.attr("href", "/users/edit");
      this.updateUserName();
      this.ui.participateMenuItem.hide();
      this.ui.newEntryMenuItem.show();
      this.ui.myEntriesMenuItem.show();
    },

    onLogout: function() {
      this.ui.signInToggle.text("Anmelden");
      this.ui.signInToggle.attr("href", "/users/sign_in");
      this.ui.signUpToggle.text("Registrieren");
      this.ui.signUpToggle.attr("href", "/users/sign_up");
      this.updateUserName();
      this.ui.participateMenuItem.show();
      this.ui.newEntryMenuItem.hide();
      this.ui.myEntriesMenuItem.hide();
    },

    updateUserName: function() {
      userName = this.model.get("name");
      this.ui.currentUserMenuItem.parent().show();
      if (userName === null || userName === undefined) {
        userName = "";
        this.ui.currentUserMenuItem.parent().hide();
      }
      this.ui.currentUserMenuItem.text(userName);
    }

  });
});
<commit_msg>Refactor MenuView to use tokenIsPresent() instead of loggedIn flag.<commit_after>Teikei.module("User", function(User, App, Backbone, Marionette, $, _) {

  User.MenuView = Marionette.View.extend({

    el: "#user",

    ui: {
      signInToggle: "#signin",
      signUpToggle: "#signup",
      currentUserMenuItem: "#current_user",
      participateMenuItem: "#participate",
      newEntryMenuItem: "#new-entry",
      myEntriesMenuItem: "#my-entries"
    },

    events: {
      "click #signin": "toggleAuth",
      "click #signup": "onSignUp",
      "click #add-farm": "addFarm",
      "click #add-depot": "addDepot",
      "click #my-entries": "showEntryList",
      "click #participate-depot": "onParticipateDepot",
      "click #participate-farm": "onParticipateFarm"
    },

    initialize: function() {
      this.bindUIElements();
      this.invalidate();
      App.vent.on("user:signin:success", this.invalidate, this);
      App.vent.on("user:logout:success", this.invalidate, this);
    },

    toggleAuth: function(event) {
      event.preventDefault();
      signedIn = this.model.tokenIsPresent();
      if (!signedIn) {
        this.trigger("signin:selected");
      }
      else {
        this.trigger("logout:selected");
      }
    },

    invalidate: function() {
      signedIn = this.model.tokenIsPresent();
      if (signedIn) {
        userName = this.model.get("name");
        this.renderSignedInState(userName);
      }
      else {
        this.renderSignedOutState();
      }
      console.log(" ");
    },

    addFarm: function(event) {
      event.preventDefault();
      App.vent.trigger("user:add:farm");
    },

    addDepot: function(event) {
      event.preventDefault();
      App.vent.trigger("user:add:depot");
    },

    showEntryList: function() {
      event.preventDefault();
      App.vent.trigger("user:show:entrylist");
    },

    onParticipateDepot: function(event) {
      event.preventDefault();
      App.vent.trigger("participate:for:citizens");
    },

    onParticipateFarm: function(event) {
      event.preventDefault();
      App.vent.trigger("participate:for:farmers");
    },

    onSignUp: function(event) {
      var loggedIn = this.model.get("loggedIn");
      if (!loggedIn) {
        event.preventDefault();
        this.trigger("signup:selected");
      }
    },

    renderSignedInState: function(userName) {
      this.ui.signInToggle.text("Abmelden");
      this.ui.signInToggle.attr("href", "/users/sign_out");
      this.ui.signUpToggle.text("Konto anpassen");
      this.ui.signUpToggle.attr("href", "/users/edit");
      this.ui.participateMenuItem.hide();
      this.ui.newEntryMenuItem.show();
      this.ui.myEntriesMenuItem.show();
      this.ui.currentUserMenuItem.text(userName);
      this.ui.currentUserMenuItem.parent().show();
    },

    renderSignedOutState: function() {
      this.ui.signInToggle.text("Anmelden");
      this.ui.signInToggle.attr("href", "/users/sign_in");
      this.ui.signUpToggle.text("Registrieren");
      this.ui.signUpToggle.attr("href", "/users/sign_up");
      this.ui.participateMenuItem.show();
      this.ui.newEntryMenuItem.hide();
      this.ui.myEntriesMenuItem.hide();
      this.ui.currentUserMenuItem.text("");
      this.ui.currentUserMenuItem.parent().hide();
    }

  });
});
<|endoftext|>


<commit_before>Bullet.PixiRenderer = function(){

    this.renderer = new PIXI.autoDetectRenderer(640,480,null,false,1,false); //
    this.stage = new PIXI.Stage(0xffffff);

    this._nodeCache = [];

}

Bullet.PixiRenderer.prototype = {

    getElement: function(){
        return this.renderer.view;
    },

    render: function(hexMatrix, resolution){

        var stage = this.stage,
            nodeCache = this._nodeCache,
            pixelWidth = 1 / resolution,
            pixelRadius = pixelWidth / 2;

        _.each(hexMatrix, function(row, r){

            if(!nodeCache[r]){ nodeCache.push([]); }

            _.each(row, function(pixelColor, c){

                if(pixelColor !== null){

                    var pixel = nodeCache[r][c];

                    if(!pixel){

                        var graphics = new PIXI.Graphics();

                        graphics.beginFill(0x000000);
                        graphics.drawCircle(0, 0, pixelWidth);
                        graphics.endFill();

                        pixel = new PIXI.Sprite(graphics.generateTexture());

                        stage.addChild(pixel);

                        nodeCache[r].push(pixel);

                    }

                    var rasterWidth = (pixelWidth * ((15 - Bullet.Util.hexToBw(pixelColor)) / 15));

                    var xOnCanvas = (c * pixelWidth),
                        yOnCanvas = (r * pixelWidth);

                    pixel.x = xOnCanvas + ((pixelWidth - rasterWidth) / 2);
                    pixel.y = yOnCanvas + ((pixelWidth - rasterWidth) / 2);

                    pixel.width = pixel.height = rasterWidth;

                }

            });

        });

        this.renderer.render(stage);

    }

};<commit_msg>Locking X and Y Position for Pixi Renderer<commit_after>Bullet.PixiRenderer = function(){

    this.renderer = new PIXI.autoDetectRenderer(640,480,null,false,1,false); //
    this.stage = new PIXI.Stage(0xffffff);

    this._nodeCache = [];

}

Bullet.PixiRenderer.prototype = {

    getElement: function(){
        return this.renderer.view;
    },

    render: function(hexMatrix, resolution){

        var stage = this.stage,
            nodeCache = this._nodeCache,
            pixelWidth = 1 / resolution,
            pixelRadius = pixelWidth / 2;

        _.each(hexMatrix, function(row, r){

            if(!nodeCache[r]){ nodeCache.push([]); }

            _.each(row, function(pixelColor, c){

                if(pixelColor !== null){

                    var pixel = nodeCache[r][c];

                    if(!pixel){

                        var graphics = new PIXI.Graphics();

                        graphics.beginFill(0x000000);
                        graphics.drawCircle(0, 0, pixelWidth);
                        graphics.endFill();

                        pixel = new PIXI.Sprite(graphics.generateTexture());
                        pixel.anchor.x = 0.5;
                        pixel.anchor.y = 0.5;

                        var xOnCanvas = (c * pixelWidth),
                            yOnCanvas = (r * pixelWidth);

                        pixel.x = xOnCanvas + pixelRadius;
                        pixel.y = yOnCanvas + pixelRadius;

                        stage.addChild(pixel);

                        nodeCache[r].push(pixel);

                    }

                    var rasterWidth = (pixelWidth * ((15 - Bullet.Util.hexToBw(pixelColor)) / 15));

                    pixel.width = pixel.height = rasterWidth;

                }

            });

        });

        this.renderer.render(stage);

    }

};<|endoftext|>