<commit_before>import React from 'react'
import Page from 'components/Page'
import {Modal, Alert, Button} from 'react-bootstrap'
import autobind from 'autobind-decorator'
import moment from 'moment'

import Fieldset from 'components/Fieldset'
import Breadcrumbs from 'components/Breadcrumbs'
import ReportCollection from 'components/ReportCollection'
import CalendarButton from 'components/CalendarButton'
import Form from 'components/Form'
import History from 'components/History'

import API from 'api'

var d3 = null/* required later */

const graphCss = {
	width: '100%',
	height: '300px'
}

const barColors = {
	cancelled: '#e39394',
	verified: '#9CBDA4',
	unverified: '#F5D98C',
	incoming: '#DA9795',
}

const calendarButtonCss = {
	marginLeft: '20px',
	marginTop: '-8px',
}

export default class RollupShow extends Page {
	static propTypes = {
		date: React.PropTypes.object,
	}

	get dateStr() { return this.state.date.format('DD MMM YYYY') }
	get dateLongStr() { return this.state.date.format('DD MMMM YYYY') }
	get rollupStart() { return moment(this.state.date).subtract(1, 'days').startOf('day').hour(19) } //7pm yesterday
	get rollupEnd() { return moment(this.state.date).endOf('day').hour(18) } // 6:59:59pm today.

	constructor(props) {
		super(props)

		this.state = {
			date: moment(+props.date || +props.location.query.date || undefined),
			reports: {list: []},
			reportsPageNum: 0,
			graphData: {},
			showEmailModal: false,
			email: {},
		}
	}

	componentDidMount() {
		super.componentDidMount()

		if (d3) {
			return
		}

		require.ensure([], () => {
			d3 = require('d3')
			this.forceUpdate()
		})
	}

	fetchData(props) {
		const rollupQuery = {
			state: ['RELEASED'], //Specifically excluding cancelled engagements.
			releasedAtStart: this.rollupStart.valueOf(),
			releasedAtEnd: this.rollupEnd.valueOf(),
			engagementDateStart: moment(this.rollupStart).subtract(14, 'days').valueOf(),
			sortBy: "ENGAGEMENT_DATE",
			sortOrder: "DESC",
			pageNum: this.state.reportsPageNum,
		}
		API.query(/* GraphQL */`
			reportList(f:search, query:$rollupQuery) {
				pageNum, pageSize, totalCount, list {
					id, state, intent, engagementDate, intent, keyOutcomes, nextSteps, cancelledReason, atmosphere, atmosphereDetails
					author { id, name }
					location { id, name, lat, lng }
					poams { id, longName }
					comments { id }
					primaryAdvisor {
						id, name, rank
						position { organization { id, shortName, longName }}
					}
					primaryPrincipal {
						id, name, rank
						position { organization { id, shortName, longName }}
					}
					advisorOrg {
						id, shortName
						parentOrg { id, shortName }
					}
					principalOrg {
						id, shortName
						parentOrg { id, shortName }
					}
				}
			}
		`, {rollupQuery}, '($rollupQuery: ReportSearchQuery)')
		.then(data => {
			this.setState({reports: data.reportList})
		})

		API.fetch(`/api/reports/rollupGraph?startDate=${this.rollupStart.valueOf()}&endDate=${this.rollupEnd.valueOf()}`
		).then(data => {
			this.setState({graphData: data})
		})
	}

	componentDidUpdate() {
		let graphData = this.state.graphData
		if (!graphData || !d3) {
			return
		}

		this.graph.innerHTML = ''

		// Set up the data
		const step1 = d3.nest()
			.key((entry) => (entry.org && entry.org.shortName) || "Unknown")
			.rollup(entry => entry[0])
			.entries(graphData)

		var svg = d3.select(this.graph),
			margin = {top: 20, right: 20, bottom: 20, left: 20},
			width = this.graph.clientWidth - margin.left - margin.right,
			height = this.graph.clientHeight - margin.top - margin.bottom,
			padding = 22,
			g = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

		var x = d3.scaleBand().rangeRound([0,width])

		var y = d3.scaleLinear()
			.rangeRound([height, 0])

		x.domain(step1.map(d => d.key))
		y.domain([0,d3.max(step1.map(d => d.value.released + d.value.cancelled))])
		d3.line()
			.x(function(d,i) { return x(d.key) })
			.y(function(d,i) { return y(d.value.released) })

		var xAxis = d3.axisBottom()
			.scale(x)
		var maxValue = d3.max(y.domain())
		var yAxis = d3.axisLeft()
			.scale(y).ticks(Math.min(maxValue+1,10))

		g.append('g')
			.attr('transform', 'translate(0,' + height + ')')
			.attr('fill', '#00f')
			.call(xAxis)

		g.append('g')
			.attr('fill', '#400')
			.call(yAxis)
			.append('text')
			.attr('fill', '#000')
			.attr('transform', 'rotate(-90)')
			.attr('y', 6)
			.attr('dy', '0.71em')
			.style('text-anchor', 'end')
			.text('# of Reports')

		function createBarPart(color, getYVal, getHeight) {
			g.selectAll('.bar')
				.data(step1)
				.enter()
				.append('rect')
				.attr('class', 'line')
				.attr('width', width / step1.length - padding)
				.attr('x', d => x(d.key) + (width / (step1.length + padding)))
				.attr('height', d => height - y(getHeight(d.value) || 0))
				.attr('y', d => y(getYVal(d.value) || 0))
				.attr('fill', () => color)
		}

		createBarPart(barColors.verified, val => val.released, val => val.released)
		createBarPart(barColors.cancelled, val => val.released + val.cancelled, val => val.cancelled)
	}

	render() {
		return (

				<Breadcrumbs items={[[`Rollup for ${this.dateStr}`, 'rollup/']]} />

				<Fieldset title={

						Daily Rollup - {this.dateLongStr}
						<CalendarButton onChange={this.changeRollupDate} value={this.state.date.toISOString()} style={calendarButtonCss} />

				} action={

						<Button href={`/api/reports/rollup?startDate=${this.rollupStart.valueOf()}&endDate=${this.rollupEnd.valueOf()}`}>Print</Button>
						<Button onClick={this.toggleEmailModal} bsStyle="primary">Email rollup</Button>

				}>
					<svg ref={el => this.graph = el} style={graphCss} />
				</Fieldset>

				<Fieldset title="Reports">
					<ReportCollection paginatedReports={this.state.reports} goToPage={this.goToReportsPage} />
				</Fieldset>

				{this.renderEmailModal()}

		)
	}

	@autobind
	goToReportsPage(newPageNum) {
		this.state.reportsPageNum = newPageNum
		this.loadData()
	}

	@autobind
	changeRollupDate(newDate) {
		let date = moment(newDate)
		History.replace({pathname: 'rollup', query: {date: date.valueOf()}})

		this.setState({date: date}, () => {
			this.loadData()
		})
	}

	@autobind
	renderEmailModal() {
		let email = this.state.email
		return <Modal show={this.state.showEmailModal} onHide={this.toggleEmailModal}>
			<Form formFor={email} onChange={this.onChange} submitText={false} >
				<Modal.Header closeButton>
					<Modal.Title>Email rollup</Modal.Title>
				</Modal.Header>

				<Modal.Body>
					{email.errors &&
						<Alert bsStyle="danger">{email.errors}</Alert>
					}

					<Form.Field id="to" />
					<Form.Field componentClass="textarea" id="comment" />
				</Modal.Body>

				<Modal.Footer>
					<Button bsStyle="primary" onClick={this.emailRollup}>Send email</Button>
				</Modal.Footer>
			</Form>
		</Modal>
	}

	@autobind
	toggleEmailModal() {
		this.setState({showEmailModal: !this.state.showEmailModal})
	}

	@autobind
	emailRollup() {
		let email = this.state.email
		if (!email.to) {
			email.errors = 'You must select a person to send this to'
			this.setState({email})
			return
		}

		email = {
			toAddresses: email.to.replace(/\s/g, '').split(/[,;]/),
			comment: email.comment
		}

		API.send(`/api/reports/rollup/email?startDate=${this.rollupStart.valueOf()}&endDate=${this.rollupEnd.valueOf()}`, email).then (() =>
			this.setState({
				success: 'Email successfully sent',
				showEmailModal: false,
				email: {}
			})
		)
	}
}
<commit_msg>Second attempt at clearing rollup graph between renders<commit_after>import React from 'react'
import Page from 'components/Page'
import {Modal, Alert, Button} from 'react-bootstrap'
import autobind from 'autobind-decorator'
import moment from 'moment'

import Fieldset from 'components/Fieldset'
import Breadcrumbs from 'components/Breadcrumbs'
import ReportCollection from 'components/ReportCollection'
import CalendarButton from 'components/CalendarButton'
import Form from 'components/Form'
import History from 'components/History'

import API from 'api'

var d3 = null/* required later */

const graphCss = {
	width: '100%',
	height: '300px'
}

const barColors = {
	cancelled: '#e39394',
	verified: '#9CBDA4',
	unverified: '#F5D98C',
	incoming: '#DA9795',
}

const calendarButtonCss = {
	marginLeft: '20px',
	marginTop: '-8px',
}

export default class RollupShow extends Page {
	static propTypes = {
		date: React.PropTypes.object,
	}

	get dateStr() { return this.state.date.format('DD MMM YYYY') }
	get dateLongStr() { return this.state.date.format('DD MMMM YYYY') }
	get rollupStart() { return moment(this.state.date).subtract(1, 'days').startOf('day').hour(19) } //7pm yesterday
	get rollupEnd() { return moment(this.state.date).endOf('day').hour(18) } // 6:59:59pm today.

	constructor(props) {
		super(props)

		this.state = {
			date: moment(+props.date || +props.location.query.date || undefined),
			reports: {list: []},
			reportsPageNum: 0,
			graphData: {},
			showEmailModal: false,
			email: {},
		}
	}

	componentDidMount() {
		super.componentDidMount()

		if (d3) {
			return
		}

		require.ensure([], () => {
			d3 = require('d3')
			this.forceUpdate()
		})
	}

	fetchData(props) {
		const rollupQuery = {
			state: ['RELEASED'], //Specifically excluding cancelled engagements.
			releasedAtStart: this.rollupStart.valueOf(),
			releasedAtEnd: this.rollupEnd.valueOf(),
			engagementDateStart: moment(this.rollupStart).subtract(14, 'days').valueOf(),
			sortBy: "ENGAGEMENT_DATE",
			sortOrder: "DESC",
			pageNum: this.state.reportsPageNum,
		}
		API.query(/* GraphQL */`
			reportList(f:search, query:$rollupQuery) {
				pageNum, pageSize, totalCount, list {
					id, state, intent, engagementDate, intent, keyOutcomes, nextSteps, cancelledReason, atmosphere, atmosphereDetails
					author { id, name }
					location { id, name, lat, lng }
					poams { id, longName }
					comments { id }
					primaryAdvisor {
						id, name, rank
						position { organization { id, shortName, longName }}
					}
					primaryPrincipal {
						id, name, rank
						position { organization { id, shortName, longName }}
					}
					advisorOrg {
						id, shortName
						parentOrg { id, shortName }
					}
					principalOrg {
						id, shortName
						parentOrg { id, shortName }
					}
				}
			}
		`, {rollupQuery}, '($rollupQuery: ReportSearchQuery)')
		.then(data => {
			this.setState({reports: data.reportList})
		})

		API.fetch(`/api/reports/rollupGraph?startDate=${this.rollupStart.valueOf()}&endDate=${this.rollupEnd.valueOf()}`
		).then(data => {
			this.setState({graphData: data})
		})
	}

	componentDidUpdate() {
		let graphData = this.state.graphData
		if (!graphData || !d3) {
			return
		}

		// Set up the data
		const step1 = d3.nest()
			.key((entry) => (entry.org && entry.org.shortName) || "Unknown")
			.rollup(entry => entry[0])
			.entries(graphData)

		var svg = d3.select(this.graph)
		svg.selectAll('*').remove()

		var	margin = {top: 20, right: 20, bottom: 20, left: 20},
			width = this.graph.clientWidth - margin.left - margin.right,
			height = this.graph.clientHeight - margin.top - margin.bottom,
			padding = 22,
			g = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

		var x = d3.scaleBand().rangeRound([0,width])

		var y = d3.scaleLinear()
			.rangeRound([height, 0])

		x.domain(step1.map(d => d.key))
		y.domain([0,d3.max(step1.map(d => d.value.released + d.value.cancelled))])
		d3.line()
			.x(function(d,i) { return x(d.key) })
			.y(function(d,i) { return y(d.value.released) })

		var xAxis = d3.axisBottom()
			.scale(x)
		var maxValue = d3.max(y.domain())
		var yAxis = d3.axisLeft()
			.scale(y).ticks(Math.min(maxValue+1,10))

		g.append('g')
			.attr('transform', 'translate(0,' + height + ')')
			.attr('fill', '#00f')
			.call(xAxis)

		g.append('g')
			.attr('fill', '#400')
			.call(yAxis)
			.append('text')
			.attr('fill', '#000')
			.attr('transform', 'rotate(-90)')
			.attr('y', 6)
			.attr('dy', '0.71em')
			.style('text-anchor', 'end')
			.text('# of Reports')

		function createBarPart(color, getYVal, getHeight) {
			g.selectAll('.bar')
				.data(step1)
				.enter()
				.append('rect')
				.attr('class', 'line')
				.attr('width', width / step1.length - padding)
				.attr('x', d => x(d.key) + (width / (step1.length + padding)))
				.attr('height', d => height - y(getHeight(d.value) || 0))
				.attr('y', d => y(getYVal(d.value) || 0))
				.attr('fill', () => color)
		}

		createBarPart(barColors.verified, val => val.released, val => val.released)
		createBarPart(barColors.cancelled, val => val.released + val.cancelled, val => val.cancelled)
	}

	render() {
		return (

				<Breadcrumbs items={[[`Rollup for ${this.dateStr}`, 'rollup/']]} />

				<Fieldset title={

						Daily Rollup - {this.dateLongStr}
						<CalendarButton onChange={this.changeRollupDate} value={this.state.date.toISOString()} style={calendarButtonCss} />

				} action={

						<Button href={`/api/reports/rollup?startDate=${this.rollupStart.valueOf()}&endDate=${this.rollupEnd.valueOf()}`}>Print</Button>
						<Button onClick={this.toggleEmailModal} bsStyle="primary">Email rollup</Button>

				}>
					<svg ref={el => this.graph = el} style={graphCss} />
				</Fieldset>

				<Fieldset title="Reports">
					<ReportCollection paginatedReports={this.state.reports} goToPage={this.goToReportsPage} />
				</Fieldset>

				{this.renderEmailModal()}

		)
	}

	@autobind
	goToReportsPage(newPageNum) {
		this.state.reportsPageNum = newPageNum
		this.loadData()
	}

	@autobind
	changeRollupDate(newDate) {
		let date = moment(newDate)
		History.replace({pathname: 'rollup', query: {date: date.valueOf()}})

		this.setState({date: date}, () => {
			this.loadData()
		})
	}

	@autobind
	renderEmailModal() {
		let email = this.state.email
		return <Modal show={this.state.showEmailModal} onHide={this.toggleEmailModal}>
			<Form formFor={email} onChange={this.onChange} submitText={false} >
				<Modal.Header closeButton>
					<Modal.Title>Email rollup</Modal.Title>
				</Modal.Header>

				<Modal.Body>
					{email.errors &&
						<Alert bsStyle="danger">{email.errors}</Alert>
					}

					<Form.Field id="to" />
					<Form.Field componentClass="textarea" id="comment" />
				</Modal.Body>

				<Modal.Footer>
					<Button bsStyle="primary" onClick={this.emailRollup}>Send email</Button>
				</Modal.Footer>
			</Form>
		</Modal>
	}

	@autobind
	toggleEmailModal() {
		this.setState({showEmailModal: !this.state.showEmailModal})
	}

	@autobind
	emailRollup() {
		let email = this.state.email
		if (!email.to) {
			email.errors = 'You must select a person to send this to'
			this.setState({email})
			return
		}

		email = {
			toAddresses: email.to.replace(/\s/g, '').split(/[,;]/),
			comment: email.comment
		}

		API.send(`/api/reports/rollup/email?startDate=${this.rollupStart.valueOf()}&endDate=${this.rollupEnd.valueOf()}`, email).then (() =>
			this.setState({
				success: 'Email successfully sent',
				showEmailModal: false,
				email: {}
			})
		)
	}
}
<|endoftext|>


<commit_before>Template.lottoAdd.created = function() {
  Session.set('lottoAddErrors', {});
};
Template.lottoAdd.rendered = function() {
  this.find('input[name=date]').valueAsDate = new Date();
};

Template.lottoAdd.helpers({
  errorMessage: function(field) {
    return Session.get('lottoAddErrors')[field];
  },
  errorClass: function (field) {
    return !!Session.get('lottoAddErrors')[field] ? 'has-error' : '';
  }
});

Template.lottoAdd.events({
  'submit form': function(e) {
    e.preventDefault();

    var lotto = {
      date: e.target.date.valueAsDate
    };

    if (!lotto.date) {
      var errors = {};
      errors.date = "Please select a date";
      return Session.set('lottoAddErrors', errors);
    }

    Meteor.call('lottoInsert', lotto, function(error, result) {
      if(error) {
        return throwError(error.reason);
      }
      if(result.lottoExists) {
        throwError('Lotto for this date already exists');
      }
      Router.go('lottoPage', {_id: result._id});
    });
  }
});<commit_msg>Using moment.js to parse the date.<commit_after>Template.lottoAdd.created = function() {
  Session.set('lottoAddErrors', {});
};
Template.lottoAdd.rendered = function() {
  this.find('input[name=date]').valueAsDate = new Date();
};

Template.lottoAdd.helpers({
  errorMessage: function(field) {
    return Session.get('lottoAddErrors')[field];
  },
  errorClass: function (field) {
    return !!Session.get('lottoAddErrors')[field] ? 'has-error' : '';
  }
});

Template.lottoAdd.events({
  'submit form': function(e) {
    e.preventDefault();

    var lotto = {
      date: moment(e.target.date.value).toDate()
    };

    if (!lotto.date) {
      var errors = {};
      errors.date = "Please select a date";
      return Session.set('lottoAddErrors', errors);
    }

    Meteor.call('lottoInsert', lotto, function(error, result) {
      if(error) {
        return throwError(error.reason);
      }
      if(result.lottoExists) {
        throwError('Lotto for this date already exists');
      }
      Router.go('lottoPage', {_id: result._id});
    });
  }
});<|endoftext|>


<commit_before>'use strict'
var Hub = require('../../lib')
var hub = global.hub = new Hub()

require('./style.less')

hub.set({
  adapter: {
    inject: require('../../lib/protocol/websocket'),
    websocket: 'ws://localhost:3031'
  },
  shows: {
    // 977: {

    // }
  }
})

var Element = require('vigour-element')
var Prop = require('vigour-element/lib/property')
var app = require('vigour-element/lib/app')

var A = new Element({
  $: true,
  text: { $: 'title' },
  button: {
    type: 'button',
    text: 'yo button',
    on: {
      click () {
        if (hub.blurf.origin === hub.blarf) {
          hub.blurf.val = false //.remove()
        } else {
          hub.set({'blarf': {
            title: 'its a blurf!'
          }})
          hub.blurf.val = hub.blarf
        }
      }
    }
  }
}).Constructor

var PAGE = new Element({
  $: 'shows', // so nested on collection listeners are rly fucked
  shows: {
    $collection: true,
    ChildConstructor: new Element({
      css: 'show',
      text: { $: 'title' },
      // image: {
      //   // url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7)
      //   $prepend: 'data:image/jpg;base64,',
      //   $: 'thumb'
      //   // val: 'R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'
      // },
      seasons: {
        $collection: 'seasons',
        ChildConstructor: new Element({
          css: 'season',
          text: { $prepend: 'season: ', $: 'number' },
          episodes: {
            $collection: 'episodes',
            ChildConstructor: new Element({
              css: 'episode',
              text: { $: 'number' },
              // img: {
              //   type: 'img',
              //   src: {
              //     $: 'img',
              //     $transform (val) {
              //       if (typeof val === 'string') {
              //         var ret = `https://imgmtvplay-a.akamaihd.net/image/20/20?url=http://images.mtvnn.com/${val}/original`
              //         return ret
              //       }
              //       return
              //     }
              //   }
              // }
            })
          }
        })
      }
    })
  }
}).Constructor

app.set({
  c: new PAGE(hub),
  // b: new A(hub.get('blurf', {})),
  marcus: {
    text: 'lalala'
  }
})

hub.on(() => {
  console.log('incoming!')
  // console.time(1)
  // if (typeof window !== void 0) {
    // window.requestAnimationFrame(function () {
      // console.timeEnd(1)
    // })
  // }
})

module.exports = app
<commit_msg>update<commit_after>'use strict'
var Hub = require('../../lib')
var hub = global.hub = new Hub()

require('./style.less')

hub.set({
  adapter: {
    inject: require('../../lib/protocol/websocket'),
    websocket: 'ws://localhost:50500'
  },
  shows: {
    // 977: {

    // }
  }
})

var Element = require('vigour-element')
var Prop = require('vigour-element/lib/property')
var app = require('vigour-element/lib/app')

var A = new Element({
  $: true,
  text: { $: 'title' },
  button: {
    type: 'button',
    text: 'yo button',
    on: {
      click () {
        if (hub.blurf.origin === hub.blarf) {
          hub.blurf.val = false //.remove()
        } else {
          hub.set({'blarf': {
            title: 'its a blurf!'
          }})
          hub.blurf.val = hub.blarf
        }
      }
    }
  }
}).Constructor

var PAGE = new Element({
  $: 'shows', // so nested on collection listeners are rly fucked
  shows: {
    $collection: true,
    ChildConstructor: new Element({
      css: 'show',
      text: { $: 'title' },
      // image: {
      //   // url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7)
      //   $prepend: 'data:image/jpg;base64,',
      //   $: 'thumb'
      //   // val: 'R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'
      // },
      seasons: {
        $collection: 'seasons',
        ChildConstructor: new Element({
          css: 'season',
          text: { $prepend: 'season: ', $: 'number' },
          episodes: {
            $collection: 'episodes',
            ChildConstructor: new Element({
              css: 'episode',
              text: { $: 'number' },
              // img: {
              //   type: 'img',
              //   src: {
              //     $: 'img',
              //     $transform (val) {
              //       if (typeof val === 'string') {
              //         var ret = `https://imgmtvplay-a.akamaihd.net/image/20/20?url=http://images.mtvnn.com/${val}/original`
              //         return ret
              //       }
              //       return
              //     }
              //   }
              // }
            })
          }
        })
      }
    })
  }
}).Constructor

app.set({
  c: new PAGE(hub),
  // b: new A(hub.get('blurf', {})),
  marcus: {
    text: 'lalala'
  }
})

hub.on(() => {
  console.log('incoming!')
  // console.time(1)
  // if (typeof window !== void 0) {
    // window.requestAnimationFrame(function () {
      // console.timeEnd(1)
    // })
  // }
})

module.exports = app
<|endoftext|>


<commit_before>import { useState } from 'react';
import gql from 'graphql-tag';
import querystring from 'querystring';
import { t, jt } from 'ttag';
import Router, { useRouter } from 'next/router';
import getConfig from 'next/config';
import url from 'url';
import { useQuery } from '@apollo/react-hooks';

import Grid from '@material-ui/core/Grid';
import Box from '@material-ui/core/Box';
import Fab from '@material-ui/core/Fab';
import Modal from '@material-ui/core/Modal';
import Backdrop from '@material-ui/core/Backdrop';
import Fade from '@material-ui/core/Fade';
import Typography from '@material-ui/core/Typography';

import FilterListIcon from '@material-ui/icons/FilterList';
import CloseIcon from '@material-ui/icons/Close';
import { makeStyles } from '@material-ui/core/styles';

import { ellipsis } from 'lib/text';
import ArticleItem from 'components/ArticleItem';
import LoadMore from 'components/LoadMore';
import FeedDisplay from 'components/FeedDisplay';
import Filters, { Filter } from 'components/Filters';
import TimeRange from 'components/TimeRange';
import SortInput from 'components/SortInput';

const STATUSES = ['unsolved', 'solved', 'all'];
const DEFAULT_REPLY_REQUEST_COUNT = 1;
const MAX_KEYWORD_LENGTH = 100;

const {
  publicRuntimeConfig: { PUBLIC_URL },
} = getConfig();

const LIST_ARTICLES = gql`
  query ListArticles(
    $filter: ListArticleFilter
    $orderBy: [ListArticleOrderBy]
    $after: String
  ) {
    ListArticles(filter: $filter, orderBy: $orderBy, after: $after, first: 10) {
      edges {
        node {
          ...ArticleItem
        }
        cursor
      }
    }
  }
  ${ArticleItem.fragments.ArticleItem}
`;

const LIST_STAT = gql`
  query ListArticlesStat(
    $filter: ListArticleFilter
    $orderBy: [ListArticleOrderBy]
  ) {
    ListArticles(filter: $filter, orderBy: $orderBy, first: 25) {
      pageInfo {
        firstCursor
        lastCursor
      }
      totalCount
    }
  }
`;

const LIST_CATEGORIES = gql`
  query ListCategories {
    ListCategories(first: 25) {
      totalCount
      edges {
        node {
          id
          title
        }
      }
    }
  }
`;

const useStyles = makeStyles(theme => ({
  filters: {
    padding: '12px 0',
  },
  articleList: {
    padding: 0,
  },
  openFilter: {
    position: 'fixed',
    left: 22,
    bottom: 22,
    backgroundColor: theme.palette.secondary[500],
    color: theme.palette.common.white,
    [theme.breakpoints.up('md')]: {
      display: 'none',
    },
  },
  filtersModal: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10,
  },
  closeIcon: {
    position: 'absolute',
    right: 12,
    top: 20,
    color: theme.palette.secondary[100],
  },
}));

/**
 * @param {object} urlQuery - URL query object
 * @returns {object} ListArticleFilter
 */
function urlQuery2Filter(
  {
    filter,
    q,
    categoryIds = '',
    start,
    end,
    replyRequestCount = DEFAULT_REPLY_REQUEST_COUNT,
    searchUserByArticleId,
  } = {},
  defaultStatus = 'unsolved'
) {
  const status = filter || defaultStatus;

  const filterObj = {};
  if (q) {
    filterObj.moreLikeThis = {
      like: q.slice(0, MAX_KEYWORD_LENGTH),
      minimumShouldMatch: '0',
    };
  }

  filterObj.replyRequestCount = { GT: replyRequestCount - 1 };

  if (categoryIds) {
    filterObj.categoryIds = categoryIds.split(',').map(decodeURIComponent);
  }

  if (status === 'solved') {
    filterObj.replyCount = { GT: 0 };
  } else if (status === 'unsolved') {
    filterObj.replyCount = { EQ: 0 };
  }

  if (searchUserByArticleId) {
    filterObj.fromUserOfArticleId = searchUserByArticleId;
  }

  if (start) {
    filterObj.createdAt = { GT: start };
    if (end) {
      filterObj.createdAt.LTE = end;
    }
  }

  // Return filterObj only when it is populated.
  if (!Object.keys(filterObj).length) {
    return undefined;
  }
  return filterObj;
}

/**
 * @param {object} urlQuery - URL query object
 * @returns {object[]} ListArticleOrderBy array
 */
function urlQuery2OrderBy({ orderBy } = {}, defaultOrder) {
  const key = orderBy || defaultOrder;
  return [{ [key]: 'DESC' }];
}

/**
 * @param {object} urlQuery
 */
function goToUrlQueryAndResetPagination(urlQuery) {
  delete urlQuery.after;
  urlQuery = Object.fromEntries(
    Object.entries(urlQuery).filter(entry => !!entry[1])
  );
  Router.push(`${location.pathname}${url.format({ query: urlQuery })}`);
}

/**
 *
 * @param {object} query
 * @returns {object}
 */
export function getQueryVars(query, option) {
  return {
    filter: urlQuery2Filter(query, option?.filter),
    orderBy: urlQuery2OrderBy(query, option?.order),
  };
}

const FilterGroup = ({
  classes,
  query,
  filters,
  categories,
  defaultStatus,
  desktop = false,
}) => (
  <Filters className={classes.filters}>
    {filters.status && (
      <Filter
        title={t`Filter`}
        options={STATUSES.map(status => ({
          label: status,
          value: status,
          selected: status === (query.filter || defaultStatus),
        }))}
        onChange={filter =>
          goToUrlQueryAndResetPagination({
            ...query,
            filter,
          })
        }
      />
    )}

    {/* not implemented yet
    {filters.consider && (
      <Filter
        title={t`Consider`}
        multiple
      />
    )}
    */}

    {filters.category && (
      <Filter
        title={t`Topic`}
        multiple
        expandable={desktop}
        onlySelected={desktop}
        placeholder={desktop ? t`All Topics` : ''}
        options={categories}
        onChange={selected =>
          goToUrlQueryAndResetPagination({
            ...query,
            categoryIds: selected
              .map(value => encodeURIComponent(value))
              .join(','),
          })
        }
      />
    )}
  </Filters>
);

function ArticlePageLayout({
  title,
  articleDisplayConfig = {},
  defaultOrder = 'lastRequestedAt',
  defaultStatus = 'unsolved',
  filters = { status: true, consider: true, category: true },
}) {
  const classes = useStyles();
  const [showFilters, setFiltersShow] = useState(false);
  const [nextCursor, setNextCursor] = useState(null);
  const [articleEdges, setArticleEdges] = useState([]);

  const { query } = useRouter();

  const listQueryVars = getQueryVars(query, {
    filter: defaultStatus,
    order: defaultOrder,
  });

  const {
    loading,
    refetch,
    data: listArticlesData,
    error: listArticlesError,
  } = useQuery(LIST_ARTICLES, {
    variables: {
      ...listQueryVars,
      after: nextCursor,
    },
    onCompleted() {
      const newLoaded = listArticlesData?.ListArticles?.edges || [];
      setArticleEdges(articleEdges.concat(newLoaded));
    },
  });

  const { data: listCategories } = useQuery(LIST_CATEGORIES);

  // Separate these stats query so that it will be cached by apollo-client and sends no network request
  // on page change, but still works when filter options are updated.
  //
  const { data: listStatData } = useQuery(LIST_STAT, {
    variables: listQueryVars,
  });

  // List data
  const statsData = listStatData?.ListArticles || {};

  const selectedCategories =
    query.categoryIds?.split(',').map(decodeURIComponent) || [];

  const categories =
    listCategories?.ListCategories?.edges.map(({ node }) => ({
      value: node.id,
      label: node.title,
      selected: selectedCategories.includes(node.id),
    })) || [];

  // Flags
  const searchedArticleEdge = articleEdges.find(
    ({ node: { id } }) => id === query.searchUserByArticleId
  );
  const searchedUserArticleElem = (

      {ellipsis(searchedArticleEdge?.node?.text || '', { wordCount: 15 })}

  );

  const queryString = querystring.stringify(query);
  return (
    <Box pt={2}>
      {query.searchUserByArticleId && (

{jt`Messages reported by user that reported “${searchedUserArticleElem}”`}

      )}

      <Grid container alignItems="center" justify="space-between" spacing={2}>
        <Grid item xs={12} lg="auto">
          <Typography variant="h4">{title}</Typography>
        </Grid>
        <Grid item xs={12} lg="auto">
          <FeedDisplay
            feedUrl={`${PUBLIC_URL}/api/articles/rss2?${queryString}`}
          />
        </Grid>
      </Grid>

      <Grid container alignItems="center" justify="space-between">
        <Grid item>
          <TimeRange
            range={listQueryVars?.filter?.createdAt}
            onChange={createdAt =>
              goToUrlQueryAndResetPagination({
                ...query,
                start: createdAt?.GT,
                end: createdAt?.LTE,
              })
            }
          />
        </Grid>
        <Grid item>
          <SortInput
            orderBy={query.orderBy || defaultOrder}
            onChange={orderBy =>
              goToUrlQueryAndResetPagination({ ...query, orderBy })
            }
            options={[
              { value: 'lastRequestedAt', label: t`Most recently asked` },
              { value: 'lastRepliedAt', label: t`Most recently replied` },
              { value: 'replyRequestCount', label: t`Most asked` },
            ]}
          />
        </Grid>
      </Grid>

      <Box display={['none', 'none', 'block']}>
        <FilterGroup
          filters={filters}
          categories={categories}
          defaultStatus={defaultStatus}
          classes={classes}
          query={query}
          desktop
        />
      </Box>

      {loading && !articleEdges.length ? (
        'Loading....'
      ) : listArticlesError ? (
        listArticlesError.toString()
      ) : (
        <>

            {articleEdges.map(({ node }) => (
              <ArticleItem
                key={node.id}
                article={node}
                {...articleDisplayConfig}
              />
            ))}

          <LoadMore
            pageInfo={statsData?.pageInfo}
            edges={articleEdges}
            setCursor={setNextCursor}
            load={refetch}
            loading={loading}
          />
        </>
      )}
      <Fab
        variant="extended"
        aria-label="filters"
        className={classes.openFilter}
        onClick={() => setFiltersShow(!showFilters)}
      >
        <FilterListIcon />
        {t`Filter`}
      </Fab>
      <Modal
        aria-labelledby="filters"
        aria-describedby="filters"
        open={showFilters}
        onClose={() => setFiltersShow(false)}
        closeAfterTransition
        className={classes.filtersModal}
        BackdropComponent={Backdrop}
        BackdropProps={{
          timeout: 500,
        }}
      >
        <Fade in={showFilters}>
          <Box position="relative">
            <FilterGroup
              filters={filters}
              categories={categories}
              defaultStatus={defaultStatus}
              classes={classes}
              query={query}
            />
            <CloseIcon
              className={classes.closeIcon}
              onClick={() => setFiltersShow(false)}
            />
          </Box>
        </Fade>
      </Modal>
    </Box>
  );
}

export default ArticlePageLayout;
<commit_msg>Adjust styles<commit_after>import { useState } from 'react';
import gql from 'graphql-tag';
import querystring from 'querystring';
import { t, jt } from 'ttag';
import Router, { useRouter } from 'next/router';
import getConfig from 'next/config';
import url from 'url';
import { useQuery } from '@apollo/react-hooks';

import Grid from '@material-ui/core/Grid';
import Box from '@material-ui/core/Box';
import Fab from '@material-ui/core/Fab';
import Modal from '@material-ui/core/Modal';
import Backdrop from '@material-ui/core/Backdrop';
import Fade from '@material-ui/core/Fade';
import Typography from '@material-ui/core/Typography';

import FilterListIcon from '@material-ui/icons/FilterList';
import CloseIcon from '@material-ui/icons/Close';
import { makeStyles } from '@material-ui/core/styles';

import { ellipsis } from 'lib/text';
import ArticleItem from 'components/ArticleItem';
import LoadMore from 'components/LoadMore';
import FeedDisplay from 'components/FeedDisplay';
import Filters, { Filter } from 'components/Filters';
import TimeRange from 'components/TimeRange';
import SortInput from 'components/SortInput';

const STATUSES = ['unsolved', 'solved', 'all'];
const DEFAULT_REPLY_REQUEST_COUNT = 1;
const MAX_KEYWORD_LENGTH = 100;

const {
  publicRuntimeConfig: { PUBLIC_URL },
} = getConfig();

const LIST_ARTICLES = gql`
  query ListArticles(
    $filter: ListArticleFilter
    $orderBy: [ListArticleOrderBy]
    $after: String
  ) {
    ListArticles(filter: $filter, orderBy: $orderBy, after: $after, first: 10) {
      edges {
        node {
          ...ArticleItem
        }
        cursor
      }
    }
  }
  ${ArticleItem.fragments.ArticleItem}
`;

const LIST_STAT = gql`
  query ListArticlesStat(
    $filter: ListArticleFilter
    $orderBy: [ListArticleOrderBy]
  ) {
    ListArticles(filter: $filter, orderBy: $orderBy, first: 25) {
      pageInfo {
        firstCursor
        lastCursor
      }
      totalCount
    }
  }
`;

const LIST_CATEGORIES = gql`
  query ListCategories {
    ListCategories(first: 25) {
      totalCount
      edges {
        node {
          id
          title
        }
      }
    }
  }
`;

const useStyles = makeStyles(theme => ({
  filters: {
    padding: '12px 0',
  },
  articleList: {
    padding: 0,
    marginBottom: -12,
  },
  openFilter: {
    position: 'fixed',
    left: 22,
    bottom: 22,
    backgroundColor: theme.palette.secondary[500],
    color: theme.palette.common.white,
    [theme.breakpoints.up('md')]: {
      display: 'none',
    },
  },
  filtersModal: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    padding: 10,
  },
  closeIcon: {
    position: 'absolute',
    right: 12,
    top: 20,
    color: theme.palette.secondary[100],
  },
}));

/**
 * @param {object} urlQuery - URL query object
 * @returns {object} ListArticleFilter
 */
function urlQuery2Filter(
  {
    filter,
    q,
    categoryIds = '',
    start,
    end,
    replyRequestCount = DEFAULT_REPLY_REQUEST_COUNT,
    searchUserByArticleId,
  } = {},
  defaultStatus = 'unsolved'
) {
  const status = filter || defaultStatus;

  const filterObj = {};
  if (q) {
    filterObj.moreLikeThis = {
      like: q.slice(0, MAX_KEYWORD_LENGTH),
      minimumShouldMatch: '0',
    };
  }

  filterObj.replyRequestCount = { GT: replyRequestCount - 1 };

  if (categoryIds) {
    filterObj.categoryIds = categoryIds.split(',').map(decodeURIComponent);
  }

  if (status === 'solved') {
    filterObj.replyCount = { GT: 0 };
  } else if (status === 'unsolved') {
    filterObj.replyCount = { EQ: 0 };
  }

  if (searchUserByArticleId) {
    filterObj.fromUserOfArticleId = searchUserByArticleId;
  }

  if (start) {
    filterObj.createdAt = { GT: start };
    if (end) {
      filterObj.createdAt.LTE = end;
    }
  }

  // Return filterObj only when it is populated.
  if (!Object.keys(filterObj).length) {
    return undefined;
  }
  return filterObj;
}

/**
 * @param {object} urlQuery - URL query object
 * @returns {object[]} ListArticleOrderBy array
 */
function urlQuery2OrderBy({ orderBy } = {}, defaultOrder) {
  const key = orderBy || defaultOrder;
  return [{ [key]: 'DESC' }];
}

/**
 * @param {object} urlQuery
 */
function goToUrlQueryAndResetPagination(urlQuery) {
  delete urlQuery.after;
  urlQuery = Object.fromEntries(
    Object.entries(urlQuery).filter(entry => !!entry[1])
  );
  Router.push(`${location.pathname}${url.format({ query: urlQuery })}`);
}

/**
 *
 * @param {object} query
 * @returns {object}
 */
export function getQueryVars(query, option) {
  return {
    filter: urlQuery2Filter(query, option?.filter),
    orderBy: urlQuery2OrderBy(query, option?.order),
  };
}

const FilterGroup = ({
  classes,
  query,
  filters,
  categories,
  defaultStatus,
  desktop = false,
}) => (
  <Filters className={classes.filters}>
    {filters.status && (
      <Filter
        title={t`Filter`}
        options={STATUSES.map(status => ({
          label: status,
          value: status,
          selected: status === (query.filter || defaultStatus),
        }))}
        onChange={filter =>
          goToUrlQueryAndResetPagination({
            ...query,
            filter,
          })
        }
      />
    )}

    {/* not implemented yet
    {filters.consider && (
      <Filter
        title={t`Consider`}
        multiple
      />
    )}
    */}

    {filters.category && (
      <Filter
        title={t`Topic`}
        multiple
        expandable={desktop}
        onlySelected={desktop}
        placeholder={desktop ? t`All Topics` : ''}
        options={categories}
        onChange={selected =>
          goToUrlQueryAndResetPagination({
            ...query,
            categoryIds: selected
              .map(value => encodeURIComponent(value))
              .join(','),
          })
        }
      />
    )}
  </Filters>
);

function ArticlePageLayout({
  title,
  articleDisplayConfig = {},
  defaultOrder = 'lastRequestedAt',
  defaultStatus = 'unsolved',
  filters = { status: true, consider: true, category: true },
}) {
  const classes = useStyles();
  const [showFilters, setFiltersShow] = useState(false);
  const [nextCursor, setNextCursor] = useState(null);
  const [articleEdges, setArticleEdges] = useState([]);

  const { query } = useRouter();

  const listQueryVars = getQueryVars(query, {
    filter: defaultStatus,
    order: defaultOrder,
  });

  const {
    loading,
    refetch,
    data: listArticlesData,
    error: listArticlesError,
  } = useQuery(LIST_ARTICLES, {
    variables: {
      ...listQueryVars,
      after: nextCursor,
    },
    onCompleted() {
      const newLoaded = listArticlesData?.ListArticles?.edges || [];
      setArticleEdges(articleEdges.concat(newLoaded));
    },
  });

  const { data: listCategories } = useQuery(LIST_CATEGORIES);

  // Separate these stats query so that it will be cached by apollo-client and sends no network request
  // on page change, but still works when filter options are updated.
  //
  const { data: listStatData } = useQuery(LIST_STAT, {
    variables: listQueryVars,
  });

  // List data
  const statsData = listStatData?.ListArticles || {};

  const selectedCategories =
    query.categoryIds?.split(',').map(decodeURIComponent) || [];

  const categories =
    listCategories?.ListCategories?.edges.map(({ node }) => ({
      value: node.id,
      label: node.title,
      selected: selectedCategories.includes(node.id),
    })) || [];

  // Flags
  const searchedArticleEdge = articleEdges.find(
    ({ node: { id } }) => id === query.searchUserByArticleId
  );
  const searchedUserArticleElem = (

      {ellipsis(searchedArticleEdge?.node?.text || '', { wordCount: 15 })}

  );

  const queryString = querystring.stringify(query);
  return (
    <Box pt={2}>
      {query.searchUserByArticleId && (

{jt`Messages reported by user that reported “${searchedUserArticleElem}”`}

      )}

      <Grid container alignItems="center" justify="space-between" spacing={2}>
        <Grid item xs={12} lg="auto">
          <Typography variant="h4">{title}</Typography>
        </Grid>
        <Grid item xs={12} lg="auto">
          <FeedDisplay
            feedUrl={`${PUBLIC_URL}/api/articles/rss2?${queryString}`}
          />
        </Grid>
      </Grid>

      <Grid container alignItems="center" justify="space-between">
        <Grid item>
          <TimeRange
            range={listQueryVars?.filter?.createdAt}
            onChange={createdAt =>
              goToUrlQueryAndResetPagination({
                ...query,
                start: createdAt?.GT,
                end: createdAt?.LTE,
              })
            }
          />
        </Grid>
        <Grid item>
          <SortInput
            orderBy={query.orderBy || defaultOrder}
            onChange={orderBy =>
              goToUrlQueryAndResetPagination({ ...query, orderBy })
            }
            options={[
              { value: 'lastRequestedAt', label: t`Most recently asked` },
              { value: 'lastRepliedAt', label: t`Most recently replied` },
              { value: 'replyRequestCount', label: t`Most asked` },
            ]}
          />
        </Grid>
      </Grid>

      <Box display={['none', 'none', 'block']}>
        <FilterGroup
          filters={filters}
          categories={categories}
          defaultStatus={defaultStatus}
          classes={classes}
          query={query}
          desktop
        />
      </Box>

      {loading && !articleEdges.length ? (
        'Loading....'
      ) : listArticlesError ? (
        listArticlesError.toString()
      ) : (
        <>

            {articleEdges.map(({ node }) => (
              <ArticleItem
                key={node.id}
                article={node}
                {...articleDisplayConfig}
              />
            ))}

          <LoadMore
            pageInfo={statsData?.pageInfo}
            edges={articleEdges}
            setCursor={setNextCursor}
            load={refetch}
            loading={loading}
          />
        </>
      )}
      <Fab
        variant="extended"
        aria-label="filters"
        className={classes.openFilter}
        onClick={() => setFiltersShow(!showFilters)}
      >
        <FilterListIcon />
        {t`Filter`}
      </Fab>
      <Modal
        aria-labelledby="filters"
        aria-describedby="filters"
        open={showFilters}
        onClose={() => setFiltersShow(false)}
        closeAfterTransition
        className={classes.filtersModal}
        BackdropComponent={Backdrop}
        BackdropProps={{
          timeout: 500,
        }}
      >
        <Fade in={showFilters}>
          <Box position="relative">
            <FilterGroup
              filters={filters}
              categories={categories}
              defaultStatus={defaultStatus}
              classes={classes}
              query={query}
            />
            <CloseIcon
              className={classes.closeIcon}
              onClick={() => setFiltersShow(false)}
            />
          </Box>
        </Fade>
      </Modal>
    </Box>
  );
}

export default ArticlePageLayout;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { bisector } from 'd3';
import vega from 'vega';
import isEmpty from 'lodash/isEmpty';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import isEqual from 'lodash/isEqual';

// Redux
import withRedux from 'next-redux-wrapper';
import { initStore } from 'store';
import { toggleTooltip } from 'redactions/tooltip';

// Components
import VegaChartTooltip from './VegaChartTooltip';

class VegaChart extends React.Component {

  constructor(props) {
    super(props);

    // BINDINGS
    this.triggerResize = debounce(this.triggerResize.bind(this), 250);
  }

  componentDidMount() {
    this.mounted = true;
    this.renderChart();
    window.addEventListener('resize', this.triggerResize);
  }

  shouldComponentUpdate(nextProps) {
    return !isEqual(nextProps.data, this.props.data);
  }

  componentDidUpdate() {
    // We should check if the data has changed
    this.renderChart();
  }

  componentWillUnmount() {
    this.mounted = false;
    window.removeEventListener('resize', this.triggerResize);
  }

  /**
   * Event handler executed when the mouse goes out of the chart
   */
  onMouseOut() {
    // Right after this instruction, we hide the tooltip
    // Nevertheless, because the onMouseMove is throttled, an
    // execution might be delayed causing the tooltip from
    // leaking out of the chart container
    // The solution is to cancel any further executions at
    // this point
    if (this.throttledMouseMove) this.throttledMouseMove.cancel();

    // We hide the tooltip
    this.props.toggleTooltip(false);
  }

  /**
   * Event handler for the mouse move events on the chart
   * @param {object} vegaConfig Vega configuration of the chart
   * @param {object[]} visData Data fetched by Vega and used to draw the chart
   * @param {string|number} x x value associated with the position of the cursor
   * @param {object} item Data associated with the hovered mark, if exist
   */
  onMousemove(vegaConfig, visData, x, item) {
    // Regarding the Vega configuration, check if a column is a date
    const isDate = (columnName) => { // eslint-disable-line arrow-body-style
      return vegaConfig
        && vegaConfig.data[0].format
        && vegaConfig.data[0].format.parse
        && vegaConfig.data[0].format.parse[columnName] === 'date';
    };

    // Return the type of the data passed as argument
    const getType = (columnName, val) => {
      // We need to check the type date before the number one
      // because a date could be a timestamp
      if (val instanceof Date || isDate(columnName)) return 'date';
      if (typeof val === 'number') return 'number';
      return 'string';
    };

    // Return the label of the specified column
    const getLabel = (columnName) => {
      if (!vegaConfig) return null;

      const axis = vegaConfig.axes.find(a => a.type === columnName);
      if (!axis) return null;

      return axis.name;
    };

    // If the cursor is on top of a mark, we display the data
    // associated to that mark
    // The only exception is for the lines because the data
    // they own is always the first one (first point)
    if (!isEmpty(item) && item.datum.x && item.mark.marktype !== 'line') {
      return this.props.toggleTooltip(true, {
        follow: true,
        direction: 'bottom',
        children: VegaChartTooltip,
        childrenProps: {
          item: {
            x: {
              type: getType('x', item.datum.x),
              label: getLabel('x'),
              value: item.datum.x
            },
            y: {
              type: getType('y', item.datum.y),
              label: getLabel('y'),
              value: item.datum.y
            }
          }
        }
      });
    }

    // If the chart doesn't have an x axis or if the data is undefined,
    // we don't determine the data to show in the tooltip depending on
    // the x position of the cursor (based on the x scale)
    // We actually hide the tooltip
    const hasXAxis = !!(vegaConfig.axes && vegaConfig.axes.find(axis => axis.type === 'x'));
    if (!hasXAxis || !visData) {
      return this.props.toggleTooltip(false);
    }

    // d3's bisector function needs sorted data because it
    // determines the closest index using with a log2(n)
    // complexity (i.e. splitting the array in two in each
    // iteration)
    const sortedVisData = visData.sort((d1, d2) => d1.x - d2.x);

    // If not, then we retrieve the x position of the cursor,
    // and display the data of the closest point/line/bar
    let data; // eslint-disable-line no-shadow
    if (getType('x', x) === 'string') data = visData.find(d => d.x === x);
    if (getType('x', x) === 'number') {
      const bisectDate = bisector(d => d.x).left;
      const i = bisectDate(sortedVisData, x, 1);
      const d0 = visData[i - 1];
      const d1 = visData[i];
      data = (d0 && d1 && (x - d0.x > d1.x - x)) ? d1 : d0;
    }
    if (getType('x', x)) {
      const timestamp = x.getTime ? x.getTime() : x;
      const bisectDate = bisector(d => d.x).left;
      const i = bisectDate(sortedVisData, timestamp, 1);
      const d0 = visData[i - 1];
      const d1 = visData[i];
      data = (d0 && d1 && (timestamp - d0.x > d1.x - timestamp)) ? d1 : d0;
    }

    if (data) {
      return this.props.toggleTooltip(true, {
        follow: true,
        direction: 'bottom',
        children: VegaChartTooltip,
        childrenProps: {
          item: {
            x: {
              type: getType('x', x), // Don't use data.x here
              label: getLabel('x'),
              value: data.x,
              range: getType('x', x) === 'date'
                // We don't need to pass the full array
                ? [sortedVisData[0].x, sortedVisData[sortedVisData.length - 1].x]
                : []
            },
            y: {
              type: getType('y', data.y),
              label: getLabel('y'),
              value: data.y,
              range: getType('y', data.y) === 'date'
                // We don't need to pass the full array
                ? [sortedVisData[0].y, sortedVisData[sortedVisData.length - 1].y]
                : []
            }
          }
        }
      });
    }

    return null;
  }

  setSize() {
    if (this.chartViewportContainer) {
      this.width = this.chartViewportContainer.offsetWidth;
      this.height = this.chartViewportContainer.offsetHeight;
    }
  }

  /**
   * Return the Vega configuration of the chart
   * This method is necessary because we need to define the size of
   * the chart, as well as a signal to display a tooltip
   * @returns {object}
   */
  getVegaConfig() {
    const padding = this.props.data.padding || { top: 20, right: 20, bottom: 20, left: 20 };
    const size = {
      // Please don't change these conditions, the bar chart
      // needs its height and width to not be overriden to display
      // properly
      width: this.props.data.width || (this.width - padding.left - padding.right),
      height: this.props.data.height || (this.height - padding.top - padding.bottom)
    };

    // This signal is used for the tooltip
    const signal = {
      /* eslint-disable */
      "name": "onMousemove",
      "streams": [{
        "type": "mousemove",
        "expr": "{ x: iscale('x', eventX()), item: eventItem() }"
      }]
      /* eslint-enable */
    };

    const config = Object.assign({}, this.props.data, size);

    // If the configuration already has signals, we don't override it
    // but push a new one instead
    if (config.signals) {
      config.signals.push(signal);
    } else {
      config.signals = [signal];
    }

    return config;
  }

  /**
   * Toggle the visibility of the loader depending of the
   * passed value
   * @param {boolean} isLoading
   */
  toggleLoading(isLoading) {
    if (this.mounted && this.props.toggleLoading) {
      this.props.toggleLoading(isLoading);
    }
  }

  parseVega() {
    const theme = this.props.theme || {};
    const vegaConfig = this.getVegaConfig();

    // While Vega parses the configuration and renders the chart
    // we display a loader
    this.toggleLoading(true);

    vega.parse.spec(vegaConfig, theme, (err, chart) => {
      // If there's an error or the component has been unmounted
      // we don't do anything
      if (err || !this.mounted) return;

      // We render the chart
      const vis = chart({ el: this.chart, renderer: 'canvas' });
      vis.update();

      // We toggle off the loader once we've rendered the chart
      this.toggleLoading(false);

      // Data fetched by Vega and used to draw the chart
      const data = vis.data().table;

      // We display a tooltip at maximum 30 FPS (approximatively)
      // We save the throttled function in a global variable so
      // we can eventually cancel pending executions when the
      // cursor goes out of the chart's container
      this.throttledMouseMove = throttle((_, { x, item }) => {
        this.onMousemove(vegaConfig, data, x, item);
      }, 30);

      vis.onSignal('onMousemove', this.throttledMouseMove);
    });
  }

  triggerResize() {
    if (this.props.reloadOnResize) {
      this.renderChart();
    }
  }

  renderChart() {
    this.setSize();
    this.parseVega();
  }

  render() {
    return (
      <div
        className="c-chart"
        onMouseOut={() => this.onMouseOut()}
        ref={(el) => { this.chartViewportContainer = el; }}
      >
        <div ref={(c) => { this.chart = c; }} className="chart" />
      </div>
    );
  }
}

VegaChart.propTypes = {
  reloadOnResize: PropTypes.bool.isRequired,
  // Define the chart data
  data: PropTypes.object,
  theme: PropTypes.object,
  // Callbacks
  toggleLoading: PropTypes.func,
  toggleTooltip: PropTypes.func
};

const mapStateToProps = ({ tooltip }) => ({
  tooltip
});

const mapDispatchToProps = dispatch => ({
  toggleTooltip: (visibility, options) => dispatch(toggleTooltip(visibility, options))
});

export default withRedux(initStore, mapStateToProps, mapDispatchToProps)(VegaChart);
<commit_msg>For the scatter plot, only show the tooltip when hovering a dot<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { bisector } from 'd3';
import vega from 'vega';
import isEmpty from 'lodash/isEmpty';
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
import isEqual from 'lodash/isEqual';

// Redux
import withRedux from 'next-redux-wrapper';
import { initStore } from 'store';
import { toggleTooltip } from 'redactions/tooltip';

// Components
import VegaChartTooltip from './VegaChartTooltip';

class VegaChart extends React.Component {

  constructor(props) {
    super(props);

    // BINDINGS
    this.triggerResize = debounce(this.triggerResize.bind(this), 250);
  }

  componentDidMount() {
    this.mounted = true;
    this.renderChart();
    window.addEventListener('resize', this.triggerResize);
  }

  shouldComponentUpdate(nextProps) {
    return !isEqual(nextProps.data, this.props.data);
  }

  componentDidUpdate() {
    // We should check if the data has changed
    this.renderChart();
  }

  componentWillUnmount() {
    this.mounted = false;
    window.removeEventListener('resize', this.triggerResize);
  }

  /**
   * Event handler executed when the mouse goes out of the chart
   */
  onMouseOut() {
    // Right after this instruction, we hide the tooltip
    // Nevertheless, because the onMouseMove is throttled, an
    // execution might be delayed causing the tooltip from
    // leaking out of the chart container
    // The solution is to cancel any further executions at
    // this point
    if (this.throttledMouseMove) this.throttledMouseMove.cancel();

    // We hide the tooltip
    this.props.toggleTooltip(false);
  }

  /**
   * Event handler for the mouse move events on the chart
   * @param {object} vegaConfig Vega configuration of the chart
   * @param {object[]} visData Data fetched by Vega and used to draw the chart
   * @param {string|number} x x value associated with the position of the cursor
   * @param {object} item Data associated with the hovered mark, if exist
   */
  onMousemove(vegaConfig, visData, x, item) {
    // Regarding the Vega configuration, check if a column is a date
    const isDate = (columnName) => { // eslint-disable-line arrow-body-style
      return vegaConfig
        && vegaConfig.data[0].format
        && vegaConfig.data[0].format.parse
        && vegaConfig.data[0].format.parse[columnName] === 'date';
    };

    // Return the type of the data passed as argument
    const getType = (columnName, val) => {
      // We need to check the type date before the number one
      // because a date could be a timestamp
      if (val instanceof Date || isDate(columnName)) return 'date';
      if (typeof val === 'number') return 'number';
      return 'string';
    };

    // Return the label of the specified column
    const getLabel = (columnName) => {
      if (!vegaConfig) return null;

      const axis = vegaConfig.axes.find(a => a.type === columnName);
      if (!axis) return null;

      return axis.name;
    };

    // If the cursor is on top of a mark, we display the data
    // associated to that mark
    // The only exception is for the lines because the data
    // they own is always the first one (first point)
    if (!isEmpty(item) && item.datum.x && item.mark.marktype !== 'line') {
      return this.props.toggleTooltip(true, {
        follow: true,
        direction: 'bottom',
        children: VegaChartTooltip,
        childrenProps: {
          item: {
            x: {
              type: getType('x', item.datum.x),
              label: getLabel('x'),
              value: item.datum.x
            },
            y: {
              type: getType('y', item.datum.y),
              label: getLabel('y'),
              value: item.datum.y
            }
          }
        }
      });
    }

    // If the chart doesn't have an x axis, if the data is undefined
    // or if the chart is a scatter plot [1], we don't determine the
    // data to show in the tooltip depending on the x position of
    // the cursor (based on the x scale)
    // We actually hide the tooltip
    //
    // [1] As the scatter plot can have several points at the same
    //     x position, we want to avoid showing the data of a
    //     random point when not hovering a dot
    const hasXAxis = !!(vegaConfig.axes && vegaConfig.axes.find(axis => axis.type === 'x'));
    const isScatter = vegaConfig.marks.length === 1 && vegaConfig.marks[0].type === 'symbol';
    if (!hasXAxis || !visData || isScatter) {
      return this.props.toggleTooltip(false);
    }

    // d3's bisector function needs sorted data because it
    // determines the closest index using with a log2(n)
    // complexity (i.e. splitting the array in two in each
    // iteration)
    const sortedVisData = visData.sort((d1, d2) => d1.x - d2.x);

    // If not, then we retrieve the x position of the cursor,
    // and display the data of the closest point/line/bar
    let data; // eslint-disable-line no-shadow
    if (getType('x', x) === 'string') data = visData.find(d => d.x === x);
    if (getType('x', x) === 'number') {
      const bisectDate = bisector(d => d.x).left;
      const i = bisectDate(sortedVisData, x, 1);
      const d0 = visData[i - 1];
      const d1 = visData[i];
      data = (d0 && d1 && (x - d0.x > d1.x - x)) ? d1 : d0;
    }
    if (getType('x', x)) {
      const timestamp = x.getTime ? x.getTime() : x;
      const bisectDate = bisector(d => d.x).left;
      const i = bisectDate(sortedVisData, timestamp, 1);
      const d0 = visData[i - 1];
      const d1 = visData[i];
      data = (d0 && d1 && (timestamp - d0.x > d1.x - timestamp)) ? d1 : d0;
    }

    if (data) {
      return this.props.toggleTooltip(true, {
        follow: true,
        direction: 'bottom',
        children: VegaChartTooltip,
        childrenProps: {
          item: {
            x: {
              type: getType('x', x), // Don't use data.x here
              label: getLabel('x'),
              value: data.x,
              range: getType('x', x) === 'date'
                // We don't need to pass the full array
                ? [sortedVisData[0].x, sortedVisData[sortedVisData.length - 1].x]
                : []
            },
            y: {
              type: getType('y', data.y),
              label: getLabel('y'),
              value: data.y,
              range: getType('y', data.y) === 'date'
                // We don't need to pass the full array
                ? [sortedVisData[0].y, sortedVisData[sortedVisData.length - 1].y]
                : []
            }
          }
        }
      });
    }

    return null;
  }

  setSize() {
    if (this.chartViewportContainer) {
      this.width = this.chartViewportContainer.offsetWidth;
      this.height = this.chartViewportContainer.offsetHeight;
    }
  }

  /**
   * Return the Vega configuration of the chart
   * This method is necessary because we need to define the size of
   * the chart, as well as a signal to display a tooltip
   * @returns {object}
   */
  getVegaConfig() {
    const padding = this.props.data.padding || { top: 20, right: 20, bottom: 20, left: 20 };
    const size = {
      // Please don't change these conditions, the bar chart
      // needs its height and width to not be overriden to display
      // properly
      width: this.props.data.width || (this.width - padding.left - padding.right),
      height: this.props.data.height || (this.height - padding.top - padding.bottom)
    };

    // This signal is used for the tooltip
    const signal = {
      /* eslint-disable */
      "name": "onMousemove",
      "streams": [{
        "type": "mousemove",
        "expr": "{ x: iscale('x', eventX()), item: eventItem() }"
      }]
      /* eslint-enable */
    };

    const config = Object.assign({}, this.props.data, size);

    // If the configuration already has signals, we don't override it
    // but push a new one instead
    if (config.signals) {
      config.signals.push(signal);
    } else {
      config.signals = [signal];
    }

    return config;
  }

  /**
   * Toggle the visibility of the loader depending of the
   * passed value
   * @param {boolean} isLoading
   */
  toggleLoading(isLoading) {
    if (this.mounted && this.props.toggleLoading) {
      this.props.toggleLoading(isLoading);
    }
  }

  parseVega() {
    const theme = this.props.theme || {};
    const vegaConfig = this.getVegaConfig();

    // While Vega parses the configuration and renders the chart
    // we display a loader
    this.toggleLoading(true);

    vega.parse.spec(vegaConfig, theme, (err, chart) => {
      // If there's an error or the component has been unmounted
      // we don't do anything
      if (err || !this.mounted) return;

      // We render the chart
      const vis = chart({ el: this.chart, renderer: 'canvas' });
      vis.update();

      // We toggle off the loader once we've rendered the chart
      this.toggleLoading(false);

      // Data fetched by Vega and used to draw the chart
      const data = vis.data().table;

      // We display a tooltip at maximum 30 FPS (approximatively)
      // We save the throttled function in a global variable so
      // we can eventually cancel pending executions when the
      // cursor goes out of the chart's container
      this.throttledMouseMove = throttle((_, { x, item }) => {
        this.onMousemove(vegaConfig, data, x, item);
      }, 30);

      vis.onSignal('onMousemove', this.throttledMouseMove);
    });
  }

  triggerResize() {
    if (this.props.reloadOnResize) {
      this.renderChart();
    }
  }

  renderChart() {
    this.setSize();
    this.parseVega();
  }

  render() {
    return (
      <div
        className="c-chart"
        onMouseOut={() => this.onMouseOut()}
        ref={(el) => { this.chartViewportContainer = el; }}
      >
        <div ref={(c) => { this.chart = c; }} className="chart" />
      </div>
    );
  }
}

VegaChart.propTypes = {
  reloadOnResize: PropTypes.bool.isRequired,
  // Define the chart data
  data: PropTypes.object,
  theme: PropTypes.object,
  // Callbacks
  toggleLoading: PropTypes.func,
  toggleTooltip: PropTypes.func
};

const mapStateToProps = ({ tooltip }) => ({
  tooltip
});

const mapDispatchToProps = dispatch => ({
  toggleTooltip: (visibility, options) => dispatch(toggleTooltip(visibility, options))
});

export default withRedux(initStore, mapStateToProps, mapDispatchToProps)(VegaChart);
<|endoftext|>


<commit_before>var xhr = require('../xhr'),
    success = require('../utils').success,
    failure = require('../utils').failure,
    ServerActionCreators = require('../actions/server-action-creators');

module.exports = function (isAnnouncing) {
  if (isAnnouncing == null) {
    xhr.get('/announcer/state.json')
       .then(function (data) {
          return JSON.parse(data);
       })
       .then(ServerActionCreators.receiveAnnouncerState, failure);
  }
  var method = isAnnouncing ? 'DELETE' : 'POST';
  xhr(method, '/announcer').catch(failure);
}<commit_msg>Exorcise ghost announcer on web app load<commit_after>var xhr = require('../xhr'),
    success = require('../utils').success,
    failure = require('../utils').failure,
    ServerActionCreators = require('../actions/server-action-creators');

module.exports = function (isAnnouncing) {
  if (isAnnouncing == null) {
    xhr.get('/announcer/state.json')
       .then(function (data) {
          return JSON.parse(data);
       })
       .then(ServerActionCreators.receiveAnnouncerState, failure);
  } else {
    xhr((isAnnouncing ? 'DELETE' : 'POST'), '/announcer').catch(failure);
  }
}<|endoftext|>


<commit_before>window.CESIUM_BASE_URL = "../../Source/";

import {
  Cartesian3,
  createWorldTerrain,
  defined,
  formatError,
  Math as CesiumMath,
  objectToQuery,
  queryToObject,
  CzmlDataSource,
  GeoJsonDataSource,
  KmlDataSource,
  TileMapServiceImageryProvider,
  Viewer,
  viewerCesiumInspectorMixin,
  viewerDragDropMixin,
} from "../../Source/Cesium.js";

function main() {
  /*
     Options parsed from query string:
       source=url          The URL of a CZML/GeoJSON/KML data source to load at startup.
                           Automatic data type detection uses file extension.
       sourceType=czml/geojson/kml
                           Override data type detection for source.
       flyTo=false         Don't automatically fly to the loaded source.
       tmsImageryUrl=url   Automatically use a TMS imagery provider.
       lookAt=id           The ID of the entity to track at startup.
       stats=true          Enable the FPS performance display.
       inspector=true      Enable the inspector widget.
       debug=true          Full WebGL error reporting at substantial performance cost.
       theme=lighter       Use the dark-text-on-light-background theme.
       scene3DOnly=true    Enable 3D only mode.
       view=longitude,latitude,[height,heading,pitch,roll]
                           Automatically set a camera view. Values in degrees and meters.
                           [height,heading,pitch,roll] default is looking straight down, [300,0,-90,0]
       saveCamera=false    Don't automatically update the camera view in the URL when it changes.
     */
  var endUserOptions = queryToObject(window.location.search.substring(1));

  var imageryProvider;
  if (defined(endUserOptions.tmsImageryUrl)) {
    imageryProvider = new TileMapServiceImageryProvider({
      url: endUserOptions.tmsImageryUrl,
    });
  }

  var loadingIndicator = document.getElementById("loadingIndicator");
  var viewer;
  try {
    var hasBaseLayerPicker = !defined(imageryProvider);
    viewer = new Viewer("cesiumContainer", {
      imageryProvider: imageryProvider,
      baseLayerPicker: hasBaseLayerPicker,
      scene3DOnly: endUserOptions.scene3DOnly,
      requestRenderMode: true,
    });

    if (hasBaseLayerPicker) {
      var viewModel = viewer.baseLayerPicker.viewModel;
      viewModel.selectedTerrain = viewModel.terrainProviderViewModels[1];
    } else {
      viewer.terrainProvider = createWorldTerrain({
        requestWaterMask: true,
        requestVertexNormals: true,
      });
    }
  } catch (exception) {
    loadingIndicator.style.display = "none";
    var message = formatError(exception);
    console.error(message);
    if (!document.querySelector(".cesium-widget-errorPanel")) {
      //eslint-disable-next-line no-alert
      window.alert(message);
    }
    return;
  }

  viewer.extend(viewerDragDropMixin);
  if (endUserOptions.inspector) {
    viewer.extend(viewerCesiumInspectorMixin);
  }

  var showLoadError = function (name, error) {
    var title = "An error occurred while loading the file: " + name;
    var message =
      "An error occurred while loading the file, which may indicate that it is invalid.  A detailed error report is below:";
    viewer.cesiumWidget.showErrorPanel(title, message, error);
  };

  viewer.dropError.addEventListener(function (viewerArg, name, error) {
    showLoadError(name, error);
  });

  var scene = viewer.scene;
  var context = scene.context;
  if (endUserOptions.debug) {
    context.validateShaderProgram = true;
    context.validateFramebuffer = true;
    context.logShaderCompilation = true;
    context.throwOnWebGLError = true;
  }

  var view = endUserOptions.view;
  var source = endUserOptions.source;
  if (defined(source)) {
    var sourceType = endUserOptions.sourceType;
    if (!defined(sourceType)) {
      // autodetect using file extension if not specified
      if (/\.czml$/i.test(source)) {
        sourceType = "czml";
      } else if (
        /\.geojson$/i.test(source) ||
        /\.json$/i.test(source) ||
        /\.topojson$/i.test(source)
      ) {
        sourceType = "geojson";
      } else if (/\.kml$/i.test(source) || /\.kmz$/i.test(source)) {
        sourceType = "kml";
      }
    }

    var loadPromise;
    if (sourceType === "czml") {
      loadPromise = CzmlDataSource.load(source);
    } else if (sourceType === "geojson") {
      loadPromise = GeoJsonDataSource.load(source);
    } else if (sourceType === "kml") {
      loadPromise = KmlDataSource.load(source, {
        camera: scene.camera,
        canvas: scene.canvas,
      });
    } else {
      showLoadError(source, "Unknown format.");
    }

    if (defined(loadPromise)) {
      viewer.dataSources
        .add(loadPromise)
        .then(function (dataSource) {
          var lookAt = endUserOptions.lookAt;
          if (defined(lookAt)) {
            var entity = dataSource.entities.getById(lookAt);
            if (defined(entity)) {
              viewer.trackedEntity = entity;
            } else {
              var error =
                'No entity with id "' +
                lookAt +
                '" exists in the provided data source.';
              showLoadError(source, error);
            }
          } else if (!defined(view) && endUserOptions.flyTo !== "false") {
            viewer.flyTo(dataSource);
          }
        })
        .otherwise(function (error) {
          showLoadError(source, error);
        });
    }
  }

  if (endUserOptions.stats) {
    scene.debugShowFramesPerSecond = true;
  }

  var theme = endUserOptions.theme;
  if (defined(theme)) {
    if (endUserOptions.theme === "lighter") {
      document.body.classList.add("cesium-lighter");
      viewer.animation.applyThemeChanges();
    } else {
      var error = "Unknown theme: " + theme;
      viewer.cesiumWidget.showErrorPanel(error, "");
    }
  }

  if (defined(view)) {
    var splitQuery = view.split(/[ ,]+/);
    if (splitQuery.length > 1) {
      var longitude = !isNaN(+splitQuery[0]) ? +splitQuery[0] : 0.0;
      var latitude = !isNaN(+splitQuery[1]) ? +splitQuery[1] : 0.0;
      var height =
        splitQuery.length > 2 && !isNaN(+splitQuery[2])
          ? +splitQuery[2]
          : 300.0;
      var heading =
        splitQuery.length > 3 && !isNaN(+splitQuery[3])
          ? CesiumMath.toRadians(+splitQuery[3])
          : undefined;
      var pitch =
        splitQuery.length > 4 && !isNaN(+splitQuery[4])
          ? CesiumMath.toRadians(+splitQuery[4])
          : undefined;
      var roll =
        splitQuery.length > 5 && !isNaN(+splitQuery[5])
          ? CesiumMath.toRadians(+splitQuery[5])
          : undefined;

      viewer.camera.setView({
        destination: Cartesian3.fromDegrees(longitude, latitude, height),
        orientation: {
          heading: heading,
          pitch: pitch,
          roll: roll,
        },
      });
    }
  }

  var camera = viewer.camera;
  function saveCamera() {
    var position = camera.positionCartographic;
    var hpr = "";
    if (defined(camera.heading)) {
      hpr =
        "," +
        CesiumMath.toDegrees(camera.heading) +
        "," +
        CesiumMath.toDegrees(camera.pitch) +
        "," +
        CesiumMath.toDegrees(camera.roll);
    }
    endUserOptions.view =
      CesiumMath.toDegrees(position.longitude) +
      "," +
      CesiumMath.toDegrees(position.latitude) +
      "," +
      position.height +
      hpr;
    history.replaceState(undefined, "", "?" + objectToQuery(endUserOptions));
  }

  var timeout;
  if (endUserOptions.saveCamera !== "false") {
    camera.changed.addEventListener(function () {
      window.clearTimeout(timeout);
      timeout = window.setTimeout(saveCamera, 1000);
    });
  }

  loadingIndicator.style.display = "none";
}

main();
<commit_msg>Add GpxDataSource to CesiumViewer<commit_after>window.CESIUM_BASE_URL = "../../Source/";

import {
  Cartesian3,
  createWorldTerrain,
  defined,
  formatError,
  Math as CesiumMath,
  objectToQuery,
  queryToObject,
  CzmlDataSource,
  GeoJsonDataSource,
  KmlDataSource,
  GpxDataSource,
  TileMapServiceImageryProvider,
  Viewer,
  viewerCesiumInspectorMixin,
  viewerDragDropMixin,
} from "../../Source/Cesium.js";

function main() {
  /*
     Options parsed from query string:
       source=url          The URL of a CZML/GeoJSON/KML data source to load at startup.
                           Automatic data type detection uses file extension.
       sourceType=czml/geojson/kml
                           Override data type detection for source.
       flyTo=false         Don't automatically fly to the loaded source.
       tmsImageryUrl=url   Automatically use a TMS imagery provider.
       lookAt=id           The ID of the entity to track at startup.
       stats=true          Enable the FPS performance display.
       inspector=true      Enable the inspector widget.
       debug=true          Full WebGL error reporting at substantial performance cost.
       theme=lighter       Use the dark-text-on-light-background theme.
       scene3DOnly=true    Enable 3D only mode.
       view=longitude,latitude,[height,heading,pitch,roll]
                           Automatically set a camera view. Values in degrees and meters.
                           [height,heading,pitch,roll] default is looking straight down, [300,0,-90,0]
       saveCamera=false    Don't automatically update the camera view in the URL when it changes.
     */
  var endUserOptions = queryToObject(window.location.search.substring(1));

  var imageryProvider;
  if (defined(endUserOptions.tmsImageryUrl)) {
    imageryProvider = new TileMapServiceImageryProvider({
      url: endUserOptions.tmsImageryUrl,
    });
  }

  var loadingIndicator = document.getElementById("loadingIndicator");
  var viewer;
  try {
    var hasBaseLayerPicker = !defined(imageryProvider);
    viewer = new Viewer("cesiumContainer", {
      imageryProvider: imageryProvider,
      baseLayerPicker: hasBaseLayerPicker,
      scene3DOnly: endUserOptions.scene3DOnly,
      requestRenderMode: true,
    });

    if (hasBaseLayerPicker) {
      var viewModel = viewer.baseLayerPicker.viewModel;
      viewModel.selectedTerrain = viewModel.terrainProviderViewModels[1];
    } else {
      viewer.terrainProvider = createWorldTerrain({
        requestWaterMask: true,
        requestVertexNormals: true,
      });
    }
  } catch (exception) {
    loadingIndicator.style.display = "none";
    var message = formatError(exception);
    console.error(message);
    if (!document.querySelector(".cesium-widget-errorPanel")) {
      //eslint-disable-next-line no-alert
      window.alert(message);
    }
    return;
  }

  viewer.extend(viewerDragDropMixin);
  if (endUserOptions.inspector) {
    viewer.extend(viewerCesiumInspectorMixin);
  }

  var showLoadError = function (name, error) {
    var title = "An error occurred while loading the file: " + name;
    var message =
      "An error occurred while loading the file, which may indicate that it is invalid.  A detailed error report is below:";
    viewer.cesiumWidget.showErrorPanel(title, message, error);
  };

  viewer.dropError.addEventListener(function (viewerArg, name, error) {
    showLoadError(name, error);
  });

  var scene = viewer.scene;
  var context = scene.context;
  if (endUserOptions.debug) {
    context.validateShaderProgram = true;
    context.validateFramebuffer = true;
    context.logShaderCompilation = true;
    context.throwOnWebGLError = true;
  }

  var view = endUserOptions.view;
  var source = endUserOptions.source;
  if (defined(source)) {
    var sourceType = endUserOptions.sourceType;
    if (!defined(sourceType)) {
      // autodetect using file extension if not specified
      if (/\.czml$/i.test(source)) {
        sourceType = "czml";
      } else if (
        /\.geojson$/i.test(source) ||
        /\.json$/i.test(source) ||
        /\.topojson$/i.test(source)
      ) {
        sourceType = "geojson";
      } else if (/\.kml$/i.test(source) || /\.kmz$/i.test(source)) {
        sourceType = "kml";
      } else if (/\.gpx$/i.test(source) || /\.gpx$/i.test(source)) {
        sourceType = "gpx";
      }
    }

    var loadPromise;
    if (sourceType === "czml") {
      loadPromise = CzmlDataSource.load(source);
    } else if (sourceType === "geojson") {
      loadPromise = GeoJsonDataSource.load(source);
    } else if (sourceType === "kml") {
      loadPromise = KmlDataSource.load(source, {
        camera: scene.camera,
        canvas: scene.canvas,
      });
    else if (sourceType === "gpx") {
      loadPromise = GpxDataSource.load(source);
    } else {
      showLoadError(source, "Unknown format.");
    }

    if (defined(loadPromise)) {
      viewer.dataSources
        .add(loadPromise)
        .then(function (dataSource) {
          var lookAt = endUserOptions.lookAt;
          if (defined(lookAt)) {
            var entity = dataSource.entities.getById(lookAt);
            if (defined(entity)) {
              viewer.trackedEntity = entity;
            } else {
              var error =
                'No entity with id "' +
                lookAt +
                '" exists in the provided data source.';
              showLoadError(source, error);
            }
          } else if (!defined(view) && endUserOptions.flyTo !== "false") {
            viewer.flyTo(dataSource);
          }
        })
        .otherwise(function (error) {
          showLoadError(source, error);
        });
    }
  }

  if (endUserOptions.stats) {
    scene.debugShowFramesPerSecond = true;
  }

  var theme = endUserOptions.theme;
  if (defined(theme)) {
    if (endUserOptions.theme === "lighter") {
      document.body.classList.add("cesium-lighter");
      viewer.animation.applyThemeChanges();
    } else {
      var error = "Unknown theme: " + theme;
      viewer.cesiumWidget.showErrorPanel(error, "");
    }
  }

  if (defined(view)) {
    var splitQuery = view.split(/[ ,]+/);
    if (splitQuery.length > 1) {
      var longitude = !isNaN(+splitQuery[0]) ? +splitQuery[0] : 0.0;
      var latitude = !isNaN(+splitQuery[1]) ? +splitQuery[1] : 0.0;
      var height =
        splitQuery.length > 2 && !isNaN(+splitQuery[2])
          ? +splitQuery[2]
          : 300.0;
      var heading =
        splitQuery.length > 3 && !isNaN(+splitQuery[3])
          ? CesiumMath.toRadians(+splitQuery[3])
          : undefined;
      var pitch =
        splitQuery.length > 4 && !isNaN(+splitQuery[4])
          ? CesiumMath.toRadians(+splitQuery[4])
          : undefined;
      var roll =
        splitQuery.length > 5 && !isNaN(+splitQuery[5])
          ? CesiumMath.toRadians(+splitQuery[5])
          : undefined;

      viewer.camera.setView({
        destination: Cartesian3.fromDegrees(longitude, latitude, height),
        orientation: {
          heading: heading,
          pitch: pitch,
          roll: roll,
        },
      });
    }
  }

  var camera = viewer.camera;
  function saveCamera() {
    var position = camera.positionCartographic;
    var hpr = "";
    if (defined(camera.heading)) {
      hpr =
        "," +
        CesiumMath.toDegrees(camera.heading) +
        "," +
        CesiumMath.toDegrees(camera.pitch) +
        "," +
        CesiumMath.toDegrees(camera.roll);
    }
    endUserOptions.view =
      CesiumMath.toDegrees(position.longitude) +
      "," +
      CesiumMath.toDegrees(position.latitude) +
      "," +
      position.height +
      hpr;
    history.replaceState(undefined, "", "?" + objectToQuery(endUserOptions));
  }

  var timeout;
  if (endUserOptions.saveCamera !== "false") {
    camera.changed.addEventListener(function () {
      window.clearTimeout(timeout);
      timeout = window.setTimeout(saveCamera, 1000);
    });
  }

  loadingIndicator.style.display = "none";
}

main();
<|endoftext|>


<commit_before>'use strict';

/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */

var protractor = require('protractor');
var APP_BASE_URL = 'call_centre/';

// UTILS
function expectUrl(absUrl, expectedUrl) {
  var pro = protractor.getInstance();

  expect((new RegExp(expectedUrl+'$')).test(absUrl)).toBe(true,
    ['Expected', absUrl, 'to be', pro.baseUrl+expectedUrl].join(' ')
  );
}

function createCase() {
  browser.get(APP_BASE_URL);
  browser.getLocationAbsUrl().then(function (url) {
    expectUrl(url, APP_BASE_URL);
  });
  browser.findElement(by.css('.newCaseForm')).submit();
}

function showPersonalDetailsForm() {
  browser.findElement(by.css('#personal_details')).click();
}

function enterPersonalDetails(details) {
  for (var name in details) {
    fillField(name, details[name]);
  }
}

function fillField(name, value) {
  browser.findElement(by.css('[name=' + name + ']')).sendKeys(value);
}

function saveCase() {
  browser.findElement(by.css('#personal_details [type=submit]')).click();
}

describe('operatorApp', function() {

  // logs the user in before each test
  beforeEach(function() {
    var pro = protractor.getInstance();
    var driver = pro.driver;

    driver.get(pro.baseUrl + 'call_centre/login/');

    driver.findElement(by.id('id_username')).sendKeys('test_operator');
    driver.findElement(by.id('id_password')).sendKeys('test_operator');
    driver.findElement(by.css('form')).submit();

    // kill django debug toolbar if it's showing
    pro.manage().addCookie('djdt', 'hide');

  });

  describe('Case List', function() {
    it('should get case list', function() {
      browser.get(APP_BASE_URL);
      browser.getLocationAbsUrl().then(function(url) {
        expectUrl(url, APP_BASE_URL);
      });
    });
  });

  describe('Create Case', function() {
    it('should create new case', function() {
      // check that the case number in the URL matches that in the page title

      createCase();

      var newCaseUrl;
      browser.getLocationAbsUrl().then(function(url) {
        // note: angular url, not from driver
        newCaseUrl = url;
      });

      browser.findElement(by.css('.CaseDetails-caseNum')).getInnerHtml().then(function(h1) {
        // console.log("h1 is: "+h1);
        // h1 is: MK-1983-0912
        expectUrl(APP_BASE_URL+ newCaseUrl, h1 + '/');
      });

    });
  });

  describe('Case List Navigation', function () {
    it('should keep query params from case_list when going back from case_detail', function () {
      browser.get(APP_BASE_URL);

      browser.getLocationAbsUrl().then(function(url) {
        expectUrl(url, APP_BASE_URL);
      });

      // add some query params by sending a search
      var searchBox = browser.findElement(by.name('q'));

      searchBox.sendKeys('Foo123');
      expect(searchBox.getAttribute('value')).toBe('Foo123');
      browser.findElement(by.id('search')).submit();
      browser.getLocationAbsUrl().then(function (url) {
        var searched_url = url;

        // create a case

        browser.findElement(by.id('create_case')).click();
        // go back & see that query params have been retained.

        browser.findElement(by.cssContainingText('a','Back to cases')).click();
        browser.getLocationAbsUrl().then(function (url2) {
          expect(searched_url).toBe(url2);
        });
      });
    });
  });

  describe('Case Detail', function() {
    it('should get case list when given non existant case reference', function() {
      browser.get('call_centre/XX-0000-0000/');
      browser.getLocationAbsUrl().then(function(url) {
        expectUrl(url, APP_BASE_URL);

        browser.findElement(by.css('.Notice li')).getInnerHtml().then(function(el) {
          expect(el).toBe('The Case XX-0000-0000 could not be found!');
        });
      });
    });
  });

  describe('Create Case with Adaptations', function () {
    it('should create a new case with the BSL - Webcam adaptation', function () {
      createCase();
      showPersonalDetailsForm();
      enterPersonalDetails({
        'full_name': 'Foo Bar Quux',
        'postcode': 'F00 B4R',
        'street': '1 Foo Bar',
        'mobile_phone': '0123456789'
      });
      showAdaptationsOptions();
      selectAdaptations(['bsl_webcam']);
      saveCase();
      expect(displayedAdaptation()).toBe('BSL - Webcam');
    });

    function showAdaptationsOptions() {
      browser.findElement(by.css('#show_adaptations')).click();
    }

    function selectAdaptations(checkboxes) {
      checkboxes.map(function (name) {
        browser.findElement(by.css('[name=' + name + '] + span')).click();
      });
    }

    function displayedAdaptation() {
      return browser.findElement(by.repeater('item in selected_adaptations').row(0)).getText();
    }
  });

  describe('Case Set Matter Types and Assign', function () {

    function open_modal() {
      return browser.findElement(by.css("a.Button[ng-click^=assign_to_provider]")).click();
    }

    it('should show modal when trying to assign without matter types set', function () {
      createCase();
      open_modal();
        expect(browser.findElement(by.css('.modal-content')).getText()).toContain('Set Matter Types');
    });

    it('should not allow saving modal without setting matter type 1 and 2', function () {
      createCase();
      open_modal();
      var modalEl = browser.findElement(by.css('div.modal'));
      modalEl.findElement(by.css("button[type='submit'")).click();
      expect(modalEl.isElementPresent(by.css("button[type='submit'"))).toBe(true);
    });

    it('should allow saving modal after setting matter type 1 and 2', function () {
      createCase();
      open_modal();

      var modalEl = browser.findElement(by.css('div.modal'));
      modalEl.findElement(by.css("input[name='matter_type1']")).click();
      modalEl.findElement(by.css("input[name='matter_type2']")).click();
      modalEl.findElement(by.css("button[type='submit'")).click();
      expect(browser.isElementPresent(by.css("div.modal"))).toBe(false);
    });

    it('should go straight to assign page if MT1 and MT2 are already set', function () {
      createCase();
      open_modal();
      expect(browser.findElement(by.css('.modal-content')).getText()).toContain('Set Matter Types');
      var modalEl = browser.findElement(by.css('div.modal'));
      modalEl.findElement(by.css("input[name='matter_type1']")).click();
      modalEl.findElement(by.css("input[name='matter_type2']")).click();
      modalEl.findElement(by.css("button[type='submit'")).click();
      expect(browser.isElementPresent(by.css("div.modal"))).toBe(false);
      var assignCaseUrl;
      browser.getLocationAbsUrl().then(function (url) {
        assignCaseUrl = url;
      });
      browser.findElement(by.css("a[ui-sref='case_detail.edit']")).click();
      browser.findElement(by.css("a.Button[ng-click^=assign_to_provider]")).click();
      expect(browser.isElementPresent(by.css("div.modal"))).toBe(false);
      browser.getLocationAbsUrl()
        .then(function (url) {
        expect(url).toBe(assignCaseUrl);
      });
    });
  });

  describe('Set media code on case', function () {
    it('should set a media code on a new case', function () {
      createCase();
      showPersonalDetailsForm();
      enterPersonalDetails({
        'full_name': 'Foo Bar Quux',
        'postcode': 'F00 B4R',
        'street': '1 Foo Bar',
        'mobile_phone': '0123456789'
      });
      selectMediaCode('Age Concern');
      saveCase();
      expect(displayedMediaCode()).toBe('Age Concern');
    });

    function selectMediaCode(code_name) {
      browser.findElement(by.css('.selectize-control')).click();
      var field = browser.findElement(by.css('.ui-select-search'));
      field.sendKeys(code_name);
      field.sendKeys(protractor.Key.ENTER);
    }

    function displayedMediaCode() {
      return browser.findElement(by.binding('mediaCode(media_code.selected).name')).getText();
    }
  });
});
<commit_msg>Fix failing tests since buttons are now within dropdown<commit_after>'use strict';

/* http://docs.angularjs.org/guide/dev_guide.e2e-testing */

var protractor = require('protractor');
var APP_BASE_URL = 'call_centre/';

// UTILS
function expectUrl(absUrl, expectedUrl) {
  var pro = protractor.getInstance();

  expect((new RegExp(expectedUrl+'$')).test(absUrl)).toBe(true,
    ['Expected', absUrl, 'to be', pro.baseUrl+expectedUrl].join(' ')
  );
}

function createCase() {
  browser.get(APP_BASE_URL);
  browser.getLocationAbsUrl().then(function (url) {
    expectUrl(url, APP_BASE_URL);
  });
  browser.findElement(by.css('.newCaseForm')).submit();
}

function showPersonalDetailsForm() {
  browser.findElement(by.css('#personal_details')).click();
}

function enterPersonalDetails(details) {
  for (var name in details) {
    fillField(name, details[name]);
  }
}

function fillField(name, value) {
  browser.findElement(by.css('[name=' + name + ']')).sendKeys(value);
}

function saveCase() {
  browser.findElement(by.css('#personal_details [type=submit]')).click();
}

describe('operatorApp', function() {

  // logs the user in before each test
  beforeEach(function() {
    var pro = protractor.getInstance();
    var driver = pro.driver;

    driver.get(pro.baseUrl + 'call_centre/login/');

    driver.findElement(by.id('id_username')).sendKeys('test_operator');
    driver.findElement(by.id('id_password')).sendKeys('test_operator');
    driver.findElement(by.css('form')).submit();

    // kill django debug toolbar if it's showing
    pro.manage().addCookie('djdt', 'hide');

  });

  describe('Case List', function() {
    it('should get case list', function() {
      browser.get(APP_BASE_URL);
      browser.getLocationAbsUrl().then(function(url) {
        expectUrl(url, APP_BASE_URL);
      });
    });
  });

  describe('Create Case', function() {
    it('should create new case', function() {
      // check that the case number in the URL matches that in the page title

      createCase();

      var newCaseUrl;
      browser.getLocationAbsUrl().then(function(url) {
        // note: angular url, not from driver
        newCaseUrl = url;
      });

      browser.findElement(by.css('.CaseDetails-caseNum')).getInnerHtml().then(function(h1) {
        // console.log("h1 is: "+h1);
        // h1 is: MK-1983-0912
        expectUrl(APP_BASE_URL+ newCaseUrl, h1 + '/');
      });

    });
  });

  describe('Case List Navigation', function () {
    it('should keep query params from case_list when going back from case_detail', function () {
      browser.get(APP_BASE_URL);

      browser.getLocationAbsUrl().then(function(url) {
        expectUrl(url, APP_BASE_URL);
      });

      // add some query params by sending a search
      var searchBox = browser.findElement(by.name('q'));

      searchBox.sendKeys('Foo123');
      expect(searchBox.getAttribute('value')).toBe('Foo123');
      browser.findElement(by.id('search')).submit();
      browser.getLocationAbsUrl().then(function (url) {
        var searched_url = url;

        // create a case

        browser.findElement(by.id('create_case')).click();
        // go back & see that query params have been retained.

        browser.findElement(by.cssContainingText('a','Back to cases')).click();
        browser.getLocationAbsUrl().then(function (url2) {
          expect(searched_url).toBe(url2);
        });
      });
    });
  });

  describe('Case Detail', function() {
    it('should get case list when given non existant case reference', function() {
      browser.get('call_centre/XX-0000-0000/');
      browser.getLocationAbsUrl().then(function(url) {
        expectUrl(url, APP_BASE_URL);

        browser.findElement(by.css('.Notice.error')).getInnerHtml().then(function(el) {
          expect(el).toBe('The Case XX-0000-0000 could not be found!');
        });
      });
    });
  });

  describe('Create Case with Adaptations', function () {
    it('should create a new case with the BSL - Webcam adaptation', function () {
      createCase();
      showPersonalDetailsForm();
      enterPersonalDetails({
        'full_name': 'Foo Bar Quux',
        'postcode': 'F00 B4R',
        'street': '1 Foo Bar',
        'mobile_phone': '0123456789'
      });
      showAdaptationsOptions();
      selectAdaptations(['bsl_webcam']);
      saveCase();
      expect(displayedAdaptation()).toBe('BSL - Webcam');
    });

    function showAdaptationsOptions() {
      browser.findElement(by.css('#show_adaptations')).click();
    }

    function selectAdaptations(checkboxes) {
      checkboxes.map(function (name) {
        browser.findElement(by.css('[name=' + name + '] + span')).click();
      });
    }

    function displayedAdaptation() {
      return browser.findElement(by.repeater('item in selected_adaptations').row(0)).getText();
    }
  });

  describe('Case Set Matter Types and Assign', function () {

    function open_modal() {
      browser.findElement(by.css(".CaseDetails-actions button.Button--dropdown")).click();
      return browser.findElement(by.css("a.Button[ng-click^=assign_to_provider]")).click();
    }

    it('should show modal when trying to assign without matter types set', function () {
      createCase();
      open_modal();
        expect(browser.findElement(by.css('.modal-content')).getText()).toContain('Set Matter Types');
    });

    it('should not allow saving modal without setting matter type 1 and 2', function () {
      createCase();
      open_modal();
      var modalEl = browser.findElement(by.css('div.modal'));
      modalEl.findElement(by.css("button[type='submit'")).click();
      expect(modalEl.isElementPresent(by.css("button[type='submit'"))).toBe(true);
    });

    it('should allow saving modal after setting matter type 1 and 2', function () {
      createCase();
      open_modal();

      var modalEl = browser.findElement(by.css('div.modal'));
      modalEl.findElement(by.css("input[name='matter_type1']")).click();
      modalEl.findElement(by.css("input[name='matter_type2']")).click();
      modalEl.findElement(by.css("button[type='submit'")).click();
      expect(browser.isElementPresent(by.css("div.modal"))).toBe(false);
    });

    it('should go straight to assign page if MT1 and MT2 are already set', function () {
      createCase();
      open_modal();
      expect(browser.findElement(by.css('.modal-content')).getText()).toContain('Set Matter Types');
      var modalEl = browser.findElement(by.css('div.modal'));
      modalEl.findElement(by.css("input[name='matter_type1']")).click();
      modalEl.findElement(by.css("input[name='matter_type2']")).click();
      modalEl.findElement(by.css("button[type='submit'")).click();
      expect(browser.isElementPresent(by.css("div.modal"))).toBe(false);
      var assignCaseUrl;
      browser.getLocationAbsUrl().then(function (url) {
        assignCaseUrl = url;
      });
      browser.findElement(by.css("a[ui-sref='case_detail.edit']")).click();
      browser.findElement(by.css(".CaseDetails-actions button.Button--dropdown")).click();
      browser.findElement(by.css("a.Button[ng-click^=assign_to_provider]")).click();
      expect(browser.isElementPresent(by.css("div.modal"))).toBe(false);
      browser.getLocationAbsUrl()
        .then(function (url) {
        expect(url).toBe(assignCaseUrl);
      });
    });
  });

  describe('Set media code on case', function () {
    it('should set a media code on a new case', function () {
      createCase();
      showPersonalDetailsForm();
      enterPersonalDetails({
        'full_name': 'Foo Bar Quux',
        'postcode': 'F00 B4R',
        'street': '1 Foo Bar',
        'mobile_phone': '0123456789'
      });
      selectMediaCode('Age Concern');
      saveCase();
      expect(displayedMediaCode()).toBe('Age Concern');
    });

    function selectMediaCode(code_name) {
      browser.findElement(by.css('.selectize-control')).click();
      var field = browser.findElement(by.css('.ui-select-search'));
      field.sendKeys(code_name);
      field.sendKeys(protractor.Key.ENTER);
    }

    function displayedMediaCode() {
      return browser.findElement(by.binding('mediaCode(media_code.selected).name')).getText();
    }
  });
});
<|endoftext|>