<commit_before>/**
 * modules/analytics data store: properties.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { actions as profileActions } from './profiles';
import { isValidAccountID, isValidPropertyID, parsePropertyID, isValidPropertySelection } from '../util';
import { PROPERTY_CREATE } from './constants';

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const FETCH_CREATE_PROPERTY_STARTED = 'FETCH_CREATE_PROPERTY_STARTED';
const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const FETCH_PROPERTIES_PROFILES_STARTED = 'FETCH_PROPERTIES_PROFILES_STARTED';
const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_CREATE_PROPERTY_FAILED = 'RECEIVE_CREATE_PROPERTY_FAILED';
const RECEIVE_MATCHED_PROPERTY = 'RECEIVE_MATCHED_PROPERTY';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const RECEIVE_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_PROPERTIES_PROFILES_FAILED = 'RECEIVE_PROPERTIES_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProperty: {},
	isFetchingPropertiesProfiles: {},
	properties: {},
	matchedProperty: undefined,
};

export const actions = {
	/**
	 * Creates a new Analytics property.
	 *
	 * Creates a new Analytics property for an existing Google Analytics account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} accountID Google Analytics account ID.
	 * @return {Object} Redux-style action.
	 */
	*createProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		try {
			const property = yield actions.fetchCreateProperty( accountID );

			const registry = yield Data.commonActions.getRegistry();
			registry.dispatch( STORE_NAME ).setPropertyID( property.id );
			registry.dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId );

			return actions.receiveCreateProperty( { accountID, property } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreatePropertyFailed( { accountID, error } );
		}
	},

	*fetchCreateProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		yield {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY_STARTED,
		};

		return {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY,
		};
	},

	*fetchPropertiesProfiles( accountID ) {
		invariant( accountID, 'accountID is required.' );

		yield {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES_STARTED,
		};

		return {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.property  Google Analytics property object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProperty( { accountID, property } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( property, 'property is required.' );

		return {
			payload: { accountID, property },
			type: RECEIVE_CREATE_PROPERTY,
		};
	},

	/**
	 * Logs an error with property creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.error     Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreatePropertyFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_CREATE_PROPERTY_FAILED,
		};
	},

	/**
	 * Adds a matchedProperty to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} matchedProperty Property object.
	 * @return {Object} Redux-style action.
	 */
	receiveMatchedProperty( matchedProperty ) {
		invariant( matchedProperty, 'matchedProperty is required.' );

		return {
			payload: { matchedProperty },
			type: RECEIVE_MATCHED_PROPERTY,
		};
	},

	*selectProperty( propertyID, internalPropertyID = '' ) {
		invariant( isValidPropertySelection( propertyID ), 'A valid propertyID selection is required.' );

		const registry = yield Data.commonActions.getRegistry();
		registry.dispatch( STORE_NAME ).setPropertyID( propertyID );

		if ( PROPERTY_CREATE === propertyID ) {
			registry.dispatch( STORE_NAME ).setInternalWebPropertyID( '' );
			registry.dispatch( STORE_NAME ).setProfileID( '' );
			return;
		}
		if ( ! internalPropertyID ) {
			const property = registry.select( STORE_NAME ).getPropertyByID( propertyID ) || {};
			internalPropertyID = property.internalWebPropertyId;
		}
		registry.dispatch( STORE_NAME ).setInternalWebPropertyID( internalPropertyID || '' );

		// Cascading selection.
		registry.dispatch( STORE_NAME ).setProfileForProperty( propertyID );
	},

	/**
	 * Adds properties to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} properties Properties to add.
	 * @return {Object} Redux-style action.
	 */
	receiveProperties( properties ) {
		invariant( Array.isArray( properties ), 'properties must be an array.' );

		return {
			payload: { properties },
			type: RECEIVE_PROPERTIES,
		};
	},

	receivePropertiesProfilesCompleted( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: RECEIVE_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receivePropertiesProfilesFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROPERTY ]: ( { payload: { accountID } } ) => {
		return API.set( 'modules', 'analytics', 'create-property', { accountID } );
	},
	[ FETCH_PROPERTIES_PROFILES ]: ( { payload: { accountID } } ) => {
		return API.get( 'modules', 'analytics', 'properties-profiles', { accountID }, {
			useCache: false,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROPERTY_STARTED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: true,
				},
			};
		}

		case FETCH_PROPERTIES_PROFILES_STARTED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY: {
			const { accountID, property } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
				properties: {
					...state.properties || {},
					[ accountID ]: [
						...( state.properties || {} )[ accountID ] || [],
						property,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_MATCHED_PROPERTY: {
			const { matchedProperty } = payload;

			return {
				...state,
				matchedProperty,
			};
		}

		case RECEIVE_PROPERTIES: {
			const { properties } = payload;

			return {
				...state,
				properties: {
					...state.properties,
					...groupBy( properties, 'accountId' ), // Capitalization rule exception: `accountId` is a property of an API returned value.
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_COMPLETED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProperties( accountID ) {
		if ( ! isValidAccountID( accountID ) ) {
			return undefined;
		}
		try {
			const registry = yield Data.commonActions.getRegistry();

			let properties = registry.select( STORE_NAME ).getProperties( accountID );
			let matchedProperty = registry.select( STORE_NAME ).getMatchedProperty();

			// Only fetch properties if there are none in the store for the given account.
			if ( ! properties ) {
				const { profiles, ...response } = yield actions.fetchPropertiesProfiles( accountID );
				( { properties, matchedProperty } = response );

				yield actions.receiveProperties( properties );
				yield profileActions.receiveProfiles( profiles );

				if ( matchedProperty ) {
					yield actions.receiveMatchedProperty( matchedProperty );
				}
			}

			const propertyID = registry.select( STORE_NAME ).getPropertyID();
			if ( ! propertyID ) {
				const property = matchedProperty || properties[ 0 ] || { id: PROPERTY_CREATE };
				yield actions.selectProperty( property.id, property.internalWebPropertyId );
			}

			return actions.receivePropertiesProfilesCompleted( accountID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receivePropertiesProfilesFailed( { accountID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Gets the property object by the property ID.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} propertyID Property ID.
	 * @return {?Object} Property object, or undefined if not present in store.
	 */
	getPropertyByID( state, propertyID ) {
		if ( ! isValidPropertyID( propertyID ) ) {
			return undefined;
		}
		const { accountID } = parsePropertyID( propertyID );

		return ( state.properties[ accountID ] || [] ).find( ( p ) => p.id === propertyID );
	},
	/**
	 * Gets the matched property, if any.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Object} Matched property if set, otherwise `undefined`.
	 */
	getMatchedProperty( state ) {
		return state.matchedProperty;
	},
	/**
	 * Get all Google Analytics properties this account can access.
	 *
	 * Returns an array of all analytics properties.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to fetch properties for.
	 * @return {?Array.<Object>} An array of Analytics properties; `undefined` if not loaded.
	 */
	getProperties( state, accountID ) {
		const { properties } = state;

		return properties[ accountID ];
	},

	/**
	 * Checks if a property is being created for an account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if creating a property, `false` if not.
	 */
	isDoingCreateProperty( state, accountID ) {
		const { isFetchingCreateProperty } = state;

		return !! isFetchingCreateProperty[ accountID ];
	},

	/**
	 * Checks if properties are being fetched for the given account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if fetching a properties, `false` if not.
	 */
	isDoingGetProperties( state, accountID ) {
		const { isFetchingPropertiesProfiles } = state;

		return !! isFetchingPropertiesProfiles[ accountID ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Update selectProperty to properly handle create case.<commit_after>/**
 * modules/analytics data store: properties.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { groupBy } from 'lodash';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './index';
import { actions as profileActions } from './profiles';
import { isValidAccountID, isValidPropertyID, parsePropertyID, isValidPropertySelection } from '../util';
import { PROPERTY_CREATE } from './constants';

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const FETCH_CREATE_PROPERTY_STARTED = 'FETCH_CREATE_PROPERTY_STARTED';
const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const FETCH_PROPERTIES_PROFILES_STARTED = 'FETCH_PROPERTIES_PROFILES_STARTED';
const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_CREATE_PROPERTY_FAILED = 'RECEIVE_CREATE_PROPERTY_FAILED';
const RECEIVE_MATCHED_PROPERTY = 'RECEIVE_MATCHED_PROPERTY';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const RECEIVE_PROPERTIES_PROFILES_COMPLETED = 'RECEIVE_PROPERTIES_PROFILES_COMPLETED';
const RECEIVE_PROPERTIES_PROFILES_FAILED = 'RECEIVE_PROPERTIES_PROFILES_FAILED';

export const INITIAL_STATE = {
	isFetchingCreateProperty: {},
	isFetchingPropertiesProfiles: {},
	properties: {},
	matchedProperty: undefined,
};

export const actions = {
	/**
	 * Creates a new Analytics property.
	 *
	 * Creates a new Analytics property for an existing Google Analytics account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} accountID Google Analytics account ID.
	 * @return {Object} Redux-style action.
	 */
	*createProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		try {
			const property = yield actions.fetchCreateProperty( accountID );

			const registry = yield Data.commonActions.getRegistry();
			registry.dispatch( STORE_NAME ).setPropertyID( property.id );
			registry.dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId );

			return actions.receiveCreateProperty( { accountID, property } );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receiveCreatePropertyFailed( { accountID, error } );
		}
	},

	*fetchCreateProperty( accountID ) {
		invariant( accountID, 'accountID is required.' );

		yield {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY_STARTED,
		};

		return {
			payload: { accountID },
			type: FETCH_CREATE_PROPERTY,
		};
	},

	*fetchPropertiesProfiles( accountID ) {
		invariant( accountID, 'accountID is required.' );

		yield {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES_STARTED,
		};

		return {
			payload: { accountID },
			type: FETCH_PROPERTIES_PROFILES,
		};
	},

	/**
	 * Adds a property to the data store.
	 *
	 * Adds the newly-created property to the existing properties in
	 * the data store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.property  Google Analytics property object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreateProperty( { accountID, property } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( property, 'property is required.' );

		return {
			payload: { accountID, property },
			type: RECEIVE_CREATE_PROPERTY,
		};
	},

	/**
	 * Logs an error with property creation.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} args           Argument params.
	 * @param {string} args.accountID Google Analytics account ID.
	 * @param {Object} args.error     Error object.
	 * @return {Object} Redux-style action.
	 */
	receiveCreatePropertyFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_CREATE_PROPERTY_FAILED,
		};
	},

	/**
	 * Adds a matchedProperty to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} matchedProperty Property object.
	 * @return {Object} Redux-style action.
	 */
	receiveMatchedProperty( matchedProperty ) {
		invariant( matchedProperty, 'matchedProperty is required.' );

		return {
			payload: { matchedProperty },
			type: RECEIVE_MATCHED_PROPERTY,
		};
	},

	/**
	 * Sets the given property and related fields in the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {string} propertyID Property ID to select.
	 * @param {string} [internalPropertyID] Internal property ID (if available).
	 */
	*selectProperty( propertyID, internalPropertyID = '' ) {
		invariant( isValidPropertySelection( propertyID ), 'A valid propertyID selection is required.' );

		const registry = yield Data.commonActions.getRegistry();
		registry.dispatch( STORE_NAME ).setPropertyID( propertyID );

		if ( PROPERTY_CREATE !== propertyID && ! internalPropertyID ) {
			const property = registry.select( STORE_NAME ).getPropertyByID( propertyID ) || {};
			internalPropertyID = property.internalWebPropertyId;
		}
		registry.dispatch( STORE_NAME ).setInternalWebPropertyID( internalPropertyID || '' );

		// Cascading selection.
		registry.dispatch( STORE_NAME ).setProfileForProperty( propertyID );
	},

	/**
	 * Adds properties to the store.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Array} properties Properties to add.
	 * @return {Object} Redux-style action.
	 */
	receiveProperties( properties ) {
		invariant( Array.isArray( properties ), 'properties must be an array.' );

		return {
			payload: { properties },
			type: RECEIVE_PROPERTIES,
		};
	},

	receivePropertiesProfilesCompleted( accountID ) {
		invariant( accountID, 'accountID is required.' );

		return {
			payload: { accountID },
			type: RECEIVE_PROPERTIES_PROFILES_COMPLETED,
		};
	},

	receivePropertiesProfilesFailed( { accountID, error } ) {
		invariant( accountID, 'accountID is required.' );
		invariant( error, 'error is required.' );

		return {
			payload: { accountID, error },
			type: RECEIVE_PROPERTIES_PROFILES_FAILED,
		};
	},
};

export const controls = {
	[ FETCH_CREATE_PROPERTY ]: ( { payload: { accountID } } ) => {
		return API.set( 'modules', 'analytics', 'create-property', { accountID } );
	},
	[ FETCH_PROPERTIES_PROFILES ]: ( { payload: { accountID } } ) => {
		return API.get( 'modules', 'analytics', 'properties-profiles', { accountID }, {
			useCache: false,
		} );
	},
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case FETCH_CREATE_PROPERTY_STARTED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: true,
				},
			};
		}

		case FETCH_PROPERTIES_PROFILES_STARTED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: true,
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY: {
			const { accountID, property } = payload;

			return {
				...state,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
				properties: {
					...state.properties || {},
					[ accountID ]: [
						...( state.properties || {} )[ accountID ] || [],
						property,
					],
				},
			};
		}

		case RECEIVE_CREATE_PROPERTY_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingCreateProperty: {
					...state.isFetchingCreateProperty,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_MATCHED_PROPERTY: {
			const { matchedProperty } = payload;

			return {
				...state,
				matchedProperty,
			};
		}

		case RECEIVE_PROPERTIES: {
			const { properties } = payload;

			return {
				...state,
				properties: {
					...state.properties,
					...groupBy( properties, 'accountId' ), // Capitalization rule exception: `accountId` is a property of an API returned value.
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_COMPLETED: {
			const { accountID } = payload;

			return {
				...state,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		case RECEIVE_PROPERTIES_PROFILES_FAILED: {
			const { accountID, error } = payload;

			return {
				...state,
				error,
				isFetchingPropertiesProfiles: {
					...state.isFetchingPropertiesProfiles,
					[ accountID ]: false,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getProperties( accountID ) {
		if ( ! isValidAccountID( accountID ) ) {
			return undefined;
		}
		try {
			const registry = yield Data.commonActions.getRegistry();

			let properties = registry.select( STORE_NAME ).getProperties( accountID );
			let matchedProperty = registry.select( STORE_NAME ).getMatchedProperty();

			// Only fetch properties if there are none in the store for the given account.
			if ( ! properties ) {
				const { profiles, ...response } = yield actions.fetchPropertiesProfiles( accountID );
				( { properties, matchedProperty } = response );

				yield actions.receiveProperties( properties );
				yield profileActions.receiveProfiles( profiles );

				if ( matchedProperty ) {
					yield actions.receiveMatchedProperty( matchedProperty );
				}
			}

			const propertyID = registry.select( STORE_NAME ).getPropertyID();
			if ( ! propertyID ) {
				const property = matchedProperty || properties[ 0 ] || { id: PROPERTY_CREATE };
				yield actions.selectProperty( property.id, property.internalWebPropertyId );
			}

			return actions.receivePropertiesProfilesCompleted( accountID );
		} catch ( error ) {
			// TODO: Implement an error handler store or some kind of centralized
			// place for error dispatch...
			return actions.receivePropertiesProfilesFailed( { accountID, error } );
		}
	},
};

export const selectors = {
	/**
	 * Gets the property object by the property ID.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} propertyID Property ID.
	 * @return {?Object} Property object, or undefined if not present in store.
	 */
	getPropertyByID( state, propertyID ) {
		if ( ! isValidPropertyID( propertyID ) ) {
			return undefined;
		}
		const { accountID } = parsePropertyID( propertyID );

		return ( state.properties[ accountID ] || [] ).find( ( p ) => p.id === propertyID );
	},
	/**
	 * Gets the matched property, if any.
	 *
	 * @since n.e.x.t
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {?Object} Matched property if set, otherwise `undefined`.
	 */
	getMatchedProperty( state ) {
		return state.matchedProperty;
	},
	/**
	 * Get all Google Analytics properties this account can access.
	 *
	 * Returns an array of all analytics properties.
	 *
	 * Returns `undefined` if accounts have not yet loaded.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to fetch properties for.
	 * @return {?Array.<Object>} An array of Analytics properties; `undefined` if not loaded.
	 */
	getProperties( state, accountID ) {
		const { properties } = state;

		return properties[ accountID ];
	},

	/**
	 * Checks if a property is being created for an account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if creating a property, `false` if not.
	 */
	isDoingCreateProperty( state, accountID ) {
		const { isFetchingCreateProperty } = state;

		return !! isFetchingCreateProperty[ accountID ];
	},

	/**
	 * Checks if properties are being fetched for the given account.
	 *
	 * @since n.e.x.t
	 *
	 * @param {Object} state     Data store's state.
	 * @param {string} accountID The Analytics Account ID to check for property creation.
	 * @return {boolean} `true` if fetching a properties, `false` if not.
	 */
	isDoingGetProperties( state, accountID ) {
		const { isFetchingPropertiesProfiles } = state;

		return !! isFetchingPropertiesProfiles[ accountID ];
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before>/**
 * `modules/analytics` data store: setup-flow.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import {
	MODULES_ANALYTICS,
	SETUP_FLOW_MODE_LEGACY,
	SETUP_FLOW_MODE_UA,
	SETUP_FLOW_MODE_GA4,
	SETUP_FLOW_MODE_GA4_TRANSITIONAL,
	ACCOUNT_CREATE,
	FORM_SETUP,
} from './constants';
import { MODULES_ANALYTICS_4 } from '../../analytics-4/datastore/constants';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';

const { createRegistrySelector } = Data;

const baseSelectors = {
	/**
	 * Gets the setup flow mode based on different conditions.
	 *
	 * @since 1.37.0
	 *
	 * @return {string} Setup flow mode.
	 */
	getSetupFlowMode: createRegistrySelector( ( select ) => () => {
		// Check to see if the Admin API is working—if it's `false` we should also use
		// the legacy analytics because the API isn't working properly.
		const isAdminAPIWorking = select(
			MODULES_ANALYTICS_4
		).isAdminAPIWorking();
		if ( isAdminAPIWorking === false ) {
			return SETUP_FLOW_MODE_LEGACY;
		}

		// Ensure the Analytics settings have loaded. If we check
		// `select( MODULES_ANALYTICS ).getAccountID();` directly, it
		// could return `undefined` because the settings are loading OR
		// because accountID is not set. Ensuring the settings are loaded
		// means an `undefined` accountID is legitimate.
		// See: https://github.com/google/site-kit-wp/pull/3260#discussion_r623924928
		if ( select( MODULES_ANALYTICS ).getSettings() === undefined ) {
			return undefined;
		}

		const accountID = select( MODULES_ANALYTICS ).getAccountID();

		// If no accountID exists then no account is selected. This means we should
		// use the UA setup flow.
		if ( ! accountID || accountID === ACCOUNT_CREATE ) {
			return SETUP_FLOW_MODE_UA;
		}

		const ga4Properties = select( MODULES_ANALYTICS_4 ).getProperties(
			accountID
		);

		if ( ga4Properties === undefined ) {
			return undefined;
		}

		// If there are no GA4 properties available for this account, don't use
		// GA4 and use the UA version.
		if ( ga4Properties.length === 0 ) {
			return SETUP_FLOW_MODE_UA;
		}

		const uaProperties = select( MODULES_ANALYTICS ).getProperties(
			accountID
		);

		if ( uaProperties === undefined ) {
			return undefined;
		}

		// If no UA properties exist and there are GA4 properties, use GA4-only.
		if ( uaProperties.length === 0 ) {
			return SETUP_FLOW_MODE_GA4;
		}

		// There are UA and GA4 properties, so use the transitional mode.
		return SETUP_FLOW_MODE_GA4_TRANSITIONAL;
	} ),

	/**
	 * Determines whether GA4 controls should be displayed or not.
	 *
	 * @since 1.37.0
	 *
	 * @return {boolean} TRUE if we can use GA4 controls, otherwise FALSE.
	 */
	canUseGA4Controls: createRegistrySelector( ( select ) => () => {
		const uaConnected = select( CORE_MODULES ).isModuleConnected(
			'analytics'
		);
		const ga4Connected = select( CORE_MODULES ).isModuleConnected(
			'analytics-4'
		);
		const enableGA4 = select( CORE_FORMS ).getValue(
			FORM_SETUP,
			'enableGA4'
		);

		return uaConnected === ga4Connected || enableGA4;
	} ),
};

const store = Data.combineStores( {
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<commit_msg>Update canUseGA4Controls to fix test.<commit_after>/**
 * `modules/analytics` data store: setup-flow.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import {
	MODULES_ANALYTICS,
	SETUP_FLOW_MODE_LEGACY,
	SETUP_FLOW_MODE_UA,
	SETUP_FLOW_MODE_GA4,
	SETUP_FLOW_MODE_GA4_TRANSITIONAL,
	ACCOUNT_CREATE,
	FORM_SETUP,
} from './constants';
import { MODULES_ANALYTICS_4 } from '../../analytics-4/datastore/constants';
import { CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';

const { createRegistrySelector } = Data;

const baseSelectors = {
	/**
	 * Gets the setup flow mode based on different conditions.
	 *
	 * @since 1.37.0
	 *
	 * @return {string} Setup flow mode.
	 */
	getSetupFlowMode: createRegistrySelector( ( select ) => () => {
		// Check to see if the Admin API is working—if it's `false` we should also use
		// the legacy analytics because the API isn't working properly.
		const isAdminAPIWorking = select(
			MODULES_ANALYTICS_4
		).isAdminAPIWorking();
		if ( isAdminAPIWorking === false ) {
			return SETUP_FLOW_MODE_LEGACY;
		}

		// Ensure the Analytics settings have loaded. If we check
		// `select( MODULES_ANALYTICS ).getAccountID();` directly, it
		// could return `undefined` because the settings are loading OR
		// because accountID is not set. Ensuring the settings are loaded
		// means an `undefined` accountID is legitimate.
		// See: https://github.com/google/site-kit-wp/pull/3260#discussion_r623924928
		if ( select( MODULES_ANALYTICS ).getSettings() === undefined ) {
			return undefined;
		}

		const accountID = select( MODULES_ANALYTICS ).getAccountID();

		// If no accountID exists then no account is selected. This means we should
		// use the UA setup flow.
		if ( ! accountID || accountID === ACCOUNT_CREATE ) {
			return SETUP_FLOW_MODE_UA;
		}

		const ga4Properties = select( MODULES_ANALYTICS_4 ).getProperties(
			accountID
		);

		if ( ga4Properties === undefined ) {
			return undefined;
		}

		// If there are no GA4 properties available for this account, don't use
		// GA4 and use the UA version.
		if ( ga4Properties.length === 0 ) {
			return SETUP_FLOW_MODE_UA;
		}

		const uaProperties = select( MODULES_ANALYTICS ).getProperties(
			accountID
		);

		if ( uaProperties === undefined ) {
			return undefined;
		}

		// If no UA properties exist and there are GA4 properties, use GA4-only.
		if ( uaProperties.length === 0 ) {
			return SETUP_FLOW_MODE_GA4;
		}

		// There are UA and GA4 properties, so use the transitional mode.
		return SETUP_FLOW_MODE_GA4_TRANSITIONAL;
	} ),

	/**
	 * Determines whether GA4 controls should be displayed or not.
	 *
	 * @since 1.37.0
	 *
	 * @return {boolean} TRUE if we can use GA4 controls, otherwise FALSE.
	 */
	canUseGA4Controls: createRegistrySelector( ( select ) => () => {
		const enableGA4 = select( CORE_FORMS ).getValue(
			FORM_SETUP,
			'enableGA4'
		);

		if ( enableGA4 ) {
			return true;
		}

		const { isModuleConnected } = select( CORE_MODULES );
		const uaConnected = isModuleConnected( 'analytics' );
		const ga4Connected = isModuleConnected( 'analytics-4' );

		return uaConnected === ga4Connected;
	} ),
};

const store = Data.combineStores( {
	selectors: baseSelectors,
} );

export const initialState = store.initialState;
export const actions = store.actions;
export const controls = store.controls;
export const reducer = store.reducer;
export const resolvers = store.resolvers;
export const selectors = store.selectors;

export default store;
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import {
  TouchableOpacity,
  ListView,
  StyleSheet,
  View,
  Text,
  Image,
  TouchableWithoutFeedback,
} from 'react-native'
import Button from 'react-native-button'
import Icon from 'react-native-vector-icons/Ionicons'
import { responsiveFontSize } from 'react-native-responsive-dimensions';

import SceneSelector from '../components/SceneSelector'

export default class extends Component {
  constructor(props) {
    super(props)
    this.onBoxPressed = this.onBoxPressed.bind(this)
    this.sceneSelected = this.sceneSelected.bind(this)
  }

  makeThumb(entry) {
    const imgUrl = this.makeImgUrl(entry)
    return (
      <Image
        style={styles.thumbnail}
        source={{uri: imgUrl}}
        onError={() => { this.onErrorLoadImage(entry) }}
      />
    )
  }

  onErrorLoadImage(entry) {
    console.tron.info('Box#onErrorLoadImage', entry)
    return
  }

  makeImgUrl(entry) {
    const thumbsPath = 'https://d2aed4ktvx51jm.cloudfront.net/items/thumbs'
    const item10Id = `0000000000${entry.eid}`.substr(-10, 10)
    const itemId3 = item10Id.slice(0, 3)
    return `${thumbsPath}/${itemId3}/${item10Id}.jpg`
  }

  onBoxPressed() {
    const { item, work, entry, actions, sceneSelectorHidden } = this.props
    const imgUrl = this.makeImgUrl(this.props.item)
    this.props.navigator.push({
      screen: "Chase.Interlude",
      passProps: { item, work, entry, actions, imgUrl, sceneSelectorHidden },
      navigatorStyle: {
        tabBarHidden: true,
      },
    });
  }

  judgeArchived(entry, work) {
    try {
      return work[entry.eid].archive
    } catch(e) {}
    return false
  }

  sceneSelected() {
    const { actions, item } = this.props
    this.setState({ done: true })
    actions.archive(item.itemId)
  }

  render() {
    const { item, work, entry, sceneSelectorHidden } = this.props
    if (!item) { return null }
    const thumb = this.makeThumb(entry)
    let imageOpcty = this.judgeArchived(entry, work) ? 0.5 : 1
    let archivedBG = this.judgeArchived(entry, work) ? '#aaa' : '#fff'
    return (
      <View style={[styles.box, {backgroundColor: archivedBG}]}>
        <TouchableWithoutFeedback onPress={this.onBoxPressed}>
          <View>
            <View style={[styles.thumbWrap, {opacity: imageOpcty}]}>
              { thumb }
            </View>
            <View style={styles.boxBody}>
              <Text style={styles.itemTitle}>{ entry.title }</Text>
              <Text style={styles.domain}>{ entry.siteName }</Text>
              <Text style={styles.domain}>{ entry.fqdn }</Text>
            </View>
          </View>
        </TouchableWithoutFeedback>
        <SceneSelector
          {...this.props}
          sceneSelected={this.sceneSelected}
          hidden={sceneSelectorHidden}
        />
      </View>
    )
  }
}

let styles = StyleSheet.create({
  box: {
    margin: 10,
    backgroundColor: '#fff',
    borderRadius: 2,
    shadowColor: '#000000',
    shadowOffset: {
      width: 0,
      height: 1
    },
    shadowRadius: 1,
    shadowOpacity: 0.2,
  },
  thumbWrap: {
    flex: 1,
    height: 100,
  },
  thumbnail: {
    flex: 1,
  },
  boxBody: {
    paddingTop: 10,
    paddingBottom: 10,
    paddingLeft: 20,
    paddingRight: 20,
  },
  itemTitle: {
    fontSize: responsiveFontSize(2.5),
    lineHeight: responsiveFontSize(3),
    marginBottom: 3,
  },
  domain: {
    fontSize: responsiveFontSize(1.5),
    color: '#a3aab1',
  },
  selectScene: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingBottom: 5,
  },
  sceneBtn: {
    fontSize: responsiveFontSize(2),
    padding: 10,
    overflow: 'hidden',
  },
})
<commit_msg>:+1: bugfix<commit_after>import React, { Component } from 'react'
import {
  TouchableOpacity,
  ListView,
  StyleSheet,
  View,
  Text,
  Image,
  TouchableWithoutFeedback,
} from 'react-native'
import Button from 'react-native-button'
import Icon from 'react-native-vector-icons/Ionicons'
import { responsiveFontSize } from 'react-native-responsive-dimensions';

import SceneSelector from '../components/SceneSelector'

export default class extends Component {
  constructor(props) {
    super(props)
    this.onBoxPressed = this.onBoxPressed.bind(this)
    this.sceneSelected = this.sceneSelected.bind(this)
  }

  makeThumb(entry) {
    const imgUrl = this.makeImgUrl(entry)
    return (
      <Image
        style={styles.thumbnail}
        source={{uri: imgUrl}}
        onError={() => { this.onErrorLoadImage(entry) }}
      />
    )
  }

  onErrorLoadImage(entry) {
    console.tron.info('Box#onErrorLoadImage', entry)
    return
  }

  makeImgUrl(entry) {
    const thumbsPath = 'https://d2aed4ktvx51jm.cloudfront.net/items/thumbs'
    const item10Id = `0000000000${entry.eid}`.substr(-10, 10)
    const itemId3 = item10Id.slice(0, 3)
    return `${thumbsPath}/${itemId3}/${item10Id}.jpg`
  }

  onBoxPressed() {
    const { item, work, entry, actions, sceneSelectorHidden } = this.props
    const imgUrl = this.makeImgUrl(this.props.entry)
    this.props.navigator.push({
      screen: "Chase.Interlude",
      passProps: { item, work, entry, actions, imgUrl, sceneSelectorHidden },
      navigatorStyle: {
        tabBarHidden: true,
      },
    });
  }

  judgeArchived(entry, work) {
    try {
      return work[entry.eid].archive
    } catch(e) {}
    return false
  }

  sceneSelected() {
    const { actions, item } = this.props
    this.setState({ done: true })
    actions.archive(item.itemId)
  }

  render() {
    const { item, work, entry, sceneSelectorHidden } = this.props
    if (!item) { return null }
    const thumb = this.makeThumb(entry)
    let imageOpcty = this.judgeArchived(entry, work) ? 0.5 : 1
    let archivedBG = this.judgeArchived(entry, work) ? '#aaa' : '#fff'
    return (
      <View style={[styles.box, {backgroundColor: archivedBG}]}>
        <TouchableWithoutFeedback onPress={this.onBoxPressed}>
          <View>
            <View style={[styles.thumbWrap, {opacity: imageOpcty}]}>
              { thumb }
            </View>
            <View style={styles.boxBody}>
              <Text style={styles.itemTitle}>{ entry.title }</Text>
              <Text style={styles.domain}>{ entry.siteName }</Text>
              <Text style={styles.domain}>{ entry.fqdn }</Text>
            </View>
          </View>
        </TouchableWithoutFeedback>
        <SceneSelector
          {...this.props}
          sceneSelected={this.sceneSelected}
          hidden={sceneSelectorHidden}
        />
      </View>
    )
  }
}

let styles = StyleSheet.create({
  box: {
    margin: 10,
    backgroundColor: '#fff',
    borderRadius: 2,
    shadowColor: '#000000',
    shadowOffset: {
      width: 0,
      height: 1
    },
    shadowRadius: 1,
    shadowOpacity: 0.2,
  },
  thumbWrap: {
    flex: 1,
    height: 100,
  },
  thumbnail: {
    flex: 1,
  },
  boxBody: {
    paddingTop: 10,
    paddingBottom: 10,
    paddingLeft: 20,
    paddingRight: 20,
  },
  itemTitle: {
    fontSize: responsiveFontSize(2.5),
    lineHeight: responsiveFontSize(3),
    marginBottom: 3,
  },
  domain: {
    fontSize: responsiveFontSize(1.5),
    color: '#a3aab1',
  },
  selectScene: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    paddingBottom: 5,
  },
  sceneBtn: {
    fontSize: responsiveFontSize(2),
    padding: 10,
    overflow: 'hidden',
  },
})
<|endoftext|>


<commit_before>/*
 @author Zakai Hamilton
 @component CoreHttp
 */

screens.core.http = function CoreHttp(me, packages) {
    const { core, db } = packages;
    if (screens.platform === "server") {
        me.port = process.env.PORT || 4040;
    } else if (screens.platform === "service") {
        me.port = 4050;
    } else {
        me.port = 80;
    }
    if (me.port === 4040) {
        me.localhost = true;
    }
    me.listeners = [];
    me.forwardUrl = null;
    me.init = async function () {
        if (me.platform === "server" || me.platform === "service") {
            me.http = require("http");
            me.https = require("https");
            me.fs = require("fs");
            if (me.platform === "server" && me.port !== 4040) {
                try {
                    let server = await me.createServer(true);
                    me.log("secure server is listening");
                    me.io = require("socket.io")(server);
                }
                catch (err) {
                    me.log_error("cannot create secure server, error: " + err);
                }
            }
            me.log("creating normal server");
            let server = await me.createServer(false);
            if (!me.io) {
                me.io = require("socket.io")(server);
            }
            me.log("normal server is listening");
            process.on("SIGINT", () => {
                console.info("SIGINT signal received.");
                server.close(async function (err) {
                    if (err) {
                        console.error(err);
                        process.exit(1);
                    }
                    for (var name of screens.components) {
                        try {
                            var component = me.browse(name);
                            if ("shutdown" in component) {
                                component.shutdown();
                            }
                        }
                        catch (err) {
                            console.error("Failed to shutdown component: " + name + " err: " + err);
                        }
                    }
                    console.log("Components shutdown");
                    process.exit(0);
                });
            });
        }
    };
    me.createServer = async function (secure) {
        var server = null;
        var port = me.port;
        var requestHandler = function (request, response) {
            if (!secure && me.forwardUrl) {
                me.log("forwardUrl: " + me.forwardUrl + " headers: " + JSON.stringify(request.headers) + " url: " + request.url);
                response.writeHead(301, {
                    Location: me.forwardUrl + request.url
                });
                response.end();
                return;
            }
            var body = [];
            request.on("error", function (err) {
                me.log_error("found http error: " + err);
            }).on("data", function (chunk) {
                body.push(chunk);
            }).on("end", async function () {
                if (request.url.includes("private") && !await db.shared.settings.get("system.private")) {
                    response.writeHead(403);
                    response.end();
                } else {
                    me.handleRequest(request, response, body);
                }
            });
        };
        if (secure) {
            var keys = await core.private.keys("https");
            if (keys && keys.key && keys.cert && keys.ca) {
                return new Promise((resolve, reject) => {
                    try {
                        var options = {
                            ca: me.fs.readFileSync(keys.ca),
                            key: me.fs.readFileSync(keys.key),
                            cert: me.fs.readFileSync(keys.cert)
                        };
                        var https = require("https");
                        server = https.createServer(options, requestHandler);
                        port = 443;
                        server.on("error", function (e) {
                            reject(e);
                        });
                        server.listen(port, function () {
                            me.forwardUrl = keys.redirect;
                            resolve(server);
                        });
                        me.log("Secure server working on port: " + port);
                    } catch (e) {
                        me.log("Failed to create secure server, error: " + e.message);
                        reject(e);
                    }
                });
            }
        } else {
            me.log("using http");
            return new Promise((resolve, reject) => {
                server = me.http.createServer(requestHandler);
                server.on("error", function (e) {
                    reject(e);
                });
                server.listen(port, function (err) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(server);
                        me.log("Normal server working on port: " + port);
                    }
                });
            });
        }
    };
    me.clientIp = function (request) {
        var ip = null;
        if (request) {
            ip = core.json.value(request, {
                "headers.x-forwarded-for": value => value ? value.split(",").pop() : null,
                "connection.remoteAddress": null,
                "connection.socket.remoteAddress": null,
                "socket.remoteAddress": null
            });
        }
        return ip;
    };
    me.handleRequest = async function (request, response, body, secure) {
        if (body) {
            body = Buffer.concat(body).toString();
        }
        var url = request.url;
        var query = "";
        var query_offset = url.lastIndexOf("?");
        if (query_offset !== -1) {
            url = request.url.substring(0, query_offset);
            query = request.url.substring(query_offset + 1);
        }
        var info = {
            secure: secure,
            method: request.method,
            url: decodeURIComponent(url),
            query: core.http.parse_query(query),
            headers: request.headers,
            code: 200,
            "content-type": "application/json",
            body: body,
            response: response,
            custom: false,
            stop: false,
            clientIp: me.clientIp(request),
            responseHeaders: {}
        };
        core.property.object.create(me, info);
        me.log("method: " + info.method + " url: " + info.url + " query: " + JSON.stringify(info.query));
        var messages = ["receive", "compress", "end"];
        core.util.performance("http request url: " + info.url, async () => {
            for (var message of messages) {
                if (info.stop) {
                    break;
                }
                try {
                    await core.property.set(info, message);
                }
                catch (err) {
                    info.stop = true;
                    me.log("error in: " + info.url + " message: " + message + " err: " + (err.message || err) + " stack: " + err.stack);
                    response.writeHead(403);
                    response.end();
                }
            }
        });
    };
    me.parse_query = function (query) {
        var array = {};
        if (query !== "") {
            var a = (query[0] === "?" ? query.substr(1) : query).split("&");
            for (var i = 0; i < a.length; i++) {
                var b = a[i].split("=");
                array[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || "");
            }
        }
        return array;
    };
    me.headers = function () {

    };
    me.send = async function (info) {
        core.property.object.create(me, info);
        if (!info.headers) {
            info.headers = {};
        }
        await core.property.set(info, "headers", null);
        var headers = Object.assign({}, info.headers);
        if (me.platform === "service") {
            return await core.message.send_server(me.id + ".send", info);
        } else if (me.platform === "server") {
            return new Promise((resolve, reject) => {
                var request = {
                    url: info.url,
                    headers: headers,
                    method: info.method.toUpperCase()
                };
                var response = {
                    writeHead: function () { },
                    end: function (body) {
                        if (body) {
                            me.log("returning body of length: " + body.length);
                        }
                        else {
                            me.log("returning no result");
                        }
                        resolve(body);
                    }
                };
                me.log("sending request:" + JSON.stringify(request) + " body: " + info.body + " headers: " + JSON.stringify(info.headers));
                try {
                    me.handleRequest(request, response, info.body);
                }
                catch (err) {
                    reject(err);
                }
            });
        } else {
            var request = new XMLHttpRequest();
            if (info.mimeType && request.overrideMimeType) {
                request.overrideMimeType(info.mimeType);
            }
            me.log("sending request:" + info.url);
            request.open(info.method, info.url, true);
            return new Promise((resolve, reject) => {
                request.onreadystatechange = function () {
                    if (request.readyState === 4) {
                        if (request.status === 200 || request.status === 201) {
                            resolve(request.responseText);
                        }
                        else {
                            reject(request.status);
                        }
                    }
                };
                if (headers) {
                    for (var key in headers) {
                        request.setRequestHeader(key, headers[key]);
                    }
                }
                request.send(info.body);
            });
        }
    };
    me.compress = {
        set: function () {

        }
    };
    me.check = {
        set: function () {

        }
    };
    me.parse = {
        set: function () {

        }
    };
    me.receive = {
        set: function () {

        }
    };
    me.end = {
        set: function (info) {
            if (info.custom === false) {
                info.responseHeaders["Content-Type"] = info["content-type"];
                info.responseHeaders["Access-Control-Allow-Origin"] = "*";
                info.responseHeaders["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept";
                info.response.writeHead(info.code, info.responseHeaders);
                info.response.end(info.body);
            }
        }
    };
    me.urlEncode = function (obj) {
        var str = [];
        for (var p in obj)
            if (obj.hasOwnProperty(p)) {
                str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
            }
        return str.join("&");
    };
};
<commit_msg>bug fix<commit_after>/*
 @author Zakai Hamilton
 @component CoreHttp
 */

screens.core.http = function CoreHttp(me, packages) {
    const { core, db } = packages;
    if (screens.platform === "server") {
        me.port = process.env.PORT || 4040;
    } else if (screens.platform === "service") {
        me.port = 4050;
    } else {
        me.port = 80;
    }
    if (me.port === 4040) {
        me.localhost = true;
    }
    me.listeners = [];
    me.forwardUrl = null;
    me.init = async function () {
        if (me.platform === "server" || me.platform === "service") {
            me.http = require("http");
            me.https = require("https");
            me.fs = require("fs");
            if (me.platform === "server" && me.port !== 4040) {
                try {
                    let server = await me.createServer(true);
                    me.log("secure server is listening");
                    me.io = require("socket.io")(server);
                }
                catch (err) {
                    me.log_error("cannot create secure server, error: " + err);
                }
            }
            me.log("creating normal server");
            let server = await me.createServer(false);
            if (!me.io) {
                me.io = require("socket.io")(server);
            }
            me.log("normal server is listening");
            process.on("SIGINT", () => {
                console.info("SIGINT signal received.");
                server.close(async function (err) {
                    if (err) {
                        console.error(err);
                        process.exit(1);
                    }
                    for (var name of screens.components) {
                        try {
                            var component = me.browse(name);
                            if ("shutdown" in component) {
                                component.shutdown();
                            }
                        }
                        catch (err) {
                            console.error("Failed to shutdown component: " + name + " err: " + err);
                        }
                    }
                    console.log("Components shutdown");
                    process.exit(0);
                });
            });
        }
    };
    me.createServer = async function (secure) {
        var server = null;
        var port = me.port;
        var requestHandler = function (request, response) {
            if (!secure && me.forwardUrl) {
                me.log("forwardUrl: " + me.forwardUrl + " headers: " + JSON.stringify(request.headers) + " url: " + request.url);
                response.writeHead(301, {
                    Location: me.forwardUrl + request.url
                });
                response.end();
                return;
            }
            var body = [];
            request.on("error", function (err) {
                me.log_error("found http error: " + err);
            }).on("data", function (chunk) {
                body.push(chunk);
            }).on("end", async function () {
                if (request.url.includes("private") && !await db.shared.settings.get("system.private")) {
                    me.log_error("private files not authorized" + request.url);
                    response.writeHead(403);
                    response.end();
                } else {
                    me.handleRequest(request, response, body);
                }
            });
        };
        if (secure) {
            var keys = await core.private.keys("https");
            if (keys && keys.key && keys.cert && keys.ca) {
                return new Promise((resolve, reject) => {
                    try {
                        var options = {
                            ca: me.fs.readFileSync(keys.ca),
                            key: me.fs.readFileSync(keys.key),
                            cert: me.fs.readFileSync(keys.cert)
                        };
                        var https = require("https");
                        server = https.createServer(options, requestHandler);
                        port = 443;
                        server.on("error", function (e) {
                            reject(e);
                        });
                        server.listen(port, function () {
                            me.forwardUrl = keys.redirect;
                            resolve(server);
                        });
                        me.log("Secure server working on port: " + port);
                    } catch (e) {
                        me.log("Failed to create secure server, error: " + e.message);
                        reject(e);
                    }
                });
            }
        } else {
            me.log("using http");
            return new Promise((resolve, reject) => {
                server = me.http.createServer(requestHandler);
                server.on("error", function (e) {
                    reject(e);
                });
                server.listen(port, function (err) {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve(server);
                        me.log("Normal server working on port: " + port);
                    }
                });
            });
        }
    };
    me.clientIp = function (request) {
        var ip = null;
        if (request) {
            ip = core.json.value(request, {
                "headers.x-forwarded-for": value => value ? value.split(",").pop() : null,
                "connection.remoteAddress": null,
                "connection.socket.remoteAddress": null,
                "socket.remoteAddress": null
            });
        }
        return ip;
    };
    me.handleRequest = async function (request, response, body, secure) {
        if (body) {
            body = Buffer.concat(body).toString();
        }
        var url = request.url;
        var query = "";
        var query_offset = url.lastIndexOf("?");
        if (query_offset !== -1) {
            url = request.url.substring(0, query_offset);
            query = request.url.substring(query_offset + 1);
        }
        var info = {
            secure: secure,
            method: request.method,
            url: decodeURIComponent(url),
            query: core.http.parse_query(query),
            headers: request.headers,
            code: 200,
            "content-type": "application/json",
            body: body,
            response: response,
            custom: false,
            stop: false,
            clientIp: me.clientIp(request),
            responseHeaders: {}
        };
        core.property.object.create(me, info);
        me.log("method: " + info.method + " url: " + info.url + " query: " + JSON.stringify(info.query));
        var messages = ["receive", "compress", "end"];
        core.util.performance("http request url: " + info.url, async () => {
            for (var message of messages) {
                if (info.stop) {
                    break;
                }
                try {
                    await core.property.set(info, message);
                }
                catch (err) {
                    info.stop = true;
                    me.log("error in: " + info.url + " message: " + message + " err: " + (err.message || err) + " stack: " + err.stack);
                    response.writeHead(403);
                    response.end();
                }
            }
        });
    };
    me.parse_query = function (query) {
        var array = {};
        if (query !== "") {
            var a = (query[0] === "?" ? query.substr(1) : query).split("&");
            for (var i = 0; i < a.length; i++) {
                var b = a[i].split("=");
                array[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || "");
            }
        }
        return array;
    };
    me.headers = function () {

    };
    me.send = async function (info) {
        core.property.object.create(me, info);
        if (!info.headers) {
            info.headers = {};
        }
        await core.property.set(info, "headers", null);
        var headers = Object.assign({}, info.headers);
        if (me.platform === "service") {
            return await core.message.send_server(me.id + ".send", info);
        } else if (me.platform === "server") {
            return new Promise((resolve, reject) => {
                var request = {
                    url: info.url,
                    headers: headers,
                    method: info.method.toUpperCase()
                };
                var response = {
                    writeHead: function () { },
                    end: function (body) {
                        if (body) {
                            me.log("returning body of length: " + body.length);
                        }
                        else {
                            me.log("returning no result");
                        }
                        resolve(body);
                    }
                };
                me.log("sending request:" + JSON.stringify(request) + " body: " + info.body + " headers: " + JSON.stringify(info.headers));
                try {
                    me.handleRequest(request, response, info.body);
                }
                catch (err) {
                    reject(err);
                }
            });
        } else {
            var request = new XMLHttpRequest();
            if (info.mimeType && request.overrideMimeType) {
                request.overrideMimeType(info.mimeType);
            }
            me.log("sending request:" + info.url);
            request.open(info.method, info.url, true);
            return new Promise((resolve, reject) => {
                request.onreadystatechange = function () {
                    if (request.readyState === 4) {
                        if (request.status === 200 || request.status === 201) {
                            resolve(request.responseText);
                        }
                        else {
                            reject(request.status);
                        }
                    }
                };
                if (headers) {
                    for (var key in headers) {
                        request.setRequestHeader(key, headers[key]);
                    }
                }
                request.send(info.body);
            });
        }
    };
    me.compress = {
        set: function () {

        }
    };
    me.check = {
        set: function () {

        }
    };
    me.parse = {
        set: function () {

        }
    };
    me.receive = {
        set: function () {

        }
    };
    me.end = {
        set: function (info) {
            if (info.custom === false) {
                info.responseHeaders["Content-Type"] = info["content-type"];
                info.responseHeaders["Access-Control-Allow-Origin"] = "*";
                info.responseHeaders["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Accept";
                info.response.writeHead(info.code, info.responseHeaders);
                info.response.end(info.body);
            }
        }
    };
    me.urlEncode = function (obj) {
        var str = [];
        for (var p in obj)
            if (obj.hasOwnProperty(p)) {
                str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
            }
        return str.join("&");
    };
};
<|endoftext|>


<commit_before>/* eslint-disable max-len */
var fs = require('fs');
var os = require('os');
var path = require('path');
var env = process.env;

var ADBLOCK = is(env.ADBLOCK);
var COLOR = is(env.npm_config_color);
var DISABLE_OPENCOLLECTIVE = is(env.DISABLE_OPENCOLLECTIVE);
var SILENT = ['silent', 'error', 'warn'].indexOf(env.npm_config_loglevel) !== -1;
var MINUTE = 60 * 1000;

// you could add a PR with an env variable for your CI detection
var CI = [
  'BUILD_NUMBER',
  'CI',
  'CONTINUOUS_INTEGRATION',
  'RUN_ID'
].some(function (it) { return is(env[it]); });

var BANNER = '\u001B[96mThank you for using core-js (\u001B[94m https://github.com/zloirock/core-js \u001B[96m) for polyfilling JavaScript standard library!\u001B[0m\n\n' +
             '\u001B[96mThe project needs your help! Please consider supporting of core-js on Open Collective or Patreon: \u001B[0m\n' +
             '\u001B[96m>\u001B[94m https://opencollective.com/core-js \u001B[0m\n' +
             '\u001B[96m>\u001B[94m https://www.patreon.com/zloirock \u001B[0m\n\n' +
             '\u001B[96mAlso, the author of core-js (\u001B[94m https://github.com/zloirock \u001B[96m) is looking for a good job -)\u001B[0m\n';

function is(it) {
  return !!it && it !== '0' && it !== 'false';
}

function isBannerRequired() {
  if (ADBLOCK || CI || DISABLE_OPENCOLLECTIVE || SILENT) return false;
  var file = path.join(os.tmpdir(), 'core-js-banners');
  var banners = [];
  try {
    var DELTA = Date.now() - fs.statSync(file).mtime;
    if (DELTA >= 0 && DELTA < MINUTE * 3) {
      banners = JSON.parse(fs.readFileSync(file, 'utf8'));
      if (banners.indexOf(BANNER) !== -1) return false;
    }
  } catch (error) {
    banners = [];
  }
  try {
    banners.push(BANNER);
    fs.writeFileSync(file, JSON.stringify(banners), 'utf8');
  } catch (error) { /* empty */ }
  return true;
}

function showBanner() {
  // eslint-disable-next-line no-console,no-control-regex
  console.log(COLOR ? BANNER : BANNER.replace(/\u001B\[\d+m/g, ''));
}

if (isBannerRequired()) showBanner();
<commit_msg>chore: Remove ads if OPEN_SOURCE_CONTRIBUTOR<commit_after>/* eslint-disable max-len */
var fs = require('fs');
var os = require('os');
var path = require('path');
var env = process.env;

var ADBLOCK = is(env.ADBLOCK);
var COLOR = is(env.npm_config_color);
var DISABLE_OPENCOLLECTIVE = is(env.DISABLE_OPENCOLLECTIVE);
var SILENT = ['silent', 'error', 'warn'].indexOf(env.npm_config_loglevel) !== -1;
var MINUTE = 60 * 1000;

// you could add a PR with an env variable for your CI detection
var CI = [
  'BUILD_NUMBER',
  'CI',
  'CONTINUOUS_INTEGRATION',
  'OPEN_SOURCE_CONTRIBUTOR',
  'RUN_ID'
].some(function (it) { return is(env[it]); });

var BANNER = '\u001B[96mThank you for using core-js (\u001B[94m https://github.com/zloirock/core-js \u001B[96m) for polyfilling JavaScript standard library!\u001B[0m\n\n' +
             '\u001B[96mThe project needs your help! Please consider supporting of core-js on Open Collective or Patreon: \u001B[0m\n' +
             '\u001B[96m>\u001B[94m https://opencollective.com/core-js \u001B[0m\n' +
             '\u001B[96m>\u001B[94m https://www.patreon.com/zloirock \u001B[0m\n\n' +
             '\u001B[96mAlso, the author of core-js (\u001B[94m https://github.com/zloirock \u001B[96m) is looking for a good job -)\u001B[0m\n';

function is(it) {
  return !!it && it !== '0' && it !== 'false';
}

function isBannerRequired() {
  if (ADBLOCK || CI || DISABLE_OPENCOLLECTIVE || SILENT) return false;
  var file = path.join(os.tmpdir(), 'core-js-banners');
  var banners = [];
  try {
    var DELTA = Date.now() - fs.statSync(file).mtime;
    if (DELTA >= 0 && DELTA < MINUTE * 3) {
      banners = JSON.parse(fs.readFileSync(file, 'utf8'));
      if (banners.indexOf(BANNER) !== -1) return false;
    }
  } catch (error) {
    banners = [];
  }
  try {
    banners.push(BANNER);
    fs.writeFileSync(file, JSON.stringify(banners), 'utf8');
  } catch (error) { /* empty */ }
  return true;
}

function showBanner() {
  // eslint-disable-next-line no-console,no-control-regex
  console.log(COLOR ? BANNER : BANNER.replace(/\u001B\[\d+m/g, ''));
}

if (isBannerRequired()) showBanner();
<|endoftext|>


<commit_before>import React from 'react'
import { PropTypes } from 'react'
import Text from './Text'
import Select from './Select'
import FormMoveButtons from './FormMoveButtons'
import FormRemoveButton from './FormRemoveButton'
import { removeItem, moveItemDown, moveItemUp } from './FormInteractions'
const { string } = PropTypes

export default class FormDefinitions extends React.Component {
  static defaultProps = {
    className: 'FormDefinitions',
    groupName: 'FormDefinitions__group',
    name: 'FormDefinitions',
  }

  static propTypes = {
    name: string.isRequired,
    className: string,
    groupName: string,
  }

  state = {
    items: [],
    itemData: {},
  }

  render() {
    const { className } = this.props

    const { items } = this.state

    return (
      <fieldset className={`${className} ${this.props.groupName}`}>
        <legend>Definitions</legend>

Describe what the word actually means

        <button
          type="button"
          onClick={() => {
            this.handleClickAddItem()
          }}
        >
          Add a Definition
        </button>

        {items}

        {/* <input type="hidden" name="fv:definitions" value={JSON.stringify(this.state.itemData[id])} /> */}
        {this._generateHiddenInput()}

        {/* SCREEN READER DESCRIPTIONS --------------- */}

          You are adding a Definition for the new word. Please specify the Language of the Definition.

          You are adding a Definition for the new word. Please type a Translation of the Definition in the selected
          Language.

          {`If you are adding multiple Definitions, you can change the position of the Definition with the 'Move
Definition up' and 'Move Definition down' buttons`}

      </fieldset>
    )
  }

  handleClickAddItem = () => {
    const items = this.state.items
    const { className } = this.props
    const id = `${className}_${items.length}_${Date.now()}`

    items.push(
      <fieldset key={id} id={id} className={this.props.groupName}>
        <legend>Definition</legend>

          <FormMoveButtons
            id={id}
            idDescribedByItemMove={'describedByDefinitionMove'}
            textBtnMoveItemUp={'Move Definition up'}
            textBtnMoveItemDown={'Move Definition down'}
            handleClickMoveItemUp={this.handleClickMoveItemUp}
            handleClickMoveItemDown={this.handleClickMoveItemDown}
          />
          <FormRemoveButton
            id={id}
            textBtnRemoveItem={'Remove Definition'}
            handleClickRemoveItem={this.handleClickRemoveItem}
          />

        <Select
          ariaDescribedby="describedbyLanguage"
          className="DefinitionLanguage"
          id={`definitions.${items.length}.language`}
          labelText="Language"
          name="" // Note: intentionally generating invalid name so won't be picked up by `new FormData(this.form)`
          handleChange={(value) => {
            this._handleChange({
              id,
              value,
              property: 'language',
            })
          }}
          value="english"
        >
          {/* Note: Using optgroup until React 16 when can use Fragments, eg: <React.Fragment> or <> */}
          <optgroup>
            <option value="english">English</option>
            <option value="french">French</option>
            <option value="mandarin">Mandarin</option>
          </optgroup>
        </Select>
        <Text
          className="Create__DefinitionTranslation"
          ariaDescribedby="describedByTranslation"
          id={`definitions.${items.length}.translation`}
          labelText="Translation"
          name="" // Note: intentionally generating invalid name so won't be picked up by `new FormData(this.form)`
          handleChange={(value) => {
            this._handleChange({
              id,
              value,
              property: 'translation',
            })
          }}
        />
      </fieldset>
    )
    const { itemData } = this.state
    itemData[id] = { language: 'english', translation: '' }
    this.setState({
      items,
      itemData,
    })
  }

  _generateHiddenInput = () => {
    const { items, itemData } = this.state
    const arrayOfObjects = items.map((element) => {
      const id = element.props.id
      return itemData[id]
    })
    return <input type="hidden" name="fv:definitions" value={JSON.stringify(arrayOfObjects)} />
  }

  _handleChange = (arg) => {
    const { id, value, property } = arg
    const { itemData: _itemData } = this.state
    _itemData[id][property] = value
    this.setState({
      itemData: _itemData,
    })
  }

  handleClickRemoveItem = (id) => {
    this.setState({
      items: removeItem({ id, items: this.state.items }),
    })
  }

  handleClickMoveItemDown = (id) => {
    const items = moveItemDown({ id, items: this.state.items })
    this.setState({
      items,
    })
  }

  handleClickMoveItemUp = (id) => {
    this.setState({
      items: moveItemUp({ id, items: this.state.items }),
    })
  }
}
<commit_msg>Delete commented out code<commit_after>import React from 'react'
import { PropTypes } from 'react'
import Text from './Text'
import Select from './Select'
import FormMoveButtons from './FormMoveButtons'
import FormRemoveButton from './FormRemoveButton'
import { removeItem, moveItemDown, moveItemUp } from './FormInteractions'
const { string } = PropTypes

export default class FormDefinitions extends React.Component {
  static defaultProps = {
    className: 'FormDefinitions',
    groupName: 'FormDefinitions__group',
    name: 'FormDefinitions',
  }

  static propTypes = {
    name: string.isRequired,
    className: string,
    groupName: string,
  }

  state = {
    items: [],
    itemData: {},
  }

  render() {
    const { className } = this.props

    const { items } = this.state

    return (
      <fieldset className={`${className} ${this.props.groupName}`}>
        <legend>Definitions</legend>

Describe what the word actually means

        <button
          type="button"
          onClick={() => {
            this.handleClickAddItem()
          }}
        >
          Add a Definition
        </button>

        {items}

        {this._generateHiddenInput()}

        {/* SCREEN READER DESCRIPTIONS --------------- */}

          You are adding a Definition for the new word. Please specify the Language of the Definition.

          You are adding a Definition for the new word. Please type a Translation of the Definition in the selected
          Language.

          {`If you are adding multiple Definitions, you can change the position of the Definition with the 'Move
Definition up' and 'Move Definition down' buttons`}

      </fieldset>
    )
  }

  handleClickAddItem = () => {
    const items = this.state.items
    const { className } = this.props
    const id = `${className}_${items.length}_${Date.now()}`

    items.push(
      <fieldset key={id} id={id} className={this.props.groupName}>
        <legend>Definition</legend>

          <FormMoveButtons
            id={id}
            idDescribedByItemMove={'describedByDefinitionMove'}
            textBtnMoveItemUp={'Move Definition up'}
            textBtnMoveItemDown={'Move Definition down'}
            handleClickMoveItemUp={this.handleClickMoveItemUp}
            handleClickMoveItemDown={this.handleClickMoveItemDown}
          />
          <FormRemoveButton
            id={id}
            textBtnRemoveItem={'Remove Definition'}
            handleClickRemoveItem={this.handleClickRemoveItem}
          />

        <Select
          ariaDescribedby="describedbyLanguage"
          className="DefinitionLanguage"
          id={`definitions.${items.length}.language`}
          labelText="Language"
          name="" // Note: intentionally generating invalid name so won't be picked up by `new FormData(this.form)`
          handleChange={(value) => {
            this._handleChange({
              id,
              value,
              property: 'language',
            })
          }}
          value="english"
        >
          {/* Note: Using optgroup until React 16 when can use Fragments, eg: <React.Fragment> or <> */}
          <optgroup>
            <option value="english">English</option>
            <option value="french">French</option>
            <option value="mandarin">Mandarin</option>
          </optgroup>
        </Select>
        <Text
          className="Create__DefinitionTranslation"
          ariaDescribedby="describedByTranslation"
          id={`definitions.${items.length}.translation`}
          labelText="Translation"
          name="" // Note: intentionally generating invalid name so won't be picked up by `new FormData(this.form)`
          handleChange={(value) => {
            this._handleChange({
              id,
              value,
              property: 'translation',
            })
          }}
        />
      </fieldset>
    )
    const { itemData } = this.state
    itemData[id] = { language: 'english', translation: '' }
    this.setState({
      items,
      itemData,
    })
  }

  _generateHiddenInput = () => {
    const { items, itemData } = this.state
    const arrayOfObjects = items.map((element) => {
      const id = element.props.id
      return itemData[id]
    })
    return <input type="hidden" name="fv:definitions" value={JSON.stringify(arrayOfObjects)} />
  }

  _handleChange = (arg) => {
    const { id, value, property } = arg
    const { itemData: _itemData } = this.state
    _itemData[id][property] = value
    this.setState({
      itemData: _itemData,
    })
  }

  handleClickRemoveItem = (id) => {
    this.setState({
      items: removeItem({ id, items: this.state.items }),
    })
  }

  handleClickMoveItemDown = (id) => {
    const items = moveItemDown({ id, items: this.state.items })
    this.setState({
      items,
    })
  }

  handleClickMoveItemUp = (id) => {
    this.setState({
      items: moveItemUp({ id, items: this.state.items }),
    })
  }
}
<|endoftext|>


<commit_before>var builder = require('focus').component.builder;
var React = require('react');
var applicationStore = require('focus').application.builtInStore();

var barMixin = {
  getDefaultProps: function getCartridgeDefaultProps(){
    return {
      appName: "",
      style: {}
    };
  },
  /** @inheriteddoc */
  getInitialState: function getCartridgeInitialState() {
    return this._getStateFromStore();
  },
  /** @inheriteddoc */
  componentWillMount: function cartridgeWillMount() {
    applicationStore.addSummaryComponentChangeListener(this._handleComponentChange);
    applicationStore.addBarContentLeftComponentChangeListener(this._handleComponentChange);
  },
  /** @inheriteddoc */
  componentWillUnMount: function cartridgeWillUnMount(){
    applicationStore.removeSummaryComponentChangeListener(this._handleComponentChange);
    applicationStore.addBarContentLeftComponentChangeListener(this._handleComponentChange);
  },
  _getStateFromStore: function getCartridgeStateFromStore(){
    return {
      summaryComponent: applicationStore.getSummaryComponent() || {component: 'div', props: {}},
      barContentLeftComponent: applicationStore.getBarContentLeftComponent() || {component: 'div', props:{}}
    };
  },
  _handleComponentChange: function _handleComponentChangeBarSummary(){
    this.setState(this._getStateFromStore());
  },
  /** @inheriteddoc */
  render: function renderBar() {
    var className = `bar ${this.props.style.className}`;
    return (

<this.state.barContentLeftComponent.component {...this.state.barContentLeftComponent.props}/>

<this.state.summaryComponent.component {...this.state.summaryComponent.props}/>

    );
  }
};

module.exports = builder(barMixin);
<commit_msg>bar : remove useless attribute<commit_after>var builder = require('focus').component.builder;
var React = require('react');
var applicationStore = require('focus').application.builtInStore();

var barMixin = {
  getDefaultProps: function getCartridgeDefaultProps(){
    return {
      style: {}
    };
  },
  /** @inheriteddoc */
  getInitialState: function getCartridgeInitialState() {
    return this._getStateFromStore();
  },
  /** @inheriteddoc */
  componentWillMount: function cartridgeWillMount() {
    applicationStore.addSummaryComponentChangeListener(this._handleComponentChange);
    applicationStore.addBarContentLeftComponentChangeListener(this._handleComponentChange);
  },
  /** @inheriteddoc */
  componentWillUnMount: function cartridgeWillUnMount(){
    applicationStore.removeSummaryComponentChangeListener(this._handleComponentChange);
    applicationStore.addBarContentLeftComponentChangeListener(this._handleComponentChange);
  },
  _getStateFromStore: function getCartridgeStateFromStore(){
    return {
      summaryComponent: applicationStore.getSummaryComponent() || {component: 'div', props: {}},
      barContentLeftComponent: applicationStore.getBarContentLeftComponent() || {component: 'div', props:{}}
    };
  },
  _handleComponentChange: function _handleComponentChangeBarSummary(){
    this.setState(this._getStateFromStore());
  },
  /** @inheriteddoc */
  render: function renderBar() {
    var className = `bar ${this.props.style.className}`;
    return (

<this.state.barContentLeftComponent.component {...this.state.barContentLeftComponent.props}/>

<this.state.summaryComponent.component {...this.state.summaryComponent.props}/>

    );
  }
};

module.exports = builder(barMixin);
<|endoftext|>


<commit_before>var utils = require('./_nav-utils.js');

casper.test.begin('Login from home page', function(test) {

    utils.setUp();

    casper.start(utils.baseurl + '/', function () {
        test.assertUrlMatch('/login/?next=/', 'Redirects to login page.');
        test.assertExists('form', 'Form present.');
    });

    casper.then(function () {
        casper.fill('form', {
            'username':    'admin',
            'password':    'admin',
        }, true);
        casper.click("button[type='submit']");
    });

    casper.then(function () {
        test.assertUrlMatch('/', 'Logged-in and redirected.');
        utils.saveCookies();
    });

    casper.run(function done() {
        test.done();
    });
});
<commit_msg>Output HTML to test travis's output, build fail remotely but not locally.<commit_after>var utils = require('./_nav-utils.js');

casper.test.begin('Login from home page', function(test) {

    utils.setUp();

    casper.start(utils.baseurl + '/', function () {
        test.assertUrlMatch('/login/?next=/', 'Redirects to login page.');
        test.assertExists('form', 'Form present.');
    });

    casper.then(function() {
        require('utils').dump(this.getPageContent());
    });

    casper.then(function () {
        casper.fill('form', {
            'username':    'admin',
            'password':    'admin',
        }, true);
        casper.click("button[type='submit']");
    });

    casper.then(function () {
        test.assertUrlMatch('/', 'Logged-in and redirected.');
        utils.saveCookies();
    });

    casper.run(function done() {
        test.done();
    });
});
<|endoftext|>


<commit_before>var Troll = require('troll-opt').Troll;
var models = require('./models/');

var buildInfo = (new Troll()).options(function(troll) {
	troll.banner('Outputs the newest source-code timestamp for the given device / subdirectory.');
	troll.opt('device', 'The device ID.', { type: 'string', required: true });
	troll.opt('subdirectory', 'The subdirectory from which the file can be downloaded.', { type: 'string' });
});

models.sequelize.sync().success(function() {
	models.Device.find({ where: { name: buildInfo.device } }).success(function(device) {
		if (device) {
			models.Rom.max('id', {
				where: {
					DeviceId: device.id,
					subdirectory: buildInfo.subdirectory,
				},
				parseFloat: false,
				parseInt: false,
			}).success(function(romId) {
				if (romId) {
					models.Rom.find(romId).success(function(rom) {
						if (rom.sourceCodeTimestamp) {
							console.log(rom.sourceCodeTimestamp.toISOString());
						}
					});
				}
			});
		}
	});
});
<commit_msg>Description++<commit_after>var Troll = require('troll-opt').Troll;
var models = require('./models/');

var buildInfo = (new Troll()).options(function(troll) {
	troll.banner('Outputs the newest source-code as ISO-8601 string for the given device / subdirectory.');
	troll.opt('device', 'The device ID.', { type: 'string', required: true });
	troll.opt('subdirectory', 'The subdirectory from which the file can be downloaded.', { type: 'string' });
});

models.sequelize.sync().success(function() {
	models.Device.find({ where: { name: buildInfo.device } }).success(function(device) {
		if (device) {
			models.Rom.max('id', {
				where: {
					DeviceId: device.id,
					subdirectory: buildInfo.subdirectory,
				},
				parseFloat: false,
				parseInt: false,
			}).success(function(romId) {
				if (romId) {
					models.Rom.find(romId).success(function(rom) {
						if (rom.sourceCodeTimestamp) {
							console.log(rom.sourceCodeTimestamp.toISOString());
						}
					});
				}
			});
		}
	});
});
<|endoftext|>


<commit_before>function switchPage({data}) {
    return {
        page: data.page,
        lastPage: data.lastPage || null
    };
}

function togglePopup({state}) {
    return {
        showPopup: !state.showPopup
    };
}

function openPopup({data}) {
    return {
        showPopup: true,
        page: data.page
    };
}

function back({state}) {
    if (state.lastPage) {
        return {
            page: state.lastPage
        };
    } else {
        return closePopup({state});
    }
}

function closePopup({state}) {
    return {
        showPopup: false,
        page: state.page === 'magiclink' ? '' : state.page
    };
}

async function signout({api}) {
    await api.member.signout();
    return {
        action: 'signout:success'
    };
}

async function signin({data, api}) {
    await api.member.sendMagicLink(data);
    return {
        page: 'magiclink'
    };
}

async function signup({data, api}) {
    const {plan, email, name} = data;
    if (plan.toLowerCase() === 'free') {
        await api.member.sendMagicLink(data);
    } else {
        await api.member.checkoutPlan({plan, email, name});
    }
    return {
        page: 'magiclink'
    };
}

async function updateEmail({data, api}) {
    await api.member.sendMagicLink(data);
    return {
        action: 'updateEmail:success'
    };
}

async function checkoutPlan({data, api}) {
    const {plan} = data;
    await api.member.checkoutPlan({
        plan
    });
}

async function updateSubscription({data, api}) {
    const {plan, subscriptionId, cancelAtPeriodEnd} = data;
    await api.member.updateSubscription({
        planName: plan, subscriptionId, cancelAtPeriodEnd
    });
    const member = await api.member.sessionData();
    return {
        action: 'updateSubscription:success',
        page: 'accountHome',
        member: member
    };
}

async function cancelSubscription({data, api}) {
    const {subscriptionId, cancelAtPeriodEnd} = data;
    await api.member.updateSubscription({
        subscriptionId, cancelAtPeriodEnd
    });
    const member = await api.member.sessionData();
    return {
        action: 'cancelSubscription:success',
        page: 'accountHome',
        member: member
    };
}

async function editBilling({data, updateState, state, api}) {
    await api.member.editBilling();
}

async function updateMember({data, updateState, state, api}) {
    const {name, subscribed} = data;
    const member = await api.member.update({name, subscribed});
    if (!member) {
        return {
            action: 'updateMember:failed'
        };
    } else {
        return {
            action: 'updateMember:success',
            member: member
        };
    }
}

async function updateProfile({data, updateState, state, api}) {
    const {name, subscribed} = data;
    const member = await api.member.update({name, subscribed});
    if (!member) {
        return {
            action: 'updateProfile:failed'
        };
    } else {
        return {
            action: 'updateProfile:success',
            member: member,
            page: 'accountHome'
        };
    }
}

const Actions = {
    togglePopup,
    openPopup,
    closePopup,
    switchPage,
    back,
    signout,
    signin,
    signup,
    updateEmail,
    updateSubscription,
    cancelSubscription,
    updateMember,
    updateProfile,
    editBilling,
    checkoutPlan
};

/** Handle actions in the App, returns updated state */
export default async function ActionHandler({action, data, updateState, state, api}) {
    const handler = Actions[action];
    if (handler) {
        return await handler({data, updateState, state, api}) || {};
    }
    return {};
}<commit_msg>Reset any "last page" state on closing popup<commit_after>function switchPage({data}) {
    return {
        page: data.page,
        lastPage: data.lastPage || null
    };
}

function togglePopup({state}) {
    return {
        showPopup: !state.showPopup
    };
}

function openPopup({data}) {
    return {
        showPopup: true,
        page: data.page
    };
}

function back({state}) {
    if (state.lastPage) {
        return {
            page: state.lastPage
        };
    } else {
        return closePopup({state});
    }
}

function closePopup({state}) {
    return {
        showPopup: false,
        lastPage: null,
        page: state.page === 'magiclink' ? '' : state.page
    };
}

async function signout({api}) {
    await api.member.signout();
    return {
        action: 'signout:success'
    };
}

async function signin({data, api}) {
    await api.member.sendMagicLink(data);
    return {
        page: 'magiclink'
    };
}

async function signup({data, api}) {
    const {plan, email, name} = data;
    if (plan.toLowerCase() === 'free') {
        await api.member.sendMagicLink(data);
    } else {
        await api.member.checkoutPlan({plan, email, name});
    }
    return {
        page: 'magiclink'
    };
}

async function updateEmail({data, api}) {
    await api.member.sendMagicLink(data);
    return {
        action: 'updateEmail:success'
    };
}

async function checkoutPlan({data, api}) {
    const {plan} = data;
    await api.member.checkoutPlan({
        plan
    });
}

async function updateSubscription({data, api}) {
    const {plan, subscriptionId, cancelAtPeriodEnd} = data;
    await api.member.updateSubscription({
        planName: plan, subscriptionId, cancelAtPeriodEnd
    });
    const member = await api.member.sessionData();
    return {
        action: 'updateSubscription:success',
        page: 'accountHome',
        member: member
    };
}

async function cancelSubscription({data, api}) {
    const {subscriptionId, cancelAtPeriodEnd} = data;
    await api.member.updateSubscription({
        subscriptionId, cancelAtPeriodEnd
    });
    const member = await api.member.sessionData();
    return {
        action: 'cancelSubscription:success',
        page: 'accountHome',
        member: member
    };
}

async function editBilling({data, updateState, state, api}) {
    await api.member.editBilling();
}

async function updateMember({data, updateState, state, api}) {
    const {name, subscribed} = data;
    const member = await api.member.update({name, subscribed});
    if (!member) {
        return {
            action: 'updateMember:failed'
        };
    } else {
        return {
            action: 'updateMember:success',
            member: member
        };
    }
}

async function updateProfile({data, updateState, state, api}) {
    const {name, subscribed} = data;
    const member = await api.member.update({name, subscribed});
    if (!member) {
        return {
            action: 'updateProfile:failed'
        };
    } else {
        return {
            action: 'updateProfile:success',
            member: member,
            page: 'accountHome'
        };
    }
}

const Actions = {
    togglePopup,
    openPopup,
    closePopup,
    switchPage,
    back,
    signout,
    signin,
    signup,
    updateEmail,
    updateSubscription,
    cancelSubscription,
    updateMember,
    updateProfile,
    editBilling,
    checkoutPlan
};

/** Handle actions in the App, returns updated state */
export default async function ActionHandler({action, data, updateState, state, api}) {
    const handler = Actions[action];
    if (handler) {
        return await handler({data, updateState, state, api}) || {};
    }
    return {};
}<|endoftext|>