<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from 'material-ui/styles';
import classNames from 'classnames';
import IconButton from 'material-ui/IconButton';
import Menu, { MenuItem } from 'material-ui/Menu';
import Typography from 'material-ui/Typography';
import ExpandMoreIcon from 'material-ui-icons/ExpandMore';
import GridRow from '../../common/grid/gridRow';
import { sessionChange } from '../../../reducers/session';

const styleSheet = theme => ({
  expand: {
    transform: 'rotate(0deg)',
    transition: theme.transitions.create('transform', {
      duration: theme.transitions.duration.shortest,
    }),
  },
  expandOpen: {
    transform: 'rotate(180deg)',
  },
  icon: {
    color: theme.palette.secondary[500],
  },
});

class PartnerSwitch extends Component {
  constructor() {
    super();
    this.state = {
      anchorEl: null,
      open: false,
    };
    this.handleClick = this.handleClick.bind(this);
    this.handleRequestClose = this.handleRequestClose.bind(this);
  }

  handleClick(event) {
    this.setState({ open: true, anchorEl: event.currentTarget });
  }

  handleRequestClose(partner) {
    if (partner.id) {
      this.props.saveNewCurrentPartner({
        partnerId: partner.id,
        partnerCountry: partner.country_code,
        partnerName: partner.legal_name,
      });
    }
    this.setState({ open: false });
  }

  renderMenuItems(partners, countries) {
    return partners.map(partner => (
      <MenuItem
        key={partner.id}
        onClick={() => this.handleRequestClose(partner)}
        selected={partner.id === this.props.partnerId}
      >
        <Typography type="body2">
          {`${partner.legal_name}, ${countries[partner.country_code]} `}
        </Typography>
      </MenuItem>
    ));
  }

  render() {
    const { classes, partners, name, partnerCountry, countries } = this.props;
    return (

        <GridRow align="center">
          <Typography type="body2">
            {name}
          </Typography>
          <IconButton
            className={classNames(classes.icon, classes.expand, {
              [classes.expandOpen]: this.state.open,
            })}
            onClick={this.handleClick}
            aria-haspopup="true"
            aria-label="switch-partner"
          >
            <ExpandMoreIcon />
          </IconButton>
        </GridRow>
        <Typography type="body2">
          {`${name}, ${partnerCountry} `}
        </Typography>
        <Menu
          id="switch-partner"
          anchorEl={this.state.anchorEl}
          open={this.state.open}
          onRequestClose={this.handleRequestClose}
        >
          {this.renderMenuItems(partners, countries)}
        </Menu>

    );
  }
}

PartnerSwitch.propTypes = {
  classes: PropTypes.object.isRequired,
  partners: PropTypes.array,
  partnerCountry: PropTypes.string,
  countries: PropTypes.object,
  name: PropTypes.string,
  saveNewCurrentPartner: PropTypes.func,
  partnerId: PropTypes.number,
};

const mapStateToProps = state => ({
  name: state.session.partnerName,
  partners: state.session.partners,
  partnerCountry: state.countries[state.session.partnerCountry],
  countries: state.countries,
  partnerId: state.session.partnerId,
});

const mapDispatchToProps = dispatch => ({
  saveNewCurrentPartner: session => dispatch(sessionChange(session)),
});

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(withStyles(styleSheet, { name: 'PartnerSwitch' })(PartnerSwitch));
<commit_msg>added application reloading<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withStyles } from 'material-ui/styles';
import classNames from 'classnames';
import IconButton from 'material-ui/IconButton';
import Menu, { MenuItem } from 'material-ui/Menu';
import Typography from 'material-ui/Typography';
import ExpandMoreIcon from 'material-ui-icons/ExpandMore';
import { browserHistory as history, withRouter } from 'react-router';
import GridRow from '../../common/grid/gridRow';
import { sessionChange } from '../../../reducers/session';

const styleSheet = theme => ({
  expand: {
    transform: 'rotate(0deg)',
    transition: theme.transitions.create('transform', {
      duration: theme.transitions.duration.shortest,
    }),
  },
  expandOpen: {
    transform: 'rotate(180deg)',
  },
  icon: {
    color: theme.palette.secondary[500],
  },
});

class PartnerSwitch extends Component {
  constructor() {
    super();
    this.state = {
      anchorEl: null,
      open: false,
    };
    this.handleClick = this.handleClick.bind(this);
    this.handleRequestClose = this.handleRequestClose.bind(this);
  }

  handleClick(event) {
    this.setState({ open: true, anchorEl: event.currentTarget });
  }

  handleRequestClose(partner) {
    if (partner.id) {
      this.props.saveNewCurrentPartner({
        partnerId: partner.id,
        partnerCountry: partner.country_code,
        partnerName: partner.legal_name,
      });
    }
    this.setState({ open: false });
    const loc = history.getCurrentLocation();
    //debugger
    history.push(history.getCurrentLocation().pathname);
  }

  renderMenuItems(partners, countries) {
    return partners.map(partner => (
      <MenuItem
        key={partner.id}
        onClick={() => this.handleRequestClose(partner)}
        selected={partner.id === this.props.partnerId}
      >
        <Typography type="body2">
          {`${partner.legal_name}, ${countries[partner.country_code]} `}
        </Typography>
      </MenuItem>
    ));
  }

  render() {
    const { classes, partners, name, partnerCountry, countries } = this.props;
    return (

        <GridRow align="center">
          <Typography type="body2">
            {name}
          </Typography>
          <IconButton
            className={classNames(classes.icon, classes.expand, {
              [classes.expandOpen]: this.state.open,
            })}
            onClick={this.handleClick}
            aria-haspopup="true"
            aria-label="switch-partner"
          >
            <ExpandMoreIcon />
          </IconButton>
        </GridRow>
        <Typography type="body2">
          {`${name}, ${partnerCountry} `}
        </Typography>
        <Menu
          id="switch-partner"
          anchorEl={this.state.anchorEl}
          open={this.state.open}
          onRequestClose={this.handleRequestClose}
        >
          {this.renderMenuItems(partners, countries)}
        </Menu>

    );
  }
}

PartnerSwitch.propTypes = {
  classes: PropTypes.object.isRequired,
  partners: PropTypes.array,
  partnerCountry: PropTypes.string,
  countries: PropTypes.object,
  name: PropTypes.string,
  saveNewCurrentPartner: PropTypes.func,
  partnerId: PropTypes.number,
};

const mapStateToProps = state => ({
  name: state.session.partnerName,
  partners: state.session.partners,
  partnerCountry: state.countries[state.session.partnerCountry],
  countries: state.countries,
  partnerId: state.session.partnerId,
});

const mapDispatchToProps = dispatch => ({
  saveNewCurrentPartner: session => dispatch(sessionChange(session)),
});

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(withStyles(styleSheet, { name: 'PartnerSwitch' })(PartnerSwitch));
<|endoftext|>


<commit_before>/* eslint-env jest */

import uuid from 'uuid/v4'

import tableNames from '../../tables'
import Charity from '../CharityModel'
// import { DatabaseOperation, setMockDBResponse } from '../../test-utils'

jest.mock('../../databaseClient')

afterEach(() => {
  jest.resetAllMocks()
})

describe('CharityModel', () => {
  it('implements the name property', () => {
    expect(Charity.name).toBeDefined()
  })

  it('implements the hashKey property', () => {
    expect(Charity.hashKey).toBeDefined()
  })

  it('implements the tableName property', () => {
    expect(Charity.tableName).toBe(tableNames['charities'])
  })

  // TODO: test permissions

  // TODO: use these in other models where `create` is useful.
  // it('auto creates an id', async () => {
  //   setMockDBResponse(DatabaseOperation.CREATE)
  //   const charity = await Charity.create({ name: 'something' })
  //   expect(charity.id).toBeDefined()
  //   expect(charity.name).toEqual('something')
  // })

  // it('creates with an existing id', async () => {
  //   const dbQueryMock = setMockDBResponse(DatabaseOperation.CREATE)
  //   const someId = uuid()
  //   const charity = await Charity.create({
  //     id: someId,
  //     name: 'my-charity-name'
  //   })
  //   const DBParam = dbQueryMock.mock.calls[0][0]
  //   expect(DBParam['Item']['name']).toEqual('my-charity-name')
  //   expect(DBParam['Item']['id']).toEqual(someId)
  //   expect(charity.id).toBe(someId)
  // })
})
<commit_msg>Fix linting.<commit_after>/* eslint-env jest */

// import uuid from 'uuid/v4'

import tableNames from '../../tables'
import Charity from '../CharityModel'
// import { DatabaseOperation, setMockDBResponse } from '../../test-utils'

jest.mock('../../databaseClient')

afterEach(() => {
  jest.resetAllMocks()
})

describe('CharityModel', () => {
  it('implements the name property', () => {
    expect(Charity.name).toBeDefined()
  })

  it('implements the hashKey property', () => {
    expect(Charity.hashKey).toBeDefined()
  })

  it('implements the tableName property', () => {
    expect(Charity.tableName).toBe(tableNames['charities'])
  })

  // TODO: test permissions

  // TODO: use these in other models where `create` is useful.
  // it('auto creates an id', async () => {
  //   setMockDBResponse(DatabaseOperation.CREATE)
  //   const charity = await Charity.create({ name: 'something' })
  //   expect(charity.id).toBeDefined()
  //   expect(charity.name).toEqual('something')
  // })

  // it('creates with an existing id', async () => {
  //   const dbQueryMock = setMockDBResponse(DatabaseOperation.CREATE)
  //   const someId = uuid()
  //   const charity = await Charity.create({
  //     id: someId,
  //     name: 'my-charity-name'
  //   })
  //   const DBParam = dbQueryMock.mock.calls[0][0]
  //   expect(DBParam['Item']['name']).toEqual('my-charity-name')
  //   expect(DBParam['Item']['id']).toEqual(someId)
  //   expect(charity.id).toBe(someId)
  // })
})
<|endoftext|>


<commit_before>dojo.provide("dojox.charting.Theme");
dojo.require("dojox.charting._color");

(function(){
	var dxc=dojox.charting;
	//	TODO: Legend information
	dxc.Theme=function(/*object?*/kwArgs){
		kwArgs=kwArgs||{};
		this.chart=dojo.mixin(dojo.clone(dxc.Theme._def.chart), kwArgs.chart||{});
		this.plotarea=dojo.mixin(dojo.clone(dxc.Theme._def.plotarea), kwArgs.plotarea||{});
		this.axis=dojo.mixin(dojo.clone(dxc.Theme._def.axis), kwArgs.axis||{});
		this.series=dojo.mixin(dojo.clone(dxc.Theme._def.series), kwArgs.series||{});
		this.marker=dojo.mixin(dojo.clone(dxc.Theme._def.marker), kwArgs.marker||{});
		this.markers=dojo.mixin(dojo.clone(dxc.Theme.Markers), kwArgs.markers||{});
		this.colors=[];
		this.antiAlias=("antiAlias" in kwArgs)?kwArgs.antiAlias:true;
		this.assignColors=("assignColors" in kwArgs)?kwArgs.assignColors:true;
		this.assignMarkers=("assignMarkers" in kwArgs)?kwArgs.assignMarkers:true;
		this._colorCache=null;

		//	push the colors, use _def colors if none passed.
		kwArgs.colors=kwArgs.colors||dxc.Theme._def.colors;
		dojo.forEach(kwArgs.colors, function(item){
			this.colors.push(item);
		}, this);

		//	private variables for color and marker indexing
		this._current={ color:0, marker: 0 };
		this._markers=[];
		this._buildMarkerArray();
	};

	//	"static" fields
	//	default markers.
	//	A marker is defined by an SVG path segment; it should be defined as
	//		relative motion, and with the assumption that the path segment
	//		will be moved to the value point (i.e prepend Mx,y)
	dxc.Theme.Markers={
		NONE:"",
		CIRCLE:"m-3,0 c0,-4 6,-4 6,0 m-6,0 c0,4 6,4 6,0",
		SQUARE:"m-3,-3 l0,6 6,0 0,-6 z",
		DIAMOND:"m0,-3 l3,3 -3,3 -3,-3 z",
		CROSS:"m0,-3 l0,6 m-3,-3 l6,0",
		X:"m-3,-3 l6,6 m0,-6 l-6,6",
		TRIANGLE:"m-3,3 l3,-6 3,6 z",
		TRIANGLE_INVERTED:"m-3,-3 l3,6 3,-6 z"
	};
	dxc.Theme._def={
		//	all objects are structs used directly in dojox.gfx
		chart:{
			stroke: {width: 0},
			fill: "white"
		},
		plotarea:{
			stroke: {width: 0},
			fill: "white"
		},
		//	TODO: label rotation on axis
		axis:{
			stroke:{ color:"#000",width:2 },							//	the axis itself
			line:{ color:"#ccc",width:1,style:"Dot",cap:"round" },		//	gridlines
			majorTick:{ color:"#999", width:2, length:12, position:"center" },	//	major ticks on axis
			minorTick:{ color:"#999", width:1, length:8, position:"center" },	//	minor ticks on axis
			font:"normal normal normal 8pt Tahoma",						//	labels on axis
			fontColor:"#000"											//	color of labels
		},
		series:{
			outline: {width: 2, color: "#ccc"},							//	line or outline
			stroke: {width: 2, color: "#333"},							//	line or outline
			fill: "#ccc",												//	fill, if appropriate
			font: "normal normal normal 7pt Tahoma",					//	if there's a label
			fontColor: "#000"											// 	color of labels
		},
		marker:{	//	any markers on a series.
			stroke: {width:2},											//	stroke or outline
			fill: "#333",												//	fill if needed
			font: "normal normal normal 7pt Tahoma",					//	label
			fontColor: "#000"
		},
		colors:[
			"#000","#111","#222","#333",
			"#444","#555","#666","#777",
			"#888","#999","#aaa","#bbb",
			"#ccc"
		]
	};

	//	prototype methods
	dojo.extend(dxc.Theme, {
		defineColors: function(obj){
			//	we can generate a set of colors based on keyword arguments
			var kwArgs=obj||{};

			//	deal with caching
			var cache=false;
			if(kwArgs.cache===undefined){ cache=true; }
			if(kwArgs.cache==true){ cache=true; }

			if(cache){
				this._colorCache=kwArgs;
			} else {
				var mix=this._colorCache||{};
				kwArgs=dojo.mixin(dojo.clone(mix), kwArgs);
			}

			var c=[], n=kwArgs.num||32;	//	the number of colors to generate
			if(kwArgs.colors){
				//	we have an array of colors predefined, so fix for the number of series.
				var l=kwArgs.colors.length;
				for(var i=0; i<n; i++){
					c.push(kwArgs.colors[i%l]);
				}
				this.colors=c;
			}
			else if(kwArgs.hue){
				//	single hue, generate a set based on brightness
				var s=kwArgs.saturation||100;	//	saturation
				var st=kwArgs.low||30;
				var end=kwArgs.high||90;
				var step=(end-st)/n;			//	brightness steps
				for(var i=0; i<n; i++){
					c.push(dxc._color.fromHsb(kwArgs.hue, s, st+(step*i)).toHex());
				}
				this.colors=c;
			}
			else if(kwArgs.stops){
				//	create color ranges that are either equally distributed, or
				//	(optionally) based on a passed "offset" property.  If you
				//	pass an array of Colors, it will equally distribute, if
				//	you pass an array of structs { color, offset }, it will
				//	use the offset (0.0 - 1.0) to distribute.  Note that offset
				//	values should be plotted on a line from 0.0 to 1.0--i.e.
				//	they should be additive.  For example:
				//	[ {color, offset:0}, { color, offset:0.2 }, { color, offset:0.5 }, { color, offset:1.0 } ]
				//
				//	If you use stops for colors, you MUST have a color at 0.0 and one
				//	at 1.0.

				//	figure out how many stops we have
				var l=kwArgs.stops.length;
				if(l<2){
					throw new Error(
						"dojox.charting.Theme::defineColors: when using stops to "
						+ "define a color range, you MUST specify at least 2 colors."
					);
				}

				//	figure out if the distribution is equal or not.  Note that
				//	colors may not exactly match the stops you define; because
				//	color generation is linear (i.e. evenly divided on a linear
				//	axis), it's very possible that a color will land in between
				//	two stops and not exactly *at* a stop.
				//
				//	The only two colors guaranteed will be the end stops (i.e.
				//	the first and last stop), which will *always* be set as
				//	the end stops.
				if(typeof(kwArgs.stops[0].offset)=="undefined"){
					//	set up equal offsets
					var off=1/(l-1);
					for(var i=0; i<l; i++){
						kwArgs.stops[i]={
							color:kwArgs.stops[i],
							offset:off*i
						};
					}
				}
				//	ensure the ends.
				kwArgs.stops[0].offset=0;
				kwArgs.stops[l-1].offset=1;
				kwArgs.stops.sort(function(a,b){ return a.offset-b.offset; });

				//	create the colors.
				//	first stop.
				c.push(kwArgs.stops[0].color.toHex());

				//	TODO: calculate the blend at n/steps and set the color

				//	last stop
				c.push(kwArgs.stops[l-1].color.toHex());
				this.colors=c;
			}
		},

		_buildMarkerArray: function(){
			this._markers=[];
			for(var p in this.markers){ this._markers.push(this.markers[p]); }
			//	reset the position
			this._current.marker=0;
		},

		addMarker:function(/* string */name, /*string*/segment){
			//	summary
			//	Add a custom marker to this theme.
			//
			//	example
			//	myTheme.addMarker("Ellipse", foo);
			this.markers[name]=segment;
			this._buildMarkerArray();
		},
		setMarkers:function(/* object */obj){
			//	summary
			//	Set all the markers of this theme at once.  obj should be
			//	a dictionary of keys and path segments.
			//
			//	example
			//	myTheme.setMarkers({ "CIRCLE": foo });
			this.markers=obj;
			this._buildMarkerArray();
		},

		next: function(/* string? */type){
			//	summary
			//	get either the next color or the next marker, depending on what was
			//	passed.  If type is not passed, it assumes color.
			//
			//	example
			//	var color=myTheme.next();
			//	var color=myTheme.next("color");
			//	var marker=myTheme.next("marker");
			if(!type) type="color";
			if(type=="color"){
				return this.colors[this._current.color++%this.colors.length];
			}
			else if(type=="marker"){
				return this._markers[this._current.marker++%this._markers.length];
			}
		},
		clear: function(){
			this._current = {color: 0, marker: 0};
		}
	});
})();
<commit_msg>Default definition fixes.<commit_after>dojo.provide("dojox.charting.Theme");
dojo.require("dojox.charting._color");

(function(){
	var dxc=dojox.charting;
	//	TODO: Legend information
	dxc.Theme=function(/*object?*/kwArgs){
		kwArgs=kwArgs||{};
		this.chart=dojo.mixin(dojo.clone(dxc.Theme._def.chart), kwArgs.chart||{});
		this.plotarea=dojo.mixin(dojo.clone(dxc.Theme._def.plotarea), kwArgs.plotarea||{});
		this.axis=dojo.mixin(dojo.clone(dxc.Theme._def.axis), kwArgs.axis||{});
		this.series=dojo.mixin(dojo.clone(dxc.Theme._def.series), kwArgs.series||{});
		this.marker=dojo.mixin(dojo.clone(dxc.Theme._def.marker), kwArgs.marker||{});
		this.markers=dojo.mixin(dojo.clone(dxc.Theme.Markers), kwArgs.markers||{});
		this.colors=[];
		this.antiAlias=("antiAlias" in kwArgs)?kwArgs.antiAlias:true;
		this.assignColors=("assignColors" in kwArgs)?kwArgs.assignColors:true;
		this.assignMarkers=("assignMarkers" in kwArgs)?kwArgs.assignMarkers:true;
		this._colorCache=null;

		//	push the colors, use _def colors if none passed.
		kwArgs.colors=kwArgs.colors||dxc.Theme._def.colors;
		dojo.forEach(kwArgs.colors, function(item){
			this.colors.push(item);
		}, this);

		//	private variables for color and marker indexing
		this._current={ color:0, marker: 0 };
		this._markers=[];
		this._buildMarkerArray();
	};

	//	"static" fields
	//	default markers.
	//	A marker is defined by an SVG path segment; it should be defined as
	//		relative motion, and with the assumption that the path segment
	//		will be moved to the value point (i.e prepend Mx,y)
	dxc.Theme.Markers={
		NONE:"",
		CIRCLE:"m-3,0 c0,-4 6,-4 6,0 m-6,0 c0,4 6,4 6,0",
		SQUARE:"m-3,-3 l0,6 6,0 0,-6 z",
		DIAMOND:"m0,-3 l3,3 -3,3 -3,-3 z",
		CROSS:"m0,-3 l0,6 m-3,-3 l6,0",
		X:"m-3,-3 l6,6 m0,-6 l-6,6",
		TRIANGLE:"m-3,3 l3,-6 3,6 z",
		TRIANGLE_INVERTED:"m-3,-3 l3,6 3,-6 z"
	};
	dxc.Theme._def={
		//	all objects are structs used directly in dojox.gfx
		chart:{
			stroke: {width: 0},
			fill: "white"
		},
		plotarea:{
			stroke: {width: 0},
			fill: "white"
		},
		//	TODO: label rotation on axis
		axis:{
			stroke:{ color:"#333",width:1 },							//	the axis itself
			line:{ color:"#ccc",width:1,style:"Dot",cap:"round" },		//	gridlines
			majorTick:{ color:"#666", width:1, length:6, position:"center" },	//	major ticks on axis
			minorTick:{ color:"#666", width:0.8, length:3, position:"center" },	//	minor ticks on axis
			font:"normal normal normal 7pt Tahoma",						//	labels on axis
			fontColor:"#333"											//	color of labels
		},
		series:{
			outline: {width: 2, color: "#ccc"},							//	line or outline
			stroke: {width: 2, color: "#333"},							//	line or outline
			fill: "#ccc",												//	fill, if appropriate
			font: "normal normal normal 7pt Tahoma",					//	if there's a label
			fontColor: "#000"											// 	color of labels
		},
		marker:{	//	any markers on a series.
			stroke: {width:1},											//	stroke or outline
			fill: "#333",												//	fill if needed
			font: "normal normal normal 7pt Tahoma",					//	label
			fontColor: "#000"
		},
		colors:[
			"#000","#111","#222","#333",
			"#444","#555","#666","#777",
			"#888","#999","#aaa","#bbb",
			"#ccc"
		]
	};

	//	prototype methods
	dojo.extend(dxc.Theme, {
		defineColors: function(obj){
			//	we can generate a set of colors based on keyword arguments
			var kwArgs=obj||{};

			//	deal with caching
			var cache=false;
			if(kwArgs.cache===undefined){ cache=true; }
			if(kwArgs.cache==true){ cache=true; }

			if(cache){
				this._colorCache=kwArgs;
			} else {
				var mix=this._colorCache||{};
				kwArgs=dojo.mixin(dojo.clone(mix), kwArgs);
			}

			var c=[], n=kwArgs.num||32;	//	the number of colors to generate
			if(kwArgs.colors){
				//	we have an array of colors predefined, so fix for the number of series.
				var l=kwArgs.colors.length;
				for(var i=0; i<n; i++){
					c.push(kwArgs.colors[i%l]);
				}
				this.colors=c;
			}
			else if(kwArgs.hue){
				//	single hue, generate a set based on brightness
				var s=kwArgs.saturation||100;	//	saturation
				var st=kwArgs.low||30;
				var end=kwArgs.high||90;
				var step=(end-st)/n;			//	brightness steps
				for(var i=0; i<n; i++){
					c.push(dxc._color.fromHsb(kwArgs.hue, s, st+(step*i)).toHex());
				}
				this.colors=c;
			}
			else if(kwArgs.stops){
				//	create color ranges that are either equally distributed, or
				//	(optionally) based on a passed "offset" property.  If you
				//	pass an array of Colors, it will equally distribute, if
				//	you pass an array of structs { color, offset }, it will
				//	use the offset (0.0 - 1.0) to distribute.  Note that offset
				//	values should be plotted on a line from 0.0 to 1.0--i.e.
				//	they should be additive.  For example:
				//	[ {color, offset:0}, { color, offset:0.2 }, { color, offset:0.5 }, { color, offset:1.0 } ]
				//
				//	If you use stops for colors, you MUST have a color at 0.0 and one
				//	at 1.0.

				//	figure out how many stops we have
				var l=kwArgs.stops.length;
				if(l<2){
					throw new Error(
						"dojox.charting.Theme::defineColors: when using stops to "
						+ "define a color range, you MUST specify at least 2 colors."
					);
				}

				//	figure out if the distribution is equal or not.  Note that
				//	colors may not exactly match the stops you define; because
				//	color generation is linear (i.e. evenly divided on a linear
				//	axis), it's very possible that a color will land in between
				//	two stops and not exactly *at* a stop.
				//
				//	The only two colors guaranteed will be the end stops (i.e.
				//	the first and last stop), which will *always* be set as
				//	the end stops.
				if(typeof(kwArgs.stops[0].offset)=="undefined"){
					//	set up equal offsets
					var off=1/(l-1);
					for(var i=0; i<l; i++){
						kwArgs.stops[i]={
							color:kwArgs.stops[i],
							offset:off*i
						};
					}
				}
				//	ensure the ends.
				kwArgs.stops[0].offset=0;
				kwArgs.stops[l-1].offset=1;
				kwArgs.stops.sort(function(a,b){ return a.offset-b.offset; });

				//	create the colors.
				//	first stop.
				c.push(kwArgs.stops[0].color.toHex());

				//	TODO: calculate the blend at n/steps and set the color

				//	last stop
				c.push(kwArgs.stops[l-1].color.toHex());
				this.colors=c;
			}
		},

		_buildMarkerArray: function(){
			this._markers=[];
			for(var p in this.markers){ this._markers.push(this.markers[p]); }
			//	reset the position
			this._current.marker=0;
		},

		addMarker:function(/* string */name, /*string*/segment){
			//	summary
			//	Add a custom marker to this theme.
			//
			//	example
			//	myTheme.addMarker("Ellipse", foo);
			this.markers[name]=segment;
			this._buildMarkerArray();
		},
		setMarkers:function(/* object */obj){
			//	summary
			//	Set all the markers of this theme at once.  obj should be
			//	a dictionary of keys and path segments.
			//
			//	example
			//	myTheme.setMarkers({ "CIRCLE": foo });
			this.markers=obj;
			this._buildMarkerArray();
		},

		next: function(/* string? */type){
			//	summary
			//	get either the next color or the next marker, depending on what was
			//	passed.  If type is not passed, it assumes color.
			//
			//	example
			//	var color=myTheme.next();
			//	var color=myTheme.next("color");
			//	var marker=myTheme.next("marker");
			if(!type) type="color";
			if(type=="color"){
				return this.colors[this._current.color++%this.colors.length];
			}
			else if(type=="marker"){
				return this._markers[this._current.marker++%this._markers.length];
			}
		},
		clear: function(){
			this._current = {color: 0, marker: 0};
		}
	});
})();
<|endoftext|>


<commit_before>/**
 *
 * Version: 0.1.4
 * Author: Gianluca Guarini
 * Contact: gianluca.guarini@gmail.com
 * Website: http://www.gianlucaguarini.com/
 * Twitter: @gianlucaguarini
 *
 * Copyright (c) Gianluca Guarini
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 **/

(function(doc, win) {
  'use strict';
  if (typeof doc.createEvent !== 'function') return false; // no tap events here
  // helpers
  var useJquery = typeof jQuery !== 'undefined',
    // some helpers borrowed from https://github.com/WebReflection/ie-touch
    msPointerEnabled = !!navigator.pointerEnabled || navigator.msPointerEnabled,
    isTouch = (!!('ontouchstart' in win) && navigator.userAgent.indexOf('PhantomJS') < 0) || msPointerEnabled,
    msEventType = function(type) {
      var lo = type.toLowerCase(),
        ms = 'MS' + type;
      return navigator.msPointerEnabled ? ms : lo;
    },
    touchevents = {
      touchstart: msEventType('PointerDown') + ' touchstart',
      touchend: msEventType('PointerUp') + ' touchend',
      touchmove: msEventType('PointerMove') + ' touchmove'
    },
    setListener = function(elm, events, callback) {
      var eventsArray = events.split(' '),
        i = eventsArray.length;

      while (i--) {
        elm.addEventListener(eventsArray[i], callback, false);
      }
    },
    getPointerEvent = function(event) {
      return event.targetTouches ? event.targetTouches[0] : event;
    },
    getTimestamp = function () {
      return new Date().getTime();
    },
    sendEvent = function(elm, eventName, originalEvent, data) {
      var customEvent = doc.createEvent('Event');
      data = data || {};
      data.x = currX;
      data.y = currY;
      data.distance = data.distance;
      if (useJquery)
        jQuery(elm).trigger(eventName, data);
      else {
        customEvent.originalEvent = originalEvent;
        for (var key in data) {
          customEvent[key] = data[key];
        }
        customEvent.initEvent(eventName, true, true);
        elm.dispatchEvent(customEvent);
      }
    },

    onTouchStart = function(e) {
      var pointer = getPointerEvent(e);
      // caching the current x
      cachedX = currX = pointer.pageX;
      // caching the current y
      cachedY = currY = pointer.pageY;

      timestamp = getTimestamp();
      tapNum++;
      // we will use these variables on the touchend events
    },
    onTouchEnd = function(e) {
      var eventsArr = [],
        deltaY = cachedY - currY,
        deltaX = cachedX - currX;

      // clear the previous timer in case it was set
      clearTimeout(tapTimer);

      if (deltaX <= -swipeThreshold)
        eventsArr.push('swiperight');

      if (deltaX >= swipeThreshold)
        eventsArr.push('swipeleft');

      if (deltaY <= -swipeThreshold)
        eventsArr.push('swipedown');

      if (deltaY >= swipeThreshold)
        eventsArr.push('swipeup');
      if (eventsArr.length) {
        for (var i = 0; i < eventsArr.length; i++) {
          var eventName = eventsArr[i];
          sendEvent(e.target, eventName, e, {
            distance: {
              x: Math.abs(deltaX),
              y: Math.abs(deltaY)
            }
          });
        }
      } else {

        if (
          (timestamp + tapThreshold) - getTimestamp() >= 0 &&
          cachedX >= currX - tapPrecision &&
          cachedX <= currX + tapPrecision &&
          cachedY >= currY - tapPrecision &&
          cachedY <= currY + tapPrecision
        ) {
          // Here you get the Tap event
          sendEvent(e.target, (tapNum === 2) && (target === e.target) ? 'dbltap' : 'tap', e);
          target= e.target;
        }

        // reset the tap counter
        tapTimer = setTimeout(function() {
          tapNum = 0;
        }, dbltapThreshold);

      }
    },
    onTouchMove = function(e) {
      var pointer = getPointerEvent(e);
      currX = pointer.pageX;
      currY = pointer.pageY;
    },
    swipeThreshold = win.SWIPE_THRESHOLD || 100,
    tapThreshold = win.TAP_THRESHOLD || 150, // range of time where a tap event could be detected
    dbltapThreshold = win.DBL_TAP_THRESHOLD || 200, // delay needed to detect a double tap
    tapPrecision = win.TAP_PRECISION / 2 || 60 / 2, // touch events boundaries ( 60px by default )
    justTouchEvents = win.JUST_ON_TOUCH_DEVICES || isTouch,
    tapNum = 0,
    currX, currY, cachedX, cachedY, tapTimer, timestamp, target;

  //setting the events listeners
  setListener(doc, touchevents.touchstart + (justTouchEvents ? '' : ' mousedown'), onTouchStart);
  setListener(doc, touchevents.touchend + (justTouchEvents ? '' : ' mouseup'), onTouchEnd);
  setListener(doc, touchevents.touchmove + (justTouchEvents ? '' : ' mousemove'), onTouchMove);
}(document, window));
<commit_msg>jQuery now triggers a custom jquery Event containing the originalEvent<commit_after>/**
 *
 * Version: 0.1.4
 * Author: Gianluca Guarini
 * Contact: gianluca.guarini@gmail.com
 * Website: http://www.gianlucaguarini.com/
 * Twitter: @gianlucaguarini
 *
 * Copyright (c) Gianluca Guarini
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 **/

(function(doc, win) {
  'use strict';
  if (typeof doc.createEvent !== 'function') return false; // no tap events here
  // helpers
  var useJquery = typeof jQuery !== 'undefined',
    // some helpers borrowed from https://github.com/WebReflection/ie-touch
    msPointerEnabled = !!navigator.pointerEnabled || navigator.msPointerEnabled,
    isTouch = (!!('ontouchstart' in win) && navigator.userAgent.indexOf('PhantomJS') < 0) || msPointerEnabled,
    msEventType = function(type) {
      var lo = type.toLowerCase(),
        ms = 'MS' + type;
      return navigator.msPointerEnabled ? ms : lo;
    },
    touchevents = {
      touchstart: msEventType('PointerDown') + ' touchstart',
      touchend: msEventType('PointerUp') + ' touchend',
      touchmove: msEventType('PointerMove') + ' touchmove'
    },
    setListener = function(elm, events, callback) {
      var eventsArray = events.split(' '),
        i = eventsArray.length;

      while (i--) {
        elm.addEventListener(eventsArray[i], callback, false);
      }
    },
    getPointerEvent = function(event) {
      return event.targetTouches ? event.targetTouches[0] : event;
    },
    getTimestamp = function () {
      return new Date().getTime();
    },
    sendEvent = function(elm, eventName, originalEvent, data) {
      var customEvent;
      data = data || {};
      data.x = currX;
      data.y = currY;
      data.distance = data.distance;
      if (useJquery) {
        customEvent = $.Event(eventName, {originalEvent: originalEvent});
        jQuery(elm).trigger(customEvent, data);
      } else {
        customEvent = doc.createEvent('Event');
        customEvent.originalEvent = originalEvent;
        for (var key in data) {
          customEvent[key] = data[key];
        }
        customEvent.initEvent(eventName, true, true);
        elm.dispatchEvent(customEvent);
      }
    },

    onTouchStart = function(e) {
      var pointer = getPointerEvent(e);
      // caching the current x
      cachedX = currX = pointer.pageX;
      // caching the current y
      cachedY = currY = pointer.pageY;

      timestamp = getTimestamp();
      tapNum++;
      // we will use these variables on the touchend events
    },
    onTouchEnd = function(e) {
      var eventsArr = [],
        deltaY = cachedY - currY,
        deltaX = cachedX - currX;

      // clear the previous timer in case it was set
      clearTimeout(tapTimer);

      if (deltaX <= -swipeThreshold)
        eventsArr.push('swiperight');

      if (deltaX >= swipeThreshold)
        eventsArr.push('swipeleft');

      if (deltaY <= -swipeThreshold)
        eventsArr.push('swipedown');

      if (deltaY >= swipeThreshold)
        eventsArr.push('swipeup');
      if (eventsArr.length) {
        for (var i = 0; i < eventsArr.length; i++) {
          var eventName = eventsArr[i];
          sendEvent(e.target, eventName, e, {
            distance: {
              x: Math.abs(deltaX),
              y: Math.abs(deltaY)
            }
          });
        }
      } else {

        if (
          (timestamp + tapThreshold) - getTimestamp() >= 0 &&
          cachedX >= currX - tapPrecision &&
          cachedX <= currX + tapPrecision &&
          cachedY >= currY - tapPrecision &&
          cachedY <= currY + tapPrecision
        ) {
          // Here you get the Tap event
          sendEvent(e.target, (tapNum === 2) && (target === e.target) ? 'dbltap' : 'tap', e);
          target= e.target;
        }

        // reset the tap counter
        tapTimer = setTimeout(function() {
          tapNum = 0;
        }, dbltapThreshold);

      }
    },
    onTouchMove = function(e) {
      var pointer = getPointerEvent(e);
      currX = pointer.pageX;
      currY = pointer.pageY;
    },
    swipeThreshold = win.SWIPE_THRESHOLD || 100,
    tapThreshold = win.TAP_THRESHOLD || 150, // range of time where a tap event could be detected
    dbltapThreshold = win.DBL_TAP_THRESHOLD || 200, // delay needed to detect a double tap
    tapPrecision = win.TAP_PRECISION / 2 || 60 / 2, // touch events boundaries ( 60px by default )
    justTouchEvents = win.JUST_ON_TOUCH_DEVICES || isTouch,
    tapNum = 0,
    currX, currY, cachedX, cachedY, tapTimer, timestamp, target;

  //setting the events listeners
  setListener(doc, touchevents.touchstart + (justTouchEvents ? '' : ' mousedown'), onTouchStart);
  setListener(doc, touchevents.touchend + (justTouchEvents ? '' : ' mouseup'), onTouchEnd);
  setListener(doc, touchevents.touchmove + (justTouchEvents ? '' : ' mousemove'), onTouchMove);
}(document, window));
<|endoftext|>


<commit_before>module.define("org/arangodb/arango-query-cursor", function(exports, module) {
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief ArangoQueryCursor
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Achim Brandt
/// @author Dr. Frank Celler
/// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangosh = require("org/arangodb/arangosh");

// -----------------------------------------------------------------------------
// --SECTION--                                                 ArangoQueryCursor
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                      constructors and destructors
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief constructor
////////////////////////////////////////////////////////////////////////////////

function ArangoQueryCursor (database, data) {
  this._database = database;
  this.data = data;
  this._hasNext = false;
  this._hasMore = false;
  this._pos = 0;
  this._count = 0;
  this._total = 0;

  if (data.result !== undefined) {
    this._count = data.result.length;

    if (this._pos < this._count) {
      this._hasNext = true;
    }

    if (data.hasMore !== undefined && data.hasMore) {
      this._hasMore = true;
    }
  }
}

exports.ArangoQueryCursor = ArangoQueryCursor;

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief return a string representation of the cursor
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.toString = function () {
  return arangosh.getIdString(this, "ArangoQueryCursor");
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return all remaining result documents from the cursor
///
/// If no more results are available locally but more results are available on
/// the server, this function will make one or multiple roundtrips to the
/// server. Calling this function will also fully exhaust the cursor.
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.toArray =
ArangoQueryCursor.prototype.elements = function () {
  var result = [];

  while (this.hasNext()) {
    result.push( this.next() );
  }

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief print the help for the cursor
////////////////////////////////////////////////////////////////////////////////

var helpArangoQueryCursor = arangosh.createHelpHeadline("ArangoQueryCursor help") +
  'ArangoQueryCursor constructor:                                      ' + "\n" +
  ' > cu1 = qi1.execute();                                             ' + "\n" +
  'Functions:                                                          ' + "\n" +
  '  hasNext();                            returns true if there       ' + "\n" +
  '                                        are more results            ' + "\n" +
  '  next();                               returns the next document   ' + "\n" +
  '  elements();                           returns all documents       ' + "\n" +
  '  _help();                              this help                   ' + "\n" +
  'Attributes:                                                         ' + "\n" +
  '  _database                             database object             ' + "\n" +
  'Example:                                                            ' + "\n" +
  ' > st = db._createStatement({ "query" : "for c in coll return c" });' + "\n" +
  ' > c = st.execute();                                                ' + "\n" +
  ' > documents = c.elements();                                        ' + "\n" +
  ' > c = st.execute();                                                ' + "\n" +
  ' > while (c.hasNext()) { print( c.next() ); }                       ';

ArangoQueryCursor.prototype._help = function () {
  internal.print(helpArangoQueryCursor);
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                  public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief return whether there are more results available in the cursor
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.hasNext = function () {
  return this._hasNext;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the next result document from the cursor
///
/// If no more results are available locally but more results are available on
/// the server, this function will make a roundtrip to the server
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.next = function () {
  if (!this._hasNext) {
    throw "No more results";
  }

  var result = this.data.result[this._pos];
  this._pos++;

  // reached last result
  if (this._pos === this._count) {
    this._hasNext = false;
    this._pos = 0;

    if (this._hasMore && this.data.id) {
      this._hasMore = false;

      // load more results
      var requestResult = this._database._connection.PUT(this._baseurl(), "");

      arangosh.checkRequestResult(requestResult);

      this.data = requestResult;
      this._count = requestResult.result.length;

      if (this._pos < this._count) {
        this._hasNext = true;
      }

      if (requestResult.hasMore !== undefined && requestResult.hasMore) {
        this._hasMore = true;
      }
    }
  }

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief explicitly dispose the cursor
///
/// Calling this function will mark the cursor as deleted on the server. It will
/// therefore make a roundtrip to the server. Using a cursor after it has been
/// disposed is considered a user error
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.dispose = function () {
  if (! this.data.id) {
    // client side only cursor
    return;
  }

  var requestResult = this._database._connection.DELETE(this._baseurl(), "");

  arangosh.checkRequestResult(requestResult);

  this.data.id = undefined;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the total number of documents in the cursor
///
/// The number will remain the same regardless how much result documents have
/// already been fetched from the cursor.
///
/// This function will return the number only if the cursor was constructed
/// with the "doCount" attribute. Otherwise it will return undefined.
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.count = function () {
  if (! this.data.id) {
    throw "cursor has been disposed";
  }

  return this.data.count;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return baseurl for query cursor
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype._baseurl = function () {
  return "/_api/cursor/"+ encodeURIComponent(this.data.id);
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint\\)"
// End:
});
<commit_msg>derived file<commit_after>module.define("org/arangodb/arango-query-cursor", function(exports, module) {
/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */
/*global require, exports */

////////////////////////////////////////////////////////////////////////////////
/// @brief ArangoQueryCursor
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2013 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Achim Brandt
/// @author Dr. Frank Celler
/// @author Copyright 2012-2013, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////

var internal = require("internal");
var arangosh = require("org/arangodb/arangosh");

// -----------------------------------------------------------------------------
// --SECTION--                                                 ArangoQueryCursor
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
// --SECTION--                                      constructors and destructors
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief constructor
////////////////////////////////////////////////////////////////////////////////

function ArangoQueryCursor (database, data) {
  this._database = database;
  this.data = data;
  this._hasNext = false;
  this._hasMore = false;
  this._pos = 0;
  this._count = 0;
  this._total = 0;

  if (data.result !== undefined) {
    this._count = data.result.length;

    if (this._pos < this._count) {
      this._hasNext = true;
    }

    if (data.hasMore !== undefined && data.hasMore) {
      this._hasMore = true;
    }
  }
}

exports.ArangoQueryCursor = ArangoQueryCursor;

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                 private functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief return a string representation of the cursor
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.toString = function () {
  return arangosh.getIdString(this, "ArangoQueryCursor");
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return all remaining result documents from the cursor
///
/// If no more results are available locally but more results are available on
/// the server, this function will make one or multiple roundtrips to the
/// server. Calling this function will also fully exhaust the cursor.
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.toArray =
ArangoQueryCursor.prototype.elements = function () {
  var result = [];

  while (this.hasNext()) {
    result.push( this.next() );
  }

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief print the help for the cursor
////////////////////////////////////////////////////////////////////////////////

var helpArangoQueryCursor = arangosh.createHelpHeadline("ArangoQueryCursor help") +
  'ArangoQueryCursor constructor:                                      ' + "\n" +
  ' > cu1 = qi1.execute();                                             ' + "\n" +
  'Functions:                                                          ' + "\n" +
  '  hasNext();                            returns true if there       ' + "\n" +
  '                                        are more results            ' + "\n" +
  '  next();                               returns the next document   ' + "\n" +
  '  elements();                           returns all documents       ' + "\n" +
  '  _help();                              this help                   ' + "\n" +
  'Attributes:                                                         ' + "\n" +
  '  _database                             database object             ' + "\n" +
  'Example:                                                            ' + "\n" +
  ' > st = db._createStatement({ "query" : "for c in coll return c" });' + "\n" +
  ' > c = st.execute();                                                ' + "\n" +
  ' > documents = c.elements();                                        ' + "\n" +
  ' > c = st.execute();                                                ' + "\n" +
  ' > while (c.hasNext()) { print( c.next() ); }                       ';

ArangoQueryCursor.prototype._help = function () {
  internal.print(helpArangoQueryCursor);
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                  public functions
// -----------------------------------------------------------------------------

////////////////////////////////////////////////////////////////////////////////
/// @addtogroup ArangoShell
/// @{
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
/// @brief return whether there are more results available in the cursor
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.hasNext = function () {
  return this._hasNext;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the next result document from the cursor
///
/// If no more results are available locally but more results are available on
/// the server, this function will make a roundtrip to the server
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.next = function () {
  if (! this._hasNext) {
    throw "No more results";
  }

  var result = this.data.result[this._pos];
  this._pos++;

  // reached last result
  if (this._pos === this._count) {
    this._hasNext = false;
    this._pos = 0;

    if (this._hasMore && this.data.id) {
      this._hasMore = false;

      // load more results
      var requestResult = this._database._connection.PUT(this._baseurl(), "");

      arangosh.checkRequestResult(requestResult);

      this.data = requestResult;
      this._count = requestResult.result.length;

      if (this._pos < this._count) {
        this._hasNext = true;
      }

      if (requestResult.hasMore !== undefined && requestResult.hasMore) {
        this._hasMore = true;
      }
    }
  }

  return result;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief explicitly dispose the cursor
///
/// Calling this function will mark the cursor as deleted on the server. It will
/// therefore make a roundtrip to the server. Using a cursor after it has been
/// disposed is considered a user error
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.dispose = function () {
  if (! this.data.id) {
    // client side only cursor
    return;
  }

  var requestResult = this._database._connection.DELETE(this._baseurl(), "");

  arangosh.checkRequestResult(requestResult);

  this.data.id = undefined;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return the total number of documents in the cursor
///
/// The number will remain the same regardless how much result documents have
/// already been fetched from the cursor.
///
/// This function will return the number only if the cursor was constructed
/// with the "doCount" attribute. Otherwise it will return undefined.
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype.count = function () {
  return this.data.count;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief return baseurl for query cursor
////////////////////////////////////////////////////////////////////////////////

ArangoQueryCursor.prototype._baseurl = function () {
  return "/_api/cursor/"+ encodeURIComponent(this.data.id);
};

////////////////////////////////////////////////////////////////////////////////
/// @}
////////////////////////////////////////////////////////////////////////////////

// -----------------------------------------------------------------------------
// --SECTION--                                                       END-OF-FILE
// -----------------------------------------------------------------------------

// Local Variables:
// mode: outline-minor
// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @}\\|/\\*jslint\\)"
// End:
});
<|endoftext|>


<commit_before>import { sendDelegationVotes, publishContract, vote } from '../../../../../startup/both/modules/Contract';

Template.action.helpers({
  disabled: function () {
    if (disableContractExecution()) {
      return 'disabled';
    } else {
      if (Session.get('newVote') != undefined) {
        switch (Session.get('newVote').mode) {
          case WALLET_MODE_EXECUTED:
            return 'executed';
          default:
            return '';
        }
      } else {
        return '';
      }
    }
  }
});

Template.action.events({
    "click .action-button": function (event) {
      //get all info from current draft
      if (this.enabled) {
        if (disableContractExecution() == false) {
          switch(Session.get('contract').kind) {
            case 'DELEGATION':
              for (stamp in Session.get('contract').signatures) {
                if (Session.get('contract').signatures[stamp]._id != Meteor.user()._id) {
                  var counterPartyId = Session.get('contract').signatures[stamp]._id;
                }
              };
              Modules.client.displayModal(
                true,
                {
                  icon            : 'images/modal-delegation.png',
                  title           : TAPi18n.__('send-delegation-votes'),
                  message         : TAPi18n.__('delegate-votes-warning').replace('<quantity>', Session.get('newVote').allocateQuantity),
                  cancel          : TAPi18n.__('not-now'),
                  action          : TAPi18n.__('delegate-votes'),
                  displayProfile  : true,
                  profileId       : counterPartyId
                },
                function() {
                  settings = {
                    condition: {
                      transferable : Session.get('contract').transferable,
                      portable : Session.get('contract').portable,
                      tags : Session.get('contract').tags,
                    },
                    currency: 'VOTES',
                    kind: Session.get('contract').kind,
                    contractId: Session.get('contract')._id
                  }
                  sendDelegationVotes(Session.get('contract').signatures[0]._id, Session.get('contract')._id, Session.get('newVote').allocateQuantity, settings);
                }
              );
              break;
            case 'VOTE':
              if (Session.get('contract').stage == STAGE_DRAFT) {
                Modules.client.displayModal(
                  true,
                  {
                    icon            : 'images/modal-ballot.png',
                    title           : TAPi18n.__('launch-vote-proposal'),
                    message         : TAPi18n.__('publish-proposal-warning'),
                    cancel          : TAPi18n.__('not-now'),
                    action          : TAPi18n.__('publish-proposal'),
                    displayProfile  : false
                  },
                  function() {
                    publishContract(Session.get('contract')._id);
                  }
                );
              } else if (Session.get('contract').stage == 'LIVE') {
                Modules.client.displayModal(
                  true,
                  {
                    icon            : 'images/modal-vote.png',
                    title           : TAPi18n.__('place-vote'),
                    message         : TAPi18n.__('place-votes-warning').replace('<quantity>', Session.get('newVote').allocateQuantity),
                    cancel          : TAPi18n.__('not-now'),
                    action          : TAPi18n.__('vote'),
                    displayProfile  : false
                  },
                  function() {
                    var ballot = Modules.client.purgeBallot(Session.get('candidateBallot'));
                    settings = {
                      condition: {
                        tags : Session.get('contract').tags,
                        ballot : ballot
                      },
                      currency: 'VOTES',
                      kind: Session.get('contract').kind,
                      contractId: Session.get('contract')._id
                    };
                    vote(Meteor.user()._id, Session.get('contract')._id, Session.get('newVote').allocateQuantity, settings);
                  }
                );
              }
              break;
          }
        }
      }
    }
});

function disableContractExecution() {
  if (Session.get('emptyBallot')) {
    return true;
  } else if (Session.get('unauthorizedFork')) {
    return true;
  } else if (Session.get('missingTitle')) {
    return true;
  } else if (Session.get('mistypedTitle')) {
    return true;
  } else if (Session.get('duplicateURL')) {
    return true;
  } else if (Session.get('noVotes')) {
    return true;
  } else if (Session.get('draftOptions')) {
    return true;
  } else if (!Session.get('rightToVote')) {
    return true;
  } else {
    if (Session.get('contract').kind == 'VOTE' && Session.get('contract').stage == 'LIVE') {
      if (!Modules.client.ballotReady()) {
        return true;
      }
    }
    if (Session.get('newVote') != undefined) {
      if (Session.get('newVote').mode == WALLET_MODE_PENDING || Session.get('newVote').mode == undefined) {
        return false;
      } else {
        return true;
      }
    } else {
      return false;
    }
  }
}
<commit_msg>added imports, values from const<commit_after>import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { TAPi18n } from 'meteor/tap:i18n';

import { sendDelegationVotes, publishContract, vote } from '/imports/startup/both/modules/Contract';

Template.action.helpers({
  disabled: function () {
    if (disableContractExecution()) {
      return 'disabled';
    } else {
      if (Session.get('newVote') != undefined) {
        switch (Session.get('newVote').mode) {
          case 'EXECUTED':
            return 'executed';
          default:
            return '';
        }
      } else {
        return '';
      }
    }
  }
});

Template.action.events({
    "click .action-button": function (event) {
      //get all info from current draft
      if (this.enabled) {
        if (disableContractExecution() == false) {
          switch(Session.get('contract').kind) {
            case 'DELEGATION':
              for (stamp in Session.get('contract').signatures) {
                if (Session.get('contract').signatures[stamp]._id != Meteor.user()._id) {
                  var counterPartyId = Session.get('contract').signatures[stamp]._id;
                }
              };
              Modules.client.displayModal(
                true,
                {
                  icon            : 'images/modal-delegation.png',
                  title           : TAPi18n.__('send-delegation-votes'),
                  message         : TAPi18n.__('delegate-votes-warning').replace('<quantity>', Session.get('newVote').allocateQuantity),
                  cancel          : TAPi18n.__('not-now'),
                  action          : TAPi18n.__('delegate-votes'),
                  displayProfile  : true,
                  profileId       : counterPartyId
                },
                function() {
                  settings = {
                    condition: {
                      transferable : Session.get('contract').transferable,
                      portable : Session.get('contract').portable,
                      tags : Session.get('contract').tags,
                    },
                    currency: 'VOTES',
                    kind: Session.get('contract').kind,
                    contractId: Session.get('contract')._id
                  }
                  sendDelegationVotes(Session.get('contract').signatures[0]._id, Session.get('contract')._id, Session.get('newVote').allocateQuantity, settings);
                }
              );
              break;
            case 'VOTE':
              if (Session.get('contract').stage === 'DRAFT') {
                Modules.client.displayModal(
                  true,
                  {
                    icon            : 'images/modal-ballot.png',
                    title           : TAPi18n.__('launch-vote-proposal'),
                    message         : TAPi18n.__('publish-proposal-warning'),
                    cancel          : TAPi18n.__('not-now'),
                    action          : TAPi18n.__('publish-proposal'),
                    displayProfile  : false
                  },
                  function() {
                    publishContract(Session.get('contract')._id);
                  }
                );
              } else if (Session.get('contract').stage == 'LIVE') {
                Modules.client.displayModal(
                  true,
                  {
                    icon            : 'images/modal-vote.png',
                    title           : TAPi18n.__('place-vote'),
                    message         : TAPi18n.__('place-votes-warning').replace('<quantity>', Session.get('newVote').allocateQuantity),
                    cancel          : TAPi18n.__('not-now'),
                    action          : TAPi18n.__('vote'),
                    displayProfile  : false
                  },
                  function() {
                    var ballot = Modules.client.purgeBallot(Session.get('candidateBallot'));
                    settings = {
                      condition: {
                        tags : Session.get('contract').tags,
                        ballot : ballot
                      },
                      currency: 'VOTES',
                      kind: Session.get('contract').kind,
                      contractId: Session.get('contract')._id
                    };
                    vote(Meteor.user()._id, Session.get('contract')._id, Session.get('newVote').allocateQuantity, settings);
                  }
                );
              }
              break;
          }
        }
      }
    }
});

function disableContractExecution() {
  if (Session.get('emptyBallot')) {
    return true;
  } else if (Session.get('unauthorizedFork')) {
    return true;
  } else if (Session.get('missingTitle')) {
    return true;
  } else if (Session.get('mistypedTitle')) {
    return true;
  } else if (Session.get('duplicateURL')) {
    return true;
  } else if (Session.get('noVotes')) {
    return true;
  } else if (Session.get('draftOptions')) {
    return true;
  } else if (!Session.get('rightToVote')) {
    return true;
  } else {
    if (Session.get('contract').kind == 'VOTE' && Session.get('contract').stage == 'LIVE') {
      if (!Modules.client.ballotReady()) {
        return true;
      }
    }
    if (Session.get('newVote') != undefined) {
      if (Session.get('newVote').mode === 'PENDING' || Session.get('newVote').mode == undefined) {
        return false;
      } else {
        return true;
      }
    } else {
      return false;
    }
  }
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import queryString from 'query-string';
import fetch from 'isomorphic-fetch';

import transitionToParams from '@folio/stripes-components/util/transitionToParams';
import makeQueryFunction from '@folio/stripes-components/util/makeQueryFunction';
import { stripesShape } from '@folio/stripes-core/src/Stripes';
import Notes from '@folio/stripes-smart-components/lib/Notes';
import { SubmissionError } from 'redux-form';
import uuid from 'uuid';

import SearchAndSort from'./lib/SearchAndSort';

import UserForm from './UserForm';
import ViewUser from './ViewUser';

import removeQueryParam from './removeQueryParam';
import contactTypes from './data/contactTypes';
import { toUserAddresses } from './converters/address';
import { getFullName } from './util';
import packageInfo from './package';

const INITIAL_RESULT_COUNT = 30;
const RESULT_COUNT_INCREMENT = 30;

const filterConfig = [
  {
    label: 'Status',
    name: 'active',
    cql: 'active',
    values: [
      { name: 'Active', cql: 'true' },
      { name: 'Inactive', cql: 'false' },
    ],
  },
  {
    label: 'Patron group',
    name: 'pg',
    cql: 'patronGroup',
    values: [], // will be filled in by componentWillUpdate
  },
];

class Users extends React.Component {
  static propTypes = {
    stripes: stripesShape.isRequired,
    resources: PropTypes.shape({
      patronGroups: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      addressTypes: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      users: PropTypes.shape({
        hasLoaded: PropTypes.bool.isRequired,
        other: PropTypes.shape({
          totalRecords: PropTypes.number.isRequired,
        }),
        isPending: PropTypes.bool.isPending,
        successfulMutations: PropTypes.arrayOf(
          PropTypes.shape({
            record: PropTypes.shape({
              id: PropTypes.string.isRequired,
              username: PropTypes.string.isRequired,
            }).isRequired,
          }),
        ),
      }),
      userCount: PropTypes.number,
      notes: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
    }).isRequired,
    history: PropTypes.shape({
      push: PropTypes.func.isRequired,
    }).isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      search: PropTypes.string,
    }).isRequired,
    match: PropTypes.shape({
      path: PropTypes.string.isRequired,
    }).isRequired,
    mutator: PropTypes.shape({
      userCount: PropTypes.shape({
        replace: PropTypes.func,
      }),
      users: PropTypes.shape({
        POST: PropTypes.func,
      }),
    }).isRequired,
    okapi: PropTypes.shape({
      url: PropTypes.string.isRequired,
      tenant: PropTypes.string.isRequired,
      token: PropTypes.string.isRequired,
    }).isRequired,
    onSelectRow: PropTypes.func,
    disableUserCreation: PropTypes.bool,
  };

  static manifest = Object.freeze({
    userCount: { initialValue: INITIAL_RESULT_COUNT },
    users: {
      type: 'okapi',
      records: 'users',
      recordsRequired: '%{userCount}',
      perRequest: RESULT_COUNT_INCREMENT,
      path: 'users',
      GET: {
        params: {
          query: makeQueryFunction(
            'username=*',
            'username="$QUERY*" or personal.firstName="$QUERY*" or personal.lastName="$QUERY*" or personal.email="$QUERY*" or barcode="$QUERY*" or id="$QUERY*" or externalSystemId="$QUERY*"',
            {
              Status: 'active',
              Name: 'personal.lastName personal.firstName',
              'Patron Group': 'patronGroup.group',
              Username: 'username',
              Barcode: 'barcode',
              Email: 'personal.email',
            },
            filterConfig,
          ),
        },
        staticFallback: { params: {} },
      },
    },
    patronGroups: {
      type: 'okapi',
      path: 'groups',
      records: 'usergroups',
    },
    addressTypes: {
      type: 'okapi',
      path: 'addresstypes',
      records: 'addressTypes',
    },
    notes: {
      type: 'okapi',
      path: 'notes',
      records: 'notes',
      clear: false,
      GET: {
        params: {
          query: 'link=:{id}',
        },
      },
    },
  });

  constructor(props) {
    super(props);

    this.state = {};
    this.okapi = props.okapi;

    this.transitionToParams = transitionToParams.bind(this);
    this.connectedViewUser = props.stripes.connect(ViewUser);
    this.connectedNotes = props.stripes.connect(Notes);

    const logger = props.stripes.logger;
    this.log = logger.log.bind(logger);

    this.anchoredRowFormatter = this.anchoredRowFormatter.bind(this);

    this.resultsList = null;
    this.SRStatus = null;

    this.toggleNotes = this.toggleNotes.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const resource = this.props.resources.users;
    if (resource) {
      const sm = nextProps.resources.users.successfulMutations;
      if (sm.length > resource.successfulMutations.length)
        this.onSelectRow(undefined, { id: sm[0].record.id, username: sm[0].record.username });
    }

    if (resource && resource.isPending && !nextProps.resources.users.isPending) {
      this.log('event', 'new search-result');
      const resultAmount = nextProps.resources.users.other.totalRecords;
      this.SRStatus.sendMessage(`Search returned ${resultAmount} result${resultAmount !== 1 ? 's' : ''}`);
    }
  }

  componentWillUpdate() {
    const pg = (this.props.resources.patronGroups || {}).records || [];

    if (pg && pg.length) {
      filterConfig[1].values = pg.map(rec => ({ name: rec.group, cql: rec.id }));
    }
  }

  onClearSearch = () => {
    const path = (_.get(packageInfo, ['stripes', 'home']) ||
                  _.get(packageInfo, ['stripes', 'route']));
    this.setState({
      searchTerm: '',
      sortOrder: 'Name',
      filters: { 'active.Active': true },
    });
    this.log('action', `cleared search: navigating to ${path}`);
    this.props.history.push(path);
  }

  onSort = (e, meta) => {
    const newOrder = meta.alias;
    const oldOrder = this.state.sortOrder || '';

    const orders = oldOrder ? oldOrder.split(',') : [];
    if (orders[0] && newOrder === orders[0].replace(/^-/, '')) {
      orders[0] = `-${orders[0]}`.replace(/^--/, '');
    } else {
      orders.unshift(newOrder);
    }

    const sortOrder = orders.slice(0, 2).join(',');
    this.log('action', `sorted by ${sortOrder}`);
    this.setState({ sortOrder });
    this.transitionToParams({ sort: sortOrder });
  }

  onSelectRow = this.props.onSelectRow ? this.props.onSelectRow : (e, meta) => {
    const userId = meta.id;
    this.log('action', `clicked ${userId}, selected user =`, meta);
    this.setState({ selectedItem: meta });
    this.props.history.push(`/users/view/${userId}${this.props.location.search}`);
  }

  onClickAddNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "add new user"');
    this.transitionToParams({ layer: 'create' });
  }

  onClickCloseNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "close new user"');
    removeQueryParam('layer', this.props.location, this.props.history);
  }

  onChangeSearch = (e) => {
    const query = e.target.value;
    this.props.mutator.userCount.replace(INITIAL_RESULT_COUNT);
    this.setState({ searchTerm: query });
    this.performSearch(query);
  }

  onNeedMore = () => {
    this.props.mutator.userCount.replace(this.props.resources.userCount + RESULT_COUNT_INCREMENT);
  }

  getRowURL(rowData) {
    return `/users/view/${rowData.id}${this.props.location.search}`;
  }

  performSearch = _.debounce((query) => {
    this.log('action', `searched for '${query}'`);
    this.transitionToParams({ query });
  }, 350);

  create = (userdata) => {
    if (userdata.personal.addresses) {
      const addressTypes = (this.props.resources.addressTypes || {}).records || [];
      userdata.personal.addresses = toUserAddresses(userdata.personal.addresses, addressTypes); // eslint-disable-line no-param-reassign
    }
    const creds = Object.assign({}, userdata.creds, { username: userdata.username });
    const user = Object.assign({}, userdata, { id: uuid() });
    if (user.creds) delete user.creds;

    this.postUser(user)
    .then(newUser => this.postCreds(newUser.id, creds))
    .then(userId => this.postPerms(userId))
    .then((userId) => {
      this.onClickCloseNewUser();
      this.onSelectRow(null, { id: userId });
    });
  }

  postUser = user =>
    fetch(`${this.okapi.url}/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify(user),
    }).then((userPostResponse) => {
      if (userPostResponse.status >= 400) {
        throw new SubmissionError('Creating new user failed');
      } else {
        return userPostResponse.json();
      }
    }).then(userJson => userJson);

  postCreds = (userId, creds) => {
    this.log('xhr', `POST credentials for new user '${userId}':`, creds);
    const localCreds = Object.assign({}, creds, creds.password ? {} : { password: '' }, { userId });
    return fetch(`${this.okapi.url}/authn/credentials`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify(localCreds),
    }).then((credsPostResponse) => {
      if (credsPostResponse.status >= 400) {
        throw new SubmissionError('Creating credentials for new user failed');
      } else {
        return userId;
      }
    });
  }

  postPerms = (userId) => {
    const permissions = [];
    this.log('xhr', `POST permissions for new user '${userId}':`, permissions);
    return fetch(`${this.okapi.url}/perms/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify({ userId, permissions }),
    }).then((response) => {
      if (response.status >= 400) {
        throw new SubmissionError('Creating empty permissions for new user failed');
      } else {
        return userId;
      }
    });
  }

  collapseDetails = () => {
    this.setState({
      selectedItem: {},
    });
    this.props.history.push(`${this.props.match.path}${this.props.location.search}`);
  }

  toggleNotes() {
    this.setState((curState) => {
      const show = !curState.showNotesPane;
      return {
        showNotesPane: show,
      };
    });
  }

  // custom row formatter to wrap rows in anchor tags.
  anchoredRowFormatter(
    { rowIndex,
      rowClass,
      rowData,
      cells,
      rowProps,
      labelStrings,
    },
  ) {
    return (
      <a
        href={this.getRowURL(rowData)} key={`row-${rowIndex}`}
        aria-label={labelStrings && labelStrings.join('...')}
        role="listitem"
        className={rowClass}
        {...rowProps}
      >
        {cells}
      </a>
    );
  }

  render() {
    const urlQuery = queryString.parse(this.props.location.search || '');
    return (<SearchAndSort
      stripes={this.props.stripes}
      parentResources={this.props.resources}
      urlQuery={urlQuery}
      path={this.props.location.pathname}
      filterConfig={filterConfig}
    />);
  }
}

export default Users;
<commit_msg>Rolling<commit_after>import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import queryString from 'query-string';
import fetch from 'isomorphic-fetch';

import transitionToParams from '@folio/stripes-components/util/transitionToParams';
import makeQueryFunction from '@folio/stripes-components/util/makeQueryFunction';
import { stripesShape } from '@folio/stripes-core/src/Stripes';
import Notes from '@folio/stripes-smart-components/lib/Notes';
import { SubmissionError } from 'redux-form';
import uuid from 'uuid';

import SearchAndSort from'./lib/SearchAndSort';

import removeQueryParam from './removeQueryParam';
import { toUserAddresses } from './converters/address';
import { getFullName } from './util';
import packageInfo from './package';

const INITIAL_RESULT_COUNT = 30;
const RESULT_COUNT_INCREMENT = 30;

const filterConfig = [
  {
    label: 'Status',
    name: 'active',
    cql: 'active',
    values: [
      { name: 'Active', cql: 'true' },
      { name: 'Inactive', cql: 'false' },
    ],
  },
  {
    label: 'Patron group',
    name: 'pg',
    cql: 'patronGroup',
    values: [], // will be filled in by componentWillUpdate
  },
];

class Users extends React.Component {
  static propTypes = {
    stripes: stripesShape.isRequired,
    resources: PropTypes.shape({
      patronGroups: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      addressTypes: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
      users: PropTypes.shape({
        hasLoaded: PropTypes.bool.isRequired,
        other: PropTypes.shape({
          totalRecords: PropTypes.number.isRequired,
        }),
        isPending: PropTypes.bool.isPending,
        successfulMutations: PropTypes.arrayOf(
          PropTypes.shape({
            record: PropTypes.shape({
              id: PropTypes.string.isRequired,
              username: PropTypes.string.isRequired,
            }).isRequired,
          }),
        ),
      }),
      userCount: PropTypes.number,
      notes: PropTypes.shape({
        records: PropTypes.arrayOf(PropTypes.object),
      }),
    }).isRequired,
    history: PropTypes.shape({
      push: PropTypes.func.isRequired,
    }).isRequired,
    location: PropTypes.shape({
      pathname: PropTypes.string.isRequired,
      search: PropTypes.string,
    }).isRequired,
    match: PropTypes.shape({
      path: PropTypes.string.isRequired,
    }).isRequired,
    mutator: PropTypes.shape({
      userCount: PropTypes.shape({
        replace: PropTypes.func,
      }),
      users: PropTypes.shape({
        POST: PropTypes.func,
      }),
    }).isRequired,
    okapi: PropTypes.shape({
      url: PropTypes.string.isRequired,
      tenant: PropTypes.string.isRequired,
      token: PropTypes.string.isRequired,
    }).isRequired,
    onSelectRow: PropTypes.func,
    disableUserCreation: PropTypes.bool,
  };

  static manifest = Object.freeze({
    userCount: { initialValue: INITIAL_RESULT_COUNT },
    users: {
      type: 'okapi',
      records: 'users',
      recordsRequired: '%{userCount}',
      perRequest: RESULT_COUNT_INCREMENT,
      path: 'users',
      GET: {
        params: {
          query: makeQueryFunction(
            'username=*',
            'username="$QUERY*" or personal.firstName="$QUERY*" or personal.lastName="$QUERY*" or personal.email="$QUERY*" or barcode="$QUERY*" or id="$QUERY*" or externalSystemId="$QUERY*"',
            {
              Status: 'active',
              Name: 'personal.lastName personal.firstName',
              'Patron Group': 'patronGroup.group',
              Username: 'username',
              Barcode: 'barcode',
              Email: 'personal.email',
            },
            filterConfig,
          ),
        },
        staticFallback: { params: {} },
      },
    },
    patronGroups: {
      type: 'okapi',
      path: 'groups',
      records: 'usergroups',
    },
    addressTypes: {
      type: 'okapi',
      path: 'addresstypes',
      records: 'addressTypes',
    },
    notes: {
      type: 'okapi',
      path: 'notes',
      records: 'notes',
      clear: false,
      GET: {
        params: {
          query: 'link=:{id}',
        },
      },
    },
  });

  constructor(props) {
    super(props);

    this.state = {};
    this.okapi = props.okapi;

    this.transitionToParams = transitionToParams.bind(this);
    this.connectedNotes = props.stripes.connect(Notes);

    const logger = props.stripes.logger;
    this.log = logger.log.bind(logger);

    this.anchoredRowFormatter = this.anchoredRowFormatter.bind(this);

    this.resultsList = null;
    this.SRStatus = null;

    this.toggleNotes = this.toggleNotes.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const resource = this.props.resources.users;
    if (resource) {
      const sm = nextProps.resources.users.successfulMutations;
      if (sm.length > resource.successfulMutations.length)
        this.onSelectRow(undefined, { id: sm[0].record.id, username: sm[0].record.username });
    }

    if (resource && resource.isPending && !nextProps.resources.users.isPending) {
      this.log('event', 'new search-result');
      const resultAmount = nextProps.resources.users.other.totalRecords;
      this.SRStatus.sendMessage(`Search returned ${resultAmount} result${resultAmount !== 1 ? 's' : ''}`);
    }
  }

  componentWillUpdate() {
    const pg = (this.props.resources.patronGroups || {}).records || [];

    if (pg && pg.length) {
      filterConfig[1].values = pg.map(rec => ({ name: rec.group, cql: rec.id }));
    }
  }

  onClearSearch = () => {
    const path = (_.get(packageInfo, ['stripes', 'home']) ||
                  _.get(packageInfo, ['stripes', 'route']));
    this.setState({
      searchTerm: '',
      sortOrder: 'Name',
      filters: { 'active.Active': true },
    });
    this.log('action', `cleared search: navigating to ${path}`);
    this.props.history.push(path);
  }

  onSort = (e, meta) => {
    const newOrder = meta.alias;
    const oldOrder = this.state.sortOrder || '';

    const orders = oldOrder ? oldOrder.split(',') : [];
    if (orders[0] && newOrder === orders[0].replace(/^-/, '')) {
      orders[0] = `-${orders[0]}`.replace(/^--/, '');
    } else {
      orders.unshift(newOrder);
    }

    const sortOrder = orders.slice(0, 2).join(',');
    this.log('action', `sorted by ${sortOrder}`);
    this.setState({ sortOrder });
    this.transitionToParams({ sort: sortOrder });
  }

  onSelectRow = this.props.onSelectRow ? this.props.onSelectRow : (e, meta) => {
    const userId = meta.id;
    this.log('action', `clicked ${userId}, selected user =`, meta);
    this.setState({ selectedItem: meta });
    this.props.history.push(`/users/view/${userId}${this.props.location.search}`);
  }

  onClickAddNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "add new user"');
    this.transitionToParams({ layer: 'create' });
  }

  onClickCloseNewUser = (e) => {
    if (e) e.preventDefault();
    this.log('action', 'clicked "close new user"');
    removeQueryParam('layer', this.props.location, this.props.history);
  }

  onChangeSearch = (e) => {
    const query = e.target.value;
    this.props.mutator.userCount.replace(INITIAL_RESULT_COUNT);
    this.setState({ searchTerm: query });
    this.performSearch(query);
  }

  onNeedMore = () => {
    this.props.mutator.userCount.replace(this.props.resources.userCount + RESULT_COUNT_INCREMENT);
  }

  getRowURL(rowData) {
    return `/users/view/${rowData.id}${this.props.location.search}`;
  }

  performSearch = _.debounce((query) => {
    this.log('action', `searched for '${query}'`);
    this.transitionToParams({ query });
  }, 350);

  create = (userdata) => {
    if (userdata.personal.addresses) {
      const addressTypes = (this.props.resources.addressTypes || {}).records || [];
      userdata.personal.addresses = toUserAddresses(userdata.personal.addresses, addressTypes); // eslint-disable-line no-param-reassign
    }
    const creds = Object.assign({}, userdata.creds, { username: userdata.username });
    const user = Object.assign({}, userdata, { id: uuid() });
    if (user.creds) delete user.creds;

    this.postUser(user)
    .then(newUser => this.postCreds(newUser.id, creds))
    .then(userId => this.postPerms(userId))
    .then((userId) => {
      this.onClickCloseNewUser();
      this.onSelectRow(null, { id: userId });
    });
  }

  postUser = user =>
    fetch(`${this.okapi.url}/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify(user),
    }).then((userPostResponse) => {
      if (userPostResponse.status >= 400) {
        throw new SubmissionError('Creating new user failed');
      } else {
        return userPostResponse.json();
      }
    }).then(userJson => userJson);

  postCreds = (userId, creds) => {
    this.log('xhr', `POST credentials for new user '${userId}':`, creds);
    const localCreds = Object.assign({}, creds, creds.password ? {} : { password: '' }, { userId });
    return fetch(`${this.okapi.url}/authn/credentials`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify(localCreds),
    }).then((credsPostResponse) => {
      if (credsPostResponse.status >= 400) {
        throw new SubmissionError('Creating credentials for new user failed');
      } else {
        return userId;
      }
    });
  }

  postPerms = (userId) => {
    const permissions = [];
    this.log('xhr', `POST permissions for new user '${userId}':`, permissions);
    return fetch(`${this.okapi.url}/perms/users`, {
      method: 'POST',
      headers: Object.assign({}, { 'X-Okapi-Tenant': this.okapi.tenant, 'X-Okapi-Token': this.okapi.token, 'Content-Type': 'application/json' }),
      body: JSON.stringify({ userId, permissions }),
    }).then((response) => {
      if (response.status >= 400) {
        throw new SubmissionError('Creating empty permissions for new user failed');
      } else {
        return userId;
      }
    });
  }

  collapseDetails = () => {
    this.setState({
      selectedItem: {},
    });
    this.props.history.push(`${this.props.match.path}${this.props.location.search}`);
  }

  toggleNotes() {
    this.setState((curState) => {
      const show = !curState.showNotesPane;
      return {
        showNotesPane: show,
      };
    });
  }

  // custom row formatter to wrap rows in anchor tags.
  anchoredRowFormatter(
    { rowIndex,
      rowClass,
      rowData,
      cells,
      rowProps,
      labelStrings,
    },
  ) {
    return (
      <a
        href={this.getRowURL(rowData)} key={`row-${rowIndex}`}
        aria-label={labelStrings && labelStrings.join('...')}
        role="listitem"
        className={rowClass}
        {...rowProps}
      >
        {cells}
      </a>
    );
  }

  render() {
    const urlQuery = queryString.parse(this.props.location.search || '');
    return (<SearchAndSort
      stripes={this.props.stripes}
      parentResources={this.props.resources}
      urlQuery={urlQuery}
      path={this.props.location.pathname}
      filterConfig={filterConfig}
    />);
  }
}

export default Users;
<|endoftext|>