<commit_before>var menu = function(game){
	this.menuSpace = 160;
	maxPlayers = 7;
  maxMods = 3;
	  keys = [
    Phaser.Keyboard.W,
    Phaser.Keyboard.P,
    Phaser.Keyboard.B,
    Phaser.Keyboard.Z,
    Phaser.Keyboard.M,
    Phaser.Keyboard.C,
    Phaser.Keyboard.R,
    Phaser.Keyboard.U,]
  numberPlayers = 0;
  mod = 0;
  bestScore = 0;
  ballsScore = 0;
  deathScore = 0;
  this.scoreLabel = null;
  this.scoreText = null;
  menuMusic = null;

}

menu.prototype = {
  create: function(){
    if (changeColor) {
      chosenColor = this.game.rnd.integerInRange(0, 3);
      colorHex = bgColors[chosenColor];
      colorHexDark = bgColorsDark[chosenColor];
      document.body.style.background = colorHex;
      this.game.stage.backgroundColor = colorHex;
      changeColor = false;
    }

    bgColor = Phaser.Color.hexToColor(colorHex);
  	this.game.stage.backgroundColor = colorHex;
    document.body.style.background = colorHex;
  	w2 = this.game.world.width/2;
		h2 = this.game.world.height/2;

  	this.game.world.scale.set(1);

    if (numberPlayers == 0) {
      if (!menuMusic && !mute) {
        menuMusic = this.game.add.audio('dream');
        menuMusic.loop = true;
        menuMusic.play();
      } else if (!menuMusic.isPlaying && !mute){
        menuMusic.loop = true;
        menuMusic.play();
        menuMusic.volume = 1;
      }
    }

  	bestScore = parseInt(localStorage.getItem("highScore"));
  	if(isNaN(bestScore)) {
  		bestScore = 0;
  	}

    ballsScore = parseInt(localStorage.getItem("ballsScore"));
    if(isNaN(ballsScore)) {
      ballsScore = 0;
    }

    deathScore = parseInt(localStorage.getItem("deathScore"));
    if(isNaN(deathScore)) {
      deathScore = 0;
    }

		//Game Title
		var text = this.game.add.text(w2,120, "curvatron", {
      font: "200px dosis",
      fill: "#ffffff",
      align: "center"
  	});
  	text.anchor.setTo(0.5,0.5);

    var text = this.game.add.text(w2+360,210, "BETA", {
      font: "50px dosis",
      fill: "#ffffff",
      align: "center"
    });
    text.anchor.setTo(0.5,0.5);

    //Single Player
		var spButton = this.game.add.button(w2-w2/4,h2,"singleplayer_button",this.modMenu,this);
		spButton.anchor.setTo(0.5,0.5);
		spButton.onInputOver.add(this.spOver, this);
		spButton.onInputOut.add(this.spOut, this);
    spButton.input.useHandCursor=true;

		//Score label that shows on hove
    if(bestScore != 0){
  		this.scoreLabel = this.game.add.sprite(w2-275,h2,"sp_score");
  		this.scoreLabel.anchor.setTo(0.5,0.5);
  		this.scoreLabel.alpha = 0;
  		this.scoreText = this.game.add.text(w2-300,h2+10, bestScore, {
          font: "120px dosis",
          fill: colorHex,
          align: "center"
    	});
    	this.scoreText.anchor.setTo(0.5,0.5);
    	this.scoreText.alpha = 0;
    }

    //Multiplayer
		var mpButton = this.game.add.button(w2+w2/4,h2,"multiplayer_button",this.multiplayer,this);
		mpButton.anchor.setTo(0.5,0.5);
    if (mobile) {
      mpButton.alpha = 0.2;
    }
    mpButton.input.useHandCursor=true;

    //FullScreen
    /*var fullScreenButton = this.game.add.button(w2+w2/2,h2+230,"fullscreen_button",this.fullScreen,this);
    fullScreenButton.anchor.setTo(0.5,0.5);
    fullScreenButton.input.useHandCursor=true;*/
    //SetKeys
    if(!mobile){
      var keysButton = this.game.add.button(w2+w2/2,h2+230,"setkeys_button",this.setKeys,this);
      keysButton.anchor.setTo(0.5,0.5);
      keysButton.input.useHandCursor=true;
    }

  	//Stats
  	var statsButton = this.game.add.button(w2,h2+230,"stats_button",this.stats,this);
		statsButton.anchor.setTo(0.5,0.5);
    statsButton.input.useHandCursor=true;
    if (mobile) {
      statsButton.x = w2+w2/6;
    }

  	//Audio
    if(mute){
    	audioButton = this.game.add.button(w2/2,h2+230,"audiooff_button",this.muteSound,this);
  		audioButton.anchor.setTo(0.5,0.5);
      audioButton.input.useHandCursor=true;
    }
    else{
      audioButton = this.game.add.button(w2/2,h2+230,"audio_button",this.muteSound,this);
      audioButton.anchor.setTo(0.5,0.5);
      audioButton.input.useHandCursor=true;
    }
    if (mobile) {
      audioButton.x = w2-w2/6;
    }
	},

	modMenu: function(){
		this.game.state.start("ModMenu",true,false);
	},

	multiplayer: function(){
    if (!mobile) {
      this.game.state.start("Multiplayer");
    }
	},

	setKeys: function() {
    if (!mobile) {
      this.game.state.start("SetKeys");
    }
	},

  stats: function() {
    this.game.state.start("Stats");
  },

	spOver: function() {
		this.game.add.tween(this.scoreLabel).to( { alpha: 1 }, 200, Phaser.Easing.Linear.None, true);
		this.game.add.tween(this.scoreText).to( { alpha: 1 }, 200, Phaser.Easing.Linear.None, true);

	},

	spOut: function() {
		this.game.add.tween(this.scoreLabel).to( { alpha: 0 }, 200, Phaser.Easing.Linear.None, true);
		this.game.add.tween(this.scoreText).to( { alpha: 0 }, 200, Phaser.Easing.Linear.None, true);
	},

  muteSound: function(){
    if(mute){
      audioButton.loadTexture('audio_button');
      //this.game.sound.mute = false;
      mute = false;
      if (!menuMusic) {
        menuMusic = this.game.add.audio('dream');
      }
      menuMusic.loop = true;
      menuMusic.play();
      menuMusic.volume = 1;
    }
    else{
      audioButton.loadTexture('audiooff_button');
      //this.game.sound.mute = true;
      mute = true;
      if (menuMusic && menuMusic.isPlaying) {
        menuMusic.stop();
      }
    }
  },

  fullScreen: function(){
    if (this.game.scale.isFullScreen){
      this.game.scale.stopFullScreen();
    }
    else{
      this.game.scale.startFullScreen(false);
    }
  },

  backPressed: function() {
    //exit game?
  }
}<commit_msg>minimal mobile menu changes<commit_after>var menu = function(game){
	this.menuSpace = 160;
	maxPlayers = 7;
  maxMods = 3;
	  keys = [
    Phaser.Keyboard.W,
    Phaser.Keyboard.P,
    Phaser.Keyboard.B,
    Phaser.Keyboard.Z,
    Phaser.Keyboard.M,
    Phaser.Keyboard.C,
    Phaser.Keyboard.R,
    Phaser.Keyboard.U,]
  numberPlayers = 0;
  mod = 0;
  bestScore = 0;
  ballsScore = 0;
  deathScore = 0;
  this.scoreLabel = null;
  this.scoreText = null;
  menuMusic = null;

}

menu.prototype = {
  create: function(){
    if (changeColor) {
      chosenColor = this.game.rnd.integerInRange(0, 3);
      colorHex = bgColors[chosenColor];
      colorHexDark = bgColorsDark[chosenColor];
      document.body.style.background = colorHex;
      this.game.stage.backgroundColor = colorHex;
      changeColor = false;
    }

    bgColor = Phaser.Color.hexToColor(colorHex);
  	this.game.stage.backgroundColor = colorHex;
    document.body.style.background = colorHex;
  	w2 = this.game.world.width/2;
		h2 = this.game.world.height/2;

  	this.game.world.scale.set(1);

    if (numberPlayers == 0) {
      if (!menuMusic && !mute) {
        menuMusic = this.game.add.audio('dream');
        menuMusic.loop = true;
        menuMusic.play();
      } else if (!menuMusic.isPlaying && !mute){
        menuMusic.loop = true;
        menuMusic.play();
        menuMusic.volume = 1;
      }
    }

  	bestScore = parseInt(localStorage.getItem("highScore"));
  	if(isNaN(bestScore)) {
  		bestScore = 0;
  	}

    ballsScore = parseInt(localStorage.getItem("ballsScore"));
    if(isNaN(ballsScore)) {
      ballsScore = 0;
    }

    deathScore = parseInt(localStorage.getItem("deathScore"));
    if(isNaN(deathScore)) {
      deathScore = 0;
    }

		//Game Title
		var text = this.game.add.text(w2,120, "curvatron", {
      font: "200px dosis",
      fill: "#ffffff",
      align: "center"
  	});
  	text.anchor.setTo(0.5,0.5);
    if(mobile){
      text.fontSize = 150;
    }

    var text = this.game.add.text(w2+360,210, "BETA", {
      font: "50px dosis",
      fill: "#ffffff",
      align: "center"
    });
    text.anchor.setTo(0.5,0.5);
    if(mobile){
      text.x = w2+260;
    }

    //Single Player
		var spButton = this.game.add.button(w2-170,h2,"singleplayer_button",this.modMenu,this);
		spButton.anchor.setTo(0.5,0.5);
		spButton.onInputOver.add(this.spOver, this);
		spButton.onInputOut.add(this.spOut, this);
    spButton.input.useHandCursor=true;

		//Score label that shows on hove
    if(bestScore != 0){
  		this.scoreLabel = this.game.add.sprite(w2-270,h2,"sp_score");
  		this.scoreLabel.anchor.setTo(0.5,0.5);
  		this.scoreLabel.alpha = 0;
  		this.scoreText = this.game.add.text(w2-315,h2+10, bestScore, {
          font: "120px dosis",
          fill: colorHex,
          align: "center"
    	});
    	this.scoreText.anchor.setTo(0.5,0.5);
    	this.scoreText.alpha = 0;
    }

    //Multiplayer
		var mpButton = this.game.add.button(w2+170,h2,"multiplayer_button",this.multiplayer,this);
		mpButton.anchor.setTo(0.5,0.5);
    if (mobile) {
      mpButton.alpha = 0.2;
    }
    mpButton.input.useHandCursor=true;

    //FullScreen
    /*var fullScreenButton = this.game.add.button(w2+w2/2,h2+230,"fullscreen_button",this.fullScreen,this);
    fullScreenButton.anchor.setTo(0.5,0.5);
    fullScreenButton.input.useHandCursor=true;*/
    //SetKeys
    if(!mobile){
      var keysButton = this.game.add.button(w2+w2/2,h2+230,"setkeys_button",this.setKeys,this);
      keysButton.anchor.setTo(0.5,0.5);
      keysButton.input.useHandCursor=true;
    }

  	//Stats
  	var statsButton = this.game.add.button(w2,h2+230,"stats_button",this.stats,this);
		statsButton.anchor.setTo(0.5,0.5);
    statsButton.input.useHandCursor=true;
    if (mobile) {
      statsButton.x = w2+120;
    }

  	//Audio
    if(mute){
    	audioButton = this.game.add.button(w2/2,h2+230,"audiooff_button",this.muteSound,this);
  		audioButton.anchor.setTo(0.5,0.5);
      audioButton.input.useHandCursor=true;
    }
    else{
      audioButton = this.game.add.button(w2/2,h2+230,"audio_button",this.muteSound,this);
      audioButton.anchor.setTo(0.5,0.5);
      audioButton.input.useHandCursor=true;
    }
    if (mobile) {
      audioButton.x = w2-120;
    }
	},

	modMenu: function(){
		this.game.state.start("ModMenu",true,false);
	},

	multiplayer: function(){
    if (!mobile) {
      this.game.state.start("Multiplayer");
    }
	},

	setKeys: function() {
    if (!mobile) {
      this.game.state.start("SetKeys");
    }
	},

  stats: function() {
    this.game.state.start("Stats");
  },

	spOver: function() {
		this.game.add.tween(this.scoreLabel).to( { alpha: 1 }, 200, Phaser.Easing.Linear.None, true);
		this.game.add.tween(this.scoreText).to( { alpha: 1 }, 200, Phaser.Easing.Linear.None, true);

	},

	spOut: function() {
		this.game.add.tween(this.scoreLabel).to( { alpha: 0 }, 200, Phaser.Easing.Linear.None, true);
		this.game.add.tween(this.scoreText).to( { alpha: 0 }, 200, Phaser.Easing.Linear.None, true);
	},

  muteSound: function(){
    if(mute){
      audioButton.loadTexture('audio_button');
      //this.game.sound.mute = false;
      mute = false;
      if (!menuMusic) {
        menuMusic = this.game.add.audio('dream');
      }
      menuMusic.loop = true;
      menuMusic.play();
      menuMusic.volume = 1;
    }
    else{
      audioButton.loadTexture('audiooff_button');
      //this.game.sound.mute = true;
      mute = true;
      if (menuMusic && menuMusic.isPlaying) {
        menuMusic.stop();
      }
    }
  },

  fullScreen: function(){
    if (this.game.scale.isFullScreen){
      this.game.scale.stopFullScreen();
    }
    else{
      this.game.scale.startFullScreen(false);
    }
  },

  backPressed: function() {
    //exit game?
  }
}<|endoftext|>


<commit_before>var check = require('check-types');
var code = require('./code');

function parseEqualArguments(equal) {
	check.verifyString(equal, 'equal is not a string');

	console.log('splitting', equal);
	var split = code.split(equal);
	check.verifyArray(split, 'did not get array from', equal);
	console.log(split);
	var result = {
		op: split[0],
		expected: split[1]
	};
	return result;
}

function parseEqual(line) {
	var isEqualReg = /(?:gt|QUnit)\.equal\(([\W\w]+)\);/;
	if (!isEqualReg.test(line)) {
		return null;
	}
	var matches = isEqualReg.exec(line);
	console.log('matches', matches);
	var equalArguments = matches[1];
	check.verifyString(equalArguments, 'invalid equal arguments');
	var parsed = parseEqualArguments(equalArguments);
	check.verifyObject(parsed, 'did not get parsed arguments');
	return parsed.op + '; // ' + parsed.expected;
}

function parseAssertion(line) {
	check.verifyString(line, 'missing line');

	var equalParsed = parseEqual(line);
	if (check.isString(equalParsed)) {
		return equalParsed;
	}

	var equalReg = /(?:gt|QUnit)\.equal\(([\W\w]+),\s*([\W\w]+)\s*\)/;
	var matched = equalReg.exec(line);
	console.log(matched);
	if (!Array.isArray(matched)) {
		return line;
	}

	var op = matched[1];
	var output = matched[2];
	if (!check.isString(op) ||
		!check.isString(output)) {
		return line;
	}

	return op + '; // ' + output;
}

// parses multiline list of assertions in the code
// replaces all gt.ok(...) and other assertions with
// human-readable code
function parseUnitTestCode(text) {
	check.verifyString(text, 'missing text');
	var lines = text.split('\n');
	var transformedLines = lines.map(parseAssertion);
	var output = transformedLines.join('\n');
	return output;
}

module.exports = {
	parseUnitTestCode: parseUnitTestCode
};<commit_msg>added gt.number support<commit_after>var check = require('check-types');
var code = require('./code');

function parseEqualArguments(equal) {
	check.verifyString(equal, 'equal is not a string');

	console.log('splitting', equal);
	var split = code.split(equal);
	check.verifyArray(split, 'did not get array from', equal);
	console.log(split);
	var result = {
		op: split[0],
		expected: split[1]
	};
	return result;
}

function parseNumberArguments(args) {
	check.verifyString(args, 'args is not a string');

	console.log('splitting', args);
	var split = code.split(args);
	check.verifyArray(split, 'did not get array from', args);
	var result = {
		op: split[0]
	};
	return result;
}

function parseEqual(line) {
	var isEqualReg = /(?:gt|QUnit)\.equal\(([\W\w]+)\);/;
	if (!isEqualReg.test(line)) {
		return null;
	}
	var matches = isEqualReg.exec(line);
	console.log('matches', matches);
	var equalArguments = matches[1];
	check.verifyString(equalArguments, 'invalid equal arguments');
	var parsed = parseEqualArguments(equalArguments);
	check.verifyObject(parsed, 'did not get parsed arguments');
	return parsed.op + '; // ' + parsed.expected;
}

function parseNumber(line) {
	var reg = /(?:gt|QUnit)\.number\(([\W\w]+)\);/;
	if (!reg.test(line)) {
		return null;
	}
	var matches = reg.exec(line);
	console.log('number matches', matches);
	var args = matches[1];
	check.verifyString(args, 'invalid number arguments');
	var parsed = parseNumberArguments(args);
	check.verifyObject(parsed, 'did not get parsed arguments');
	return parsed.op + '; // returns a number';
}

function parseAssertion(line) {
	check.verifyString(line, 'missing line');

	var parsed = parseEqual(line);
	if (check.isString(parsed)) {
		return parsed;
	}
	parsed = parseNumber(line);
	if (check.isString(parsed)) {
		return parsed;
	}

	var equalReg = /(?:gt|QUnit)\.equal\(([\W\w]+),\s*([\W\w]+)\s*\)/;
	var matched = equalReg.exec(line);
	console.log(matched);
	if (!Array.isArray(matched)) {
		var cleaned = code.reformat(line);
		return cleaned;
	}

	var op = matched[1];
	var output = matched[2];
	if (!check.isString(op) ||
		!check.isString(output)) {
		return line;
	}

	return op + '; // ' + output;
}

// parses multiline list of assertions in the code
// replaces all gt.ok(...) and other assertions with
// human-readable code
function parseUnitTestCode(text) {
	check.verifyString(text, 'missing text');
	var lines = text.split('\n');
	var transformedLines = lines.map(parseAssertion);
	var output = transformedLines.join('\n');
	return output;
}

module.exports = {
	parseUnitTestCode: parseUnitTestCode
};<|endoftext|>


<commit_before>import {extent, min, max} from "d3-array";
import {nest} from "d3-collection";
import * as scales from "d3-scale";
import {stack} from "d3-shape";
import {mouse} from "d3-selection";

import {AxisBottom, AxisLeft, date} from "d3plus-axis";
import {assign} from "d3plus-color";
import {accessor, closest, constant, elem} from "d3plus-common";
import * as shapes from "d3plus-shape";
import {Viz} from "d3plus-viz";

import {default as CircleBuffer} from "./buffers/Circle.js";
import {default as RectBuffer} from "./buffers/Rect.js";
import {default as LineBuffer} from "./buffers/Line.js";

/**
    @class Plot
    @extends Viz
    @desc Creates an x/y plot based on an array of data.
*/
export default class Plot extends Viz {

  /**
      @memberof Plot
      @desc Invoked when creating a new class instance, and sets any default parameters.
      @private
  */
  constructor() {

    super();
    this._buffer = {
      Circle: CircleBuffer,
      Line: LineBuffer,
      Rect: RectBuffer
    };
    this._shape = constant("Circle");
    this._shapeConfig = Object.assign(this._shapeConfig, {
      Circle: {
        r: constant(5)
      },
      Line: {
        fill: constant("none"),
        label: false,
        stroke: (d, i) => assign(this._id(d, i)),
        strokeWidth: constant(1)
      },
      Rect: {
        height: constant(10),
        width: constant(10)
      }
    });
    this._x = accessor("x");
    this._xAxis = new AxisBottom().align("end");
    this._xTest = new AxisBottom().align("end").gridSize(0);
    this._xConfig = {
      title: "X Axis"
    };
    this._y = accessor("y");
    this._yAxis = new AxisLeft().align("start");
    this._yTest = new AxisLeft().align("start").gridSize(0);
    this._yConfig = {
      title: "Y Axis"
    };

  }

  /**
      Extends the render behavior of the abstract Viz class.
      @private
  */
  render(callback) {

    super.render(callback);

    const data = this._filteredData.map((d, i) => ({
            data: d,
            i,
            id: this._id(d, i),
            shape: this._shape(d, i),
            x: this._x(d, i),
            y: this._y(d, i)
          })),
          height = this._height - this._margin.top - this._margin.bottom,
          opp = this._discrete ? this._discrete === "x" ? "y" : "x" : undefined,
          parent = this._select,
          that = this,
          transform = `translate(${this._margin.left}, ${this._margin.top})`,
          transition = this._transition,
          width = this._width - this._margin.left - this._margin.right;

    let domains, stackData, stackKeys;
    if (this._stacked) {

      stackKeys = Array.from(new Set(data.map(d => d.id)));
      stackData = stack()
        .keys(stackKeys)
        .value((group, key) => {
          const d = group.filter(g => g.id === key);
          return d.length ? d[0][opp] : 0;
        })
        (nest().key(d => d[this._discrete]).entries(data).map(d => d.values));

      domains = {
        [this._discrete]: extent(data, d => d[this._discrete]),
        [opp]: [min(stackData.map(g => min(g.map(p => p[1])))), max(stackData.map(g => max(g.map(p => p[1]))))]
      };

    }
    else domains = {x: extent(data, d => d.x), y: extent(data, d => d.y)};

    const xTime = this._time && this._x(data[0], 0) === this._time(data[0], 0),
          yTime = this._time && this._y(data[0], 0) === this._time(data[0], 0);

    if (xTime || yTime) {
      data.forEach(d => {
        if (xTime) d.x = date(d.x);
        if (yTime) d.y = date(d.y);
      });
    }

    let xDomain = this._xDomain ? this._xDomain.slice() : domains.x;
    if (xDomain[0] === void 0) xDomain[0] = domains.x[0];
    if (xDomain[1] === void 0) xDomain[1] = domains.x[1];
    if (xTime) xDomain = xDomain.map(date);

    let yDomain = this._yDomain ? this._yDomain.slice() : domains.y;
    if (yDomain[0] === void 0) yDomain[0] = domains.y[0];
    if (yDomain[1] === void 0) yDomain[1] = domains.y[1];
    if (yTime) yDomain = yDomain.map(date);

    domains = {x: xDomain, y: yDomain};

    if (opp && this._baseline !== void 0) {
      const b = this._baseline;
      if (domains[opp][0] > b) domains[opp][0] = b;
      else if (domains[opp][1] < b) domains[opp][1] = b;
    }

    let x = scales[`scale${xTime ? "Time" : "Linear"}`]().domain(domains.x).range([0, width]),
        y = scales[`scale${yTime ? "Time" : "Linear"}`]().domain(domains.y.reverse()).range([0, height]);

    const shapeData = nest().key(d => d.shape).entries(data);
    shapeData.forEach(d => {
      if (this._buffer[d.key]) {
        const res = this._buffer[d.key].bind(this)(d.values, x, y, this._shapeConfig[d.key]);
        x = res[0];
        y = res[1];
      }
    });
    xDomain = x.domain();
    yDomain = y.domain();

    const xTicks = this._discrete === "x" && !xTime ? Array.from(new Set(data.map(d => d.x))) : undefined,
          yTicks = this._discrete === "y" && !yTime ? Array.from(new Set(data.map(d => d.y))) : undefined;

    this._yTest
      .domain(yDomain)
      .height(height)
      .scale(yTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-test", {enter: {opacity: 0}, parent: this._select}).node())
      .ticks(yTicks)
      .width(width)
      .config(this._yConfig)
      .render();

    const xOffset = this._yTest.outerBounds().width + this._yTest.padding();

    this._xTest
      .domain(xDomain)
      .height(height)
      .range([xOffset, undefined])
      .scale(xTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-test", {enter: {opacity: 0}, parent: this._select}).node())
      .ticks(xTicks)
      .width(width)
      .config(this._xConfig)
      .render();

    this._xAxis
      .domain(xDomain)
      .height(height)
      .range([xOffset, undefined])
      .scale(xTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-x-axis", {parent, transition, enter: {transform}, update: {transform}}).node())
      .ticks(xTicks)
      .width(width)
      .config(this._xConfig)
      .render();

    x = this._xAxis._d3Scale;

    this._yAxis
      .domain(yDomain)
      .height(height)
      .range([this._xAxis.outerBounds().y, this._xTest.outerBounds().y])
      .scale(yTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-y-axis", {parent, transition, enter: {transform}, update: {transform}}).node())
      .ticks(yTicks)
      .width(x.range()[1] + this._xAxis.padding())
      .config(this._yConfig)
      .render();

    y = this._yAxis._d3Scale;

    let shapeConfig = {
      duration: this._duration,
      label: d => this._drawLabel(d.data, d.i),
      select: elem("g.d3plus-plot-shapes", {parent, transition, enter: {transform}, update: {transform}}).node(),
      x: d => x(d.x),
      y: d => y(d.y)
    };

    function wrapConfig(config) {
      const obj = {};
      for (const k in config) {
        if ({}.hasOwnProperty.call(config, k) && !shapes[k]) {
          obj[k] = typeof config[k] === "function" ? d => config[k](d.data, d.i) : config[k];
        }
      }
      return obj;
    }

    shapeConfig = Object.assign(shapeConfig, wrapConfig(this._shapeConfig));

    const positions = {
      x0: this._discrete === "x" ? shapeConfig.x : x(0),
      x1: this._discrete === "x" ? null : shapeConfig.x,
      y0: this._discrete === "y" ? shapeConfig.y : y(0),
      y1: this._discrete === "y" ? null : shapeConfig.y
    };

    if (this._stacked) {
      const scale = opp === "x" ? x : y;
      positions[`${opp}0`] = (d, i) => {
        const index = stackKeys.indexOf(d.id);
        return index >= 0 ? scale(stackData[index][i][0]) : scale(0);
      };
      positions[`${opp}1`] = (d, i) => {
        const index = stackKeys.indexOf(d.id);
        return index >= 0 ? scale(stackData[index][i][1]) : scale(0);
      };
    }

    shapeConfig = Object.assign(shapeConfig, positions);

    /**
        @desc Handles mouse events for nested shapes, finding the closest discrete data point to send to the defined event function.
        @private
    */
    function mouseEvent(d) {
      if (d.nested && d.values) {
        const axis = that._discrete,
              cursor = mouse(that._select.node())[axis === "x" ? 0 : 1],
              values = d.values.map(d => shapeConfig[axis](d));
        d = d.values[values.indexOf(closest(cursor, values))];
      }
      return this(d.data, d.i);
    }

    const events = Object.keys(this._on);
    shapeData.forEach(d => {

      const s = new shapes[d.key]().config(shapeConfig).data(d.values);
      const classEvents = events.filter(e => e.includes(`.${d.key}`)),
            globalEvents = events.filter(e => !e.includes(".")),
            shapeEvents = events.filter(e => e.includes(".shape"));
      for (let e = 0; e < globalEvents.length; e++) s.on(globalEvents[e], mouseEvent.bind(this._on[globalEvents[e]]));
      for (let e = 0; e < shapeEvents.length; e++) s.on(shapeEvents[e], mouseEvent.bind(this._on[shapeEvents[e]]));
      for (let e = 0; e < classEvents.length; e++) s.on(classEvents[e], mouseEvent.bind(this._on[classEvents[e]]));
      s.config(this._shapeConfig[d.key] ? wrapConfig(this._shapeConfig[d.key]) : {}).render();
      this._shapes.push(s);

    });

    return this;

  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the baseline for the x/y plot and returns the current class instance. If *value* is not specified, returns the current baseline.
      @param {Number} [*value*]
  */
  baseline(_) {
    return arguments.length ? (this._baseline = _, this) : this._baseline;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, toggles shape stacking and returns the current class instance. If *value* is not specified, returns the current stack value.
      @param {Boolean} [*value* = false]
  */
  stacked(_) {
    return arguments.length ? (this._stacked = _, this) : this._stacked;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the x accessor to the specified function or number and returns the current class instance. If *value* is not specified, returns the current x accessor.
      @param {Function|Number} [*value*]
  */
  x(_) {
    if (arguments.length) {
      if (typeof _ === "function") this._x = _;
      else {
        this._x = accessor(_);
        if (!this._aggs[_] && this._discrete === "x") {
          this._aggs[_] = a => {
            const v = Array.from(new Set(a));
            return v.length === 1 ? v[0] : v;
          };
        }
      }
      return this;
    }
    else return this._x;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the config method for the x-axis and returns the current class instance. If *value* is not specified, returns the current x-axis configuration.
      @param {Object} [*value*]
  */
  xConfig(_) {
    return arguments.length ? (this._xConfig = Object.assign(this._xConfig, _), this) : this._xConfig;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the x domain to the specified array and returns the current class instance. If *value* is not specified, returns the current x domain. Additionally, if either value of the array is undefined, it will be calculated from the data.
      @param {Array} [*value*]
  */
  xDomain(_) {
    return arguments.length ? (this._xDomain = _, this) : this._xDomain;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the y accessor to the specified function or number and returns the current class instance. If *value* is not specified, returns the current y accessor.
      @param {Function|Number} [*value*]
  */
  y(_) {
    if (arguments.length) {
      if (typeof _ === "function") this._y = _;
      else {
        this._y = accessor(_);
        if (!this._aggs[_] && this._discrete === "y") {
          this._aggs[_] = a => {
            const v = Array.from(new Set(a));
            return v.length === 1 ? v[0] : v;
          };
        }
      }
      return this;
    }
    else return this._y;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the config method for the y-axis and returns the current class instance. If *value* is not specified, returns the current y-axis configuration.
      @param {Object} [*value*]
  */
  yConfig(_) {
    return arguments.length ? (this._yConfig = Object.assign(this._yConfig, _), this) : this._yConfig;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the y domain to the specified array and returns the current class instance. If *value* is not specified, returns the current y domain. Additionally, if either value of the array is undefined, it will be calculated from the data.
      @param {Array} [*value*]
  */
  yDomain(_) {
    return arguments.length ? (this._yDomain = _, this) : this._yDomain;
  }

}
<commit_msg>fills zero data for StackedArea plots<commit_after>import {extent, min, max} from "d3-array";
import {nest} from "d3-collection";
import * as scales from "d3-scale";
import {stack} from "d3-shape";
import {mouse} from "d3-selection";

import {AxisBottom, AxisLeft, date} from "d3plus-axis";
import {assign} from "d3plus-color";
import {accessor, closest, constant, elem} from "d3plus-common";
import * as shapes from "d3plus-shape";
import {Viz} from "d3plus-viz";

import {default as CircleBuffer} from "./buffers/Circle.js";
import {default as RectBuffer} from "./buffers/Rect.js";
import {default as LineBuffer} from "./buffers/Line.js";

/**
    @class Plot
    @extends Viz
    @desc Creates an x/y plot based on an array of data.
*/
export default class Plot extends Viz {

  /**
      @memberof Plot
      @desc Invoked when creating a new class instance, and sets any default parameters.
      @private
  */
  constructor() {

    super();
    this._buffer = {
      Circle: CircleBuffer,
      Line: LineBuffer,
      Rect: RectBuffer
    };
    this._shape = constant("Circle");
    this._shapeConfig = Object.assign(this._shapeConfig, {
      Circle: {
        r: constant(5)
      },
      Line: {
        fill: constant("none"),
        label: false,
        stroke: (d, i) => assign(this._id(d, i)),
        strokeWidth: constant(1)
      },
      Rect: {
        height: constant(10),
        width: constant(10)
      }
    });
    this._x = accessor("x");
    this._xAxis = new AxisBottom().align("end");
    this._xTest = new AxisBottom().align("end").gridSize(0);
    this._xConfig = {
      title: "X Axis"
    };
    this._y = accessor("y");
    this._yAxis = new AxisLeft().align("start");
    this._yTest = new AxisLeft().align("start").gridSize(0);
    this._yConfig = {
      title: "Y Axis"
    };

  }

  /**
      Extends the render behavior of the abstract Viz class.
      @private
  */
  render(callback) {

    super.render(callback);

    let data = this._filteredData.map((d, i) => ({
      data: d,
      i,
      id: this._id(d, i),
      shape: this._shape(d, i),
      x: this._x(d, i),
      y: this._y(d, i)
    }));

    const height = this._height - this._margin.top - this._margin.bottom,
          opp = this._discrete ? this._discrete === "x" ? "y" : "x" : undefined,
          parent = this._select,
          that = this,
          transform = `translate(${this._margin.left}, ${this._margin.top})`,
          transition = this._transition,
          width = this._width - this._margin.left - this._margin.right;

    let domains, stackData, stackKeys;
    if (this._stacked) {

      stackKeys = Array.from(new Set(data.map(d => d.id)));

      stackData = nest().key(d => d[this._discrete]).entries(data).map(d => d.values);

      stackData.forEach((g, i) => {
        const ids = Array.from(new Set(g.map(d => d.id)));
        if (ids.length < stackKeys.length) {
          stackKeys.forEach(k => {
            if (!ids.includes(k)) {
              const d = data.filter(d => d.id === k)[0];
              if (d.shape === "Area") {
                const fillerPoint = {
                  data: d.data,
                  id: k,
                  shape: d.shape,
                  [this._discrete]: g[0][this._discrete],
                  [opp]: 0
                };
                stackData[i].push(fillerPoint);
                data.push(fillerPoint);
              }
            }
          });
        }
      });

      data = data.sort((a, b) => a[this._discrete] - b[this._discrete]);

      stackData = stack()
        .keys(stackKeys)
        .value((group, key) => {
          const d = group.filter(g => g.id === key);
          return d.length ? d[0][opp] : 0;
        })(stackData);

      domains = {
        [this._discrete]: extent(data, d => d[this._discrete]),
        [opp]: [min(stackData.map(g => min(g.map(p => p[1])))), max(stackData.map(g => max(g.map(p => p[1]))))]
      };

    }
    else domains = {x: extent(data, d => d.x), y: extent(data, d => d.y)};

    const xTime = this._time && this._x(data[0], 0) === this._time(data[0], 0),
          yTime = this._time && this._y(data[0], 0) === this._time(data[0], 0);

    if (xTime || yTime) {
      data.forEach(d => {
        if (xTime) d.x = date(d.x);
        if (yTime) d.y = date(d.y);
      });
    }

    let xDomain = this._xDomain ? this._xDomain.slice() : domains.x;
    if (xDomain[0] === void 0) xDomain[0] = domains.x[0];
    if (xDomain[1] === void 0) xDomain[1] = domains.x[1];
    if (xTime) xDomain = xDomain.map(date);

    let yDomain = this._yDomain ? this._yDomain.slice() : domains.y;
    if (yDomain[0] === void 0) yDomain[0] = domains.y[0];
    if (yDomain[1] === void 0) yDomain[1] = domains.y[1];
    if (yTime) yDomain = yDomain.map(date);

    domains = {x: xDomain, y: yDomain};

    if (opp && this._baseline !== void 0) {
      const b = this._baseline;
      if (domains[opp][0] > b) domains[opp][0] = b;
      else if (domains[opp][1] < b) domains[opp][1] = b;
    }

    let x = scales[`scale${xTime ? "Time" : "Linear"}`]().domain(domains.x).range([0, width]),
        y = scales[`scale${yTime ? "Time" : "Linear"}`]().domain(domains.y.reverse()).range([0, height]);

    const shapeData = nest().key(d => d.shape).entries(data);
    shapeData.forEach(d => {
      if (this._buffer[d.key]) {
        const res = this._buffer[d.key].bind(this)(d.values, x, y, this._shapeConfig[d.key]);
        x = res[0];
        y = res[1];
      }
    });
    xDomain = x.domain();
    yDomain = y.domain();

    const xTicks = this._discrete === "x" && !xTime ? Array.from(new Set(data.map(d => d.x))) : undefined,
          yTicks = this._discrete === "y" && !yTime ? Array.from(new Set(data.map(d => d.y))) : undefined;

    this._yTest
      .domain(yDomain)
      .height(height)
      .scale(yTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-test", {enter: {opacity: 0}, parent: this._select}).node())
      .ticks(yTicks)
      .width(width)
      .config(this._yConfig)
      .render();

    const xOffset = this._yTest.outerBounds().width + this._yTest.padding();

    this._xTest
      .domain(xDomain)
      .height(height)
      .range([xOffset, undefined])
      .scale(xTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-test", {enter: {opacity: 0}, parent: this._select}).node())
      .ticks(xTicks)
      .width(width)
      .config(this._xConfig)
      .render();

    this._xAxis
      .domain(xDomain)
      .height(height)
      .range([xOffset, undefined])
      .scale(xTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-x-axis", {parent, transition, enter: {transform}, update: {transform}}).node())
      .ticks(xTicks)
      .width(width)
      .config(this._xConfig)
      .render();

    x = this._xAxis._d3Scale;

    this._yAxis
      .domain(yDomain)
      .height(height)
      .range([this._xAxis.outerBounds().y, this._xTest.outerBounds().y])
      .scale(yTime ? "time" : "linear")
      .select(elem("g.d3plus-plot-y-axis", {parent, transition, enter: {transform}, update: {transform}}).node())
      .ticks(yTicks)
      .width(x.range()[1] + this._xAxis.padding())
      .config(this._yConfig)
      .render();

    y = this._yAxis._d3Scale;

    let shapeConfig = {
      duration: this._duration,
      label: d => this._drawLabel(d.data, d.i),
      select: elem("g.d3plus-plot-shapes", {parent, transition, enter: {transform}, update: {transform}}).node(),
      x: d => x(d.x),
      y: d => y(d.y)
    };

    function wrapConfig(config) {
      const obj = {};
      for (const k in config) {
        if ({}.hasOwnProperty.call(config, k) && !shapes[k]) {
          obj[k] = typeof config[k] === "function" ? d => config[k](d.data, d.i) : config[k];
        }
      }
      return obj;
    }

    shapeConfig = Object.assign(shapeConfig, wrapConfig(this._shapeConfig));

    const positions = {
      x0: this._discrete === "x" ? shapeConfig.x : x(0),
      x1: this._discrete === "x" ? null : shapeConfig.x,
      y0: this._discrete === "y" ? shapeConfig.y : y(0),
      y1: this._discrete === "y" ? null : shapeConfig.y
    };

    if (this._stacked) {
      const scale = opp === "x" ? x : y;
      positions[`${opp}0`] = (d, i) => {
        const index = stackKeys.indexOf(d.id);
        return index >= 0 ? scale(stackData[index][i][0]) : scale(0);
      };
      positions[`${opp}1`] = (d, i) => {
        const index = stackKeys.indexOf(d.id);
        return index >= 0 ? scale(stackData[index][i][1]) : scale(0);
      };
    }

    shapeConfig = Object.assign(shapeConfig, positions);

    /**
        @desc Handles mouse events for nested shapes, finding the closest discrete data point to send to the defined event function.
        @private
    */
    function mouseEvent(d) {
      if (d.nested && d.values) {
        const axis = that._discrete,
              cursor = mouse(that._select.node())[axis === "x" ? 0 : 1],
              values = d.values.map(d => shapeConfig[axis](d));
        d = d.values[values.indexOf(closest(cursor, values))];
      }
      return this(d.data, d.i);
    }

    const events = Object.keys(this._on);
    shapeData.forEach(d => {

      const s = new shapes[d.key]().config(shapeConfig).data(d.values);
      const classEvents = events.filter(e => e.includes(`.${d.key}`)),
            globalEvents = events.filter(e => !e.includes(".")),
            shapeEvents = events.filter(e => e.includes(".shape"));
      for (let e = 0; e < globalEvents.length; e++) s.on(globalEvents[e], mouseEvent.bind(this._on[globalEvents[e]]));
      for (let e = 0; e < shapeEvents.length; e++) s.on(shapeEvents[e], mouseEvent.bind(this._on[shapeEvents[e]]));
      for (let e = 0; e < classEvents.length; e++) s.on(classEvents[e], mouseEvent.bind(this._on[classEvents[e]]));
      s.config(this._shapeConfig[d.key] ? wrapConfig(this._shapeConfig[d.key]) : {}).render();
      this._shapes.push(s);

    });

    return this;

  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the baseline for the x/y plot and returns the current class instance. If *value* is not specified, returns the current baseline.
      @param {Number} [*value*]
  */
  baseline(_) {
    return arguments.length ? (this._baseline = _, this) : this._baseline;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, toggles shape stacking and returns the current class instance. If *value* is not specified, returns the current stack value.
      @param {Boolean} [*value* = false]
  */
  stacked(_) {
    return arguments.length ? (this._stacked = _, this) : this._stacked;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the x accessor to the specified function or number and returns the current class instance. If *value* is not specified, returns the current x accessor.
      @param {Function|Number} [*value*]
  */
  x(_) {
    if (arguments.length) {
      if (typeof _ === "function") this._x = _;
      else {
        this._x = accessor(_);
        if (!this._aggs[_] && this._discrete === "x") {
          this._aggs[_] = a => {
            const v = Array.from(new Set(a));
            return v.length === 1 ? v[0] : v;
          };
        }
      }
      return this;
    }
    else return this._x;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the config method for the x-axis and returns the current class instance. If *value* is not specified, returns the current x-axis configuration.
      @param {Object} [*value*]
  */
  xConfig(_) {
    return arguments.length ? (this._xConfig = Object.assign(this._xConfig, _), this) : this._xConfig;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the x domain to the specified array and returns the current class instance. If *value* is not specified, returns the current x domain. Additionally, if either value of the array is undefined, it will be calculated from the data.
      @param {Array} [*value*]
  */
  xDomain(_) {
    return arguments.length ? (this._xDomain = _, this) : this._xDomain;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the y accessor to the specified function or number and returns the current class instance. If *value* is not specified, returns the current y accessor.
      @param {Function|Number} [*value*]
  */
  y(_) {
    if (arguments.length) {
      if (typeof _ === "function") this._y = _;
      else {
        this._y = accessor(_);
        if (!this._aggs[_] && this._discrete === "y") {
          this._aggs[_] = a => {
            const v = Array.from(new Set(a));
            return v.length === 1 ? v[0] : v;
          };
        }
      }
      return this;
    }
    else return this._y;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the config method for the y-axis and returns the current class instance. If *value* is not specified, returns the current y-axis configuration.
      @param {Object} [*value*]
  */
  yConfig(_) {
    return arguments.length ? (this._yConfig = Object.assign(this._yConfig, _), this) : this._yConfig;
  }

  /**
      @memberof Plot
      @desc If *value* is specified, sets the y domain to the specified array and returns the current class instance. If *value* is not specified, returns the current y domain. Additionally, if either value of the array is undefined, it will be calculated from the data.
      @param {Array} [*value*]
  */
  yDomain(_) {
    return arguments.length ? (this._yDomain = _, this) : this._yDomain;
  }

}
<|endoftext|>


<commit_before>/**
 * @class Oskari.mapframework.bundle.parcel.view.MainView
 *
 * Handles events related to the tool selections.
 * Also, starts the saving flow for the feature data when 'Parcel.SaveDrawingEvent' is received.
 * If saving flow is started, this view shows the save form before continuing to the WFST operation.
 */
Oskari.clazz.define("Oskari.mapframework.bundle.parcel.view.MainView",

/**
 * @method create called automatically on construction
 * @static
 */
function(instance) {
    this.instance = instance;
    this.popupId = 'parcelForm';
    this.form = undefined;
}, {
    /**
     * @method getName
     * @return {String} the name for the component
     */
    getName : function() {
        return 'ParcelMainView';
    },
    /**
     * @method getSandbox
     * @return {Oskari.mapframework.sandbox.Sandbox}
     */
    getSandbox : function() {
        return this.instance.sandbox;
    },
    /**
     * @method init
     * implements Module protocol init method
     */
    init : function() {
    },
    /**
     * @method start
     * implements Module protocol start methdod
     */
    start : function() {
        var me = this;

        var sandbox = this.instance.sandbox;
        sandbox.register(me);
        for (p in me.eventHandlers) {
            sandbox.registerForEventByName(me, p);
        }

        var mapModule = sandbox.findRegisteredModuleInstance('MainMapModule');

        // register plugin for map (drawing for parcels)
        var drawPlugin = Oskari.clazz.create('Oskari.mapframework.bundle.parcel.plugin.DrawPlugin', this.instance);
        mapModule.registerPlugin(drawPlugin);
        mapModule.startPlugin(drawPlugin);
        this.drawPlugin = drawPlugin;
    },
    /**
     * @method update
     * implements Module protocol update method
     */
    stop : function() {
        var sandbox = this.instance.sandbox;
        for (p in this.eventHandlers) {
            sandbox.unregisterFromEventByName(this, p);
        }
        sandbox.unregister(this);
    },
    /**
     * @method onEvent
     * @param {Oskari.mapframework.event.Event} event a Oskari event object
     * Event is handled forwarded to correct #eventHandlers if found or discarded if not.
     */
    onEvent : function(event) {
        var handler = this.eventHandlers[event.getName()];
        if (!handler) {
            return;
        }
        return handler.apply(this, [event]);
    },
    /**
     * @property {Object} eventHandlers
     * @static
     */
    eventHandlers : {
        /**
         * @method Toolbar.ToolSelectedEvent
         * User changed tool -> cancel parcel actions
         * @param {Oskari.mapframework.bundle.toolbar.event.ToolSelectedEvent} event
         */
        'Toolbar.ToolSelectedEvent' : function(event) {
            // changed tool
            this._cleanupPopup();
        },
        /**
         * @method Parcel.SaveDrawingEvent
         * Shows the save form.
         * @param {Oskari.mapframework.bundle.parcel.event.SaveDrawingEvent} event
         */
        'Parcel.SaveDrawingEvent' : function(event) {
            this._handleFinishedDrawingEvent(event);
        }
    },
    /**
     * @method _handleFinishedDrawingEvent
     * @private
     * Shows the saving form.
     * @param {Oskari.mapframework.bundle.parcel.event.FinishedDrawingEvent} event
     */
    _handleFinishedDrawingEvent : function(event) {
        var center = event.getDrawing().geometry.getCentroid();
        var lonlat = {
            lon : center.x,
            lat : center.y
        };
        this.showPlaceForm(lonlat);
    },
    /**
     * @method showPlaceForm
     * Displays a form popup on given location. Prepopulates the form if place is given
     * @param {OpenLayers.LonLat} location location to point with the popup
     */
    showPlaceForm : function(location) {
        var me = this;
        var sandbox = this.instance.sandbox;
        sandbox.postRequestByName('DisableMapKeyboardMovementRequest');
        var loc = this.instance.getLocalization();
        this.form = Oskari.clazz.create('Oskari.mapframework.bundle.parcel.view.PlaceForm', this.instance);

        // Get default value from the feature.
        var defaultValues = {
            place : {}
        };
        var feature = this.drawPlugin.getDrawing();
        var oldpreparcel = this.drawPlugin.getOldPreParcel();
        if (feature) {
            defaultValues.place.area = this.drawPlugin.getParcelGeometry().getArea().toFixed(0);
            if (feature.attributes) {
                if (this.instance.conf.pid) {
                    defaultValues.place.name = this.instance.conf.pid;
                }
                else {
                    // Should never be here in KVP use
                    defaultValues.place.name = feature.attributes.name + '-K';
                }
                defaultValues.place.parent_property_id = feature.attributes.name;
                defaultValues.place.parent_property_quality = this._decodeQuality('q' + feature.attributes.quality);

            }
            // Override if old values available
            if (oldpreparcel) {
                defaultValues.place.id = oldpreparcel.id;
                defaultValues.place.name = oldpreparcel.preparcel_id;
                defaultValues.place.title = oldpreparcel.title;
                defaultValues.place.subtitle = oldpreparcel.subtitle;
                defaultValues.place.desc = oldpreparcel.description;
                defaultValues.place.parent_property_id = oldpreparcel.parent_property_id;
                defaultValues.place.parent_property_quality = oldpreparcel.parent_property_quality;
                defaultValues.place.reporter = oldpreparcel.reporter;
                defaultValues.place.area_unit = oldpreparcel.area_unit;
            }
        }
        // Set the default values for the form.
        this.form.setValues(defaultValues);

        var content = [{
            html : me.form.getForm(),
            useButtons : true,
            primaryButton : loc.buttons.save,
            actions : {}
        }];
        // cancel button
        content[0].actions[loc.buttons.cancel] = function() {
            me._cleanupPopup();
            // ask toolbar to select default tool
            var toolbarRequest = me.instance.sandbox.getRequestBuilder('Toolbar.SelectToolButtonRequest')();
            me.instance.sandbox.request(me, toolbarRequest);
        };
        // print button
        content[0].actions[loc.buttons.print] = function() {
            me._printForm();
        };
        // save button
        content[0].actions[loc.buttons.save] = function() {
            me._saveForm();
        };

        // Enable / disable input fields
        if (!oldpreparcel)me.form.enableDisableFields();

        var request = sandbox.getRequestBuilder('InfoBox.ShowInfoBoxRequest')(this.popupId, loc.placeform.title, content, location, true);
        sandbox.request(me.getName(), request);
    },
    /**
     * @method _validateForm
     * Validates form data, returns an object array if any errors.
     * Error objects have field and error properties ({field : 'name', error: 'Name missing'}).
     * @private
     * @param {Object} values form values as returned by Oskari.mapframework.bundle.parcel.view.PlaceForm.getValues()
     * @return {Object[]}
     */
    _validateForm : function(values) {
        var errors = [];
        var loc = this.instance.getLocalization('validation');
        if (!values.place.name) {
            errors.push({
                name : 'name',
                error : loc.placeName
            });
        }
        return errors;
    },
    /**
     * @method __showValidationErrorMessage
     * @private
     * @param {Object[]} errors Error objects have field and error properties ({field : 'name', error: 'Name missing'}).
     */
    _showValidationErrorMessage : function(errors) {
        var loc = this.instance.getLocalization();
        var dialog = Oskari.clazz.create('Oskari.userinterface.component.Popup');
        var okBtn = dialog.createCloseButton(loc.buttons.ok);
        var content = jQuery('

');
        for (var i = 0; i < errors.length; ++i) {
            var row = jQuery('

');
            row.append(errors[i]['error'])
            content.append(row);
        }
        dialog.show(loc.validation.title, content, [okBtn]);
    },
    /**
     * @method _saveForm
     * @private
     * Handles save button on parcels form.
     */
    _saveForm : function() {
        // form not open, nothing to do
        if (!this.form) {
            return;
        }
        var me = this;
        var formValues = this.form.getValues();
        // validation
        var errors = this._validateForm(formValues);
        if (errors.length != 0) {
            this._showValidationErrorMessage(errors);
            return;
        }
        // validation passed -> go save stuff
        this.__savePlace(formValues.place);
    },
    /**
     * @method _printForm
     * @private
     * Handles print button on parcels form.
     */
    _printForm : function() {
    // form not open, nothing to do
    if (!this.form) {
        return;
    }
    var me = this;
    var formValues = this.form.getValues();
    // validation
    var errors = this._validateForm(formValues);
    if (errors.length != 0) {
        this._showValidationErrorMessage(errors);
        return;
    }
    // validation passed -> go save stuff
    this.__printPlace(formValues.place);
},
    /**
     * @method __printPlace
     * Handles print place.
     * @private
     * @param {Object} values place properties
     */
    __printPlace : function(values) {
        var me = this;
        // form not open, nothing to do
        if (!values) {
            // should not happen
            var loc = me.instance.getLocalization('notification')['error'];
            me.instance.showMessage(loc.title, loc.savePlace);
            return;
        }

        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess) {
            if (blnSuccess) {
                me._cleanupPopup();
            }
        }
        this.instance.getService().printPlace(this.drawPlugin.getDrawing(), this.drawPlugin.getFeatureType(), values, serviceCallback);
    },
    /**
     * @method __savePlace
     * Handles save place.
     * @private
     * @param {Object} values place properties
     */
    __savePlace : function(values) {
        var me = this;
        // form not open, nothing to do
        if (!values) {
            // should not happen
            var loc = me.instance.getLocalization('notification')['error'];
            me.instance.showMessage(loc.title, loc.savePlace);
            return;
        }
        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess, model, blnNew) {
            if (blnSuccess) {
                me._success2Url(values);
                me.instance.showMessage('Tallennus onnistui');
                me._cleanupPopup();
            } else {
                // blnNew should always be true since we are adding a preparcel
                if (blnNew) {
                    me.instance.showMessage('Tallennusvirhe - kohdetunnus tod. on jo käytössä');
                } else {
                    me.instance.showMessage('Muokkausvirhe');
                }
            }
        }
        this.instance.getService().savePlace(me.drawPlugin, values, serviceCallback);
    },

    /**
     * @method _loadPreParcel
     * Handles preparcel loading.
     * @private
     */
    _loadPreParcel : function() {
        var me = this;
        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess, model) {
            if (blnSuccess) {
                me._cleanupPopup();
            } else {
                me.instance.showMessage('Error in loading preparcel');
            }
        };
        this.instance.getService().loadPreParcel(me.drawPlugin, serviceCallback);
    },

    /**
     * @method _loadPreParcelData
     * Handles geometry data of place loading.
     * @param {String} parcel_id parcel identification
     * @private
     */
    _loadPreParcelData : function(parcel_id) {
        var me = this;
        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess, model) {
            if (blnSuccess) {
                me._cleanupPopup();
            } else {
                me.instance.showMessage('Error in loading preparcel');
            }
        };
        this.instance.getService().loadPreParcelData(parcel_id, me.drawPlugin, serviceCallback);
    },
        /**
         * Decode the quality code to locale description
         * @param quality_code  (lahdeaineisto property in KTJ WFS schema)
         * @private
         */
        _decodeQuality : function(quality_code) {
            var codes = this.instance.getLocalization().qualitycodes;
            var quali = codes[quality_code];
            if(!quali) quali = codes['q0'];
            return quali;

        },
    /**
     * @method _cleanupPopup
     * Cancels operations:
     * - close popup
     * - destroy form
     * @private
     */
    _cleanupPopup : function() {
        // form not open, nothing to do
        if (!this.form) {
            return;
        }
        var sandbox = this.instance.sandbox;
        var request = sandbox.getRequestBuilder('InfoBox.HideInfoBoxRequest')(this.popupId);
        sandbox.request(this, request);
        this.form.destroy();
        this.form = undefined;
        sandbox.postRequestByName('EnableMapKeyboardMovementRequest');
    },
        _success2Url : function(values) {
            // form not open, nothing to do
            if (!values || !this.instance.conf.successUrl) {
                return;
            }
            var url = this.instance.conf.successUrl;
            if(url.indexOf('?') < 0) url = url + '?';
            else  url = url + '&';

            window.location.assign(this.instance.conf.successUrl + url+'parcel.area='+values.area);
        }
}, {
    /**
     * @property {String[]} protocol
     * @static
     */
    protocol : ['Oskari.mapframework.module.Module']
});
<commit_msg>[AH-550] KPV parcel pdf actions<commit_after>/**
 * @class Oskari.mapframework.bundle.parcel.view.MainView
 *
 * Handles events related to the tool selections.
 * Also, starts the saving flow for the feature data when 'Parcel.SaveDrawingEvent' is received.
 * If saving flow is started, this view shows the save form before continuing to the WFST operation.
 */
Oskari.clazz.define("Oskari.mapframework.bundle.parcel.view.MainView",

/**
 * @method create called automatically on construction
 * @static
 */
function(instance) {
    this.instance = instance;
    this.popupId = 'parcelForm';
    this.form = undefined;
}, {
    /**
     * @method getName
     * @return {String} the name for the component
     */
    getName : function() {
        return 'ParcelMainView';
    },
    /**
     * @method getSandbox
     * @return {Oskari.mapframework.sandbox.Sandbox}
     */
    getSandbox : function() {
        return this.instance.sandbox;
    },
    /**
     * @method init
     * implements Module protocol init method
     */
    init : function() {
    },
    /**
     * @method start
     * implements Module protocol start methdod
     */
    start : function() {
        var me = this;

        var sandbox = this.instance.sandbox;
        sandbox.register(me);
        for (p in me.eventHandlers) {
            sandbox.registerForEventByName(me, p);
        }

        var mapModule = sandbox.findRegisteredModuleInstance('MainMapModule');

        // register plugin for map (drawing for parcels)
        var drawPlugin = Oskari.clazz.create('Oskari.mapframework.bundle.parcel.plugin.DrawPlugin', this.instance);
        mapModule.registerPlugin(drawPlugin);
        mapModule.startPlugin(drawPlugin);
        this.drawPlugin = drawPlugin;
    },
    /**
     * @method update
     * implements Module protocol update method
     */
    stop : function() {
        var sandbox = this.instance.sandbox;
        for (p in this.eventHandlers) {
            sandbox.unregisterFromEventByName(this, p);
        }
        sandbox.unregister(this);
    },
    /**
     * @method onEvent
     * @param {Oskari.mapframework.event.Event} event a Oskari event object
     * Event is handled forwarded to correct #eventHandlers if found or discarded if not.
     */
    onEvent : function(event) {
        var handler = this.eventHandlers[event.getName()];
        if (!handler) {
            return;
        }
        return handler.apply(this, [event]);
    },
    /**
     * @property {Object} eventHandlers
     * @static
     */
    eventHandlers : {
        /**
         * @method Toolbar.ToolSelectedEvent
         * User changed tool -> cancel parcel actions
         * @param {Oskari.mapframework.bundle.toolbar.event.ToolSelectedEvent} event
         */
        'Toolbar.ToolSelectedEvent' : function(event) {
            // changed tool
            this._cleanupPopup();
        },
        /**
         * @method Parcel.SaveDrawingEvent
         * Shows the save form.
         * @param {Oskari.mapframework.bundle.parcel.event.SaveDrawingEvent} event
         */
        'Parcel.SaveDrawingEvent' : function(event) {
            this._handleFinishedDrawingEvent(event);
        }
    },
    /**
     * @method _handleFinishedDrawingEvent
     * @private
     * Shows the saving form.
     * @param {Oskari.mapframework.bundle.parcel.event.FinishedDrawingEvent} event
     */
    _handleFinishedDrawingEvent : function(event) {
        var center = event.getDrawing().geometry.getCentroid();
        var lonlat = {
            lon : center.x,
            lat : center.y
        };
        this.showPlaceForm(lonlat);
    },
    /**
     * @method showPlaceForm
     * Displays a form popup on given location. Prepopulates the form if place is given
     * @param {OpenLayers.LonLat} location location to point with the popup
     */
    showPlaceForm : function(location) {
        var me = this;
        var sandbox = this.instance.sandbox;
        sandbox.postRequestByName('DisableMapKeyboardMovementRequest');
        var loc = this.instance.getLocalization();
        this.form = Oskari.clazz.create('Oskari.mapframework.bundle.parcel.view.PlaceForm', this.instance);

        // Get default value from the feature.
        var defaultValues = {
            place : {}
        };
        var feature = this.drawPlugin.getDrawing();
        var oldpreparcel = this.drawPlugin.getOldPreParcel();
        if (feature) {
            defaultValues.place.area = this.drawPlugin.getParcelGeometry().getArea().toFixed(0);
            if (feature.attributes) {
                if (this.instance.conf.pid) {
                    defaultValues.place.name = this.instance.conf.pid;
                }
                else {
                    // Should never be here in KVP use
                    defaultValues.place.name = feature.attributes.name + '-K';
                }
                if (this.instance.conf.printContent) {
                    defaultValues.place.desc = this.instance.conf.printContent;
                }
                defaultValues.place.parent_property_id = feature.attributes.name;
                defaultValues.place.parent_property_quality = this._decodeQuality('q' + feature.attributes.quality);

            }
            // Override if old values available
            if (oldpreparcel) {
                defaultValues.place.id = oldpreparcel.id;
                defaultValues.place.name = oldpreparcel.preparcel_id;
                defaultValues.place.title = oldpreparcel.title;
                defaultValues.place.subtitle = oldpreparcel.subtitle;
                defaultValues.place.desc = oldpreparcel.description;
                defaultValues.place.parent_property_id = oldpreparcel.parent_property_id;
                defaultValues.place.parent_property_quality = oldpreparcel.parent_property_quality;
                defaultValues.place.reporter = oldpreparcel.reporter;
                defaultValues.place.area_unit = oldpreparcel.area_unit;
            }
        }
        // Set the default values for the form.
        this.form.setValues(defaultValues);

        var content = [{
            html : me.form.getForm(),
            useButtons : true,
            primaryButton : loc.buttons.save,
            actions : {}
        }];
        // cancel button
        content[0].actions[loc.buttons.cancel] = function() {
            me._cleanupPopup();
            // ask toolbar to select default tool
            var toolbarRequest = me.instance.sandbox.getRequestBuilder('Toolbar.SelectToolButtonRequest')();
            me.instance.sandbox.request(me, toolbarRequest);
        };
        // print button
        content[0].actions[loc.buttons.print] = function() {
            me._printForm();
        };
        // save button
        content[0].actions[loc.buttons.save] = function() {
            me._saveForm();
        };

        // Enable / disable input fields
        if (!oldpreparcel)me.form.enableDisableFields();

        var request = sandbox.getRequestBuilder('InfoBox.ShowInfoBoxRequest')(this.popupId, loc.placeform.title, content, location, true);
        sandbox.request(me.getName(), request);
    },
    /**
     * @method _validateForm
     * Validates form data, returns an object array if any errors.
     * Error objects have field and error properties ({field : 'name', error: 'Name missing'}).
     * @private
     * @param {Object} values form values as returned by Oskari.mapframework.bundle.parcel.view.PlaceForm.getValues()
     * @return {Object[]}
     */
    _validateForm : function(values) {
        var errors = [];
        var loc = this.instance.getLocalization('validation');
        if (!values.place.name) {
            errors.push({
                name : 'name',
                error : loc.placeName
            });
        }
        return errors;
    },
    /**
     * @method __showValidationErrorMessage
     * @private
     * @param {Object[]} errors Error objects have field and error properties ({field : 'name', error: 'Name missing'}).
     */
    _showValidationErrorMessage : function(errors) {
        var loc = this.instance.getLocalization();
        var dialog = Oskari.clazz.create('Oskari.userinterface.component.Popup');
        var okBtn = dialog.createCloseButton(loc.buttons.ok);
        var content = jQuery('

');
        for (var i = 0; i < errors.length; ++i) {
            var row = jQuery('

');
            row.append(errors[i]['error'])
            content.append(row);
        }
        dialog.show(loc.validation.title, content, [okBtn]);
    },
    /**
     * @method _saveForm
     * @private
     * Handles save button on parcels form.
     */
    _saveForm : function() {
        // form not open, nothing to do
        if (!this.form) {
            return;
        }
        var me = this;
        var formValues = this.form.getValues();
        // validation
        var errors = this._validateForm(formValues);
        if (errors.length != 0) {
            this._showValidationErrorMessage(errors);
            return;
        }
        // validation passed -> go save stuff
        this.__savePlace(formValues.place);
    },
    /**
     * @method _printForm
     * @private
     * Handles print button on parcels form.
     */
    _printForm : function() {
    // form not open, nothing to do
    if (!this.form) {
        return;
    }
    var me = this;
    var formValues = this.form.getValues();
    // validation
    var errors = this._validateForm(formValues);
    if (errors.length != 0) {
        this._showValidationErrorMessage(errors);
        return;
    }
    // validation passed -> go save stuff
    this.__printPlace(formValues.place);
},
    /**
     * @method __printPlace
     * Handles print place.
     * @private
     * @param {Object} values place properties
     */
    __printPlace : function(values) {
        var me = this;
        // form not open, nothing to do
        if (!values) {
            // should not happen
            var loc = me.instance.getLocalization('notification')['error'];
            me.instance.showMessage(loc.title, loc.savePlace);
            return;
        }

        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess) {
            if (blnSuccess) {
                me._cleanupPopup();
            }
        }
        this.instance.getService().printPlace(this.drawPlugin.getDrawing(), this.drawPlugin.getFeatureType(), values, serviceCallback);
    },
    /**
     * @method __savePlace
     * Handles save place.
     * @private
     * @param {Object} values place properties
     */
    __savePlace : function(values) {
        var me = this;
        // form not open, nothing to do
        if (!values) {
            // should not happen
            var loc = me.instance.getLocalization('notification')['error'];
            me.instance.showMessage(loc.title, loc.savePlace);
            return;
        }
        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess, model, blnNew) {
            if (blnSuccess) {
                me._success2Url(values);
                me.instance.showMessage('Tallennus onnistui');
                me._cleanupPopup();
            } else {
                // blnNew should always be true since we are adding a preparcel
                if (blnNew) {
                    me.instance.showMessage('Tallennusvirhe - kohdetunnus tod. on jo käytössä');
                } else {
                    me.instance.showMessage('Muokkausvirhe');
                }
            }
        }
        this.instance.getService().savePlace(me.drawPlugin, values, serviceCallback);
    },

    /**
     * @method _loadPreParcel
     * Handles preparcel loading.
     * @private
     */
    _loadPreParcel : function() {
        var me = this;
        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess, model) {
            if (blnSuccess) {
                me._cleanupPopup();
            } else {
                me.instance.showMessage('Error in loading preparcel');
            }
        };
        this.instance.getService().loadPreParcel(me.drawPlugin, serviceCallback);
    },

    /**
     * @method _loadPreParcelData
     * Handles geometry data of place loading.
     * @param {String} parcel_id parcel identification
     * @private
     */
    _loadPreParcelData : function(parcel_id) {
        var me = this;
        // Callback handles the end of the asynchronous operation.
        var serviceCallback = function(blnSuccess, model) {
            if (blnSuccess) {
                me._cleanupPopup();
            } else {
                me.instance.showMessage('Error in loading preparcel');
            }
        };
        this.instance.getService().loadPreParcelData(parcel_id, me.drawPlugin, serviceCallback);
    },
        /**
         * Decode the quality code to locale description
         * @param quality_code  (lahdeaineisto property in KTJ WFS schema)
         * @private
         */
        _decodeQuality : function(quality_code) {
            var codes = this.instance.getLocalization().qualitycodes;
            var quali = codes[quality_code];
            if(!quali) quali = codes['q0'];
            return quali;

        },
    /**
     * @method _cleanupPopup
     * Cancels operations:
     * - close popup
     * - destroy form
     * @private
     */
    _cleanupPopup : function() {
        // form not open, nothing to do
        if (!this.form) {
            return;
        }
        var sandbox = this.instance.sandbox;
        var request = sandbox.getRequestBuilder('InfoBox.HideInfoBoxRequest')(this.popupId);
        sandbox.request(this, request);
        this.form.destroy();
        this.form = undefined;
        sandbox.postRequestByName('EnableMapKeyboardMovementRequest');
    },
        _success2Url : function(values) {
            // form not open, nothing to do
            if (!values || !this.instance.conf.successUrl) {
                return;
            }
            var url = this.instance.conf.successUrl;
            if(url.indexOf('?') < 0) url = url + '?';
            else  url = url + '&';

            window.location.assign(this.instance.conf.successUrl + url+'parcel.area='+values.area);
        }
}, {
    /**
     * @property {String[]} protocol
     * @static
     */
    protocol : ['Oskari.mapframework.module.Module']
});
<|endoftext|>


<commit_before>import * as R from "ramda"

//

function Identity(value) {this.value = value}
const Ident = x => new Identity(x)
Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
Identity.prototype.of = Ident
Identity.prototype.ap = function (x) {return new Identity(this.value(x.value))}

//

function Constant(value) {this.value = value}
const Const = x => new Constant(x)
const Single = x => Const([x])
Constant.prototype.map = function () {return this}
Constant.prototype.of = Const
Constant.prototype.ap = function (x) {return new Const(R.concat(this.value, x.value))}

//

const warn = process.env.NODE_ENV === "production" ? () => {} : (() => {
  const warned = {}

  return message => {
    if (!(message in warned)) {
      warned[message] = message
      console.warn("partial.lenses:", message)
    }
  }
})()

//

const isArray  = x => x && x.constructor === Array
const isObject = x => x && x.constructor === Object

const unArray  = x =>  isArray(x) ? x : undefined
const unObject = x => isObject(x) ? x : undefined

const mkArray = x => isArray(x) ? x : []

//

const id = x => x
const snd = (_, c) => c

//

const check = (expected, predicate) => x => {
  if (predicate(x))
    return x
  else
    throw new Error(`Expected ${expected}, but got ${x}.`)
}

const assert = process.env.NODE_ENV === "production" ? () => id : check

//

const empty = {}

const deleteKey = (k, o = {}) => {
  let r
  for (const p in o) {
    if (p !== k) {
      if (undefined === r)
        r = {}
      r[p] = o[p]
    }
  }
  return r
}

const setKey = (k, v, o = {}) => {
  if (k in o && R.equals(v, o[k]))
    return o
  const r = {[k]: v}
  for (const p in o)
    if (p !== k)
      r[p] = o[p]
  return r
}

//

const dropped = xs => Object.keys(xs).length === 0 ? undefined : xs

//

const toPartial = transform => x => undefined === x ? x : transform(x)

//

const filtered =
  toPartial(R.pipe(R.filter(x => x !== undefined), dropped))

//

const conserve = (c1, c0) => R.equals(c1, c0) ? c0 : c1

const toConserve = f => (y, c0) => conserve(f(y, c0), c0)

//

const seemsLens = x => typeof x === "function" && x.length === 1

const lifted = assert("a lens", seemsLens)

const lift = l => {
  if (isProp(l))  return liftProp(l)
  if (isIndex(l)) return liftIndex(l)
  return lifted(l)
}

export const compose = (...ls) =>
  ls.length === 0 ? identity :
  ls.length === 1 ? ls[0] :
  (toCat => R.compose(...ls.map(l => lift(l)(toCat))))

export const remove = R.curry((l, s) => setI(lift(l), undefined, s))

export const removeAll = R.curry((lens, data) => {
  warn("`removeAll` is deprecated and will be removed in next major version --- use a different approach.")
  while (get(lens, data) !== undefined)
    data = remove(lens, data)
  return data
})

const setI = (l, x, s) => l(Ident)(() => Ident(x))(s).value
const getI = (l, s) => l(Const)(Const)(s).value
const modifyI = (l, x2x, s) => l(Ident)(y => Ident(x2x(y)))(s).value
const lensI = (getter, setter) => _constructor => inner => target =>
  inner(getter(target)).map(focus => setter(focus, target))
const isoI = (to, from) => lensI(to, toConserve(from))
const collectI = (l, s) => l(Const)(Single)(s).value

export const lens = R.curry(lensI)
export const modify = R.curry((l, x2x, s) => modifyI(lift(l), x2x, s))
export const set = R.curry((l, x, s) => setI(lift(l), x, s))
export const get = R.curry((l, s) => getI(lift(l), s))
export const collect = R.curry((l, s) =>
  warn("`collect` is experimental and might be removed, renamed or changed semantically before next major release") ||
  mkArray(filtered(collectI(lift(l), s))))

export const chain = R.curry((x2yL, xL) =>
  compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))

export const just = x => lensI(R.always(x), snd)

export const choose = x2yL => constructor => inner => target =>
  lift(x2yL(target))(constructor)(inner)(target)

export const nothing = lensI(snd, snd)

export const orElse =
  R.curry((d, l) => choose(x => getI(lift(l), x) !== undefined ? l : d))

export const choice = (...ls) => choose(x => {
  const i = ls.findIndex(l => getI(lift(l), x) !== undefined)
  return 0 <= i ? ls[i] : nothing
})

const replacer = (inn, out) => x => R.equals(x, inn) ? out : x
const normalizer = fn => isoI(fn, fn)

export const replace = R.curry((inn, out) =>
  isoI(replacer(inn, out), replacer(out, inn)))

export const defaults = replace(undefined)
export const required = inn => replace(inn, undefined)
export const define = v => normalizer(replacer(undefined, v))

export const valueOr = v =>
  lensI(x => x === undefined || x === null ? v : x, conserve)

export const normalize = transform => normalizer(toPartial(transform))

const isProp = x => typeof x === "string"

export const prop = assert("a string", isProp)

const liftProp = k => _c => inner => o => inner(isObject(o) ? o[k] : undefined).map(v => {
  const oOut = unObject(o)
  return v === undefined ? deleteKey(k, oOut) : setKey(k, v, oOut)
})

export const find = predicate => choose(xs => {
  if (isArray(xs)) {
    const i = xs.findIndex(predicate)
    return i < 0 ? append : i
  } else {
    return append
  }
})

export const findWith = (...ls) => {
  const lls = lift(compose(...ls))
  return compose(find(x => getI(lls, x) !== undefined), lls)
}

const isIndex = x => Number.isInteger(x) && 0 <= x

export const index = assert("a non-negative integer", isIndex)

const liftIndex = i => _c => inner => xs => inner(isArray(xs) ? xs[i] : undefined).map(x => {
  if (x === undefined) {
    if (!isArray(xs))
      return undefined
    if (xs.length <= i)
      return dropped(xs)
    const ys = xs.slice(0)
    ys.splice(i, 1)
    return dropped(ys)
  } else {
    if (!isArray(xs))
      return Array(i).concat([x])
    if (xs.length <= i)
      return xs.concat(Array(i - xs.length), [x])
    if (R.equals(x, xs[i]))
      return xs
    const ys = xs.slice(0)
    ys[i] = x
    return ys
  }
})

export const append = lensI(snd, (x, xs) =>
  x === undefined ? unArray(xs) : isArray(xs) ? xs.concat([x]) : [x])

export const filter = p => lensI(xs => unArray(xs) && xs.filter(p), (ys, xs) =>
  conserve(dropped(R.concat(mkArray(ys), mkArray(xs).filter(R.complement(p)))), xs))

export const augment = template => lensI(
  x => {
    if (isObject(x)) {
      const z = {...x}
      for (const k in template)
        z[k] = template[k](z)
      return z
    } else {
      return undefined
    }
  },
  toConserve((y, cIn) => {
    if (isObject(y)) {
      const c = unObject(cIn) || {}
      let z
      const set = (k, v) => {
        if (undefined === z)
          z = {}
        z[k] = v
      }
      for (const k in y) {
        if (!(k in template))
          set(k, y[k])
        else
          if (k in c)
            set(k, c[k])
      }
      return z
    } else {
      return undefined
    }
  }))

export const pick = template => lensI(
  c => {
    let r
    for (const k in template) {
      const v = getI(lift(template[k]), c)
      if (v !== undefined) {
        if (r === undefined)
          r = {}
        r[k] = v
      }
    }
    return r
  },
  (o = empty, cIn) => {
    let c = cIn
    for (const k in template)
      c = setI(lift(template[k]), o[k], c)
    return c
  })

export const identity = lensI(id, conserve)

export const props = (...ks) => pick(R.zipObj(ks, ks))

const show = (...labels) => x => console.log(...labels, x) || x

export const log = (...labels) =>
  lensI(show(...labels, "get"), show(...labels, "set"))

export const sequence = constructor => inner => target =>
  warn("`sequence` is experimental and might be removed, renamed or changed semantically before next major release") ||
  R.traverse(constructor, inner, mkArray(target))
  .map(filtered)

export const optional =
  compose(lensI(toPartial(x => [x]),
                toPartial(([x]) => x)),
          sequence)

export const fromRamda = l => _constructor => l
const fantasy = () => {throw new Error("Sorry, `toRamda` is only fantasy!")}
export const toRamda = l => lift(l)(fantasy)

export const fromArrayBy = id =>
  warn("`fromArrayBy` is experimental and might be removed, renamed or changed semantically before next major release") ||
  isoI(xs => {
    if (isArray(xs)) {
      const o = {}
      for (let i=0, n=xs.length; i<n; ++i) {
        const x = xs[i]
        o[x[id]] = x
      }
      return o
    }
  },
  o => unObject(o) && R.values(o))

export default compose
<commit_msg>Minor opt<commit_after>import * as R from "ramda"

//

function Identity(value) {this.value = value}
const Ident = x => new Identity(x)
Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
Identity.prototype.of = Ident
Identity.prototype.ap = function (x) {return new Identity(this.value(x.value))}

//

function Constant(value) {this.value = value}
const Const = x => new Constant(x)
const Single = x => Const([x])
Constant.prototype.map = function () {return this}
Constant.prototype.of = Const
Constant.prototype.ap = function (x) {return new Const(R.concat(this.value, x.value))}

//

const warn = process.env.NODE_ENV === "production" ? () => {} : (() => {
  const warned = {}

  return message => {
    if (!(message in warned)) {
      warned[message] = message
      console.warn("partial.lenses:", message)
    }
  }
})()

//

const isArray  = x => x && x.constructor === Array
const isObject = x => x && x.constructor === Object

const unArray  = x =>  isArray(x) ? x : undefined
const unObject = x => isObject(x) ? x : undefined

const mkArray = x => isArray(x) ? x : []

//

const id = x => x
const snd = (_, c) => c

//

const check = (expected, predicate) => x => {
  if (predicate(x))
    return x
  else
    throw new Error(`Expected ${expected}, but got ${x}.`)
}

const assert = process.env.NODE_ENV === "production" ? () => id : check

//

const empty = {}

const deleteKey = (k, o = empty) => {
  let r
  for (const p in o) {
    if (p !== k) {
      if (undefined === r)
        r = {}
      r[p] = o[p]
    }
  }
  return r
}

const setKey = (k, v, o = empty) => {
  if (k in o && R.equals(v, o[k]))
    return o
  const r = {[k]: v}
  for (const p in o)
    if (p !== k)
      r[p] = o[p]
  return r
}

//

const dropped = xs => Object.keys(xs).length === 0 ? undefined : xs

//

const toPartial = transform => x => undefined === x ? x : transform(x)

//

const filtered =
  toPartial(R.pipe(R.filter(x => x !== undefined), dropped))

//

const conserve = (c1, c0) => R.equals(c1, c0) ? c0 : c1

const toConserve = f => (y, c0) => conserve(f(y, c0), c0)

//

const seemsLens = x => typeof x === "function" && x.length === 1

const lifted = assert("a lens", seemsLens)

const lift = l => {
  if (isProp(l))  return liftProp(l)
  if (isIndex(l)) return liftIndex(l)
  return lifted(l)
}

export const compose = (...ls) =>
  ls.length === 0 ? identity :
  ls.length === 1 ? ls[0] :
  (toCat => R.compose(...ls.map(l => lift(l)(toCat))))

export const remove = R.curry((l, s) => setI(lift(l), undefined, s))

export const removeAll = R.curry((lens, data) => {
  warn("`removeAll` is deprecated and will be removed in next major version --- use a different approach.")
  while (get(lens, data) !== undefined)
    data = remove(lens, data)
  return data
})

const setI = (l, x, s) => l(Ident)(() => Ident(x))(s).value
const getI = (l, s) => l(Const)(Const)(s).value
const modifyI = (l, x2x, s) => l(Ident)(y => Ident(x2x(y)))(s).value
const lensI = (getter, setter) => _constructor => inner => target =>
  inner(getter(target)).map(focus => setter(focus, target))
const isoI = (to, from) => lensI(to, toConserve(from))
const collectI = (l, s) => l(Const)(Single)(s).value

export const lens = R.curry(lensI)
export const modify = R.curry((l, x2x, s) => modifyI(lift(l), x2x, s))
export const set = R.curry((l, x, s) => setI(lift(l), x, s))
export const get = R.curry((l, s) => getI(lift(l), s))
export const collect = R.curry((l, s) =>
  warn("`collect` is experimental and might be removed, renamed or changed semantically before next major release") ||
  mkArray(filtered(collectI(lift(l), s))))

export const chain = R.curry((x2yL, xL) =>
  compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))

export const just = x => lensI(R.always(x), snd)

export const choose = x2yL => constructor => inner => target =>
  lift(x2yL(target))(constructor)(inner)(target)

export const nothing = lensI(snd, snd)

export const orElse =
  R.curry((d, l) => choose(x => getI(lift(l), x) !== undefined ? l : d))

export const choice = (...ls) => choose(x => {
  const i = ls.findIndex(l => getI(lift(l), x) !== undefined)
  return 0 <= i ? ls[i] : nothing
})

const replacer = (inn, out) => x => R.equals(x, inn) ? out : x
const normalizer = fn => isoI(fn, fn)

export const replace = R.curry((inn, out) =>
  isoI(replacer(inn, out), replacer(out, inn)))

export const defaults = replace(undefined)
export const required = inn => replace(inn, undefined)
export const define = v => normalizer(replacer(undefined, v))

export const valueOr = v =>
  lensI(x => x === undefined || x === null ? v : x, conserve)

export const normalize = transform => normalizer(toPartial(transform))

const isProp = x => typeof x === "string"

export const prop = assert("a string", isProp)

const liftProp = k => _c => inner => o => inner(isObject(o) ? o[k] : undefined).map(v => {
  const oOut = unObject(o)
  return v === undefined ? deleteKey(k, oOut) : setKey(k, v, oOut)
})

export const find = predicate => choose(xs => {
  if (isArray(xs)) {
    const i = xs.findIndex(predicate)
    return i < 0 ? append : i
  } else {
    return append
  }
})

export const findWith = (...ls) => {
  const lls = lift(compose(...ls))
  return compose(find(x => getI(lls, x) !== undefined), lls)
}

const isIndex = x => Number.isInteger(x) && 0 <= x

export const index = assert("a non-negative integer", isIndex)

const liftIndex = i => _c => inner => xs => inner(isArray(xs) ? xs[i] : undefined).map(x => {
  if (x === undefined) {
    if (!isArray(xs))
      return undefined
    if (xs.length <= i)
      return dropped(xs)
    const ys = xs.slice(0)
    ys.splice(i, 1)
    return dropped(ys)
  } else {
    if (!isArray(xs))
      return Array(i).concat([x])
    if (xs.length <= i)
      return xs.concat(Array(i - xs.length), [x])
    if (R.equals(x, xs[i]))
      return xs
    const ys = xs.slice(0)
    ys[i] = x
    return ys
  }
})

export const append = lensI(snd, (x, xs) =>
  x === undefined ? unArray(xs) : isArray(xs) ? xs.concat([x]) : [x])

export const filter = p => lensI(xs => unArray(xs) && xs.filter(p), (ys, xs) =>
  conserve(dropped(R.concat(mkArray(ys), mkArray(xs).filter(R.complement(p)))), xs))

export const augment = template => lensI(
  x => {
    if (isObject(x)) {
      const z = {...x}
      for (const k in template)
        z[k] = template[k](z)
      return z
    } else {
      return undefined
    }
  },
  toConserve((y, cIn) => {
    if (isObject(y)) {
      const c = unObject(cIn) || empty
      let z
      const set = (k, v) => {
        if (undefined === z)
          z = {}
        z[k] = v
      }
      for (const k in y) {
        if (!(k in template))
          set(k, y[k])
        else
          if (k in c)
            set(k, c[k])
      }
      return z
    } else {
      return undefined
    }
  }))

export const pick = template => lensI(
  c => {
    let r
    for (const k in template) {
      const v = getI(lift(template[k]), c)
      if (v !== undefined) {
        if (r === undefined)
          r = {}
        r[k] = v
      }
    }
    return r
  },
  (o = empty, cIn) => {
    let c = cIn
    for (const k in template)
      c = setI(lift(template[k]), o[k], c)
    return c
  })

export const identity = lensI(id, conserve)

export const props = (...ks) => pick(R.zipObj(ks, ks))

const show = (...labels) => x => console.log(...labels, x) || x

export const log = (...labels) =>
  lensI(show(...labels, "get"), show(...labels, "set"))

export const sequence = constructor => inner => target =>
  warn("`sequence` is experimental and might be removed, renamed or changed semantically before next major release") ||
  R.traverse(constructor, inner, mkArray(target))
  .map(filtered)

export const optional =
  compose(lensI(toPartial(x => [x]),
                toPartial(([x]) => x)),
          sequence)

export const fromRamda = l => _constructor => l
const fantasy = () => {throw new Error("Sorry, `toRamda` is only fantasy!")}
export const toRamda = l => lift(l)(fantasy)

export const fromArrayBy = id =>
  warn("`fromArrayBy` is experimental and might be removed, renamed or changed semantically before next major release") ||
  isoI(xs => {
    if (isArray(xs)) {
      const o = {}
      for (let i=0, n=xs.length; i<n; ++i) {
        const x = xs[i]
        o[x[id]] = x
      }
      return o
    }
  },
  o => unObject(o) && R.values(o))

export default compose
<|endoftext|>


<commit_before>function Ship(game, pos, vel, angle, image, info) {
    var self = this;

    self.GameState = game;
    self.pos = pos;
    self.vel = vel;
    self.thrust = false;
    self.shooting = false;
    self.angle = angle;
    self.angle_vel = 0;
    self.image = image;
    self.image_center = info.center;
    self.image_size = info.size;
    self.radius = info.radius;
    self.last_shooting = 0;
    self.invulnerability = RESPAWN_INVULNERABILITY;

    self.shipThrustSound = document.getElementById('ship_thrust_sound');

    self.draw = function (canvas) {
        if (self.thrust == true) {
            self.shipThrustSound.play();
            canvas.drawImage({
                image: ship_img.image,
                draw: new Rectangle({ center: new Point(self.pos.x, self.pos.y), size: new Size(90, 90)}),
                crop: new Rectangle(90, 0, 90, 90),
                angle: self.angle
            });
        } else {
            self.shipThrustSound.pause();
            canvas.drawImage({
                image: ship_img.image,
                draw: new Rectangle({ center: new Point(self.pos.x, self.pos.y), size: new Size(90, 90)}),
                crop: new Rectangle(0, 0, 90, 90),
                angle: self.angle
            });
        }

//        if (self.invulnerability > 0 && self.invulnerability % 5 == 0) {
//            canvas.draw_circle(self.pos, ship_img.radius + 10.0, 1, "White");
//        }
    };

    self.destroy = function () {
        game.lives -= 1;

        //explosions.push(new Explosion(self.pos));

        /*if (lives <= 0) {
         game.game_over = true;
         } else {
         self.invulnerability = RESPAWN_INVULNERABILITY;
         }*/
    };

    self.check_collision = function () {
        for (var rock in rocks) {
            var dst = Helpers.dist(self.pos, rock.pos);
            if (dst <= self.radius + rock.radius) {
                if (self.invulnerability <= 0) {
                    self.destroy();
                    rocks.remove(rock);
                }
            }
        }
    };

    self.get_speed = function () {
        return Math.sqrt(Math.pow(self.vel.x, 2.0) + Math.pow(self.vel.y, 2.0))
    };

    self.update = function () {
        if (self.invulnerability > 0) {
            self.invulnerability -= 1;
        }

        self.angle = self.angle + self.angle_vel;
        while (self.angle > 2 * Math.PI) {
            self.angle -= 2 * Math.PI;
        }
        while (self.angle < 0) {
            self.angle += 2 * Math.PI;
        }

        var acceleration = {x: 0, y: 0};
        if (self.thrust == true) {
            acceleration = Helpers.angle_to_vector(self.angle);
            acceleration.x *= ACCELERATION_COEF;
            acceleration.y *= ACCELERATION_COEF;
        }

        var friction = {x: self.vel.x * FRICTION_COEF, y: self.vel.y * FRICTION_COEF};

        if (Helpers.dist(self.vel) <= MAX_VEL) {
            self.vel.x += acceleration.x;
            self.vel.y += acceleration.y;
        }

        self.vel.x -= friction.x;
        self.vel.y -= friction.y;

        self.pos.x += self.vel.x;
        self.pos.y += self.vel.y;

        // cyclic space
        if (self.pos.x > WIDTH) {
            self.pos.x = 0;
        } else if (self.pos.x < 0) {
            self.pos.x = WIDTH;
        }

        if (self.pos.y > HEIGHT) {
            self.pos.y = 0;
        }
        else if (self.pos.y < 0) {
            self.pos.y = HEIGHT;
        }

        if (self.shooting == true && (time - self.last_shooting > TIME_BETWEEN_SHOOTING)) {
            var missile_vector = Helpers.angle_to_vector(self.angle, MISSILE_SPEED + self.get_speed());

            // setting missile position
            var missile_position = { x: self.pos.x, y: self.pos.y };
            var ship_size = ship_img.radius + 1.0;
            var ship_size_vector = Helpers.angle_to_vector(self.angle, ship_size);
            missile_position.x += ship_size_vector.x;
            missile_position.y += ship_size_vector.y;

            var item = new Sprite(missile_img, missile_position, {
                angle: self.angle,
                sound: missile_sound,
                vel: missile_vector,
                lifespan: 30
            });
            missiles.push(item);

            self.last_shooting = time;
        }

        self.check_collision();
    }
}<commit_msg>Fixed ship-rock collision<commit_after>function Ship(game, pos, vel, angle, image, info) {
    var self = this;

    self.GameState = game;
    self.pos = pos;
    self.vel = vel;
    self.thrust = false;
    self.shooting = false;
    self.angle = angle;
    self.angle_vel = 0;
    self.image = image;
    self.image_center = info.center;
    self.image_size = info.size;
    self.radius = info.radius;
    self.last_shooting = 0;
    self.invulnerability = RESPAWN_INVULNERABILITY;

    self.shipThrustSound = document.getElementById('ship_thrust_sound');

    self.draw = function (canvas) {
        if (self.thrust == true) {
            self.shipThrustSound.play();
            canvas.drawImage({
                image: ship_img.image,
                draw: new Rectangle({ center: new Point(self.pos.x, self.pos.y), size: new Size(90, 90)}),
                crop: new Rectangle(90, 0, 90, 90),
                angle: self.angle
            });
        } else {
            self.shipThrustSound.pause();
            canvas.drawImage({
                image: ship_img.image,
                draw: new Rectangle({ center: new Point(self.pos.x, self.pos.y), size: new Size(90, 90)}),
                crop: new Rectangle(0, 0, 90, 90),
                angle: self.angle
            });
        }

//        if (self.invulnerability > 0 && self.invulnerability % 5 == 0) {
//            canvas.draw_circle(self.pos, ship_img.radius + 10.0, 1, "White");
//        }
    };

    self.destroy = function () {
        game.lives -= 1;

        explosions.push(new Sprite(explosion_img, self.pos, {
            sound: explosion_sound,
            animated: true
        }));

        if (lives <= 0) {
            game_over = true;
        } else {
            self.invulnerability = RESPAWN_INVULNERABILITY;
        }
    };

    self.check_collision = function () {
        for (var i = 0; i < rocks.length; i++) {
            var dst = Helpers.dist(self.pos, rocks[i].pos);
            if (dst <= self.radius + rocks[i].radius) {
                if (self.invulnerability <= 0) {
                    self.destroy();
                    //rocks.remove(rocks[i]);
                    rocks[i] = rocks[rocks.length - 1];
                    rocks.pop();
                    i--;
                }
            }
        }
    };

    self.get_speed = function () {
        return Math.sqrt(Math.pow(self.vel.x, 2.0) + Math.pow(self.vel.y, 2.0))
    };

    self.update = function () {
        if (self.invulnerability > 0) {
            self.invulnerability -= 1;
        }

        self.angle = self.angle + self.angle_vel;
        while (self.angle > 2 * Math.PI) {
            self.angle -= 2 * Math.PI;
        }
        while (self.angle < 0) {
            self.angle += 2 * Math.PI;
        }

        var acceleration = {x: 0, y: 0};
        if (self.thrust == true) {
            acceleration = Helpers.angle_to_vector(self.angle);
            acceleration.x *= ACCELERATION_COEF;
            acceleration.y *= ACCELERATION_COEF;
        }

        var friction = {x: self.vel.x * FRICTION_COEF, y: self.vel.y * FRICTION_COEF};

        if (Helpers.dist(self.vel) <= MAX_VEL) {
            self.vel.x += acceleration.x;
            self.vel.y += acceleration.y;
        }

        self.vel.x -= friction.x;
        self.vel.y -= friction.y;

        self.pos.x += self.vel.x;
        self.pos.y += self.vel.y;

        // cyclic space
        if (self.pos.x > WIDTH) {
            self.pos.x = 0;
        } else if (self.pos.x < 0) {
            self.pos.x = WIDTH;
        }

        if (self.pos.y > HEIGHT) {
            self.pos.y = 0;
        }
        else if (self.pos.y < 0) {
            self.pos.y = HEIGHT;
        }

        if (self.shooting == true && (time - self.last_shooting > TIME_BETWEEN_SHOOTING)) {
            var missile_vector = Helpers.angle_to_vector(self.angle, MISSILE_SPEED + self.get_speed());

            // setting missile position
            var missile_position = { x: self.pos.x, y: self.pos.y };
            var ship_size = ship_img.radius + 1.0;
            var ship_size_vector = Helpers.angle_to_vector(self.angle, ship_size);
            missile_position.x += ship_size_vector.x;
            missile_position.y += ship_size_vector.y;

            var item = new Sprite(missile_img, missile_position, {
                angle: self.angle,
                sound: missile_sound,
                vel: missile_vector,
                lifespan: 30
            });
            missiles.push(item);

            self.last_shooting = time;
        }

        self.check_collision();
    }
}<|endoftext|>