<commit_before>/**
 * @file MaterialDatePicker component
 * @author sunday(sunday.wei@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import _ from 'lodash';
import classNames from 'classnames';
import Position from '../_statics/Position';

import MaterialDatePickerTextField from '../_MaterialDatePickerTextField';
import DayPicker from '../_DayPicker';
import MonthPicker from '../_MonthPicker';
import YearPicker from '../_YearPicker';
import Popup from '../Popup';
import Theme from '../Theme';

class MaterialDatePicker extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.validValue = true;
        const defaultValue = props.value ? props.value: moment().format('YYYY-MM-DD');
        this.state = {
            value: props.value,
            popupVisible: false,
            year: moment(defaultValue).format('YYYY'),
            month: moment(defaultValue).format('MM'),
            day: moment(defaultValue).format('DD'),
            datePickerLevel: 'day',
            marginLeft: 0
        };

        this.textFieldChangeHandle = ::this.textFieldChangeHandle;
        this.togglePopup = ::this.togglePopup;
        this.closePopup = ::this.closePopup;
        this.datePickerChangeHandle = ::this.datePickerChangeHandle;
        this.yearPickerChangeHandle = ::this.yearPickerChangeHandle;
        this.monthPickerChangeHandle = ::this.monthPickerChangeHandle;
        this.dayPickerChangeHandle = ::this.dayPickerChangeHandle;
        this.todayHandle = ::this.todayHandle;

    }

    datePickerChangeHandle(selectLevel) {
        this.setState({
            datePickerLevel: selectLevel
        });
    }

    textFieldChangeHandle(text) {
        const {minValue, maxValue, dateFormat} = this.props;
        if (text && text.length) {
            const flag = moment(text, this.props.dateFormat, true).isValid();
            if (flag) {
                if (minValue && moment(text).isBefore(minValue) || maxValue && moment(text).isAfter(maxValue)) {

                } else {
                    const year = moment(text).format('YYYY'),
                        month = moment(text).format('MM'),
                        day = moment(text).format('DD');
                    this.setState({
                        value: moment(text, dateFormat),
                        year: year,
                        month: month,
                        day: day
                    });
                }
            } else {

            }
        } else {
            this.setState({
                value: ''
            });
        }
    }

    dayPickerChangeHandle(date) {
        const {autoClose, dateFormat} = this.props;
        let state = _.cloneDeep(this.state);
        state.value = moment(date.time, dateFormat);
        state.year = date.year;
        state.month = date.month;
        state.day = date.day;
        state.popupVisible = !autoClose;
        if (state.popupVisible) {
            this.setState(state);
        } else {
            !this.props.disabled && this.setState(state, () => {
                this.props.onChange && this.props.onChange(moment(date.time).format(dateFormat));
            });
        }
    }

    monthPickerChangeHandle(date) {
        this.setState({
            datePickerLevel: 'day',
            year: date.year,
            month: date.month
        });
    }

    yearPickerChangeHandle(year) {
        this.setState({
            datePickerLevel: 'month',
            year: year
        });
    }

    todayHandle() {
        const {dateFormat} = this.props;
        const year = moment().format('YYYY'),
            month = moment().format('MM'),
            day = moment().format('DD');
        let timer = moment(moment(), dateFormat);
        this.setState({
            value: timer,
            year: year,
            month: month,
            day: day
        });
    }

    togglePopup(e) {
        if (this.validValue) {
            this.setState({
                popupVisible: !this.state.popupVisible,
                triggerEl: e.target
            });
        }
    }

    closePopup() {
        const {value} = this.state;
        !this.props.disabled && this.setState({
            popupVisible: false
        }, () => {
            this.props.onChange && this.props.onChange(value && moment(value).format(this.props.dateFormat));
        });
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.props.value || nextProps.dateFormat !== this.props.dateFormat) {
            this.setState({
                value: moment(nextProps.value, nextProps.dateFormat),
                dateFormat: nextProps.dateFormat,
                year: moment(nextProps.value).format('YYYY'),
                month: moment(nextProps.value).format('MM'),
                day: moment(nextProps.value).format('DD')
            });
        }
    }

    componentDidMount() {
        // debugger
        const {value, dateFormat} = this.props;
        let state = _.cloneDeep(this.state);
        if (value) {
            if (moment(value, dateFormat).isValid()) {
                const year = moment(value).format('YYYY'),
                    month = moment(value).format('MM'),
                    day = moment(value).format('DD');
                state.value = moment(value, dateFormat);
                state.year = year;
                state.month = month;
                state.day = day;
                this.setState(state);
            } else {
                this.validValue = false;
                console.error('Invalid date');
            }
        }
    }

    render() {

        const {
                className, name, placeholder, dateFormat, maxValue, minValue, label, isLabelAnimate, isFooter, position
            } = this.props,
            {value, popupVisible, datePickerLevel, year, month, day, triggerEl, isHover, isFocus} = this.state,

            pickerClassName = classNames('date-picker', {
                [className]: className
            }),

            textValue = value && moment(value).format(dateFormat);

        return (

                <MaterialDatePickerTextField
                    ref="datePickerInput"
                    name={name}
                    placeholder={placeholder}
                    value={textValue}
                    readOnly={!popupVisible}
                    clearButtonVisible={false}
                    isFocusedSelectAll={false}
                    popupVisible={popupVisible}
                    label={label}
                    isLabelAnimate={isLabelAnimate}
                    onChange={this.textFieldChangeHandle}
                    onTouchTap={e => {
                        this.togglePopup(e);
                    }}/>

                <Popup className={`material-date-picker-popup`}
                       visible={popupVisible}
                       triggerEl={triggerEl}
                       position={position}
                       hasTriangle={false}
                       onRequestClose={() => {
                           this.closePopup(3);
                       }}>

                    {
                        datePickerLevel == 'day' ?
                            <DayPicker
                                value={value}
                                dateFormat={dateFormat}
                                year={year}
                                month={month}
                                day={day}
                                maxValue={maxValue}
                                minValue={minValue}
                                isFooter={isFooter}
                                onChange={this.dayPickerChangeHandle}
                                previousClick={this.datePickerChangeHandle}/>
                            : (
                                datePickerLevel == 'month' ?
                                    <MonthPicker
                                        value={value}
                                        year={year}
                                        month={month}
                                        day={day}
                                        maxValue={maxValue}
                                        minValue={minValue}
                                        onChange={this.monthPickerChangeHandle}
                                        previousClick={this.datePickerChangeHandle}/>
                                    :
                                    <YearPicker
                                        value={value}
                                        year={year}
                                        month={month}
                                        day={day}
                                        maxValue={maxValue}
                                        minValue={minValue}
                                        onChange={this.yearPickerChangeHandle}/>
                            )
                    }

                    {
                        isFooter && datePickerLevel == 'day' ?

                                {
                                    (minValue && moment(this.props.value).isBefore(minValue)) || (maxValue && moment(maxValue).isBefore(this.props.value)) ?

                                            Today

                                        :

                                            Today

                                }

                            :
                            null
                    }

                </Popup>

        );
    }
};

MaterialDatePicker.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Date picker input name.
     */
    name: PropTypes.string,

    /**
     * This is the initial date value of the component.
     */
    value: PropTypes.any,

    /**
     * The label of the date picker.
     */
    label: PropTypes.any,

    /**
     * The animate of the date picker.
     */
    isLabelAnimate: PropTypes.bool,

    /**
     * The ending of a range of valid dates. The range includes the endDate.
     */
    maxValue: PropTypes.any,

    /**
     * The beginning of a range of valid dates. The range includes the startDate.
     */
    minValue: PropTypes.any,

    /**
     * DatePicker textField element placeholder.
     */
    placeholder: PropTypes.string,

    /**
     * Date format.
     */
    dateFormat: PropTypes.string,

    /**
     * If true,hide date display.
     */
    autoClose: PropTypes.bool,

    /**
     * Callback function that is fired when the date value changes.
     */
    onChange: PropTypes.func

};

MaterialDatePicker.defaultProps = {
    className: '',
    style: null,
    name: '',
    maxValue: '',
    minValue: '',
    placeholder: 'Date',
    dateFormat: 'YYYY-MM-DD',
    isLabelAnimate: true,
    autoClose: true,
    isFooter: true,
    position:Position.BOTTOM_LEFT
};

export default MaterialDatePicker;<commit_msg>update MaterialDatePicker<commit_after>/**
 * @file MaterialDatePicker component
 * @author sunday(sunday.wei@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import _ from 'lodash';
import classNames from 'classnames';

import DatePickerTextField from '../_MaterialDatePickerTextField';
import DayPicker from '../_DayPicker';
import MonthPicker from '../_MonthPicker';
import YearPicker from '../_YearPicker';
import Popup from '../Popup';
import Theme from '../Theme';

import Position from '../_statics/Position';

class MaterialDatePicker extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.validValue = true;
        const defaultValue = props.value ? props.value : moment().format('YYYY-MM-DD');
        this.state = {
            value: props.value,
            popupVisible: false,
            year: moment(defaultValue).format('YYYY'),
            month: moment(defaultValue).format('MM'),
            day: moment(defaultValue).format('DD'),
            datePickerLevel: 'day',
            marginLeft: 0
        };

        this.textFieldChangeHandle = ::this.textFieldChangeHandle;
        this.togglePopup = ::this.togglePopup;
        this.closePopup = ::this.closePopup;
        this.datePickerChangeHandle = ::this.datePickerChangeHandle;
        this.yearPickerChangeHandle = ::this.yearPickerChangeHandle;
        this.monthPickerChangeHandle = ::this.monthPickerChangeHandle;
        this.dayPickerChangeHandle = ::this.dayPickerChangeHandle;
        this.todayHandle = ::this.todayHandle;

    }

    datePickerChangeHandle(selectLevel) {
        this.setState({
            datePickerLevel: selectLevel
        });
    }

    textFieldChangeHandle(text) {
        const {minValue, maxValue, dateFormat} = this.props;
        if (text && text.length) {
            const flag = moment(text, this.props.dateFormat, true).isValid();
            if (flag) {
                if (minValue && moment(text).isBefore(minValue) || maxValue && moment(text).isAfter(maxValue)) {

                } else {
                    const year = moment(text).format('YYYY'),
                        month = moment(text).format('MM'),
                        day = moment(text).format('DD');
                    this.setState({
                        value: moment(text, dateFormat),
                        year: year,
                        month: month,
                        day: day
                    });
                }
            } else {

            }
        } else {
            this.setState({
                value: ''
            });
        }
    }

    dayPickerChangeHandle(date) {
        const {autoClose, dateFormat} = this.props;
        let state = _.cloneDeep(this.state);
        state.value = moment(date.time, dateFormat);
        state.year = date.year;
        state.month = date.month;
        state.day = date.day;
        state.popupVisible = !autoClose;
        if (state.popupVisible) {
            this.setState(state);
        } else {
            !this.props.disabled && this.setState(state, () => {
                this.props.onChange && this.props.onChange(moment(date.time).format(dateFormat));
            });
        }
    }

    monthPickerChangeHandle(date) {
        this.setState({
            datePickerLevel: 'day',
            year: date.year,
            month: date.month
        });
    }

    yearPickerChangeHandle(year) {
        this.setState({
            datePickerLevel: 'month',
            year: year
        });
    }

    todayHandle() {
        const {dateFormat} = this.props;
        const year = moment().format('YYYY'),
            month = moment().format('MM'),
            day = moment().format('DD');
        let timer = moment(moment(), dateFormat);
        this.setState({
            value: timer,
            year: year,
            month: month,
            day: day
        });
    }

    togglePopup(e) {
        if (this.validValue) {
            this.setState({
                popupVisible: !this.state.popupVisible,
                triggerEl: e.target
            });
        }
    }

    closePopup() {
        const {value} = this.state;
        !this.props.disabled && this.setState({
            popupVisible: false
        }, () => {
            this.props.onChange && this.props.onChange(value && moment(value).format(this.props.dateFormat));
        });
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.props.value || nextProps.dateFormat !== this.props.dateFormat) {
            this.setState({
                value: moment(nextProps.value, nextProps.dateFormat),
                dateFormat: nextProps.dateFormat,
                year: moment(nextProps.value).format('YYYY'),
                month: moment(nextProps.value).format('MM'),
                day: moment(nextProps.value).format('DD')
            });
        }
    }

    componentDidMount() {
        // debugger
        const {value, dateFormat} = this.props;
        let state = _.cloneDeep(this.state);
        if (value) {
            if (moment(value, dateFormat).isValid()) {
                const year = moment(value).format('YYYY'),
                    month = moment(value).format('MM'),
                    day = moment(value).format('DD');
                state.value = moment(value, dateFormat);
                state.year = year;
                state.month = month;
                state.day = day;
                this.setState(state);
            } else {
                this.validValue = false;
                console.error('Invalid date');
            }
        }
    }

    render() {

        const {
                className, name, placeholder, dateFormat, maxValue, minValue, label, isLabelAnimate, isFooter, position
            } = this.props,
            {value, popupVisible, datePickerLevel, year, month, day, triggerEl, isHover, isFocus} = this.state,

            pickerClassName = classNames('date-picker', {
                [className]: className
            }),

            textValue = value && moment(value).format(dateFormat);

        return (

                <DatePickerTextField ref="datePickerInput"
                                     name={name}
                                     placeholder={placeholder}
                                     value={textValue}
                                     readOnly={!popupVisible}
                                     clearButtonVisible={false}
                                     isFocusedSelectAll={false}
                                     popupVisible={popupVisible}
                                     label={label}
                                     isLabelAnimate={isLabelAnimate}
                                     onChange={this.textFieldChangeHandle}
                                     onTouchTap={e => {
                                         this.togglePopup(e);
                                     }}/>

                <Popup className="material-date-picker-popup"
                       visible={popupVisible}
                       triggerEl={triggerEl}
                       position={position}
                       hasTriangle={false}
                       onRequestClose={() => {
                           this.closePopup(3);
                       }}>

                    {
                        datePickerLevel == 'day' ?
                            <DayPicker value={value}
                                       dateFormat={dateFormat}
                                       year={year}
                                       month={month}
                                       day={day}
                                       maxValue={maxValue}
                                       minValue={minValue}
                                       isFooter={isFooter}
                                       onChange={this.dayPickerChangeHandle}
                                       previousClick={this.datePickerChangeHandle}/>
                            : (
                                datePickerLevel == 'month' ?
                                    <MonthPicker value={value}
                                                 year={year}
                                                 month={month}
                                                 day={day}
                                                 maxValue={maxValue}
                                                 minValue={minValue}
                                                 onChange={this.monthPickerChangeHandle}
                                                 previousClick={this.datePickerChangeHandle}/>
                                    :
                                    <YearPicker value={value}
                                                year={year}
                                                month={month}
                                                day={day}
                                                maxValue={maxValue}
                                                minValue={minValue}
                                                onChange={this.yearPickerChangeHandle}/>
                            )
                    }

                    {
                        isFooter && datePickerLevel == 'day' ?

                                {
                                    (minValue && moment(this.props.value).isBefore(minValue))
                                    || (maxValue && moment(maxValue).isBefore(this.props.value)) ?

                                            Today

                                        :

                                            Today

                                }

                            :
                            null
                    }

                </Popup>

        );
    }
};

MaterialDatePicker.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Date picker input name.
     */
    name: PropTypes.string,

    /**
     * This is the initial date value of the component.
     */
    value: PropTypes.any,

    /**
     * The label of the date picker.
     */
    label: PropTypes.any,

    /**
     * The animate of the date picker.
     */
    isLabelAnimate: PropTypes.bool,

    /**
     * The ending of a range of valid dates. The range includes the endDate.
     */
    maxValue: PropTypes.any,

    /**
     * The beginning of a range of valid dates. The range includes the startDate.
     */
    minValue: PropTypes.any,

    /**
     * DatePicker textField element placeholder.
     */
    placeholder: PropTypes.string,

    /**
     * Date format.
     */
    dateFormat: PropTypes.string,

    /**
     * If true,hide date display.
     */
    autoClose: PropTypes.bool,

    /**
     * Callback function that is fired when the date value changes.
     */
    onChange: PropTypes.func

};

MaterialDatePicker.defaultProps = {
    className: '',
    style: null,
    name: '',
    maxValue: '',
    minValue: '',
    placeholder: 'Date',
    dateFormat: 'YYYY-MM-DD',
    isLabelAnimate: true,
    autoClose: true,
    isFooter: true,
    position: Position.BOTTOM_LEFT
};

export default MaterialDatePicker;<|endoftext|>


<commit_before>/**
 * ngAuth 0.0.1
 * (c) 2014 Sahat Yalkabov <sahat@me.com>
 * License: MIT
 */

(function(window, angular, undefined) {
  'use strict';

  angular.module('ngAuth', [])
    .provider('Auth', function() {

      var config = this.config = {
        logoutRedirect: '/',
        loginRedirect: '/',
        loginUrl: '/auth/login',
        signupUrl: '/auth/signup',
        signupRedirect: '/login',
        userGlobal: 'currentUser',
        providers: {
          facebook: {
            url: '/auth/facebook',
            authorizationEndpoint: 'https://www.facebook.com/dialog/oauth',
            redirectUri: 'http://localhost:3000',
            scope: 'email',
            requiredUrlParams: ['display'],
            display: 'popup'
          },
          google: {
            url: '/auth/google',
            authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
            redirectUri: 'http://localhost:3000',
            scope: 'openid profile email',
            requiredUrlParams: ['display'],
            display: 'popup'
          },
          linkedin: {
            authorizationEndpoint: 'https://www.linkedin.com/uas/oauth2/authorization',
            redirectUri: 'http://localhost:3000',
            requiredUrlParams: ['state'],
            state: 'STATE'
          }
        }
      };

      // TODO: defineProperty setter on scope
      // TODO: $state = md5(rand());
      // TODO: Use requiredUrlParams instead of passing all properties
      // TODO: pass scope delimiter
      // TODO: default responseType to code if token is obtained on server

      return {
        setProvider: function(params) {
          config.providers[params.name] = config.providers[params.name] || {};
          angular.extend(config.providers[params.name], params);
        },

        $get: function($interval, $timeout, $http, $location, $rootScope, $alert, $q, $injector, $window) {

          // BEGIN OAUTH

          // Popup window.
          var Popup = function() {
            this.popup = null;
          };

          Popup.prototype.open = function(url, keys, options) {
            console.log(url)
            var deferred = $q.defer();
            // TODO: refactor
            // todo wildcard star
            var optionsString = stringifyOptions(prepareOptions(options || {}));
            this.popup = $window.open(url, '*', optionsString);
            this.popup.focus();

            this.createPostMessageHandler(deferred);
            this.pollPopup(deferred);

            return deferred.promise;
          };

          Popup.prototype.pollPopup = function(deferred) {
            var intervalHandler = function() {
              if (this.popup.closed) {
                $interval.cancel(this.polling);
                deferred.reject('Popup was closed by the user.');
              }
              this.requestAuthorizationCode();
            }.bind(this);
            this.polling = $interval(intervalHandler, 35);
          };

          Popup.prototype.requestAuthorizationCode = function() {
            var query = this.popup.location.search.substring(1);
            if (query.indexOf('code') > -1) {
              var code = this.parseQueryString(query).code;
              $window.postMessage(code, $window.location.origin);
              $interval.cancel(this.polling);
              this.popup.close();
            }
          };

          Popup.prototype.createPostMessageHandler = function(deferred) {
            $window.addEventListener('message', function(event) {
              deferred.resolve(event.data);
            }, false);
          };

          Popup.prototype.parseQueryString = function(keyValue) {
            var obj = {}, key, value;
            angular.forEach((keyValue || '').split('&'), function(keyValue) {
              if (keyValue) {
                value = keyValue.split('=');
                key = decodeURIComponent(value[0]);
                obj[key] = angular.isDefined(value[1]) ? decodeURIComponent(value[1]) : true;
              }
            });
            return obj;
          };

          Popup.prototype.close = function() {
            if (this.popup) {
              this.popup.close();
              this.popup = null;
            }
          };

          function prepareOptions(options) {
            var width = options.width || 500;
            var height = options.height || 500;
            return angular.extend({
              left: ((screen.width / 2) - (width / 2)),
              top: ((screen.height / 2) - (height / 2)),
              width: width,
              height: height
            }, options);
          }

          function stringifyOptions(options) {
            var optionsStrings = [];
            for (var key in options) {
              if (options.hasOwnProperty(key)) {
                var value;
                switch (options[key]) {
                  case true:
                    value = '1';
                    break;
                  case false:
                    value = '0';
                    break;
                  default:
                    value = options[key];
                }
                optionsStrings.push(
                  key + '=' + value
                );
              }
            }
            return optionsStrings.join(',');
          }

          var defaultUrlParams = ['response_type', 'client_id', 'redirect_uri'];

          var Oauth2 = function(config) {
            //if (this instanceof CustomObject)

            this.name = config.name;
            this.clientId = config.clientId;
            this.scope = config.scope;
            this.authorizationEndpoint = config.authorizationEndpoint;
            this.redirectUri = config.redirectUri;
            this.responseType = 'code';
            this.requiredUrlParams = defaultUrlParams.concat(config.requiredUrlParams);
            this.optionalUrlParams = ['scope'];
          };

          Oauth2.prototype.camelCase = function(name) {
            return name.replace(/([\:\-\_]+(.))/g, function(_, separator, letter, offset) {
              return offset ? letter.toUpperCase() : letter;
            });
          };

          Oauth2.prototype.open = function() {
            var deferred = $q.defer();
            var url = this.buildUrl();
            var popup = new Popup();

            popup.open(url).then(function(code) {
              this.exchangeForToken(code).then(function(response) {
                deferred.resolve(response.data);
              });
            }.bind(this));

            return deferred.promise;
          };

          Oauth2.prototype.exchangeForToken = function(code) {
            var params = {
              clientId: config.providers[this.name].clientId,
              redirectUri: $window.location.origin + '/',
              code: code
            };

            return $http.post('/auth/facebook', params);
          };

          Oauth2.prototype.buildUrl = function() {
            var baseUrl = this.authorizationEndpoint;
            var qs = this.buildQueryString();
            return [baseUrl, qs].join('?');
          };

          Oauth2.prototype.buildQueryString = function() {
            var obj = this;
            var keyValuePairs = [];

            angular.forEach(this.requiredUrlParams, function(paramName) {
              var camelizedName = obj.camelCase(paramName);
              var paramValue = obj[camelizedName];
              keyValuePairs.push([paramName, paramValue]);
            });

            angular.forEach(this.optionalUrlParams, function(paramName) {
              var camelizedName = obj.camelCase(paramName);
              var paramValue = obj[camelizedName];
              keyValuePairs.push([paramName, paramValue]);
            });

            return keyValuePairs.map(function(pair) {
              return pair.join('=');
            }).join('&');
          };

          Oauth2.createProvider = function(providerName) {
            var providerOptions = config.providers[providerName];
            var provider = new Oauth2(providerOptions);
            return provider;
          };

          // END OAUTH

          var token = $window.localStorage.token;
          if (token) {
            var payload = JSON.parse($window.atob(token.split('.')[1]));
            $rootScope[config.userGlobal] = payload.user;
          }

          return {
            authenticate: function(providerName, options) {
              if (!providerName) {
                return $q.reject('Expected a provider named \'' + providerName + '\', did you forget to add it?');
              }

              var provider = Oauth2.createProvider(providerName);

              var deferred = $q.defer();

              provider.open(options).then(function(data) {
                console.log('OPENED')
                deferred.resolve(data);
              });

              return deferred.promise;
            },

            loginOauth: function(provider) {
              provider = provider.trim().toLowerCase();

              switch (provider) {
                case 'facebook':
                  var scope = config.providers.facebook.scope.join(',');
                  FB.login(function(response) {
                    FB.api('/me', function(profile) {
                      // TODO normalize return properties like passport
                      var data = {
                        accessToken: response.authResponse.accessToken,
                        signedRequest: response.authResponse.signedRequest,
                        profile: profile
                      };
                      $http.post(config.providers.facebook.url, data).success(function(token) {
                        var payload = JSON.parse($window.atob(token.split('.')[1]));
                        $window.localStorage.token = token;
                        $rootScope[config.userGlobal] = payload.user;
                        $location.path(config.loginRedirect);
                      });
                    });
                  }, {scope: scope});
                  break;
                case 'google':

                  break;
                case 'linkedin':
                  break;
                default:
                  break;
              }
            },
            login: function(user) {
              return $http.post(config.loginUrl, user).success(function(data) {
                $window.localStorage.token = data.token;
                var payload = JSON.parse($window.atob(data.token.split('.')[1]));
                $rootScope[config.userGlobal] = payload.user;
                $location.path(config.loginRedirect);
              });
            },
            signup: function(user) {
              return $http.post(config.signupUrl, user).success(function() {
                $location.path(config.signupRedirect);
              });
            },
            logout: function() {
              delete $window.localStorage.token;
              $rootScope[config.userGlobal] = null;
              $location.path(config.logoutRedirect);
            },
            isAuthenticated: function() {
              return $rootScope[config.userGlobal];
            }
          };
        }
      };
    })
    .factory('authInterceptor', function($q, $window, $location) {
      return {
        request: function(config) {
          if ($window.localStorage.token) {
            config.headers.Authorization = 'Bearer ' + $window.localStorage.token;
          }
          return config;
        },
        responseError: function(response) {
          if (response.status === 401 || response.status === 403) {
            $location.path('/login');
          }
          return $q.reject(response);
        }
      }
    })
    .config(function($httpProvider) {
      $httpProvider.interceptors.push('authInterceptor');
    })
    .run(function($rootScope, $location) {
      $rootScope.$on('$routeChangeStart', function(event, current, previous) {
        if ($rootScope.currentUser &&
          (current.originalPath === '/login' || current.originalPath === '/signup')) {
          $location.path('/');
        }
        if (current.authenticated && !$rootScope.currentUser) {
          $location.path('/login');
        }
      });
    });
})(window, window.angular);

<commit_msg>oauth2 default params are now a separate property and there is a seprate for loop to construct a querystring<commit_after>/**
 * ngAuth 0.0.1
 * (c) 2014 Sahat Yalkabov <sahat@me.com>
 * License: MIT
 */

(function(window, angular, undefined) {
  'use strict';

  angular.module('ngAuth', [])
    .provider('Auth', function() {

      var config = this.config = {
        logoutRedirect: '/',
        loginRedirect: '/',
        loginUrl: '/auth/login',
        signupUrl: '/auth/signup',
        signupRedirect: '/login',
        userGlobal: 'currentUser',
        providers: {
          facebook: {
            url: '/auth/facebook',
            authorizationEndpoint: 'https://www.facebook.com/dialog/oauth',
            redirectUri: 'http://localhost:3000',
            scope: 'email',
            requiredUrlParams: ['display'],
            display: 'popup'
          },
          google: {
            url: '/auth/google',
            authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
            redirectUri: 'http://localhost:3000',
            scope: 'openid profile email',
            requiredUrlParams: ['scope'],
            optionalUrlParams: ['display', 'state'],
            display: 'popup',
            state: 'STATE'
          },
          linkedin: {
            authorizationEndpoint: 'https://www.linkedin.com/uas/oauth2/authorization',
            redirectUri: 'http://localhost:3000',
            requiredUrlParams: ['state'],
            state: 'STATE'
          }
        }
      };

      // TODO: defineProperty setter on scope
      // TODO: $state = md5(rand());
      // TODO: Use requiredUrlParams instead of passing all properties
      // TODO: pass scope delimiter
      // TODO: default responseType to code if token is obtained on server

      return {
        setProvider: function(params) {
          config.providers[params.name] = config.providers[params.name] || {};
          angular.extend(config.providers[params.name], params);
        },

        $get: function($interval, $timeout, $http, $location, $rootScope, $alert, $q, $injector, $window) {

          // BEGIN OAUTH

          // Popup window.
          var Popup = function() {
            this.popup = null;
          };

          Popup.prototype.open = function(url, keys, options) {
            console.log(url)
            var deferred = $q.defer();
            // TODO: refactor
            // todo wildcard star
            var optionsString = stringifyOptions(prepareOptions(options || {}));
            this.popup = $window.open(url, '*', optionsString);
            this.popup.focus();

            this.createPostMessageHandler(deferred);
            this.pollPopup(deferred);

            return deferred.promise;
          };

          Popup.prototype.pollPopup = function(deferred) {
            var intervalHandler = function() {
              if (this.popup.closed) {
                $interval.cancel(this.polling);
                deferred.reject('Popup was closed by the user.');
              }
              this.requestAuthorizationCode();
            }.bind(this);
            this.polling = $interval(intervalHandler, 35);
          };

          Popup.prototype.requestAuthorizationCode = function() {
            var query = this.popup.location.search.substring(1);
            if (query.indexOf('code') > -1) {
              var code = this.parseQueryString(query).code;
              $window.postMessage(code, $window.location.origin);
              $interval.cancel(this.polling);
              this.popup.close();
            }
          };

          Popup.prototype.createPostMessageHandler = function(deferred) {
            $window.addEventListener('message', function(event) {
              deferred.resolve(event.data);
            }, false);
          };

          Popup.prototype.parseQueryString = function(keyValue) {
            var obj = {}, key, value;
            angular.forEach((keyValue || '').split('&'), function(keyValue) {
              if (keyValue) {
                value = keyValue.split('=');
                key = decodeURIComponent(value[0]);
                obj[key] = angular.isDefined(value[1]) ? decodeURIComponent(value[1]) : true;
              }
            });
            return obj;
          };

          Popup.prototype.close = function() {
            if (this.popup) {
              this.popup.close();
              this.popup = null;
            }
          };

          function prepareOptions(options) {
            var width = options.width || 500;
            var height = options.height || 500;
            return angular.extend({
              left: ((screen.width / 2) - (width / 2)),
              top: ((screen.height / 2) - (height / 2)),
              width: width,
              height: height
            }, options);
          }

          function stringifyOptions(options) {
            var optionsStrings = [];
            for (var key in options) {
              if (options.hasOwnProperty(key)) {
                var value;
                switch (options[key]) {
                  case true:
                    value = '1';
                    break;
                  case false:
                    value = '0';
                    break;
                  default:
                    value = options[key];
                }
                optionsStrings.push(
                  key + '=' + value
                );
              }
            }
            return optionsStrings.join(',');
          }

          var Oauth2 = function(config) {
            //if (this instanceof CustomObject)

            this.name = config.name;
            this.clientId = config.clientId;
            this.scope = config.scope;
            this.authorizationEndpoint = config.authorizationEndpoint;
            this.redirectUri = config.redirectUri;
            this.responseType = 'code';
            this.defaultUrlParams = ['response_type', 'client_id', 'redirect_uri'];
            this.requiredUrlParams = config.requiredUrlParams;
            this.optionalUrlParams = config.optionalUrlParams;
          };

          Oauth2.prototype.camelCase = function(name) {
            return name.replace(/([\:\-\_]+(.))/g, function(_, separator, letter, offset) {
              return offset ? letter.toUpperCase() : letter;
            });
          };

          Oauth2.prototype.open = function() {
            var deferred = $q.defer();
            var url = this.buildUrl();
            var popup = new Popup();

            popup.open(url).then(function(code) {
              this.exchangeForToken(code).then(function(response) {
                deferred.resolve(response.data);
              });
            }.bind(this));

            return deferred.promise;
          };

          Oauth2.prototype.exchangeForToken = function(code) {
            var params = {
              clientId: config.providers[this.name].clientId,
              redirectUri: $window.location.origin + '/',
              code: code
            };

            return $http.post('/auth/facebook', params);
          };

          Oauth2.prototype.buildUrl = function() {
            var baseUrl = this.authorizationEndpoint;
            var qs = this.buildQueryString();
            return [baseUrl, qs].join('?');
          };

          Oauth2.prototype.buildQueryString = function() {
            var obj = this;
            var keyValuePairs = [];

            angular.forEach(this.defaultUrlParams, function(paramName) {
              var camelizedName = obj.camelCase(paramName);
              var paramValue = obj[camelizedName];
              keyValuePairs.push([paramName, paramValue]);
            });

            angular.forEach(this.requiredUrlParams, function(paramName) {
              var camelizedName = obj.camelCase(paramName);
              var paramValue = obj[camelizedName];
              keyValuePairs.push([paramName, paramValue]);
            });

            angular.forEach(this.optionalUrlParams, function(paramName) {
              var camelizedName = obj.camelCase(paramName);
              var paramValue = obj[camelizedName];
              keyValuePairs.push([paramName, paramValue]);
            });

            console.log(keyValuePairs)
            return keyValuePairs.map(function(pair) {
              return pair.join('=');
            }).join('&');
          };

          Oauth2.createProvider = function(providerName) {
            var providerOptions = config.providers[providerName];
            var provider = new Oauth2(providerOptions);
            return provider;
          };

          // END OAUTH

          var token = $window.localStorage.token;
          if (token) {
            var payload = JSON.parse($window.atob(token.split('.')[1]));
            $rootScope[config.userGlobal] = payload.user;
          }

          return {
            authenticate: function(providerName, options) {
              if (!providerName) {
                return $q.reject('Expected a provider named \'' + providerName + '\', did you forget to add it?');
              }

              var provider = Oauth2.createProvider(providerName);

              var deferred = $q.defer();

              provider.open(options).then(function(data) {
                console.log('OPENED')
                deferred.resolve(data);
              });

              return deferred.promise;
            },

            loginOauth: function(provider) {
              provider = provider.trim().toLowerCase();

              switch (provider) {
                case 'facebook':
                  var scope = config.providers.facebook.scope.join(',');
                  FB.login(function(response) {
                    FB.api('/me', function(profile) {
                      // TODO normalize return properties like passport
                      var data = {
                        accessToken: response.authResponse.accessToken,
                        signedRequest: response.authResponse.signedRequest,
                        profile: profile
                      };
                      $http.post(config.providers.facebook.url, data).success(function(token) {
                        var payload = JSON.parse($window.atob(token.split('.')[1]));
                        $window.localStorage.token = token;
                        $rootScope[config.userGlobal] = payload.user;
                        $location.path(config.loginRedirect);
                      });
                    });
                  }, {scope: scope});
                  break;
                case 'google':

                  break;
                case 'linkedin':
                  break;
                default:
                  break;
              }
            },
            login: function(user) {
              return $http.post(config.loginUrl, user).success(function(data) {
                $window.localStorage.token = data.token;
                var payload = JSON.parse($window.atob(data.token.split('.')[1]));
                $rootScope[config.userGlobal] = payload.user;
                $location.path(config.loginRedirect);
              });
            },
            signup: function(user) {
              return $http.post(config.signupUrl, user).success(function() {
                $location.path(config.signupRedirect);
              });
            },
            logout: function() {
              delete $window.localStorage.token;
              $rootScope[config.userGlobal] = null;
              $location.path(config.logoutRedirect);
            },
            isAuthenticated: function() {
              return $rootScope[config.userGlobal];
            }
          };
        }
      };
    })
    .factory('authInterceptor', function($q, $window, $location) {
      return {
        request: function(config) {
          if ($window.localStorage.token) {
            config.headers.Authorization = 'Bearer ' + $window.localStorage.token;
          }
          return config;
        },
        responseError: function(response) {
          if (response.status === 401 || response.status === 403) {
            $location.path('/login');
          }
          return $q.reject(response);
        }
      }
    })
    .config(function($httpProvider) {
      $httpProvider.interceptors.push('authInterceptor');
    })
    .run(function($rootScope, $location) {
      $rootScope.$on('$routeChangeStart', function(event, current, previous) {
        if ($rootScope.currentUser &&
          (current.originalPath === '/login' || current.originalPath === '/signup')) {
          $location.path('/');
        }
        if (current.authenticated && !$rootScope.currentUser) {
          $location.path('/login');
        }
      });
    });
})(window, window.angular);

<|endoftext|>


<commit_before>/*** Directives and services for responding to idle users in AngularJS
* @author Mike Grabski <me@mikegrabski.com>
* @version v1.1.0
* @link https://github.com/HackedByChinese/ng-idle.git
* @license MIT
*/
(function(window, angular, undefined) {
'use strict';
angular.module('ngIdle', ['ngIdle.keepalive', 'ngIdle.idle', 'ngIdle.countdown', 'ngIdle.title', 'ngIdle.localStorage']);
angular.module('ngIdle.keepalive', [])
  .provider('Keepalive', function() {
    var options = {
      http: null,
      interval: 10 * 60
    };

    this.http = function(value) {
      if (!value) throw new Error('Argument must be a string containing a URL, or an object containing the HTTP request configuration.');
      if (angular.isString(value)) {
        value = {
          url: value,
          method: 'GET'
        };
      }

      value.cache = false;

      options.http = value;
    };

    var setInterval = this.interval = function(seconds) {
      seconds = parseInt(seconds);

      if (isNaN(seconds) || seconds <= 0) throw new Error('Interval must be expressed in seconds and be greater than 0.');
      options.interval = seconds;
    };

    this.$get = ['$rootScope', '$log', '$interval', '$http',
      function($rootScope, $log, $interval, $http) {

        var state = {
          ping: null
        };

        function handleResponse(data, status) {
          $rootScope.$broadcast('KeepaliveResponse', data, status);
        }

        function ping() {
          $rootScope.$broadcast('Keepalive');

          if (angular.isObject(options.http)) {
            $http(options.http)
              .success(handleResponse)
              .error(handleResponse);
          }
        }

        return {
          _options: function() {
            return options;
          },
          setInterval: setInterval,
          start: function() {
            $interval.cancel(state.ping);

            state.ping = $interval(ping, options.interval * 1000);
            return state.ping;
          },
          stop: function() {
            $interval.cancel(state.ping);
          },
          ping: function() {
            ping();
          }
        };
      }
    ];
  });

angular.module('ngIdle.idle', ['ngIdle.keepalive', 'ngIdle.localStorage'])
  .provider('Idle', function() {
    var options = {
      idle: 20 * 60, // in seconds (default is 20min)
      timeout: 30, // in seconds (default is 30sec)
      autoResume: 'idle', // lets events automatically resume (unsets idle state/resets warning)
      interrupt: 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove scroll',
      keepalive: true,
      titleDisabled: false // disable changes in document's title
    };

    /**
     *  Sets the number of seconds a user can be idle before they are considered timed out.
     *  @param {Number|Boolean} seconds A positive number representing seconds OR 0 or false to disable this feature.
     */
    var setTimeout = this.timeout = function(seconds) {
      if (seconds === false) options.timeout = 0;
      else if (angular.isNumber(seconds) && seconds >= 0) options.timeout = seconds;
      else throw new Error('Timeout must be zero or false to disable the feature, or a positive integer (in seconds) to enable it.');
    };

    this.interrupt = function(events) {
      options.interrupt = events;
    };

    var setIdle = this.idle = function(seconds) {
      if (seconds <= 0) throw new Error('Idle must be a value in seconds, greater than 0.');

      options.idle = seconds;
    };

    var setTitleDisabled = this.titleDisabled = function(disabled) {
      options.titleDisabled = disabled === true;
    };

    this.autoResume = function(value) {
      if (value === true) options.autoResume = 'idle';
      else if (value === false) options.autoResume = 'off';
      else options.autoResume = value;
    };

    this.keepalive = function(enabled) {
      options.keepalive = enabled === true;
    };

    this.$get = ['$interval', '$log', '$rootScope', '$document', 'Keepalive', 'IdleLocalStorage', '$window',
      function($interval, $log, $rootScope, $document, Keepalive, LocalStorage, $window) {
        var state = {
          idle: null,
          timeout: null,
          idling: false,
          running: false,
          countdown: null
        };

        var id = new Date().getTime();

        function startKeepalive() {
          if (!options.keepalive) return;

          if (state.running) Keepalive.ping();

          Keepalive.start();
        }

        function stopKeepalive() {
          if (!options.keepalive) return;

          Keepalive.stop();
        }

        function toggleState() {
          state.idling = !state.idling;
          var name = state.idling ? 'Start' : 'End';

          $rootScope.$broadcast('Idle' + name);

          if (state.idling) {
            stopKeepalive();
            if (options.timeout) {
              state.countdown = options.timeout;
              countdown();
              state.timeout = $interval(countdown, 1000, options.timeout, false);
            }
          } else {
            startKeepalive();
          }

          $interval.cancel(state.idle);
        }

        function countdown() {
          // countdown has expired, so signal timeout
          if (state.countdown <= 0) {
            timeout();
            return;
          }

          // countdown hasn't reached zero, so warn and decrement
          $rootScope.$broadcast('IdleWarn', state.countdown);
          state.countdown--;
        }

        function timeout() {
          stopKeepalive();
          $interval.cancel(state.idle);
          $interval.cancel(state.timeout);

          state.idling = true;
          state.running = false;
          state.countdown = 0;

          $rootScope.$broadcast('IdleTimeout');
        }

        function changeOption(self, fn, value) {
          var reset = self.running();

          self.unwatch();
          fn(value);
          if (reset) self.watch();
        }

        function getExpiry() {
          var obj = LocalStorage.get('expiry');

          return obj && obj.time ? new Date(obj.time) : null;
        }

        function setExpiry(date) {
          if (!date) LocalStorage.remove('expiry');
          else LocalStorage.set('expiry', {id: id, time: date});
        }

        var svc = {
          _options: function() {
            return options;
          },
          _getNow: function() {
            return new Date();
          },
          getIdle: function(){
            return options.idle;
          },
          getTimeout: function(){
            return options.timeout;
          },
          setIdle: function(seconds) {
            changeOption(this, setIdle, seconds);
          },
          setTitleDisabled: function(disabled) {
            changeOption(this, setTitleDisabled, disabled);
          },
          isTitleDisabled: function() {
            return options.titleDisabled;
          },
          setTimeout: function(seconds) {
            changeOption(this, setTimeout, seconds);
          },
          isExpired: function() {
            var expiry = getExpiry();
            return expiry !== null && expiry <= this._getNow();
          },
          running: function() {
            return state.running;
          },
          idling: function() {
            return state.idling;
          },
          watch: function(noExpiryUpdate) {
            $interval.cancel(state.idle);
            $interval.cancel(state.timeout);

            // calculate the absolute expiry date, as added insurance against a browser sleeping or paused in the background
            var timeout = !options.timeout ? 0 : options.timeout;
            if (!noExpiryUpdate) setExpiry(new Date(new Date().getTime() + ((options.idle + timeout) * 1000)));

            if (state.idling) toggleState(); // clears the idle state if currently idling
            else if (!state.running) startKeepalive(); // if about to run, start keep alive

            state.running = true;

            state.idle = $interval(toggleState, options.idle * 1000, 0, false);
          },
          unwatch: function() {
            $interval.cancel(state.idle);
            $interval.cancel(state.timeout);

            state.idling = false;
            state.running = false;
            setExpiry(null);

            stopKeepalive();
          },
          interrupt: function(noExpiryUpdate) {
            if (!state.running) return;

            if (options.timeout && this.isExpired()) {
              timeout();
              return;
            }

            // note: you can no longer auto resume once we exceed the expiry; you will reset state by calling watch() manually
            if (options.autoResume === 'idle' || (options.autoResume === 'notIdle' && !state.idling)) this.watch(noExpiryUpdate);
          }
        };

        $document.find('body').on(options.interrupt, function() {
          svc.interrupt();
        });

        var wrap = function(event) {
          if (event.key === 'ngIdle.expiry' && event.newValue !== event.oldValue) {
            var val = angular.fromJson(event.newValue);
            if (val.id === id) return;
            svc.interrupt(true);
          }
        };

        if ($window.addEventListener) $window.addEventListener('storage', wrap, false);
        else $window.attachEvent('onstorage', wrap);

        return svc;
      }
    ];
  });

angular.module('ngIdle.countdown', ['ngIdle.idle'])
  .directive('idleCountdown', ['Idle', function(Idle) {
    return {
      restrict: 'A',
      scope: {
        value: '=idleCountdown'
      },
      link: function($scope) {
        // Initialize the scope's value to the configured timeout.
        $scope.value = Idle.getTimeout();

        $scope.$on('IdleWarn', function(e, countdown) {
          $scope.$evalAsync(function() {
            $scope.value = countdown;
          });
        });

        $scope.$on('IdleTimeout', function() {
          $scope.$evalAsync(function() {
            $scope.value = 0;
          });
        });
      }
    };
  }]);

angular.module('ngIdle.title', [])
  .factory('Title', ['$document', '$interpolate', function($document, $interpolate) {

    function padLeft(nr, n, str){
      return new Array(n-String(nr).length+1).join(str||'0')+nr;
    }

    var state = {
      original: null,
      idle: '{{minutes}}:{{seconds}} until your session times out!',
      timedout: 'Your session has expired.'
    };

    return {
      original: function(val) {
        if (angular.isUndefined(val)) return state.original;

        state.original = val;
      },
      store: function(overwrite) {
        if (overwrite || !state.original) state.original = this.value();
      },
      value: function(val) {
        if (angular.isUndefined(val)) return $document[0].title;

        $document[0].title = val;
      },
      idleMessage: function(val) {
        if (angular.isUndefined(val)) return state.idle;

        state.idle = val;
      },
      timedOutMessage: function(val) {
        if (angular.isUndefined(val)) return state.timedout;

        state.timedout = val;
      },
      setAsIdle: function(countdown) {
        this.store();

        var remaining = { totalSeconds: countdown };
        remaining.minutes = Math.floor(countdown/60);
        remaining.seconds = padLeft(countdown - remaining.minutes * 60, 2);

        this.value($interpolate(this.idleMessage())(remaining));
      },
      setAsTimedOut: function() {
        this.store();

        this.value(this.timedOutMessage());
      },
      restore: function() {
        if (this.original()) this.value(this.original());
      }
    };
  }])
  .directive('title', ['Idle', 'Title', function(Idle, Title) {
      return {
        restrict: 'E',
        link: function($scope, $element, $attr) {
          if (Idle.isTitleDisabled() || $attr.idleDisabled) return;

          Title.store(true);

          $scope.$on('IdleStart', function() {
            Title.original($element[0].innerText);
          });

          $scope.$on('IdleWarn', function(e, countdown) {
            Title.setAsIdle(countdown);
          });

          $scope.$on('IdleEnd', function() {
            Title.restore();
          });

          $scope.$on('IdleTimeout', function() {
            Title.setAsTimedOut();
          });
        }
      };
  }]);

angular.module('ngIdle.localStorage', [])
  .service('IdleLocalStorage', ['$window', function($window) {
    var storage = $window.localStorage;

    return {
      set: function(key, value) {
        storage.setItem('ngIdle.'+key, angular.toJson(value));
      },
      get: function(key) {
        return angular.fromJson(storage.getItem('ngIdle.'+key));
      },
      remove: function(key) {
        storage.removeItem('ngIdle.'+key);
      }
    };
  }]);

})(window, window.angular);<commit_msg>Fix for desktop notifications and Chrome<commit_after>/*** Directives and services for responding to idle users in AngularJS
* @author Mike Grabski <me@mikegrabski.com>
* @version v1.1.0
* @link https://github.com/HackedByChinese/ng-idle.git
* @license MIT
*/
(function(window, angular, undefined) {
'use strict';
angular.module('ngIdle', ['ngIdle.keepalive', 'ngIdle.idle', 'ngIdle.countdown', 'ngIdle.title', 'ngIdle.localStorage']);
angular.module('ngIdle.keepalive', [])
  .provider('Keepalive', function() {
    var options = {
      http: null,
      interval: 10 * 60
    };

    this.http = function(value) {
      if (!value) throw new Error('Argument must be a string containing a URL, or an object containing the HTTP request configuration.');
      if (angular.isString(value)) {
        value = {
          url: value,
          method: 'GET'
        };
      }

      value.cache = false;

      options.http = value;
    };

    var setInterval = this.interval = function(seconds) {
      seconds = parseInt(seconds);

      if (isNaN(seconds) || seconds <= 0) throw new Error('Interval must be expressed in seconds and be greater than 0.');
      options.interval = seconds;
    };

    this.$get = ['$rootScope', '$log', '$interval', '$http',
      function($rootScope, $log, $interval, $http) {

        var state = {
          ping: null
        };

        function handleResponse(data, status) {
          $rootScope.$broadcast('KeepaliveResponse', data, status);
        }

        function ping() {
          $rootScope.$broadcast('Keepalive');

          if (angular.isObject(options.http)) {
            $http(options.http)
              .success(handleResponse)
              .error(handleResponse);
          }
        }

        return {
          _options: function() {
            return options;
          },
          setInterval: setInterval,
          start: function() {
            $interval.cancel(state.ping);

            state.ping = $interval(ping, options.interval * 1000);
            return state.ping;
          },
          stop: function() {
            $interval.cancel(state.ping);
          },
          ping: function() {
            ping();
          }
        };
      }
    ];
  });

angular.module('ngIdle.idle', ['ngIdle.keepalive', 'ngIdle.localStorage'])
  .provider('Idle', function() {
    var options = {
      idle: 20 * 60, // in seconds (default is 20min)
      timeout: 30, // in seconds (default is 30sec)
      autoResume: 'idle', // lets events automatically resume (unsets idle state/resets warning)
      interrupt: 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove scroll',
      keepalive: true,
      titleDisabled: false // disable changes in document's title
    };

    /**
     *  Sets the number of seconds a user can be idle before they are considered timed out.
     *  @param {Number|Boolean} seconds A positive number representing seconds OR 0 or false to disable this feature.
     */
    var setTimeout = this.timeout = function(seconds) {
      if (seconds === false) options.timeout = 0;
      else if (angular.isNumber(seconds) && seconds >= 0) options.timeout = seconds;
      else throw new Error('Timeout must be zero or false to disable the feature, or a positive integer (in seconds) to enable it.');
    };

    this.interrupt = function(events) {
      options.interrupt = events;
    };

    var setIdle = this.idle = function(seconds) {
      if (seconds <= 0) throw new Error('Idle must be a value in seconds, greater than 0.');

      options.idle = seconds;
    };

    var setTitleDisabled = this.titleDisabled = function(disabled) {
      options.titleDisabled = disabled === true;
    };

    this.autoResume = function(value) {
      if (value === true) options.autoResume = 'idle';
      else if (value === false) options.autoResume = 'off';
      else options.autoResume = value;
    };

    this.keepalive = function(enabled) {
      options.keepalive = enabled === true;
    };

    this.$get = ['$interval', '$log', '$rootScope', '$document', 'Keepalive', 'IdleLocalStorage', '$window',
      function($interval, $log, $rootScope, $document, Keepalive, LocalStorage, $window) {
        var state = {
          idle: null,
          timeout: null,
          idling: false,
          running: false,
          countdown: null
        };

        var id = new Date().getTime();

        function startKeepalive() {
          if (!options.keepalive) return;

          if (state.running) Keepalive.ping();

          Keepalive.start();
        }

        function stopKeepalive() {
          if (!options.keepalive) return;

          Keepalive.stop();
        }

        function toggleState() {
          state.idling = !state.idling;
          var name = state.idling ? 'Start' : 'End';

          $rootScope.$broadcast('Idle' + name);

          if (state.idling) {
            stopKeepalive();
            if (options.timeout) {
              state.countdown = options.timeout;
              countdown();
              state.timeout = $interval(countdown, 1000, options.timeout, false);
            }
          } else {
            startKeepalive();
          }

          $interval.cancel(state.idle);
        }

        function countdown() {
          // countdown has expired, so signal timeout
          if (state.countdown <= 0) {
            timeout();
            return;
          }

          // countdown hasn't reached zero, so warn and decrement
          $rootScope.$broadcast('IdleWarn', state.countdown);
          state.countdown--;
        }

        function timeout() {
          stopKeepalive();
          $interval.cancel(state.idle);
          $interval.cancel(state.timeout);

          state.idling = true;
          state.running = false;
          state.countdown = 0;

          $rootScope.$broadcast('IdleTimeout');
        }

        function changeOption(self, fn, value) {
          var reset = self.running();

          self.unwatch();
          fn(value);
          if (reset) self.watch();
        }

        function getExpiry() {
          var obj = LocalStorage.get('expiry');

          return obj && obj.time ? new Date(obj.time) : null;
        }

        function setExpiry(date) {
          if (!date) LocalStorage.remove('expiry');
          else LocalStorage.set('expiry', {id: id, time: date});
        }

        var svc = {
          _options: function() {
            return options;
          },
          _getNow: function() {
            return new Date();
          },
          getIdle: function(){
            return options.idle;
          },
          getTimeout: function(){
            return options.timeout;
          },
          setIdle: function(seconds) {
            changeOption(this, setIdle, seconds);
          },
          setTitleDisabled: function(disabled) {
            changeOption(this, setTitleDisabled, disabled);
          },
          isTitleDisabled: function() {
            return options.titleDisabled;
          },
          setTimeout: function(seconds) {
            changeOption(this, setTimeout, seconds);
          },
          isExpired: function() {
            var expiry = getExpiry();
            return expiry !== null && expiry <= this._getNow();
          },
          running: function() {
            return state.running;
          },
          idling: function() {
            return state.idling;
          },
          watch: function(noExpiryUpdate) {
            $interval.cancel(state.idle);
            $interval.cancel(state.timeout);

            // calculate the absolute expiry date, as added insurance against a browser sleeping or paused in the background
            var timeout = !options.timeout ? 0 : options.timeout;
            if (!noExpiryUpdate) setExpiry(new Date(new Date().getTime() + ((options.idle + timeout) * 1000)));

            if (state.idling) toggleState(); // clears the idle state if currently idling
            else if (!state.running) startKeepalive(); // if about to run, start keep alive

            state.running = true;

            state.idle = $interval(toggleState, options.idle * 1000, 0, false);
          },
          unwatch: function() {
            $interval.cancel(state.idle);
            $interval.cancel(state.timeout);

            state.idling = false;
            state.running = false;
            setExpiry(null);

            stopKeepalive();
          },
          interrupt: function(noExpiryUpdate) {
            if (!state.running) return;

            if (options.timeout && this.isExpired()) {
              timeout();
              return;
            }

            // note: you can no longer auto resume once we exceed the expiry; you will reset state by calling watch() manually
            if (options.autoResume === 'idle' || (options.autoResume === 'notIdle' && !state.idling)) this.watch(noExpiryUpdate);
          }
        };

        $document.find('body').on(options.interrupt, function(event) {
          if (event.type === 'mousemove' && event.originalEvent.movementX === 0 && event.originalEvent.movementY === 0) {
            return; // Fix for Chrome desktop notifications, triggering mousemove event.
          }
          svc.interrupt();
        });

        var wrap = function(event) {
          if (event.key === 'ngIdle.expiry' && event.newValue !== event.oldValue) {
            var val = angular.fromJson(event.newValue);
            if (val.id === id) return;
            svc.interrupt(true);
          }
        };

        if ($window.addEventListener) $window.addEventListener('storage', wrap, false);
        else $window.attachEvent('onstorage', wrap);

        return svc;
      }
    ];
  });

angular.module('ngIdle.countdown', ['ngIdle.idle'])
  .directive('idleCountdown', ['Idle', function(Idle) {
    return {
      restrict: 'A',
      scope: {
        value: '=idleCountdown'
      },
      link: function($scope) {
        // Initialize the scope's value to the configured timeout.
        $scope.value = Idle.getTimeout();

        $scope.$on('IdleWarn', function(e, countdown) {
          $scope.$evalAsync(function() {
            $scope.value = countdown;
          });
        });

        $scope.$on('IdleTimeout', function() {
          $scope.$evalAsync(function() {
            $scope.value = 0;
          });
        });
      }
    };
  }]);

angular.module('ngIdle.title', [])
  .factory('Title', ['$document', '$interpolate', function($document, $interpolate) {

    function padLeft(nr, n, str){
      return new Array(n-String(nr).length+1).join(str||'0')+nr;
    }

    var state = {
      original: null,
      idle: '{{minutes}}:{{seconds}} until your session times out!',
      timedout: 'Your session has expired.'
    };

    return {
      original: function(val) {
        if (angular.isUndefined(val)) return state.original;

        state.original = val;
      },
      store: function(overwrite) {
        if (overwrite || !state.original) state.original = this.value();
      },
      value: function(val) {
        if (angular.isUndefined(val)) return $document[0].title;

        $document[0].title = val;
      },
      idleMessage: function(val) {
        if (angular.isUndefined(val)) return state.idle;

        state.idle = val;
      },
      timedOutMessage: function(val) {
        if (angular.isUndefined(val)) return state.timedout;

        state.timedout = val;
      },
      setAsIdle: function(countdown) {
        this.store();

        var remaining = { totalSeconds: countdown };
        remaining.minutes = Math.floor(countdown/60);
        remaining.seconds = padLeft(countdown - remaining.minutes * 60, 2);

        this.value($interpolate(this.idleMessage())(remaining));
      },
      setAsTimedOut: function() {
        this.store();

        this.value(this.timedOutMessage());
      },
      restore: function() {
        if (this.original()) this.value(this.original());
      }
    };
  }])
  .directive('title', ['Idle', 'Title', function(Idle, Title) {
      return {
        restrict: 'E',
        link: function($scope, $element, $attr) {
          if (Idle.isTitleDisabled() || $attr.idleDisabled) return;

          Title.store(true);

          $scope.$on('IdleStart', function() {
            Title.original($element[0].innerText);
          });

          $scope.$on('IdleWarn', function(e, countdown) {
            Title.setAsIdle(countdown);
          });

          $scope.$on('IdleEnd', function() {
            Title.restore();
          });

          $scope.$on('IdleTimeout', function() {
            Title.setAsTimedOut();
          });
        }
      };
  }]);

angular.module('ngIdle.localStorage', [])
  .service('IdleLocalStorage', ['$window', function($window) {
    var storage = $window.localStorage;

    return {
      set: function(key, value) {
        storage.setItem('ngIdle.'+key, angular.toJson(value));
      },
      get: function(key) {
        return angular.fromJson(storage.getItem('ngIdle.'+key));
      },
      remove: function(key) {
        storage.removeItem('ngIdle.'+key);
      }
    };
  }]);

})(window, window.angular);
<|endoftext|>


<commit_before>/**
  @module ember-data
*/

import { PromiseArray } from "ember-data/system/store";

import {
  relationshipFromMeta,
  typeForRelationshipMeta
} from "ember-data/system/relationship-meta";

var get = Ember.get;
var set = Ember.set;
var setProperties = Ember.setProperties;
var map = Ember.EnumerableUtils.map;

function asyncHasMany(type, options, meta) {
  return Ember.computed('data', function(key) {
    var relationship = this._relationships[key];
    var promiseLabel = "DS: Async hasMany " + this + " : " + key;

    meta.key = key;

    if (!relationship) {
      var resolver = Ember.RSVP.defer(promiseLabel);
      relationship = buildRelationship(this, key, options, function(store, data) {
        var link = data.links && data.links[key];
        var rel;
        if (link) {
          rel = store.findHasMany(this, link, relationshipFromMeta(store, meta), resolver);
        } else {
          //This is a temporary workaround for setting owner on the relationship
          //until single source of truth lands. It only works for OneToMany atm
          var records = data[key];
          var inverse = this.constructor.inverseFor(key);
          var owner = this;
          if (inverse && records) {
            if (inverse.kind === 'belongsTo'){
              map(records, function(record){
                set(record, inverse.name, owner);
              });
            }
          }
          rel = store.findMany(owner, data[key], typeForRelationshipMeta(store, meta), resolver);
        }
        // cache the promise so we can use it
        // when we come back and don't need to rebuild
        // the relationship.
        set(rel, 'promise', resolver.promise);
        return rel;
      });
    }

    var promise = relationship.get('promise').then(function() {
      return relationship;
    }, null, "DS: Async hasMany records received");

    return PromiseArray.create({
      promise: promise
    });
  }).meta(meta).readOnly();
}

function buildRelationship(record, key, options, callback) {
  var rels = record._relationships;

  if (rels[key]) { return rels[key]; }

  var data = get(record, 'data');
  var store = get(record, 'store');

  var relationship = rels[key] = callback.call(record, store, data);

  return setProperties(relationship, {
    owner: record,
    name: key,
    isPolymorphic: options.polymorphic
  });
}

function hasRelationship(type, options) {
  options = options || {};

  var meta = {
    type: type,
    isRelationship: true,
    options: options,
    kind: 'hasMany',
    key: null
  };

  if (options.async) {
    return asyncHasMany(type, options, meta);
  }

  return Ember.computed('data', function(key) {
    return buildRelationship(this, key, options, function(store, data) {
      var records = data[key];
      Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).isEvery('isEmpty', false));
      return store.findMany(this, data[key], typeForRelationshipMeta(store, meta));
    });
  }).meta(meta).readOnly();
}

/**
  `DS.hasMany` is used to define One-To-Many and Many-To-Many
  relationships on a [DS.Model](/api/data/classes/DS.Model.html).

  `DS.hasMany` takes an optional hash as a second parameter, currently
  supported options are:

  - `async`: A boolean value used to explicitly declare this to be an async relationship.
  - `inverse`: A string used to identify the inverse property on a related model.

  #### One-To-Many
  To declare a one-to-many relationship between two models, use
  `DS.belongsTo` in combination with `DS.hasMany`, like this:

  ```javascript
  App.Post = DS.Model.extend({
    comments: DS.hasMany('comment')
  });

  App.Comment = DS.Model.extend({
    post: DS.belongsTo('post')
  });
  ```

  #### Many-To-Many
  To declare a many-to-many relationship between two models, use
  `DS.hasMany`:

  ```javascript
  App.Post = DS.Model.extend({
    tags: DS.hasMany('tag')
  });

  App.Tag = DS.Model.extend({
    posts: DS.hasMany('post')
  });
  ```

  #### Explicit Inverses

  Ember Data will do its best to discover which relationships map to
  one another. In the one-to-many code above, for example, Ember Data
  can figure out that changing the `comments` relationship should update
  the `post` relationship on the inverse because post is the only
  relationship to that model.

  However, sometimes you may have multiple `belongsTo`/`hasManys` for the
  same type. You can specify which property on the related model is
  the inverse using `DS.hasMany`'s `inverse` option:

  ```javascript
  var belongsTo = DS.belongsTo,
      hasMany = DS.hasMany;

  App.Comment = DS.Model.extend({
    onePost: belongsTo('post'),
    twoPost: belongsTo('post'),
    redPost: belongsTo('post'),
    bluePost: belongsTo('post')
  });

  App.Post = DS.Model.extend({
    comments: hasMany('comment', {
      inverse: 'redPost'
    })
  });
  ```

  You can also specify an inverse on a `belongsTo`, which works how
  you'd expect.

  @namespace
  @method hasMany
  @for DS
  @param {String or DS.Model} type the model type of the relationship
  @param {Object} options a hash of options
  @return {Ember.computed} relationship
*/
function hasMany(type, options) {
  if (typeof type === 'object') {
    options = type;
    type = undefined;
  }
  return hasRelationship(type, options);
}

export default hasMany;
<commit_msg>Refactor hasMany code for comments and clarity<commit_after>/**
  @module ember-data
*/

import { PromiseArray } from "ember-data/system/store";

import {
  relationshipFromMeta,
  typeForRelationshipMeta
} from "ember-data/system/relationship-meta";

var get = Ember.get;
var set = Ember.set;
var setProperties = Ember.setProperties;
var map = Ember.EnumerableUtils.map;

/**
  Returns a computed property that synchronously returns a ManyArray for
  this relationship. If not all of the records in this relationship are
  loaded, it will raise an exception.
*/

function syncHasMany(type, options, meta) {
  return Ember.computed('data', function(key) {
    return buildRelationship(this, key, options, function(store, data) {
      // Configure the metadata for the computed property to contain
      // the key.
      meta.key = key;

      var records = data[key];

      Ember.assert("You looked up the '" + key + "' relationship on '" + this + "' but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (`DS.hasMany({ async: true })`)", Ember.A(records).isEvery('isEmpty', false));

      return store.findMany(this, data[key], typeForRelationshipMeta(store, meta));
    });
  }).meta(meta).readOnly();
}

/**
  Returns a computed property that itself returns a promise that resolves to a
  ManyArray.
 */

function asyncHasMany(type, options, meta) {
  return Ember.computed('data', function(key) {
    // Configure the metadata for the computed property to contain
    // the key.
    meta.key = key;

    var relationship = buildRelationship(this, key, options, function(store, data) {
      var link = data.links && data.links[key];
      var rel;
      var promiseLabel = "DS: Async hasMany " + this + " : " + key;
      var resolver = Ember.RSVP.defer(promiseLabel);

      if (link) {
        rel = store.findHasMany(this, link, relationshipFromMeta(store, meta), resolver);
      } else {

        //This is a temporary workaround for setting owner on the relationship
        //until single source of truth lands. It only works for OneToMany atm
        var records = data[key];
        var inverse = this.constructor.inverseFor(key);
        var owner = this;
        if (inverse && records) {
          if (inverse.kind === 'belongsTo'){
            map(records, function(record){
              set(record, inverse.name, owner);
            });
          }
        }

        rel = store.findMany(owner, data[key], typeForRelationshipMeta(store, meta), resolver);
      }

      // Cache the promise so we can use it when we come back and don't
      // need to rebuild the relationship.
      set(rel, 'promise', resolver.promise);

      return rel;
    });

    var promise = relationship.get('promise').then(function() {
      return relationship;
    }, null, "DS: Async hasMany records received");

    return PromiseArray.create({
      promise: promise
    });
  }).meta(meta).readOnly();
}

/*
  Builds the ManyArray for a relationship using the provided callback,
  but only if it had not been created previously. After building, it
  sets some metadata on the created ManyArray, such as the record which
  owns it and the name of the relationship.
*/
function buildRelationship(record, key, options, callback) {
  var rels = record._relationships;

  if (rels[key]) { return rels[key]; }

  var data = get(record, 'data');
  var store = get(record, 'store');

  var relationship = rels[key] = callback.call(record, store, data);

  return setProperties(relationship, {
    owner: record,
    name: key,
    isPolymorphic: options.polymorphic
  });
}

/**
  `DS.hasMany` is used to define One-To-Many and Many-To-Many
  relationships on a [DS.Model](/api/data/classes/DS.Model.html).

  `DS.hasMany` takes an optional hash as a second parameter, currently
  supported options are:

  - `async`: A boolean value used to explicitly declare this to be an async relationship.
  - `inverse`: A string used to identify the inverse property on a related model.

  #### One-To-Many
  To declare a one-to-many relationship between two models, use
  `DS.belongsTo` in combination with `DS.hasMany`, like this:

  ```javascript
  App.Post = DS.Model.extend({
    comments: DS.hasMany('comment')
  });

  App.Comment = DS.Model.extend({
    post: DS.belongsTo('post')
  });
  ```

  #### Many-To-Many
  To declare a many-to-many relationship between two models, use
  `DS.hasMany`:

  ```javascript
  App.Post = DS.Model.extend({
    tags: DS.hasMany('tag')
  });

  App.Tag = DS.Model.extend({
    posts: DS.hasMany('post')
  });
  ```

  #### Explicit Inverses

  Ember Data will do its best to discover which relationships map to
  one another. In the one-to-many code above, for example, Ember Data
  can figure out that changing the `comments` relationship should update
  the `post` relationship on the inverse because post is the only
  relationship to that model.

  However, sometimes you may have multiple `belongsTo`/`hasManys` for the
  same type. You can specify which property on the related model is
  the inverse using `DS.hasMany`'s `inverse` option:

  ```javascript
  var belongsTo = DS.belongsTo,
      hasMany = DS.hasMany;

  App.Comment = DS.Model.extend({
    onePost: belongsTo('post'),
    twoPost: belongsTo('post'),
    redPost: belongsTo('post'),
    bluePost: belongsTo('post')
  });

  App.Post = DS.Model.extend({
    comments: hasMany('comment', {
      inverse: 'redPost'
    })
  });
  ```

  You can also specify an inverse on a `belongsTo`, which works how
  you'd expect.

  @namespace
  @method hasMany
  @for DS
  @param {String or DS.Model} type the model type of the relationship
  @param {Object} options a hash of options
  @return {Ember.computed} relationship
*/
function hasMany(type, options) {
  if (typeof type === 'object') {
    options = type;
    type = undefined;
  }

  options = options || {};

  // Metadata about relationships is stored on the meta of
  // the relationship. This is used for introspection and
  // serialization. Note that `key` is populated lazily
  // the first time the CP is called.
  var meta = {
    type: type,
    isRelationship: true,
    options: options,
    kind: 'hasMany',
    key: null
  };

  if (options.async) {
    return asyncHasMany(type, options, meta);
  } else {
    return syncHasMany(type, options, meta);
  }
}

export default hasMany;
<|endoftext|>


<commit_before><commit_msg>No loading animation.<commit_after><|endoftext|>