<commit_before>/*
 * Copyright (c) 2013, David Farrell <shokku.ra@gmail.com>
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of iBot nor the names of its contributors may be used to
 *   endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

exports.mod = function(context)
{
	this.segmentDuration = 604800000;
	this.segmentInterval = undefined;
	this.schedules = [];
	this.timeouts = [];

	this._load = function(data)
	{

	}

	this._save = function()
	{

	}

	this._suspend = function()
	{
		var data =
		{
			lastCbInterval: this.lastCbInterval,
			segmentInterval: this.segmentInterval,
			schedules: this.schedules,
			timeouts: this.timeouts
		};
		return data;
	}

	this._resume = function(data)
	{
		if(data.lastCbInterval !== undefined) this.lastCbInterval = data.lastCbInterval;
		if(data.segmentInterval !== undefined) this.segmentInterval = data.segmentInterval;
		if(data.schedules !== undefined) this.schedules = data.schedules;
		if(data.timeouts !== undefined) this.timeouts = data.timeouts;
	}

	this._loaded = function(server)
	{
		var ms = 0;

		if(this.segmentInterval !== undefined)
		{
			clearInterval(this.segmentInterval);

			if(this.lastCbInterval !== undefined)
			{
				var diff = process.hrtime(this.lastCbInterval);
				ms = this.segmentDuration - (diff[0] * 1000 + diff[1] / 1000000);
			}
		}
		else
		{
			ms = this.segmentDuration;
		}

		setTimeout(function()
		{
			this.cbInterval(server);
			this.segmentInterval = setInterval(function()
			{
				this.cbInterval(server);
			}.bind(this), this.segmentDuration);
		}.bind(this), ms);
	}

	this._unloaded = function(server)
	{
		clearInterval(this.segmentInterval);

		for(var iTimeout in this.timeouts)
		{
			clearTimeout(this.timeouts[iTimeout]);
		}
	}

	this.core$cmd = function(server, prefix, target, cmd, params)
	{
		if(cmd === 'scheduler')
		{
			switch(params[0])
			{
				case '+':
					var offset = parseInt(params[1]);
					var segments = parseInt(params[2]);
					if(isNaN(offset)) offset = 0;
					if(isNaN(segments)) segments = 0;

					server.do('scheduler$schedule', server, offset, segments, function()
					{
						server.do('core$cmd', server, prefix, target, params[3], params.slice(4));
					});
					break;
				case '?':
					server.do('core$privmsg', server, target, 'Number of schedules: ' + this.schedules.length);
					break;
			}
		}
	}

	this._fire = function()
	{
		var args = arguments;
		var server = args[0];
		var offset = args[1];
		var segments = args[2];
		var eventName = args[3];
		var params = Array.prototype.slice.call(arguments, 4);
		var a = [eventName].concat(params);

		server.do('scheduler$schedule', server, offset, segments, function()
		{
			server.fire.apply(server, a);
		});
	}

	this._schedule = function(server, offset, segments, callback)
	{
		if(segments > 0)
		{
			var newOffset = offset;

			if(this.lastCbInterval !== undefined)
			{
				var diff = process.hrtime(this.lastCbInterval);
				newOffset += Math.floor(diff[0] * 1000 + diff[1] / 1000000);
			}

			this.schedules.push({offset:newOffset, segments:segments, callback:callback});
		}
		else
		{
			this.timeouts.push(setTimeout(callback, offset));
		}
	}

	this._unschedule = function(index)
	{
		// TODO
	}

	this.cbInterval = function(server)
	{
		this.lastCbInterval = process.hrtime();

		for(var iSchedule in this.schedules)
		{
			--this.schedules[iSchedule].segments;

			if(this.schedules[iSchedule].segments <= 0)
			{
				this.timeouts.push(setTimeout(this.schedules[iSchedule].callback, this.schedules[iSchedule].offset));
				this.schedules.splice(iSchedule, 1);
			}
		}
	}.bind(this);
}
<commit_msg>Made various changes to scheduler and added _timeToMilliseconds function.<commit_after>/*
 * Copyright (c) 2013, David Farrell <shokku.ra@gmail.com>
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of iBot nor the names of its contributors may be used to
 *   endorse or promote products derived from this software without specific
 *   prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

exports.mod = function(context)
{
	this.segmentDuration = 604800000;
	this.segmentInterval = undefined;
	this.schedules = [];
	this.timeouts = [];

	this._load = function(data)
	{

	}

	this._save = function()
	{

	}

	this._suspend = function()
	{
		var data =
		{
			lastCbInterval: this.lastCbInterval,
			segmentInterval: this.segmentInterval,
			schedules: this.schedules,
			timeouts: this.timeouts
		};
		return data;
	}

	this._resume = function(data)
	{
		if(data.lastCbInterval !== undefined) this.lastCbInterval = data.lastCbInterval;
		if(data.segmentInterval !== undefined) this.segmentInterval = data.segmentInterval;
		if(data.schedules !== undefined) this.schedules = data.schedules;
		if(data.timeouts !== undefined) this.timeouts = data.timeouts;
	}

	this._loaded = function(server)
	{
		var ms = 0;

		if(this.segmentInterval !== undefined)
		{
			clearInterval(this.segmentInterval);

			if(this.lastCbInterval !== undefined)
			{
				var diff = process.hrtime(this.lastCbInterval);
				ms = this.segmentDuration - (diff[0] * 1000 + diff[1] / 1000000);
			}
		}
		else
		{
			ms = this.segmentDuration;
		}

		setTimeout(function()
		{
			this.cbInterval(server);
			this.segmentInterval = setInterval(function()
			{
				this.cbInterval(server);
			}.bind(this), this.segmentDuration);
		}.bind(this), ms);
	}

	this._unloaded = function(server)
	{
		clearInterval(this.segmentInterval);

		for(var iTimeout in this.timeouts)
		{
			clearTimeout(this.timeouts[iTimeout]);
		}
	}

	this.core$cmd = function(server, prefix, target, cmd, params)
	{
		if(cmd === 'scheduler')
		{
			switch(params[0])
			{
				case '+':
					server.do('scheduler$schedule', server, params[1], params[2], function()
					{
						server.do('core$cmd', server, prefix, target, params[3], params.slice(4));
					});
					break;
				case '?':
					server.do('core$privmsg', server, target, 'Number of schedules: ' + this.schedules.length);
					break;
			}
		}
	}

	this._fire = function()
	{
		var args = arguments;
		var server = args[0];
		var offset = args[1];
		var segments = args[2];
		var eventName = args[3];
		var params = Array.prototype.slice.call(arguments, 4);
		var a = [eventName].concat(params);

		server.do('scheduler$schedule', server, offset, segments, function()
		{
			server.fire.apply(server, a);
		});
	}

	this._schedule = function(server, offset, segments, callback)
	{
		var offset = server.do('scheduler$timeToMilliseconds', offset);

		if(segments > 0)
		{
			var newOffset = offset;

			if(this.lastCbInterval !== undefined)
			{
				var diff = process.hrtime(this.lastCbInterval);
				newOffset += Math.floor(diff[0] * 1000 + diff[1] / 1000000);
			}

			this.schedules.push({offset:newOffset, segments:segments, callback:callback});
		}
		else
		{
			this.timeouts.push(setTimeout(callback, offset));
		}
	}

	this._unschedule = function(index)
	{
		// TODO
	}

	this.cbInterval = function(server)
	{
		this.lastCbInterval = process.hrtime();

		for(var iSchedule in this.schedules)
		{
			--this.schedules[iSchedule].segments;

			if(this.schedules[iSchedule].segments <= 0)
			{
				this.timeouts.push(setTimeout(this.schedules[iSchedule].callback, this.schedules[iSchedule].offset));
				this.schedules.splice(iSchedule, 1);
			}
		}
	}.bind(this);

	this._timeToMilliseconds = function(timeString)
	{
		var r = /(\d+d)?(\d+h)?(\d+m)?(\d+s)?(\d+)?/;
		var match = r.exec(timeString);

		var t = 0;
		if(match[1] !== undefined) t += parseInt(match[1]) * 1000 * 60 * 60 * 24;
		if(match[2] !== undefined) t += parseInt(match[2]) * 1000 * 60 * 60;
		if(match[3] !== undefined) t += parseInt(match[3]) * 1000 * 60;
		if(match[4] !== undefined) t += parseInt(match[4]) * 1000;
		if(match[5] !== undefined) t += parseInt(match[5]);

		return t;
	}
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { Field } from 'react-final-form';
import { FormattedMessage, injectIntl } from 'react-intl';
import moment from 'moment-timezone';
import { OnChange } from 'react-final-form-listeners';

import { ViewMetaData } from '@folio/stripes/smart-components';
import {
  Button,
  Select,
  TextField,
  Row,
  Col,
  Accordion,
  Datepicker,
  Headline,
  Modal,
  ModalFooter,
} from '@folio/stripes/components';

import asyncValidateField from '../../validators/asyncValidateField';

import css from './EditUserInfo.css';

class EditUserInfo extends React.Component {
  static propTypes = {
    accordionId: PropTypes.string.isRequired,
    expanded: PropTypes.bool,
    initialValues: PropTypes.object,
    intl: PropTypes.object.isRequired,
    onToggle: PropTypes.func,
    patronGroups: PropTypes.arrayOf(PropTypes.object),
    stripes: PropTypes.shape({
      store: PropTypes.shape({
        dispatch: PropTypes.func.isRequired,
        getState: PropTypes.func,
      }),
    }).isRequired,
    form: PropTypes.object,
    uniquenessValidator: PropTypes.object,
  };

  constructor(props) {
    super(props);

    const { initialValues: { patronGroup } } = props;
    this.state = {
      showRecalculateModal: false,
      selectedPatronGroup: patronGroup,
    };
  }

  componentDidMount() {
    if (this.getPatronGroupOffset()) {
      this.showModal(true);
    }
  }

  showModal = (val) => {
    this.setState({
      showRecalculateModal: val,
    });
  }

  handleClose = () => {
    this.setState({ showRecalculateModal: false });
  }

  setRecalculatedExpirationDate = () => {
    const { form: { change } } = this.props;
    const recalculatedDate = this.calculateNewExpirationDate();

    change('expirationDate', recalculatedDate);
    this.setState({ showRecalculateModal: false });
  }

  calculateNewExpirationDate = () => {
    const { initialValues } = this.props;
    const expirationDate = new Date(initialValues.expirationDate);
    const now = Date.now();
    const offsetOfSelectedPatronGroup = this.state.selectedPatronGroup ? this.getPatronGroupOffset() : '';
    let recalculatedDate;

    if (initialValues.expirationDate === undefined || expirationDate <= now) {
      recalculatedDate = (moment().add(offsetOfSelectedPatronGroup, 'd').format('YYYY-MM-DD'));
    } else {
      recalculatedDate = (moment(expirationDate).add(offsetOfSelectedPatronGroup, 'd').format('YYYY-MM-DD'));
    }
    return recalculatedDate;
  }

  getPatronGroupOffset = () => {
    const selectedPatronGroup = this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup);
    return _.get(selectedPatronGroup, 'expirationOffsetInDays', '');
  };

  render() {
    const {
      patronGroups,
      initialValues,
      expanded,
      onToggle,
      accordionId,
      intl,
      uniquenessValidator,
    } = this.props;

    const { barcode } = initialValues;

    const isUserExpired = () => {
      const expirationDate = new Date(initialValues.expirationDate);
      const now = Date.now();
      return expirationDate <= now;
    };

    const willUserExtend = () => {
      const { form } = this.props;
      const expirationDate = form.getFieldState('expirationDate')?.value ?? '';
      const currentExpirationDate = new Date(expirationDate);
      const now = Date.now();
      return currentExpirationDate >= now;
    };

    const isStatusFieldDisabled = () => {
      let statusFieldDisabled = false;
      statusFieldDisabled = isUserExpired();
      return statusFieldDisabled;
    };

    const checkShowRecalculateButton = () => {
      const offsetOfSelectedPatronGroup = this.state.selectedPatronGroup ? this.getPatronGroupOffset() : '';
      return offsetOfSelectedPatronGroup !== '';
    };

    const patronGroupOptions = [
      {
        value: '',
        label: intl.formatMessage({ id: 'ui-users.information.selectPatronGroup' }),
      },
      ...patronGroups.map(g => ({
        key: g.id,
        value: g.id,
        label: g.group.concat(g.desc ? ` (${g.desc})` : ''),
      }))
    ];

    const statusOptions = [
      {
        value: true,
        label: intl.formatMessage({ id: 'ui-users.active' })
      },
      {
        value: false,
        label: intl.formatMessage({ id: 'ui-users.inactive' })
      }
    ];

    const offset = this.getPatronGroupOffset();
    const group = _.get(this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup), 'group', '');
    const date = moment(this.calculateNewExpirationDate()).format('MMMM Do YYYY');

    const modalFooter = (
      <ModalFooter>
        <Button
          id="expirationDate-modal-recalculate-btn"
          onClick={this.setRecalculatedExpirationDate}
        >
          <FormattedMessage id="ui-users.information.recalculate.modal.button" />
        </Button>
        <Button
          id="expirationDate-modal-cancel-btn"
          onClick={this.handleClose}
        >
          <FormattedMessage id="ui-users.cancel" />
        </Button>
      </ModalFooter>
    );

    return (
      <>
        <Accordion
          label={<Headline size="large" tag="h3"><FormattedMessage id="ui-users.information.userInformation" /></Headline>}
          open={expanded}
          id={accordionId}
          onToggle={onToggle}
        >

          { initialValues.metadata && <ViewMetaData metadata={initialValues.metadata} /> }

          <Row>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.lastName" />}
                name="personal.lastName"
                id="adduser_lastname"
                component={TextField}
                required
                fullWidth
                autoFocus
              />
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.firstName" />}
                name="personal.firstName"
                id="adduser_firstname"
                component={TextField}
                fullWidth
              />
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.middleName" />}
                name="personal.middleName"
                id="adduser_middlename"
                component={TextField}
                fullWidth
              />
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.preferredName" />}
                name="personal.preferredFirstName"
                id="adduser_preferredname"
                component={TextField}
                fullWidth
              />
            </Col>
          </Row>

          <Row>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.patronGroup" />}
                name="patronGroup"
                id="adduser_group"
                component={Select}
                selectClass={css.patronGroup}
                fullWidth
                dataOptions={patronGroupOptions}
                defaultValue={initialValues.patronGroup}
                aria-required="true"
                required
              />
              <OnChange name="patronGroup">
                {(selectedPatronGroup) => {
                  this.setState({ selectedPatronGroup }, () => {
                    if (this.getPatronGroupOffset()) {
                      this.showModal(true);
                    }
                  });
                }}
              </OnChange>
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.status" />}
                name="active"
                id="useractive"
                component={Select}
                fullWidth
                disabled={isStatusFieldDisabled()}
                dataOptions={statusOptions}
                defaultValue={initialValues.active}
                aria-required="true"
                required
              />
              {isUserExpired() && (

                  <FormattedMessage id="ui-users.errors.userExpired" />

              )}
              {isUserExpired() && willUserExtend() && (

                  <FormattedMessage id="ui-users.information.recalculate.will.reactivate.user" />

              )}
            </Col>
            <Col xs={12} md={3}>
              <Field
                component={Datepicker}
                label={<FormattedMessage id="ui-users.expirationDate" />}
                dateFormat="YYYY-MM-DD"
                defaultValue={initialValues.expirationDate}
                name="expirationDate"
                id="adduser_expirationdate"
              />
              {checkShowRecalculateButton() && (
                <Button
                  id="recalculate-expirationDate-btn"
                  onClick={this.setRecalculatedExpirationDate}
                >
                  <FormattedMessage id="ui-users.information.recalculate.expirationDate" />
                </Button>
              )}
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.barcode" />}
                name="barcode"
                id="adduser_barcode"
                component={TextField}
                validate={asyncValidateField('barcode', barcode, uniquenessValidator)}
                fullWidth
              />
            </Col>
          </Row>
        </Accordion>
        <Modal
          footer={modalFooter}
          id="recalculate_expirationdate_modal"
          label={<FormattedMessage id="ui-users.information.recalculate.modal.label" />}
          open={this.state.showRecalculateModal}
        >

            <FormattedMessage
              id="ui-users.information.recalculate.modal.text"
              values={{ group, offset, date }}
            />

        </Modal>
      </>
    );
  }
}

export default injectIntl(EditUserInfo);
<commit_msg>UIU-2058-change-text-for-expiration-date-modal adapt format of date<commit_after>import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { Field } from 'react-final-form';
import { FormattedMessage, injectIntl } from 'react-intl';
import moment from 'moment-timezone';
import { OnChange } from 'react-final-form-listeners';

import { ViewMetaData } from '@folio/stripes/smart-components';
import {
  Button,
  Select,
  TextField,
  Row,
  Col,
  Accordion,
  Datepicker,
  Headline,
  Modal,
  ModalFooter,
} from '@folio/stripes/components';

import asyncValidateField from '../../validators/asyncValidateField';

import css from './EditUserInfo.css';

class EditUserInfo extends React.Component {
  static propTypes = {
    accordionId: PropTypes.string.isRequired,
    expanded: PropTypes.bool,
    initialValues: PropTypes.object,
    intl: PropTypes.object.isRequired,
    onToggle: PropTypes.func,
    patronGroups: PropTypes.arrayOf(PropTypes.object),
    stripes: PropTypes.shape({
      store: PropTypes.shape({
        dispatch: PropTypes.func.isRequired,
        getState: PropTypes.func,
      }),
    }).isRequired,
    form: PropTypes.object,
    uniquenessValidator: PropTypes.object,
  };

  constructor(props) {
    super(props);

    const { initialValues: { patronGroup } } = props;
    this.state = {
      showRecalculateModal: false,
      selectedPatronGroup: patronGroup,
    };
  }

  componentDidMount() {
    if (this.getPatronGroupOffset()) {
      this.showModal(true);
    }
  }

  showModal = (val) => {
    this.setState({
      showRecalculateModal: val,
    });
  }

  handleClose = () => {
    this.setState({ showRecalculateModal: false });
  }

  setRecalculatedExpirationDate = () => {
    const { form: { change } } = this.props;
    const recalculatedDate = this.calculateNewExpirationDate();

    change('expirationDate', recalculatedDate);
    this.setState({ showRecalculateModal: false });
  }

  calculateNewExpirationDate = () => {
    const { initialValues } = this.props;
    const expirationDate = new Date(initialValues.expirationDate);
    const now = Date.now();
    const offsetOfSelectedPatronGroup = this.state.selectedPatronGroup ? this.getPatronGroupOffset() : '';
    let recalculatedDate;

    if (initialValues.expirationDate === undefined || expirationDate <= now) {
      recalculatedDate = (moment().add(offsetOfSelectedPatronGroup, 'd').format('YYYY-MM-DD'));
    } else {
      recalculatedDate = (moment(expirationDate).add(offsetOfSelectedPatronGroup, 'd').format('YYYY-MM-DD'));
    }
    return recalculatedDate;
  }

  getPatronGroupOffset = () => {
    const selectedPatronGroup = this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup);
    return _.get(selectedPatronGroup, 'expirationOffsetInDays', '');
  };

  render() {
    const {
      patronGroups,
      initialValues,
      expanded,
      onToggle,
      accordionId,
      intl,
      uniquenessValidator,
    } = this.props;

    const { barcode } = initialValues;

    const isUserExpired = () => {
      const expirationDate = new Date(initialValues.expirationDate);
      const now = Date.now();
      return expirationDate <= now;
    };

    const willUserExtend = () => {
      const { form } = this.props;
      const expirationDate = form.getFieldState('expirationDate')?.value ?? '';
      const currentExpirationDate = new Date(expirationDate);
      const now = Date.now();
      return currentExpirationDate >= now;
    };

    const isStatusFieldDisabled = () => {
      let statusFieldDisabled = false;
      statusFieldDisabled = isUserExpired();
      return statusFieldDisabled;
    };

    const checkShowRecalculateButton = () => {
      const offsetOfSelectedPatronGroup = this.state.selectedPatronGroup ? this.getPatronGroupOffset() : '';
      return offsetOfSelectedPatronGroup !== '';
    };

    const patronGroupOptions = [
      {
        value: '',
        label: intl.formatMessage({ id: 'ui-users.information.selectPatronGroup' }),
      },
      ...patronGroups.map(g => ({
        key: g.id,
        value: g.id,
        label: g.group.concat(g.desc ? ` (${g.desc})` : ''),
      }))
    ];

    const statusOptions = [
      {
        value: true,
        label: intl.formatMessage({ id: 'ui-users.active' })
      },
      {
        value: false,
        label: intl.formatMessage({ id: 'ui-users.inactive' })
      }
    ];

    const offset = this.getPatronGroupOffset();
    const group = _.get(this.props.patronGroups.find(i => i.id === this.state.selectedPatronGroup), 'group', '');
    const date = moment(this.calculateNewExpirationDate()).format('LL');

    const modalFooter = (
      <ModalFooter>
        <Button
          id="expirationDate-modal-recalculate-btn"
          onClick={this.setRecalculatedExpirationDate}
        >
          <FormattedMessage id="ui-users.information.recalculate.modal.button" />
        </Button>
        <Button
          id="expirationDate-modal-cancel-btn"
          onClick={this.handleClose}
        >
          <FormattedMessage id="ui-users.cancel" />
        </Button>
      </ModalFooter>
    );

    return (
      <>
        <Accordion
          label={<Headline size="large" tag="h3"><FormattedMessage id="ui-users.information.userInformation" /></Headline>}
          open={expanded}
          id={accordionId}
          onToggle={onToggle}
        >

          { initialValues.metadata && <ViewMetaData metadata={initialValues.metadata} /> }

          <Row>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.lastName" />}
                name="personal.lastName"
                id="adduser_lastname"
                component={TextField}
                required
                fullWidth
                autoFocus
              />
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.firstName" />}
                name="personal.firstName"
                id="adduser_firstname"
                component={TextField}
                fullWidth
              />
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.middleName" />}
                name="personal.middleName"
                id="adduser_middlename"
                component={TextField}
                fullWidth
              />
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.preferredName" />}
                name="personal.preferredFirstName"
                id="adduser_preferredname"
                component={TextField}
                fullWidth
              />
            </Col>
          </Row>

          <Row>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.patronGroup" />}
                name="patronGroup"
                id="adduser_group"
                component={Select}
                selectClass={css.patronGroup}
                fullWidth
                dataOptions={patronGroupOptions}
                defaultValue={initialValues.patronGroup}
                aria-required="true"
                required
              />
              <OnChange name="patronGroup">
                {(selectedPatronGroup) => {
                  this.setState({ selectedPatronGroup }, () => {
                    if (this.getPatronGroupOffset()) {
                      this.showModal(true);
                    }
                  });
                }}
              </OnChange>
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.status" />}
                name="active"
                id="useractive"
                component={Select}
                fullWidth
                disabled={isStatusFieldDisabled()}
                dataOptions={statusOptions}
                defaultValue={initialValues.active}
                aria-required="true"
                required
              />
              {isUserExpired() && (

                  <FormattedMessage id="ui-users.errors.userExpired" />

              )}
              {isUserExpired() && willUserExtend() && (

                  <FormattedMessage id="ui-users.information.recalculate.will.reactivate.user" />

              )}
            </Col>
            <Col xs={12} md={3}>
              <Field
                component={Datepicker}
                label={<FormattedMessage id="ui-users.expirationDate" />}
                dateFormat="YYYY-MM-DD"
                defaultValue={initialValues.expirationDate}
                name="expirationDate"
                id="adduser_expirationdate"
              />
              {checkShowRecalculateButton() && (
                <Button
                  id="recalculate-expirationDate-btn"
                  onClick={this.setRecalculatedExpirationDate}
                >
                  <FormattedMessage id="ui-users.information.recalculate.expirationDate" />
                </Button>
              )}
            </Col>
            <Col xs={12} md={3}>
              <Field
                label={<FormattedMessage id="ui-users.information.barcode" />}
                name="barcode"
                id="adduser_barcode"
                component={TextField}
                validate={asyncValidateField('barcode', barcode, uniquenessValidator)}
                fullWidth
              />
            </Col>
          </Row>
        </Accordion>
        <Modal
          footer={modalFooter}
          id="recalculate_expirationdate_modal"
          label={<FormattedMessage id="ui-users.information.recalculate.modal.label" />}
          open={this.state.showRecalculateModal}
        >

            <FormattedMessage
              id="ui-users.information.recalculate.modal.text"
              values={{ group, offset, date }}
            />

        </Modal>
      </>
    );
  }
}

export default injectIntl(EditUserInfo);
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react'
import Slider, { Handle } from 'rc-slider'
import './VolumeSlider.scss'

export default class VolumeSlider extends React.Component {
  static propTypes = {
    volume: PropTypes.number.isRequired,
    onVolumeChange: PropTypes.func.isRequired,
    className: PropTypes.string.isRequired,
  }

  state = {
    vol: this.props.volume,
    isDragging: false,
  }

  handleChange = this.handleChange.bind(this)
  handleAfterChange = this.handleAfterChange.bind(this)

  handleChange(vol) {
    this.setState({
      vol,
      isDragging: true,
    })
    this.props.onVolumeChange(vol)
    this.ignoreStatus = 0
  }

  handleAfterChange(vol) {
    this.setState({
      vol,
      isDragging: false,
    })
    this.props.onVolumeChange(vol)
    this.ignoreStatus = 2
  }

  componentDidUpdate(prevProps) {
    if (this.ignoreStatus && prevProps.volume !== this.props.volume) {
      this.ignoreStatus--
    }
  }

  render() {
    return (
      <Slider
        min={0}
        max={1}
        step={0.01}
        value={this.state.isDragging || this.ignoreStatus ? this.state.vol : this.props.volume}
        onChange={this.handleChange}
        onAfterChange={this.handleAfterChange}
        handle={handle}
        className={this.props.className}
      />
    )
  }
}

// volume slider handle/grabber
const handle = (props) => {
  const style = Object.assign({left: `${props.offset}%`}, {
    position: 'absolute',
    transform: 'translate(-50%, -50%)',
    marginTop: '6px',
    fontSize: '40px',
    opacity: .7,
    color: '#333',
  })

  let icon = "volume_up"
  if (props.value === 0) icon = "volume_off"
  else if (props.value < .4) icon = "volume_mute"
  else if (props.value < .7) icon = "volume_down"

  return (

      {icon}
      <Handle {...props}/>

  )
}
<commit_msg>Fix Chrome v56 warning "Unable to preventDefault inside passive event listener"<commit_after>import React, { PropTypes } from 'react'
import Slider, { Handle } from 'rc-slider'
import './VolumeSlider.scss'

export default class VolumeSlider extends React.Component {
  static propTypes = {
    volume: PropTypes.number.isRequired,
    onVolumeChange: PropTypes.func.isRequired,
    className: PropTypes.string.isRequired,
  }

  state = {
    vol: this.props.volume,
    isDragging: false,
  }

  handleChange = this.handleChange.bind(this)
  handleAfterChange = this.handleAfterChange.bind(this)

  handleChange(vol) {
    this.setState({
      vol,
      isDragging: true,
    })
    this.props.onVolumeChange(vol)
    this.ignoreStatus = 0
  }

  handleAfterChange(vol) {
    this.setState({
      vol,
      isDragging: false,
    })
    this.props.onVolumeChange(vol)
    this.ignoreStatus = 2
  }

  componentDidUpdate(prevProps) {
    if (this.ignoreStatus && prevProps.volume !== this.props.volume) {
      this.ignoreStatus--
    }
  }

  render() {
    return (
      <Slider
        min={0}
        max={1}
        step={0.01}
        value={this.state.isDragging || this.ignoreStatus ? this.state.vol : this.props.volume}
        onChange={this.handleChange}
        onAfterChange={this.handleAfterChange}
        handle={handle}
        className={this.props.className}
      />
    )
  }
}

// volume slider handle/grabber
const handle = (props) => {
  const style = Object.assign({left: `${props.offset}%`}, {
    position: 'absolute',
    transform: 'translate(-50%, -50%)',
    marginTop: '6px',
    fontSize: '40px',
    opacity: .7,
    color: '#333',
    touchAction: 'pan-x',
  })

  let icon = "volume_up"
  if (props.value === 0) icon = "volume_off"
  else if (props.value < .4) icon = "volume_mute"
  else if (props.value < .7) icon = "volume_down"

  return (

      {icon}
      <Handle {...props}/>

  )
}
<|endoftext|>


<commit_before>export default class ComponentModal {
  static render(component, data, topLevel) {
    const children = component.renderTemplate('component', data, topLevel);

    return component.renderTemplate('componentModal', {
      ...data,
      children,
    });
  }

  constructor(component, modal) {
    this.component = component;
    this.modal = modal;
    this.currentValue = this.component.dataValue;
    this.dataLoaded = false;
    this.init();
  }

  get refs() {
    return this.component.refs;
  }

  init() {
    this.loadRefs();
  }

  setValue(value) {
    if (this.dataLoaded) {
      return;
    }

    this.currentValue = value;
    this.dataLoaded = true;
    this.updateView();
  }

  setOpenModalElement(template) {
    this.openModalTemplate = template;
    this.component.setContent(this.refs.openModalWrapper, template);
    this.loadRefs();
    this.setEventListeners();
  }

  loadRefs() {
    this.component.loadRefs(this.modal, {
      modalOverlay: 'single',
      modalContents: 'single',
      modalClose: 'single',
      openModalWrapper: 'single',
      openModal: 'single',
      modalSave: 'single',
      modalWrapper: 'single',
    });
  }

  setEventListeners() {
    this.component.addEventListener(this.refs.openModal, 'click', this.openModalHandler.bind(this));
    this.component.addEventListener(this.refs.modalOverlay, 'click', this.closeModalHandler.bind(this));
    this.component.addEventListener(this.refs.modalClose, 'click', this.closeModalHandler.bind(this));
    this.component.addEventListener(this.refs.modalSave, 'click', this.saveModalValueHandler.bind(this));
  }

  setOpenEventListener() {
    this.component.loadRefs(this.modal, {
      'openModal': 'single',
    });

    this.component.addEventListener(this.refs.openModal, 'click', this.openModalHandler.bind(this));
  }

  openModalHandler(event) {
    event.preventDefault();
    this.refs.modalWrapper.classList.remove('component-rendering-hidden');
  }

  updateView() {
    const template = this.currentValue === this.component.defaultValue
      ? this.openModalTemplate
      : this.component.getModalPreviewTemplate();
    this.component.setContent(this.refs.openModalWrapper, template);
    this.setOpenEventListener();
  }

  closeModal() {
    this.refs.modalWrapper.classList.add('component-rendering-hidden');
    this.updateView();
  }

  closeModalHandler(event) {
    event.preventDefault();
    this.component.setValue(this.currentValue);
    this.closeModal();
  }

  saveModalValueHandler(event) {
    event.preventDefault();
    this.currentValue = this.component.dataValue;
    this.closeModal();
  }
}
<commit_msg>Fixed an issue of default message that is not displayed in modal edit mode<commit_after>import _ from 'lodash';

export default class ComponentModal {
  static render(component, data, topLevel) {
    const children = component.renderTemplate('component', data, topLevel);

    return component.renderTemplate('componentModal', {
      ...data,
      children,
    });
  }

  constructor(component, modal) {
    this.component = component;
    this.modal = modal;
    this.currentValue = this.component.dataValue;
    this.dataLoaded = false;
    this.init();
  }

  get refs() {
    return this.component.refs;
  }

  init() {
    this.loadRefs();
  }

  setValue(value) {
    if (this.dataLoaded) {
      return;
    }

    this.currentValue = value;
    this.dataLoaded = true;
    this.updateView();
  }

  setOpenModalElement(template) {
    this.openModalTemplate = template;
    this.component.setContent(this.refs.openModalWrapper, template);
    this.loadRefs();
    this.setEventListeners();
  }

  loadRefs() {
    this.component.loadRefs(this.modal, {
      modalOverlay: 'single',
      modalContents: 'single',
      modalClose: 'single',
      openModalWrapper: 'single',
      openModal: 'single',
      modalSave: 'single',
      modalWrapper: 'single',
    });
  }

  setEventListeners() {
    this.component.addEventListener(this.refs.openModal, 'click', this.openModalHandler.bind(this));
    this.component.addEventListener(this.refs.modalOverlay, 'click', this.closeModalHandler.bind(this));
    this.component.addEventListener(this.refs.modalClose, 'click', this.closeModalHandler.bind(this));
    this.component.addEventListener(this.refs.modalSave, 'click', this.saveModalValueHandler.bind(this));
  }

  setOpenEventListener() {
    this.component.loadRefs(this.modal, {
      'openModal': 'single',
    });

    this.component.addEventListener(this.refs.openModal, 'click', this.openModalHandler.bind(this));
  }

  openModalHandler(event) {
    event.preventDefault();
    this.refs.modalWrapper.classList.remove('component-rendering-hidden');
  }

  updateView() {
    const template = _.isEqual(this.currentValue, this.component.defaultValue)
      ? this.openModalTemplate
      : this.component.getModalPreviewTemplate();
    this.component.setContent(this.refs.openModalWrapper, template);
    this.setOpenEventListener();
  }

  closeModal() {
    this.refs.modalWrapper.classList.add('component-rendering-hidden');
    this.updateView();
  }

  closeModalHandler(event) {
    event.preventDefault();
    this.component.setValue(this.currentValue);
    this.closeModal();
  }

  saveModalValueHandler(event) {
    event.preventDefault();
    this.currentValue = this.component.dataValue;
    this.closeModal();
  }
}
<|endoftext|>


<commit_before>import * as d3 from 'd3';
import _ from 'lodash';
import { t } from '../util/locale';
import { d3combobox } from '../lib/d3.combobox.js';
import { osmChangeset } from '../osm';
import { modeSelect } from '../modes';
import { svgIcon } from '../svg';
import { tooltip } from '../util/tooltip';
import { uiRawTagEditor } from './raw_tag_editor';
import { utilDetect } from '../util/detect';
import {
    utilDisplayName,
    utilDisplayType,
    utilEntityOrMemberSelector,
    utilRebind,
    utilTriggerEvent
} from '../util';

var changeset;
var readOnlyTags = ['created_by', 'imagery_used', 'host', 'locale'];

export function uiCommit(context) {
    var dispatch = d3.dispatch('cancel', 'save');

    function commit(selection) {
        var osm = context.connection();
        if (!osm) return;

        if (!changeset) {
            var detected = utilDetect(),
                tags = {
                    created_by: ('iD ' + context.version).substr(0, 255),
                    imagery_used: context.history().imageryUsed().join(';').substr(0, 255),
                    host: detected.host.substr(0, 255),
                    locale: detected.locale.substr(0, 255)
                };

            changeset = new osmChangeset({ tags: tags });
        }

        var changes = context.history().changes(),
            summary = context.history().difference().summary(),
            rawTagEditor = uiRawTagEditor(context).on('change', changeTags),
            comment = context.storage('comment') || '',
            commentDate = +context.storage('commentDate') || 0,
            currDate = Date.now(),
            cutoff = 2 * 86400 * 1000;   // 2 days

        // expire the stored comment if it is too old - #3947
        if (commentDate > currDate || currDate - commentDate > cutoff) {
            comment = '';
        }

        selection
            .append('div')
            .attr('class', 'header fillL')
            .append('h3')
            .text(t('commit.title'));

        var body = selection
            .append('div')
            .attr('class', 'body');

        // Fields
        var fieldSection = body
            .append('div')
            .attr('class', 'modal-section form-field commit-form');

        fieldSection
            .append('label')
            .attr('class', 'form-label')
            .text(t('commit.message_label'));

        var commentField = fieldSection
            .append('textarea')
            .attr('class', 'commit-form-comment')
            .attr('placeholder', t('commit.description_placeholder'))
            .attr('maxlength', 255)
            .property('value', comment)
            .on('input.save', change(true))
            .on('change.save', change())
            .on('blur.save', function() {
                context.storage('comment', this.value);
                context.storage('commentDate', Date.now());
            });

        commentField.node().select();

        osm.userChangesets(function (err, changesets) {
            if (err) return;

            var comments = changesets.map(function(changeset) {
                return {
                    title: changeset.tags.comment,
                    value: changeset.tags.comment
                };
            });

            commentField
                .call(d3combobox()
                    .container(context.container())
                    .caseSensitive(true)
                    .data(_.uniqBy(comments, 'title'))
                );
        });

        var commentWarning = fieldSection.append('div')
            .attr('class', 'field-warning comment-warning');

        var changeSetInfo = fieldSection.append('div')
            .attr('class', 'changeset-info');

        changeSetInfo.append('a')
            .attr('target', '_blank')
            .attr('tabindex', -1)
            .call(svgIcon('#icon-out-link', 'inline'))
            .attr('href', t('commit.about_changeset_comments_link'))
            .append('span')
            .text(t('commit.about_changeset_comments'));

        var requestReview = fieldSection
            .append('p')
            .attr('class', 'request-review')
            .text(t('commit.request_review'));

        requestReview
            .append('input')
            .attr('type', 'checkbox')
            .on('change', toggleRequestReview());

        // Warnings
        var warnings = body.selectAll('div.warning-section')
            .data([context.history().validate(changes)]);

        warnings = warnings.enter()
            .append('div')
            .attr('class', 'modal-section warning-section fillL2')
            .style('display', function(d) { return _.isEmpty(d) ? 'none' : null; })
            .merge(warnings);

        warnings
            .append('h3')
            .text(t('commit.warnings'));

        warnings
            .append('ul')
            .attr('class', 'changeset-list');

        var warningLi = warnings.select('ul').selectAll('li')
            .data(function(d) { return d; });

        warningLi = warningLi.enter()
            .append('li')
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', warningClick)
            .merge(warningLi);

        warningLi
            .call(svgIcon('#icon-alert', 'pre-text'));

        warningLi
            .append('strong')
            .text(function(d) { return d.message; });

        warningLi.filter(function(d) { return d.tooltip; })
            .call(tooltip()
                .title(function(d) { return d.tooltip; })
                .placement('top')
            );

        // Upload Explanation
        var saveSection = body
            .append('div')
            .attr('class','modal-section save-section fillL cf');

        var prose = saveSection
            .append('p')
            .attr('class', 'commit-info')
            .text(t('commit.upload_explanation'));

        osm.userDetails(function(err, user) {
            if (err) return;

            var userLink = d3.select(document.createElement('div'));

            if (user.image_url) {
                userLink
                    .append('img')
                    .attr('src', user.image_url)
                    .attr('class', 'icon pre-text user-icon');
            }

            userLink
                .append('a')
                .attr('class','user-info')
                .text(user.display_name)
                .attr('href', osm.userURL(user.display_name))
                .attr('tabindex', -1)
                .attr('target', '_blank');

            prose
                .html(t('commit.upload_explanation_with_user', { user: userLink.html() }));
        });

        // Buttons
        var buttonSection = saveSection
            .append('div')
            .attr('class', 'buttons fillL cf');

        var cancelButton = buttonSection
            .append('button')
            .attr('class', 'secondary-action col5 button cancel-button')
            .on('click.cancel', function() {
                dispatch.call('cancel');
            });

        cancelButton
            .append('span')
            .attr('class', 'label')
            .text(t('commit.cancel'));

        var saveButton = buttonSection
            .append('button')
            .attr('class', 'action col5 button save-button')
            .attr('disabled', function() {
                var n = d3.select('.commit-form textarea').node();
                return (n && n.value.length) ? null : true;
            })
            .on('click.save', function() {
                dispatch.call('save', this, changeset);
            });

        saveButton
            .append('span')
            .attr('class', 'label')
            .text(t('commit.save'));

        // Raw Tag Editor
        var tagSection = body
            .append('div')
            .attr('class', 'modal-section tag-section raw-tag-editor');

        // Changes
        var changeSection = body
            .append('div')
            .attr('class', 'commit-section modal-section fillL2');

        changeSection.append('h3')
            .text(t('commit.changes', { count: summary.length }));

        var li = changeSection
            .append('ul')
            .attr('class', 'changeset-list')
            .selectAll('li')
            .data(summary);

        li = li.enter()
            .append('li')
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', zoomToEntity)
            .merge(li);

        li.each(function(d) {
            d3.select(this)
                .call(svgIcon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType));
        });

        li.append('span')
            .attr('class', 'change-type')
            .text(function(d) { return t('commit.' + d.changeType) + ' '; });

        li.append('strong')
            .attr('class', 'entity-type')
            .text(function(d) {
                var matched = context.presets().match(d.entity, d.graph);
                return (matched && matched.name()) || utilDisplayType(d.entity.id);
            });

        li.append('span')
            .attr('class', 'entity-name')
            .text(function(d) {
                var name = utilDisplayName(d.entity) || '',
                    string = '';
                if (name !== '') string += ':';
                return string += ' ' + name;
            });

        li.style('opacity', 0)
            .transition()
            .style('opacity', 1);

        // Call change() off the bat, in case a changeset
        // comment is recovered from localStorage
        utilTriggerEvent(commentField, 'input');

        function mouseover(d) {
            if (d.entity) {
                context.surface().selectAll(
                    utilEntityOrMemberSelector([d.entity.id], context.graph())
                ).classed('hover', true);
            }
        }

        function mouseout() {
            context.surface().selectAll('.hover')
                .classed('hover', false);
        }

        function warningClick(d) {
            if (d.entity) {
                context.map().zoomTo(d.entity);
                context.enter(modeSelect(context, [d.entity.id]));
            }
        }

        function zoomToEntity(change) {
            var entity = change.entity;
            if (change.changeType !== 'deleted' &&
                context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') {
                context.map().zoomTo(entity);
                context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph()))
                    .classed('hover', true);
            }
        }

        function checkComment(comment) {
            // Save button disabled if there is no comment..
            d3.selectAll('.save-section .save-button')
                .attr('disabled', (comment.length ? null : true));

            // Warn if comment mentions Google..
            var googleWarning = commentWarning
               .html('')
               .selectAll('a')
               .data(comment.match(/google/i) ? [true] : []);

            googleWarning.exit()
                .remove();

            googleWarning.enter()
               .append('a')
               .attr('target', '_blank')
               .attr('tabindex', -1)
               .call(svgIcon('#icon-alert', 'inline'))
               .attr('href', t('commit.google_warning_link'))
               .append('span')
               .text(t('commit.google_warning'));
        }

        function change(onInput) {
            return function() {
                var comment = commentField.property('value').trim();
                if (!onInput) {
                    commentField.property('value', comment);
                }

                checkComment(comment);

                var changeset = updateChangeset({ comment: comment });
                var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();

                tagSection
                    .call(rawTagEditor
                        .expanded(expanded)
                        .readOnlyTags(readOnlyTags)
                        .tags(_.clone(changeset.tags))
                    );
            };
        }

        function toggleRequestReview() {
            var toggled = false;

            return function() {
                var changeset = updateChangeset({ review_requested: (toggled ? 'yes' : undefined) });
                var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();

                toggled = !toggled;

                tagSection
                    .call(rawTagEditor
                        .expanded(expanded)
                        .readOnlyTags(readOnlyTags)
                        .tags(_.clone(changeset.tags))
                    );
            };
        }

        function changeTags(changed) {
            if (changed.hasOwnProperty('comment')) {
                if (changed.comment === undefined) {
                    changed.comment = '';
                }
                changed.comment = changed.comment.trim();
                commentField.property('value', changed.comment);
            }
            updateChangeset(changed);
            utilTriggerEvent(commentField, 'input');
        }

        function updateChangeset(changed) {
            var tags = _.clone(changeset.tags);

            _.forEach(changed, function(v, k) {
                k = k.trim().substr(0, 255);
                if (readOnlyTags.indexOf(k) !== -1) return;

                if (k !== '' && v !== undefined) {
                    tags[k] = v.trim().substr(0, 255);
                } else {
                    delete tags[k];
                }
            });

            if (!_.isEqual(changeset.tags, tags)) {
                changeset = changeset.update({ tags: tags });
            }

            return changeset;
        }

    }

    commit.reset = function() {
        changeset = null;
    };

    return utilRebind(commit, dispatch, 'on');
}
<commit_msg>Move review_requested back to the buttons.. Make it work on tag changes<commit_after>import * as d3 from 'd3';
import _ from 'lodash';
import { t } from '../util/locale';
import { d3combobox } from '../lib/d3.combobox.js';
import { osmChangeset } from '../osm';
import { modeSelect } from '../modes';
import { svgIcon } from '../svg';
import { tooltip } from '../util/tooltip';
import { uiRawTagEditor } from './raw_tag_editor';
import { utilDetect } from '../util/detect';
import {
    utilDisplayName,
    utilDisplayType,
    utilEntityOrMemberSelector,
    utilRebind,
    utilTriggerEvent
} from '../util';

var changeset;
var readOnlyTags = ['created_by', 'imagery_used', 'host', 'locale'];

export function uiCommit(context) {
    var dispatch = d3.dispatch('cancel', 'save');

    function commit(selection) {
        var osm = context.connection();
        if (!osm) return;

        if (!changeset) {
            var detected = utilDetect(),
                tags = {
                    created_by: ('iD ' + context.version).substr(0, 255),
                    imagery_used: context.history().imageryUsed().join(';').substr(0, 255),
                    host: detected.host.substr(0, 255),
                    locale: detected.locale.substr(0, 255)
                };

            changeset = new osmChangeset({ tags: tags });
        }

        var changes = context.history().changes(),
            summary = context.history().difference().summary(),
            rawTagEditor = uiRawTagEditor(context).on('change', changeTags),
            comment = context.storage('comment') || '',
            commentDate = +context.storage('commentDate') || 0,
            currDate = Date.now(),
            cutoff = 2 * 86400 * 1000;   // 2 days

        // expire the stored comment if it is too old - #3947
        if (commentDate > currDate || currDate - commentDate > cutoff) {
            comment = '';
        }

        selection
            .append('div')
            .attr('class', 'header fillL')
            .append('h3')
            .text(t('commit.title'));

        var body = selection
            .append('div')
            .attr('class', 'body');

        // Fields
        var fieldSection = body
            .append('div')
            .attr('class', 'modal-section form-field commit-form');

        fieldSection
            .append('label')
            .attr('class', 'form-label')
            .text(t('commit.message_label'));

        var commentField = fieldSection
            .append('textarea')
            .attr('class', 'commit-form-comment')
            .attr('placeholder', t('commit.description_placeholder'))
            .attr('maxlength', 255)
            .property('value', comment)
            .on('input.save', change(true))
            .on('change.save', change())
            .on('blur.save', function() {
                context.storage('comment', this.value);
                context.storage('commentDate', Date.now());
            });

        commentField.node().select();

        osm.userChangesets(function (err, changesets) {
            if (err) return;

            var comments = changesets.map(function(changeset) {
                return {
                    title: changeset.tags.comment,
                    value: changeset.tags.comment
                };
            });

            commentField
                .call(d3combobox()
                    .container(context.container())
                    .caseSensitive(true)
                    .data(_.uniqBy(comments, 'title'))
                );
        });

        var commentWarning = fieldSection.append('div')
            .attr('class', 'field-warning comment-warning');

        var changeSetInfo = fieldSection.append('div')
            .attr('class', 'changeset-info');

        changeSetInfo.append('a')
            .attr('target', '_blank')
            .attr('tabindex', -1)
            .call(svgIcon('#icon-out-link', 'inline'))
            .attr('href', t('commit.about_changeset_comments_link'))
            .append('span')
            .text(t('commit.about_changeset_comments'));

        // Warnings
        var warnings = body.selectAll('div.warning-section')
            .data([context.history().validate(changes)]);

        warnings = warnings.enter()
            .append('div')
            .attr('class', 'modal-section warning-section fillL2')
            .style('display', function(d) { return _.isEmpty(d) ? 'none' : null; })
            .merge(warnings);

        warnings
            .append('h3')
            .text(t('commit.warnings'));

        warnings
            .append('ul')
            .attr('class', 'changeset-list');

        var warningLi = warnings.select('ul').selectAll('li')
            .data(function(d) { return d; });

        warningLi = warningLi.enter()
            .append('li')
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', warningClick)
            .merge(warningLi);

        warningLi
            .call(svgIcon('#icon-alert', 'pre-text'));

        warningLi
            .append('strong')
            .text(function(d) { return d.message; });

        warningLi.filter(function(d) { return d.tooltip; })
            .call(tooltip()
                .title(function(d) { return d.tooltip; })
                .placement('top')
            );

        // Upload Explanation
        var saveSection = body
            .append('div')
            .attr('class','modal-section save-section fillL cf');

        var prose = saveSection
            .append('p')
            .attr('class', 'commit-info')
            .text(t('commit.upload_explanation'));

        osm.userDetails(function(err, user) {
            if (err) return;

            var userLink = d3.select(document.createElement('div'));

            if (user.image_url) {
                userLink
                    .append('img')
                    .attr('src', user.image_url)
                    .attr('class', 'icon pre-text user-icon');
            }

            userLink
                .append('a')
                .attr('class','user-info')
                .text(user.display_name)
                .attr('href', osm.userURL(user.display_name))
                .attr('tabindex', -1)
                .attr('target', '_blank');

            prose
                .html(t('commit.upload_explanation_with_user', { user: userLink.html() }));
        });

        var requestReview = saveSection
            .append('p')
            .attr('class', 'request-review')
            .text(t('commit.request_review'));

        var requestReviewField = requestReview
            .append('input')
            .attr('type', 'checkbox')
            .property('checked', isReviewRequested(changeset.tags))
            .on('change', toggleRequestReview());

        // Buttons
        var buttonSection = saveSection
            .append('div')
            .attr('class', 'buttons fillL cf');

        var cancelButton = buttonSection
            .append('button')
            .attr('class', 'secondary-action col5 button cancel-button')
            .on('click.cancel', function() {
                dispatch.call('cancel');
            });

        cancelButton
            .append('span')
            .attr('class', 'label')
            .text(t('commit.cancel'));

        var saveButton = buttonSection
            .append('button')
            .attr('class', 'action col5 button save-button')
            .attr('disabled', function() {
                var n = d3.select('.commit-form textarea').node();
                return (n && n.value.length) ? null : true;
            })
            .on('click.save', function() {
                dispatch.call('save', this, changeset);
            });

        saveButton
            .append('span')
            .attr('class', 'label')
            .text(t('commit.save'));

        // Raw Tag Editor
        var tagSection = body
            .append('div')
            .attr('class', 'modal-section tag-section raw-tag-editor');

        // Changes
        var changeSection = body
            .append('div')
            .attr('class', 'commit-section modal-section fillL2');

        changeSection.append('h3')
            .text(t('commit.changes', { count: summary.length }));

        var li = changeSection
            .append('ul')
            .attr('class', 'changeset-list')
            .selectAll('li')
            .data(summary);

        li = li.enter()
            .append('li')
            .on('mouseover', mouseover)
            .on('mouseout', mouseout)
            .on('click', zoomToEntity)
            .merge(li);

        li.each(function(d) {
            d3.select(this)
                .call(svgIcon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType));
        });

        li.append('span')
            .attr('class', 'change-type')
            .text(function(d) { return t('commit.' + d.changeType) + ' '; });

        li.append('strong')
            .attr('class', 'entity-type')
            .text(function(d) {
                var matched = context.presets().match(d.entity, d.graph);
                return (matched && matched.name()) || utilDisplayType(d.entity.id);
            });

        li.append('span')
            .attr('class', 'entity-name')
            .text(function(d) {
                var name = utilDisplayName(d.entity) || '',
                    string = '';
                if (name !== '') string += ':';
                return string += ' ' + name;
            });

        li.style('opacity', 0)
            .transition()
            .style('opacity', 1);

        // Call change() off the bat, in case a changeset
        // comment is recovered from localStorage
        utilTriggerEvent(commentField, 'input');

        function mouseover(d) {
            if (d.entity) {
                context.surface().selectAll(
                    utilEntityOrMemberSelector([d.entity.id], context.graph())
                ).classed('hover', true);
            }
        }

        function mouseout() {
            context.surface().selectAll('.hover')
                .classed('hover', false);
        }

        function warningClick(d) {
            if (d.entity) {
                context.map().zoomTo(d.entity);
                context.enter(modeSelect(context, [d.entity.id]));
            }
        }

        function zoomToEntity(change) {
            var entity = change.entity;
            if (change.changeType !== 'deleted' &&
                context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') {
                context.map().zoomTo(entity);
                context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph()))
                    .classed('hover', true);
            }
        }

        function checkComment(comment) {
            // Save button disabled if there is no comment..
            d3.selectAll('.save-section .save-button')
                .attr('disabled', (comment.length ? null : true));

            // Warn if comment mentions Google..
            var googleWarning = commentWarning
               .html('')
               .selectAll('a')
               .data(comment.match(/google/i) ? [true] : []);

            googleWarning.exit()
                .remove();

            googleWarning.enter()
               .append('a')
               .attr('target', '_blank')
               .attr('tabindex', -1)
               .call(svgIcon('#icon-alert', 'inline'))
               .attr('href', t('commit.google_warning_link'))
               .append('span')
               .text(t('commit.google_warning'));
        }

        function change(onInput) {
            return function() {
                var comment = commentField.property('value').trim();
                if (!onInput) {
                    commentField.property('value', comment);
                }

                checkComment(comment);

                var changeset = updateChangeset({ comment: comment });
                var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();

                tagSection
                    .call(rawTagEditor
                        .expanded(expanded)
                        .readOnlyTags(readOnlyTags)
                        .tags(_.clone(changeset.tags))
                    );
            };
        }

        function toggleRequestReview() {
            return function() {
                var rr = requestReviewField.property('checked');
                var changeset = updateChangeset({ review_requested: (rr ? 'yes' : undefined) });
                var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();

                tagSection
                    .call(rawTagEditor
                        .expanded(expanded)
                        .readOnlyTags(readOnlyTags)
                        .tags(_.clone(changeset.tags))
                    );
            };
        }

        function changeTags(changed) {
            if (changed.hasOwnProperty('comment')) {
                if (changed.comment === undefined) {
                    changed.comment = '';
                }
                changed.comment = changed.comment.trim();
                commentField
                    .property('value', changed.comment);
            }

            if (changed.hasOwnProperty('review_requested')) {
                if (changed.review_requested === undefined) {
                    requestReviewField
                        .property('checked', false);
                } else {
                    changed.review_requested = changed.review_requested.trim();
                    requestReviewField
                        .property('checked', isReviewRequested(changed));
                }
            }

            updateChangeset(changed);
            utilTriggerEvent(commentField, 'input');
        }

        function isReviewRequested(tags) {
            var rr = tags.review_requested;
            if (rr === undefined) return false;
            rr = rr.trim().toLowerCase();
            return !(rr == '' || rr === 'no')
        }

        function updateChangeset(changed) {
            var tags = _.clone(changeset.tags);

            _.forEach(changed, function(v, k) {
                k = k.trim().substr(0, 255);
                if (readOnlyTags.indexOf(k) !== -1) return;

                if (k !== '' && v !== undefined) {
                    tags[k] = v.trim().substr(0, 255);
                } else {
                    delete tags[k];
                }
            });

            if (!_.isEqual(changeset.tags, tags)) {
                changeset = changeset.update({ tags: tags });
            }

            return changeset;
        }

    }

    commit.reset = function() {
        changeset = null;
    };

    return utilRebind(commit, dispatch, 'on');
}
<|endoftext|>


<commit_before>'use strict';

var websocket = angular.module('websocket', []);

websocket.
    factory('websocket', ['$rootScope', function($rootScope) {
        var ws, make_message, parse_message, dispatch, Service, ready, queue;
        ready = false;
        queue = [];

        //Websocket setup
        ws = new window.WebSocket("ws://automation.azurestandard.com:9000");

        ws.onopen = function () {
            console.log("Socket has been opened");
            ready = true;
            if (!_.isEmpty(queue)) {
                _.each(queue, function (item) {
                    this.send(item);
                }, ws);
            }
        }

        ws.onerror = function (error) {
            console.log("WebSocket Error ");
            console.log(error);
        };

        // Log messages from the server
        ws.onmessage = function (msg) {
            var parsed;
            parsed = parse_message(msg.data);
            //This is a 'service' level message, which all service
            //consumers should listen to, and react be reloading.
            if (parsed.topic == '/refresh') {
                window.location.reload();
            }
            dispatch.handle(parsed.topic, parsed.body)
            $rootScope.$apply();
        };

        //dispatch
        dispatch = {
            //TODO underscore has handy functions to treat arrays as
            //sets. Maybe clearer then this.
            listeners: {},
            register: function (topic, func) {
                var current;
                topic = topic.toLowerCase();
                current = this.listeners[topic] || [];
                current.push(func);
                this.listeners[topic] = _.unique(current, false, toString);
            },
            deRegister: function (topic, func) {
                this.listeners['topic'] = _.reject(
                    this.listeners['topic'], function (el) {
                        return el.toString() === func.toString();
                    });
            },
            handle: function (topic, body) {
                var key, interested;
                interested = [];
                _.each(_.keys(this.listeners),
                       function (key) {
                           if (topic.indexOf(key) === 0) {
                               interested.push(this.listeners[key])
                           }
                       },
                       this
                      );
                _.map(_.unique(_.flatten(interested), false, toString),
                      function (item) {
                          item(topic, _.extend(body));
                      });
            }
        }

        // Utilities
        make_message = function (topic, body) {
            return topic + " " + JSON.stringify(body);
        };

        parse_message = function (msg) {
            var topic, body, parts;
            parts = msg.split(" ", 1);
            topic = parts[0];
            body = JSON.parse(msg.substring(topic.length + 1));
            return {"topic": topic, "body": body};
        };

        // We return this object to anything injecting our service
        Service = {
            emit: function (topic, body) {
                this.send(make_message(topic,body));
            },

            register: function (topic, func) {
                dispatch.register(topic, func);
            },

            make_message: function (topic, body) {
                return make_message(topic, body);
            },

            send: function (msg) {
                if (ready) {
                    ws.send(msg);
                } else {
                    queue.push(msg);
                }
            }
        }

        return Service;
    }]);
<commit_msg>app/js/websocket.js: Fix 'be' -> 'by' typo in a comment<commit_after>'use strict';

var websocket = angular.module('websocket', []);

websocket.
    factory('websocket', ['$rootScope', function($rootScope) {
        var ws, make_message, parse_message, dispatch, Service, ready, queue;
        ready = false;
        queue = [];

        //Websocket setup
        ws = new window.WebSocket("ws://automation.azurestandard.com:9000");

        ws.onopen = function () {
            console.log("Socket has been opened");
            ready = true;
            if (!_.isEmpty(queue)) {
                _.each(queue, function (item) {
                    this.send(item);
                }, ws);
            }
        }

        ws.onerror = function (error) {
            console.log("WebSocket Error ");
            console.log(error);
        };

        // Log messages from the server
        ws.onmessage = function (msg) {
            var parsed;
            parsed = parse_message(msg.data);
            //This is a 'service' level message, which all service
            //consumers should listen to, and react by reloading.
            if (parsed.topic == '/refresh') {
                window.location.reload();
            }
            dispatch.handle(parsed.topic, parsed.body)
            $rootScope.$apply();
        };

        //dispatch
        dispatch = {
            //TODO underscore has handy functions to treat arrays as
            //sets. Maybe clearer then this.
            listeners: {},
            register: function (topic, func) {
                var current;
                topic = topic.toLowerCase();
                current = this.listeners[topic] || [];
                current.push(func);
                this.listeners[topic] = _.unique(current, false, toString);
            },
            deRegister: function (topic, func) {
                this.listeners['topic'] = _.reject(
                    this.listeners['topic'], function (el) {
                        return el.toString() === func.toString();
                    });
            },
            handle: function (topic, body) {
                var key, interested;
                interested = [];
                _.each(_.keys(this.listeners),
                       function (key) {
                           if (topic.indexOf(key) === 0) {
                               interested.push(this.listeners[key])
                           }
                       },
                       this
                      );
                _.map(_.unique(_.flatten(interested), false, toString),
                      function (item) {
                          item(topic, _.extend(body));
                      });
            }
        }

        // Utilities
        make_message = function (topic, body) {
            return topic + " " + JSON.stringify(body);
        };

        parse_message = function (msg) {
            var topic, body, parts;
            parts = msg.split(" ", 1);
            topic = parts[0];
            body = JSON.parse(msg.substring(topic.length + 1));
            return {"topic": topic, "body": body};
        };

        // We return this object to anything injecting our service
        Service = {
            emit: function (topic, body) {
                this.send(make_message(topic,body));
            },

            register: function (topic, func) {
                dispatch.register(topic, func);
            },

            make_message: function (topic, body) {
                return make_message(topic, body);
            },

            send: function (msg) {
                if (ready) {
                    ws.send(msg);
                } else {
                    queue.push(msg);
                }
            }
        }

        return Service;
    }]);
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import * as EditorActions from '../../../actions/editor';

const toggleStyle = {
  cursor:'pointer',
  position: 'absolute',
  bottom: 0,
};

const svgStyle = {
  position:'absolute',
  right:'50%',
  height: 25,
  width: 35
}

class CompiledSpecDisplayHeader extends React.Component {
  editVegaSpec () {
    this.props.updateVegaSpec(JSON.stringify(this.props.value, null, 2));
  }

  render () {
    if (this.props.compiledVegaSpec) {
      const toggleStyleUp = Object.assign({}, toggleStyle, {
        position: 'static'
      });
      return (

              Compiled Vega

          <svg
              style={svgStyle}>
              <polygon points="5,5 30,5 17.5,20" />
          </svg>
          <button
              onClick={this.editVegaSpec.bind(this)}
              style={{position:'absolute', right:'3%', cursor:'pointer'}}>
              Edit Vega spec
          </button>

      );
    } else {
      return (

              Compiled Vega

            <svg
              style={svgStyle}>
              <polygon points="5,20 30,20 17.5,5" />
            </svg>

      );
    }
  }
};

function mapStateToProps (state, ownProps) {
  return {
    value: state.app.vegaSpec,
    compiledVegaSpec: state.app.compiledVegaSpec,
    mode: state.app.mode,
  };
}

const mapDispatchToProps = function (dispatch) {
  return {
    updateVegaSpec: (val) => {
      dispatch(EditorActions.updateVegaSpec(val));
    },
    showCompiledVegaSpec: () => {
      dispatch(EditorActions.showCompiledVegaSpec());
    }
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(CompiledSpecDisplayHeader);
<commit_msg>Fixes #174<commit_after>import React from 'react';
import { connect } from 'react-redux';
import * as EditorActions from '../../../actions/editor';

const toggleStyle = {
  cursor:'pointer'
};

const svgStyle = {
  position:'absolute',
  right:'50%',
  height: 25,
  width: 35
}

class CompiledSpecDisplayHeader extends React.Component {
  editVegaSpec () {
    this.props.updateVegaSpec(JSON.stringify(this.props.value, null, 2));
  }

  render () {
    if (this.props.compiledVegaSpec) {
      const toggleStyleUp = Object.assign({}, toggleStyle, {
        position: 'static'
      });
      return (

              Compiled Vega

          <svg
              style={svgStyle}>
              <polygon points="5,5 30,5 17.5,20" />
          </svg>
          <button
              onClick={this.editVegaSpec.bind(this)}
              style={{position:'absolute', right:'3%', cursor:'pointer'}}>
              Edit Vega spec
          </button>

      );
    } else {
      return (

            Compiled Vega

          <svg
            style={svgStyle}>
            <polygon points="5,20 30,20 17.5,5" />
          </svg>

      );
    }
  }
};

function mapStateToProps (state, ownProps) {
  return {
    value: state.app.vegaSpec,
    compiledVegaSpec: state.app.compiledVegaSpec,
    mode: state.app.mode,
  };
}

const mapDispatchToProps = function (dispatch) {
  return {
    updateVegaSpec: (val) => {
      dispatch(EditorActions.updateVegaSpec(val));
    },
    showCompiledVegaSpec: () => {
      dispatch(EditorActions.showCompiledVegaSpec());
    }
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(CompiledSpecDisplayHeader);
<|endoftext|>


<commit_before>/* global DeviceOrientationEvent  */
var registerComponent = require('../../core/component').registerComponent;
var utils = require('../../utils/');
var bind = utils.bind;

var constants = require('../../constants/');

var MODAL_CLASS = 'a-modal';
var DIALOG_CLASS = 'a-dialog';
var DIALOG_TEXT_CLASS = 'a-dialog-text';
var DIALOG_TEXT_CONTAINER_CLASS = 'a-dialog-text-container';
var DIALOG_BUTTONS_CONTAINER_CLASS = 'a-dialog-buttons-container';
var DIALOG_BUTTON_CLASS = 'a-dialog-button';
var DIALOG_ALLOW_BUTTON_CLASS = 'a-dialog-allow-button';
var DIALOG_DENY_BUTTON_CLASS = 'a-dialog-deny-button';
var DIALOG_OK_BUTTON_CLASS = 'a-dialog-ok-button';

/**
 * UI for enabling device motion permission
 */
module.exports.Component = registerComponent('device-orientation-permission-ui', {
  schema: {enabled: {default: true}},

  init: function () {
    var self = this;

    if (!this.data.enabled) { return; }

    // Show alert on iPad if Safari is on desktop mode.
    if (utils.device.isMobileDeviceRequestingDesktopSite()) { this.showMobileDesktopModeAlert(); }

    // Browser doesn't support or doesn't require permission to DeviceOrientationEvent API.
    if (typeof DeviceOrientationEvent === 'undefined' || !DeviceOrientationEvent.requestPermission) {
      this.permissionGranted = true;
      return;
    }

    this.onDeviceMotionDialogAllowClicked = bind(this.onDeviceMotionDialogAllowClicked, this);
    this.onDeviceMotionDialogDenyClicked = bind(this.onDeviceMotionDialogDenyClicked, this);
    // Show dialog only if permission has not yet been granted.
    DeviceOrientationEvent.requestPermission().catch(function () {
      self.devicePermissionDialogEl = createPermissionDialog(
        'This immersive website requires access to your device motion sensors.',
        self.onDeviceMotionDialogAllowClicked,
        self.onDeviceMotionDialogDenyClicked);
      self.el.appendChild(self.devicePermissionDialogEl);
    }).then(function () {
      self.el.emit('deviceorientationpermissiongranted');
      self.permissionGranted = true;
    });
  },

  remove: function () {
    // This removes the modal screen
    if (this.devicePermissionDialogEl) { this.el.removeChild(this.devicePermissionDialogEl); }
  },

  onDeviceMotionDialogDenyClicked: function () {
    this.remove();
  },

  showMobileDesktopModeAlert: function () {
    var self = this;
    var safariIpadAlertEl = createAlertDialog(
      'Request the mobile version of this site to enjoy it in immersive mode.',
      function () { self.el.removeChild(safariIpadAlertEl); });
    this.el.appendChild(safariIpadAlertEl);
  },

  /**
   * Enable device motion permission when clicked.
   */
  onDeviceMotionDialogAllowClicked: function () {
    var self = this;
    this.el.emit('deviceorientationpermissionrequested');
    DeviceOrientationEvent.requestPermission().then(function (response) {
      if (response === 'granted') {
        self.el.emit('deviceorientationpermissiongranted');
        self.permissionGranted = true;
      } else {
        self.el.emit('deviceorientationpermissionrejected');
      }
      self.remove();
    }).catch(console.error);
  }
});

/**
 * Create a modal dialog that request users permission to access the Device Motion API.
 *
 * @param {function} onAllowClicked - click event handler
 * @returns {Element} Wrapper <div>.
 */
function createPermissionDialog (text, onAllowClicked, onDenyClicked) {
  var buttonsContainer;
  var denyButton;
  var acceptButton;

  buttonsContainer = document.createElement('div');
  buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS);

  // Buttons
  denyButton = document.createElement('button');
  denyButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_DENY_BUTTON_CLASS);
  denyButton.setAttribute(constants.AFRAME_INJECTED, '');
  denyButton.innerHTML = 'Deny';
  buttonsContainer.appendChild(denyButton);

  acceptButton = document.createElement('button');
  acceptButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_ALLOW_BUTTON_CLASS);
  acceptButton.setAttribute(constants.AFRAME_INJECTED, '');
  acceptButton.innerHTML = 'Allow';
  buttonsContainer.appendChild(acceptButton);

  // Ask for sensor events to be used
  acceptButton.addEventListener('click', function (evt) {
    evt.stopPropagation();
    onAllowClicked();
  });

  denyButton.addEventListener('click', function (evt) {
    evt.stopPropagation();
    onDenyClicked();
  });

  return createDialog(text, buttonsContainer);
}

function createAlertDialog (text, onOkClicked) {
  var buttonsContainer;
  var okButton;

  buttonsContainer = document.createElement('div');
  buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS);

  // Buttons
  okButton = document.createElement('button');
  okButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_OK_BUTTON_CLASS);
  okButton.setAttribute(constants.AFRAME_INJECTED, '');
  okButton.innerHTML = 'Ok';
  buttonsContainer.appendChild(okButton);

  // Ask for sensor events to be used
  okButton.addEventListener('click', function (evt) {
    evt.stopPropagation();
    onOkClicked();
  });

  return createDialog(text, buttonsContainer);
}

function createDialog (text, buttonsContainerEl) {
  var modalContainer;
  var dialog;
  var dialogTextContainer;
  var dialogText;

  modalContainer = document.createElement('div');
  modalContainer.classList.add(MODAL_CLASS);
  modalContainer.setAttribute(constants.AFRAME_INJECTED, '');

  dialog = document.createElement('div');
  dialog.className = DIALOG_CLASS;
  dialog.setAttribute(constants.AFRAME_INJECTED, '');
  modalContainer.appendChild(dialog);

  dialogTextContainer = document.createElement('div');
  dialogTextContainer.classList.add(DIALOG_TEXT_CONTAINER_CLASS);
  dialog.appendChild(dialogTextContainer);

  dialogText = document.createElement('div');
  dialogText.classList.add(DIALOG_TEXT_CLASS);
  dialogText.innerHTML = text;
  dialogTextContainer.appendChild(dialogText);

  dialog.appendChild(buttonsContainerEl);

  return modalContainer;
}
<commit_msg>Show alert dialog if the site is served over HTTP (fix #4357)<commit_after>/* global DeviceOrientationEvent, location  */
var registerComponent = require('../../core/component').registerComponent;
var utils = require('../../utils/');
var bind = utils.bind;

var constants = require('../../constants/');

var MODAL_CLASS = 'a-modal';
var DIALOG_CLASS = 'a-dialog';
var DIALOG_TEXT_CLASS = 'a-dialog-text';
var DIALOG_TEXT_CONTAINER_CLASS = 'a-dialog-text-container';
var DIALOG_BUTTONS_CONTAINER_CLASS = 'a-dialog-buttons-container';
var DIALOG_BUTTON_CLASS = 'a-dialog-button';
var DIALOG_ALLOW_BUTTON_CLASS = 'a-dialog-allow-button';
var DIALOG_DENY_BUTTON_CLASS = 'a-dialog-deny-button';
var DIALOG_OK_BUTTON_CLASS = 'a-dialog-ok-button';

/**
 * UI for enabling device motion permission
 */
module.exports.Component = registerComponent('device-orientation-permission-ui', {
  schema: {enabled: {default: true}},

  init: function () {
    var self = this;

    if (!this.data.enabled) { return; }

    if (location.hostname !== 'localhost' &&
        location.hostname !== '127.0.0.1' &&
        location.protocol === 'http:') {
      this.showHTTPAlert();
    }

    // Show alert on iPad if Safari is on desktop mode.
    if (utils.device.isMobileDeviceRequestingDesktopSite()) { this.showMobileDesktopModeAlert(); }

    // Browser doesn't support or doesn't require permission to DeviceOrientationEvent API.
    if (typeof DeviceOrientationEvent === 'undefined' || !DeviceOrientationEvent.requestPermission) {
      this.permissionGranted = true;
      return;
    }

    this.onDeviceMotionDialogAllowClicked = bind(this.onDeviceMotionDialogAllowClicked, this);
    this.onDeviceMotionDialogDenyClicked = bind(this.onDeviceMotionDialogDenyClicked, this);
    // Show dialog only if permission has not yet been granted.
    DeviceOrientationEvent.requestPermission().catch(function () {
      self.devicePermissionDialogEl = createPermissionDialog(
        'This immersive website requires access to your device motion sensors.',
        self.onDeviceMotionDialogAllowClicked,
        self.onDeviceMotionDialogDenyClicked);
      self.el.appendChild(self.devicePermissionDialogEl);
    }).then(function () {
      self.el.emit('deviceorientationpermissiongranted');
      self.permissionGranted = true;
    });
  },

  remove: function () {
    // This removes the modal screen
    if (this.devicePermissionDialogEl) { this.el.removeChild(this.devicePermissionDialogEl); }
  },

  onDeviceMotionDialogDenyClicked: function () {
    this.remove();
  },

  showMobileDesktopModeAlert: function () {
    var self = this;
    var safariIpadAlertEl = createAlertDialog(
      'Request the mobile version of this site to enjoy it in immersive mode.',
      function () { self.el.removeChild(safariIpadAlertEl); });
    this.el.appendChild(safariIpadAlertEl);
  },

  showHTTPAlert: function () {
    var self = this;
    var httpAlertEl = createAlertDialog(
      'Access this site over HTTPS to enter VR mode and grant access to the device sensors.',
      function () { self.el.removeChild(httpAlertEl); });
    this.el.appendChild(httpAlertEl);
  },

  /**
   * Enable device motion permission when clicked.
   */
  onDeviceMotionDialogAllowClicked: function () {
    var self = this;
    this.el.emit('deviceorientationpermissionrequested');
    DeviceOrientationEvent.requestPermission().then(function (response) {
      if (response === 'granted') {
        self.el.emit('deviceorientationpermissiongranted');
        self.permissionGranted = true;
      } else {
        self.el.emit('deviceorientationpermissionrejected');
      }
      self.remove();
    }).catch(console.error);
  }
});

/**
 * Create a modal dialog that request users permission to access the Device Motion API.
 *
 * @param {function} onAllowClicked - click event handler
 * @returns {Element} Wrapper <div>.
 */
function createPermissionDialog (text, onAllowClicked, onDenyClicked) {
  var buttonsContainer;
  var denyButton;
  var acceptButton;

  buttonsContainer = document.createElement('div');
  buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS);

  // Buttons
  denyButton = document.createElement('button');
  denyButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_DENY_BUTTON_CLASS);
  denyButton.setAttribute(constants.AFRAME_INJECTED, '');
  denyButton.innerHTML = 'Deny';
  buttonsContainer.appendChild(denyButton);

  acceptButton = document.createElement('button');
  acceptButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_ALLOW_BUTTON_CLASS);
  acceptButton.setAttribute(constants.AFRAME_INJECTED, '');
  acceptButton.innerHTML = 'Allow';
  buttonsContainer.appendChild(acceptButton);

  // Ask for sensor events to be used
  acceptButton.addEventListener('click', function (evt) {
    evt.stopPropagation();
    onAllowClicked();
  });

  denyButton.addEventListener('click', function (evt) {
    evt.stopPropagation();
    onDenyClicked();
  });

  return createDialog(text, buttonsContainer);
}

function createAlertDialog (text, onOkClicked) {
  var buttonsContainer;
  var okButton;

  buttonsContainer = document.createElement('div');
  buttonsContainer.classList.add(DIALOG_BUTTONS_CONTAINER_CLASS);

  // Buttons
  okButton = document.createElement('button');
  okButton.classList.add(DIALOG_BUTTON_CLASS, DIALOG_OK_BUTTON_CLASS);
  okButton.setAttribute(constants.AFRAME_INJECTED, '');
  okButton.innerHTML = 'Ok';
  buttonsContainer.appendChild(okButton);

  // Ask for sensor events to be used
  okButton.addEventListener('click', function (evt) {
    evt.stopPropagation();
    onOkClicked();
  });

  return createDialog(text, buttonsContainer);
}

function createDialog (text, buttonsContainerEl) {
  var modalContainer;
  var dialog;
  var dialogTextContainer;
  var dialogText;

  modalContainer = document.createElement('div');
  modalContainer.classList.add(MODAL_CLASS);
  modalContainer.setAttribute(constants.AFRAME_INJECTED, '');

  dialog = document.createElement('div');
  dialog.className = DIALOG_CLASS;
  dialog.setAttribute(constants.AFRAME_INJECTED, '');
  modalContainer.appendChild(dialog);

  dialogTextContainer = document.createElement('div');
  dialogTextContainer.classList.add(DIALOG_TEXT_CONTAINER_CLASS);
  dialog.appendChild(dialogTextContainer);

  dialogText = document.createElement('div');
  dialogText.classList.add(DIALOG_TEXT_CLASS);
  dialogText.innerHTML = text;
  dialogTextContainer.appendChild(dialogText);

  dialog.appendChild(buttonsContainerEl);

  return modalContainer;
}
<|endoftext|>