<commit_before>/*global State, Config*/

State.GamePlay = function (game) {
	"use strict";
	this.game = game;
};
State.GamePlay.prototype = {

	preload: function () {
		var cursors, tapR, tapL;
		var playerX,playerY, fishXLeft,fishXRight, runY;
		var fishes, lastY,lastFish;
		var score, style,labelScore;
        var bar;//barra de tempo
		var overlap, initTap, showEnd;
        var labelLevel,levelText,countLevelText;//variaveis do level
        var pressLeft, pressRight;
        var gotasEmmiter;
	},

	create: function () {
		this.game.physics.startSystem(Phaser.Game.ARCADE);
		playerY = 400; playerX = 100;
		fishXLeft = 150; fishXRight = 300;
		runY = 40;
		lastFish = null;
		score = 0; style = { font: "30px Arial Bold", fill: "#ffffff", align: "center"};
		initTap = true;
		showEnd = false;
        levelText = "Level  0";
        this.gameOver = false;

        /*Botão de Tap*/
    	this.game.add.sprite(0, 0, 'bg');
    	tapR = this.game.add.sprite(this.game.world.centerX + 100, this.game.world.height-100, 'btnTapR');
    	tapR.anchor.setTo(.5, .5);
    	tapL = this.game.add.sprite(this.game.world.centerX - 100, this.game.world.height-100, 'btnTapL');
    	tapL.anchor.setTo(.5, .5);
    	this.animScape = false;
    	this.animTap();
    	/*Botão de Tap*/

		fishes = game.add.group();
		this.addFishes();

		this.player = this.game.add.sprite(playerX, playerY ,'player');
		this.game.physics.enable(this.player);
		this.player.animations.add('ticar',[0,1,0],30,false);
		this.player.body.setSize(this.player.width, 10, 0, 5);
		this.player.anchor.setTo(.5, .5);

		cursors = this.game.input.keyboard.createCursorKeys();
		this.game.input.keyboard.start();
		pressRight = false;
    	pressLeft = false;
		this.game.input.onDown.add(this.touch, this);
    	this.game.input.onUp.add(this.noTouch, this);

		this.ticou = false;
		labelScore = this.game.add.text(this.game.world.centerX, 200, score, style);
		labelScore.anchor.set(0.5, 0);

        labelLevel = this.game.add.text(this.game.world.centerX,450, levelText,style);
        labelLevel.anchor.set(0.5,0);
        labelLevel.alpha=0;

        /*bar = game.add.graphics();//adicionando o grafico
    	bar.lineStyle(20, 0x33FF00);
    	bar.moveTo(100,30);
    	bar.lineTo(this.game.world.width-100, 30);
    	bar.endFill();*/

		decrementValue=0.001;//variavel responsavel pelo aumento ou decremento do tempo, valor inicial
    	countLevelText=0;//contador do level, é concatenado com o texto

    	bar = game.add.graphics(100,30);
  		bar.lineStyle(20, 0x33FF00);
  		bar.moveTo(0, 0);
  		bar.lineTo(this.game.world.width-200, 0);

  		bar.scale.x = 0.5;

  		gotasEmmiter = this.game.add.emitter(0, 0, 100);
  		gotasEmmiter.makeParticles('gota');
  		gotasEmmiter.gravity = 200;
  		gotasEmmiter.minParticleScale = 0.1;
		gotasEmmiter.maxParticleScale = 1;
		gotasEmmiter.minRotation = 0;
		gotasEmmiter.maxRotation = 0;
    },

	update: function () {
		Config.global.screen.resize(this.game);
		overlap = false;
		this.game.physics.arcade.overlap(this.player, fishes, this.ticar, null,this);

		labelScore.setText(score);

		if(!initTap && !this.animScape){
			this.animTapScape();
		}

		if (cursors.left.isUp ) {
			this.leftUp = true;
		}
		if (cursors.right.isUp ) {
			this.rightUp = true;
		}

		if(!overlap && !initTap || bar.scale.x ==0){
			if(!showEnd){
				this.gameOver = true;
				this.game.input.keyboard.stop();
				this.end();
			}
		}else{
			if(!initTap){
				bar.scale.x -= decrementValue;
				if(bar.scale.x <=0){
					bar.scale.x = 0;
				}
			}
		}

		if(this.gameOver){
			pressLeft = false;
			pressRight = false;
		}

		if ( (pressLeft || cursors.left.isDown) && this.leftUp) {
			this.run();
			this.leftUp = false;
			this.changeSprite(-1);
			this.player.position.setTo(playerX,playerY);
			this.player.animations.play('ticar');
			ticou = true;
			initTap = false;
			pressLeft = false;
		}

        else if( (pressRight || cursors.right.isDown) && this.rightUp){
			this.run();
			this.rightUp = false;
			this.changeSprite(1);
			this.player.position.setTo(Config.global.screen.width-playerX,playerY);
			this.player.animations.play('ticar');
			ticou = true;
			initTap = false;
			pressRight = false;
		}
	},

	changeSprite: function(scale){
		this.player.scale.x = scale;
		this.player.scale.x = (-1)*scale;
	},

    upLevelTest:function(){
    	if(score%10==0){//testa o up de level seguindo o score do jogador
    		decrementValue *= 1.07;//valor aumentado em *1.5
            labelLevel.setText("Level "+ ++countLevelText);
            labelLevel.alpha=1;
            this.game.add.tween(labelLevel).to( { alpha: 0 }, 2000, Phaser.Easing.Linear.None, true, 0, 0, false);//faz o texto do level aparecer

    	}
    },

	run: function(){
		lastY = 1000;
		fishes.forEach(function (f){
			f.y += runY;
			if(f.y < lastY){
				lastY = f.y;
				lastFish = f;
			}
		}, this);
		fishes.forEach(function (f){
			if( (f.y - (f.height/2)) >= Config.global.screen.height){
				var posY = lastFish.y-(lastFish.height+f.height)/2;
				this.resetPosition(f, posY);
				f.frame = 0;
			}
		}, this);
	},

	resetPosition: function(fish, y){
		var rnd = this.game.rnd.integerInRange(-10, 9);
		if(rnd < 0)
			fish.reset(fishXLeft, y);
		else
			fish.reset(fishXRight, y);
	},

	addFishes: function(){
		for(var i=0;i<25;i++){
			var rnd = this.game.rnd.integerInRange(1, 10);
			if(rnd <= 2)
				this.fishRnd = 'fish2';
			else if(rnd <= 5)
				this.fishRnd = 'fish3';
			else if(rnd <= 10)
				this.fishRnd = 'fish4';

			var fish = this.game.add.sprite(-200, 200, this.fishRnd);
			fish.frame = 0;

			this.game.physics.enable(fish);
			fish.body.setSize(fish.width, fish.height-10, 0, 0);
			fish.anchor.setTo(.5, .5);
			if(lastFish==null)
				this.newY = playerY-10-5-fish.height/2;
			else
				this.newY = lastFish.y - (lastFish.height+fish.height)/2;

			this.resetPosition(fish, this.newY);
			lastFish = fish;
			fishes.add(fish);
		}
	},

	ticar: function(player, f){
		if(ticou){
			score++;
			f.frame++;
			ticou = false;
			bar.scale.x += 10*decrementValue;
			if(bar.scale.x > 1)
				bar.scale.x = 1;
            this.upLevelTest();
            this.mostarGotas(f.x, f.y);
		}
		overlap = true;
	},

	mostarGotas: function(x,y){
		gotasEmmiter.x = x;
    	gotasEmmiter.y = y;

    	gotasEmmiter.start(true, 2000, null, 10);
	},

	animTap: function(){
		this.game.add.tween(tapL).to( { x: tapL.x-35 }, 500, Phaser.Easing.Linear.None, true, 0, 500,true);
		this.game.add.tween(tapR).to( { x: tapR.x+35 }, 500, Phaser.Easing.Linear.None, true, 0, 500,true);
	},

	animTapScape: function(){
		this.game.add.tween(tapL).to( { y: this.game.world.height + 100 }, 500, Phaser.Easing.Bounce.Out, true, 0, 0,false);
		this.game.add.tween(tapR).to( { y: this.game.world.height + 100 }, 500, Phaser.Easing.Bounce.Out, true, 0, 0,false);
		this.animScape = true;
	},

	end: function(){
		//Show score
		var bgGO = this.game.add.sprite(-this.game.world.centerX, 50, 'bgGameOver');
		bgGO.anchor.set(0.5, 0);
		this.game.add.tween(bgGO).to( { x: this.game.world.centerX }, 2400, Phaser.Easing.Bounce.Out, true);
		var text;
		var text2;
		var hScore = localStorage.getItem("highscore");
		if (score > hScore) {
        	localStorage.setItem("highscore", score);
        	text = this.game.add.text(this.game.world.centerX, -200, 'Best ' + score, style);
        	text.anchor.setTo(0.5, 0.5);
        	text2 = this.game.add.text(this.game.world.centerX, -250, 'Score: ' + score, style);
        	text2.anchor.setTo(0.5, 0.5);
    	}
    	else {
        	text = this.game.add.text(this.game.world.centerX, -200, 'Best: ' + hScore, style);
        	text.anchor.setTo(0.5, 0.5);
        	text2 = this.game.add.text(this.game.world.centerX, -250, 'Score: ' + score, style);
        	text2.anchor.setTo(0.5, 0.5);
    	}

    	this.game.add.tween(text).to( { y: 200 }, 2400, Phaser.Easing.Bounce.Out, true);
    	this.game.add.tween(text2).to( { y: 250 }, 2400, Phaser.Easing.Bounce.Out, true);

    	var btPlay = this.game.add.button(this.game.world.centerX, -(50 + bgGO.height), 'btnPlay', this.restart, this, 1, 0, 1);
    	btPlay.anchor.set(0.5, 0.5);
    	this.game.add.tween(btPlay).to( { y: (50 + bgGO.height) }, 2400, Phaser.Easing.Bounce.Out, true);
    	showEnd = true;
	},

	restart: function(){
		this.game.state.start('GamePlay');
	},

	touch: function(pointer) {
		if (pointer.x < this.game.world.centerX)
        	pressLeft = true;
        else
        	pressRight = true;
	},

	noTouch: function() {
    	pressLeft = false;
    	pressRight = false;
	},

    render: function (){
    	game.debug.body(this.player);

    	/*fishes.forEach(function (f){
			game.debug.body(f);
		}, this);*/
    },
};<commit_msg>score<commit_after>/*global State, Config*/

State.GamePlay = function (game) {
	"use strict";
	this.game = game;
};
State.GamePlay.prototype = {

	preload: function () {
		var cursors, tapR, tapL;
		var playerX,playerY, fishXLeft,fishXRight, runY;
		var fishes, lastY,lastFish;
		var score, style,labelScore;
        var bar;//barra de tempo
		var overlap, initTap, showEnd;
        var labelLevel,levelText,countLevelText;//variaveis do level
        var pressLeft, pressRight;
        var gotasEmmiter;
	},

	create: function () {
		this.game.physics.startSystem(Phaser.Game.ARCADE);
		playerY = 400; playerX = 100;
		fishXLeft = 150; fishXRight = 300;
		runY = 40;
		lastFish = null;
		score = 0; style = { font: "30px Arial Bold", fill: "#ffffff", align: "center"};
		initTap = true;
		showEnd = false;
        levelText = "Level  0";
        this.gameOver = false;
        this.maxScoreByLevel = 0;
        this.levelScore = 0;

        /*Botão de Tap*/
    	this.game.add.sprite(0, 0, 'bg');
    	tapR = this.game.add.sprite(this.game.world.centerX + 100, this.game.world.height-100, 'btnTapR');
    	tapR.anchor.setTo(.5, .5);
    	tapL = this.game.add.sprite(this.game.world.centerX - 100, this.game.world.height-100, 'btnTapL');
    	tapL.anchor.setTo(.5, .5);
    	this.animScape = false;
    	this.animTap();
    	/*Botão de Tap*/

		fishes = game.add.group();
		this.addFishes();

		this.player = this.game.add.sprite(playerX, playerY ,'player');
		this.game.physics.enable(this.player);
		this.player.animations.add('ticar',[0,1,0],30,false);
		this.player.body.setSize(this.player.width, 10, 0, 5);
		this.player.anchor.setTo(.5, .5);

		cursors = this.game.input.keyboard.createCursorKeys();
		this.game.input.keyboard.start();
		pressRight = false;
    	pressLeft = false;
		this.game.input.onDown.add(this.touch, this);
    	this.game.input.onUp.add(this.noTouch, this);

		this.ticou = false;
		labelScore = this.game.add.text(this.game.world.centerX, 200, score, style);
		labelScore.anchor.set(0.5, 0);

        labelLevel = this.game.add.text(this.game.world.centerX,450, levelText,style);
        labelLevel.anchor.set(0.5,0);
        labelLevel.alpha=0;

        /*bar = game.add.graphics();//adicionando o grafico
    	bar.lineStyle(20, 0x33FF00);
    	bar.moveTo(100,30);
    	bar.lineTo(this.game.world.width-100, 30);
    	bar.endFill();*/

		decrementValue=0.001;//variavel responsavel pelo aumento ou decremento do tempo, valor inicial
    	countLevelText=0;//contador do level, é concatenado com o texto

    	bar = game.add.graphics(100,30);
  		bar.lineStyle(20, 0x33FF00);
  		bar.moveTo(0, 0);
  		bar.lineTo(this.game.world.width-200, 0);

  		bar.scale.x = 0.5;

  		gotasEmmiter = this.game.add.emitter(0, 0, 100);
  		gotasEmmiter.makeParticles('gota');
  		gotasEmmiter.gravity = 200;
  		gotasEmmiter.minParticleScale = 0.1;
		gotasEmmiter.maxParticleScale = 1;
		gotasEmmiter.minRotation = 0;
		gotasEmmiter.maxRotation = 0;
    },

	update: function () {
		Config.global.screen.resize(this.game);
		overlap = false;
		this.game.physics.arcade.overlap(this.player, fishes, this.ticar, null,this);

		labelScore.setText(score);

		if(!initTap && !this.animScape){
			this.animTapScape();
		}

		if (cursors.left.isUp ) {
			this.leftUp = true;
		}
		if (cursors.right.isUp ) {
			this.rightUp = true;
		}

		if(!overlap && !initTap || bar.scale.x ==0){
			if(!showEnd){
				this.gameOver = true;
				this.game.input.keyboard.stop();
				this.end();
			}
		}else{
			if(!initTap){
				bar.scale.x -= decrementValue;
				if(bar.scale.x <=0){
					bar.scale.x = 0;
				}
			}
		}

		if(this.gameOver){
			pressLeft = false;
			pressRight = false;
		}

		if ( (pressLeft || cursors.left.isDown) && this.leftUp) {
			this.run();
			this.leftUp = false;
			this.changeSprite(-1);
			this.player.position.setTo(playerX,playerY);
			this.player.animations.play('ticar');
			ticou = true;
			initTap = false;
			pressLeft = false;
		}

        else if( (pressRight || cursors.right.isDown) && this.rightUp){
			this.run();
			this.rightUp = false;
			this.changeSprite(1);
			this.player.position.setTo(Config.global.screen.width-playerX,playerY);
			this.player.animations.play('ticar');
			ticou = true;
			initTap = false;
			pressRight = false;
		}
	},

	changeSprite: function(scale){
		this.player.scale.x = scale;
		this.player.scale.x = (-1)*scale;
	},

    upLevelTest:function(){
    	this.levelScore ++;
    	if(this.levelScore==this.maxScoreByLevel){//testa o up de level seguindo o score do jogador
    		decrementValue *= 1.07;//valor aumentado em *1.7
            labelLevel.setText("Level "+ ++countLevelText);
            labelLevel.alpha=1;
            this.game.add.tween(labelLevel).to( { alpha: 0 }, 2000, Phaser.Easing.Linear.None, true, 0, 0, false);//faz o texto do level aparecer
            this.levelScore = 0;
    	}
    },

	run: function(){
		lastY = 1000;
		fishes.forEach(function (f){
			f.y += runY;
			if(f.y < lastY){
				lastY = f.y;
				lastFish = f;
			}
		}, this);
		fishes.forEach(function (f){
			if( (f.y - (f.height/2)) >= Config.global.screen.height){
				var posY = lastFish.y-(lastFish.height+f.height)/2;
				this.resetPosition(f, posY);
				f.frame = 0;
			}
		}, this);
	},

	resetPosition: function(fish, y){
		var rnd = this.game.rnd.integerInRange(-10, 9);
		if(rnd < 0)
			fish.reset(fishXLeft, y);
		else
			fish.reset(fishXRight, y);
	},

	addFishes: function(){
		for(var i=0;i<25;i++){
			var rnd = this.game.rnd.integerInRange(1, 10);
			if(rnd <= 2){
				this.fishRnd = 'fish2';
				this.maxScoreByLevel +=1;
			}
			else if(rnd <= 5){
				this.fishRnd = 'fish3';
				this.maxScoreByLevel +=2;
			}
			else if(rnd <= 10){
				this.fishRnd = 'fish4';
				this.maxScoreByLevel +=2;
			}

			var fish = this.game.add.sprite(-200, 200, this.fishRnd);
			fish.frame = 0;

			this.game.physics.enable(fish);
			fish.body.setSize(fish.width, fish.height-10, 0, 0);
			fish.anchor.setTo(.5, .5);
			if(lastFish==null)
				this.newY = playerY-10-5-fish.height/2;
			else
				this.newY = lastFish.y - (lastFish.height+fish.height)/2;

			this.resetPosition(fish, this.newY);
			lastFish = fish;
			fishes.add(fish);
		}
	},

	ticar: function(player, f){
		if(ticou){
			score++;
			f.frame++;
			ticou = false;
			bar.scale.x += 10*decrementValue;
			if(bar.scale.x > 1)
				bar.scale.x = 1;
            this.upLevelTest();
            this.mostarGotas(f.x, f.y);
		}
		overlap = true;
	},

	mostarGotas: function(x,y){
		gotasEmmiter.x = x;
    	gotasEmmiter.y = y;

    	gotasEmmiter.start(true, 2000, null, 10);
	},

	animTap: function(){
		this.game.add.tween(tapL).to( { x: tapL.x-35 }, 500, Phaser.Easing.Linear.None, true, 0, 500,true);
		this.game.add.tween(tapR).to( { x: tapR.x+35 }, 500, Phaser.Easing.Linear.None, true, 0, 500,true);
	},

	animTapScape: function(){
		this.game.add.tween(tapL).to( { y: this.game.world.height + 100 }, 500, Phaser.Easing.Bounce.Out, true, 0, 0,false);
		this.game.add.tween(tapR).to( { y: this.game.world.height + 100 }, 500, Phaser.Easing.Bounce.Out, true, 0, 0,false);
		this.animScape = true;
	},

	end: function(){
		//Show score
		var bgGO = this.game.add.sprite(-this.game.world.centerX, 50, 'bgGameOver');
		bgGO.anchor.set(0.5, 0);
		this.game.add.tween(bgGO).to( { x: this.game.world.centerX }, 2400, Phaser.Easing.Bounce.Out, true);
		var text;
		var text2;
		var hScore = localStorage.getItem("highscore");
		if (score > hScore) {
        	localStorage.setItem("highscore", score);
        	text = this.game.add.text(this.game.world.centerX, -200, 'Best ' + score, style);
        	text.anchor.setTo(0.5, 0.5);
        	text2 = this.game.add.text(this.game.world.centerX, -250, 'Score: ' + score, style);
        	text2.anchor.setTo(0.5, 0.5);
    	}
    	else {
        	text = this.game.add.text(this.game.world.centerX, -200, 'Best: ' + hScore, style);
        	text.anchor.setTo(0.5, 0.5);
        	text2 = this.game.add.text(this.game.world.centerX, -250, 'Score: ' + score, style);
        	text2.anchor.setTo(0.5, 0.5);
    	}

    	this.game.add.tween(text).to( { y: 200 }, 2400, Phaser.Easing.Bounce.Out, true);
    	this.game.add.tween(text2).to( { y: 250 }, 2400, Phaser.Easing.Bounce.Out, true);

    	var btPlay = this.game.add.button(this.game.world.centerX, -(50 + bgGO.height), 'btnPlay', this.restart, this, 1, 0, 1);
    	btPlay.anchor.set(0.5, 0.5);
    	this.game.add.tween(btPlay).to( { y: (50 + bgGO.height) }, 2400, Phaser.Easing.Bounce.Out, true);
    	showEnd = true;
	},

	restart: function(){
		this.game.state.start('GamePlay');
	},

	touch: function(pointer) {
		if (pointer.x < this.game.world.centerX)
        	pressLeft = true;
        else
        	pressRight = true;
	},

	noTouch: function() {
    	pressLeft = false;
    	pressRight = false;
	},

    render: function (){
    	game.debug.body(this.player);

    	/*fishes.forEach(function (f){
			game.debug.body(f);
		}, this);*/
    },
};<|endoftext|>


<commit_before>//Reusable functions
var Alphagov = {
  cookie_domain: function() {
    var host_parts = document.location.host.split(':')[0].split('.').slice(-3);
    return '.' + host_parts.join('.');
  },
  read_cookie: function(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = jQuery.trim(cookies[i]);
        if (cookie.substring(0, name.length + 1) == (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  },
  delete_cookie: function(name) {
    if (document.cookie && document.cookie != '') {
      var date = new Date();
      date.setTime(date.getTime()-(24*60*60*1000)); // 1 day ago
      document.cookie = name + "=; expires=" + date.toGMTString() + "; domain=" + Alphagov.cookie_domain() + "; path=/";
    }
  },
  write_permanent_cookie: function(name, value) {
    var date = new Date(2021, 12, 31);
    document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + date.toGMTString() + "; domain=" +  Alphagov.cookie_domain() + "; path=/";
  },

  get_display_place_name: function(locality_name, council_name){
    var result = '';

    //get long/short version of council name
    council_short_name =  council_name.replace(' Borough Council', '').replace(' County Council', '').replace(' District Council', '').replace(' Council', '');

    if(council_short_name != '' && council_short_name != undefined){
      result = locality_name + ', ' + council_short_name;
    }else{
      result = locality_name;
    }

    return result;
  }

}

//General page setup
jQuery(document).ready(function() {

  //Setup annotator links
  $('a.annotation').each(function(index) {
    $(this).linkAnnotator();
  });

  //feedback
  $('#send_feedback').click(function () {
    $('#feedback-router').show();
    return false;
  });
  $('a.close').click(function () {
    $(this).closest('.popover-mask').hide();
  });

  //tour

  var has_no_tour_cookie = function() {
    return Alphagov.read_cookie('been_on_tour') == null;
  }
  var write_tour_coookie = function() {
    Alphagov.write_permanent_cookie('been_on_tour', 'true');
  }

  var launch_tour = function() {
    $('

').appendTo($('body'));
    $('#splash-back').hide();
    $('#splash-back').load('/tour.html #splash', function() {
      $(document).trigger('tour-ready');
      $('#tour-close').click(close_tour);
      $('#tour-close-top').click(close_tour);
      $('#splash-back').fadeIn();
    });
    return false;
  }

  var close_tour = function() {
    $('#splash-back').fadeOut().remove();
    return false;
  }

  //setup tour click event
  // $('#tour-launcher').click(launch_tour);

  //set initial cookie ?
  if (has_no_tour_cookie()) {
    write_tour_coookie();

    launch_tour();
  }
});

<commit_msg>Removed tour javascript<commit_after>//Reusable functions
var Alphagov = {
  cookie_domain: function() {
    var host_parts = document.location.host.split(':')[0].split('.').slice(-3);
    return '.' + host_parts.join('.');
  },
  read_cookie: function(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = jQuery.trim(cookies[i]);
        if (cookie.substring(0, name.length + 1) == (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  },
  delete_cookie: function(name) {
    if (document.cookie && document.cookie != '') {
      var date = new Date();
      date.setTime(date.getTime()-(24*60*60*1000)); // 1 day ago
      document.cookie = name + "=; expires=" + date.toGMTString() + "; domain=" + Alphagov.cookie_domain() + "; path=/";
    }
  },
  write_permanent_cookie: function(name, value) {
    var date = new Date(2021, 12, 31);
    document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + date.toGMTString() + "; domain=" +  Alphagov.cookie_domain() + "; path=/";
  },

  get_display_place_name: function(locality_name, council_name){
    var result = '';

    //get long/short version of council name
    council_short_name =  council_name.replace(' Borough Council', '').replace(' County Council', '').replace(' District Council', '').replace(' Council', '');

    if(council_short_name != '' && council_short_name != undefined){
      result = locality_name + ', ' + council_short_name;
    }else{
      result = locality_name;
    }

    return result;
  }

}

//General page setup
jQuery(document).ready(function() {

  //Setup annotator links
  $('a.annotation').each(function(index) {
    $(this).linkAnnotator();
  });

  //feedback
  $('#send_feedback').click(function () {
    $('#feedback-router').show();
    return false;
  });
  $('a.close').click(function () {
    $(this).closest('.popover-mask').hide();
  });

});

<|endoftext|>


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

import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { debounce } from "lodash";

import Popup from "./Popup";

import {
  getPreview,
  getSelectedSource,
  isSelectedFrameVisible
} from "../../../selectors";
import actions from "../../../actions";
import { toEditorRange } from "../../../utils/editor";

import type { SelectedLocation, SourceRecord } from "../../../reducers/types";
import type { Preview as PreviewType } from "../../../reducers/ast";

type Props = {
  loadObjectProperties: Object => void,
  addExpression: (string, ?Object) => void,
  loadedObjects: Object,
  editor: any,
  selectedSource: SourceRecord,
  selectedLocation: SelectedLocation,
  selectedFrame: any,
  clearPreview: () => void,
  preview: PreviewType,
  selectedFrameVisible: boolean,
  updatePreview: any => void
};

class Preview extends PureComponent {
  props: Props;

  constructor() {
    super();

    const self = this;
    self.onScroll = this.onScroll.bind(this);
    self.onMouseOver = debounce(this.onMouseOver, 40);
    self.onMouseOver = this.onMouseOver.bind(this);
    self.onMouseUp = this.onMouseUp.bind(this);
    self.onMouseDown = this.onMouseDown.bind(this);
  }

  componentDidMount() {
    const { codeMirror } = this.props.editor;
    const codeMirrorWrapper = codeMirror.getWrapperElement();

    codeMirror.on("scroll", this.onScroll);
    codeMirrorWrapper.addEventListener("mouseover", this.onMouseOver);
    codeMirrorWrapper.addEventListener("mouseup", this.onMouseUp);
    codeMirrorWrapper.addEventListener("mousedown", this.onMouseDown);
  }

  componentWillUnmount() {
    const codeMirror = this.props.editor.codeMirror;
    const codeMirrorWrapper = codeMirror.getWrapperElement();
    codeMirrorWrapper.removeEventListener("mouseover", this.onMouseOver);
    codeMirrorWrapper.removeEventListener("mouseup", this.onMouseUp);
    codeMirrorWrapper.removeEventListener("mousedown", this.onMouseDown);

    codeMirror.off("scroll", this.onScroll);
  }

  onMouseOver(e) {
    const { target } = e;
    if (this.props.selectedFrameVisible) {
      this.props.updatePreview(target, this.props.editor);
    }
  }

  onMouseUp() {
    this.currentlySelecting = false;
  }

  onMouseDown() {
    this.currentlySelecting = true;
  }

  onScroll() {
    this.props.clearPreview();
  }

  onClose(e) {
    this.props.clearPreview();
  }

  render() {
    const { selectedSource, preview } = this.props;

    if (!this.props.editor || !selectedSource || this.currentlySelecting) {
      return null;
    }

    if (!preview || preview.updating) {
      return null;
    }

    const { result, expression, location, cursorPos, extra } = preview;
    const value = result;
    if (typeof value == "undefined" || value.optimizedOut) {
      return null;
    }

    const editorRange = toEditorRange(selectedSource.get("id"), location);

    return (
      <Popup
        value={value}
        editor={this.props.editor}
        range={editorRange}
        expression={expression}
        popoverPos={cursorPos}
        extra={extra}
        onClose={e => this.onClose(e)}
      />
    );
  }
}

const {
  addExpression,
  loadObjectProperties,
  updatePreview,
  clearPreview
} = actions;

export default connect(
  state => ({
    preview: getPreview(state),
    selectedSource: getSelectedSource(state),
    selectedFrameVisible: isSelectedFrameVisible(state)
  }),
  {
    addExpression,
    loadObjectProperties,
    updatePreview,
    clearPreview
  }
)(Preview);
<commit_msg>Cleanup preview selection (#5011)<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */

import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { debounce } from "lodash";

import Popup from "./Popup";

import {
  getPreview,
  getSelectedSource,
  isSelectedFrameVisible
} from "../../../selectors";
import actions from "../../../actions";
import { toEditorRange } from "../../../utils/editor";

import type { SelectedLocation, SourceRecord } from "../../../reducers/types";
import type { Preview as PreviewType } from "../../../reducers/ast";

type Props = {
  loadObjectProperties: Object => void,
  addExpression: (string, ?Object) => void,
  loadedObjects: Object,
  editor: any,
  selectedSource: SourceRecord,
  selectedLocation: SelectedLocation,
  selectedFrame: any,
  clearPreview: () => void,
  preview: PreviewType,
  selectedFrameVisible: boolean,
  updatePreview: any => void
};

class Preview extends PureComponent {
  props: Props;

  constructor() {
    super();
    this.state = { selecting: false };
    self.onMouseOver = debounce(this.onMouseOver, 40);
  }

  componentDidMount() {
    const { codeMirror } = this.props.editor;
    const codeMirrorWrapper = codeMirror.getWrapperElement();

    codeMirror.on("scroll", this.onScroll);
    codeMirrorWrapper.addEventListener("mouseover", this.onMouseOver);
    codeMirrorWrapper.addEventListener("mouseup", this.onMouseUp);
    codeMirrorWrapper.addEventListener("mousedown", this.onMouseDown);
  }

  componentWillUnmount() {
    const codeMirror = this.props.editor.codeMirror;
    const codeMirrorWrapper = codeMirror.getWrapperElement();
    codeMirrorWrapper.removeEventListener("mouseover", this.onMouseOver);
    codeMirrorWrapper.removeEventListener("mouseup", this.onMouseUp);
    codeMirrorWrapper.removeEventListener("mousedown", this.onMouseDown);
    codeMirror.off("scroll", this.onScroll);
  }

  onMouseOver = e => {
    const { target } = e;
    if (this.props.selectedFrameVisible) {
      this.props.updatePreview(target, this.props.editor);
    }
  };

  onMouseUp = () => {
    this.setState({ selecting: false });
    return true;
  };

  onMouseDown = () => {
    this.setState({ selecting: true });
    return true;
  };

  onScroll = () => {
    this.props.clearPreview();
  };

  onClose = e => {
    this.props.clearPreview();
  };

  render() {
    const { selectedSource, preview } = this.props;

    if (!this.props.editor || !selectedSource || this.state.selecting) {
      return null;
    }

    if (!preview || preview.updating) {
      return null;
    }

    const { result, expression, location, cursorPos, extra } = preview;
    const value = result;
    if (typeof value == "undefined" || value.optimizedOut) {
      return null;
    }

    const editorRange = toEditorRange(selectedSource.get("id"), location);

    return (
      <Popup
        value={value}
        editor={this.props.editor}
        range={editorRange}
        expression={expression}
        popoverPos={cursorPos}
        extra={extra}
        onClose={e => this.onClose(e)}
      />
    );
  }
}

const {
  addExpression,
  loadObjectProperties,
  updatePreview,
  clearPreview
} = actions;

export default connect(
  state => ({
    preview: getPreview(state),
    selectedSource: getSelectedSource(state),
    selectedFrameVisible: isSelectedFrameVisible(state)
  }),
  {
    addExpression,
    loadObjectProperties,
    updatePreview,
    clearPreview
  }
)(Preview);
<|endoftext|>


<commit_before>const debug = require('debug')('cosa:db');
const defaults = require('defaults');
const EJSON = require('mongodb-extended-json');
const EventEmitter = require('events').EventEmitter;
const MongoClient = require('mongodb').MongoClient;
const { curry, split, last, pipe } = require('omnibelt');
const RESULT_FIELDS = [ 'insertedCount', 'insertedIds', 'matchedCount', 'modifiedCount', 'upsertedCount', 'deletedCount', 'upsertedIds' ];
const getDbName = pipe(split('/'), last);

const normalizeResult = (r) => {
  const result = {
    result: r.result,
    ops: r.ops
  };
  RESULT_FIELDS.forEach((type) => {
    if ('undefined' !== typeof r[type]) {
      result[type] = r[type];
    }
  });
  return result;
};

const actionOneOrMany = curry(async (action, collection, isMultiple, ...params) => {
  const type = isMultiple ? `${action}Many` : `${action}One`;
  debug(`db.${collection.collectionName}.${type}`, ...params);
  const r = await collection[type](...params);
  return normalizeResult(r);
});
const insertOneOrMany = actionOneOrMany('insert');
const updateOneOrMany = actionOneOrMany('update');
const removeOneOrMany = actionOneOrMany('remove');

/**
 * Singleton that provides methods for connecting to a MongoDB collection.
 * @class Database
 */
class Database extends EventEmitter {
  constructor() {
    super();
    this._db = null;
    this._client = null;
    this._connectionStatus = 'disconnected';
    Object.defineProperty(this, 'connectionStatus', {
      enumerable: true,
      get: function() {
        return this._connectionStatus;
      }
    });
  }

  /**
   * Initialize the database connection.
   * @param {string} [uri] - URI to the database. If no URI is provided, the value
     *    of `process.env.COSA_DB_URI` is used. See
     *    https://docs.mongodb.com/manual/reference/connection-string/ for
     *    information on the URI format.
   * @param {object} [options] - Optional collection settings.
   * @see {@link init}
   * @returns {Promise} returns the db when connected
   */
  async init(uri, options) {
    options = options || {};
    this._uri = uri || process.env.COSA_DB_URI;
    if (!(/mongodb:\/\//).test(this._uri)) {
      throw new Error('invalid mongodb uri');
    }
    // so we dont mess up people environment variables.
    const dbName = options.dbName || getDbName(this._uri);
    debug(`connecting to database: ${this._uri}`);
    this._connectionStatus = 'connecting';
    this.emit('connecting');
    try {
      this._client = await MongoClient.connect(this._uri, { useNewUrlParser: true });
      this._db = await this._client.db(dbName);
    } catch (err) {
      debug('failed to connect', err);
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      throw err;
    }
    this._connectionStatus = 'connected';
    this._client.on('close', () => {
      debug('database connection closed');
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      this.emit('disconnect');
    });
    this.emit('connect', this);
    debug('connected to database');
    return this;
  }

  /**
   * Fetch a specific database collection.
   * @param {string} name - Collection name.
   * @param {object} [options] - Optional collection settings.
   * @returns {Promise} resolves with the connection
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html#collection}
   */
  async collection(name, options) {
    if (this._connectionStatus === 'connected') {
      debug(`loading collection ${name}`);
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    } else if (this._connectionStatus === 'connecting') {
      debug(`waiting on connection before loading collection ${name}`);
      return new Promise((resolve) => {
        this.on('connect', async () => {
          const collection = await this._db.collection(name, options);
          debug(`collection ${name} loaded`);
          return resolve(collection);
        });
      });
    } else {
      await this.init();
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    }
  }

  /**
   * Fetches documents from a collection with the given query.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - MongoDB query object.
   * @param {object} [options] - MongoDB query options.
   * @param {object} [options.fields] - (deprecated, will be mapped to projection) The fields to return in the query. Object of fields to include or exclude (not both), {'a':1
   * @param {object} [options.projection] - The projection to return in the query. Object of projection to include or exclude (not both), {'a':1
   * @param {(object|Array)} [options.sort] - Set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
   * @param {number} [options.skip=0] - Set to skip N documents ahead in your query (useful for pagination).
   * @param {object} [options.limit=1000] - Sets the limit of documents returned in the query.
   * @param {object} [options.count=false] - get a count of the items, instead of the items themselves.
   * @param {object} [options.findOne=false] - Should a single item be returned.
   * @returns {Cursor} returns Cursor object
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Cursor.html}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#find}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#findOne}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#count}
   */
  async find(collectionName, query, options) {
    options = defaults(options, {
      projection: undefined,
      sort: undefined,
      skip: undefined,
      limit: options && options.count ? null : 1000,
      count: false,
      findOne: false
    });
    query = EJSON.deserialize(query);
    debug('getting db collection');
    const collection = await this.collection(collectionName);
    if (options.count) {
      const countOptions = { limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.countDocuments`, query, countOptions);
      return collection.countDocuments(query, options);
    } else if (options.findOne) {
      const findOptions = { projection: options.projection || options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.findOne`, query, findOptions);
      return collection.findOne(query, options);
    } else {
      const findOptions = { projection: options.projection || options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.find (toArray)`, query, findOptions);
      return collection.find(query, options);
    }
  }

  /**
   * Inserts the given docs into a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {(object|Array)} docs - Documents objects to insert.
   * @returns {Promise} resolves with an object with results, and ops as keys
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#insertmany}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#insertOne}
   */
  async insert(collectionName, docs) {
    debug('getting db collection');
    docs = EJSON.deserialize(docs);
    const collection = await this.collection(collectionName);
    return insertOneOrMany(collection, Array.isArray(docs), docs);
  }

  /**
   * Updates docs in a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - Query to find which documents to update.
   * @param {object} update - Document properties to update.
   * @param {object} [options] - Optional settings see mongo documentation
   * @param {boolean} [options.multiple=false] - Should multiple documents be updated.
   * @param {boolean} [options.upsert=false] - Should documents be inserted if they don't already exist..
   * @returns {Promise} resolves with an object with results, and ops as keys
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#updateMany}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#updateOne}
   */
  async update(collectionName, query, update, options) {
    options = defaults(options, {
      multiple: false,
      upsert: false
    });
    debug('getting db collection');
    query = EJSON.deserialize(query);
    update = EJSON.deserialize(update);
    const collection = await this.collection(collectionName);
    const updateOptions = { upsert: options.upsert };
    return updateOneOrMany(collection, options.multiple, query, update, updateOptions);
  }

  /**
   * Removes docs from a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - Query to find which documents to remove.
   * @param {object} [options] - Optional settings see mongo documentation
   * @param {boolean} [options.multiple=false] - Should multiple documents be removed.
   * @returns {Promise} resolves with an object with results, and ops as keys
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#deleteMany}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#deleteOne}
   */
  async remove(collectionName, query, options) {
    options = defaults(options, {
      multiple: false
    });
    debug('getting db collection');
    query = EJSON.deserialize(query);
    const collection = await this.collection(collectionName);
    return removeOneOrMany(collection, options.multiple, query);
  }

  /**
   * Executes aggregation pipeline against a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {object} pipeline - Aggregation pipeline.
   * @param {object} [options] - Optional settings see mongo documentation
   * @param {boolean} [options.explain=false] - Should should the execution plan be returned.
   * @returns {Promise} resovles with the result of the aggregatation from mongo
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#aggregate}
   */
  async aggregate(collectionName, pipeline, options) {
    options = defaults(options, {
      explain: false
    });
    debug('getting db collection');
    const collection = await this.collection(collectionName);
    return new Promise((resolve, reject) => {
      collection.aggregate(pipeline, options, function(err, r) {
        if (err) { return reject(err); }
        return resolve(r);
      });
    });
  }

  /**
   * Returns list of unique values for the given key across a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {string} key - Document property.
   * @param {object} query - Query to find which documents evaluate.
   * @param {object} [options] - Optional settings see mongo documentation
   * @returns {Promise} resovles with the result of the distinct query from mongo
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#distinct}
   */
  async distinct(collectionName, key, query, options) {
    debug('getting db collection');
    query = EJSON.deserialize(query);
    const collection = await this.collection(collectionName);
    return collection.distinct(key, query, options);
  }

  async replace(collectionName, query, replace) {
    const collection = await this.collection(collectionName);
    query = EJSON.deserialize(query);
    replace = EJSON.deserialize(replace);
    debug(`db.${collection.collectionName}.replaceOne`, query, replace);
    const r = await collection.replaceOne(query, replace);
    return normalizeResult(r);
  }

}

module.exports = new Database();
<commit_msg>fix db name parser<commit_after>const debug = require('debug')('cosa:db');
const defaults = require('defaults');
const EJSON = require('mongodb-extended-json');
const EventEmitter = require('events').EventEmitter;
const MongoClient = require('mongodb').MongoClient;
const { curry, split, last, pipe } = require('omnibelt');
const RESULT_FIELDS = [ 'insertedCount', 'insertedIds', 'matchedCount', 'modifiedCount', 'upsertedCount', 'deletedCount', 'upsertedIds' ];
const getDbName = (uri) => {
  let name = pipe(split('/'), last)(uri);
  if (name.indexOf('?') !== -1) {
    name = name.slice(0, name.indexOf('?'));
  }
  return name;
};

const normalizeResult = (r) => {
  const result = {
    result: r.result,
    ops: r.ops
  };
  RESULT_FIELDS.forEach((type) => {
    if ('undefined' !== typeof r[type]) {
      result[type] = r[type];
    }
  });
  return result;
};

const actionOneOrMany = curry(async (action, collection, isMultiple, ...params) => {
  const type = isMultiple ? `${action}Many` : `${action}One`;
  debug(`db.${collection.collectionName}.${type}`, ...params);
  const r = await collection[type](...params);
  return normalizeResult(r);
});
const insertOneOrMany = actionOneOrMany('insert');
const updateOneOrMany = actionOneOrMany('update');
const removeOneOrMany = actionOneOrMany('remove');

/**
 * Singleton that provides methods for connecting to a MongoDB collection.
 * @class Database
 */
class Database extends EventEmitter {
  constructor() {
    super();
    this._db = null;
    this._client = null;
    this._connectionStatus = 'disconnected';
    Object.defineProperty(this, 'connectionStatus', {
      enumerable: true,
      get: function() {
        return this._connectionStatus;
      }
    });
  }

  /**
   * Initialize the database connection.
   * @param {string} [uri] - URI to the database. If no URI is provided, the value
     *    of `process.env.COSA_DB_URI` is used. See
     *    https://docs.mongodb.com/manual/reference/connection-string/ for
     *    information on the URI format.
   * @param {object} [options] - Optional collection settings.
   * @see {@link init}
   * @returns {Promise} returns the db when connected
   */
  async init(uri, options) {
    options = options || {};
    this._uri = uri || process.env.COSA_DB_URI;
    if (!(/mongodb:\/\//).test(this._uri)) {
      throw new Error('invalid mongodb uri');
    }
    // so we dont mess up people environment variables.
    const dbName = options.dbName || getDbName(this._uri);
    debug(`connecting to database: ${this._uri}`);
    this._connectionStatus = 'connecting';
    this.emit('connecting');
    try {
      this._client = await MongoClient.connect(this._uri, { useNewUrlParser: true });
      this._db = await this._client.db(dbName);
    } catch (err) {
      debug('failed to connect', err);
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      throw err;
    }
    this._connectionStatus = 'connected';
    this._client.on('close', () => {
      debug('database connection closed');
      this._db = null;
      this._client = null;
      this._connectionStatus = 'disconnected';
      this.emit('disconnect');
    });
    this.emit('connect', this);
    debug('connected to database');
    return this;
  }

  /**
   * Fetch a specific database collection.
   * @param {string} name - Collection name.
   * @param {object} [options] - Optional collection settings.
   * @returns {Promise} resolves with the connection
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Db.html#collection}
   */
  async collection(name, options) {
    if (this._connectionStatus === 'connected') {
      debug(`loading collection ${name}`);
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    } else if (this._connectionStatus === 'connecting') {
      debug(`waiting on connection before loading collection ${name}`);
      return new Promise((resolve) => {
        this.on('connect', async () => {
          const collection = await this._db.collection(name, options);
          debug(`collection ${name} loaded`);
          return resolve(collection);
        });
      });
    } else {
      await this.init();
      const collection = await this._db.collection(name, options);
      debug(`collection ${name} loaded`);
      return collection;
    }
  }

  /**
   * Fetches documents from a collection with the given query.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - MongoDB query object.
   * @param {object} [options] - MongoDB query options.
   * @param {object} [options.fields] - (deprecated, will be mapped to projection) The fields to return in the query. Object of fields to include or exclude (not both), {'a':1
   * @param {object} [options.projection] - The projection to return in the query. Object of projection to include or exclude (not both), {'a':1
   * @param {(object|Array)} [options.sort] - Set to sort the documents coming back from the query. Array of indexes, [['a', 1]] etc.
   * @param {number} [options.skip=0] - Set to skip N documents ahead in your query (useful for pagination).
   * @param {object} [options.limit=1000] - Sets the limit of documents returned in the query.
   * @param {object} [options.count=false] - get a count of the items, instead of the items themselves.
   * @param {object} [options.findOne=false] - Should a single item be returned.
   * @returns {Cursor} returns Cursor object
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Cursor.html}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#find}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#findOne}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#count}
   */
  async find(collectionName, query, options) {
    options = defaults(options, {
      projection: undefined,
      sort: undefined,
      skip: undefined,
      limit: options && options.count ? null : 1000,
      count: false,
      findOne: false
    });
    query = EJSON.deserialize(query);
    debug('getting db collection');
    const collection = await this.collection(collectionName);
    if (options.count) {
      const countOptions = { limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.countDocuments`, query, countOptions);
      return collection.countDocuments(query, options);
    } else if (options.findOne) {
      const findOptions = { projection: options.projection || options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.findOne`, query, findOptions);
      return collection.findOne(query, options);
    } else {
      const findOptions = { projection: options.projection || options.fields, limit: options.limit, skip: options.skip, sort: options.sort };
      debug(`db.${collection.collectionName}.find (toArray)`, query, findOptions);
      return collection.find(query, options);
    }
  }

  /**
   * Inserts the given docs into a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {(object|Array)} docs - Documents objects to insert.
   * @returns {Promise} resolves with an object with results, and ops as keys
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#insertmany}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#insertOne}
   */
  async insert(collectionName, docs) {
    debug('getting db collection');
    docs = EJSON.deserialize(docs);
    const collection = await this.collection(collectionName);
    return insertOneOrMany(collection, Array.isArray(docs), docs);
  }

  /**
   * Updates docs in a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - Query to find which documents to update.
   * @param {object} update - Document properties to update.
   * @param {object} [options] - Optional settings see mongo documentation
   * @param {boolean} [options.multiple=false] - Should multiple documents be updated.
   * @param {boolean} [options.upsert=false] - Should documents be inserted if they don't already exist..
   * @returns {Promise} resolves with an object with results, and ops as keys
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#updateMany}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#updateOne}
   */
  async update(collectionName, query, update, options) {
    options = defaults(options, {
      multiple: false,
      upsert: false
    });
    debug('getting db collection');
    query = EJSON.deserialize(query);
    update = EJSON.deserialize(update);
    const collection = await this.collection(collectionName);
    const updateOptions = { upsert: options.upsert };
    return updateOneOrMany(collection, options.multiple, query, update, updateOptions);
  }

  /**
   * Removes docs from a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {object} query - Query to find which documents to remove.
   * @param {object} [options] - Optional settings see mongo documentation
   * @param {boolean} [options.multiple=false] - Should multiple documents be removed.
   * @returns {Promise} resolves with an object with results, and ops as keys
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#deleteMany}
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#deleteOne}
   */
  async remove(collectionName, query, options) {
    options = defaults(options, {
      multiple: false
    });
    debug('getting db collection');
    query = EJSON.deserialize(query);
    const collection = await this.collection(collectionName);
    return removeOneOrMany(collection, options.multiple, query);
  }

  /**
   * Executes aggregation pipeline against a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {object} pipeline - Aggregation pipeline.
   * @param {object} [options] - Optional settings see mongo documentation
   * @param {boolean} [options.explain=false] - Should should the execution plan be returned.
   * @returns {Promise} resovles with the result of the aggregatation from mongo
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#aggregate}
   */
  async aggregate(collectionName, pipeline, options) {
    options = defaults(options, {
      explain: false
    });
    debug('getting db collection');
    const collection = await this.collection(collectionName);
    return new Promise((resolve, reject) => {
      collection.aggregate(pipeline, options, function(err, r) {
        if (err) { return reject(err); }
        return resolve(r);
      });
    });
  }

  /**
   * Returns list of unique values for the given key across a collection.
   * @param {string} collectionName - Name of the collection.
   * @param {string} key - Document property.
   * @param {object} query - Query to find which documents evaluate.
   * @param {object} [options] - Optional settings see mongo documentation
   * @returns {Promise} resovles with the result of the distinct query from mongo
   * @see {@link http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html#distinct}
   */
  async distinct(collectionName, key, query, options) {
    debug('getting db collection');
    query = EJSON.deserialize(query);
    const collection = await this.collection(collectionName);
    return collection.distinct(key, query, options);
  }

  async replace(collectionName, query, replace) {
    const collection = await this.collection(collectionName);
    query = EJSON.deserialize(query);
    replace = EJSON.deserialize(replace);
    debug(`db.${collection.collectionName}.replaceOne`, query, replace);
    const r = await collection.replaceOne(query, replace);
    return normalizeResult(r);
  }

}

module.exports = new Database();
<|endoftext|>


<commit_before>/**
 * Compute an isoline as a GeoJSON feature from a regular grid
 * Uses the Marching Squares algorithm, with code ported from
 * https://github.com/conveyal/r5/blob/master/src/main/java/com/conveyal/r5/analyst/IsochroneFeature.java
 * @author mattwigway
 */

import inside from 'turf-inside'
import point from 'turf-point'

/** Create a JSON isoline. Surface is a (possibly typed) array, width and height are its width and height, and cutoff is the cutoff */
export default function jsolines ({surface, width, height, cutoff, project}) {
  // first, create the contour grid
  let contour = new Uint8Array((width - 1) * (height - 1))

  // compute contour values for each cell
  for (let x = 0; x < width - 1; x++) {
    for (let y = 0; y < height - 1; y++) {
      let topLeft = surface[y * width + x] < cutoff
      let topRight = surface[y * width + x + 1] < cutoff
      let botLeft = surface[(y + 1) * width + x] < cutoff
      let botRight = surface[(y + 1) * width + x + 1] < cutoff

      // if we're at the edge of the area, set the outer sides to false, so that isochrones always close
      // even when they actually extend beyond the edges of the surface
      if (x === 0) topLeft = botLeft = false
      if (x === width - 2) topRight = botRight = false
      if (y === 0) topLeft = topRight = false
      if (y === height - 2) botRight = botLeft = false

      let idx = 0

      if (topLeft) idx |= 1 << 3
      if (topRight) idx |= 1 << 2
      if (botLeft) idx |= 1 << 1
      if (botRight) idx |= 1

      contour[y * (width - 1) + x] = idx
    }
  }

  let cWidth = width - 1

  // javascript does not have boolean arrays. lame.
  let found = new Uint8Array((width - 1) * (height - 1))

  // we'll sort out what shell goes with what hole in a bit
  let shells = []
  let holes = []

  // find a cell that has a line in it, then follow that line, keeping filled area to your
  // left. This lets us use winding direction to determine holes.
  for (let origy = 0; origy < height - 1; origy++) {
    for (let origx = 0; origx < width - 1; origx++) {
      if (found[origy * cWidth + origx]) continue

      let idx = contour[origy * cWidth + origx]
      // continue if there is no line here or if it's a saddle, as we don't know which way the saddle goes
      if (idx === 0 || idx === 5 || idx === 10 || idx === 15) continue

      // huzzah! we have found a line, now follow it, keeping the filled area to our left,
      // which allows us to use the winding direction to determing what should be a shell and
      // what should be a hole
      let x = origx
      let y = origy

      let prevx = -1
      let prevy = -1

      // track winding direction
      let direction = 0

      let coords = []

      RING: while (true) {
        let startx = x
        let starty = y
        idx = contour[y * cWidth + x]

        // only mark as found if it's not a saddle because we expect to reach saddles twice.
        if (idx !== 5 && idx !== 10) found[y * cWidth + x] = 1

        // follow the loop
        switch (idx) {
          case 0:
            console.log('Ran off outside of ring')
            break RING
          case 1:
            x--
            break
          // NB: +y is down
          case 2:
            y++
            break
          case 3:
            x--
            break
          case 4:
            x++
            break
          case 5:
            // assume that saddle has // orientation (as opposed to \\). It doesn't
            // really matter if we're wrong, we'll just have two disjoint pieces where we should have
            // one, or vice versa
            if (prevy > y) {
              // came from bottom
              x++
            } else if (prevy < y) {
              // came from top
              x--
            } else {
              console.log('Entered case 5 saddle point from wrong direction!')
            }
            break
          case 6:
            y++
            break
          case 7:
            x--
            break
          case 8:
            y--
            break
          case 9:
            y--
            break
          case 10:
            if (prevx < x) {
              // came from left
              y++
            } else if (prevx > x) {
              // came from right
              y--
            } else {
              console.log('Entered case 10 saddle point from wrong direction.')
            }
            break
          case 11:
            y--
            break
          case 12:
            x++
            break
          case 13:
            x++
            break
          case 14:
            y++
            break
          case 15:
            console.log('Ran off inside of ring')
            break RING
        }

        // keep track of winding direction
        direction += (x - startx) * (y + starty)

        let topLeft = surface[y * width + x]
        let topRight = surface[y * width + x + 1]
        let botLeft = surface[(y + 1) * width + x]
        let botRight = surface[(y + 1) * width + x + 1]

        // do linear interpolation
        let coord
        if (startx < x) {
          // came from left
          let frac = (cutoff - topLeft) / (botLeft - topLeft)
          coord = [x, y + frac]
        } else if (startx > x) {
          // came from right
          let frac = (cutoff - topRight) / (botRight - topRight)
          coord = [x + 1, y + frac]
        } else if (starty > y) {
          // came from bottom
          let frac = (cutoff - botLeft) / (botRight - botLeft)
          coord = [x + frac, y + 1]
        } else if (starty < y) {
          // came from top
          let frac = (cutoff - topLeft) / (topRight - topLeft)
          coord = [x + frac, y + 1]
        }

        coords.push(project(coord))

        // we're back at the start of the ring
        if (x === origx && y === origy) {
          coords.push(coords[0]) // close the ring
          break RING
        }

        // make it a fully-fledged GeoJSON object
        let geom = {
          type: 'Polygon',
          coordinates: [coords]
        }

        // check winding direction
        // positive means counter clockwise, see http://stackoverflow.com/questions/1165647
        // NB +y is down so the signs are reversed from what would normally be expected
        if (direction > 0) shells.add(geom)
        else holes.add(geom)
      }
    }
  }

  // shell game time.
  // sort out shells and holes
  HOLES: for (let hole of holes) {
    for (let shell of shells) {
      // NB this is checking whether the first coordinate of the hole is inside the shell.
      // This is sufficient as shells don't overlap, and holes are guaranteed to be completely
      // contained by a single shell.
      if (inside(point(hole.coordinates[0][0]), shell)) {
        shell.coordinates.push(hole.coordinates)
        continue HOLES
      }
    }

    console.log('Did not find fitting shell for hole')
  }

  return {
    type: 'MultiPolygon',
    coordinates: shells.map(s => s.coordinates)
  }
}
<commit_msg>more bugfixes, working version.<commit_after>/**
 * Compute an isoline as a GeoJSON feature from a regular grid
 * Uses the Marching Squares algorithm, with code ported from
 * https://github.com/conveyal/r5/blob/master/src/main/java/com/conveyal/r5/analyst/IsochroneFeature.java
 * @author mattwigway
 */

import inside from 'turf-inside'
import point from 'turf-point'

/** Create a JSON isoline. Surface is a (possibly typed) array, width and height are its width and height, and cutoff is the cutoff */
export default function jsolines ({surface, width, height, cutoff, project}) {
  // first, create the contour grid
  let contour = getContour({surface, width, height, cutoff})
  let cWidth = width - 1

  // javascript does not have boolean arrays. lame.
  let found = new Uint8Array((width - 1) * (height - 1))

  // DEBUG, comment out to save memory
  let indices = []

  // we'll sort out what shell goes with what hole in a bit
  let shells = []
  let holes = []

  // find a cell that has a line in it, then follow that line, keeping filled area to your
  // left. This lets us use winding direction to determine holes.
  for (let origy = 0; origy < height - 1; origy++) {
    for (let origx = 0; origx < width - 1; origx++) {
      if (found[origy * cWidth + origx] === 1) {
        continue
      }

      let idx = contour[origy * cWidth + origx]
      // continue if there is no line here or if it's a saddle, as we don't know which way the saddle goes
      if (idx === 0 || idx === 5 || idx === 10 || idx === 15) continue

      // huzzah! we have found a line, now follow it, keeping the filled area to our left,
      // which allows us to use the winding direction to determine what should be a shell and
      // what should be a hole
      let x = origx
      let y = origy

      let prevx = -1
      let prevy = -1
      let startx = -1
      let starty = -1

      // track winding direction
      let direction = 0

      let coords = []

      RING: while (true) {
        // make sure we're not traveling in circles
        // NB using index from _previous_ cell, we have not yet set an index for this cell
        if (found[y * cWidth + x] === 1) {
          console.log(`Ring crosses other ring (or possibly self) at ${x}, ${y} coming from case ${idx}`)
          console.log(`Last few indices: ${indices.slice(Math.max(0, indices.length - 10))}`)
          break RING
        }

        prevx = startx
        prevy = starty
        startx = x
        starty = y
        idx = contour[y * cWidth + x]

        indices.push(idx)

        // only mark as found if it's not a saddle because we expect to reach saddles twice.
        if (idx !== 5 && idx !== 10) {
          found[y * cWidth + x] = 1
        }

        // follow the loop
        switch (idx) {
          case 0:
            console.log('Ran off outside of ring')
            break RING
          case 1:
            x--
            break
          // NB: +y is down
          case 2:
            y++
            break
          case 3:
            x--
            break
          case 4:
            x++
            break
          case 5:
            // assume that saddle has // orientation (as opposed to \\). It doesn't
            // really matter if we're wrong, we'll just have two disjoint pieces where we should have
            // one, or vice versa
            if (prevy > y) {
              // came from bottom
              x++
            } else if (prevy < y) {
              // came from top
              x--
            } else {
              console.log('Entered case 5 saddle point from wrong direction!')
            }
            break
          case 6:
            y++
            break
          case 7:
            x--
            break
          case 8:
            y--
            break
          case 9:
            y--
            break
          case 10:
            if (prevx < x) {
              // came from left
              y++
            } else if (prevx > x) {
              // came from right
              y--
            } else {
              console.log('Entered case 10 saddle point from wrong direction.')
            }
            break
          case 11:
            y--
            break
          case 12:
            x++
            break
          case 13:
            x++
            break
          case 14:
            y++
            break
          case 15:
            console.log('Ran off inside of ring')
            break RING
        }

        // keep track of winding direction
        direction += (x - startx) * (y + starty)

        let topLeft = surface[y * width + x]
        let topRight = surface[y * width + x + 1]
        let botLeft = surface[(y + 1) * width + x]
        let botRight = surface[(y + 1) * width + x + 1]

        // do linear interpolation
        let coord
        if (startx < x) {
          // came from left
          let frac = (cutoff - topLeft) / (botLeft - topLeft)
          coord = [x, y + frac]
        } else if (startx > x) {
          // came from right
          let frac = (cutoff - topRight) / (botRight - topRight)
          coord = [x + 1, y + frac]
        } else if (starty > y) {
          // came from bottom
          let frac = (cutoff - botLeft) / (botRight - botLeft)
          coord = [x + frac, y + 1]
        } else if (starty < y) {
          // came from top
          let frac = (cutoff - topLeft) / (topRight - topLeft)
          coord = [x + frac, y + 1]
        } else {
          console.log(`Unexpected coordinate shift from ${startx}, ${starty} to ${x}, ${y}, discarding ring`)
          break RING
        }

        coords.push(project(coord))

        if (coords.length > 10000) {
          console.log('More than 10000 coordinates found in ring, skipping this ring')
          break RING
        }

        // we're back at the start of the ring
        if (x === origx && y === origy) {
          coords.push(coords[0]) // close the ring

          // make it a fully-fledged GeoJSON object
          let geom = {
            type: 'Feature',
            geometry: {
              type: 'Polygon',
              coordinates: [coords]
            }
          }

          // check winding direction
          // positive means counter clockwise, see http://stackoverflow.com/questions/1165647
          // NB +y is down so the signs are reversed from what would normally be expected
          if (direction > 0) shells.push(geom)
          else holes.push(geom)
          break RING
        }
      }
    }
  }

  // shell game time.
  // sort out shells and holes
  HOLES: for (let hole of holes) {
    for (let shell of shells) {
      // NB this is checking whether the first coordinate of the hole is inside the shell.
      // This is sufficient as shells don't overlap, and holes are guaranteed to be completely
      // contained by a single shell.
      if (inside(point(hole.geometry.coordinates[0][0]), shell)) {
        shell.geometry.coordinates.push(hole.geometry.coordinates[0])
        continue HOLES
      }
    }

    console.log('Did not find fitting shell for hole')
  }

  return {
    type: 'Feature',
    geometry: {
      type: 'MultiPolygon',
      coordinates: shells.map(s => s.geometry.coordinates)
    }
  }
}

/** Get a contouring grid. Exported for debug purposes, not generally used outside jsolines testing */
export function getContour ({surface, width, height, cutoff}) {
  let contour = new Uint8Array((width - 1) * (height - 1))

  // compute contour values for each cell
  for (let x = 0; x < width - 1; x++) {
    for (let y = 0; y < height - 1; y++) {
      let topLeft = surface[y * width + x] < cutoff
      let topRight = surface[y * width + x + 1] < cutoff
      let botLeft = surface[(y + 1) * width + x] < cutoff
      let botRight = surface[(y + 1) * width + x + 1] < cutoff

      // if we're at the edge of the area, set the outer sides to false, so that isochrones always close
      // even when they actually extend beyond the edges of the surface
      if (x === 0) topLeft = botLeft = false
      if (x === width - 2) topRight = botRight = false
      if (y === 0) topLeft = topRight = false
      if (y === height - 2) botRight = botLeft = false

      let idx = 0

      if (topLeft) idx |= 1 << 3
      if (topRight) idx |= 1 << 2
      if (botRight) idx |= 1 << 1
      if (botLeft) idx |= 1

      contour[y * (width - 1) + x] = idx
    }
  }

  return contour
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import { Horizontal, Vertical } from '../classes/scrollable';

const {
  run: { scheduleOnce, debounce, bind }
} = Ember;

const scrollbarSelector = '.tse-scrollbar';
const handleSelector = '.drag-handle';
const scrollContentSelector = '.tse-scroll-content';
const contentSelector = '.tse-content';

export default Ember.Mixin.create({
  classNameBindings: [':as-scrollable', ':tse-scrollable', 'horizontal'],

  horizontal: false,
  autoHide: true,
  scrollBuffer: 50,

  init() {
    this._super(...arguments);

    this.setupResize();
    this.measureScrollbar();
  },

  measureScrollbar() {

    /**
     * Calculate scrollbar width
     *
     * Original function by Jonathan Sharp:
     * http://jdsharp.us/jQuery/minute/calculate-scrollbar-width.php
     * Updated to work in Chrome v25.
     */
    function scrollbarWidth() {
      // Append a temporary scrolling element to the DOM, then measure
      // the difference between between its outer and inner elements.
      var tempEl = $('

');
      $('body').append(tempEl);
      var width = $(tempEl).innerWidth();
      var widthMinusScrollbars = $('div', tempEl).innerWidth();
      tempEl.remove();
      // On OS X if the scrollbar is set to auto hide it will have zero width. On webkit we can still
      // hide it using ::-webkit-scrollbar { width:0; height:0; } but there is no moz equivalent. So we're
      // forced to sniff Firefox and return a hard-coded scrollbar width. I know, I know...
      if (width === widthMinusScrollbars && navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
        return 17;
      }
      return (width - widthMinusScrollbars);
    }

    this._scrollbarWidth = scrollbarWidth();

  },

  didInsertElement() {
    this._super(...arguments);

    this.setupElements();

    if (this.get('autoHide')) {
      this.on('mouseEnter', this, this.showScrollbar);
    }

    this._handleElement.on('mousedown', bind(this, this.startDrag));
    this._scrollbarElement.on('mousedown', bind(this, this.jumpScroll));
    this._scrollContentElement.on('scroll', bind(this, this.scrolled));

    scheduleOnce('afterRender', this, this.setupScrollbar);
  },

  setupScrollbar() {
    this.scrollbar = this.createScrollbar();

    this.scrollbar.update();
    this.showScrollbar();
  },

  setupElements() {
    this._scrollContentElement = this.$(`${scrollContentSelector}`);
    this._scrollbarElement = this.$(`${scrollbarSelector}:first`);
    this._handleElement = this.$(`${handleSelector}:first`);
    this._contentElement = this.$(`${contentSelector}:first`);
  },

  setupResize() {
    this._resizeHandler = () => {
      debounce(this, this.resizeScrollbar, 16);
    };

    window.addEventListener('resize', this._resizeHandler, true);
  },

  createScrollbar() {
    let ScrollbarClass = this.get('horizontal') ? Horizontal : Vertical;

    return new ScrollbarClass({
      scrollContentElement: this._scrollContentElement,
      scrollbarElement: this._scrollbarElement,
      handleElement: this._handleElement,
      contentElement: this._contentElement,

      width: this.$().width(),
      height: this.$().height(),
      scrollbarWidth: this._scrollbarWidth
    });
  },

  startDrag(e) {
    // Preventing the event's default action stops text being
    // selectable during the drag.
    e.preventDefault();

    this.scrollbar.startDrag(e);

    this.on('mouseMove', this, this.drag);
    this.on('mouseUp', this, this.endDrag);
  },

  /**
  * Drag scrollbar handle
  */
  drag(e) {
    e.preventDefault();

    this.scrollbar.drag(e);
  },

  endDrag() {
    this.off('mouseMove', this, this.drag);
    this.off('mouseUp', this, this.endDrag);
  },

  jumpScroll(e) {
    // If the drag handle element was pressed, don't do anything here.
    if (e.target === this._handleElement[0]) {
      return;
    }

    this.scrollbar.jumpScroll(e);
  },

  scrolled() {
    this.scrollbar.update();
    this.showScrollbar();

    let scrollBuffer = this.get('scrollBuffer');

    if (this.scrollbar.isScrolledToBottom(scrollBuffer)) {
      debounce(this, this.sendScrolledToBottom, 100);
    }
  },

  sendScrolledToBottom() {
    this.sendAction('onScrolledToBottom');
  },

  resizeScrollbar() {
    if (!this.scrollbar) {
      return;
    }

    this.scrollbar = this.createScrollbar();

    if (!this.scrollbar.isNecessary()) {
      this.removeScrollbar();
    }

    this.scrollbar.update();
    this.showScrollbar();
  },

  removeScrollbar() {

  },

  showScrollbar() {
    if (this.isDestroyed) {
      return;
    }
    this.set('showHandle', true);

    if (!this.get('autoHide')) {
      return;
    }

    debounce(this, this.hideScrollbar, 1000);
  },

  hideScrollbar() {
    if (this.isDestroyed) {
      return;
    }
    this.set('showHandle', false);
  },

  willDestroyElement() {
    this._super(...arguments);

    window.removeEventListener('resize', this._resizeHandler, true);
  },

  actions: {

    /**
     * Update action should be called when size of the scroll area changes
     */
    recalculate() {
      this.resizeScrollbar();
    },

    /**
     * Can be called when scrollbars changes as a result of value change,
     *
     * for example
     * ```
     * {{#as-scrollable as |scrollbar|}}
     *   {{#each (compute scrollbar.update rows) as |row|}}
     *     {{row.title}}
     *   {{/each}}
     * {{/as-scrollable}}
     * ```
     */
    update(value) {
      scheduleOnce('afterRender', this, this.resizeScrollbar);
      return value;
    }
  }
});<commit_msg>Check if scrolled to bottom on first render<commit_after>import Ember from 'ember';
import { Horizontal, Vertical } from '../classes/scrollable';

const {
  run: { scheduleOnce, debounce, bind }
} = Ember;

const scrollbarSelector = '.tse-scrollbar';
const handleSelector = '.drag-handle';
const scrollContentSelector = '.tse-scroll-content';
const contentSelector = '.tse-content';

export default Ember.Mixin.create({
  classNameBindings: [':as-scrollable', ':tse-scrollable', 'horizontal'],

  horizontal: false,
  autoHide: true,
  scrollBuffer: 50,

  init() {
    this._super(...arguments);

    this.setupResize();
    this.measureScrollbar();
  },

  measureScrollbar() {

    /**
     * Calculate scrollbar width
     *
     * Original function by Jonathan Sharp:
     * http://jdsharp.us/jQuery/minute/calculate-scrollbar-width.php
     * Updated to work in Chrome v25.
     */
    function scrollbarWidth() {
      // Append a temporary scrolling element to the DOM, then measure
      // the difference between between its outer and inner elements.
      var tempEl = $('

');
      $('body').append(tempEl);
      var width = $(tempEl).innerWidth();
      var widthMinusScrollbars = $('div', tempEl).innerWidth();
      tempEl.remove();
      // On OS X if the scrollbar is set to auto hide it will have zero width. On webkit we can still
      // hide it using ::-webkit-scrollbar { width:0; height:0; } but there is no moz equivalent. So we're
      // forced to sniff Firefox and return a hard-coded scrollbar width. I know, I know...
      if (width === widthMinusScrollbars && navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
        return 17;
      }
      return (width - widthMinusScrollbars);
    }

    this._scrollbarWidth = scrollbarWidth();

  },

  didInsertElement() {
    this._super(...arguments);

    this.setupElements();

    if (this.get('autoHide')) {
      this.on('mouseEnter', this, this.showScrollbar);
    }

    this._handleElement.on('mousedown', bind(this, this.startDrag));
    this._scrollbarElement.on('mousedown', bind(this, this.jumpScroll));
    this._scrollContentElement.on('scroll', bind(this, this.scrolled));

    scheduleOnce('afterRender', this, this.setupScrollbar);
  },

  setupScrollbar() {
    this.scrollbar = this.createScrollbar();

    this.scrollbar.update();
    this.checkScrolledToBottom();
    this.showScrollbar();
  },

  setupElements() {
    this._scrollContentElement = this.$(`${scrollContentSelector}`);
    this._scrollbarElement = this.$(`${scrollbarSelector}:first`);
    this._handleElement = this.$(`${handleSelector}:first`);
    this._contentElement = this.$(`${contentSelector}:first`);
  },

  setupResize() {
    this._resizeHandler = () => {
      debounce(this, this.resizeScrollbar, 16);
    };

    window.addEventListener('resize', this._resizeHandler, true);
  },

  createScrollbar() {
    let ScrollbarClass = this.get('horizontal') ? Horizontal : Vertical;

    return new ScrollbarClass({
      scrollContentElement: this._scrollContentElement,
      scrollbarElement: this._scrollbarElement,
      handleElement: this._handleElement,
      contentElement: this._contentElement,

      width: this.$().width(),
      height: this.$().height(),
      scrollbarWidth: this._scrollbarWidth
    });
  },

  startDrag(e) {
    // Preventing the event's default action stops text being
    // selectable during the drag.
    e.preventDefault();

    this.scrollbar.startDrag(e);

    this.on('mouseMove', this, this.drag);
    this.on('mouseUp', this, this.endDrag);
  },

  /**
  * Drag scrollbar handle
  */
  drag(e) {
    e.preventDefault();

    this.scrollbar.drag(e);
  },

  endDrag() {
    this.off('mouseMove', this, this.drag);
    this.off('mouseUp', this, this.endDrag);
  },

  jumpScroll(e) {
    // If the drag handle element was pressed, don't do anything here.
    if (e.target === this._handleElement[0]) {
      return;
    }

    this.scrollbar.jumpScroll(e);
  },

  scrolled() {
    this.scrollbar.update();
    this.showScrollbar();

    this.checkScrolledToBottom();
  },

  checkScrolledToBottom() {
    let scrollBuffer = this.get('scrollBuffer');

    if (this.scrollbar.isScrolledToBottom(scrollBuffer)) {
      debounce(this, this.sendScrolledToBottom, 100);
    }
  },

  sendScrolledToBottom() {
    this.sendAction('onScrolledToBottom');
  },

  resizeScrollbar() {
    if (!this.scrollbar) {
      return;
    }

    this.scrollbar = this.createScrollbar();

    if (!this.scrollbar.isNecessary()) {
      this.removeScrollbar();
    }

    this.scrollbar.update();
    this.showScrollbar();
  },

  removeScrollbar() {

  },

  showScrollbar() {
    if (this.isDestroyed) {
      return;
    }
    this.set('showHandle', true);

    if (!this.get('autoHide')) {
      return;
    }

    debounce(this, this.hideScrollbar, 1000);
  },

  hideScrollbar() {
    if (this.isDestroyed) {
      return;
    }
    this.set('showHandle', false);
  },

  willDestroyElement() {
    this._super(...arguments);

    window.removeEventListener('resize', this._resizeHandler, true);
  },

  actions: {

    /**
     * Update action should be called when size of the scroll area changes
     */
    recalculate() {
      this.resizeScrollbar();
    },

    /**
     * Can be called when scrollbars changes as a result of value change,
     *
     * for example
     * ```
     * {{#as-scrollable as |scrollbar|}}
     *   {{#each (compute scrollbar.update rows) as |row|}}
     *     {{row.title}}
     *   {{/each}}
     * {{/as-scrollable}}
     * ```
     */
    update(value) {
      scheduleOnce('afterRender', this, this.resizeScrollbar);
      return value;
    }
  }
});<|endoftext|>


<commit_before><commit_msg>Update project howlerjs to v2.0.0-beta6<commit_after><|endoftext|>