<commit_before>require('normalize.css/normalize.css');
require('styles/App.scss');

import React from 'react';

var imageDatas = require('../datas/imageData.json');

//  利用自执行函数，将图片名信息转成图片URL路径信息
var imageDatas = require('../datas/imageData.json');
imageDatas =  (function genImageURL(imageDataArr) {
  for (var i = 0, j = imageDataArr.length; i < j; i++) {
    var singleImageData = imageDataArr[i];
    singleImageData.imageURL = require('../images/' + singleImageData.fileName);
    imageDataArr[i] = singleImageData;
  }
  return imageDataArr
})(imageDatas);

class ImgFigure extends React.Component {
  render() {
    return (

{this.props.data.title}

    );
  }
}

class AppComponent extends React.Component {
  Constant : {
    centerPos: {
      left: 0,
      right: 0
    },
    hPosRange: {     //水平方向的取值范围
      leftSecX: [0, 0],
      rightSecX: [0, 0],
      y: [0, 0]
    },
    vPosRange: {     //垂直方向的取值范围
      x: [0, 0],
      topY: [0, 0]
    }
  }

  componentDidMount() {
    //首先获取舞台的大小
    var stageDOM = React.findDOMNode(this.ref.stage),
        stageW = stageDOM.scrollWidth,
        stageH = stageDOM.scrollHeight,
        halfStageW = Math.ceil(stageW / 2),
        halfStageH = Math.ceil(stageH / 2);

    //拿到一个imageFigure的大小
    var imageFigureDOM = React.findDOMNode(this.ref.imgFigure0),
        imgW = imageFigureDOM.scrollWidth,
        imgH = imageFigureDOM.scrollHeight,
        halfImgW = Math.ceil(imgW / 2),
        halfImgH = Math.ceil(imgH / 2)
  }

  render() {

    var controllerUnits = [],
      imgFigures = [];

    imageDatas.forEach(function (value, index) {
      imgFigures.push(<ImgFigure key={index} data={value} ref={'imgFigure'+index}/>)
    })

    return (

          {imgFigures}

          {controllerUnits}

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<commit_msg>排布图片，左右中上<commit_after>require('normalize.css/normalize.css');
require('styles/App.scss');

import React from 'react';
import ReactDOM from 'react-dom';

var imageDatas = require('../datas/imageData.json');

//  利用自执行函数，将图片名信息转成图片URL路径信息
var imageDatas = require('../datas/imageData.json');
imageDatas =  (function genImageURL(imageDataArr) {
  for (var i = 0, j = imageDataArr.length; i < j; i++) {
    var singleImageData = imageDataArr[i];
    singleImageData.imageURL = require('../images/' + singleImageData.fileName);
    imageDataArr[i] = singleImageData;
  }
  return imageDataArr
})(imageDatas);

function getRangeRandom(low, high) {
  return Math.floor(Math.random() * (high - low) + low);
}

class ImgFigure extends React.Component {
  render() {
    var styleObj = {};

    // 如果props属性中指定了这张图片的位置，则使用
    if (this.props.arrange) {
      styleObj = this.props.arrange.pos;
    }

    return (

{this.props.data.title}

    );
  }
}

class AppComponent extends React.Component {
  constructor(props) {
    super(props);
    this.Constant = {
      centerPos: {
        left: 0,
        top: 0
      },
      hPosRange: {     //水平方向的取值范围
        leftSecX: [0, 0],
        rightSecX: [0, 0],
        y: [0, 0]
      },
      vPosRange: {     //垂直方向的取值范围
        x: [0, 0],
        topY: [0, 0]
      }
    };
    this.state = {
        imgsArrangeArr: []
    };
  }
  /*
   * 根据传入的centerIndex重新排布图片
   */
  rearrange(centerIndex) {
    let imgsArrangeArr = this.state.imgsArrangeArr,
        Constant = this.Constant,
        centerPos = Constant.centerPos,
        hPosRange = Constant.hPosRange,
        vPosRange = Constant.vPosRange,
        hPosRangeLeftSecX = hPosRange.leftSecX,
        hPosRangeRightSecX = hPosRange.rightSecX,
        hPosRangeY = hPosRange.y,
        vPosRangeTopY = vPosRange.topY,
        vPosRangeX = vPosRange.x,

        imgsArrangeTopArr = [],
        topImgNum = Math.floor(Math.random() * 2), //取一个或者不取
        topImgSpliceIndex = 0,

        imgsArrayCenterArr = imgsArrangeArr.splice(centerIndex, 1);

        //首先居中 centerIndex 的图片
        imgsArrayCenterArr[0].pos = centerPos;

        //取出要布局上侧的图片的状态信息
        topImgSpliceIndex = Math.floor(Math.random() * (imgsArrangeArr.length - topImgNum));
        imgsArrangeTopArr = imgsArrangeArr.splice(topImgSpliceIndex, topImgNum);

        //布局位于上侧的图片
        imgsArrangeTopArr.forEach(function (value, index) {
          imgsArrangeTopArr[index].pos = {
            top: getRangeRandom(vPosRangeTopY[0], vPosRangeTopY[1]),
            left: getRangeRandom(vPosRangeX[0], vPosRangeX[1])
          }
        });

        for (let i = 0, j = imgsArrangeArr.length, k = j / 2; i< j; i++) {
          let hPosRangeLORX = null;

          //前半部分布局左边， 右半部分布局右边
          if (i < k) {
             hPosRangeLORX = hPosRangeLeftSecX;
          }
          else {
             hPosRangeLORX = hPosRangeRightSecX;
          }

          imgsArrangeArr[i] = {
            pos: {
              top: getRangeRandom(hPosRangeY[0], hPosRangeY[1]),
              left: getRangeRandom(hPosRangeLORX[0], hPosRangeLORX[1])
            }
          }
        }

        if (imgsArrangeTopArr && imgsArrangeTopArr[0]) {
          imgsArrangeArr.splice(topImgSpliceIndex, 0 , imgsArrangeTopArr[0]);
        }
        imgsArrangeArr.splice(centerIndex, 0 , imgsArrayCenterArr[0]);

        this.setState({
          imgsArrangeArr: imgsArrangeArr
        })
  }

  componentDidMount() {
    //首先获取舞台的大小
    let stageDOM = ReactDOM.findDOMNode(this.refs.stage),
        stageW = stageDOM.scrollWidth,
        stageH = stageDOM.scrollHeight,
        halfStageW = Math.floor(stageW / 2),
        halfStageH = Math.floor(stageH / 2);

    //拿到一个imageFigure的大小
    let imageFigureDOM = ReactDOM.findDOMNode(this.refs.imgFigure0),
        imgW = imageFigureDOM.scrollWidth,
        imgH = imageFigureDOM.scrollHeight,
        halfImgW = Math.floor(imgW / 2),
        halfImgH = Math.floor(imgH / 2);
    //中心点位置
    this.Constant.centerPos = {
        left: halfStageW - halfImgW,
        top: halfStageH - halfImgH
    }
    //计算左右侧区域图片排布位置的取值范围
    this.Constant.hPosRange.leftSecX[0] = -halfImgW;
    this.Constant.hPosRange.leftSecX[1] = halfStageW - halfImgW * 3;
    this.Constant.hPosRange.rightSecX[0] = halfStageW + halfImgW;
    this.Constant.hPosRange.rightSecX[1] = stageW - halfImgW;
    this.Constant.hPosRange.y[0] = -halfImgH;
    this.Constant.hPosRange.y[1] = stageH - halfImgH;

    //计算上侧区域图片排布位置取值范围
    this.Constant.vPosRange.topY[0] = -halfImgH;
    this.Constant.vPosRange.topY[1] = halfStageH - halfImgH * 3;
    this.Constant.vPosRange.x[0] = halfStageW - imgW;
    this.Constant.vPosRange.x[1] = halfStageW;
    let num = Math.floor(Math.random() * 10);
    this.rearrange(num);
  }

  render() {

    var controllerUnits = [],
      imgFigures = [];

    imageDatas.forEach(function (value, index) {
      if (!this.state.imgsArrangeArr[index]) {
        this.state.imgsArrangeArr[index] = {
            pos: {
              left: 0,
              top: 0
            }
        }
      }
      imgFigures.push(<ImgFigure key={index} data={value} ref={'imgFigure'+index} arrange={this.state.imgsArrangeArr[index]} />)
    }.bind(this))

    return (

          {imgFigures}

          {controllerUnits}

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<|endoftext|>


<commit_before>require('normalize.css/normalize.css');
require('styles/App.scss');

var imageDatas = require('../sources/imageDatas.json');

import React from 'react';
import ReactDOM from 'react-dom';

(function genImageUrl(imageDataArr) {
	for(var imageData of imageDataArr) {
		imageData.imageUrl = require('../images/' + imageData.fileName);
	}
})(imageDatas);

function getRangeRandom(low, high) {
  return Math.floor(Math.random() * (high - low) + low);
}

class ImgFigure extends React.Component {
  render() {
    var styleObj = {};
    if(this.props.arrange.pos) {
      styleObj = this.props.arrange.pos;
    }
    return (

{this.props.data.title}

    )
  }
}

class AppComponent extends React.Component {
  constructor() {
    super();
    //用来保存图片状态信息
    this.state = {
      imgsArrangeArr: [
        /*{
          pos: {
            left: '0';
            top: '0'
          }
        }*/
      ]
    };
    this.constant = {
      centerPos: {
        left: 0,
        top: 0
      },
      hPosRange: {
        leftSecX: [0, 0],
        rightSecX: [0, 0],
        y: [0, 0]
      },
      vPosRange: {
        x: [0, 0],
        topY: [0, 0]
      }
    };
  }

  /**
   * 重新布局所有图片
   * @param centerIndex 指定居中排列哪张图片
   */
  rearrange(centerIndex) {
    var imgsArrangeArr = this.state.imgsArrangeArr;
    var centerPos = this.constant.centerPos;
    var hPosRangeLeftSecX = this.constant.hPosRange.leftSecX;
    var hPosRangeRightSecX = this.constant.hPosRange.rightSecX;
    var hPosRangeY = this.constant.hPosRange.y;
    var vPosRangeTopY = this.constant.vPosRange.topY;
    var vPosRangeX = this.constant.vPosRange.x;

    // 中间图片处理
    var imgCenterArr = imgsArrangeArr.splice(centerIndex, 1);
    imgCenterArr[0].pos.left = centerPos.left;
    imgCenterArr[0].pos.top = centerPos.top;

    // 上层图片处理
    var imgTopCount = Math.floor(Math.random() * 2);
    var imgTopArrangeIndex = Math.floor(Math.random() * (imgsArrangeArr.length - imgTopCount));
    var imgTopArrangeArr = imgsArrangeArr.splice(imgTopArrangeIndex, imgTopCount);
    imgTopArrangeArr.forEach((value, index) => {
      imgTopArrangeArr[index].pos = {
        left: getRangeRandom(vPosRangeX[0], vPosRangeX[1]),
        top: getRangeRandom(vPosRangeTopY[0], vPosRangeTopY[1])
      }
    });

    // 两侧图片排列
    for(var i = 0, j = imgsArrangeArr.length, k = j / 2; i < j; i++) {
      var hPosRangeLOR;
      if(i < k) {
        // 图片放置左侧排列
        var hPosRangeLOR = hPosRangeLeftSecX;
        imgsArrangeArr[i].pos = {
          left: getRangeRandom(hPosRangeLOR[0], hPosRangeLOR[1]),
          top: getRangeRandom(hPosRangeY[0], hPosRangeY[1])
        };
      } else {
        // 图片放置右侧排列
        var hPosRangeLOR = hPosRangeRightSecX;
        imgsArrangeArr[i].pos = {
          left: getRangeRandom(hPosRangeLOR[0], hPosRangeLOR[1]),
          top: getRangeRandom(hPosRangeY[0], hPosRangeY[1])
        };
      }
    }
    imgsArrangeArr.splice(centerIndex, 0, imgCenterArr[0]);
    if(imgTopArrangeArr && imgTopArrangeArr[0]) {
      imgsArrangeArr.splice(imgTopArrangeIndex, 0, imgTopArrangeArr[0]);
    }
    this.setState({
      imgArrangeArr: imgsArrangeArr
    });
  }

  componentDidMount() {
    var stage = ReactDOM.findDOMNode(this.refs.stage);
    var stageW = stage.scrollWidth;
    var stageH = stage.scrollHeight;
    var halfStageW = Math.ceil(stageW / 2);
    var halfStageH = Math.ceil(stageH / 2);

    var imgFigure = ReactDOM.findDOMNode(this.refs.imgFigure0);
    var imgFigureW = imgFigure.scrollWidth;
    var imgFigureH = imgFigure.scrollHeight;
    var halfImgFigureW = Math.ceil(imgFigureW / 2);
    var halfImgFigureH = Math.ceil(imgFigureH / 2);

    this.constant.centerPos.left = halfStageW - halfImgFigureW;
    this.constant.centerPos.top = halfStageH - halfImgFigureH;

    this.constant.hPosRange.leftSecX[0] = -halfImgFigureW;
    this.constant.hPosRange.leftSecX[1] = halfStageW - halfImgFigureW * 3;
    this.constant.hPosRange.rightSecX[0] = halfImgFigureW + halfStageW;
    this.constant.hPosRange.rightSecX[1] = stageW - halfImgFigureW;
    this.constant.hPosRange.y[0] = -halfImgFigureH;
    this.constant.hPosRange.y[1] = stageH - halfImgFigureH;

    this.constant.vPosRange.x[0] = halfStageW - imgFigureW;
    this.constant.vPosRange.x[1] = halfStageW;
    this.constant.vPosRange.topY[0] = -halfImgFigureH;
    this.constant.vPosRange.topY[1] = halfStageH - halfImgFigureH * 3;

    this.rearrange(0);
  }

  render() {
    var imgFigures = [],
        controllersUnit = [];

    imageDatas.forEach((value, index) => {
      if(!this.state.imgsArrangeArr[index]) {
        this.state.imgsArrangeArr[index] = {
          pos: {
            top: '0',
            left: '0'
          }
        };
      }
      imgFigures.push(<ImgFigure key={index} data={value} ref={'imgFigure' + index} arrange={this.state.imgsArrangeArr[index]}/>);
    });

    return (

          {imgFigures}

          {controllersUnit}

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<commit_msg>update2<commit_after>require('normalize.css/normalize.css');
require('styles/App.scss');

var imageDatas = require('../sources/imageDatas.json');

import React from 'react';
import ReactDOM from 'react-dom';

(function genImageUrl(imageDataArr) {
	for(var imageData of imageDataArr) {
		imageData.imageUrl = require('../images/' + imageData.fileName);
	}
})(imageDatas);

function getRangeRandom(low, high) {
  return Math.floor(Math.random() * (high - low) + low);
}

function get30degRandom() {
  return (Math.random() > 0.5 ? '' : '-') + Math.ceil(Math.random() * 30);
}

class ImgFigure extends React.Component {
  render() {
    var styleObj = {};

    // 是否存在图片定位信息
    if(this.props.arrange.pos) {
      Object.assign(styleObj, this.props.arrange.pos);
    }

    // 是否存在图片旋转信息
    if(this.props.arrange.rotate) {
      ['MozTransform', 'WebkitTransform', 'msTransform', 'transform'].forEach((value) => {
        styleObj[value] = 'rotate(' + this.props.arrange.rotate + 'deg)';
      })
    }

    return (

{this.props.data.title}

    )
  }
}

class AppComponent extends React.Component {
  constructor() {
    super();
    //用来保存图片状态信息
    this.state = {
      imgsArrangeArr: [
        /*{
          pos: {
            left: '0';
            top: '0'
          },
          rotate: 30  //添加图片旋转角度
        }*/
      ]
    };
    this.constant = {
      centerPos: {
        left: 0,
        top: 0
      },
      hPosRange: {
        leftSecX: [0, 0],
        rightSecX: [0, 0],
        y: [0, 0]
      },
      vPosRange: {
        x: [0, 0],
        topY: [0, 0]
      }
    };
  }

  /**
   * 重新布局所有图片
   * @param centerIndex 指定居中排列哪张图片
   */
  rearrange(centerIndex) {
    var imgsArrangeArr = this.state.imgsArrangeArr;
    var centerPos = this.constant.centerPos;
    var hPosRangeLeftSecX = this.constant.hPosRange.leftSecX;
    var hPosRangeRightSecX = this.constant.hPosRange.rightSecX;
    var hPosRangeY = this.constant.hPosRange.y;
    var vPosRangeTopY = this.constant.vPosRange.topY;
    var vPosRangeX = this.constant.vPosRange.x;

    // 中间图片处理
    var imgCenterArr = imgsArrangeArr.splice(centerIndex, 1);
    imgCenterArr[0] = {
      pos: {
        left: centerPos.left,
        top: centerPos.top
      },
      rotate: 0
    }

    // 上层图片处理
    var imgTopCount = Math.floor(Math.random() * 2);
    var imgTopArrangeIndex = Math.floor(Math.random() * (imgsArrangeArr.length - imgTopCount));
    var imgTopArrangeArr = imgsArrangeArr.splice(imgTopArrangeIndex, imgTopCount);
    imgTopArrangeArr.forEach((value, index) => {
      imgTopArrangeArr[index] = {
        pos: {
          left: getRangeRandom(vPosRangeX[0], vPosRangeX[1]),
          top: getRangeRandom(vPosRangeTopY[0], vPosRangeTopY[1])
        },
        rotate: get30degRandom()
      }
    });

    // 两侧图片排列
    for(var i = 0, j = imgsArrangeArr.length, k = j / 2; i < j; i++) {
      var hPosRangeLOR;
      if(i < k) {
        // 图片放置左侧排列
        hPosRangeLOR = hPosRangeLeftSecX;
      } else {
        // 图片放置右侧排列
        hPosRangeLOR = hPosRangeRightSecX;
      }
      imgsArrangeArr[i] = {
        pos: {
          left: getRangeRandom(hPosRangeLOR[0], hPosRangeLOR[1]),
          top: getRangeRandom(hPosRangeY[0], hPosRangeY[1])
        },
        rotate: get30degRandom()
      };
    }
    imgsArrangeArr.splice(centerIndex, 0, imgCenterArr[0]);
    if(imgTopArrangeArr && imgTopArrangeArr[0]) {
      imgsArrangeArr.splice(imgTopArrangeIndex, 0, imgTopArrangeArr[0]);
    }
    this.setState({
      imgArrangeArr: imgsArrangeArr
    });
  }

  componentDidMount() {
    var stage = ReactDOM.findDOMNode(this.refs.stage);
    var stageW = stage.scrollWidth;
    var stageH = stage.scrollHeight;
    var halfStageW = Math.ceil(stageW / 2);
    var halfStageH = Math.ceil(stageH / 2);

    var imgFigure = ReactDOM.findDOMNode(this.refs.imgFigure0);
    var imgFigureW = imgFigure.scrollWidth;
    var imgFigureH = imgFigure.scrollHeight;
    var halfImgFigureW = Math.ceil(imgFigureW / 2);
    var halfImgFigureH = Math.ceil(imgFigureH / 2);

    this.constant.centerPos.left = halfStageW - halfImgFigureW;
    this.constant.centerPos.top = halfStageH - halfImgFigureH;

    this.constant.hPosRange.leftSecX[0] = -halfImgFigureW;
    this.constant.hPosRange.leftSecX[1] = halfStageW - halfImgFigureW * 3;
    this.constant.hPosRange.rightSecX[0] = halfImgFigureW + halfStageW;
    this.constant.hPosRange.rightSecX[1] = stageW - halfImgFigureW;
    this.constant.hPosRange.y[0] = -halfImgFigureH;
    this.constant.hPosRange.y[1] = stageH - halfImgFigureH;

    this.constant.vPosRange.x[0] = halfStageW - imgFigureW;
    this.constant.vPosRange.x[1] = halfStageW;
    this.constant.vPosRange.topY[0] = -halfImgFigureH;
    this.constant.vPosRange.topY[1] = halfStageH - halfImgFigureH * 3;

    this.rearrange(0);
  }

  render() {
    var imgFigures = [],
        controllersUnit = [];

    imageDatas.forEach((value, index) => {
      if(!this.state.imgsArrangeArr[index]) {
        this.state.imgsArrangeArr[index] = {
          pos: {
            top: '0',
            left: '0'
          },
          rotate: 0
        };
      }
      imgFigures.push(<ImgFigure key={index} data={value} ref={'imgFigure' + index} arrange={this.state.imgsArrangeArr[index]}/>);
    });

    return (

          {imgFigures}

          {controllersUnit}

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<|endoftext|>


<commit_before>require('normalize.css/normalize.css');
require('styles/App.scss');

import React from 'react';

//获取图片相关的数据
let imageDatas = require('../data/imageDatas.json');
//利用自执行函数，将图片名信息，转换为图片路径信息
imageDatas = (function generatorsImageUrl(imageDataArray){
     for(var i=0;i<imageDataArray.length;i++)
     {
         var singleImageData = imageDataArray[i];
         singleImageData.imageUrl = require('../images/' + singleImageData.fileName);
         imageDataArray[i] = singleImageData;
     }
     return imageDataArray;
})(imageDatas);

class ImgFigure extends React.Component{
   render(){
     return (
         //figure标签通常用来表示自包含的单个单元内容，自包含：单独拿出来，放在哪里它也是有意义的 figcaption用来定义标题

{this.props.data.desc}

     );
    }
}

class AppComponent extends React.Component {
  render()
    {
        let controllerUnits = [];
        let imgFigures = [];
        for (var i = 0; i < imageDatas.length; i++)
        {
            var value = imageDatas[i];
            imgFigures.push(<ImgFigure data={value} />);
        }
    return (

             {imgFigures}

             {controllerUnits}

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<commit_msg>增加图片位置排布和旋转<commit_after>require('normalize.css/normalize.css');
require('styles/App.scss');

import React from 'react';
import ReactDOM from 'react-dom';

//获取图片相关的数据
let imageDatas = require('../data/imageDatas.json');
//利用自执行函数，将图片名信息，转换为图片路径信息
imageDatas = (function generatorsImageUrl(imageDataArray){
     for(let i=0;i<imageDataArray.length;i++)
     {
         let singleImageData = imageDataArray[i];
         singleImageData.imageUrl = require('../images/' + singleImageData.fileName);
         imageDataArray[i] = singleImageData;
     }
     return imageDataArray;
})(imageDatas);

//获取区间内的一个随机值
function getRangeRandom(low,high)
{
    return Math.ceil(Math.random() * (high - low) + low);
}

//获取0-30度之间的任意正负值
function get30DegRandom()
{
    return ((Math.random > 0.5 ? '' : '-') + Math.ceil(Math.random() * 30));
}

class ImgFigure extends React.Component{
   render(){
     var styleObj = {};
     //如果props属性中指定这张图片的位置，则使用
     if(this.props.arrange.pos)
     {
        styleObj = this.props.arrange.pos;
     }

     //如果图片的旋转角度有值并且不为0，添加旋转角度
     if(this.props.arrange.rotate)
     {
         (['-moz-','-ms-','-webkit-','']).forEach(function (value,index){

             styleObj[value + "transform"] = 'rotate(' + this.props.arrange.rotate + 'deg)';

         }.bind(this));

     }
     return (
         //figure标签通常用来表示自包含的单个单元内容，自包含：单独拿出来，放在哪里它也是有意义的 figcaption用来定义标题

{this.props.data.desc}

     );
    }
}

class AppComponent extends React.Component {
  constructor(props){
      super(props);
      this.state = ({
          imgsArrangeArr: [
              // {
              //    pos:{
              //       left: 0,
              //       top: 0
              //    },
              //    rotate: 0 //旋转角度
              // }
          ]
      });
      this.Constant = ({
           CenterPos : {
              left: 0,
              top: 0
           },
           hPosRange : {  //水平方向的取值范围
              leftSecX: [0,0],
              rightSecX: [0,0],
              y: [0,0]
           },
           vPosRange : {  //垂直方向的取值范围
              x: [0,0],
              topY: [0,0],

           }
      });
  }
  //组件加载后，为每张图片计算其位置的范围
  componentDidMount(){
     //首先，通过refs属性拿到舞台的大小
     let stageDOM = ReactDOM.findDOMNode(this.refs.stage),
     //scrollWidth：对象的实际内容的宽度，不包边线宽度，会随对象中内容超过可视区后而变大。
     //clientWidth：对象内容的可视区的宽度，不包滚动条等边线，会随对象显示大小的变化而改变。
     //offsetWidth：对象整体的实际宽度，包滚动条等边线，会随对象显示大小的变化而改变
         stageW = stageDOM.scrollWidth,
         stageH = stageDOM.scrollHeight,
         halfStageW = Math.ceil(stageW / 2),
         halfStageH = Math.ceil(stageH / 2);

     //拿到一个imFigure的大小
     let imgFigureDOM = ReactDOM.findDOMNode(this.refs.imgFigure0),
         imgW = imgFigureDOM.scrollWidth,
         imgH = imgFigureDOM.scrollHeight,
         halfImgW = Math.ceil(imgW / 2),
         halfImgH = Math.ceil(imgH / 2);
      //计算中心图片的位置点
      this.Constant.CenterPos.left = halfStageW - halfImgW;
      this.Constant.CenterPos.top = halfStageH - halfImgH;

      //计算左侧，右侧区域图片排布位置的取值范围
      this.Constant.hPosRange.leftSecX[0] = 0 - halfImgW;
      this.Constant.hPosRange.leftSecX[1] = halfStageW - halfImgW * 3;
      this.Constant.hPosRange.rightSecX[0] = halfStageW + halfImgW;
      this.Constant.hPosRange.rightSecX[1] = stageW - halfImgW;
      this.Constant.hPosRange.y[0] = 0 - halfImgH;
      this.Constant.hPosRange.y[1] = stageH - halfImgH;

      //计算上侧区域图片排布范围的取值范围
      this.Constant.vPosRange.topY[0] = 0 - halfImgW;
      this.Constant.vPosRange.topY[1] = halfStageH - halfImgH * 3;
      this.Constant.vPosRange.x[0] = halfStageW - imgW;
      this.Constant.vPosRange.x[1] = halfStageW;

      //指定初始时第一张的位置居中
      this.rearrange(0);
  }
  /*
   重新布局所有图片
  */
  rearrange(centerIndex){
      let imgsArrangeArr = this.state.imgsArrangeArr;
      let Constant = this.Constant;
      let centerPos = Constant.CenterPos;
      let hPosRange = Constant.hPosRange;
      let vPosRange = Constant.vPosRange;
      let hPosRangeLeftSecX = Constant.hPosRange.leftSecX;
      let hPosRangeRightSecX = Constant.hPosRange.rightSecX;
      let hPosRangeY = hPosRange.y;
      let vPosRangeTopY = vPosRange.topY;
      let vPosRangeX = vPosRange.x;

      let imgsArrangeTopArr = [];
      let topImgNum = Math.ceil(Math.random() * 2)   //取一个或者不取
      let topImgSpliceIndex = 0;

      let imgsArrangeCenterArr = imgsArrangeArr.splice(centerIndex,1);

      //首先居中centerIndex的图片
      imgsArrangeCenterArr[0].pos = centerPos;
      //居中的 centerIndex 的图片不需要旋转
      imgsArrangeCenterArr[0].rotate = 0;

      //取出要布局上侧的图片的状态信息
      topImgSpliceIndex = Math.ceil(Math.random() * (imgsArrangeArr.length - topImgNum));
      imgsArrangeTopArr = imgsArrangeArr.splice(topImgSpliceIndex,topImgNum);

      //布局位于上侧的图片
      imgsArrangeTopArr.forEach((value,index) => {
          imgsArrangeTopArr[index] = {
             pos:{
                  top: getRangeRandom(vPosRangeTopY[0],vPosRangeTopY[1]),
                  left: getRangeRandom(vPosRangeX[0],vPosRangeX[1])
             },
             rotate: get30DegRandom()
          }
      });

      let k = imgsArrangeArr.length / 2;
      //布局左右两侧的图片
      for(let i = 0; i < imgsArrangeArr.length; i++)
      {
           let hPosRangeLORX = null;
           //前半部分布局左边，右半部份布局右边
           if(i < k){
               hPosRangeLORX = hPosRangeLeftSecX;
           }
           else{
               hPosRangeLORX = hPosRangeRightSecX;
           }
           imgsArrangeArr[i] = {
               pos:{
                  top : getRangeRandom(hPosRangeY[0],hPosRangeY[1]),
                  left : getRangeRandom(hPosRangeLORX[0],hPosRangeLORX[1])
               },
               rotate: get30DegRandom()
           }
      }

      if(imgsArrangeTopArr && imgsArrangeTopArr[0])
      {
         imgsArrangeArr.splice(topImgSpliceIndex,0,imgsArrangeTopArr[0]);
      }
      imgsArrangeArr.splice(centerIndex,0,imgsArrangeCenterArr[0]);

      this.setState({
         imgsArrangeArr : imgsArrangeArr
      });
  }
  render()
    {
        let controllerUnits = [];
        let imgFigures = [];
        // for (var i = 0; i < imageDatas.length; i++)
        // {
        //     var value = imageDatas[i];
        //     imgFigures.push(<ImgFigure data={value}  ref={'imgFigure' + i}/>);
        // }
        imageDatas.forEach(function(value,index) {
            if(!this.state.imgsArrangeArr[index])
            {
                this.state.imgsArrangeArr[index] = {
                    pos:{
                        left : 0,
                        top : 0
                    },
                    rotate : 0
                }
            }
            imgFigures.push(<ImgFigure data={value}  ref={'imgFigure' + index}  arrange={this.state.imgsArrangeArr[index]} />);
        }.bind(this));
    return (

             {imgFigures}

             {controllerUnits}

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;
<|endoftext|>


<commit_before>import React from 'react';
import { Map } from 'react-leaflet';
import { connect } from 'react-redux';
import { ToastContainer, ToastMessage } from 'react-toastr';
import queryString from 'query-string';

import Navbar from 'react-bootstrap/lib/Navbar';
import Row from 'react-bootstrap/lib/Row';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';

import NavbarHeader from 'fm3/components/NavbarHeader';
import Layers from 'fm3/components/Layers';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import ProgressIndicator from 'fm3/components/ProgressIndicator';

import SearchMenu from 'fm3/components/SearchMenu';
import SearchResults from 'fm3/components/SearchResults';

import ObjectsMenu from 'fm3/components/ObjectsMenu';
import ObjectsResult from 'fm3/components/ObjectsResult';

import MeasurementMenu from 'fm3/components/MeasurementMenu';
import DistanceMeasurementResult from 'fm3/components/DistanceMeasurementResult';
import AreaMeasurementResult from 'fm3/components/AreaMeasurementResult';
import ElevationMeasurementResult from 'fm3/components/ElevationMeasurementResult';

import RoutePlannerMenu from 'fm3/components/RoutePlannerMenu';
import RoutePlannerResult from 'fm3/components/RoutePlannerResult';
import Settings from 'fm3/components/Settings';

import * as FmPropTypes from 'fm3/propTypes';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';

import { mapRefocus } from 'fm3/actions/mapActions';
import { setTool } from 'fm3/actions/mainActions';
import { setActivePopup } from 'fm3/actions/mainActions';

import { baseLayers, overlayLayers } from 'fm3/mapDefinitions';
import { setLeafletElement } from 'fm3/leafletElementHolder';

import 'fm3/styles/main.scss';

const ToastMessageFactory = React.createFactory(ToastMessage.animation);

class Main extends React.Component {

  static propTypes = {
    lat: React.PropTypes.number,
    lon: React.PropTypes.number,
    zoom: React.PropTypes.number,
    match: React.PropTypes.object,
    history: React.PropTypes.object,
    tool: React.PropTypes.string,
    tileFormat: FmPropTypes.tileFormat.isRequired,
    overlays: FmPropTypes.overlays,
    overlayOpacity: React.PropTypes.any,
    mapType: FmPropTypes.mapType.isRequired,
    onSetTool: React.PropTypes.func.isRequired,
    onMapRefocus: React.PropTypes.func.isRequired,
    activePopup: React.PropTypes.string,
    onLaunchPopup: React.PropTypes.func.isRequired,
    progress: React.PropTypes.bool,
  };

  componentWillMount() {
    // set redux according to URL
    this.props.onMapRefocus(getMapDiff(this.props));
  }

  componentWillUnmount() {
    setLeafletElement(null);
  }

  componentDidMount() {
    setLeafletElement(this.refs.map.leafletElement);
  }

  componentWillReceiveProps(newProps) {
    this.ignoreMapMoveEndEvent = true;

    const stateChanged = [ 'mapType', 'overlays', 'zoom', 'lat', 'lon' ].some(prop => newProps[prop] !== this.props[prop]);
    if (stateChanged) {
      // update URL
      updateUrl(newProps);
    } else {
      // set redux according to URL
      const changes = getMapDiff(newProps);
      if (Object.keys(changes).length) {
        newProps.onMapRefocus(changes);
      }
    }
  }

  componentDidUpdate() {
    this.ignoreMapMoveEndEvent = false;
  }

  handleMapMoveEnd() {
    if (this.ignoreMapMoveEndEvent) {
      return;
    }

    const map = this.refs.map.leafletElement;
    const { lat, lng: lon } = map.getCenter();
    const zoom = map.getZoom();

    if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== zoom) {
      this.props.onMapRefocus({ lat, lon, zoom });
    }
  }

  handleMapTypeChange(mapType) {
    if (this.props.mapType !== mapType) {
      this.props.onMapRefocus({ mapType });
    }
  }

  handleOverlayChange(overlays) {
    this.props.onMapRefocus({ overlays });
  }

  handleMapClick({ latlng: { lat, lng: lon } }) {
    mapEventEmitter.emit('mapClick', lat, lon);
  }

  // handlePoiSearch() {
  //   if (this.props.zoom < 12) {
  //     this.showToast('info', null, 'Vyhľadávanie miest funguje až od zoom úrovne 12');
  //   } else {
  //     this.props.onSetTool('objects');
  //   }
  // }

  showToast(toastType, line1, line2) {
    this.refs.toastContainer[toastType](
      line2,
      line1, // sic!
      { timeOut: 3000, showAnimation: 'animated fadeIn', hideAnimation: 'animated fadeOut' }
    );
  }

  handleToolSet(tool) {
    this.props.onSetTool(this.props.tool === tool ? null : tool); // toggle tool
  }

  render() {
    const { tool, tileFormat, activePopup, onLaunchPopup, progress } = this.props;
    const b = (fn, ...args) => fn.bind(this, ...args);
    const showDefaultMenu = [ null, 'select-home-location' ].indexOf(tool) !== -1;

    return (

        <Row>
          <Navbar fluid style={{ marginBottom: 0 }}>
            <NavbarHeader/>
            <Navbar.Collapse>
              {tool === 'objects' && <ObjectsMenu onShowToast={b(this.showToast)}/>}
              {(showDefaultMenu || tool === 'search') && <SearchMenu/>}
              {tool === 'route-planner' && <RoutePlannerMenu onShowToast={b(this.showToast)}/>}
              {(tool === 'measure' || tool === 'measure-ele' || tool === 'measure-area') && <MeasurementMenu/>}
              {activePopup === 'settings' && <Settings onShowToast={b(this.showToast)}/>}
              {showDefaultMenu &&

                  <NavItem onClick={b(this.handleToolSet, 'objects')}>
                  <FontAwesomeIcon icon="map-marker"/> Miesta
                  </NavItem>
                  <NavItem onClick={b(this.handleToolSet, 'route-planner')}>
                    <FontAwesomeIcon icon="map-signs"/> Plánovač
                  </NavItem>
                  <NavItem onClick={b(this.handleToolSet, 'measure')}>
                    <FontAwesomeIcon icon="arrows-h"/> Meranie
                  </NavItem>

              }
              {showDefaultMenu &&

                  <NavDropdown title="Viac" id="additional-menu-items">
                    <MenuItem onClick={() => onLaunchPopup('settings')}><FontAwesomeIcon icon="cog"/> Nastavenia</MenuItem>
                    <MenuItem divider></MenuItem>
                    <MenuItem onClick={() => window.open('http://wiki.freemap.sk/NahlasenieChyby')}><FontAwesomeIcon icon="exclamation-triangle"/> Nahlás chybu</MenuItem>
                  </NavDropdown>

              }
            </Navbar.Collapse>
          </Navbar>
        </Row>
        <Row>
          <ProgressIndicator active={progress}/>
        </Row>
        <Row className={`map-holder tool-${tool || 'none'} active-map-type-${this.props.mapType}`}>
          <Map
            ref="map"
            center={L.latLng(this.props.lat, this.props.lon)}
            zoom={this.props.zoom}
            onMoveend={b(this.handleMapMoveEnd)}
            onClick={b(this.handleMapClick)}
          >
            <Layers
              mapType={this.props.mapType} onMapChange={b(this.handleMapTypeChange)}
              overlays={this.props.overlays} onOverlaysChange={b(this.handleOverlayChange)}
              tileFormat={tileFormat} overlayOpacity={this.props.overlayOpacity}
            />

            {(showDefaultMenu || tool === 'search') && <SearchResults/>}

            {tool === 'objects' && <ObjectsResult/>}

            {tool === 'route-planner' && <RoutePlannerResult onShowToast={b(this.showToast)}/>}

            {tool === 'measure' && <DistanceMeasurementResult/>}

            {tool === 'measure-ele' && <ElevationMeasurementResult/>}

            {tool === 'measure-area' && <AreaMeasurementResult onShowToast={b(this.showToast)}/>}
          </Map>
        </Row>

        <ToastContainer
          ref="toastContainer"
          toastMessageFactory={ToastMessageFactory}
          className="toast-top-right"/>

    );
  }
}

export default connect(
  function (state) {
    return {
      lat: state.map.lat,
      lon: state.map.lon,
      zoom: state.map.zoom,
      tool: state.main.tool,
      mapType: state.map.mapType,
      overlays: state.map.overlays,
      overlayOpacity: state.map.overlayOpacity,
      tileFormat: state.map.tileFormat,
      activePopup: state.main.activePopup,
      progress: state.main.progress,
    };
  },
  function (dispatch) {
    return {
      onSetTool(tool) {
        dispatch(setTool(tool));
      },
      onMapRefocus(changes) {
        dispatch(mapRefocus(changes));
      },
      onLaunchPopup(popupName) {
        dispatch(setActivePopup(popupName));
      }
    };
  }
)(Main);

const baseLetters = baseLayers.map(({ type }) => type).join('');
const overlayLetters = overlayLayers.map(({ type }) => type).join('');
const layersRegExp = new RegExp(`^[${baseLetters}][${overlayLetters}]*$`);

function getMapDiff(props) {
  const { location: { search } } = props;

  const query = queryString.parse(search);

  const [ zoomFrag, latFrag, lonFrag ] = (query.map || '').split('/');

  const lat = parseFloat(latFrag);
  const lon = parseFloat(lonFrag);
  const zoom = parseInt(zoomFrag);

  const layers = query.layers || '';

  const layersOK = layersRegExp.test(layers);

  if (!layersOK || isNaN(lat) || isNaN(lon) || isNaN(zoom)) {
    updateUrl(props);
    return {};
  }

  const mapType = layers.charAt(0);
  const overlays = layers.length > 1 ? layers.substring(1).split('') : [];

  const changes = {};

  if (mapType !== props.mapType) {
    changes.mapType = mapType;
  }

  if (overlays.join('') !== props.overlays.join('')) {
    changes.overlays = overlays;
  }

  if (Math.abs(lat - props.lat) > 0.00001) {
    changes.lat = lat;
  }

  if (Math.abs(lon - props.lon) > 0.00001) {
    changes.lon = lon;
  }

  if (zoom !== props.zoom) {
    changes.zoom = zoom;
  }

  return changes;
}

function updateUrl(props) {
  const { mapType, overlays, zoom, lat, lon } = props;
  props.history.replace({
    search: `?map=${zoom}/${lat.toFixed(5)}/${lon.toFixed(5)}&layers=${mapType}${overlays.join('')}`
  });
}
<commit_msg>#85: show map scale<commit_after>import React from 'react';
import { Map, ScaleControl } from 'react-leaflet';
import { connect } from 'react-redux';
import { ToastContainer, ToastMessage } from 'react-toastr';
import queryString from 'query-string';

import Navbar from 'react-bootstrap/lib/Navbar';
import Row from 'react-bootstrap/lib/Row';
import Nav from 'react-bootstrap/lib/Nav';
import NavItem from 'react-bootstrap/lib/NavItem';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';

import NavbarHeader from 'fm3/components/NavbarHeader';
import Layers from 'fm3/components/Layers';
import FontAwesomeIcon from 'fm3/components/FontAwesomeIcon';
import ProgressIndicator from 'fm3/components/ProgressIndicator';

import SearchMenu from 'fm3/components/SearchMenu';
import SearchResults from 'fm3/components/SearchResults';

import ObjectsMenu from 'fm3/components/ObjectsMenu';
import ObjectsResult from 'fm3/components/ObjectsResult';

import MeasurementMenu from 'fm3/components/MeasurementMenu';
import DistanceMeasurementResult from 'fm3/components/DistanceMeasurementResult';
import AreaMeasurementResult from 'fm3/components/AreaMeasurementResult';
import ElevationMeasurementResult from 'fm3/components/ElevationMeasurementResult';

import RoutePlannerMenu from 'fm3/components/RoutePlannerMenu';
import RoutePlannerResult from 'fm3/components/RoutePlannerResult';
import Settings from 'fm3/components/Settings';

import * as FmPropTypes from 'fm3/propTypes';
import mapEventEmitter from 'fm3/emitters/mapEventEmitter';

import { mapRefocus } from 'fm3/actions/mapActions';
import { setTool } from 'fm3/actions/mainActions';
import { setActivePopup } from 'fm3/actions/mainActions';

import { baseLayers, overlayLayers } from 'fm3/mapDefinitions';
import { setLeafletElement } from 'fm3/leafletElementHolder';

import 'fm3/styles/main.scss';

const ToastMessageFactory = React.createFactory(ToastMessage.animation);

class Main extends React.Component {

  static propTypes = {
    lat: React.PropTypes.number,
    lon: React.PropTypes.number,
    zoom: React.PropTypes.number,
    match: React.PropTypes.object,
    history: React.PropTypes.object,
    tool: React.PropTypes.string,
    tileFormat: FmPropTypes.tileFormat.isRequired,
    overlays: FmPropTypes.overlays,
    overlayOpacity: React.PropTypes.any,
    mapType: FmPropTypes.mapType.isRequired,
    onSetTool: React.PropTypes.func.isRequired,
    onMapRefocus: React.PropTypes.func.isRequired,
    activePopup: React.PropTypes.string,
    onLaunchPopup: React.PropTypes.func.isRequired,
    progress: React.PropTypes.bool,
  };

  componentWillMount() {
    // set redux according to URL
    this.props.onMapRefocus(getMapDiff(this.props));
  }

  componentWillUnmount() {
    setLeafletElement(null);
  }

  componentDidMount() {
    setLeafletElement(this.refs.map.leafletElement);
  }

  componentWillReceiveProps(newProps) {
    this.ignoreMapMoveEndEvent = true;

    const stateChanged = [ 'mapType', 'overlays', 'zoom', 'lat', 'lon' ].some(prop => newProps[prop] !== this.props[prop]);
    if (stateChanged) {
      // update URL
      updateUrl(newProps);
    } else {
      // set redux according to URL
      const changes = getMapDiff(newProps);
      if (Object.keys(changes).length) {
        newProps.onMapRefocus(changes);
      }
    }
  }

  componentDidUpdate() {
    this.ignoreMapMoveEndEvent = false;
  }

  handleMapMoveEnd() {
    if (this.ignoreMapMoveEndEvent) {
      return;
    }

    const map = this.refs.map.leafletElement;
    const { lat, lng: lon } = map.getCenter();
    const zoom = map.getZoom();

    if (this.props.lat !== lat || this.props.lon !== lon || this.props.zoom !== zoom) {
      this.props.onMapRefocus({ lat, lon, zoom });
    }
  }

  handleMapTypeChange(mapType) {
    if (this.props.mapType !== mapType) {
      this.props.onMapRefocus({ mapType });
    }
  }

  handleOverlayChange(overlays) {
    this.props.onMapRefocus({ overlays });
  }

  handleMapClick({ latlng: { lat, lng: lon } }) {
    mapEventEmitter.emit('mapClick', lat, lon);
  }

  // handlePoiSearch() {
  //   if (this.props.zoom < 12) {
  //     this.showToast('info', null, 'Vyhľadávanie miest funguje až od zoom úrovne 12');
  //   } else {
  //     this.props.onSetTool('objects');
  //   }
  // }

  showToast(toastType, line1, line2) {
    this.refs.toastContainer[toastType](
      line2,
      line1, // sic!
      { timeOut: 3000, showAnimation: 'animated fadeIn', hideAnimation: 'animated fadeOut' }
    );
  }

  handleToolSet(tool) {
    this.props.onSetTool(this.props.tool === tool ? null : tool); // toggle tool
  }

  render() {
    const { tool, tileFormat, activePopup, onLaunchPopup, progress } = this.props;
    const b = (fn, ...args) => fn.bind(this, ...args);
    const showDefaultMenu = [ null, 'select-home-location' ].indexOf(tool) !== -1;

    return (

        <Row>
          <Navbar fluid style={{ marginBottom: 0 }}>
            <NavbarHeader/>
            <Navbar.Collapse>
              {tool === 'objects' && <ObjectsMenu onShowToast={b(this.showToast)}/>}
              {(showDefaultMenu || tool === 'search') && <SearchMenu/>}
              {tool === 'route-planner' && <RoutePlannerMenu onShowToast={b(this.showToast)}/>}
              {(tool === 'measure' || tool === 'measure-ele' || tool === 'measure-area') && <MeasurementMenu/>}
              {activePopup === 'settings' && <Settings onShowToast={b(this.showToast)}/>}
              {showDefaultMenu &&

                  <NavItem onClick={b(this.handleToolSet, 'objects')}>
                  <FontAwesomeIcon icon="map-marker"/> Miesta
                  </NavItem>
                  <NavItem onClick={b(this.handleToolSet, 'route-planner')}>
                    <FontAwesomeIcon icon="map-signs"/> Plánovač
                  </NavItem>
                  <NavItem onClick={b(this.handleToolSet, 'measure')}>
                    <FontAwesomeIcon icon="arrows-h"/> Meranie
                  </NavItem>

              }
              {showDefaultMenu &&

                  <NavDropdown title="Viac" id="additional-menu-items">
                    <MenuItem onClick={() => onLaunchPopup('settings')}><FontAwesomeIcon icon="cog"/> Nastavenia</MenuItem>
                    <MenuItem divider></MenuItem>
                    <MenuItem onClick={() => window.open('http://wiki.freemap.sk/NahlasenieChyby')}><FontAwesomeIcon icon="exclamation-triangle"/> Nahlás chybu</MenuItem>
                  </NavDropdown>

              }
            </Navbar.Collapse>
          </Navbar>
        </Row>
        <Row>
          <ProgressIndicator active={progress}/>
        </Row>
        <Row className={`map-holder tool-${tool || 'none'} active-map-type-${this.props.mapType}`}>
          <Map
            ref="map"
            center={L.latLng(this.props.lat, this.props.lon)}
            zoom={this.props.zoom}
            onMoveend={b(this.handleMapMoveEnd)}
            onClick={b(this.handleMapClick)}
          >
            <Layers
              mapType={this.props.mapType} onMapChange={b(this.handleMapTypeChange)}
              overlays={this.props.overlays} onOverlaysChange={b(this.handleOverlayChange)}
              tileFormat={tileFormat} overlayOpacity={this.props.overlayOpacity}
            />

            {(tool === 'measure' || tool === 'measure-ele' || tool === 'measure-area') && <ScaleControl imperial={false} position="bottomright"/> }

            {(showDefaultMenu || tool === 'search') && <SearchResults/>}

            {tool === 'objects' && <ObjectsResult/>}

            {tool === 'route-planner' && <RoutePlannerResult onShowToast={b(this.showToast)}/>}

            {tool === 'measure' && <DistanceMeasurementResult/>}

            {tool === 'measure-ele' && <ElevationMeasurementResult/>}

            {tool === 'measure-area' && <AreaMeasurementResult onShowToast={b(this.showToast)}/>}
          </Map>
        </Row>

        <ToastContainer
          ref="toastContainer"
          toastMessageFactory={ToastMessageFactory}
          className="toast-top-right"/>

    );
  }
}

export default connect(
  function (state) {
    return {
      lat: state.map.lat,
      lon: state.map.lon,
      zoom: state.map.zoom,
      tool: state.main.tool,
      mapType: state.map.mapType,
      overlays: state.map.overlays,
      overlayOpacity: state.map.overlayOpacity,
      tileFormat: state.map.tileFormat,
      activePopup: state.main.activePopup,
      progress: state.main.progress,
    };
  },
  function (dispatch) {
    return {
      onSetTool(tool) {
        dispatch(setTool(tool));
      },
      onMapRefocus(changes) {
        dispatch(mapRefocus(changes));
      },
      onLaunchPopup(popupName) {
        dispatch(setActivePopup(popupName));
      }
    };
  }
)(Main);

const baseLetters = baseLayers.map(({ type }) => type).join('');
const overlayLetters = overlayLayers.map(({ type }) => type).join('');
const layersRegExp = new RegExp(`^[${baseLetters}][${overlayLetters}]*$`);

function getMapDiff(props) {
  const { location: { search } } = props;

  const query = queryString.parse(search);

  const [ zoomFrag, latFrag, lonFrag ] = (query.map || '').split('/');

  const lat = parseFloat(latFrag);
  const lon = parseFloat(lonFrag);
  const zoom = parseInt(zoomFrag);

  const layers = query.layers || '';

  const layersOK = layersRegExp.test(layers);

  if (!layersOK || isNaN(lat) || isNaN(lon) || isNaN(zoom)) {
    updateUrl(props);
    return {};
  }

  const mapType = layers.charAt(0);
  const overlays = layers.length > 1 ? layers.substring(1).split('') : [];

  const changes = {};

  if (mapType !== props.mapType) {
    changes.mapType = mapType;
  }

  if (overlays.join('') !== props.overlays.join('')) {
    changes.overlays = overlays;
  }

  if (Math.abs(lat - props.lat) > 0.00001) {
    changes.lat = lat;
  }

  if (Math.abs(lon - props.lon) > 0.00001) {
    changes.lon = lon;
  }

  if (zoom !== props.zoom) {
    changes.zoom = zoom;
  }

  return changes;
}

function updateUrl(props) {
  const { mapType, overlays, zoom, lat, lon } = props;
  props.history.replace({
    search: `?map=${zoom}/${lat.toFixed(5)}/${lon.toFixed(5)}&layers=${mapType}${overlays.join('')}`
  });
}
<|endoftext|>


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

/**
 * @fileoverview
 *
 * Provides access to user prefs, module dimensions, and messages.
 *
 * Clients can access their preferences by constructing an instance of
 * gadgets.Prefs and passing in their module id.  Example:
 *
 *   var prefs = new gadgets.Prefs();
 *   var name = prefs.getString("name");
 *   var lang = prefs.getLang();
 *
 * Modules with type=url can also use this library to parse arguments passed
 * by URL, but this is not the common case:
 *
 *   <script src="http://apache.org/shindig/prefs.js"></script>
 *   <script>
 *   gadgets.Prefs.parseUrl();
 *   var prefs = new gadgets.Prefs();
 *   var name = prefs.getString("name");
 *   </script≶
 */

var gadgets = gadgets || {};

/**
 * Stores preferences for the default shindig implementation.
 * @private
 */
gadgets.prefs_ = function() {
  var modules = {};

  /**
   * Returns the module named by moduleId
   * @param {Number | String} moduleId The module id to fetch
   * @return {Object} An object containing module data
   */
  function getModuleData(moduleId) {
    if (!modules[moduleId]) {
      modules[moduleId] = {
        prefs:{},
        msgs:{},
        language:"all",
        country:"all"
      };
    }
    return modules[moduleId];
  }

  /**
   * Adds a new user preference to the stored set for the given module id.
   *
   * @param {String | Number} moduleId The module id to add the pref for
   * @param {String} key The key to add; may be an object where keys = key and
   *     values = value
   * @param {String} opt_value An optional value used to set the value of the
   *     key.
   */
  function setPref(moduleId, key, opt_value) {
    var module = getModuleData(moduleId);
    if (typeof key !== "string") {
      for (var i in key) {
        module.prefs[i] = key[i];
      }
    } else {
      module.prefs[key] = opt_value;
    }
  }

  /**
   * Adds a new message to the stored set for the given module id.
   *
   * @param {String | Number} moduleId The module id to add the pref for
   * @param {String | Object} key The key to add; may be an object where keys =
   *     key and values = value
   * @param {String} opt_value An optional value used to set the value of the
   *     key.
   */
  function setMsg(moduleId, key, opt_value) {
    var module = getModuleData(moduleId);
    if (typeof key !== "string") {
      for (var i in key) {
        module.msgs[i] = key[i];
      }
    } else {
      module.msgs[key] = opt_value;
    }
  }

  var defaultModuleId = 0;

  /**
   * Sets the default module id.
   *
   * @param {String | Number} moduleId The module id to set as default
   */
  function setDefaultModuleId(moduleId) {
    defaultModuleId = moduleId;
  }

  /**
   * Gets the default module id.
   *
   * @return {String | Number} The default module id
   */
  function getDefaultModuleId() {
    return defaultModuleId;
  }

  /**
   * Sets the default language for this module id.
   *
   * @param {String | Number} moduleId The module id to set the language for
   * @param {String} language The language code as an ISO 639-1 code
   */
  function setLanguage(moduleId, language) {
    getModuleData(moduleId).language = language;
  }

  /**
   * Sets the default country for this module id.
   *
   * @param {String | Number} moduleId The id of the gagdet instance
   * @param {String} country The country code as an ISO 3166-1 alpha-2 code
   */
  function setCountry(moduleId, country) {
    getModuleData(moduleId).country = country;
  }

  // Export public API for the gadget container code. Gadget authors should
  // not use this class.
  return {
    setPref: setPref,
    setMsg: setMsg,
    setCountry: setCountry,
    setLanguage: setLanguage,
    getModuleData: getModuleData,
    setDefaultModuleId: setDefaultModuleId,
    getDefaultModuleId: getDefaultModuleId
  };
}();

/**
 * @class
 * Provides access to user preferences, module dimensions, and messages.
 *
 * Clients can access their preferences by constructing an instance of
 * gadgets.Prefs and passing in their module id.  Example:
 *

var prefs = new gadgets.Prefs();
var name = prefs.getString("name");
var lang = prefs.getLang();

 *
 * @description Creates a new Prefs object.
 * @param {String | Number} opt_moduleId An optional parameter specifying the
 *     module id to create prefs for; if not provided, the default module id
 *     is used
 */
gadgets.Prefs = function(opt_moduleId) {
  if (typeof opt_moduleId === "undefined") {
    this.moduleId_ = gadgets.prefs_.getDefaultModuleId();
  } else {
    this.moduleId_ = opt_moduleId;
  }
  this.data_ = gadgets.prefs_.getModuleData(this.moduleId_);
  // This is used to eliminate one hash table lookup per value fetched.
  this.prefs_ = this.data_.prefs;
  this.msgs_ = this.data_.msgs;
};

/**
 * @static
 * @method
 * @scope gadgets.Prefs
 *
 * Static pref parser. Parses all parameters from the url and stores them
 * for later use when creating a new gadgets.Prefs object.
 * You should only ever call this if you are a type=url gadget.
 *
 * @param {String | Number} moduleId The id of the gadget instance
 * @private
 */
gadgets.Prefs.parseUrl = function(moduleId) {
  var prefs = {};
  var msgs = {};
  var country = "all";
  var language = "all";
  if (gadgets.util) {
    var params = gadgets.util.getUrlParameters();
    for (var i in params) {
      if (i.indexOf("up_") === 0 && i.length > 3) {
        prefs[i.substr(3)] = String(params[i]);
      } else if (i.indexOf("msg_") === 0 && i.length > 4) {
        msgs[i.substr(4)] = String(params[i]);
      } else if (i === "country") {
        country = params[i];
      } else if (i === "lang") {
        language = params[i];
      } else if (i === "mid") {
        moduleId = params[i];
      }
    }
  }
  gadgets.prefs_.setDefaultModuleId(moduleId);
  gadgets.prefs_.setPref(moduleId, prefs);
  gadgets.prefs_.setMsg(moduleId, msgs);
  gadgets.prefs_.setLanguage(moduleId, language);
  gadgets.prefs_.setCountry(moduleId, country);
};

/**
 * Internal helper for pref fetching.
 * @param {String} key The key to fetch
 * @return {Object} The preference
 * @private
 */
gadgets.Prefs.prototype.getPref_ = function(key) {
  var val = this.prefs_[key];
  return typeof val === "undefined" ? null : val;
}

/**
 * Retrieves a string preference.
 * @param {String} key The preference to fetch
 * @return {String} The preference; if not set, an empty string
 */
gadgets.Prefs.prototype.getString = function(key) {
  var val = this.getPref_(key);
  return val === null ? "" : val;
};

/**
 * Retrieves an integer preference.
 * @param {String} key The preference to fetch
 * @return {Number} The preference; if not set, 0
 */
gadgets.Prefs.prototype.getInt = function(key) {
  var val = parseInt(this.getPref_(key), 10);
  return isNaN(val) ? 0 : val;
};

/**
 * Retrieves a floating-point preference.
 * @param {String} key The preference to fetch
 * @return {Number} The preference; if not set, 0
 */
gadgets.Prefs.prototype.getFloat = function(key) {
  var val = parseFloat(this.getPref_(key));
  return isNaN(val) ? 0 : val;
};

/**
 * Retrieves a boolean preference.
 * @param {String} key The preference to fetch
 * @return {Boolean} The preference; if not set, false
 */
gadgets.Prefs.prototype.getBool = function(key) {
  var val = this.getPref_(key);
  if (val !== null) {
    return val === "true" || val === true || !!parseInt(val, 10);
  }
  return false;
};

/**
 * Stores a preference.
 * To use this call,
 * the gadget must require the feature setprefs.
 *
 *

 * Note:
 * If the gadget needs to store an Array it should use setArray instead of
 * this call.
 *

 *
 * @param {String} key The pref to store
 * @param {Object} val The values to store
 */
gadgets.Prefs.prototype.set = function(key, value) {
  throw new Error("setprefs feature required to make this call.");
};

/**
 * Retrieves an array preference.
 * UserPref values that were not declared as lists are treated as
 * one-element arrays.
 *
 * @param {String} key The preference to fetch
 * @return {Array.<String>} The preference; if not set, an empty array
 */
gadgets.Prefs.prototype.getArray = function(key) {
  var val = this.getPref_(key);
  if (val !== null) {
    var arr = val.split("|");
    // Decode pipe characters.
    for (var i = 0, j = arr.length; i < j; ++i) {
      arr[i] = arr[i].replace(/%7C/g, "|");
    }
    return arr;
  }
  return [];
};

/**
 * Stores an array preference.
 * To use this call,
 * the gadget must require the feature setprefs.
 *
 * @param {String} key The pref to store
 * @param {Array} val The values to store
 */
gadgets.Prefs.prototype.setArray = function(key, val) {
  throw new Error("setprefs feature required to make this call.");
};

/**
 * Fetches an unformatted message.
 * @param {String} key The message to fetch
 * @return {String} The message
 */
gadgets.Prefs.prototype.getMsg = function(key) {
  var val = this.msgs_[key];
  return typeof val === "undefined" ? "" : val;
};

/**
 *

 * The regex pulls out the text before and after the positional argument
 * and digs down for a possible example value in case no actual value
 * was provided.  It is used by the function getMsgFormatted.
 *

 *
 *

 * Example: "Foo <ph name="number"><ex>bar</ex>%1</ph> baz."
 * 0 = "Foo <ph name="number"><ex>bar</ex>%1</ph> baz." : match for the
 *     whole regex.
 *
 * 1 = "Foo " : matches first (.*) in regex
 *
 * 2 = "<ph name="number"><ex>bar</ex>%1</ph>" : matches
 *     (\<ph.*?\>\s*(\<ex\>(.*?)\<\/ex\>)?\s*%1\s*\<\/ph\>) in regex
 * 3 = "<ex>bar</ex>" : matches (\<ex\>(.*?)\<\/ex\>)? in regex, since it
 *     is an optional param it may have the value "undefined"
 * 4 = "bar" : matches (.*?) in regex (it is a non-greedy regex)
 *     if 3=undefined then 4 = "undefined".
 *
 * 5 = " baz." : matches final (.*) in regex
 *
 *

 * @private
 */
 // TODO: this may need to be a single line even though it's > 80 characters
 // because some browsers may not properly interepret the line continuation.
gadgets.Prefs.MESSAGE_SUBST_REGEX =
    /(.*)(\<ph.*?\>\s*(\<ex\>(.*?)\<\/ex\>)?\s*%1\s*\<\/ph\>)(.*)/;

/**
 * Returns a message value with the positional argument opt_subst in place if
 * it is provided or the provided example value if it is not, or the empty
 * string if the message is not found.
 *
 * @param {String} key The message to fetch
 * @param {String} opt_subst An optional string to substitute into the message
 * @return {String} The formatted string
 */
gadgets.Prefs.prototype.getMsgFormatted = function(key, opt_subst) {
  var val = this.getMsg(key);

  var result = val.match(gadgets.Prefs.MESSAGE_SUBST_REGEX);
  // Allows string that should be getMsg to also call getMsgFormatted
  if (!result || !result[0]) {
    return val;
  }
  if (typeof opt_subst === "undefined") {
    var sub = result[4] || "";
    return result[1] + sub + result[5];
  }
  return result[1] + opt_subst + result[5];
};

/**
 * Gets the current country, returned as ISO 3166-1 alpha-2 code.
 *
 * @return {String} The country for this module instance
 */
gadgets.Prefs.prototype.getCountry = function() {
  return this.data_.country;
};

/**
 * Gets the current language the gadget should use when rendering, returned as a
 * ISO 639-1 language code.
 *
 * @return {String} The language for this module instance
 */
gadgets.Prefs.prototype.getLang = function() {
  return this.data_.language;
};

/**
 * Gets the module id for the current instance.
 *
 * @return {String | Number} The module id for this module instance
 */
gadgets.Prefs.prototype.getModuleId = function() {
  return this.moduleId_;
};
<commit_msg>Minor documentation fixes in gadget.Prefs.<commit_after>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview
 *
 * Provides access to user prefs, module dimensions, and messages.
 *
 * Clients can access their preferences by constructing an instance of
 * gadgets.Prefs and passing in their module id.  Example:
 *
 *   var prefs = new gadgets.Prefs();
 *   var name = prefs.getString("name");
 *   var lang = prefs.getLang();
 *
 * Modules with type=url can also use this library to parse arguments passed
 * by URL, but this is not the common case:
 *
 *   <script src="http://apache.org/shindig/prefs.js"></script>
 *   <script>
 *   gadgets.Prefs.parseUrl();
 *   var prefs = new gadgets.Prefs();
 *   var name = prefs.getString("name");
 *   </script≶
 */

var gadgets = gadgets || {};

/**
 * Stores preferences for the default shindig implementation.
 * @private
 */
gadgets.prefs_ = function() {
  var modules = {};

  /**
   * Returns the module named by moduleId
   * @param {Number | String} moduleId The module id to fetch
   * @return {Object} An object containing module data
   */
  function getModuleData(moduleId) {
    if (!modules[moduleId]) {
      modules[moduleId] = {
        prefs:{},
        msgs:{},
        language:"all",
        country:"all"
      };
    }
    return modules[moduleId];
  }

  /**
   * Adds a new user preference to the stored set for the given module id.
   *
   * @param {String | Number} moduleId The module id to add the pref for
   * @param {String} key The key to add; may be an object where keys = key and
   *     values = value
   * @param {String} opt_value An optional value used to set the value of the
   *     key.
   */
  function setPref(moduleId, key, opt_value) {
    var module = getModuleData(moduleId);
    if (typeof key !== "string") {
      for (var i in key) {
        module.prefs[i] = key[i];
      }
    } else {
      module.prefs[key] = opt_value;
    }
  }

  /**
   * Adds a new message to the stored set for the given module id.
   *
   * @param {String | Number} moduleId The module id to add the pref for
   * @param {String | Object} key The key to add; may be an object where keys =
   *     key and values = value
   * @param {String} opt_value An optional value used to set the value of the
   *     key.
   */
  function setMsg(moduleId, key, opt_value) {
    var module = getModuleData(moduleId);
    if (typeof key !== "string") {
      for (var i in key) {
        module.msgs[i] = key[i];
      }
    } else {
      module.msgs[key] = opt_value;
    }
  }

  var defaultModuleId = 0;

  /**
   * Sets the default module id.
   *
   * @param {String | Number} moduleId The module id to set as default
   */
  function setDefaultModuleId(moduleId) {
    defaultModuleId = moduleId;
  }

  /**
   * Gets the default module id.
   *
   * @return {String | Number} The default module id
   */
  function getDefaultModuleId() {
    return defaultModuleId;
  }

  /**
   * Sets the default language for this module id.
   *
   * @param {String | Number} moduleId The module id to set the language for
   * @param {String} language The language code as an ISO 639-1 code
   */
  function setLanguage(moduleId, language) {
    getModuleData(moduleId).language = language;
  }

  /**
   * Sets the default country for this module id.
   *
   * @param {String | Number} moduleId The id of the gagdet instance
   * @param {String} country The country code as an ISO 3166-1 alpha-2 code
   */
  function setCountry(moduleId, country) {
    getModuleData(moduleId).country = country;
  }

  // Export public API for the gadget container code. Gadget authors should
  // not use this class.
  return {
    setPref: setPref,
    setMsg: setMsg,
    setCountry: setCountry,
    setLanguage: setLanguage,
    getModuleData: getModuleData,
    setDefaultModuleId: setDefaultModuleId,
    getDefaultModuleId: getDefaultModuleId
  };
}();

/**
 * @class
 * Provides access to user preferences, module dimensions, and messages.
 *
 * Clients can access their preferences by constructing an instance of
 * gadgets.Prefs and passing in their module id.  Example:
 *

var prefs = new gadgets.Prefs();
var name = prefs.getString("name");
var lang = prefs.getLang();

 *
 * @description Creates a new Prefs object.
 * @param {String | Number} opt_moduleId An optional parameter specifying the
 *     module id to create prefs for; if not provided, the default module id
 *     is used
 */
gadgets.Prefs = function(opt_moduleId) {
  if (typeof opt_moduleId === "undefined") {
    this.moduleId_ = gadgets.prefs_.getDefaultModuleId();
  } else {
    this.moduleId_ = opt_moduleId;
  }
  this.data_ = gadgets.prefs_.getModuleData(this.moduleId_);
  // This is used to eliminate one hash table lookup per value fetched.
  this.prefs_ = this.data_.prefs;
  this.msgs_ = this.data_.msgs;
};

/**
 * @static
 * @method
 * @scope gadgets.Prefs
 *
 * Static pref parser. Parses all parameters from the url and stores them
 * for later use when creating a new gadgets.Prefs object.
 * You should only ever call this if you are a type=url gadget.
 *
 * @param {String | Number} moduleId The id of the gadget instance
 * @private
 */
gadgets.Prefs.parseUrl = function(moduleId) {
  var prefs = {};
  var msgs = {};
  var country = "all";
  var language = "all";
  if (gadgets.util) {
    var params = gadgets.util.getUrlParameters();
    for (var i in params) {
      if (i.indexOf("up_") === 0 && i.length > 3) {
        prefs[i.substr(3)] = String(params[i]);
      } else if (i.indexOf("msg_") === 0 && i.length > 4) {
        msgs[i.substr(4)] = String(params[i]);
      } else if (i === "country") {
        country = params[i];
      } else if (i === "lang") {
        language = params[i];
      } else if (i === "mid") {
        moduleId = params[i];
      }
    }
  }
  gadgets.prefs_.setDefaultModuleId(moduleId);
  gadgets.prefs_.setPref(moduleId, prefs);
  gadgets.prefs_.setMsg(moduleId, msgs);
  gadgets.prefs_.setLanguage(moduleId, language);
  gadgets.prefs_.setCountry(moduleId, country);
};

/**
 * Internal helper for pref fetching.
 * @param {String} key The key to fetch
 * @return {Object} The preference
 * @private
 */
gadgets.Prefs.prototype.getPref_ = function(key) {
  var val = this.prefs_[key];
  return typeof val === "undefined" ? null : val;
}

/**
 * Retrieves a preference as a string.
 * @param {String} key The preference to fetch
 * @return {String} The preference; if not set, an empty string
 */
gadgets.Prefs.prototype.getString = function(key) {
  var val = this.getPref_(key);
  return val === null ? "" : val;
};

/**
 * Retrieves a preference as an integer.
 * @param {String} key The preference to fetch
 * @return {Number} The preference; if not set, 0
 */
gadgets.Prefs.prototype.getInt = function(key) {
  var val = parseInt(this.getPref_(key), 10);
  return isNaN(val) ? 0 : val;
};

/**
 * Retrieves a preference as a floating-point value.
 * @param {String} key The preference to fetch
 * @return {Number} The preference; if not set, 0
 */
gadgets.Prefs.prototype.getFloat = function(key) {
  var val = parseFloat(this.getPref_(key));
  return isNaN(val) ? 0 : val;
};

/**
 * Retrieves a preference as a boolean.
 * @param {String} key The preference to fetch
 * @return {Boolean} The preference; if not set, false
 */
gadgets.Prefs.prototype.getBool = function(key) {
  var val = this.getPref_(key);
  if (val !== null) {
    return val === "true" || val === true || !!parseInt(val, 10);
  }
  return false;
};

/**
 * Stores a preference.
 * To use this call,
 * the gadget must require the feature setprefs.
 *
 *

 * Note:
 * If the gadget needs to store an Array it should use setArray instead of
 * this call.
 *

 *
 * @param {String} key The pref to store
 * @param {Object} val The values to store
 */
gadgets.Prefs.prototype.set = function(key, value) {
  throw new Error("setprefs feature required to make this call.");
};

/**
 * Retrieves a preference as an array.
 * UserPref values that were not declared as lists are treated as
 * one-element arrays.
 *
 * @param {String} key The preference to fetch
 * @return {Array.<String>} The preference; if not set, an empty array
 */
gadgets.Prefs.prototype.getArray = function(key) {
  var val = this.getPref_(key);
  if (val !== null) {
    var arr = val.split("|");
    // Decode pipe characters.
    for (var i = 0, j = arr.length; i < j; ++i) {
      arr[i] = arr[i].replace(/%7C/g, "|");
    }
    return arr;
  }
  return [];
};

/**
 * Stores an array preference.
 * To use this call,
 * the gadget must require the feature setprefs.
 *
 * @param {String} key The pref to store
 * @param {Array} val The values to store
 */
gadgets.Prefs.prototype.setArray = function(key, val) {
  throw new Error("setprefs feature required to make this call.");
};

/**
 * Fetches an unformatted message.
 * @param {String} key The message to fetch
 * @return {String} The message
 */
gadgets.Prefs.prototype.getMsg = function(key) {
  var val = this.msgs_[key];
  return typeof val === "undefined" ? "" : val;
};

/**
 *

 * The regex pulls out the text before and after the positional argument
 * and digs down for a possible example value in case no actual value
 * was provided.  It is used by the function getMsgFormatted.
 *

 *
 *

 * Example: "Foo <ph name="number"><ex>bar</ex>%1</ph> baz."
 * 0 = "Foo <ph name="number"><ex>bar</ex>%1</ph> baz." : match for the
 *     whole regex.
 *
 * 1 = "Foo " : matches first (.*) in regex
 *
 * 2 = "<ph name="number"><ex>bar</ex>%1</ph>" : matches
 *     (\<ph.*?\>\s*(\<ex\>(.*?)\<\/ex\>)?\s*%1\s*\<\/ph\>) in regex
 * 3 = "<ex>bar</ex>" : matches (\<ex\>(.*?)\<\/ex\>)? in regex, since it
 *     is an optional param it may have the value "undefined"
 * 4 = "bar" : matches (.*?) in regex (it is a non-greedy regex)
 *     if 3=undefined then 4 = "undefined".
 *
 * 5 = " baz." : matches final (.*) in regex
 *
 *

 * @private
 */
 // TODO: this may need to be a single line even though it's > 80 characters
 // because some browsers may not properly interepret the line continuation.
gadgets.Prefs.MESSAGE_SUBST_REGEX =
    /(.*)(\<ph.*?\>\s*(\<ex\>(.*?)\<\/ex\>)?\s*%1\s*\<\/ph\>)(.*)/;

/**
 * Returns a message value with the positional argument opt_subst in place if
 * it is provided or the provided example value if it is not, or the empty
 * string if the message is not found.
 *
 * @param {String} key The message to fetch
 * @param {String} opt_subst An optional string to substitute into the message
 * @return {String} The formatted string
 */
gadgets.Prefs.prototype.getMsgFormatted = function(key, opt_subst) {
  var val = this.getMsg(key);

  var result = val.match(gadgets.Prefs.MESSAGE_SUBST_REGEX);
  // Allows string that should be getMsg to also call getMsgFormatted
  if (!result || !result[0]) {
    return val;
  }
  if (typeof opt_subst === "undefined") {
    var sub = result[4] || "";
    return result[1] + sub + result[5];
  }
  return result[1] + opt_subst + result[5];
};

/**
 * Gets the current country, returned as ISO 3166-1 alpha-2 code.
 *
 * @return {String} The country for this module instance
 */
gadgets.Prefs.prototype.getCountry = function() {
  return this.data_.country;
};

/**
 * Gets the current language the gadget should use when rendering, returned as a
 * ISO 639-1 language code.
 *
 * @return {String} The language for this module instance
 */
gadgets.Prefs.prototype.getLang = function() {
  return this.data_.language;
};

/**
 * Gets the module id for the current instance.
 *
 * @return {String | Number} The module id for this module instance
 */
gadgets.Prefs.prototype.getModuleId = function() {
  return this.moduleId_;
};
<|endoftext|>


<commit_before>// @flow
import React, {
  Component,
  type Element as ReactElement,
  type ElementRef,
  type Node,
} from 'react';
import { css } from 'emotion';
import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';

import {
  animatedScrollTo,
  getBoundingClientObj,
  type RectType,
  getScrollParent,
  getScrollTop,
  scrollTo,
} from '../utils';
import type {
  InnerRef,
  MenuPlacement,
  MenuPosition,
  CommonProps,
} from '../types';
import type { Theme } from '../types';

// ==============================
// Menu
// ==============================

// Get Menu Placement
// ------------------------------

type MenuState = {
  placement: 'bottom' | 'top' | null,
  maxHeight: number,
};
type PlacementArgs = {
  maxHeight: number,
  menuEl: ElementRef<*>,
  minHeight: number,
  placement: 'bottom' | 'top' | 'auto',
  shouldScroll: boolean,
  isFixedPosition: boolean,
  theme: Theme,
};

export function getMenuPlacement({
  maxHeight,
  menuEl,
  minHeight,
  placement,
  shouldScroll,
  isFixedPosition,
  theme,
}: PlacementArgs): MenuState {
  const { spacing } = theme;
  const scrollParent = getScrollParent(menuEl);
  const defaultState = { placement: 'bottom', maxHeight };

  // something went wrong, return default state
  if (!menuEl || !menuEl.offsetParent) return defaultState;

  // we can't trust `scrollParent.scrollHeight` --> it may increase when
  // the menu is rendered
  const { height: scrollHeight } = scrollParent.getBoundingClientRect();
  const {
    bottom: menuBottom,
    height: menuHeight,
    top: menuTop,
  } = menuEl.getBoundingClientRect();

  const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect();
  const viewHeight = window.innerHeight;
  const scrollTop = getScrollTop(scrollParent);

  const marginBottom = parseInt(getComputedStyle(menuEl).marginBottom, 10);
  const marginTop = parseInt(getComputedStyle(menuEl).marginTop, 10);
  const viewSpaceAbove = containerTop - marginTop;
  const viewSpaceBelow = viewHeight - menuTop;
  const scrollSpaceAbove = viewSpaceAbove + scrollTop;
  const scrollSpaceBelow = scrollHeight - scrollTop - menuTop;

  const scrollDown = menuBottom - viewHeight + scrollTop + marginBottom;
  const scrollUp = scrollTop + menuTop - marginTop;
  const scrollDuration = 160;

  switch (placement) {
    case 'auto':
    case 'bottom':
      // 1: the menu will fit, do nothing
      if (viewSpaceBelow >= menuHeight) {
        return { placement: 'bottom', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceBelow >= menuHeight && !isFixedPosition) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        return { placement: 'bottom', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (
        (!isFixedPosition && scrollSpaceBelow >= minHeight) ||
        (isFixedPosition && viewSpaceBelow >= minHeight)
      ) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        const constrainedHeight = isFixedPosition
          ? viewSpaceBelow - marginBottom
          : scrollSpaceBelow - marginBottom;

        return {
          placement: 'bottom',
          maxHeight: constrainedHeight,
        };
      }

      // 4. Forked beviour when there isn't enough space below

      // AUTO: flip the menu, render above
      if (placement === 'auto' || isFixedPosition) {
        // may need to be constrained after flipping
        let constrainedHeight = maxHeight;
        const spaceAbove = isFixedPosition ? viewSpaceAbove : scrollSpaceAbove;

        if (spaceAbove >= minHeight) {
          constrainedHeight = Math.min(
            spaceAbove - marginBottom - spacing.controlHeight,
            maxHeight
          );
        }

        return { placement: 'top', maxHeight: constrainedHeight };
      }

      // BOTTOM: allow browser to increase scrollable area and immediately set scroll
      if (placement === 'bottom') {
        scrollTo(scrollParent, scrollDown);
        return { placement: 'bottom', maxHeight };
      }
      break;
    case 'top':
      // 1: the menu will fit, do nothing
      if (viewSpaceAbove >= menuHeight) {
        return { placement: 'top', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceAbove >= menuHeight && !isFixedPosition) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        return { placement: 'top', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (
        (!isFixedPosition && scrollSpaceAbove >= minHeight) ||
        (isFixedPosition && viewSpaceAbove >= minHeight)
      ) {
        let constrainedHeight = maxHeight;

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        if (
          (!isFixedPosition && scrollSpaceAbove >= minHeight) ||
          (isFixedPosition && viewSpaceAbove >= minHeight)
        ) {
          constrainedHeight = isFixedPosition
            ? viewSpaceAbove - marginTop
            : scrollSpaceAbove - marginTop;
        }

        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        return {
          placement: 'top',
          maxHeight: constrainedHeight,
        };
      }

      // 4. not enough space, the browser WILL NOT increase scrollable area when
      // absolutely positioned element rendered above the viewport (only below).
      // Flip the menu, render below
      return { placement: 'bottom', maxHeight };
    default:
      throw new Error(`Invalid placement provided "${placement}".`);
  }

  // fulfil contract with flow: implicit return value of undefined
  return defaultState;
}

// Menu Component
// ------------------------------

export type MenuAndPlacerCommon = CommonProps & {
  /** Callback to update the portal after possible flip. */
  getPortalPlacement: MenuState => void,
  /** Props to be passed to the menu wrapper. */
  innerProps: {},
  /** Set the maximum height of the menu. */
  maxMenuHeight: number,
  /** Set whether the menu should be at the top, at the bottom. The auto options sets it to bottom. */
  menuPlacement: MenuPlacement,
  /* The CSS position value of the menu, when "fixed" extra layout management is required */
  menuPosition: MenuPosition,
  /** Set the minimum height of the menu. */
  minMenuHeight: number,
  /** Set whether the page should scroll to show the menu. */
  menuShouldScrollIntoView: boolean,
};
export type MenuProps = MenuAndPlacerCommon & {
  /** Reference to the internal element, consumed by the MenuPlacer component */
  innerRef: ElementRef<*>,
  /** The children to be rendered. */
  children: ReactElement<*>,
};
export type MenuPlacerProps = MenuAndPlacerCommon & {
  /** The children to be rendered. */
  children: ({}) => Node,
};

function alignToControl(placement) {
  const placementToCSSProp = { bottom: 'top', top: 'bottom' };
  return placement ? placementToCSSProp[placement] : 'bottom';
}
const coercePlacement = p => (p === 'auto' ? 'bottom' : p);

type MenuStateWithProps = MenuState & MenuProps;

export const menuCSS = ({
  placement,
  theme: { borderRadius, spacing, colors },
}: MenuStateWithProps) => ({
  [alignToControl(placement)]: '100%',
  backgroundColor: colors.neutral0,
  borderRadius: borderRadius,
  boxShadow: '0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)',
  marginBottom: spacing.menuGutter,
  marginTop: spacing.menuGutter,
  position: 'absolute',
  width: '100%',
  zIndex: 1,
});

// NOTE: internal only
export class MenuPlacer extends Component<MenuPlacerProps, MenuState> {
  state = {
    maxHeight: this.props.maxMenuHeight,
    placement: null,
  };
  static contextTypes = {
    getPortalPlacement: PropTypes.func,
  };
  getPlacement = (ref: ElementRef<*>) => {
    const {
      minMenuHeight,
      maxMenuHeight,
      menuPlacement,
      menuPosition,
      menuShouldScrollIntoView,
      theme,
    } = this.props;
    const { getPortalPlacement } = this.context;

    if (!ref) return;

    // DO NOT scroll if position is fixed
    const isFixedPosition = menuPosition === 'fixed';
    const shouldScroll = menuShouldScrollIntoView && !isFixedPosition;

    const state = getMenuPlacement({
      maxHeight: maxMenuHeight,
      menuEl: ref,
      minHeight: minMenuHeight,
      placement: menuPlacement,
      shouldScroll,
      isFixedPosition,
      theme,
    });

    if (getPortalPlacement) getPortalPlacement(state);

    this.setState(state);
  };
  getUpdatedProps = () => {
    const { menuPlacement } = this.props;
    const placement = this.state.placement || coercePlacement(menuPlacement);

    return { ...this.props, placement, maxHeight: this.state.maxHeight };
  };
  render() {
    const { children } = this.props;

    return children({
      ref: this.getPlacement,
      placerProps: this.getUpdatedProps(),
    });
  }
}

const Menu = (props: MenuProps) => {
  const { children, className, cx, getStyles, innerRef, innerProps } = props;
  const cn = cx(css(getStyles('menu', props)), { menu: true }, className);

  return (

      {children}

  );
};

export default Menu;

// ==============================
// Menu List
// ==============================

type MenuListState = {
  /** Set classname for isMulti */
  isMulti: boolean,
  /* Set the max height of the Menu component  */
  maxHeight: number,
};

export type MenuListProps = {
  /** The children to be rendered. */
  children: Node,
  /** Inner ref to DOM Node */
  innerRef: InnerRef,
};
export type MenuListComponentProps = CommonProps &
  MenuListProps &
  MenuListState;
export const menuListCSS = ({
  maxHeight,
  theme: {
    spacing: { baseUnit },
  },
}: MenuListComponentProps) => ({
  maxHeight,
  overflowY: 'auto',
  paddingBottom: baseUnit,
  paddingTop: baseUnit,
  position: 'relative', // required for offset[Height, Top] > keyboard scroll
  WebkitOverflowScrolling: 'touch',
});
export const MenuList = (props: MenuListComponentProps) => {
  const { children, className, cx, getStyles, isMulti, innerRef } = props;
  return (

      {children}

  );
};

// ==============================
// Menu Notices
// ==============================

const noticeCSS = ({
  theme: {
    spacing: { baseUnit },
    colors,
  },
}: NoticeProps) => ({
  color: colors.neutral40,
  padding: `${baseUnit * 2}px ${baseUnit * 3}px`,
  textAlign: 'center',
});
export const noOptionsMessageCSS = noticeCSS;
export const loadingMessageCSS = noticeCSS;

export type NoticeProps = CommonProps & {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed on to the wrapper. */
  innerProps: {},
};

export const NoOptionsMessage = (props: NoticeProps) => {
  const { children, className, cx, getStyles, innerProps } = props;
  return (

      {children}

  );
};
NoOptionsMessage.defaultProps = {
  children: 'No options',
};

export const LoadingMessage = (props: NoticeProps) => {
  const { children, className, cx, getStyles, innerProps } = props;
  return (

      {children}

  );
};
LoadingMessage.defaultProps = {
  children: 'Loading...',
};

// ==============================
// Menu Portal
// ==============================

export type MenuPortalProps = CommonProps & {
  appendTo: HTMLElement,
  children: Node, // ideally Menu<MenuProps>
  controlElement: HTMLElement,
  menuPlacement: MenuPlacement,
  menuPosition: MenuPosition,
};
type MenuPortalState = {
  placement: 'bottom' | 'top' | null,
};
type PortalStyleArgs = {
  offset: number,
  position: MenuPosition,
  rect: RectType,
};

export const menuPortalCSS = ({ rect, offset, position }: PortalStyleArgs) => ({
  left: rect.left,
  position: position,
  top: offset,
  width: rect.width,
  zIndex: 1,
});

export class MenuPortal extends Component<MenuPortalProps, MenuPortalState> {
  state = { placement: null };
  static childContextTypes = {
    getPortalPlacement: PropTypes.func,
  };
  getChildContext() {
    return {
      getPortalPlacement: this.getPortalPlacement,
    };
  }

  // callback for occassions where the menu must "flip"
  getPortalPlacement = ({ placement }: MenuState) => {
    const initialPlacement = coercePlacement(this.props.menuPlacement);

    // avoid re-renders if the placement has not changed
    if (placement !== initialPlacement) {
      this.setState({ placement });
    }
  };
  render() {
    const {
      appendTo,
      children,
      controlElement,
      menuPlacement,
      menuPosition: position,
      getStyles,
    } = this.props;
    const isFixed = position === 'fixed';

    // bail early if required elements aren't present
    if ((!appendTo && !isFixed) || !controlElement) {
      return null;
    }

    const placement = this.state.placement || coercePlacement(menuPlacement);
    const rect = getBoundingClientObj(controlElement);
    const scrollDistance = isFixed ? 0 : window.pageYOffset;
    const offset = rect[placement] + scrollDistance;
    const state = { offset, position, rect };

    // same wrapper element whether fixed or portalled
    const menuWrapper = (

{children}

    );

    return appendTo ? createPortal(menuWrapper, appendTo) : menuWrapper;
  }
}
<commit_msg>added aria-role to menu<commit_after>// @flow
import React, {
  Component,
  type Element as ReactElement,
  type ElementRef,
  type Node,
} from 'react';
import { css } from 'emotion';
import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';

import {
  animatedScrollTo,
  getBoundingClientObj,
  type RectType,
  getScrollParent,
  getScrollTop,
  scrollTo,
} from '../utils';
import type {
  InnerRef,
    MenuPlacement,
    MenuPosition,
    CommonProps,
} from '../types';
import type { Theme } from '../types';

// ==============================
// Menu
// ==============================

// Get Menu Placement
// ------------------------------

type MenuState = {
  placement: 'bottom' | 'top' | null,
  maxHeight: number,
};
type PlacementArgs = {
  maxHeight: number,
  menuEl: ElementRef<*>,
  minHeight: number,
  placement: 'bottom' | 'top' | 'auto',
  shouldScroll: boolean,
  isFixedPosition: boolean,
  theme: Theme,
};

export function getMenuPlacement({
  maxHeight,
  menuEl,
  minHeight,
  placement,
  shouldScroll,
  isFixedPosition,
  theme,
}: PlacementArgs): MenuState {
  const { spacing } = theme;
  const scrollParent = getScrollParent(menuEl);
  const defaultState = { placement: 'bottom', maxHeight };

  // something went wrong, return default state
  if (!menuEl || !menuEl.offsetParent) return defaultState;

  // we can't trust `scrollParent.scrollHeight` --> it may increase when
  // the menu is rendered
  const { height: scrollHeight } = scrollParent.getBoundingClientRect();
  const {
    bottom: menuBottom,
    height: menuHeight,
    top: menuTop,
  } = menuEl.getBoundingClientRect();

  const { top: containerTop } = menuEl.offsetParent.getBoundingClientRect();
  const viewHeight = window.innerHeight;
  const scrollTop = getScrollTop(scrollParent);

  const marginBottom = parseInt(getComputedStyle(menuEl).marginBottom, 10);
  const marginTop = parseInt(getComputedStyle(menuEl).marginTop, 10);
  const viewSpaceAbove = containerTop - marginTop;
  const viewSpaceBelow = viewHeight - menuTop;
  const scrollSpaceAbove = viewSpaceAbove + scrollTop;
  const scrollSpaceBelow = scrollHeight - scrollTop - menuTop;

  const scrollDown = menuBottom - viewHeight + scrollTop + marginBottom;
  const scrollUp = scrollTop + menuTop - marginTop;
  const scrollDuration = 160;

  switch (placement) {
    case 'auto':
    case 'bottom':
      // 1: the menu will fit, do nothing
      if (viewSpaceBelow >= menuHeight) {
        return { placement: 'bottom', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceBelow >= menuHeight && !isFixedPosition) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        return { placement: 'bottom', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (
        (!isFixedPosition && scrollSpaceBelow >= minHeight) ||
        (isFixedPosition && viewSpaceBelow >= minHeight)
      ) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollDown, scrollDuration);
        }

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        const constrainedHeight = isFixedPosition
          ? viewSpaceBelow - marginBottom
          : scrollSpaceBelow - marginBottom;

        return {
          placement: 'bottom',
          maxHeight: constrainedHeight,
        };
      }

      // 4. Forked beviour when there isn't enough space below

      // AUTO: flip the menu, render above
      if (placement === 'auto' || isFixedPosition) {
        // may need to be constrained after flipping
        let constrainedHeight = maxHeight;
        const spaceAbove = isFixedPosition ? viewSpaceAbove : scrollSpaceAbove;

        if (spaceAbove >= minHeight) {
          constrainedHeight = Math.min(
            spaceAbove - marginBottom - spacing.controlHeight,
            maxHeight
          );
        }

        return { placement: 'top', maxHeight: constrainedHeight };
      }

      // BOTTOM: allow browser to increase scrollable area and immediately set scroll
      if (placement === 'bottom') {
        scrollTo(scrollParent, scrollDown);
        return { placement: 'bottom', maxHeight };
      }
      break;
    case 'top':
      // 1: the menu will fit, do nothing
      if (viewSpaceAbove >= menuHeight) {
        return { placement: 'top', maxHeight };
      }

      // 2: the menu will fit, if scrolled
      if (scrollSpaceAbove >= menuHeight && !isFixedPosition) {
        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        return { placement: 'top', maxHeight };
      }

      // 3: the menu will fit, if constrained
      if (
        (!isFixedPosition && scrollSpaceAbove >= minHeight) ||
        (isFixedPosition && viewSpaceAbove >= minHeight)
      ) {
        let constrainedHeight = maxHeight;

        // we want to provide as much of the menu as possible to the user,
        // so give them whatever is available below rather than the minHeight.
        if (
          (!isFixedPosition && scrollSpaceAbove >= minHeight) ||
          (isFixedPosition && viewSpaceAbove >= minHeight)
        ) {
          constrainedHeight = isFixedPosition
            ? viewSpaceAbove - marginTop
            : scrollSpaceAbove - marginTop;
        }

        if (shouldScroll) {
          animatedScrollTo(scrollParent, scrollUp, scrollDuration);
        }

        return {
          placement: 'top',
          maxHeight: constrainedHeight,
        };
      }

      // 4. not enough space, the browser WILL NOT increase scrollable area when
      // absolutely positioned element rendered above the viewport (only below).
      // Flip the menu, render below
      return { placement: 'bottom', maxHeight };
    default:
      throw new Error(`Invalid placement provided "${placement}".`);
  }

  // fulfil contract with flow: implicit return value of undefined
  return defaultState;
}

// Menu Component
// ------------------------------

export type MenuAndPlacerCommon = CommonProps & {
  /** Callback to update the portal after possible flip. */
  getPortalPlacement: MenuState => void,
  /** Props to be passed to the menu wrapper. */
  innerProps: {},
  /** Set the maximum height of the menu. */
  maxMenuHeight: number,
  /** Set whether the menu should be at the top, at the bottom. The auto options sets it to bottom. */
  menuPlacement: MenuPlacement,
  /* The CSS position value of the menu, when "fixed" extra layout management is required */
  menuPosition: MenuPosition,
  /** Set the minimum height of the menu. */
  minMenuHeight: number,
  /** Set whether the page should scroll to show the menu. */
  menuShouldScrollIntoView: boolean,
};
export type MenuProps = MenuAndPlacerCommon & {
  /** Reference to the internal element, consumed by the MenuPlacer component */
  innerRef: ElementRef<*>,
  /** The children to be rendered. */
  children: ReactElement<*>,
};
export type MenuPlacerProps = MenuAndPlacerCommon & {
  /** The children to be rendered. */
  children: ({ }) => Node,
};

function alignToControl(placement) {
  const placementToCSSProp = { bottom: 'top', top: 'bottom' };
  return placement ? placementToCSSProp[placement] : 'bottom';
}
const coercePlacement = p => (p === 'auto' ? 'bottom' : p);

type MenuStateWithProps = MenuState & MenuProps;

export const menuCSS = ({
  placement,
  theme: { borderRadius, spacing, colors },
}: MenuStateWithProps) => ({
  [alignToControl(placement)]: '100%',
  backgroundColor: colors.neutral0,
  borderRadius: borderRadius,
  boxShadow: '0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1)',
  marginBottom: spacing.menuGutter,
  marginTop: spacing.menuGutter,
  position: 'absolute',
  width: '100%',
  zIndex: 1,
});

// NOTE: internal only
export class MenuPlacer extends Component<MenuPlacerProps, MenuState> {
  state = {
    maxHeight: this.props.maxMenuHeight,
    placement: null,
  };
  static contextTypes = {
    getPortalPlacement: PropTypes.func,
  };
  getPlacement = (ref: ElementRef<*>) => {
    const {
      minMenuHeight,
      maxMenuHeight,
      menuPlacement,
      menuPosition,
      menuShouldScrollIntoView,
      theme,
    } = this.props;
    const { getPortalPlacement } = this.context;

    if (!ref) return;

    // DO NOT scroll if position is fixed
    const isFixedPosition = menuPosition === 'fixed';
    const shouldScroll = menuShouldScrollIntoView && !isFixedPosition;

    const state = getMenuPlacement({
      maxHeight: maxMenuHeight,
      menuEl: ref,
      minHeight: minMenuHeight,
      placement: menuPlacement,
      shouldScroll,
      isFixedPosition,
      theme,
    });

    if (getPortalPlacement) getPortalPlacement(state);

    this.setState(state);
  };
  getUpdatedProps = () => {
    const { menuPlacement } = this.props;
    const placement = this.state.placement || coercePlacement(menuPlacement);

    return { ...this.props, placement, maxHeight: this.state.maxHeight };
  };
  render() {
    const { children } = this.props;

    return children({
      ref: this.getPlacement,
      placerProps: this.getUpdatedProps(),
    });
  }
}

const Menu = (props: MenuProps) => {
  const { children, className, cx, getStyles, innerRef, innerProps } = props;
  const cn = cx(css(getStyles('menu', props)), { menu: true }, className);

  return (

      {children}

  );
};

export default Menu;

// ==============================
// Menu List
// ==============================

type MenuListState = {
  /** Set classname for isMulti */
  isMulti: boolean,
  /* Set the max height of the Menu component  */
  maxHeight: number,
};

export type MenuListProps = {
  /** The children to be rendered. */
  children: Node,
  /** Inner ref to DOM Node */
  innerRef: InnerRef,
};
export type MenuListComponentProps = CommonProps &
  MenuListProps &
  MenuListState;
export const menuListCSS = ({
  maxHeight,
  theme: {
    spacing: { baseUnit },
  },
}: MenuListComponentProps) => ({
  maxHeight,
  overflowY: 'auto',
  paddingBottom: baseUnit,
  paddingTop: baseUnit,
  position: 'relative', // required for offset[Height, Top] > keyboard scroll
  WebkitOverflowScrolling: 'touch',
});
export const MenuList = (props: MenuListComponentProps) => {
  const { children, className, cx, getStyles, isMulti, innerRef } = props;
  return (

      {children}

  );
};

// ==============================
// Menu Notices
// ==============================

const noticeCSS = ({
  theme: {
    spacing: { baseUnit },
    colors,
  },
}: NoticeProps) => ({
  color: colors.neutral40,
  padding: `${baseUnit * 2}px ${baseUnit * 3}px`,
  textAlign: 'center',
});
export const noOptionsMessageCSS = noticeCSS;
export const loadingMessageCSS = noticeCSS;

export type NoticeProps = CommonProps & {
  /** The children to be rendered. */
  children: Node,
  /** Props to be passed on to the wrapper. */
  innerProps: {},
};

export const NoOptionsMessage = (props: NoticeProps) => {
  const { children, className, cx, getStyles, innerProps } = props;
  return (

      {children}

  );
};
NoOptionsMessage.defaultProps = {
  children: 'No options',
};

export const LoadingMessage = (props: NoticeProps) => {
  const { children, className, cx, getStyles, innerProps } = props;
  return (

      {children}

  );
};
LoadingMessage.defaultProps = {
  children: 'Loading...',
};

// ==============================
// Menu Portal
// ==============================

export type MenuPortalProps = CommonProps & {
  appendTo: HTMLElement,
  children: Node, // ideally Menu<MenuProps>
  controlElement: HTMLElement,
  menuPlacement: MenuPlacement,
  menuPosition: MenuPosition,
};
type MenuPortalState = {
  placement: 'bottom' | 'top' | null,
};
type PortalStyleArgs = {
  offset: number,
  position: MenuPosition,
  rect: RectType,
};

export const menuPortalCSS = ({ rect, offset, position }: PortalStyleArgs) => ({
  left: rect.left,
  position: position,
  top: offset,
  width: rect.width,
  zIndex: 1,
});

export class MenuPortal extends Component<MenuPortalProps, MenuPortalState> {
  state = { placement: null };
  static childContextTypes = {
    getPortalPlacement: PropTypes.func,
  };
  getChildContext() {
    return {
      getPortalPlacement: this.getPortalPlacement,
    };
  }

  // callback for occassions where the menu must "flip"
  getPortalPlacement = ({ placement }: MenuState) => {
    const initialPlacement = coercePlacement(this.props.menuPlacement);

    // avoid re-renders if the placement has not changed
    if (placement !== initialPlacement) {
      this.setState({ placement });
    }
  };
  render() {
    const {
      appendTo,
      children,
      controlElement,
      menuPlacement,
      menuPosition: position,
      getStyles,
    } = this.props;
    const isFixed = position === 'fixed';

    // bail early if required elements aren't present
    if ((!appendTo && !isFixed) || !controlElement) {
      return null;
    }

    const placement = this.state.placement || coercePlacement(menuPlacement);
    const rect = getBoundingClientObj(controlElement);
    const scrollDistance = isFixed ? 0 : window.pageYOffset;
    const offset = rect[placement] + scrollDistance;
    const state = { offset, position, rect };

    // same wrapper element whether fixed or portalled
    const menuWrapper = (

{children}

    );

    return appendTo ? createPortal(menuWrapper, appendTo) : menuWrapper;
  }
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { View, StyleSheet } from 'react-native';
import appStyle from 'DailyScrum/src/appStyle';

const styles = StyleSheet.create({
  page: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'flex-start',
  },
});

const Page = props => (
  <View
    style={[styles.page, {
      paddingTop: props.noNavBar ? 0 : appStyle.grid.x2,
      paddingHorizontal: props.noMargin ? 0 : appStyle.grid.x3,
      backgroundColor: props.backgroundColor || appStyle.colors.primary,
    }]}
  >
    {props.children}
  </View>
);

Page.propTypes = {
  children: PropTypes.node,
  noMargin: PropTypes.bool,
  noNavBar: PropTypes.bool,
  backgroundColor: PropTypes.string,
};

Page.defaultProps = {
  noMargin: false,
  noNavBar: false,
  backgroundColor: appStyle.colors.primary,
};

export default Page;
<commit_msg>Fix typo<commit_after>import React, { PropTypes } from 'react';
import { View, StyleSheet } from 'react-native';
import appStyle from 'DailyScrum/src/appStyle';

const styles = StyleSheet.create({
  page: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'flex-start',
  },
});

const Page = props => (
  <View
    style={[styles.page, {
      paddingTop: props.noNavBar ? 0 : appStyle.grid.x2,
      paddingHorizontal: props.noMargin ? 0 : appStyle.grid.x3,
      backgroundColor: props.backgroundColor || appStyle.colors.background,
    }]}
  >
    {props.children}
  </View>
);

Page.propTypes = {
  children: PropTypes.node,
  noMargin: PropTypes.bool,
  noNavBar: PropTypes.bool,
  backgroundColor: PropTypes.string,
};

Page.defaultProps = {
  noMargin: false,
  noNavBar: false,
  backgroundColor: appStyle.colors.primary,
};

export default Page;
<|endoftext|>