<commit_before>/**
 * Kaltura style analytics reporting class
 */
( function( mw, $ ) { "use strict";

// Avoid undefined symbol for javascript response "Kaltura" from the api
window['Kaltura'] = true;

// KAnalytics Constructor
mw.KAnalytics = function( embedPlayer ){
	this.init( embedPlayer );
};

// Add analytics to the embed player:
mw.addKAnalytics = function( embedPlayer ) {
	embedPlayer.kAnalytics = new mw.KAnalytics( embedPlayer );
}
mw.KAnalytics.prototype = {

	// Local reference to embedPlayer
	embedPlayer: null,

	// Report Set object
	reportSet : null,

	// Stores the last time we issued a seek event
	// avoids sending lots of seeks while scrubbing
	lastSeekEventTime: 0,

	bindPostFix: '.kAnalytics',

	// Start Time
	startReportTime: 0,

	kEventTypes : {
		'WIDGET_LOADED' : 1,
		'MEDIA_LOADED' : 2,
		'PLAY' : 3,
		'PLAY_REACHED_25' : 4,
		'PLAY_REACHED_50' : 5,
		'PLAY_REACHED_75' : 6,
		'PLAY_REACHED_100' : 7,
		'OPEN_EDIT' : 8,
		'OPEN_VIRAL' : 9,
		'OPEN_DOWNLOAD' : 10,
		'OPEN_REPORT' : 11,
		'BUFFER_START' : 12,
		'BUFFER_END' : 13,
		'OPEN_FULL_SCREEN' : 14,
		'CLOSE_FULL_SCREEN' : 15,
		'REPLAY' : 16,
		'SEEK' : 17,
		'OPEN_UPLOAD' : 18,
		'SAVE_PUBLISH' : 19,
		'CLOSE_EDITOR' : 20,
		'PRE_BUMPER_PLAYED' : 21,
		'POST_BUMPER_PLAYED' : 22,
		'BUMPER_CLICKED' : 23,
		'FUTURE_USE_1' : 24,
		'FUTURE_USE_2' : 25,
		'FUTURE_USE_3' : 26
	},

	/**
	 * Constructor for kAnalytics
	 *
	 * @param {Object}
	 *		  embedPlayer Player to apply Kaltura analytics to.
	 * @parma {Object}
	 * 			kalturaClient Kaltura client object for the api session.
	 */
	init: function( embedPlayer ) {
		// set the version of html5 player
		this.version = mw.getConfig( 'version' );
		// Setup the local reference to the embed player
		this.embedPlayer = embedPlayer;
		if( ! this.kClient ) {
			this.kClient = mw.kApiGetPartnerClient( embedPlayer.kwidgetid );
		}
		// Remove any old bindings:
		$( embedPlayer ).unbind( this.bindPostFix );

		// Setup the initial state of some flags
		this.resetPlayerflags();

		// Add relevant hooks for reporting beacons
		this.bindPlayerEvents();
	},
	resetPlayerflags:function(){
		this._p25Once = false;
		this._p50Once = false;
		this._p75Once = false;
		this._p100Once = false;
		this.hasSeeked = false;
		this.lastSeek = 0;
	},
	/**
	 * Get the current report set
	 *
	 * @param {Number}
	 *			KalturaStatsEventType The eventType number.
	 */
	sendAnalyticsEvent: function( KalturaStatsEventKey ){
		var _this = this;
		mw.log("KAnalytics :: doSendAnalyticsEvent > " + KalturaStatsEventKey );
		// Kalutra analytics does not collect info for ads:
		if( this.embedPlayer.evaluate('{sequenceProxy.isInSequence}') ){
			return ;
		}

		// get the id for the given event:
		var eventKeyId = this.kEventTypes[ KalturaStatsEventKey ];

		// Generate the status event
		var eventSet = {
			'eventType'			: eventKeyId,
			'clientVer'			: this.version,
			'currentPoint'		: parseInt( this.embedPlayer.currentTime * 1000 ),
			'duration'			: this.embedPlayer.getDuration(),
			'eventTimestamp'	: new Date().getTime(),
			'isFirstInSession'	: 'false',
			'objectType'		: 'KalturaStatsEvent',
			'partnerId'			: this.embedPlayer.kpartnerid,
			'sessionId'			: this.embedPlayer.evaluate('{configProxy.sessionId}'),
			'uiconfId'			: 0
		};
		if( isNaN( eventSet.duration )  ){
			eventSet.duration = 0;
		}

		// Set the seek condition:
		eventSet[ 'seek' ] = ( this.hasSeeked ) ? 'true' : 'false';

		// Set the 'event:entryId'
		if( this.embedPlayer.kentryid ){
			eventSet[ 'entryId' ] = this.embedPlayer.kentryid;
		} else {
			// if kentryid is not set, use the selected source url
			eventSet[ 'entryId' ] = this.embedPlayer.getSrc();
		}

		// Set the 'event:uiconfId'
		if( this.embedPlayer.kuiconfid ) {
			eventSet[ 'uiconfId' ] = this.embedPlayer.kuiconfid;
		}
		// Set the 'event:widgetId'
		if( this.embedPlayer.kwidgetid ) {
			eventSet[ 'widgetId' ] = this.embedPlayer.kwidgetid;
		}
		var flashVarEvents = {
				'playbackContext' : 'contextId',
				'originFeature' : 'featureType',
				'applicationName' : 'applicationId',
				'userId' : 'userId'
		}
		// support legacy ( deprecated ) top level config
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( '', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('', fvKey ) );
			}
		}
		// check for the vars in the correct location:
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( 'statistics', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('statistics', fvKey ) );
			}
		}
		// hideUserId will remove the userId from the analytics call EVEN if the embed code sends one (unless hashedUserId is in use)
		if(this.embedPlayer.getKalturaConfig( 'statistics' , 'hideUserId')){
			delete(eventSet.userId);
		}
		// Use this new parameter to send hashed userId in case the partner has limitation about storing users-data on Kaltura database.
		// This feature
		if(this.embedPlayer.getKalturaConfig( 'statistics' , 'hashedUserId')){
			eventSet.userId = this.embedPlayer.getKalturaConfig( 'statistics' , 'hashedUserId');
		}

		// Add referrer parameter
		eventSet[ 'referrer' ] = encodeURIComponent( mw.getConfig('EmbedPlayer.IframeParentUrl') );

		// Add in base service and action calls:
		var eventRequest = {'service' : 'stats', 'action' : 'collect'};
		// Add event parameters
		for( var i in eventSet){
			eventRequest[ 'event:' + i] = eventSet[i];
		}

		// Send events for this player:
		$( this.embedPlayer ).trigger( 'KalturaSendAnalyticEvent', [ KalturaStatsEventKey, eventSet ] );

		// check for defined callback:
		var parentTrackName = this.embedPlayer.getKalturaConfig( 'statistics', 'trackEventMonitor');
		if(  mw.getConfig('EmbedPlayer.IsFriendlyIframe') ){
			try {
				if( window.parent[ parentTrackName ] ){
					 window.parent[ parentTrackName ]( KalturaStatsEventKey, eventSet );
				}
			} catch( e ){
				// error in calling parent page event
			}
		}
		//hideKS is an attribute that will prevent the request from sending the KS even if the embed code receives one
		if (this.embedPlayer.getFlashvars('ks') && !this.embedPlayer.getKalturaConfig( 'statistics' , 'hideKs') ){
			eventRequest['ks'] = this.embedPlayer.getFlashvars('ks');
		}

		// Do the api request:
		this.kClient.doRequest( eventRequest, null, true );
	},

	/**
	 * Binds player events for analytics reporting
	 */
	bindPlayerEvents: function(){

		// Setup local reference to embedPlayer
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Setup shortcut anonymous function for player bindings
		var b = function( hookName, eventType ){
			$( _this.embedPlayer ).bind( hookName + _this.bindPostFix, function(){
				_this.sendAnalyticsEvent( eventType );
			});
		};

		// When the player is ready
		b( 'widgetLoaded', 'WIDGET_LOADED' );

		// When the poster or video ( when autoplay ) media is loaded
		b( 'KalturaSupport_EntryDataReady', 'MEDIA_LOADED' );

		// When the play button is pressed or called from javascript
		b( 'firstPlay', 'PLAY' );

		// When the show Share menu is displayed
		b( 'showShareEvent', 'OPEN_VIRAL' );

		// When the show download menu is displayed
		b( 'downloadMedia', 'OPEN_DOWNLOAD' );

		// When the clip starts to buffer ( not all player types )
		b( 'bufferStartEvent', 'BUFFER_START' );

		// When the clip is full buffered
		b( 'bufferEndEvent', 'BUFFER_END' );

		// When the fullscreen button is pressed
		// ( presently does not register iPhone / iPad until it has js bindings )
		b( 'onOpenFullScreen', 'OPEN_FULL_SCREEN' );

		// When the close fullscreen button is pressed.
		// ( presently does not register iphone / ipad until it has js bindings )
		b( 'onCloseFullScreen', 'CLOSE_FULL_SCREEN' );

		// When the user plays (after the ondone event was fired )
		b( 'replayEvent', 'REPLAY' );

		// Bind on the seek event
		$( embedPlayer ).bind( 'seeked' + this.bindPostFix, function( seekTarget ) {
			// Don't send a bunch of seeks on scrub:
			if( _this.lastSeekEventTime == 0 ||
				_this.lastSeekEventTime + 2000	< new Date().getTime() )
			{
				_this.sendAnalyticsEvent( 'SEEK' );
			}

			// Update the last seekTime
			_this.lastSeekEventTime =  new Date().getTime();

			// Then set local seek flags
			this.hasSeeked = true;
			this.lastSeek = seekTarget;
		} );

		// Let updateTimeStats handle the currentTime monitor timing

		$( embedPlayer ).bind( 'monitorEvent' + this.bindPostFix, function(){
			_this.updateTimeStats();
		});

		/*
		 * Other kaltura event types that are presently not usable in the
		 * html5 player at this point in time:
		 *
		 * OPEN_EDIT = 8;
		 * OPEN_REPORT = 11;
		 * OPEN_UPLOAD = 18;
		 * SAVE_PUBLISH = 19;
		 * CLOSE_EDITOR = 20;
		 *
		 * PRE_BUMPER_PLAYED = 21;
		 * POST_BUMPER_PLAYED = 22;
		 * BUMPER_CLICKED = 23;
		 *
		 * FUTURE_USE_1 = 24;
		 * FUTURE_USE_2 = 25;
		 * FUTURE_USE_3 = 26;
		 */
	},

	/**
	 * Send updates for time stats
	 */
	updateTimeStats: function() {
		// Setup local references:
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Set the seek and time percent:
		var percent = embedPlayer.currentTime / embedPlayer.duration;
		var seekPercent = this.lastSeek/ embedPlayer.duration;

		// Send updates based on logic present in StatisticsMediator.as
		if ( !embedPlayer.isLive() ){
			if( !_this._p25Once && percent >= .25  &&  seekPercent <= .25 ) {

				_this._p25Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_25' );
				$( embedPlayer ).trigger( "firstQuartile" );

			} else if ( !_this._p50Once && percent >= .50 && seekPercent < .50 ) {

				_this._p50Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_50' );
				$( embedPlayer ).trigger( "secondQuartile" );

			} else if( !_this._p75Once && percent >= .75 && seekPercent < .75 ) {

				_this._p75Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_75' );
				$( embedPlayer ).trigger( "thirdQuartile" );

			} else if(  !_this._p100Once && percent >= .98 && seekPercent < 1) {

				_this._p100Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_100' );
			}
		}
	}
};

})( window.mw, window.jQuery );<commit_msg>switch order of new parameters<commit_after>/**
 * Kaltura style analytics reporting class
 */
( function( mw, $ ) { "use strict";

// Avoid undefined symbol for javascript response "Kaltura" from the api
window['Kaltura'] = true;

// KAnalytics Constructor
mw.KAnalytics = function( embedPlayer ){
	this.init( embedPlayer );
};

// Add analytics to the embed player:
mw.addKAnalytics = function( embedPlayer ) {
	embedPlayer.kAnalytics = new mw.KAnalytics( embedPlayer );
}
mw.KAnalytics.prototype = {

	// Local reference to embedPlayer
	embedPlayer: null,

	// Report Set object
	reportSet : null,

	// Stores the last time we issued a seek event
	// avoids sending lots of seeks while scrubbing
	lastSeekEventTime: 0,

	bindPostFix: '.kAnalytics',

	// Start Time
	startReportTime: 0,

	kEventTypes : {
		'WIDGET_LOADED' : 1,
		'MEDIA_LOADED' : 2,
		'PLAY' : 3,
		'PLAY_REACHED_25' : 4,
		'PLAY_REACHED_50' : 5,
		'PLAY_REACHED_75' : 6,
		'PLAY_REACHED_100' : 7,
		'OPEN_EDIT' : 8,
		'OPEN_VIRAL' : 9,
		'OPEN_DOWNLOAD' : 10,
		'OPEN_REPORT' : 11,
		'BUFFER_START' : 12,
		'BUFFER_END' : 13,
		'OPEN_FULL_SCREEN' : 14,
		'CLOSE_FULL_SCREEN' : 15,
		'REPLAY' : 16,
		'SEEK' : 17,
		'OPEN_UPLOAD' : 18,
		'SAVE_PUBLISH' : 19,
		'CLOSE_EDITOR' : 20,
		'PRE_BUMPER_PLAYED' : 21,
		'POST_BUMPER_PLAYED' : 22,
		'BUMPER_CLICKED' : 23,
		'FUTURE_USE_1' : 24,
		'FUTURE_USE_2' : 25,
		'FUTURE_USE_3' : 26
	},

	/**
	 * Constructor for kAnalytics
	 *
	 * @param {Object}
	 *		  embedPlayer Player to apply Kaltura analytics to.
	 * @parma {Object}
	 * 			kalturaClient Kaltura client object for the api session.
	 */
	init: function( embedPlayer ) {
		// set the version of html5 player
		this.version = mw.getConfig( 'version' );
		// Setup the local reference to the embed player
		this.embedPlayer = embedPlayer;
		if( ! this.kClient ) {
			this.kClient = mw.kApiGetPartnerClient( embedPlayer.kwidgetid );
		}
		// Remove any old bindings:
		$( embedPlayer ).unbind( this.bindPostFix );

		// Setup the initial state of some flags
		this.resetPlayerflags();

		// Add relevant hooks for reporting beacons
		this.bindPlayerEvents();
	},
	resetPlayerflags:function(){
		this._p25Once = false;
		this._p50Once = false;
		this._p75Once = false;
		this._p100Once = false;
		this.hasSeeked = false;
		this.lastSeek = 0;
	},
	/**
	 * Get the current report set
	 *
	 * @param {Number}
	 *			KalturaStatsEventType The eventType number.
	 */
	sendAnalyticsEvent: function( KalturaStatsEventKey ){
		var _this = this;
		mw.log("KAnalytics :: doSendAnalyticsEvent > " + KalturaStatsEventKey );
		// Kalutra analytics does not collect info for ads:
		if( this.embedPlayer.evaluate('{sequenceProxy.isInSequence}') ){
			return ;
		}

		// get the id for the given event:
		var eventKeyId = this.kEventTypes[ KalturaStatsEventKey ];

		// Generate the status event
		var eventSet = {
			'eventType'			: eventKeyId,
			'clientVer'			: this.version,
			'currentPoint'		: parseInt( this.embedPlayer.currentTime * 1000 ),
			'duration'			: this.embedPlayer.getDuration(),
			'eventTimestamp'	: new Date().getTime(),
			'isFirstInSession'	: 'false',
			'objectType'		: 'KalturaStatsEvent',
			'partnerId'			: this.embedPlayer.kpartnerid,
			'sessionId'			: this.embedPlayer.evaluate('{configProxy.sessionId}'),
			'uiconfId'			: 0
		};
		if( isNaN( eventSet.duration )  ){
			eventSet.duration = 0;
		}

		// Set the seek condition:
		eventSet[ 'seek' ] = ( this.hasSeeked ) ? 'true' : 'false';

		// Set the 'event:entryId'
		if( this.embedPlayer.kentryid ){
			eventSet[ 'entryId' ] = this.embedPlayer.kentryid;
		} else {
			// if kentryid is not set, use the selected source url
			eventSet[ 'entryId' ] = this.embedPlayer.getSrc();
		}

		// Set the 'event:uiconfId'
		if( this.embedPlayer.kuiconfid ) {
			eventSet[ 'uiconfId' ] = this.embedPlayer.kuiconfid;
		}
		// Set the 'event:widgetId'
		if( this.embedPlayer.kwidgetid ) {
			eventSet[ 'widgetId' ] = this.embedPlayer.kwidgetid;
		}
		var flashVarEvents = {
				'playbackContext' : 'contextId',
				'originFeature' : 'featureType',
				'applicationName' : 'applicationId',
				'userId' : 'userId'
		}
		// support legacy ( deprecated ) top level config
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( '', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('', fvKey ) );
			}
		}
		// check for the vars in the correct location:
		for( var fvKey in flashVarEvents){
			if( this.embedPlayer.getKalturaConfig( 'statistics', fvKey ) ){
				eventSet[ flashVarEvents[ fvKey ] ] = encodeURIComponent( this.embedPlayer.getKalturaConfig('statistics', fvKey ) );
			}
		}
		// Use this new parameter to send hashed userId in case the partner has limitation about storing users-data on Kaltura database.
		// This feature requires a new flashvar 'statistics.hashedUserId'.
		if(this.embedPlayer.getKalturaConfig( 'statistics' , 'hashedUserId')){
			eventSet.userId = this.embedPlayer.getKalturaConfig( 'statistics' , 'hashedUserId');
		}
		// hideUserId will remove the userId from the analytics call EVEN if the embed code sends one (unless hashedUserId is in use)
		if(this.embedPlayer.getKalturaConfig( 'statistics' , 'hideUserId') && eventSet.userId){
			delete(eventSet.userId);
		}

		// Add referrer parameter
		eventSet[ 'referrer' ] = encodeURIComponent( mw.getConfig('EmbedPlayer.IframeParentUrl') );

		// Add in base service and action calls:
		var eventRequest = {'service' : 'stats', 'action' : 'collect'};
		// Add event parameters
		for( var i in eventSet){
			eventRequest[ 'event:' + i] = eventSet[i];
		}

		// Send events for this player:
		$( this.embedPlayer ).trigger( 'KalturaSendAnalyticEvent', [ KalturaStatsEventKey, eventSet ] );

		// check for defined callback:
		var parentTrackName = this.embedPlayer.getKalturaConfig( 'statistics', 'trackEventMonitor');
		if(  mw.getConfig('EmbedPlayer.IsFriendlyIframe') ){
			try {
				if( window.parent[ parentTrackName ] ){
					 window.parent[ parentTrackName ]( KalturaStatsEventKey, eventSet );
				}
			} catch( e ){
				// error in calling parent page event
			}
		}
		//hideKS is an attribute that will prevent the request from sending the KS even if the embed code receives one
		if (this.embedPlayer.getFlashvars('ks') && !this.embedPlayer.getKalturaConfig( 'statistics' , 'hideKs') ){
			eventRequest['ks'] = this.embedPlayer.getFlashvars('ks');
		}

		// Do the api request:
		this.kClient.doRequest( eventRequest, null, true );
	},

	/**
	 * Binds player events for analytics reporting
	 */
	bindPlayerEvents: function(){

		// Setup local reference to embedPlayer
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Setup shortcut anonymous function for player bindings
		var b = function( hookName, eventType ){
			$( _this.embedPlayer ).bind( hookName + _this.bindPostFix, function(){
				_this.sendAnalyticsEvent( eventType );
			});
		};

		// When the player is ready
		b( 'widgetLoaded', 'WIDGET_LOADED' );

		// When the poster or video ( when autoplay ) media is loaded
		b( 'KalturaSupport_EntryDataReady', 'MEDIA_LOADED' );

		// When the play button is pressed or called from javascript
		b( 'firstPlay', 'PLAY' );

		// When the show Share menu is displayed
		b( 'showShareEvent', 'OPEN_VIRAL' );

		// When the show download menu is displayed
		b( 'downloadMedia', 'OPEN_DOWNLOAD' );

		// When the clip starts to buffer ( not all player types )
		b( 'bufferStartEvent', 'BUFFER_START' );

		// When the clip is full buffered
		b( 'bufferEndEvent', 'BUFFER_END' );

		// When the fullscreen button is pressed
		// ( presently does not register iPhone / iPad until it has js bindings )
		b( 'onOpenFullScreen', 'OPEN_FULL_SCREEN' );

		// When the close fullscreen button is pressed.
		// ( presently does not register iphone / ipad until it has js bindings )
		b( 'onCloseFullScreen', 'CLOSE_FULL_SCREEN' );

		// When the user plays (after the ondone event was fired )
		b( 'replayEvent', 'REPLAY' );

		// Bind on the seek event
		$( embedPlayer ).bind( 'seeked' + this.bindPostFix, function( seekTarget ) {
			// Don't send a bunch of seeks on scrub:
			if( _this.lastSeekEventTime == 0 ||
				_this.lastSeekEventTime + 2000	< new Date().getTime() )
			{
				_this.sendAnalyticsEvent( 'SEEK' );
			}

			// Update the last seekTime
			_this.lastSeekEventTime =  new Date().getTime();

			// Then set local seek flags
			this.hasSeeked = true;
			this.lastSeek = seekTarget;
		} );

		// Let updateTimeStats handle the currentTime monitor timing

		$( embedPlayer ).bind( 'monitorEvent' + this.bindPostFix, function(){
			_this.updateTimeStats();
		});

		/*
		 * Other kaltura event types that are presently not usable in the
		 * html5 player at this point in time:
		 *
		 * OPEN_EDIT = 8;
		 * OPEN_REPORT = 11;
		 * OPEN_UPLOAD = 18;
		 * SAVE_PUBLISH = 19;
		 * CLOSE_EDITOR = 20;
		 *
		 * PRE_BUMPER_PLAYED = 21;
		 * POST_BUMPER_PLAYED = 22;
		 * BUMPER_CLICKED = 23;
		 *
		 * FUTURE_USE_1 = 24;
		 * FUTURE_USE_2 = 25;
		 * FUTURE_USE_3 = 26;
		 */
	},

	/**
	 * Send updates for time stats
	 */
	updateTimeStats: function() {
		// Setup local references:
		var embedPlayer = this.embedPlayer;
		var _this = this;

		// Set the seek and time percent:
		var percent = embedPlayer.currentTime / embedPlayer.duration;
		var seekPercent = this.lastSeek/ embedPlayer.duration;

		// Send updates based on logic present in StatisticsMediator.as
		if ( !embedPlayer.isLive() ){
			if( !_this._p25Once && percent >= .25  &&  seekPercent <= .25 ) {

				_this._p25Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_25' );
				$( embedPlayer ).trigger( "firstQuartile" );

			} else if ( !_this._p50Once && percent >= .50 && seekPercent < .50 ) {

				_this._p50Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_50' );
				$( embedPlayer ).trigger( "secondQuartile" );

			} else if( !_this._p75Once && percent >= .75 && seekPercent < .75 ) {

				_this._p75Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_75' );
				$( embedPlayer ).trigger( "thirdQuartile" );

			} else if(  !_this._p100Once && percent >= .98 && seekPercent < 1) {

				_this._p100Once = true;
				_this.sendAnalyticsEvent( 'PLAY_REACHED_100' );
			}
		}
	}
};

})( window.mw, window.jQuery );<|endoftext|>


<commit_before>(function () {
  angular.module('mnAdmin').controller('mnAdminController', mnAdminController);

  function mnAdminController($scope, $rootScope, $q, mnHelper, mnSettingsNotificationsService, mnPromiseHelper, pools, mnPoll, mnAuthService, mnTasksDetails, mnAlertsService, mnPoolDefault, mnSettingsAutoFailoverService) {
    var vm = this;
    vm.launchpadId = pools.launchID;
    vm.alerts = mnAlertsService.alerts;
    vm.closeAlert = mnAlertsService.closeAlert;
    vm.logout = mnAuthService.logout;
    vm.resetAutoFailOverCount = resetAutoFailOverCount;

    activate();

    function resetAutoFailOverCount() {
      mnPromiseHelper(vm, mnSettingsAutoFailoverService.resetAutoFailOverCount())
        .showSpinner('resetQuotaLoading')
        .catchGlobalErrors('Unable to reset the auto-failover quota!')
        .reloadState()
        .cancelOnScopeDestroy($scope);
    }

    function activate() {
      mnPromiseHelper(vm, mnSettingsNotificationsService.maybeCheckUpdates())
        .applyToScope("updates")
        .onSuccess(function (updates) {
          if (updates.sendStats) {
            mnPromiseHelper(vm, mnSettingsNotificationsService.buildPhoneHomeThingy())
              .applyToScope("launchpadSource")
              .independentOfScope();
          }
        })
        .independentOfScope();

      mnPoll
        .start($scope, function () {
          return $q.all([
            mnTasksDetails.get(),
            mnPoolDefault.getFresh()
          ]);
        })
        .subscribe(function (resp) {
          vm.tasks = resp[0];
          $rootScope.tabName = resp[1] && resp[1].clusterName;
        })
        .cancelOnScopeDestroy()
        .run();
    }

  }
})();
<commit_msg>MB-12222: remove unnecessary dependency from mnAdmin module<commit_after>(function () {
  angular.module('mnAdmin').controller('mnAdminController', mnAdminController);

  function mnAdminController($scope, $rootScope, $q, mnSettingsNotificationsService, mnPromiseHelper, pools, mnPoll, mnAuthService, mnTasksDetails, mnAlertsService, mnPoolDefault, mnSettingsAutoFailoverService) {
    var vm = this;
    vm.launchpadId = pools.launchID;
    vm.alerts = mnAlertsService.alerts;
    vm.closeAlert = mnAlertsService.closeAlert;
    vm.logout = mnAuthService.logout;
    vm.resetAutoFailOverCount = resetAutoFailOverCount;

    activate();

    function resetAutoFailOverCount() {
      mnPromiseHelper(vm, mnSettingsAutoFailoverService.resetAutoFailOverCount())
        .showSpinner('resetQuotaLoading')
        .catchGlobalErrors('Unable to reset the auto-failover quota!')
        .reloadState()
        .cancelOnScopeDestroy($scope);
    }

    function activate() {
      mnPromiseHelper(vm, mnSettingsNotificationsService.maybeCheckUpdates())
        .applyToScope("updates")
        .onSuccess(function (updates) {
          if (updates.sendStats) {
            mnPromiseHelper(vm, mnSettingsNotificationsService.buildPhoneHomeThingy())
              .applyToScope("launchpadSource")
              .independentOfScope();
          }
        })
        .independentOfScope();

      mnPoll
        .start($scope, function () {
          return $q.all([
            mnTasksDetails.get(),
            mnPoolDefault.getFresh()
          ]);
        })
        .subscribe(function (resp) {
          vm.tasks = resp[0];
          $rootScope.tabName = resp[1] && resp[1].clusterName;
        })
        .cancelOnScopeDestroy()
        .run();
    }

  }
})();
<|endoftext|>


<commit_before>"use strict";

var $, ExclusionRulesOption, bgSettings, bgExclusions, BG,
KeyRe = /<(?:(?:a-(?:c-)?(?:m-)?|c-(?:m-)?|m-)(?:[A-Z][0-9A-Z]+|[a-z][0-9a-z]+|[^\s])|[A-Z][0-9A-Z]+|[a-z][0-9a-z]+)>|[^\s]/g,
__extends = function(child, parent) {
  function Ctor() { this.constructor = child; } Ctor.prototype = parent.prototype;
  child.prototype = new Ctor(); child.__super__ = parent.prototype;
};

$ = document.getElementById.bind(document);
BG = chrome.extension.getBackgroundPage();
bgSettings = BG.Settings;
bgExclusions = BG.Exclusions;
$("exclusionScrollBox").innerHTML = bgSettings.cache.exclusionTemplate;

function Option(element, onUpdated) {
  this.element = element;
  this.field = element.id;
  if (this.field in bgSettings.bufferToLoad) {
    this.onUpdated1 = onUpdated;
    onUpdated = this._onUpdated;
  }
  this.onUpdated = onUpdated.bind(this);
  this.previous = null;
  this.saved = true;
  this.fetch(bgSettings.get(this.field));
  Option.all.push(this);
}

Option.all = [];
Option.syncToFrontend = [];

Option.prototype._onUpdated = function() {
  this.onUpdated1();
  Settings.cache[this.field] = this.readValueFromElement();
};

Option.prototype.fetch = function() {
  this.populateElement(this.previous = bgSettings.get(this.field));
  this.saved = true;
};

Option.prototype.save = function() {
  var value = this.readValueFromElement(), previous = this.previous, notJSON = true;
  if (typeof value === "object") {
    value = JSON.stringify(value);
    previous = JSON.stringify(this.previous);
    if (value === previous) { return; }
    previous = value;
    notJSON = false;
    if (value === JSON.stringify(bgSettings.defaults[this.field])) {
      value = bgSettings.defaults[this.field];
    } else {
      value = BG.JSON.parse(value);
      if (this.checker) {
        value = this.checker.check(value);
      }
    }
  } else if (value === previous) {
    return;
  } else {
    previous = value;
    if (this.checker) {
      value = this.checker.check(value);
    }
  }
  bgSettings.set(this.field, value);
  this.previous = value = bgSettings.get(this.field);
  this.saved = true;
  if (previous !== (notJSON ? value : JSON.stringify(value))) {
    this.populateElement(value);
  }
  if (this.field in bgSettings.bufferToLoad) {
    Option.syncToFrontend.push(this.field);
  }
};

Option.areJSONEqual = function(a, b) {
  return this.saved = JSON.stringify(a) === JSON.stringify(b);
};

function ExclusionRulesOption() {
  var _this = this;
  this.template = $('exclusionRuleTemplate').content.children[0];
  ExclusionRulesOption.__super__.constructor.apply(this, arguments);
  $("exclusionAddButton").onclick = function() { _this.addRule(null); };
  this.element.addEventListener("input", this.onUpdated);
  this.element.addEventListener("click", function(e) { _this.onRemoveRow(e); });
}
__extends(ExclusionRulesOption, Option);

ExclusionRulesOption.prototype.addRule = function(pattern) {
  var element, exclusionScrollBox;
  element = this.appendRule(this.element, {
    pattern: pattern || "",
    passKeys: ""
  });
  this.getPattern(element).focus();
  exclusionScrollBox = this.element.parentElement;
  exclusionScrollBox.scrollTop = exclusionScrollBox.scrollHeight;
  if (pattern) {
    this.onUpdated();
  }
  return element;
};

ExclusionRulesOption.prototype.populateElement = function(rules) {
  var frag = document.createDocumentFragment(), head;
  head = this.element.querySelector('tr');
  frag.appendChild(head);
  this.element.textContent = "";
  rules.forEach(this.appendRule.bind(this, frag));
  this.element.appendChild(frag);
};

ExclusionRulesOption.prototype.appendRule = function(list, rule) {
  var row;
  row = document.importNode(this.template, true);
  row.querySelector('.pattern').value = rule.pattern;
  row.querySelector('.passKeys').value = rule.passKeys;
  list.appendChild(row);
  return row;
};

ExclusionRulesOption.prototype.onRemoveRow = function(event) {
  if (!event.target.classList.contains("exclusionRemoveButton")) {
    return;
  }
  var row1 = event.target.parentNode.parentNode;
  if (row1.classList.contains("exclusionRuleInstance")) {
    row1.remove();
    this.onUpdated();
  }
};

ExclusionRulesOption.prototype.reChar = /^[\^\*]|[^\\][\$\(\)\*\+\?\[\]\{\|\}]/;
ExclusionRulesOption.prototype._escapeRe = /\\./g;
ExclusionRulesOption.prototype.readValueFromElement = function(part) {
  var element, passKeys, pattern, rules, _i, _len, _ref, passArr;
  rules = [];
  _ref = this.element.getElementsByClassName("exclusionRuleInstance");
  part = (part === true);
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    element = _ref[_i];
    if (part && element.style.display === "none") {
      continue;
    }
    pattern = this.getPattern(element).value.trim();
    if (!pattern) {
      continue;
    }
    if (pattern[0] === ":" || element.style.display === "none") {}
    else if (this.reChar.test(pattern)) {
      pattern = pattern[0] === "^" ? pattern
        : (pattern.indexOf("://") === -1 ? "^http://" : "^") + pattern;
    } else {
      pattern = pattern.replace(this._escapeRe, "$&");
      pattern = (pattern.indexOf("://") === -1 ? ":http://" : ":") + pattern;
    }
    passKeys = this.getPassKeys(element).value;
    if (passKeys) {
      passArr = passKeys.match(KeyRe);
      passKeys = passArr ? (passArr.sort().join(" ") + " ") : "";
    }
    rules.push({
      pattern: pattern,
      passKeys: passKeys
    });
  }
  return rules;
};

ExclusionRulesOption.prototype.flatten = function(rule) {
  return (rule && rule.pattern) ? (rule.pattern + "\r" + rule.passKeys) : "";
};

ExclusionRulesOption.prototype.areEqual = Option.areJSONEqual;

ExclusionRulesOption.prototype.getPattern = function(element) {
  return element.querySelector(".pattern");
};

ExclusionRulesOption.prototype.getPassKeys = function(element) {
  return element.querySelector(".passKeys");
};

if (location.pathname.indexOf("/popup.html", location.pathname.length - 11) !== -1) {
chrome.tabs.query({currentWindow: true, active: true}, function(tab) {
  var exclusions, onUpdated, saveOptions, updateState, status = 0;

exclusions = {
  url: "",
  init: function(url, element, onUpdated) {
    this.url = url;
    this.rebuildTesters();
    Object.setPrototypeOf(this, ExclusionRulesOption.prototype);
    ExclusionRulesOption.call(this, element, onUpdated);
    this.element.addEventListener("input", this.OnInput);
    this.init = null;
  },
  rebuildTesters: function() {
    var rules = bgSettings.get("exclusionRules")
      , ref = bgExclusions.testers = Object.create(null)
      , ref2 = bgExclusions.rules, _i, _j, pattern;
    for (_i = rules.length, _j = 0; 0 <= --_i; ) {
      if (pattern = rules[_i].pattern) {
        ref[pattern] = ref2[_j++][0];
      }
    }
  },
  addRule: function() {
    ExclusionRulesOption.prototype.addRule.call(this, this.generateDefaultPattern());
  },
  populateElement: function(rules) {
    var element, elements, haveMatch, pattern, _i, _len;
    ExclusionRulesOption.prototype.populateElement.call(this, rules);
    elements = this.element.getElementsByClassName("exclusionRuleInstance");
    haveMatch = -1;
    for (_i = 0, _len = elements.length; _i < _len; _i++) {
      element = elements[_i];
      pattern = this.getPattern(element).value.trim();
      if (bgExclusions.testers[pattern](this.url)) {
        haveMatch = _i;
      } else {
        element.style.display = "none";
      }
    }
    if (haveMatch >= 0) {
      this.getPassKeys(elements[haveMatch]).focus();
    } else {
      this.addRule();
    }
  },
  OnInput: function(event) {
    var patternElement = event.target;
    if (!patternElement.classList.contains("pattern")) {
      return;
    }
    if (bgExclusions.getRe(patternElement.value)(exclusions.url)) {
      patternElement.title = patternElement.style.color = "";
    } else {
      patternElement.style.color = "red";
      patternElement.title = "Red text means that the pattern does not\nmatch the current URL.";
    }
  },
  generateDefaultPattern: function() {
    var url = this.url.lastIndexOf("https:", 0) === 0
      ? "^https?://" + this.url.split("/", 3)[2].replace(/\./g, "\\.") + "/"
      : /^[a-z]{3,}:\/\/./.test(this.url)
      ? ":" + (this.url.split("/", 3).join("/") + "/")
      : ":" + this.url;
    this.generateDefaultPattern = function() { return url; };
    return url;
  }
};

  tab = tab[0];
  var escapeRe = /[&<>]/g, escapeCallback = function(c, n) {
    n = c.charCodeAt(0);
    return (n === 60) ? "<" : (n === 62) ? ">" : "&";
  };
  updateState = function() {
    var pass = bgExclusions.getTemp(exclusions.url, exclusions.readValueFromElement(true));
    $("state").innerHTML = "Vimium++ will " + (pass
      ? "exclude: " + pass.replace(escapeRe, escapeCallback) + ""
      : pass !== null ? "be disabled" : "be enabled");
  };
  onUpdated = function() {
    if (status != 1) {
      status = 1;
      var btn = $("saveOptions");
      $("helpText").innerHTML = "Type Ctrl-Enter to save and close.";
      btn.removeAttribute("disabled");
      btn.textContent = "Save Changes";
    }
    if (!exclusions.init) {
      updateState();
    }
  };
  saveOptions = function() {
    var btn = $("saveOptions"), testers;
    if (btn.disabled) {
      return;
    }
    testers = bgExclusions.testers;
    exclusions.save();
    bgExclusions.testers = testers;
    btn.textContent = "Saved";
    btn.disabled = true;
    status = 0;
    // Here, since the popup page is showing, needIcon must be true.
    // Although the tab calls window.onfocus after this popup page closes,
    // it is too early for the tab to know new exclusion rules.
    BG.g_requestHandlers.SetIcon(tab.id, null, bgExclusions.getPattern(exclusions.url));
  };
  $("saveOptions").onclick = saveOptions;
  document.addEventListener("keyup", function(event) {
    if ((event.ctrlKey || event.metaKey) && event.keyCode === 13) {
      if (status === 1) {
        saveOptions();
      }
      setTimeout(window.close, 300);
    }
  });
  exclusions.init(bgSettings.urlForTab[tab.id] || tab.url, $("exclusionRules"), onUpdated);
  updateState();
  var link = $("optionsLink");
  link.href = bgSettings.CONST.OptionsPage;
  link.onclick = function(event) {
    BG.g_requestHandlers.focusOrLaunch({ url: this.href });
    event.preventDefault();
    event.stopImmediatePropagation();
    window.close();
  };
  window.onunload = function() {
    bgExclusions.testers = null;
  };
});

}
<commit_msg>use Object.setPrototypeOf instead of Ctor<commit_after>"use strict";

var $, ExclusionRulesOption, bgSettings, bgExclusions, BG,
KeyRe = /<(?:(?:a-(?:c-)?(?:m-)?|c-(?:m-)?|m-)(?:[A-Z][0-9A-Z]+|[a-z][0-9a-z]+|[^\s])|[A-Z][0-9A-Z]+|[a-z][0-9a-z]+)>|[^\s]/g,
__extends = function(child, parent) {
  Object.setPrototypeOf(child.prototype, parent.prototype);
  child.__super__ = parent.prototype;
};

$ = document.getElementById.bind(document);
BG = chrome.extension.getBackgroundPage();
bgSettings = BG.Settings;
bgExclusions = BG.Exclusions;
$("exclusionScrollBox").innerHTML = bgSettings.cache.exclusionTemplate;

function Option(element, onUpdated) {
  this.element = element;
  this.field = element.id;
  if (this.field in bgSettings.bufferToLoad) {
    this.onUpdated1 = onUpdated;
    onUpdated = this._onUpdated;
  }
  this.onUpdated = onUpdated.bind(this);
  this.previous = null;
  this.saved = true;
  this.fetch(bgSettings.get(this.field));
  Option.all.push(this);
}

Option.all = [];
Option.syncToFrontend = [];

Option.prototype._onUpdated = function() {
  this.onUpdated1();
  Settings.cache[this.field] = this.readValueFromElement();
};

Option.prototype.fetch = function() {
  this.populateElement(this.previous = bgSettings.get(this.field));
  this.saved = true;
};

Option.prototype.save = function() {
  var value = this.readValueFromElement(), previous = this.previous, notJSON = true;
  if (typeof value === "object") {
    value = JSON.stringify(value);
    previous = JSON.stringify(this.previous);
    if (value === previous) { return; }
    previous = value;
    notJSON = false;
    if (value === JSON.stringify(bgSettings.defaults[this.field])) {
      value = bgSettings.defaults[this.field];
    } else {
      value = BG.JSON.parse(value);
      if (this.checker) {
        value = this.checker.check(value);
      }
    }
  } else if (value === previous) {
    return;
  } else {
    previous = value;
    if (this.checker) {
      value = this.checker.check(value);
    }
  }
  bgSettings.set(this.field, value);
  this.previous = value = bgSettings.get(this.field);
  this.saved = true;
  if (previous !== (notJSON ? value : JSON.stringify(value))) {
    this.populateElement(value);
  }
  if (this.field in bgSettings.bufferToLoad) {
    Option.syncToFrontend.push(this.field);
  }
};

Option.areJSONEqual = function(a, b) {
  return this.saved = JSON.stringify(a) === JSON.stringify(b);
};

function ExclusionRulesOption() {
  var _this = this;
  this.template = $('exclusionRuleTemplate').content.children[0];
  ExclusionRulesOption.__super__.constructor.apply(this, arguments);
  $("exclusionAddButton").onclick = function() { _this.addRule(null); };
  this.element.addEventListener("input", this.onUpdated);
  this.element.addEventListener("click", function(e) { _this.onRemoveRow(e); });
}
__extends(ExclusionRulesOption, Option);

ExclusionRulesOption.prototype.addRule = function(pattern) {
  var element, exclusionScrollBox;
  element = this.appendRule(this.element, {
    pattern: pattern || "",
    passKeys: ""
  });
  this.getPattern(element).focus();
  exclusionScrollBox = this.element.parentElement;
  exclusionScrollBox.scrollTop = exclusionScrollBox.scrollHeight;
  if (pattern) {
    this.onUpdated();
  }
  return element;
};

ExclusionRulesOption.prototype.populateElement = function(rules) {
  var frag = document.createDocumentFragment(), head;
  head = this.element.querySelector('tr');
  frag.appendChild(head);
  this.element.textContent = "";
  rules.forEach(this.appendRule.bind(this, frag));
  this.element.appendChild(frag);
};

ExclusionRulesOption.prototype.appendRule = function(list, rule) {
  var row;
  row = document.importNode(this.template, true);
  row.querySelector('.pattern').value = rule.pattern;
  row.querySelector('.passKeys').value = rule.passKeys;
  list.appendChild(row);
  return row;
};

ExclusionRulesOption.prototype.onRemoveRow = function(event) {
  if (!event.target.classList.contains("exclusionRemoveButton")) {
    return;
  }
  var row1 = event.target.parentNode.parentNode;
  if (row1.classList.contains("exclusionRuleInstance")) {
    row1.remove();
    this.onUpdated();
  }
};

ExclusionRulesOption.prototype.reChar = /^[\^\*]|[^\\][\$\(\)\*\+\?\[\]\{\|\}]/;
ExclusionRulesOption.prototype._escapeRe = /\\./g;
ExclusionRulesOption.prototype.readValueFromElement = function(part) {
  var element, passKeys, pattern, rules, _i, _len, _ref, passArr;
  rules = [];
  _ref = this.element.getElementsByClassName("exclusionRuleInstance");
  part = (part === true);
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    element = _ref[_i];
    if (part && element.style.display === "none") {
      continue;
    }
    pattern = this.getPattern(element).value.trim();
    if (!pattern) {
      continue;
    }
    if (pattern[0] === ":" || element.style.display === "none") {}
    else if (this.reChar.test(pattern)) {
      pattern = pattern[0] === "^" ? pattern
        : (pattern.indexOf("://") === -1 ? "^http://" : "^") + pattern;
    } else {
      pattern = pattern.replace(this._escapeRe, "$&");
      pattern = (pattern.indexOf("://") === -1 ? ":http://" : ":") + pattern;
    }
    passKeys = this.getPassKeys(element).value;
    if (passKeys) {
      passArr = passKeys.match(KeyRe);
      passKeys = passArr ? (passArr.sort().join(" ") + " ") : "";
    }
    rules.push({
      pattern: pattern,
      passKeys: passKeys
    });
  }
  return rules;
};

ExclusionRulesOption.prototype.flatten = function(rule) {
  return (rule && rule.pattern) ? (rule.pattern + "\r" + rule.passKeys) : "";
};

ExclusionRulesOption.prototype.areEqual = Option.areJSONEqual;

ExclusionRulesOption.prototype.getPattern = function(element) {
  return element.querySelector(".pattern");
};

ExclusionRulesOption.prototype.getPassKeys = function(element) {
  return element.querySelector(".passKeys");
};

if (location.pathname.indexOf("/popup.html", location.pathname.length - 11) !== -1) {
chrome.tabs.query({currentWindow: true, active: true}, function(tab) {
  var exclusions, onUpdated, saveOptions, updateState, status = 0;

exclusions = {
  url: "",
  init: function(url, element, onUpdated) {
    this.url = url;
    this.rebuildTesters();
    Object.setPrototypeOf(this, ExclusionRulesOption.prototype);
    ExclusionRulesOption.call(this, element, onUpdated);
    this.element.addEventListener("input", this.OnInput);
    this.init = null;
  },
  rebuildTesters: function() {
    var rules = bgSettings.get("exclusionRules")
      , ref = bgExclusions.testers = Object.create(null)
      , ref2 = bgExclusions.rules, _i, _j, pattern;
    for (_i = rules.length, _j = 0; 0 <= --_i; ) {
      if (pattern = rules[_i].pattern) {
        ref[pattern] = ref2[_j++][0];
      }
    }
  },
  addRule: function() {
    ExclusionRulesOption.prototype.addRule.call(this, this.generateDefaultPattern());
  },
  populateElement: function(rules) {
    var element, elements, haveMatch, pattern, _i, _len;
    ExclusionRulesOption.prototype.populateElement.call(this, rules);
    elements = this.element.getElementsByClassName("exclusionRuleInstance");
    haveMatch = -1;
    for (_i = 0, _len = elements.length; _i < _len; _i++) {
      element = elements[_i];
      pattern = this.getPattern(element).value.trim();
      if (bgExclusions.testers[pattern](this.url)) {
        haveMatch = _i;
      } else {
        element.style.display = "none";
      }
    }
    if (haveMatch >= 0) {
      this.getPassKeys(elements[haveMatch]).focus();
    } else {
      this.addRule();
    }
  },
  OnInput: function(event) {
    var patternElement = event.target;
    if (!patternElement.classList.contains("pattern")) {
      return;
    }
    if (bgExclusions.getRe(patternElement.value)(exclusions.url)) {
      patternElement.title = patternElement.style.color = "";
    } else {
      patternElement.style.color = "red";
      patternElement.title = "Red text means that the pattern does not\nmatch the current URL.";
    }
  },
  generateDefaultPattern: function() {
    var url = this.url.lastIndexOf("https:", 0) === 0
      ? "^https?://" + this.url.split("/", 3)[2].replace(/\./g, "\\.") + "/"
      : /^[a-z]{3,}:\/\/./.test(this.url)
      ? ":" + (this.url.split("/", 3).join("/") + "/")
      : ":" + this.url;
    this.generateDefaultPattern = function() { return url; };
    return url;
  }
};

  tab = tab[0];
  var escapeRe = /[&<>]/g, escapeCallback = function(c, n) {
    n = c.charCodeAt(0);
    return (n === 60) ? "<" : (n === 62) ? ">" : "&";
  };
  updateState = function() {
    var pass = bgExclusions.getTemp(exclusions.url, exclusions.readValueFromElement(true));
    $("state").innerHTML = "Vimium++ will " + (pass
      ? "exclude: " + pass.replace(escapeRe, escapeCallback) + ""
      : pass !== null ? "be disabled" : "be enabled");
  };
  onUpdated = function() {
    if (status != 1) {
      status = 1;
      var btn = $("saveOptions");
      $("helpText").innerHTML = "Type Ctrl-Enter to save and close.";
      btn.removeAttribute("disabled");
      btn.textContent = "Save Changes";
    }
    if (!exclusions.init) {
      updateState();
    }
  };
  saveOptions = function() {
    var btn = $("saveOptions"), testers;
    if (btn.disabled) {
      return;
    }
    testers = bgExclusions.testers;
    exclusions.save();
    bgExclusions.testers = testers;
    btn.textContent = "Saved";
    btn.disabled = true;
    status = 0;
    // Here, since the popup page is showing, needIcon must be true.
    // Although the tab calls window.onfocus after this popup page closes,
    // it is too early for the tab to know new exclusion rules.
    BG.g_requestHandlers.SetIcon(tab.id, null, bgExclusions.getPattern(exclusions.url));
  };
  $("saveOptions").onclick = saveOptions;
  document.addEventListener("keyup", function(event) {
    if ((event.ctrlKey || event.metaKey) && event.keyCode === 13) {
      if (status === 1) {
        saveOptions();
      }
      setTimeout(window.close, 300);
    }
  });
  exclusions.init(bgSettings.urlForTab[tab.id] || tab.url, $("exclusionRules"), onUpdated);
  updateState();
  var link = $("optionsLink");
  link.href = bgSettings.CONST.OptionsPage;
  link.onclick = function(event) {
    BG.g_requestHandlers.focusOrLaunch({ url: this.href });
    event.preventDefault();
    event.stopImmediatePropagation();
    window.close();
  };
  window.onunload = function() {
    bgExclusions.testers = null;
  };
});

}
<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';
var db = require('../db.js');
var main_chain = require('../main_chain.js');

var args = process.argv.slice(2);
var earlier_unit = args[0];
var arrLaterUnits = args[1].split(',');

console.log("update stability of " + earlier_unit + " in " + arrLaterUnits);

db.executeInTransaction(function(conn, cb){
	main_chain.determineIfStableInLaterUnitsAndUpdateStableMcFlag(conn, earlier_unit, arrLaterUnits, false, function (bStable) {
		console.log('--- stable? ', bStable);
		cb();
	});
});
<commit_msg>init caches before updating stability<commit_after>/*jslint node: true */
'use strict';
var db = require('../db.js');
var storage = require('../storage.js');
var main_chain = require('../main_chain.js');

var args = process.argv.slice(2);
var earlier_unit = args[0];
var arrLaterUnits = args[1].split(',');

console.log("update stability of " + earlier_unit + " in " + arrLaterUnits);

storage.initCaches();

db.executeInTransaction(function(conn, cb){
	main_chain.determineIfStableInLaterUnitsAndUpdateStableMcFlag(conn, earlier_unit, arrLaterUnits, false, function (bStable) {
		console.log('--- stable? ', bStable);
		cb();
	});
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.boot',
  name: 'NSpec',

  javaImports: [
    'bsh.EvalError',
    'bsh.Interpreter',
    'foam.dao.DAO',
    'foam.core.FObject'
  ],

  ids: [ 'name' ],

  tableColumns: [ 'name', 'lazy', 'serve', 'serviceClass' ],

  searchColumns: [ ],

  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      class: 'Boolean',
      name: 'lazy',
      value: true
    },
    {
      class: 'Boolean',
      name: 'serve',
      // Used by u2.view.TableView
      tableCellFormatter: function(value, obj, property) {
        this
          .start()
            .call(function() {
              if ( value ) { this.style({color: 'green'}); } else { this.entity('nbsp'); }
            })
            .add(obj.serve ? ' Y' : '-')
          .end();
      },
      // Used by u2.TableView
      tableCellView: function(obj, e) {
        var e = e.E();
        if ( obj.serve ) { e.style({color: 'green'}); } else { e.entity('nbsp'); }
        e.add(obj.serve ? ' Y' : '-');
        return e;
      },
      documentation: 'If true, this service is served over the network.'
    },
    {
      class: 'String',
      name: 'serviceClass'
    },
    {
      class: 'String',
      name: 'boxClass'
    },
    {
      class: 'String',
      name: 'serviceScript'
    },
    {
      class: 'FObjectProperty',
      name: 'service',
      view: 'foam.u2.DetailView'
    }
    // TODO: permissions, keywords, lazy, parent
  ],

  methods: [
    {
      name: 'saveService',
      args: [ { name: 'service', javaType: 'Object' } ],
      javaCode: `
      System.err.println("***** saveService: " + service.getClass() + " " + (service instanceof FObject));
        if ( service instanceof FObject ) {
          setService((FObject) service);
          DAO dao = (DAO) getX().get("nSpecDAO");
          dao.put(this);
        }
      `
    },
    {
      name: 'createService',
      javaReturns: 'java.lang.Object',
      javaCode: `
        // if ( getService() != null ) return getService();

        if ( getServiceClass().length() > 0 ) {
          Object service = Class.forName(getServiceClass()).newInstance();
          // TODO: doesn't work with DAO's, fix
          // saveService(service);
          return service;
        }

        Interpreter shell = new Interpreter();
        try {
          Object service = shell.eval(getServiceScript());
          saveService(service);
          return service;
        } catch (EvalError e) {
          e.printStackTrace();
        }

        return null;
      `,
      javaThrows: [
        'java.lang.ClassNotFoundException',
        'java.lang.InstantiationException',
        'java.lang.IllegalAccessException'
      ],
    }
  ]
});
<commit_msg>Set NSpec.serviceScript to be a TextArea.<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.boot',
  name: 'NSpec',

  javaImports: [
    'bsh.EvalError',
    'bsh.Interpreter',
    'foam.dao.DAO',
    'foam.core.FObject'
  ],

  ids: [ 'name' ],

  tableColumns: [ 'name', 'lazy', 'serve', 'serviceClass' ],

  searchColumns: [ ],

  properties: [
    {
      class: 'String',
      name: 'name'
    },
    {
      class: 'Boolean',
      name: 'lazy',
      value: true
    },
    {
      class: 'Boolean',
      name: 'serve',
      // Used by u2.view.TableView
      tableCellFormatter: function(value, obj, property) {
        this
          .start()
            .call(function() {
              if ( value ) { this.style({color: 'green'}); } else { this.entity('nbsp'); }
            })
            .add(obj.serve ? ' Y' : '-')
          .end();
      },
      // Used by u2.TableView
      tableCellView: function(obj, e) {
        var e = e.E();
        if ( obj.serve ) { e.style({color: 'green'}); } else { e.entity('nbsp'); }
        e.add(obj.serve ? ' Y' : '-');
        return e;
      },
      documentation: 'If true, this service is served over the network.'
    },
    {
      class: 'String',
      name: 'serviceClass'
    },
    {
      class: 'String',
      name: 'boxClass'
    },
    {
      class: 'String',
      name: 'serviceScript',
      view: { class: 'foam.u2.tag.TextArea', rows: 12, cols: 80 }
    },
    {
      class: 'FObjectProperty',
      name: 'service',
      view: 'foam.u2.DetailView'
    }
    // TODO: permissions, keywords, lazy, parent
  ],

  methods: [
    {
      name: 'saveService',
      args: [ { name: 'service', javaType: 'Object' } ],
      javaCode: `
      System.err.println("***** saveService: " + service.getClass() + " " + (service instanceof FObject));
        if ( service instanceof FObject ) {
          setService((FObject) service);
          DAO dao = (DAO) getX().get("nSpecDAO");
          dao.put(this);
        }
      `
    },
    {
      name: 'createService',
      javaReturns: 'java.lang.Object',
      javaCode: `
        // if ( getService() != null ) return getService();

        if ( getServiceClass().length() > 0 ) {
          Object service = Class.forName(getServiceClass()).newInstance();
          // TODO: doesn't work with DAO's, fix
          // saveService(service);
          return service;
        }

        Interpreter shell = new Interpreter();
        try {
          Object service = shell.eval(getServiceScript());
          saveService(service);
          return service;
        } catch (EvalError e) {
          e.printStackTrace();
        }

        return null;
      `,
      javaThrows: [
        'java.lang.ClassNotFoundException',
        'java.lang.InstantiationException',
        'java.lang.IllegalAccessException'
      ],
    }
  ]
});
<|endoftext|>


<commit_before>// REACT COMPONENTS
import React from 'react';
import {
  Form,
  Radio,
  Checkbox,
  Header
} from 'semantic-ui-react';

// PROJECT COMPONENTS
import {
  FormPartsContainer,
  // MassiveToggle,
  FormHeading,
  IntervalColumnHeadings,
  CashFlowRow
} from './formHelpers';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// LOGIC
import {
  getEveryMember,
  isHeadOrSpouse,
  getDependentMembers,
  isDisabled,
  isUnder13,
} from '../utils/getMembers';

// ========================================
// COMPONENTS
// ========================================

const Utilities = function ({ current, type, time, setClientProperty }) {

  let climate     = current.hasClimateControl,
      electricity = current.nonHeatElectricity,
      phone       = current.phone,
      fuelAssist  = current.hasFuelAssistance;

  let setChecked = function ( evnt, inputProps ) {
    var obj = { ...inputProps, value: inputProps.checked };
    setClientProperty( evnt, obj );
  };  // End setChecked()

  let toBool = function ( evnt, inputProps ) {
    var val = inputProps.value === 'Yes',
        obj = { ...inputProps, value: val };
    setClientProperty( evnt, obj );
  };  // End toBool()

  return (
    <wrapper>
      Which of these utilities do you pay for?

      <Checkbox
        name={'hasClimateControl'}
        label={'Heating or cooling (e.g. A/C during summer)'}
        checked={climate}
        onChange={setChecked}
      />

      <Checkbox
        name={'nonHeatElectricity'}
        label={'Electricity for non-heating purposes'}
        checked={electricity}
        onChange={setChecked}
      />

      <Checkbox
        name={'phone'}
        label={'Telephone service'}
        checked={phone}
        onChange={setChecked}
      />

      Do you get Fuel Assistance?
      <Form.Field style={{display: 'inline-block', paddingRight: '1em'}}>
        <Radio
          name={'hasFuelAssistance'}
          label={'Yes'} value={'Yes'}
          checked={fuelAssist}
          onChange={toBool}
        />
      </Form.Field>
      <Form.Field style={{display: 'inline-block', paddingRight: '1em'}}>
        <Radio
          name={'hasFuelAssistance'}
          label={'No'} value={'No'}
          checked={!fuelAssist}
          onChange={toBool}
        />
      </Form.Field>
    </wrapper>

  );
};  // End Utilities(<>)

const ShelterDetails = function ({ current, type, time, setClientProperty }) {

  let shelter = current.shelter,
      sharedProps = {
        timeState: current, current: current, type: type, time: time,
        setClientProperty: setClientProperty
      };

  if ( current.hasHousing ) {
    return (
      <wrapper>
        <IntervalColumnHeadings type={ type }/>
        <CashFlowRow {...sharedProps} generic={'rentShare'}> Rent Share </CashFlowRow>
        <CashFlowRow {...sharedProps}
          generic={'contractRent'}
          labeInfo={'The full amount the landlord would charge without a Section 8 voucher'}>
            Contract Rent
        </CashFlowRow>
        <Utilities {...sharedProps}/>
      </wrapper>
    );

  } else if ( shelter === 'homeless' ) {
    return null;

  } else if ( shelter === 'renter' ) {
    return (
      <wrapper>
        <IntervalColumnHeadings type={ type }/>
        <CashFlowRow {...sharedProps} generic={'rent'}> Rent </CashFlowRow>
        <Utilities {...sharedProps}/>
      </wrapper>
    );

  } else if ( shelter === 'homeowner' ) {
    return (
      <wrapper>
        <IntervalColumnHeadings type={ type }/>
        <CashFlowRow {...sharedProps} generic={'mortgage'}> Mortgage </CashFlowRow>
        <CashFlowRow {...sharedProps} generic={'housingInsurance'}> Insurance Costs </CashFlowRow>
        <CashFlowRow {...sharedProps} generic={'propertyTax'}> Property Tax </CashFlowRow>
        <Utilities {...sharedProps}/>
      </wrapper>
    );

  }  // end which expenses
};  // End ShelterDetails(<>)

const ShelterRadio = function ({ currentValue, label, time, setClientProperty }) {

  var value = label.toLowerCase();

  return (
    <Form.Field>
      <Radio
        name={'shelter'}
        label={label}
        value={value}
        checked={currentValue === value}
        onChange={setClientProperty}
      />
    </Form.Field>
  );

};  // End ShelterRadio(<>)

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const Housing = function ({ current, type, time, setClientProperty }) {

  // We're using a bunch of radio buttons. Since `checked` is defined
  // in Radio components, `setClientProperty()` would store it, but we
  // want the value, so get rid of checked.
  /** Makes sure values are propagated to 'current' properties if needed */
  let ensureRouteAndValue = function ( evnt, inputProps ) {
    var obj = { ...inputProps, name: inputProps.name, value: inputProps.value, checked: null };
    setClientProperty( evnt, obj );
  };

  let sharedProps = {
    current: current, type: type, time: time,
    setClientProperty: ensureRouteAndValue
  };

  return (
    <wrapper>

      <FormHeading>Shelter</FormHeading>

      { current.hasHousing
      ? null
      : <wrapper>

        What is your housing situation?
        <ShelterRadio
          currentValue={current.shelter}
          label={'Homeless'}
          time={time}
          setClientProperty={ensureRouteAndValue}
        />
        <ShelterRadio
          currentValue={current.shelter}
          label={'Renter'}
          time={time}
          setClientProperty={ensureRouteAndValue}
        />
        <ShelterRadio
          currentValue={current.shelter}
          label={'Homeowner'}
          time={time}
          setClientProperty={ensureRouteAndValue}
        />

      </wrapper>}

      <ShelterDetails {...sharedProps}/>

    </wrapper>
  );

};  // End Housing()

/** @todo description
*
* @todo Discuss splitting into a sub-progress bar and breaking it up
* into individual form pages.
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const ExpensesFormContent = function ({ client, current, time, setClientProperty }) {

  let type        = 'expense',
      household   = client[ time ].household,
      sharedProps = { timeState: current, type: type, time: time, setClientProperty: setClientProperty };

  /* @todo Make an age-checking function to
   *     keep household data structure under
   *     control in one place.
   */
  var isOver12 = function ( member ) { return member.m_age > 12; };

  // Won't include head or spouse
  var allDependents = getDependentMembers( household ),
      under13       = getEveryMember( allDependents, isUnder13 ),
      // or under13 = getUnder13Members( allDependents ),
      over12        = getEveryMember( allDependents, isOver12 );

  // 'Elderly' here is using the lowest common denominator - SNAP standards
  var isElderlyOrDisabled = function ( member ) {
    return isDisabled( member ) || member.m_age >= 60;
  };
  var elderlyOrDisabled = getEveryMember( household, isElderlyOrDisabled ),
      elderlyOrDisabledHeadAndSpouse = getEveryMember( elderlyOrDisabled, isHeadOrSpouse );

  /**
  * @todo Does money from any programs count as income?
  * @todo Complete only show questions that are relevant to the client's slected programs
  */
  return (
    <wrapper className='field-aligner two-column'>

      { under13.length > 0
        ? <wrapper>
          <FormHeading subheading = {'A "child" is a person 12 or younger. Don\'t include amounts that are paid for by other benefit programs.\n'}>
            Reasonable Unreimbursed Non-Medical Child(ren) Care</FormHeading>
          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic={'childDirectCare'}> Direct care costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'childBeforeAndAfterSchoolCare'}> Before- and after-school care </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'childTransportation'}> Transportation costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'childOtherCare'}> Other care </CashFlowRow>
          <CashFlowRow {...sharedProps} type={'income'} generic={'earnedBecauseOfChildCare'}> Income made possible by child care expenses </CashFlowRow>
        </wrapper>
        : null
      }

      {/** Wrapper here or else margins get added here, but not other
      * places, making spacing hard to manage */}
      <wrapper>
        <FormHeading>Child Support</FormHeading>
        <IntervalColumnHeadings type={type}/>
        <CashFlowRow {...sharedProps} generic={'childSupportPaidOut'}> Legally obligated child support </CashFlowRow>
      </wrapper>

      {/* Head or spouse can't be a dependent, so they don't count */}
      {/* With future version of form, don't show if there are only elderly,
        but not disabled, members. Also, show if there are people between
        > 12, but <= 18 */}
      { over12.length > 0
        ? <wrapper>
          <FormHeading subheading = {'For the care of people who are older than 12, but are still dependents (those under 18 or disabled). Don\'t include amounts that are paid for by other benefit programs.\n'}>
            Dependent Care of Persons Over 12 Years of Age</FormHeading>
          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic={'adultDirectCare'}> Direct care costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'adultTransportation'}> Transportation costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'adultOtherCare'}> Other care </CashFlowRow>
        </wrapper>
        : null
      }

      { elderlyOrDisabled.length > 0
        ? <wrapper>
          <FormHeading>Unreimbursed Disabled/Handicapped/Elderly Assistance</FormHeading>

Unreimbursed expenses to cover care attendants and auxiliary apparatus for any family member who is elderly or is a person with disabilities. Auxiliary apparatus are items such as wheelchairs, ramps, adaptations to vehicles, or special equipment to enable a blind person to read or type, but only if these items are directly related to permitting the disabled person or other family member to work.

Examples of eligible disability assistance expenses:

The payments made on a motorized wheelchair for the 42 year old son of the head of household enable the son to leave the house and go to work each day on his own. Prior to the purchase of the motorized wheelchair, the son was unable to make the commute to work. These payments are an eligible disability assistance expense.

Payments to a care attendant to stay with a disabled 16-year-old child allow the child’s mother to go to work every day. These payments are an eligible disability assistance allowance.

          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic={'disabledAssistance'}> Disabled/Handicapped assistance </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'earnedBecauseOfAdultCare'}> Income made possible by assistance expenses </CashFlowRow>
        </wrapper>
        : null
      }

        {/* These medical expenses don't count for Section 8 unless
          the disabled person is the head or spouse. From
          http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
          Appendix B, item (D) */}
        { elderlyOrDisabledHeadAndSpouse.length > 0 || (current.hasSnap && elderlyOrDisabled.length > 0)
          ? <wrapper>
          <FormHeading>Unreimbursed Medical Expenses</FormHeading>

Do not repeat anything you already listed in the section above. Examples of allowable medical expenses:

The orthodontist expenses for a child’s braces.

Services of doctors and health care professionals.

Services of health care facilities.

Medical insurance premiums.

Prescription/non-prescription medicines (prescribed by a physician).

Transportation to treatment (cab fare, bus fare, mileage).

Dental expenses, eyeglasses, hearing aids, batteries.

Live-in or periodic medical assistance.

Monthly payment on accumulated medical bills (regular monthly payments on a bill that was previously incurred).

          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic='disabledOrElderlyMedical'> Disabled/Elderly medical expenses </CashFlowRow>
          <CashFlowRow {...sharedProps} generic='membersMedical'> Medical expenses of other members </CashFlowRow>
        </wrapper>
        : null
      }

      <Housing current={current} time={time} type={type} setClientProperty={setClientProperty} />

      <FormHeading>Other</FormHeading>
      <CashFlowRow {...sharedProps} generic={'otherExpenses'}> Other Expenses </CashFlowRow>

    </wrapper>
  );

};  // End ExpensesFormContent()

/**
* @todo SNAP: Does a medical assistant's payments count as a medical expense?
* (Answer: Yes. https://www.mass.gov/service-details/snap-verifications)
* @todo SNAP: Medical expense only matters if household has elder/disabled, but
* are they any medical expenses or only those of the disabled person? "Medical
* Expenses for Disabled or Elderly". Also, do they sometimes need to
* enter medical expenses even if they don't have an elderly or disabled
* household member?
*/

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
// `props` is a cloned version of the original props. References broken.
const CurrentExpensesStep = function ( props ) {

  /** @todo Maybe getTimeSetter can actually convert to 'route' too? */
  const setTimeProp = getTimeSetter( 'current', props.changeClient );

  return (
    <Form className = 'expense-form flex-item flex-column'>
      <FormPartsContainer
        title     = {'Current Household Expenses'}
        clarifier = {null}
        left      = {{name: 'Previous', func: props.previousStep}}
        right     = {{name: 'Next', func: props.nextStep}}
      >
        <ExpensesFormContent setClientProperty={setTimeProp} client={props.client} current={props.client.current} time={'current'} />
      </FormPartsContainer>
    </Form>
  );

};  // End CurrentExpensesStep()

export { CurrentExpensesStep };
<commit_msg>Removes unnecessary `client` passing, removes wrong description<commit_after>// REACT COMPONENTS
import React from 'react';
import {
  Form,
  Radio,
  Checkbox,
  Header
} from 'semantic-ui-react';

// PROJECT COMPONENTS
import {
  FormPartsContainer,
  // MassiveToggle,
  FormHeading,
  IntervalColumnHeadings,
  CashFlowRow
} from './formHelpers';

// COMPONENT HELPER FUNCTIONS
import { getTimeSetter } from '../utils/getTimeSetter';

// LOGIC
import {
  getEveryMember,
  isHeadOrSpouse,
  getDependentMembers,
  isDisabled,
  isUnder13,
} from '../utils/getMembers';

// ========================================
// COMPONENTS
// ========================================

const Utilities = function ({ current, type, time, setClientProperty }) {

  let climate     = current.hasClimateControl,
      electricity = current.nonHeatElectricity,
      phone       = current.phone,
      fuelAssist  = current.hasFuelAssistance;

  let setChecked = function ( evnt, inputProps ) {
    var obj = { ...inputProps, value: inputProps.checked };
    setClientProperty( evnt, obj );
  };  // End setChecked()

  let toBool = function ( evnt, inputProps ) {
    var val = inputProps.value === 'Yes',
        obj = { ...inputProps, value: val };
    setClientProperty( evnt, obj );
  };  // End toBool()

  return (
    <wrapper>
      Which of these utilities do you pay for?

      <Checkbox
        name={'hasClimateControl'}
        label={'Heating or cooling (e.g. A/C during summer)'}
        checked={climate}
        onChange={setChecked}
      />

      <Checkbox
        name={'nonHeatElectricity'}
        label={'Electricity for non-heating purposes'}
        checked={electricity}
        onChange={setChecked}
      />

      <Checkbox
        name={'phone'}
        label={'Telephone service'}
        checked={phone}
        onChange={setChecked}
      />

      Do you get Fuel Assistance?
      <Form.Field style={{display: 'inline-block', paddingRight: '1em'}}>
        <Radio
          name={'hasFuelAssistance'}
          label={'Yes'} value={'Yes'}
          checked={fuelAssist}
          onChange={toBool}
        />
      </Form.Field>
      <Form.Field style={{display: 'inline-block', paddingRight: '1em'}}>
        <Radio
          name={'hasFuelAssistance'}
          label={'No'} value={'No'}
          checked={!fuelAssist}
          onChange={toBool}
        />
      </Form.Field>
    </wrapper>

  );
};  // End Utilities(<>)

const ShelterDetails = function ({ current, type, time, setClientProperty }) {

  let shelter = current.shelter,
      sharedProps = {
        timeState: current, current: current, type: type, time: time,
        setClientProperty: setClientProperty
      };

  if ( current.hasHousing ) {
    return (
      <wrapper>
        <IntervalColumnHeadings type={ type }/>
        <CashFlowRow {...sharedProps} generic={'rentShare'}> Rent Share </CashFlowRow>
        <CashFlowRow {...sharedProps}
          generic={'contractRent'}
          labeInfo={'The full amount the landlord would charge without a Section 8 voucher'}>
            Contract Rent
        </CashFlowRow>
        <Utilities {...sharedProps}/>
      </wrapper>
    );

  } else if ( shelter === 'homeless' ) {
    return null;

  } else if ( shelter === 'renter' ) {
    return (
      <wrapper>
        <IntervalColumnHeadings type={ type }/>
        <CashFlowRow {...sharedProps} generic={'rent'}> Rent </CashFlowRow>
        <Utilities {...sharedProps}/>
      </wrapper>
    );

  } else if ( shelter === 'homeowner' ) {
    return (
      <wrapper>
        <IntervalColumnHeadings type={ type }/>
        <CashFlowRow {...sharedProps} generic={'mortgage'}> Mortgage </CashFlowRow>
        <CashFlowRow {...sharedProps} generic={'housingInsurance'}> Insurance Costs </CashFlowRow>
        <CashFlowRow {...sharedProps} generic={'propertyTax'}> Property Tax </CashFlowRow>
        <Utilities {...sharedProps}/>
      </wrapper>
    );

  }  // end which expenses
};  // End ShelterDetails(<>)

const ShelterRadio = function ({ currentValue, label, time, setClientProperty }) {

  var value = label.toLowerCase();

  return (
    <Form.Field>
      <Radio
        name={'shelter'}
        label={label}
        value={value}
        checked={currentValue === value}
        onChange={setClientProperty}
      />
    </Form.Field>
  );

};  // End ShelterRadio(<>)

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const Housing = function ({ current, type, time, setClientProperty }) {

  // We're using a bunch of radio buttons. Since `checked` is defined
  // in Radio components, `setClientProperty()` would store it, but we
  // want the value, so get rid of checked.
  /** Makes sure values are propagated to 'current' properties if needed */
  let ensureRouteAndValue = function ( evnt, inputProps ) {
    var obj = { ...inputProps, name: inputProps.name, value: inputProps.value, checked: null };
    setClientProperty( evnt, obj );
  };

  let sharedProps = {
    current: current, type: type, time: time,
    setClientProperty: ensureRouteAndValue
  };

  return (
    <wrapper>

      <FormHeading>Shelter</FormHeading>

      { current.hasHousing
      ? null
      : <wrapper>

        What is your housing situation?
        <ShelterRadio
          currentValue={current.shelter}
          label={'Homeless'}
          time={time}
          setClientProperty={ensureRouteAndValue}
        />
        <ShelterRadio
          currentValue={current.shelter}
          label={'Renter'}
          time={time}
          setClientProperty={ensureRouteAndValue}
        />
        <ShelterRadio
          currentValue={current.shelter}
          label={'Homeowner'}
          time={time}
          setClientProperty={ensureRouteAndValue}
        />

      </wrapper>}

      <ShelterDetails {...sharedProps}/>

    </wrapper>
  );

};  // End Housing()

/** @todo description
*
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
const ExpensesFormContent = function ({ current, time, setClientProperty }) {

  let type        = 'expense',
      household   = current.household,
      sharedProps = { timeState: current, type: type, time: time, setClientProperty: setClientProperty };

  /* @todo Make an age-checking function to
   *     keep household data structure under
   *     control in one place.
   */
  var isOver12 = function ( member ) { return member.m_age > 12; };

  // Won't include head or spouse
  var allDependents = getDependentMembers( household ),
      under13       = getEveryMember( allDependents, isUnder13 ),
      // or under13 = getUnder13Members( allDependents ),
      over12        = getEveryMember( allDependents, isOver12 );

  // 'Elderly' here is using the lowest common denominator - SNAP standards
  var isElderlyOrDisabled = function ( member ) {
    return isDisabled( member ) || member.m_age >= 60;
  };
  var elderlyOrDisabled = getEveryMember( household, isElderlyOrDisabled ),
      elderlyOrDisabledHeadAndSpouse = getEveryMember( elderlyOrDisabled, isHeadOrSpouse );

  /**
  * @todo Does money from any programs count as income?
  * @todo Complete only show questions that are relevant to the client's slected programs
  */
  return (
    <wrapper className='field-aligner two-column'>

      { under13.length > 0
        ? <wrapper>
          <FormHeading subheading = {'A "child" is a person 12 or younger. Don\'t include amounts that are paid for by other benefit programs.\n'}>
            Reasonable Unreimbursed Non-Medical Child(ren) Care</FormHeading>
          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic={'childDirectCare'}> Direct care costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'childBeforeAndAfterSchoolCare'}> Before- and after-school care </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'childTransportation'}> Transportation costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'childOtherCare'}> Other care </CashFlowRow>
          <CashFlowRow {...sharedProps} type={'income'} generic={'earnedBecauseOfChildCare'}> Income made possible by child care expenses </CashFlowRow>
        </wrapper>
        : null
      }

      {/** Wrapper here or else margins get added here, but not other
      * places, making spacing hard to manage */}
      <wrapper>
        <FormHeading>Child Support</FormHeading>
        <IntervalColumnHeadings type={type}/>
        <CashFlowRow {...sharedProps} generic={'childSupportPaidOut'}> Legally obligated child support </CashFlowRow>
      </wrapper>

      {/* Head or spouse can't be a dependent, so they don't count */}
      {/* With future version of form, don't show if there are only elderly,
        but not disabled, members. Also, show if there are people between
        > 12, but <= 18 */}
      { over12.length > 0
        ? <wrapper>
          <FormHeading subheading = {'For the care of people who are older than 12, but are still dependents (those under 18 or disabled). Don\'t include amounts that are paid for by other benefit programs.\n'}>
            Dependent Care of Persons Over 12 Years of Age</FormHeading>
          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic={'adultDirectCare'}> Direct care costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'adultTransportation'}> Transportation costs </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'adultOtherCare'}> Other care </CashFlowRow>
        </wrapper>
        : null
      }

      { elderlyOrDisabled.length > 0
        ? <wrapper>
          <FormHeading>Unreimbursed Disabled/Handicapped/Elderly Assistance</FormHeading>

Unreimbursed expenses to cover care attendants and auxiliary apparatus for any family member who is elderly or is a person with disabilities. Auxiliary apparatus are items such as wheelchairs, ramps, adaptations to vehicles, or special equipment to enable a blind person to read or type, but only if these items are directly related to permitting the disabled person or other family member to work.

Examples of eligible disability assistance expenses:

The payments made on a motorized wheelchair for the 42 year old son of the head of household enable the son to leave the house and go to work each day on his own. Prior to the purchase of the motorized wheelchair, the son was unable to make the commute to work. These payments are an eligible disability assistance expense.

Payments to a care attendant to stay with a disabled 16-year-old child allow the child’s mother to go to work every day. These payments are an eligible disability assistance allowance.

          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic={'disabledAssistance'}> Disabled/Handicapped assistance </CashFlowRow>
          <CashFlowRow {...sharedProps} generic={'earnedBecauseOfAdultCare'}> Income made possible by assistance expenses </CashFlowRow>
        </wrapper>
        : null
      }

        {/* These medical expenses don't count for Section 8 unless
          the disabled person is the head or spouse. From
          http://www.tacinc.org/media/58886/S8MS%20Full%20Book.pdf
          Appendix B, item (D) */}
        { elderlyOrDisabledHeadAndSpouse.length > 0 || (current.hasSnap && elderlyOrDisabled.length > 0)
          ? <wrapper>
          <FormHeading>Unreimbursed Medical Expenses</FormHeading>

Do not repeat anything you already listed in the section above. Examples of allowable medical expenses:

The orthodontist expenses for a child’s braces.

Services of doctors and health care professionals.

Services of health care facilities.

Medical insurance premiums.

Prescription/non-prescription medicines (prescribed by a physician).

Transportation to treatment (cab fare, bus fare, mileage).

Dental expenses, eyeglasses, hearing aids, batteries.

Live-in or periodic medical assistance.

Monthly payment on accumulated medical bills (regular monthly payments on a bill that was previously incurred).

          <IntervalColumnHeadings type={type}/>
          <CashFlowRow {...sharedProps} generic='disabledOrElderlyMedical'> Disabled/Elderly medical expenses </CashFlowRow>
          <CashFlowRow {...sharedProps} generic='membersMedical'> Medical expenses of other members </CashFlowRow>
        </wrapper>
        : null
      }

      <Housing current={current} time={time} type={type} setClientProperty={setClientProperty} />

      <FormHeading>Other</FormHeading>
      <CashFlowRow {...sharedProps} generic={'otherExpenses'}> Other Expenses </CashFlowRow>

    </wrapper>
  );

};  // End ExpensesFormContent()

/**
* @todo SNAP: Does a medical assistant's payments count as a medical expense?
* (Answer: Yes. https://www.mass.gov/service-details/snap-verifications)
* @todo SNAP: Medical expense only matters if household has elder/disabled, but
* are they any medical expenses or only those of the disabled person? "Medical
* Expenses for Disabled or Elderly". Also, do they sometimes need to
* enter medical expenses even if they don't have an elderly or disabled
* household member?
*/

/** @todo description
*
* @function
* @param {object} props
* @property {object} props.__ - explanation
*
* @returns Component
*/
// `props` is a cloned version of the original props. References broken.
const CurrentExpensesStep = function ( props ) {

  /** @todo Maybe getTimeSetter can actually convert to 'route' too? */
  const setTimeProp = getTimeSetter( 'current', props.changeClient );

  return (
    <Form className = 'expense-form flex-item flex-column'>
      <FormPartsContainer
        title     = {'Current Household Expenses'}
        clarifier = {null}
        left      = {{name: 'Previous', func: props.previousStep}}
        right     = {{name: 'Next', func: props.nextStep}}
      >
        <ExpensesFormContent setClientProperty={setTimeProp} current={props.client.current} time={'current'} />
      </FormPartsContainer>
    </Form>
  );

};  // End CurrentExpensesStep()

export { CurrentExpensesStep };
<|endoftext|>


<commit_before>Backbone.ImageableView = Backbone.View.extend({

  submitImage: function (options) {
    var image = options.image,
        param = options.param,
        model = options.model,
        formData = new FormData();

    if (!image) { return; }
    formData.append(param, image);
    $.ajax({
      url: _.result(model, "url"),
      type: "PATCH",
      data: formData,
      processData: false,
      contentType: false,
      success: function () {
        options.success && options.success();
      }
    });
  },

  triggerUpload: function (e) {
    e.preventDefault();
    $("#image-form").trigger("click");
  },

  replaceFormImage: function (e) {
    e.preventDefault();
    var files = e.currentTarget.files,
        reader = new FileReader();
    if (!files || !files.length) { return; }

    reader.onload = function (readerEvent) {
      $("#form-image").attr("src", readerEvent.target.result);
    };
    reader.readAsDataURL(files[0]);
  }

});
<commit_msg>Prevent image forms from prematurely syncing mid-upload<commit_after>Backbone.ImageableView = Backbone.View.extend({

  submitImage: function (options) {
    this.stopListening();

    var view = this,
        image = options.image,
        param = options.param,
        model = options.model,
        formData = new FormData();

    if (!image) { return; }
    formData.append(param, image);
    $.ajax({
      url: _.result(model, "url"),
      type: "PATCH",
      data: formData,
      processData: false,
      contentType: false,
      success: function () {
        options.success && options.success();
      }
    });
  },

  triggerUpload: function (e) {
    e.preventDefault();
    $("#image-form").trigger("click");
  },

  replaceFormImage: function (e) {
    e.preventDefault();
    var files = e.currentTarget.files,
        reader = new FileReader();
    if (!files || !files.length) { return; }

    reader.onload = function (readerEvent) {
      $("#form-image").attr("src", readerEvent.target.result);
    };
    reader.readAsDataURL(files[0]);
  }

});
<|endoftext|>


<commit_before>import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import qs from 'qs';

import { openDownloadModal as openDownloadModalAction } from '../../actions/action-creators';
import config from '../../config';
import { HeaderMessage } from '../../components/header';
import Header from '../../components/header';

import DetailsCard from '../../components/dashboard/details-card';
import NearbyLocations from './nearby-locations';
import LatestMeasurementsCard from '../../components/dashboard/lastest-measurements-card';
import SourcesCard from '../../components/dashboard/sources-card';
import MeasureandsCard from '../../components/dashboard/measurands-card';
import TemporalCoverageCard from '../../components/dashboard/temporal-coverage-card';
import TimeSeriesCard from '../../components/dashboard/time-series-card';
import MobileDataLocationsCard from '../../components/dashboard/mobile-data-locations-card';
import DateSelector from '../../components/date-selector';
import { formatValueByUnit, renderUnit } from '../../utils/format';

import { buildQS } from '../../utils/url';
import { NO_CITY } from '../../utils/constants';

const defaultState = {
  fetched: false,
  fetching: false,
  error: null,
  data: null,
};

function Location({ location, history, match, openDownloadModal }) {
  const { id } = match.params;

  const [dateRange, setDateRange] = useState(
    qs.parse(location.search, { ignoreQueryPrefix: true }).dateRange
  );

  useEffect(() => {
    let query = qs.parse(location.search, {
      ignoreQueryPrefix: true,
    });
    query.dateRange = dateRange;
    history.push(`${location.pathname}?${buildQS(query)}`);
  }, [dateRange]);

  const [{ fetched, fetching, error, data }, setState] = useState(defaultState);

  useEffect(() => {
    const fetchData = id => {
      setState(state => ({ ...state, fetching: true, error: null }));

      fetch(`${config.api}/locations/${encodeURIComponent(id)}`)
        .then(response => {
          if (response.status >= 400) {
            throw new Error('Bad response');
          }
          return response.json();
        })
        .then(
          json => {
            const dat = json.results[0];

            if (!dat) {
              setState(state => ({
                ...state,
                fetched: true,
                fetching: false,
                error: json,
              }));

              throw new Error('Bad response');
            }

            setState(state => ({
              ...state,
              fetched: true,
              fetching: false,
              data: {
                ...dat,
                parameters: dat.parameters
                  .filter(p => p) // removes null values
                  .map(p => ({
                    ...p,
                    average: formatValueByUnit(
                      p.average,
                      p.unit,
                      renderUnit(p.unit)
                    ),
                    lastValue: formatValueByUnit(
                      p.lastValue,
                      p.unit,
                      renderUnit(p.unit)
                    ),
                    unit: renderUnit(p.unit),
                  })),
              },
            }));
          },
          e => {
            console.log('e', e);
            setState(state => ({
              ...state,
              fetched: true,
              fetching: false,
              error: e,
            }));
          }
        );
    };

    fetchData(id);

    return () => {
      setState(defaultState);
    };
  }, [id]);

  if (!fetched && !fetching) {
    return null;
  }

  if (fetching) {
    return (
      <HeaderMessage>

Take a deep breath.

Location data is loading...

      </HeaderMessage>
    );
  }

  if (error || !data) {
    return (
      <HeaderMessage>

Uh oh, something went wrong.

            There was a problem getting the data. If you continue to have
            problems, please let us know.

              Send us an Email

      </HeaderMessage>
    );
  }

  function onDownloadClick() {
    openDownloadModal({
      country: data.country,
      area: data.city || NO_CITY,
      location: data.id,
    });
  }

  //
  // Lifecycle stage of different sources.
  const lifecycle = (data.sources || [])
    .map(s => s.lifecycle_stage)
    .filter(Boolean);

  return (

      <Header
        tagline="Location"
        title={data.name}
        subtitle={`in ${data.city || NO_CITY}, ${data.country}`}
        action={{
          api: `${config.apiDocs}`,
          download: onDownloadClick,
          compare: `/compare/${encodeURIComponent(data.id)}`,
        }}
        sourceType={data.sensorType}
        entity={data.entity}
        isMobile={data.isMobile}
      />

        <DateSelector setDateRange={setDateRange} dateRange={dateRange} />

          <DetailsCard
            measurements={data.measurements}
            lifecycle={lifecycle}
            coords={{
              lat: data.coordinates.latitude,
              lng: data.coordinates.longitude,
            }}
            date={{
              start: data.firstUpdated,
              end: data.lastUpdated,
            }}
          />
          <LatestMeasurementsCard parameters={data.parameters} />
          <SourcesCard sources={data.sources} />
          <TimeSeriesCard
            locationId={data.id}
            parameters={data.parameters}
            xUnit="day"
            dateRange={dateRange}
            titleInfo={
              'The value of a pollutant over time during the specified window. While locations have varying time intervals over which they report, all time series charts show data at the same intervals. For one day or one month of data the hourly average is shown. For the project lifetime the daily averages are shown for the most recent week of data.'
            }
          />
          {data.isMobile && (
            <MobileDataLocationsCard
              locationId={data.id}
              bbox={data.bounds}
              firstUpdated={data.firstUpdated}
              lastUpdated={data.lastUpdated}
            />
          )}
          <TemporalCoverageCard
            parameters={data.parameters}
            spatial="location"
            id={data.id}
            dateRange={dateRange}
            titleInfo={
              'The average number of measurements for each pollutant by hour, day, or month. In some views a window may be turned off if that view is not applicable to the selected time window.'
            }
            isMobile={data.isMobile}
          />
          <MeasureandsCard
            parameters={data.parameters}
            titleInfo={
              'The average of all values and total number of measurements for each pollutant during the chosen time window.'
            }
          />

        <NearbyLocations
          locationId={data.id}
          center={[data.coordinates.longitude, data.coordinates.latitude]}
          city={data.city || NO_CITY}
          country={data.country}
          parameters={data.parameters}
          initialActiveParameter={data.parameters[0]}
        />

  );
}

Location.propTypes = {
  location: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired,

  openDownloadModal: PropTypes.func.isRequired,
};

// /////////////////////////////////////////////////////////////////// //
// Connect functions

function dispatcher(dispatch) {
  return {
    openDownloadModal: (...args) => dispatch(openDownloadModalAction(...args)),
  };
}

export default connect(null, dispatcher)(Location);
<commit_msg>adds conditionals - needs testing<commit_after>import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import qs from 'qs';

import { openDownloadModal as openDownloadModalAction } from '../../actions/action-creators';
import config from '../../config';
import { HeaderMessage } from '../../components/header';
import Header from '../../components/header';

import DetailsCard from '../../components/dashboard/details-card';
import NearbyLocations from './nearby-locations';
import LatestMeasurementsCard from '../../components/dashboard/lastest-measurements-card';
import SourcesCard from '../../components/dashboard/sources-card';
import MeasureandsCard from '../../components/dashboard/measurands-card';
import TemporalCoverageCard from '../../components/dashboard/temporal-coverage-card';
import TimeSeriesCard from '../../components/dashboard/time-series-card';
import MobileDataLocationsCard from '../../components/dashboard/mobile-data-locations-card';
import DateSelector from '../../components/date-selector';
import { formatValueByUnit, renderUnit } from '../../utils/format';

import { buildQS } from '../../utils/url';
import { NO_CITY } from '../../utils/constants';

const defaultState = {
  fetched: false,
  fetching: false,
  error: null,
  data: null,
};

function Location({ location, history, match, openDownloadModal }) {
  const { id } = match.params;

  const [dateRange, setDateRange] = useState(
    qs.parse(location.search, { ignoreQueryPrefix: true }).dateRange
  );

  useEffect(() => {
    let query = qs.parse(location.search, {
      ignoreQueryPrefix: true,
    });
    query.dateRange = dateRange;
    history.push(`${location.pathname}?${buildQS(query)}`);
  }, [dateRange]);

  const [{ fetched, fetching, error, data }, setState] = useState(defaultState);

  useEffect(() => {
    const fetchData = id => {
      setState(state => ({ ...state, fetching: true, error: null }));

      fetch(`${config.api}/locations/${encodeURIComponent(id)}`)
        .then(response => {
          if (response.status >= 400) {
            throw new Error('Bad response');
          }
          return response.json();
        })
        .then(
          json => {
            const dat = json.results[0];

            if (!dat) {
              setState(state => ({
                ...state,
                fetched: true,
                fetching: false,
                error: json,
              }));

              throw new Error('Bad response');
            }

            setState(state => ({
              ...state,
              fetched: true,
              fetching: false,
              data: {
                ...dat,
                parameters: dat.parameters
                  .filter(p => p) // removes null values
                  .map(p => ({
                    ...p,
                    average: formatValueByUnit(
                      p.average,
                      p.unit,
                      renderUnit(p.unit)
                    ),
                    lastValue: formatValueByUnit(
                      p.lastValue,
                      p.unit,
                      renderUnit(p.unit)
                    ),
                    unit: renderUnit(p.unit),
                  })),
              },
            }));
          },
          e => {
            console.log('e', e);
            setState(state => ({
              ...state,
              fetched: true,
              fetching: false,
              error: e,
            }));
          }
        );
    };

    fetchData(id);

    return () => {
      setState(defaultState);
    };
  }, [id]);

  if (!fetched && !fetching) {
    return null;
  }

  if (fetching) {
    return (
      <HeaderMessage>

Take a deep breath.

Location data is loading...

      </HeaderMessage>
    );
  }

  if (error || !data) {
    return (
      <HeaderMessage>

Uh oh, something went wrong.

            There was a problem getting the data. If you continue to have
            problems, please let us know.

              Send us an Email

      </HeaderMessage>
    );
  }

  function onDownloadClick() {
    openDownloadModal({
      country: data.country,
      area: data.city || NO_CITY,
      location: data.id,
    });
  }

  //
  // Lifecycle stage of different sources.
  const lifecycle = (data.sources || [])
    .map(s => s.lifecycle_stage)
    .filter(Boolean);

  return (

      <Header
        tagline="Location"
        title={data.name}
        subtitle={`in ${data.city || NO_CITY}, ${data.country}`}
        action={{
          api: `${config.apiDocs}`,
          download: onDownloadClick,
          compare: `/compare/${encodeURIComponent(data.id)}`,
        }}
        sourceType={data.sensorType}
        entity={data.entity}
        isMobile={data.isMobile}
      />

        <DateSelector setDateRange={setDateRange} dateRange={dateRange} />

          <DetailsCard
            measurements={data.measurements}
            lifecycle={lifecycle}
            coords={{
              lat: data.coordinates.latitude,
              lng: data.coordinates.longitude,
            }}
            date={{
              start: data.firstUpdated,
              end: data.lastUpdated,
            }}
          />
          <LatestMeasurementsCard parameters={data.parameters} />
          <SourcesCard sources={data.sources} />
          {!data.isAnalysis && (
            <TimeSeriesCard
              locationId={data.id}
              parameters={data.parameters}
              xUnit="day"
              dateRange={dateRange}
              titleInfo={
                'The value of a pollutant over time during the specified window. While locations have varying time intervals over which they report, all time series charts show data at the same intervals. For one day or one month of data the hourly average is shown. For the project lifetime the daily averages are shown for the most recent week of data.'
              }
            />
          )}
          {data.isMobile && (
            <MobileDataLocationsCard
              locationId={data.id}
              bbox={data.bounds}
              firstUpdated={data.firstUpdated}
              lastUpdated={data.lastUpdated}
            />
          )}
          {!data.isAnalysis && (
            <TemporalCoverageCard
              parameters={data.parameters}
              spatial="location"
              id={data.id}
              dateRange={dateRange}
              titleInfo={
                'The average number of measurements for each pollutant by hour, day, or month. In some views a window may be turned off if that view is not applicable to the selected time window.'
              }
              isMobile={data.isMobile}
            />
          )}
          <MeasureandsCard
            parameters={data.parameters}
            titleInfo={
              'The average of all values and total number of measurements for each pollutant during the chosen time window.'
            }
          />

        <NearbyLocations
          locationId={data.id}
          center={[data.coordinates.longitude, data.coordinates.latitude]}
          city={data.city || NO_CITY}
          country={data.country}
          parameters={data.parameters}
          initialActiveParameter={data.parameters[0]}
        />

  );
}

Location.propTypes = {
  location: PropTypes.object.isRequired,
  history: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired,

  openDownloadModal: PropTypes.func.isRequired,
};

// /////////////////////////////////////////////////////////////////// //
// Connect functions

function dispatcher(dispatch) {
  return {
    openDownloadModal: (...args) => dispatch(openDownloadModalAction(...args)),
  };
}

export default connect(null, dispatcher)(Location);
<|endoftext|>