<commit_before>//http://stackoverflow.com/questions/5751458/getting-correct-mouse-position-in-a-css-scaled-canvas
//http://dev.opera.com/articles/view/html5-canvas-painting/

var imageView;
var context;
var tool;
var fayeClient;
var clearBtn;

$(document).ready(function(){
	//Variables
	imageView = $("#imageView");
	clearBtn = $("#clear");
	context = imageView[0].getContext('2d');
	tool = new pencil();
	fayeClient = new Faye.Client('http://localhost:3000/faye', {
		timeout : 120
	});

	//Events

	//Subscription event
	fayeClient.subscribe('/channel', function(message){
		var obj = JSON.parse(message);
		var func = tool[obj.type];
		//if there is a method
		if(func) func(obj.x, obj.y);
	});

	//Click event for clearing
	clearBtn.click(function(){
		var obj = {
			type : "clear",
			x : 0,
			y : 0
		};

		fayeClient.publish("/channel", JSON.stringify(obj), function(err){
          console.log( "Error ",err );
        });
	});

	//mouse events
	imageView.mousemove(cursorEvent);
	imageView.mousedown(cursorEvent);
	imageView.mouseup(cursorEvent);

	/**
	 * Does everything
	 */
	function cursorEvent(ev){
		var x = ev.pageX - imageView.offset().left;
		var y = ev.pageY - imageView.offset().top;
		var func = tool[ev.type];
		var obj = {
			type: ev.type,
			x : x,
			y : y
		};
		fayeClient.publish("/channel", JSON.stringify(obj), function(err){
          console.log( "Error ",err );
        });
	}
});

/**
 * Pencil Object
 */
function pencil(){
	var tool = this;
	this.started = false;

	//Mouseup
	this.mouseup = function(x, y){
		if (tool.started) {
	      tool.mousemove(x, y);
	      tool.started = false;
	    };
	};

	//Mousedown
	this.mousedown = function(x, y){
		context.beginPath();
		context.moveTo(x, y);
		tool.started = true;
	};

	//mousemove
	this.mousemove = function(x, y){
		if (tool.started) {
	      context.lineTo(x, y);
	      context.stroke();
	    }
	};

	//clear
	this.clear = function(x,y){
		context.clearRect(0, 0, imageView.width(), imageView.height());
	}
}<commit_msg>Update canvasScript.js<commit_after>//http://stackoverflow.com/questions/5751458/getting-correct-mouse-position-in-a-css-scaled-canvas
//http://dev.opera.com/articles/view/html5-canvas-painting/
//http://wesbos.com/html5-canvas-websockets-nodejs/

var imageView;
var context;
var tool;
var fayeClient;
var clearBtn;

$(document).ready(function(){
	//Variables
	imageView = $("#imageView");
	clearBtn = $("#clear");
	context = imageView[0].getContext('2d');
	tool = new pencil();
	fayeClient = new Faye.Client('http://localhost:3000/faye', {
		timeout : 120
	});

	//Events

	//Subscription event
	fayeClient.subscribe('/channel', function(message){
		var obj = JSON.parse(message);
		var func = tool[obj.type];
		//if there is a method
		if(func) func(obj.x, obj.y);
	});

	//Click event for clearing
	clearBtn.click(function(){
		var obj = {
			type : "clear",
			x : 0,
			y : 0
		};

		fayeClient.publish("/channel", JSON.stringify(obj), function(err){
          console.log( "Error ",err );
        });
	});

	//mouse events
	imageView.mousemove(cursorEvent);
	imageView.mousedown(cursorEvent);
	imageView.mouseup(cursorEvent);

	/**
	 * Does everything
	 */
	function cursorEvent(ev){
		var x = ev.pageX - imageView.offset().left;
		var y = ev.pageY - imageView.offset().top;
		var func = tool[ev.type];
		var obj = {
			type: ev.type,
			x : x,
			y : y
		};
		fayeClient.publish("/channel", JSON.stringify(obj), function(err){
          console.log( "Error ",err );
        });
	}
});

/**
 * Pencil Object
 */
function pencil(){
	var tool = this;
	this.started = false;

	//Mouseup
	this.mouseup = function(x, y){
		if (tool.started) {
	      tool.mousemove(x, y);
	      tool.started = false;
	    };
	};

	//Mousedown
	this.mousedown = function(x, y){
		context.beginPath();
		context.moveTo(x, y);
		tool.started = true;
	};

	//mousemove
	this.mousemove = function(x, y){
		if (tool.started) {
	      context.lineTo(x, y);
	      context.stroke();
	    }
	};

	//clear
	this.clear = function(x,y){
		context.clearRect(0, 0, imageView.width(), imageView.height());
	}
}
<|endoftext|>


<commit_before>angular.module('easyFin4uApp')
.config(['ChartJsProvider', function (ChartJsProvider) {
    // Configure all charts
    ChartJsProvider.setOptions({
      colours: ['#FF5252', '#FF8A80'],
      responsive: true
    });
    // Configure all line charts
    ChartJsProvider.setOptions('Line', {
      datasetFill: false
    });
  }])
	.controller('portfolioController', ['$rootScope','$scope','$http','$location','$timeout','$q',
		function($rootScope, $scope, $http,$location,$timeout,$q) {
      var userEmail = $scope.user;
      var pielabels1 ="";
      var piedata1 = "";
      //console.log(userEmail);

      if(!$rootScope.tncStatus){
					$location.url('/terms');
			}else{
				if($rootScope.tncStatus === false){
					$location.url('/terms');
				}
			}

      if(!userEmail)
        $location.url('/login');
			else{

             $(window).on("resize", function () {
              var $grid = $("#list2"),
                  newWidth = $grid.closest(".ui-jqgrid").parent().width();
              $grid.jqGrid("setGridWidth", newWidth, true);
              });

				      angular.element(document).ready(function () {

  			        $("#list2").jqGrid({
				           	url:'/api/userPortfolio',
				            mtype: "GET",
				        	  datatype: "json",
				           	colNames:['Symbol','Name', 'Last Price', 'Change %','Shares','Cost Per Share','Cost Basis','Mkt Value','Gain','Gain %'],
				           	colModel:[
                    {name:'symbol', width:80, align:"left", key:true ,editable: true,editrules:{required: true},
                          editoptions: {
                                          "dataInit": function(el) {
                                            setTimeout(function() {
                                              if (jQuery.ui) {
                                                if (jQuery.ui.autocomplete) {
                                                  jQuery(el).autocomplete({
                                                    "appendTo": "body",
                                                    "disabled": false,
                                                    "delay": 300,
                                                    "minLength": 1,
                                                    "source": function(request, response) {
                                                      request.acelem = 'symbolSearch';
                                                      request.oper = 'autocmpl';
                                                      $.ajax({
                                                        url: "/api/symbolSearch",
                                                        dataType: "json",
                                                        data: request,
                                                        type: "GET",
                                                        error: function(res, status) {
                                                          alert(res.status + " : " + res.statusText + ". Status: " + status);
                                                        },
                                                        success: function(data) {
                                                          response(data);
                                                        }
                                                      });
                                                    },
                                                    select: function( event, ui ) {
                                                      $( "#symbol" ).val( ui.item.value );
                                                      $( "#name" ).val( ui.item.label );
                                                      return false;
                                                    }
                                                  });
                                                  jQuery(el).autocomplete('widget').css('font-size', '11px');
                                                  jQuery(el).autocomplete('widget').css('z-index', '1000');
                                                }
                                              }
                                            }, 200);
                                          }
                                        }
                      },
				           		{name:'name', width:350, align:"left", editable: true},
				           		{name:'lastprice', width:80, align:"right"},
				           		{name:'chg_percent', width:80, align:"right"},
				           		{name:'shares_qty', width:50, align:"right",editable: true,editrules:{
				                                    number: true,
				                                    required: true
				                                }},
											{name:'cost_per_share', width:80, align:"right",editable: true,editrules:{
				                                    number: true,
				                                    required: true
				                                }},
				           		{name:'cost_basis', width:100,align:"right"},
				           		{name:'mkt_value', width:100, align:"right"},
				           		{name:'gain', width:80, align:"right"},
				           		{name:'gain_percent', width:80, align:"right"}
				           	],
				           	rowNum:20,
				           	rowList:[5,10,20,30],
				           	pager: '#pager2',
				            viewrecords: true,
				            sortorder: "desc",
				            caption:"Portfolio List",
				            width:'100%',
										height: '100%',
				            editurl: "/api/userPortfolio",
				            rownumbers : true
				        });

				        $("#list2").navGrid("#pager2",
				                        { edit: true, add: true, del: true, search: false, refresh: true,
                                   view: false, align: "left"},
                                { // edit option
                                  closeAfterEdit: true ,
                                  reloadAfterSubmit:true,
                                  afterComplete: function (result) {
                                                    success: {
                                                      //alert(result.responseText);
                                                      renderDistributionGraph($piechart);
                                                      renderDistributionGraphCurrent($pieChartC);

                                                    }
                                                    fail: {
                                                      //console.log("EDIT"+result.responseText);
                                                  }
                                                },
                                    beforeShowForm: function(form) {
                                      $('#name', form).attr("disabled","true");  //To Disable Edit Box
                                      /* To Centralize Edit Modal */
                                      var dlgDiv = $("#editmod" + "list2");
                                      var parentDiv = dlgDiv.parent(); // div#gbox_list
                                      var dlgWidth = dlgDiv.width();
                                      var parentWidth = parentDiv.width();
                                      var dlgHeight = dlgDiv.height();
                                      var parentHeight = parentDiv.height();
                                      // TODO: change parentWidth and parentHeight in case of the grid
                                      //       is larger as the browser window
                                      //dlgDiv[0].style.top = Math.round((parentHeight-dlgHeight)/2) + "px";
                                      dlgDiv[0].style.left = Math.round((parentWidth-dlgWidth)/2) + "px";
                                      /* To Centralize Edit Modal */
                                    }
                                },
                                { // add option
                                  closeAfterAdd: true ,
                                  reloadAfterSubmit:true,
                                  afterComplete: function (result) {
                                                    success: {
                                                      //alert(result.responseText);
                                                      renderDistributionGraph($piechart);
                                                      renderPerformanceGraph($linechart);
                                                      renderDistributionGraphCurrent($pieChartC);
                                                    }
                                                    fail: {
                                                      //console.log("ADD"+result.responseText);
                                                    }
                                                },
                                  beforeShowForm: function(form) {
                                    $('#name', form).attr("disabled","true");  //To Disable Edit Box
                                    /* To Centralize Edit Modal */
                                    var dlgDiv = $("#editmod" + "list2");
                                    var parentDiv = dlgDiv.parent(); // div#gbox_list
                                    var dlgWidth = dlgDiv.width();
                                    var parentWidth = parentDiv.width();
                                    var dlgHeight = dlgDiv.height();
                                    var parentHeight = parentDiv.height();
                                    // TODO: change parentWidth and parentHeight in case of the grid
                                    //       is larger as the browser window
                                    //dlgDiv[0].style.top = Math.round((parentHeight-dlgHeight)/2) + "px";
                                    dlgDiv[0].style.left = Math.round((parentWidth-dlgWidth)/2) + "px";
                                    /* To Centralize Edit Modal */
                                  }
                                },{
                                  reloadAfterSubmit:true,
                                  afterComplete: function (result) {
                                                    success: {
                                                      //alert("DEL"+result.responseText);
                                                      renderDistributionGraph($piechart);
                                                      renderPerformanceGraph($linechart);
                                                      renderDistributionGraphCurrent($pieChartC);
                                                    }
                                                    fail: {
                                                      //console.log(result.responseText);
                                                    }
                                                }
                                }
                                );

                          var $grid = $("#list2"),
                          newWidth = $grid.closest(".ui-jqgrid").parent().width();
                          $grid.jqGrid("setGridWidth", newWidth, true);
				     });

             var $piechart,$linechart,$pieChartC;
             $scope.$on("create", function (event, chart) {
               if (typeof chart !== "undefined") {
                 if(chart.chart.canvas.id === 'pie'){
                   //alert(chart.id);
                   $piechart = chart;
                 }else if(chart.chart.canvas.id === 'pieC'){
                   $piechartC = chart;
                 }
                 else{
                   $linechart = chart;
                 }
               }
             });

              $scope.spinnerLoaded = true;
              $scope.noDataToDisplay = false;
              $scope.noDataToDisplayPie = false;
              $scope.dataLoaded = true;

              renderPerformanceGraph($linechart);

              function renderPerformanceGraph($linechart){
                var performanceChartUrl = "/api/getDailyPerformanceData?period="+14;
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }

                });
              }

              //Currently being used for 2 weeks(14 days) and 1 month(30 days)
              $scope.daysData = function(period) {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;
                if(period === undefined)
                  period = 14; //Default is 14 days
                var performanceChartUrl = "/api/getDailyPerformanceData?period="+period;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });

              }

              //Currently being used for 2 months(8 weeks), 3 months(12 weeks) and 6 months(24 weeks)
              $scope.weeksData = function(period) {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;

                if(period === undefined)
                  period = 4;  // Default is 4 weeks
                var performanceChartUrl = "/api/getWeeklyPerformanceData?period="+period;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });
              }

              $scope.monthsData = function(period) {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;

                if(period === undefined)
                  period = 12;  //Default is 12 months
                var performanceChartUrl = "/api/getMonthlyPerformanceData?period="+period;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });
              }

              $scope.all = function() {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;

                var performanceChartUrl = "/api/getPerformanceData?period="+5;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });
              }

              renderDistributionGraph($piechart);

              $scope.pieOptions = {
                animationEasing : "easeOutBounce",
                tooltipTemplate: "<%if (label){%><%=label %> : <%}%><%= value + ' %' %>",
                responsive: true,
                tooltipFontSize: 10
              };

              function renderDistributionGraph($piechart){
                $http.get("/api/getOriginalDistributionData").then(function(response) {
                  if(response.data.label.length === 0){
                    $scope.noDataToDisplayPie = true;
                    $rootScope.pielabels = [];
                    $rootScope.piedata = [];
                    if($piechart !== undefined){
                      $piechart.destroy();
                      document.getElementsByClassName("pie-legend")[0].style.visibility='hidden';
                    }
                  }else{
                    $scope.noDataToDisplayPie = false;
                    $rootScope.pielabels = response.data.label;
                    $rootScope.piedata = response.data.data;
                  }

                });
              }

              renderDistributionGraphCurrent($pieChartC);

              function renderDistributionGraphCurrent($pieChartC){
                $http.get("/api/getCurrentDistributionData").then(function(response) {
                  if(response.data.label.length === 0){
                    $scope.noDataToDisplayPie = true;
                    $rootScope.pielabelsC = [];
                    $rootScope.piedataC = [];
                    if($piechartC !== undefined){
                      $piechartC.destroy();
                      document.getElementsByClassName("pie-legendC")[0].style.visibility='hidden';
                    }
                  }else{
                    $scope.noDataToDisplayPie = false;
                    $rootScope.pielabelsC = response.data.label;
                    $rootScope.piedataC = response.data.data;
                  }

                });
              }

          /* Added to Fix the Flicker Bug of Angular Charts */
              // var $chart;
              //   $scope.$on("create", function (event, chart) {
              //     if (typeof $chart !== "undefined") {
              //       $chart.destroy();
              //     }
              //
              //     $chart = chart;
              //   });
              /* Added to Fix the Flicker Bug of Angular Charts */
					 }

					 $rootScope.logout = function(){
              //$scope = undefined;
              //$rootScope = undefined;
						 	$rootScope.user = undefined;
				      $rootScope.message = 'Logged out.';
				      $http.get('/logout');
				    };
	}]);
<commit_msg>Commit to Fix Dots on Line Chart<commit_after>angular.module('easyFin4uApp')
.config(['ChartJsProvider', function (ChartJsProvider) {
    // Configure all charts
    ChartJsProvider.setOptions({
      colours: ['#FF5252', '#FF8A80'],
      responsive: true
    });
    // Configure all line charts
    ChartJsProvider.setOptions('Line', {
      datasetFill: false,
        //Boolean - Whether to show a dot for each point
      pointDot : true,

      //Number - Radius of each point dot in pixels
      pointDotRadius : 3,

      //Number - Pixel width of point dot stroke
      pointDotStrokeWidth : 1,

      //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
      pointHitDetectionRadius : 3
    });
  }])
	.controller('portfolioController', ['$rootScope','$scope','$http','$location','$timeout','$q',
		function($rootScope, $scope, $http,$location,$timeout,$q) {
      var userEmail = $scope.user;
      var pielabels1 ="";
      var piedata1 = "";
      //console.log(userEmail);

      if(!$rootScope.tncStatus){
					$location.url('/terms');
			}else{
				if($rootScope.tncStatus === false){
					$location.url('/terms');
				}
			}

      if(!userEmail)
        $location.url('/login');
			else{

             $(window).on("resize", function () {
              var $grid = $("#list2"),
                  newWidth = $grid.closest(".ui-jqgrid").parent().width();
              $grid.jqGrid("setGridWidth", newWidth, true);
              });

				      angular.element(document).ready(function () {

  			        $("#list2").jqGrid({
				           	url:'/api/userPortfolio',
				            mtype: "GET",
				        	  datatype: "json",
				           	colNames:['Symbol','Name', 'Last Price', 'Change %','Shares','Cost Per Share','Cost Basis','Mkt Value','Gain','Gain %'],
				           	colModel:[
                    {name:'symbol', width:80, align:"left", key:true ,editable: true,editrules:{required: true},
                          editoptions: {
                                          "dataInit": function(el) {
                                            setTimeout(function() {
                                              if (jQuery.ui) {
                                                if (jQuery.ui.autocomplete) {
                                                  jQuery(el).autocomplete({
                                                    "appendTo": "body",
                                                    "disabled": false,
                                                    "delay": 300,
                                                    "minLength": 1,
                                                    "source": function(request, response) {
                                                      request.acelem = 'symbolSearch';
                                                      request.oper = 'autocmpl';
                                                      $.ajax({
                                                        url: "/api/symbolSearch",
                                                        dataType: "json",
                                                        data: request,
                                                        type: "GET",
                                                        error: function(res, status) {
                                                          alert(res.status + " : " + res.statusText + ". Status: " + status);
                                                        },
                                                        success: function(data) {
                                                          response(data);
                                                        }
                                                      });
                                                    },
                                                    select: function( event, ui ) {
                                                      $( "#symbol" ).val( ui.item.value );
                                                      $( "#name" ).val( ui.item.label );
                                                      return false;
                                                    }
                                                  });
                                                  jQuery(el).autocomplete('widget').css('font-size', '11px');
                                                  jQuery(el).autocomplete('widget').css('z-index', '1000');
                                                }
                                              }
                                            }, 200);
                                          }
                                        }
                      },
				           		{name:'name', width:350, align:"left", editable: true},
				           		{name:'lastprice', width:80, align:"right"},
				           		{name:'chg_percent', width:80, align:"right"},
				           		{name:'shares_qty', width:50, align:"right",editable: true,editrules:{
				                                    number: true,
				                                    required: true
				                                }},
											{name:'cost_per_share', width:80, align:"right",editable: true,editrules:{
				                                    number: true,
				                                    required: true
				                                }},
				           		{name:'cost_basis', width:100,align:"right"},
				           		{name:'mkt_value', width:100, align:"right"},
				           		{name:'gain', width:80, align:"right"},
				           		{name:'gain_percent', width:80, align:"right"}
				           	],
				           	rowNum:20,
				           	rowList:[5,10,20,30],
				           	pager: '#pager2',
				            viewrecords: true,
				            sortorder: "desc",
				            caption:"Portfolio List",
				            width:'100%',
										height: '100%',
				            editurl: "/api/userPortfolio",
				            rownumbers : true
				        });

				        $("#list2").navGrid("#pager2",
				                        { edit: true, add: true, del: true, search: false, refresh: true,
                                   view: false, align: "left"},
                                { // edit option
                                  closeAfterEdit: true ,
                                  reloadAfterSubmit:true,
                                  afterComplete: function (result) {
                                                    success: {
                                                      //alert(result.responseText);
                                                      renderDistributionGraph($piechart);
                                                      renderDistributionGraphCurrent($pieChartC);

                                                    }
                                                    fail: {
                                                      //console.log("EDIT"+result.responseText);
                                                  }
                                                },
                                    beforeShowForm: function(form) {
                                      $('#name', form).attr("disabled","true");  //To Disable Edit Box
                                      /* To Centralize Edit Modal */
                                      var dlgDiv = $("#editmod" + "list2");
                                      var parentDiv = dlgDiv.parent(); // div#gbox_list
                                      var dlgWidth = dlgDiv.width();
                                      var parentWidth = parentDiv.width();
                                      var dlgHeight = dlgDiv.height();
                                      var parentHeight = parentDiv.height();
                                      // TODO: change parentWidth and parentHeight in case of the grid
                                      //       is larger as the browser window
                                      //dlgDiv[0].style.top = Math.round((parentHeight-dlgHeight)/2) + "px";
                                      dlgDiv[0].style.left = Math.round((parentWidth-dlgWidth)/2) + "px";
                                      /* To Centralize Edit Modal */
                                    }
                                },
                                { // add option
                                  closeAfterAdd: true ,
                                  reloadAfterSubmit:true,
                                  afterComplete: function (result) {
                                                    success: {
                                                      //alert(result.responseText);
                                                      renderDistributionGraph($piechart);
                                                      renderPerformanceGraph($linechart);
                                                      renderDistributionGraphCurrent($pieChartC);
                                                    }
                                                    fail: {
                                                      //console.log("ADD"+result.responseText);
                                                    }
                                                },
                                  beforeShowForm: function(form) {
                                    $('#name', form).attr("disabled","true");  //To Disable Edit Box
                                    /* To Centralize Edit Modal */
                                    var dlgDiv = $("#editmod" + "list2");
                                    var parentDiv = dlgDiv.parent(); // div#gbox_list
                                    var dlgWidth = dlgDiv.width();
                                    var parentWidth = parentDiv.width();
                                    var dlgHeight = dlgDiv.height();
                                    var parentHeight = parentDiv.height();
                                    // TODO: change parentWidth and parentHeight in case of the grid
                                    //       is larger as the browser window
                                    //dlgDiv[0].style.top = Math.round((parentHeight-dlgHeight)/2) + "px";
                                    dlgDiv[0].style.left = Math.round((parentWidth-dlgWidth)/2) + "px";
                                    /* To Centralize Edit Modal */
                                  }
                                },{
                                  reloadAfterSubmit:true,
                                  afterComplete: function (result) {
                                                    success: {
                                                      //alert("DEL"+result.responseText);
                                                      renderDistributionGraph($piechart);
                                                      renderPerformanceGraph($linechart);
                                                      renderDistributionGraphCurrent($pieChartC);
                                                    }
                                                    fail: {
                                                      //console.log(result.responseText);
                                                    }
                                                }
                                }
                                );

                          var $grid = $("#list2"),
                          newWidth = $grid.closest(".ui-jqgrid").parent().width();
                          $grid.jqGrid("setGridWidth", newWidth, true);
				     });

             var $piechart,$linechart,$pieChartC;
             $scope.$on("create", function (event, chart) {
               if (typeof chart !== "undefined") {
                 if(chart.chart.canvas.id === 'pie'){
                   //alert(chart.id);
                   $piechart = chart;
                 }else if(chart.chart.canvas.id === 'pieC'){
                   $piechartC = chart;
                 }
                 else{
                   $linechart = chart;
                 }
               }
             });

              $scope.spinnerLoaded = true;
              $scope.noDataToDisplay = false;
              $scope.noDataToDisplayPie = false;
              $scope.dataLoaded = true;

              renderPerformanceGraph($linechart);

              function renderPerformanceGraph($linechart){
                var performanceChartUrl = "/api/getDailyPerformanceData?period="+14;
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }

                });
              }

              //Currently being used for 2 weeks(14 days) and 1 month(30 days)
              $scope.daysData = function(period) {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;
                if(period === undefined)
                  period = 14; //Default is 14 days
                var performanceChartUrl = "/api/getDailyPerformanceData?period="+period;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });

              }

              //Currently being used for 2 months(8 weeks), 3 months(12 weeks) and 6 months(24 weeks)
              $scope.weeksData = function(period) {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;

                if(period === undefined)
                  period = 4;  // Default is 4 weeks
                var performanceChartUrl = "/api/getWeeklyPerformanceData?period="+period;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });
              }

              $scope.monthsData = function(period) {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;

                if(period === undefined)
                  period = 12;  //Default is 12 months
                var performanceChartUrl = "/api/getMonthlyPerformanceData?period="+period;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });
              }

              $scope.all = function() {
                $scope.spinnerLoaded = true;
                $scope.noDataToDisplay = false;

                var performanceChartUrl = "/api/getPerformanceData?period="+5;
                $http.get(performanceChartUrl).then(function(response) {
                    if(response.data.length === 0){
                      console.log("No Data to Display on Chart");
                      $scope.noDataToDisplay = true;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = undefined;
                      $rootScope.linedata = undefined;
                      $rootScope.lineseries = undefined;

                      if($linechart !== undefined){
                        $linechart.destroy();
                        document.getElementsByClassName("line-legend")[0].style.visibility='hidden';
                      }
                    }else{
                      //console.log("Render"+JSON.stringify(response));
                      $scope.noDataToDisplay = false;
                      $scope.spinnerLoaded = false;
                      $rootScope.linelabels = response.data.label;
                      $rootScope.linedata = response.data.data;
                      $rootScope.lineseries = response.data.series;

                      if($linechart !== undefined)
                        $linechart.update();

                  }
                });
              }

              renderDistributionGraph($piechart);

              $scope.pieOptions = {
                animationEasing : "easeOutBounce",
                tooltipTemplate: "<%if (label){%><%=label %> : <%}%><%= value + ' %' %>",
                responsive: true,
                tooltipFontSize: 10
              };

              function renderDistributionGraph($piechart){
                $http.get("/api/getOriginalDistributionData").then(function(response) {
                  if(response.data.label.length === 0){
                    $scope.noDataToDisplayPie = true;
                    $rootScope.pielabels = [];
                    $rootScope.piedata = [];
                    if($piechart !== undefined){
                      $piechart.destroy();
                      document.getElementsByClassName("pie-legend")[0].style.visibility='hidden';
                    }
                  }else{
                    $scope.noDataToDisplayPie = false;
                    $rootScope.pielabels = response.data.label;
                    $rootScope.piedata = response.data.data;
                  }

                });
              }

              renderDistributionGraphCurrent($pieChartC);

              function renderDistributionGraphCurrent($pieChartC){
                $http.get("/api/getCurrentDistributionData").then(function(response) {
                  if(response.data.label.length === 0){
                    $scope.noDataToDisplayPie = true;
                    $rootScope.pielabelsC = [];
                    $rootScope.piedataC = [];
                    if($piechartC !== undefined){
                      $piechartC.destroy();
                      document.getElementsByClassName("pie-legendC")[0].style.visibility='hidden';
                    }
                  }else{
                    $scope.noDataToDisplayPie = false;
                    $rootScope.pielabelsC = response.data.label;
                    $rootScope.piedataC = response.data.data;
                  }

                });
              }

          /* Added to Fix the Flicker Bug of Angular Charts */
              // var $chart;
              //   $scope.$on("create", function (event, chart) {
              //     if (typeof $chart !== "undefined") {
              //       $chart.destroy();
              //     }
              //
              //     $chart = chart;
              //   });
              /* Added to Fix the Flicker Bug of Angular Charts */
					 }

					 $rootScope.logout = function(){
              //$scope = undefined;
              //$rootScope = undefined;
						 	$rootScope.user = undefined;
				      $rootScope.message = 'Logged out.';
				      $http.get('/logout');
				    };
	}]);
<|endoftext|>


<commit_before>(function(module) {
  var DataCollector = {};

  DataCollector.snapshotVal;
  DataCollector.ref = new Firebase('https://bike-map-fd305.firebaseio.com/');
  var gba = [];
  var gda = [];
  var result = [];
  var averageAll = [];

  DataCollector.averageAllFilter = function(array) {
    averageAll = [];
    for(var i = 0; i < array.length; i++) {
      averageAll.push(Math.floor(array[i].reduce(function(prev,cur) {
        return (prev + cur);
      }) / array[i].length));
    }
    return averageAll;
  };

  DataCollector.condenseAverage = function(array, interval) {
    var result = [];
    for (var i = 0; i < array.length; i += interval) {
      var sum = array.slice(i, i + interval).reduce(function(prev, cur) {
        return prev + cur;
      });
      result.push(Math.floor(sum / interval));
    }
    result.pop();
    return result;
  };

  DataCollector.oneStationByInterval = function(array, interval, station) {
    var result = [];
    for (var i = 0; i < array.length; i += interval) {
      result.push(array[i][station]);
    }
    return result;
  };

  DataCollector.oneStationHourlyAverage = function(array,station) {
    var result = [];
    var avg = [];
    for (var i = 0; i < array.length; i++) {
      result.push(array[i][station]);
    };
    for (var i = 0; i < result.length; i += 12) {
      var sum = result.slice(i, i + 12).reduce(function(prev, cur) {
        return prev + cur;
      });
      avg.push(Math.floor(sum / 12));
    }
    avg.pop();
    return avg;
  };

  DataCollector.bikesAvailableAll = function() {
    gba = [];
    for(var i = 0 ; i < Object.keys(DataCollector.snapshotVal.data).length; i++) {
      temp = [];
      snapshotVal.data[Object.keys(DataCollector.snapshotVal.data)[i]].stations.forEach(function(station) {
        temp.push(station.ba);
      });
      gba.push(temp);
    };
  };

  DataCollector.docksAvailableAll = function() {
    gda = [];
    for(var i = 0 ; i < Object.keys(DataCollector.snapshotVal.data).length; i++) {
      temp = [];
      snapshotVal.data[Object.keys(DataCollector.snapshotVal.data)[i]].stations.forEach(function(station) {
        temp.push(station.da);
      });
      gda.push(temp);
    };
  };

  module.DataCollector = DataCollector;
})(window);
<commit_msg>putting in some iffes<commit_after>(function(module) {
  var DataCollector = {};

  DataCollector.snapshotVal;
  DataCollector.ref = new Firebase('https://bike-map-fd305.firebaseio.com/');
  var gba = [];
  var gda = [];
  var result = [];
  var averageAll = [];

  DataCollector.averageAllFilter = function(array) {
    averageAll = [];
    for(var i = 0; i < array.length; i++) {
      averageAll.push(Math.floor(array[i].reduce(function(prev,cur) {
        return (prev + cur);
      }) / array[i].length));
    }
    return averageAll;
  };

  DataCollector.condenseAverage = function(array, interval) {
    var result = [];
    for (var i = 0; i < array.length; i += interval) {
      var sum = array.slice(i, i + interval).reduce(function(prev, cur) {
        return prev + cur;
      });
      result.push(Math.floor(sum / interval));
    }
    result.pop();
    return result;
  };

  DataCollector.oneStationByInterval = function(array, interval, station) {
    var result = [];
    for (var i = 0; i < array.length; i += interval) {
      result.push(array[i][station]);
    }
    return result;
  };

  DataCollector.oneStationHourlyAverage = function(array,station) {
    var result = [];
    var avg = [];
    for (var i = 0; i < array.length; i++) {
      result.push(array[i][station]);
    };
    for (var i = 0; i < result.length; i += 12) {
      var sum = result.slice(i, i + 12).reduce(function(prev, cur) {
        return prev + cur;
      });
      avg.push(Math.floor(sum / 12));
    }
    avg.pop();
    return avg;
  };

  DataCollector.bikesAvailableAll = function() {
    gba = [];
    for(var i = 0 ; i < Object.keys(DataCollector.snapshotVal.data).length; i++) {
      temp = [];
      DataCollector.snapshotVal.data[Object.keys(DataCollector.snapshotVal.data)[i]].stations.forEach(function(station) {
        temp.push(station.ba);
      });
      gba.push(temp);
    };
  };

  DataCollector.docksAvailableAll = function() {
    gda = [];
    for(var i = 0 ; i < Object.keys(DataCollector.snapshotVal.data).length; i++) {
      temp = [];
      DataCollector.snapshotVal.data[Object.keys(DataCollector.snapshotVal.data)[i]].stations.forEach(function(station) {
        temp.push(station.da);
      });
      gda.push(temp);
    };
  };

  module.DataCollector = DataCollector;
})(window);
<|endoftext|>


<commit_before>var sub = new NchanSubscriber(url='http://websock.gelth.local/sub?id=' + window.location.hostname.split('.')[0], opt={subscriber:'websocket'});

sub.on("message", function(message, message_metadata) {
  var m = JSON.parse(message)

  $('#listeners').html( m.listeners);
  $('#maxlisteners').html(m.maxlisteners);
  $('#requestbutton').html(m.requestcount);

});

sub.start();
<commit_msg>Fixed url for subscribe.js to remove the hardcoded url.<commit_after>var dj = window.location.hostname.split('.')[0];
var url = 'http://' + window.location.hostname.split(':')[0] + '/sub?id=' + dj;
var sub = new NchanSubscriber(url=url, opt={subscriber:'websocket'});

sub.on("message", function(message, message_metadata) {
  var m = JSON.parse(message)

  $('#listeners').html( m.listeners);
  $('#maxlisteners').html(m.maxlisteners);
  $('#requestbutton').html(m.requestcount);

});

sub.start();
<|endoftext|>


<commit_before>// @flow

import type { AppState } from '../redux/redux-setup';
import {
  type ChatMessageItem,
  chatMessageItemPropType,
} from 'lib/selectors/chat-selectors';
import { type ThreadInfo, threadInfoPropType } from 'lib/types/thread-types';
import type { DispatchActionPromise } from 'lib/utils/action-utils';
import type { FetchMessageInfosPayload } from 'lib/types/message-types';

import * as React from 'react';
import PropTypes from 'prop-types';
import invariant from 'invariant';
import { DropTarget } from 'react-dnd';
import { NativeTypes } from 'react-dnd-html5-backend';
import classNames from 'classnames';
import { detect as detectBrowser } from 'detect-browser';

import { connect } from 'lib/utils/redux-utils';
import { messageKey } from 'lib/shared/message-utils';
import { threadInChatList } from 'lib/shared/thread-utils';
import threadWatcher from 'lib/shared/thread-watcher';
import { threadInfoSelector } from 'lib/selectors/thread-selectors';
import {
  fetchMessagesBeforeCursorActionTypes,
  fetchMessagesBeforeCursor,
  fetchMostRecentMessagesActionTypes,
  fetchMostRecentMessages,
} from 'lib/actions/message-actions';
import { registerFetchKey } from 'lib/reducers/loading-reducer';

import { webMessageListData } from '../selectors/chat-selectors';
import ChatInputBar from './chat-input-bar.react';
import Message, {
  type MessagePositionInfo,
  type OnMessagePositionInfo,
} from './message.react';
import LoadingIndicator from '../loading-indicator.react';
import MessageTimestampTooltip from './message-timestamp-tooltip.react';
import {
  inputStatePropType,
  type InputState,
  withInputState,
} from '../input/input-state';
import css from './chat-message-list.css';

type PassedProps = {|
  setModal: (modal: ?React.Node) => void,
  // Redux state
  activeChatThreadID: ?string,
  threadInfo: ?ThreadInfo,
  messageListData: ?$ReadOnlyArray<ChatMessageItem>,
  startReached: boolean,
  timeZone: ?string,
  firefox: boolean,
  // Redux dispatch functions
  dispatchActionPromise: DispatchActionPromise,
  // async functions that hit server APIs
  fetchMessagesBeforeCursor: (
    threadID: string,
    beforeMessageID: string,
  ) => Promise<FetchMessageInfosPayload>,
  fetchMostRecentMessages: (
    threadID: string,
  ) => Promise<FetchMessageInfosPayload>,
  // withInputState
  inputState: ?InputState,
|};
type ReactDnDProps = {|
  isActive: boolean,
  connectDropTarget: (node: React.Node) => React.Node,
|};
type Props = {|
  ...PassedProps,
  ...ReactDnDProps,
|};
type State = {|
  messageMouseover: ?OnMessagePositionInfo,
|};
class ChatMessageList extends React.PureComponent<Props, State> {
  static propTypes = {
    setModal: PropTypes.func.isRequired,
    activeChatThreadID: PropTypes.string,
    threadInfo: threadInfoPropType,
    messageListData: PropTypes.arrayOf(chatMessageItemPropType),
    startReached: PropTypes.bool.isRequired,
    timeZone: PropTypes.string,
    firefox: PropTypes.bool.isRequired,
    dispatchActionPromise: PropTypes.func.isRequired,
    fetchMessagesBeforeCursor: PropTypes.func.isRequired,
    fetchMostRecentMessages: PropTypes.func.isRequired,
    inputState: inputStatePropType,
  };
  state = {
    messageMouseover: null,
  };
  container: ?HTMLDivElement;
  messageContainer: ?HTMLDivElement;
  loadingFromScroll = false;

  componentDidMount() {
    const { threadInfo } = this.props;
    if (!threadInfo || threadInChatList(threadInfo)) {
      return;
    }
    threadWatcher.watchID(threadInfo.id);
    this.props.dispatchActionPromise(
      fetchMostRecentMessagesActionTypes,
      this.props.fetchMostRecentMessages(threadInfo.id),
    );
    this.scrollToBottom();
  }

  componentWillUnmount() {
    const { threadInfo } = this.props;
    if (!threadInfo || threadInChatList(threadInfo)) {
      return;
    }
    threadWatcher.removeID(threadInfo.id);
  }

  componentDidUpdate(prevProps: Props) {
    const { messageListData } = this.props;
    const prevMessageListData = prevProps.messageListData;

    if (
      this.loadingFromScroll &&
      messageListData &&
      (!prevMessageListData ||
        messageListData.length > prevMessageListData.length ||
        this.props.startReached)
    ) {
      this.loadingFromScroll = false;
    }

    const { messageContainer } = this;
    if (
      messageContainer &&
      prevProps.messageListData !== this.props.messageListData
    ) {
      this.onScroll();
    }

    if (
      this.props.activeChatThreadID !== prevProps.activeChatThreadID ||
      // This conditional evaluates to true when this client is the case of
      // a new message being prepended to the messageListData
      (messageListData &&
        messageListData.length > 0 &&
        (!prevMessageListData ||
          prevMessageListData.length === 0 ||
          ChatMessageList.keyExtractor(prevMessageListData[0]) !==
            ChatMessageList.keyExtractor(messageListData[0])) &&
        messageListData[0].itemType === 'message' &&
        messageListData[0].messageInfo.localID)
    ) {
      this.scrollToBottom();
    }
  }

  scrollToBottom() {
    const { messageContainer } = this;
    if (!messageContainer) {
      return;
    }
    if (this.props.firefox) {
      messageContainer.scrollTop = 0;
    } else {
      messageContainer.scrollTop = messageContainer.scrollHeight;
    }
  }

  static keyExtractor(item: ChatMessageItem) {
    if (item.itemType === 'loader') {
      return 'loader';
    }
    return messageKey(item.messageInfo);
  }

  renderItem = item => {
    if (item.itemType === 'loader') {
      return (

          <LoadingIndicator status="loading" size="large" color="black" />

      );
    }
    const { threadInfo, setModal } = this.props;
    invariant(threadInfo, 'ThreadInfo should be set if messageListData is');
    return (
      <Message
        item={item}
        threadInfo={threadInfo}
        setMouseOver={this.setTimestampTooltip}
        setModal={setModal}
        timeZone={this.props.timeZone}
        key={ChatMessageList.keyExtractor(item)}
      />
    );
  };

  setTimestampTooltip = (messagePositionInfo: MessagePositionInfo) => {
    if (!this.messageContainer) {
      return;
    }
    if (messagePositionInfo.type === 'off') {
      if (
        this.state.messageMouseover &&
        ChatMessageList.keyExtractor(this.state.messageMouseover.item) ===
          ChatMessageList.keyExtractor(messagePositionInfo.item)
      ) {
        this.setState({ messageMouseover: null });
      }
      return;
    }
    const containerTop = this.messageContainer.getBoundingClientRect().top;
    const messageMouseover = {
      ...messagePositionInfo,
      messagePosition: {
        ...messagePositionInfo.messagePosition,
        top: messagePositionInfo.messagePosition.top - containerTop,
        bottom: messagePositionInfo.messagePosition.bottom - containerTop,
      },
    };
    this.setState({ messageMouseover });
  };

  render() {
    const {
      messageListData,
      threadInfo,
      inputState,
      connectDropTarget,
      isActive,
    } = this.props;
    if (!messageListData) {
      return
;
    }
    invariant(threadInfo, 'ThreadInfo should be set if messageListData is');
    invariant(inputState, 'InputState should be set');
    const messages = messageListData.map(this.renderItem);
    const containerStyle = classNames({
      [css.container]: true,
      [css.activeContainer]: isActive,
    });

    const tooltip = (
      <MessageTimestampTooltip
        messagePositionInfo={this.state.messageMouseover}
        timeZone={this.props.timeZone}
      />
    );

    const messageContainerStyle = classNames({
      [css.messageContainer]: true,
      [css.firefoxMessageContainer]: this.props.firefox,
    });
    return connectDropTarget(

          {messages}
          {tooltip}

        <ChatInputBar threadInfo={threadInfo} inputState={inputState} />

,
    );
  }

  containerRef = (container: ?HTMLDivElement) => {
    if (container) {
      container.addEventListener('paste', this.onPaste);
    }
    this.container = container;
  };

  onPaste = (e: ClipboardEvent) => {
    const { inputState } = this.props;
    if (!inputState) {
      return;
    }
    const { clipboardData } = e;
    if (!clipboardData) {
      return;
    }
    const { files } = clipboardData;
    if (files.length === 0) {
      return;
    }
    e.preventDefault();
    inputState.appendFiles([...files]);
  };

  messageContainerRef = (messageContainer: ?HTMLDivElement) => {
    this.messageContainer = messageContainer;
    // In case we already have all the most recent messages,
    // but they're not enough
    this.possiblyLoadMoreMessages();
    if (messageContainer) {
      messageContainer.addEventListener('scroll', this.onScroll);
    }
  };

  onScroll = () => {
    if (!this.messageContainer) {
      return;
    }
    if (this.state.messageMouseover) {
      this.setState({ messageMouseover: null });
    }
    this.possiblyLoadMoreMessages();
  };

  possiblyLoadMoreMessages() {
    if (!this.messageContainer) {
      return;
    }
    if (this.messageContainer.scrollTop > 55 || this.props.startReached) {
      return;
    }
    const oldestMessageServerID = this.oldestMessageServerID();
    if (!oldestMessageServerID) {
      return;
    }
    const threadID = this.props.activeChatThreadID;
    invariant(threadID, 'should be set');
    this.loadingFromScroll = true;
    this.props.dispatchActionPromise(
      fetchMessagesBeforeCursorActionTypes,
      this.props.fetchMessagesBeforeCursor(threadID, oldestMessageServerID),
    );
  }

  oldestMessageServerID(): ?string {
    const data = this.props.messageListData;
    invariant(data, 'should be set');
    for (let i = data.length - 1; i >= 0; i--) {
      if (data[i].itemType === 'message' && data[i].messageInfo.id) {
        return data[i].messageInfo.id;
      }
    }
    return null;
  }
}

registerFetchKey(fetchMessagesBeforeCursorActionTypes);
registerFetchKey(fetchMostRecentMessagesActionTypes);

const ReduxConnectedChatMessageList = connect(
  (state: AppState) => {
    const { activeChatThreadID } = state.navInfo;
    const browser = detectBrowser(state.userAgent);
    const firefox = browser && browser.name === 'firefox';
    return {
      activeChatThreadID,
      threadInfo: activeChatThreadID
        ? threadInfoSelector(state)[activeChatThreadID]
        : null,
      messageListData: webMessageListData(state),
      startReached: !!(
        activeChatThreadID &&
        state.messageStore.threads[activeChatThreadID].startReached
      ),
      timeZone: state.timeZone,
      firefox,
    };
  },
  { fetchMessagesBeforeCursor, fetchMostRecentMessages },
)(ChatMessageList);

export default withInputState(
  DropTarget(
    NativeTypes.FILE,
    {
      drop: (props: PassedProps, monitor) => {
        const { files } = monitor.getItem();
        if (props.inputState && files.length > 0) {
          props.inputState.appendFiles(files);
        }
      },
    },
    (connector, monitor) => ({
      connectDropTarget: connector.dropTarget(),
      isActive: monitor.isOver() && monitor.canDrop(),
    }),
  )(ReduxConnectedChatMessageList),
);
<commit_msg>[web] Keep chat window scrolled to bottom on new messages<commit_after>// @flow

import type { AppState } from '../redux/redux-setup';
import {
  type ChatMessageItem,
  chatMessageItemPropType,
} from 'lib/selectors/chat-selectors';
import { type ThreadInfo, threadInfoPropType } from 'lib/types/thread-types';
import type { DispatchActionPromise } from 'lib/utils/action-utils';
import type { FetchMessageInfosPayload } from 'lib/types/message-types';

import * as React from 'react';
import PropTypes from 'prop-types';
import invariant from 'invariant';
import { DropTarget } from 'react-dnd';
import { NativeTypes } from 'react-dnd-html5-backend';
import classNames from 'classnames';
import { detect as detectBrowser } from 'detect-browser';

import { connect } from 'lib/utils/redux-utils';
import { messageKey } from 'lib/shared/message-utils';
import { threadInChatList } from 'lib/shared/thread-utils';
import threadWatcher from 'lib/shared/thread-watcher';
import { threadInfoSelector } from 'lib/selectors/thread-selectors';
import {
  fetchMessagesBeforeCursorActionTypes,
  fetchMessagesBeforeCursor,
  fetchMostRecentMessagesActionTypes,
  fetchMostRecentMessages,
} from 'lib/actions/message-actions';
import { registerFetchKey } from 'lib/reducers/loading-reducer';

import { webMessageListData } from '../selectors/chat-selectors';
import ChatInputBar from './chat-input-bar.react';
import Message, {
  type MessagePositionInfo,
  type OnMessagePositionInfo,
} from './message.react';
import LoadingIndicator from '../loading-indicator.react';
import MessageTimestampTooltip from './message-timestamp-tooltip.react';
import {
  inputStatePropType,
  type InputState,
  withInputState,
} from '../input/input-state';
import css from './chat-message-list.css';

type PassedProps = {|
  setModal: (modal: ?React.Node) => void,
  // Redux state
  activeChatThreadID: ?string,
  threadInfo: ?ThreadInfo,
  messageListData: ?$ReadOnlyArray<ChatMessageItem>,
  startReached: boolean,
  timeZone: ?string,
  firefox: boolean,
  // Redux dispatch functions
  dispatchActionPromise: DispatchActionPromise,
  // async functions that hit server APIs
  fetchMessagesBeforeCursor: (
    threadID: string,
    beforeMessageID: string,
  ) => Promise<FetchMessageInfosPayload>,
  fetchMostRecentMessages: (
    threadID: string,
  ) => Promise<FetchMessageInfosPayload>,
  // withInputState
  inputState: ?InputState,
|};
type ReactDnDProps = {|
  isActive: boolean,
  connectDropTarget: (node: React.Node) => React.Node,
|};
type Props = {|
  ...PassedProps,
  ...ReactDnDProps,
|};
type State = {|
  messageMouseover: ?OnMessagePositionInfo,
|};
type Snapshot = {|
  +scrolledToBottom: boolean,
|};
class ChatMessageList extends React.PureComponent<Props, State> {
  static propTypes = {
    setModal: PropTypes.func.isRequired,
    activeChatThreadID: PropTypes.string,
    threadInfo: threadInfoPropType,
    messageListData: PropTypes.arrayOf(chatMessageItemPropType),
    startReached: PropTypes.bool.isRequired,
    timeZone: PropTypes.string,
    firefox: PropTypes.bool.isRequired,
    dispatchActionPromise: PropTypes.func.isRequired,
    fetchMessagesBeforeCursor: PropTypes.func.isRequired,
    fetchMostRecentMessages: PropTypes.func.isRequired,
    inputState: inputStatePropType,
  };
  state = {
    messageMouseover: null,
  };
  container: ?HTMLDivElement;
  messageContainer: ?HTMLDivElement;
  loadingFromScroll = false;

  componentDidMount() {
    const { threadInfo } = this.props;
    if (!threadInfo || threadInChatList(threadInfo)) {
      return;
    }
    threadWatcher.watchID(threadInfo.id);
    this.props.dispatchActionPromise(
      fetchMostRecentMessagesActionTypes,
      this.props.fetchMostRecentMessages(threadInfo.id),
    );
    this.scrollToBottom();
  }

  componentWillUnmount() {
    const { threadInfo } = this.props;
    if (!threadInfo || threadInChatList(threadInfo)) {
      return;
    }
    threadWatcher.removeID(threadInfo.id);
  }

  getSnapshotBeforeUpdate(prevProps: Props) {
    if (
      ChatMessageList.hasNewMessage(this.props, prevProps) &&
      this.messageContainer
    ) {
      const { scrollTop, scrollHeight, clientHeight } = this.messageContainer;
      const scrolledToBottom = this.props.firefox
        ? scrollTop <= 1
        : scrollTop + clientHeight + 1 >= scrollHeight;
      return { scrolledToBottom };
    }
    return null;
  }

  static hasNewMessage(props: Props, prevProps: Props) {
    const { messageListData } = props;
    if (!messageListData || messageListData.length === 0) {
      return false;
    }
    const prevMessageListData = prevProps.messageListData;
    if (!prevMessageListData || prevMessageListData.length === 0) {
      return true;
    }
    return (
      ChatMessageList.keyExtractor(prevMessageListData[0]) !==
      ChatMessageList.keyExtractor(messageListData[0])
    );
  }

  componentDidUpdate(prevProps: Props, prevState: State, snapshot: ?Snapshot) {
    const { messageListData } = this.props;
    const prevMessageListData = prevProps.messageListData;

    if (
      this.loadingFromScroll &&
      messageListData &&
      (!prevMessageListData ||
        messageListData.length > prevMessageListData.length ||
        this.props.startReached)
    ) {
      this.loadingFromScroll = false;
    }

    const { messageContainer } = this;
    if (messageContainer && prevMessageListData !== messageListData) {
      this.onScroll();
    }

    // We'll scroll to the bottom if the user was already scrolled to the bottom
    // before the new message, or if the new message was composed locally
    const hasNewMessage = ChatMessageList.hasNewMessage(this.props, prevProps);
    if (
      this.props.activeChatThreadID !== prevProps.activeChatThreadID ||
      (hasNewMessage &&
        messageListData &&
        messageListData[0].itemType === 'message' &&
        messageListData[0].messageInfo.localID) ||
      (hasNewMessage && snapshot && snapshot.scrolledToBottom)
    ) {
      this.scrollToBottom();
    }
  }

  scrollToBottom() {
    const { messageContainer } = this;
    if (!messageContainer) {
      return;
    }
    if (this.props.firefox) {
      messageContainer.scrollTop = 0;
    } else {
      messageContainer.scrollTop = messageContainer.scrollHeight;
    }
  }

  static keyExtractor(item: ChatMessageItem) {
    if (item.itemType === 'loader') {
      return 'loader';
    }
    return messageKey(item.messageInfo);
  }

  renderItem = item => {
    if (item.itemType === 'loader') {
      return (

          <LoadingIndicator status="loading" size="large" color="black" />

      );
    }
    const { threadInfo, setModal } = this.props;
    invariant(threadInfo, 'ThreadInfo should be set if messageListData is');
    return (
      <Message
        item={item}
        threadInfo={threadInfo}
        setMouseOver={this.setTimestampTooltip}
        setModal={setModal}
        timeZone={this.props.timeZone}
        key={ChatMessageList.keyExtractor(item)}
      />
    );
  };

  setTimestampTooltip = (messagePositionInfo: MessagePositionInfo) => {
    if (!this.messageContainer) {
      return;
    }
    if (messagePositionInfo.type === 'off') {
      if (
        this.state.messageMouseover &&
        ChatMessageList.keyExtractor(this.state.messageMouseover.item) ===
          ChatMessageList.keyExtractor(messagePositionInfo.item)
      ) {
        this.setState({ messageMouseover: null });
      }
      return;
    }
    const containerTop = this.messageContainer.getBoundingClientRect().top;
    const messageMouseover = {
      ...messagePositionInfo,
      messagePosition: {
        ...messagePositionInfo.messagePosition,
        top: messagePositionInfo.messagePosition.top - containerTop,
        bottom: messagePositionInfo.messagePosition.bottom - containerTop,
      },
    };
    this.setState({ messageMouseover });
  };

  render() {
    const {
      messageListData,
      threadInfo,
      inputState,
      connectDropTarget,
      isActive,
    } = this.props;
    if (!messageListData) {
      return
;
    }
    invariant(threadInfo, 'ThreadInfo should be set if messageListData is');
    invariant(inputState, 'InputState should be set');
    const messages = messageListData.map(this.renderItem);
    const containerStyle = classNames({
      [css.container]: true,
      [css.activeContainer]: isActive,
    });

    const tooltip = (
      <MessageTimestampTooltip
        messagePositionInfo={this.state.messageMouseover}
        timeZone={this.props.timeZone}
      />
    );

    const messageContainerStyle = classNames({
      [css.messageContainer]: true,
      [css.firefoxMessageContainer]: this.props.firefox,
    });
    return connectDropTarget(

          {messages}
          {tooltip}

        <ChatInputBar threadInfo={threadInfo} inputState={inputState} />

,
    );
  }

  containerRef = (container: ?HTMLDivElement) => {
    if (container) {
      container.addEventListener('paste', this.onPaste);
    }
    this.container = container;
  };

  onPaste = (e: ClipboardEvent) => {
    const { inputState } = this.props;
    if (!inputState) {
      return;
    }
    const { clipboardData } = e;
    if (!clipboardData) {
      return;
    }
    const { files } = clipboardData;
    if (files.length === 0) {
      return;
    }
    e.preventDefault();
    inputState.appendFiles([...files]);
  };

  messageContainerRef = (messageContainer: ?HTMLDivElement) => {
    this.messageContainer = messageContainer;
    // In case we already have all the most recent messages,
    // but they're not enough
    this.possiblyLoadMoreMessages();
    if (messageContainer) {
      messageContainer.addEventListener('scroll', this.onScroll);
    }
  };

  onScroll = () => {
    if (!this.messageContainer) {
      return;
    }
    if (this.state.messageMouseover) {
      this.setState({ messageMouseover: null });
    }
    this.possiblyLoadMoreMessages();
  };

  possiblyLoadMoreMessages() {
    if (!this.messageContainer) {
      return;
    }
    if (this.messageContainer.scrollTop > 55 || this.props.startReached) {
      return;
    }
    const oldestMessageServerID = this.oldestMessageServerID();
    if (!oldestMessageServerID) {
      return;
    }
    const threadID = this.props.activeChatThreadID;
    invariant(threadID, 'should be set');
    this.loadingFromScroll = true;
    this.props.dispatchActionPromise(
      fetchMessagesBeforeCursorActionTypes,
      this.props.fetchMessagesBeforeCursor(threadID, oldestMessageServerID),
    );
  }

  oldestMessageServerID(): ?string {
    const data = this.props.messageListData;
    invariant(data, 'should be set');
    for (let i = data.length - 1; i >= 0; i--) {
      if (data[i].itemType === 'message' && data[i].messageInfo.id) {
        return data[i].messageInfo.id;
      }
    }
    return null;
  }
}

registerFetchKey(fetchMessagesBeforeCursorActionTypes);
registerFetchKey(fetchMostRecentMessagesActionTypes);

const ReduxConnectedChatMessageList = connect(
  (state: AppState) => {
    const { activeChatThreadID } = state.navInfo;
    const browser = detectBrowser(state.userAgent);
    const firefox = browser && browser.name === 'firefox';
    return {
      activeChatThreadID,
      threadInfo: activeChatThreadID
        ? threadInfoSelector(state)[activeChatThreadID]
        : null,
      messageListData: webMessageListData(state),
      startReached: !!(
        activeChatThreadID &&
        state.messageStore.threads[activeChatThreadID].startReached
      ),
      timeZone: state.timeZone,
      firefox,
    };
  },
  { fetchMessagesBeforeCursor, fetchMostRecentMessages },
)(ChatMessageList);

export default withInputState(
  DropTarget(
    NativeTypes.FILE,
    {
      drop: (props: PassedProps, monitor) => {
        const { files } = monitor.getItem();
        if (props.inputState && files.length > 0) {
          props.inputState.appendFiles(files);
        }
      },
    },
    (connector, monitor) => ({
      connectDropTarget: connector.dropTarget(),
      isActive: monitor.isOver() && monitor.canDrop(),
    }),
  )(ReduxConnectedChatMessageList),
);
<|endoftext|>