<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 { actions as profileActions } from './profiles';
import { isValidAccountID, isValidPropertyID, parsePropertyID, isValidPropertySelection } from '../util';
import { STORE_NAME, PROPERTY_CREATE } from './constants';
const { createRegistryControl } = Data;

// 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';
const WAIT_FOR_PROPERTIES = 'WAIT_FOR_PROPERTIES';

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 { accountID } = parsePropertyID( propertyID );
			yield actions.waitForProperties( accountID );
			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,
		};
	},

	waitForProperties( accountID ) {
		return {
			payload: { accountID },
			type: WAIT_FOR_PROPERTIES,
		};
	},
};

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,
		} );
	},
	[ WAIT_FOR_PROPERTIES ]: createRegistryControl( ( registry ) => ( { payload: { accountID } } ) => {
		const arePropertiesLoaded = () => registry.select( STORE_NAME ).getProperties( accountID ) !== undefined;

		if ( arePropertiesLoaded() ) {
			return true;
		}

		return new Promise( ( resolve ) => {
			const unsubscribe = registry.subscribe( () => {
				if ( arePropertiesLoaded() ) {
					unsubscribe();
					resolve();
				}
			} );
		} );
	} ),
};

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( ( { id } ) => 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 properties actions to use new convention.<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 { isValidAccountID, isValidPropertyID, parsePropertyID, isValidPropertySelection } from '../util';
import { STORE_NAME, PROPERTY_CREATE } from './constants';
const { createRegistryControl } = Data;

// Actions
const FETCH_CREATE_PROPERTY = 'FETCH_CREATE_PROPERTY';
const START_FETCH_CREATE_PROPERTY = 'START_FETCH_CREATE_PROPERTY';
const FINISH_FETCH_CREATE_PROPERTY = 'FINISH_FETCH_CREATE_PROPERTY';
const CATCH_FETCH_CREATE_PROPERTY = 'CATCH_FETCH_CREATE_PROPERTY';

const FETCH_PROPERTIES_PROFILES = 'FETCH_PROPERTIES_PROFILES';
const START_FETCH_PROPERTIES_PROFILES = 'START_FETCH_PROPERTIES_PROFILES';
const FINISH_FETCH_PROPERTIES_PROFILES = 'FINISH_FETCH_PROPERTIES_PROFILES';
const CATCH_FETCH_PROPERTIES_PROFILES = 'CATCH_FETCH_PROPERTIES_PROFILES';

const RECEIVE_CREATE_PROPERTY = 'RECEIVE_CREATE_PROPERTY';
const RECEIVE_MATCHED_PROPERTY = 'RECEIVE_MATCHED_PROPERTY';
const RECEIVE_PROPERTIES = 'RECEIVE_PROPERTIES';
const WAIT_FOR_PROPERTIES = 'WAIT_FOR_PROPERTIES';

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.' );
		let response, error;

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

		try {
			response = yield {
				payload: { accountID },
				type: FETCH_CREATE_PROPERTY,
			};
			const property = response;
			const { dispatch } = yield Data.commonActions.getRegistry();
			dispatch( STORE_NAME ).setPropertyID( property.id );
			dispatch( STORE_NAME ).setInternalWebPropertyID( property.internalWebPropertyId );
			yield actions.receiveCreateProperty( { accountID, property } );

			yield {
				payload: { accountID },
				type: FINISH_FETCH_CREATE_PROPERTY,
			};
		} catch ( e ) {
			error = e;
			yield {
				payload: {
					accountID,
					error,
				},
				type: CATCH_FETCH_CREATE_PROPERTY,
			};
		}

		return { response, error };
	},

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

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

		try {
			response = yield {
				payload: { accountID },
				type: FETCH_PROPERTIES_PROFILES,
			};
			const { properties, profiles, matchedProperty } = response;
			const { dispatch } = yield Data.commonActions.getRegistry();
			yield actions.receiveProperties( properties );
			dispatch( STORE_NAME ).receiveProfiles( profiles );

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

			yield {
				payload: { accountID },
				type: FINISH_FETCH_PROPERTIES_PROFILES,
			};
		} catch ( e ) {
			error = e;
			yield {
				payload: {
					accountID,
					error,
				},
				type: CATCH_FETCH_PROPERTIES_PROFILES,
			};
		}

		return { response, error };
	},

	/**
	 * 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,
		};
	},

	/**
	 * 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 { accountID } = parsePropertyID( propertyID );
			yield actions.waitForProperties( accountID );
			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,
		};
	},

	waitForProperties( accountID ) {
		return {
			payload: { accountID },
			type: WAIT_FOR_PROPERTIES,
		};
	},
};

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,
		} );
	},
	[ WAIT_FOR_PROPERTIES ]: createRegistryControl( ( registry ) => ( { payload: { accountID } } ) => {
		const arePropertiesLoaded = () => registry.select( STORE_NAME ).getProperties( accountID ) !== undefined;

		if ( arePropertiesLoaded() ) {
			return true;
		}

		return new Promise( ( resolve ) => {
			const unsubscribe = registry.subscribe( () => {
				if ( arePropertiesLoaded() ) {
					unsubscribe();
					resolve();
				}
			} );
		} );
	} ),
};

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

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

		case START_FETCH_PROPERTIES_PROFILES: {
			const { accountID } = payload;

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

		case FINISH_FETCH_CREATE_PROPERTY: {
			const { accountID } = payload;

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

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

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

		case CATCH_FETCH_CREATE_PROPERTY: {
			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 FINISH_FETCH_PROPERTIES_PROFILES: {
			const { accountID } = payload;

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

		case CATCH_FETCH_PROPERTIES_PROFILES: {
			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;
		}

		const registry = yield Data.commonActions.getRegistry();
		let properties = registry.select( STORE_NAME ).getProperties( accountID );

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

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

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( ( { id } ) => 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><commit_msg>preliminary fix for #204<commit_after><|endoftext|>


<commit_before>'use strict';

const BaseEmbed = require('./BaseEmbed.js');

/**
 * Generates fissure embeds
 */
class FissureEmbed extends BaseEmbed {
  /**
   * @param {Genesis} bot - An instance of Genesis
   * @param {Array.<Fissure>} fissures - The fissures to be included in the embed
   * @param {string} platform - platform
   */
  constructor(bot, fissures, platform) {
    super();

    if (fissures.length < 2) {
      this.title = `[${platform.toUpperCase()}] Worldstate - Void Fissures`;
    }
    this.thumbnail = {
      url: 'http://i.imgur.com/EfIRu6v.png',
    };
    if (fissures.length > 1) {
      fissures.sort((a, b) => a.tierNum - b.tierNum);

      this.fields = fissures.map(f => ({
        name: `${f.missionType} ${f.tier}`,
        value: `[${f.eta}] ${f.node} against ${f.enemy}`,
      }));
    } else if (fissures.length === 0) {
      this.fields = {
        name: 'Currently no fissures',
        value: '_ _',
      };
    } else {
      const f = fissures[0];
      this.title = `[${platform.toUpperCase()}] ${f.missionType} ${f.tier}`;
      this.description = `${f.node} against ${f.enemy}`;
      this.footer.text = `${f.eta} remaining | ${new Date().toLocaleString()}`;
      this.thumbnail.url = 'https://i.imgur.com/EfIRu6v.png';
    }

    this.color = 0x4aa1b2;
  }
}

module.exports = FissureEmbed;
<commit_msg>add url to help title stand out<commit_after>'use strict';

const BaseEmbed = require('./BaseEmbed.js');

/**
 * Generates fissure embeds
 */
class FissureEmbed extends BaseEmbed {
  /**
   * @param {Genesis} bot - An instance of Genesis
   * @param {Array.<Fissure>} fissures - The fissures to be included in the embed
   * @param {string} platform - platform
   */
  constructor(bot, fissures, platform) {
    super();

    if (fissures.length < 2) {
      this.title = `[${platform.toUpperCase()}] Worldstate - Void Fissures`;
    }
    this.thumbnail = {
      url: 'http://i.imgur.com/EfIRu6v.png',
    };
    this.url = 'https://ws.warframestat.us/';
    if (fissures.length > 1) {
      fissures.sort((a, b) => a.tierNum - b.tierNum);

      this.fields = fissures.map(f => ({
        name: `${f.missionType} ${f.tier}`,
        value: `[${f.eta}] ${f.node} against ${f.enemy}`,
      }));
    } else if (fissures.length === 0) {
      this.fields = {
        name: 'Currently no fissures',
        value: '_ _',
      };
    } else {
      const f = fissures[0];
      this.title = `[${platform.toUpperCase()}] ${f.missionType} ${f.tier}`;
      this.description = `${f.node} against ${f.enemy}`;
      this.footer.text = `${f.eta} remaining | ${new Date().toLocaleString()}`;
      this.thumbnail.url = 'https://i.imgur.com/EfIRu6v.png';
    }

    this.color = 0x4aa1b2;
  }
}

module.exports = FissureEmbed;
<|endoftext|>


<commit_before>import { createDocumentFragment } from '../../utils/dom';
import { SECTION_EACH, SECTION_IF, SECTION_IF_WITH, SECTION_UNLESS, SECTION_WITH } from '../../config/types';
import { isArray, isObject } from '../../utils/is';
import Fragment from '../Fragment';
import RepeatedFragment from '../RepeatedFragment';
import Mustache from './shared/Mustache';

function isEmpty ( value ) {
	return !value ||
	       ( isArray( value ) && value.length === 0 ) ||
		   ( isObject( value ) && Object.keys( value ).length === 0 );
}

function getType ( value, hasIndexRef ) {
	if ( hasIndexRef || isArray( value ) ) return SECTION_EACH;
	if ( isObject( value ) || typeof value === 'function' ) return SECTION_WITH;
	if ( value === undefined ) return null;
	return SECTION_IF;
}

export default class Section extends Mustache {
	constructor ( options ) {
		super( options );

		this.sectionType = options.template.n || null;
		this.fragment = null;
	}

	bind () {
		super.bind();

		// if we managed to bind, we need to create children
		if ( this.model ) {
			const value = this.model.value; // TODO should use this.model.get()... but weirdness around root models
			let fragment;

			if ( !this.sectionType ) this.sectionType = getType( value, this.template.i );

			if ( isEmpty( value ) && this.sectionType !== SECTION_WITH ) { // TODO again, WITH should not render if empty
				if ( this.sectionType === SECTION_UNLESS ) {
					this.fragment = new Fragment({
						owner: this,
						template: this.template.f
					}).bind();
				}

				// otherwise, create no children
				return;
			}

			if ( this.sectionType === SECTION_UNLESS ) return;

			if ( this.sectionType === SECTION_IF ) {
				fragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind();
			}

			// TODO should only be WITH, and it should behave like IF_WITH
			else if ( this.sectionType === SECTION_WITH || this.sectionType === SECTION_IF_WITH ) {
				fragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( this.model );
			}

			else {
				fragment = new RepeatedFragment({
					owner: this,
					template: this.template.f,
					indexRef: this.template.i
				}).bind( this.model );
			}

			this.fragment = fragment;
		}
		else if (this.sectionType && this.sectionType === SECTION_UNLESS) {
			this.fragment = new Fragment({
				owner: this,
				template: this.template.f
			}).bind();
		}
	}

	detach () {
		return this.fragment ? this.fragment.detach() : createDocumentFragment();
	}

	find ( selector ) {
		if ( this.fragment ) {
			return this.fragment.find( selector );
		}
	}

	findAll ( selector, query ) {
		if ( this.fragment ) {
			this.fragment.findAll( selector, query );
		}
	}

	findComponent ( name ) {
		if ( this.fragment ) {
			return this.fragment.findComponent( name );
		}
	}

	findAllComponents ( name, query ) {
		if ( this.fragment ) {
			this.fragment.findAllComponents( name, query );
		}
	}

	firstNode () {
		return this.fragment && this.fragment.firstNode();
	}

	rebind () {
		super.rebind();

		if ( this.fragment ) {
			this.fragment.rebind( this.sectionType === SECTION_IF ? null : this.model );
		}
	}

	render ( target ) {
		this.rendered = true;
		if ( this.fragment ) this.fragment.render( target );
	}

	shuffle ( newIndices ) {
		if ( this.fragment && this.sectionType === SECTION_EACH ) {
			this.fragment.shuffle( newIndices );
		}
	}

	toString ( escape ) {
		return this.fragment ? this.fragment.toString( escape ) : '';
	}

	unbind () {
		super.unbind();
		if ( this.fragment ) this.fragment.unbind();
	}

	unrender ( shouldDestroy ) {
		if ( this.rendered && this.fragment ) this.fragment.unrender( shouldDestroy );
		this.rendered = false;
	}

	// TODO DRY this out - lot of repeated stuff between this and bind()
	update () {
		if ( !this.dirty ) return;
		if ( !this.model ) return; // TODO can this happen?

		const value = this.model.value; // TODO see above - should use .get()

		if ( this.sectionType === null ) this.sectionType = getType( value, this.template.i );

		let newFragment;

		if ( this.sectionType === SECTION_EACH ) {
			if ( this.fragment ) {
				this.fragment.update();
			} else {
				// TODO can this happen?
				newFragment = new RepeatedFragment({
					owner: this,
					template: this.template.f,
					indexRef: this.template.i
				}).bind( this.model );
			}
		}

		// TODO same comment as before - WITH should be IF_WITH
		else if ( this.sectionType === SECTION_WITH ) {
			if ( this.fragment ) {
				this.fragment.update();
			} else {
				newFragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( this.model );
			}
		}

		else if ( this.sectionType === SECTION_IF_WITH ) {
			if ( this.fragment ) {
				if ( isEmpty( value ) ) {
					if ( this.rendered ) {
						this.fragment.unbind().unrender( true );
					}

					this.fragment = null;
				} else {
					this.fragment.update();
				}
			} else if ( !isEmpty( value ) ) {
				newFragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( this.model );
			}
		}

		else {
			const fragmentShouldExist = this.sectionType === SECTION_UNLESS ? isEmpty( value ) : !!value;

			if ( this.fragment ) {
				if ( fragmentShouldExist ) {
					this.fragment.update();
				} else {
					if ( this.rendered ) {
						this.fragment.unbind().unrender( true );
					}

					this.fragment = null;
				}
			} else if ( fragmentShouldExist ) {
				newFragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( null );
			}
		}

		if ( newFragment ) {
			if ( this.rendered ) {
				const parentNode = this.parentFragment.findParentNode();
				const anchor = this.parentFragment.findNextNode( this );

				if ( anchor ) {
					const docFrag = createDocumentFragment();
					newFragment.render( docFrag );

					// we use anchor.parentNode, not parentNode, because the sibling
					// may be temporarily detached as a result of a shuffle
					anchor.parentNode.insertBefore( docFrag, anchor );
				} else {
					newFragment.render( parentNode );
				}
			}

			this.fragment = newFragment;
		}

		this.dirty = false;
	}
}
<commit_msg>fix computed props on sections<commit_after>import { createDocumentFragment } from '../../utils/dom';
import { SECTION_EACH, SECTION_IF, SECTION_IF_WITH, SECTION_UNLESS, SECTION_WITH } from '../../config/types';
import { isArray, isObject } from '../../utils/is';
import Fragment from '../Fragment';
import RepeatedFragment from '../RepeatedFragment';
import Mustache from './shared/Mustache';

function isEmpty ( value ) {
	return !value ||
	       ( isArray( value ) && value.length === 0 ) ||
		   ( isObject( value ) && Object.keys( value ).length === 0 );
}

function getType ( value, hasIndexRef ) {
	if ( hasIndexRef || isArray( value ) ) return SECTION_EACH;
	if ( isObject( value ) || typeof value === 'function' ) return SECTION_WITH;
	if ( value === undefined ) return null;
	return SECTION_IF;
}

export default class Section extends Mustache {
	constructor ( options ) {
		super( options );

		this.sectionType = options.template.n || null;
		this.fragment = null;
	}

	bind () {
		super.bind();

		// if we managed to bind, we need to create children
		if ( this.model ) {
			const value = this.model.parent ? this.model.get() : this.model.value;
			let fragment;

			if ( !this.sectionType ) this.sectionType = getType( value, this.template.i );

			if ( isEmpty( value ) && this.sectionType !== SECTION_WITH ) { // TODO again, WITH should not render if empty
				if ( this.sectionType === SECTION_UNLESS ) {
					this.fragment = new Fragment({
						owner: this,
						template: this.template.f
					}).bind();
				}

				// otherwise, create no children
				return;
			}

			if ( this.sectionType === SECTION_UNLESS ) return;

			if ( this.sectionType === SECTION_IF ) {
				fragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind();
			}

			// TODO should only be WITH, and it should behave like IF_WITH
			else if ( this.sectionType === SECTION_WITH || this.sectionType === SECTION_IF_WITH ) {
				fragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( this.model );
			}

			else {
				fragment = new RepeatedFragment({
					owner: this,
					template: this.template.f,
					indexRef: this.template.i
				}).bind( this.model );
			}

			this.fragment = fragment;
		}
		else if (this.sectionType && this.sectionType === SECTION_UNLESS) {
			this.fragment = new Fragment({
				owner: this,
				template: this.template.f
			}).bind();
		}
	}

	detach () {
		return this.fragment ? this.fragment.detach() : createDocumentFragment();
	}

	find ( selector ) {
		if ( this.fragment ) {
			return this.fragment.find( selector );
		}
	}

	findAll ( selector, query ) {
		if ( this.fragment ) {
			this.fragment.findAll( selector, query );
		}
	}

	findComponent ( name ) {
		if ( this.fragment ) {
			return this.fragment.findComponent( name );
		}
	}

	findAllComponents ( name, query ) {
		if ( this.fragment ) {
			this.fragment.findAllComponents( name, query );
		}
	}

	firstNode () {
		return this.fragment && this.fragment.firstNode();
	}

	rebind () {
		super.rebind();

		if ( this.fragment ) {
			this.fragment.rebind( this.sectionType === SECTION_IF ? null : this.model );
		}
	}

	render ( target ) {
		this.rendered = true;
		if ( this.fragment ) this.fragment.render( target );
	}

	shuffle ( newIndices ) {
		if ( this.fragment && this.sectionType === SECTION_EACH ) {
			this.fragment.shuffle( newIndices );
		}
	}

	toString ( escape ) {
		return this.fragment ? this.fragment.toString( escape ) : '';
	}

	unbind () {
		super.unbind();
		if ( this.fragment ) this.fragment.unbind();
	}

	unrender ( shouldDestroy ) {
		if ( this.rendered && this.fragment ) this.fragment.unrender( shouldDestroy );
		this.rendered = false;
	}

	// TODO DRY this out - lot of repeated stuff between this and bind()
	update () {
		if ( !this.dirty ) return;
		if ( !this.model ) return; // TODO can this happen?

		const value = this.model.value; // TODO see above - should use .get()

		if ( this.sectionType === null ) this.sectionType = getType( value, this.template.i );

		let newFragment;

		if ( this.sectionType === SECTION_EACH ) {
			if ( this.fragment ) {
				this.fragment.update();
			} else {
				// TODO can this happen?
				newFragment = new RepeatedFragment({
					owner: this,
					template: this.template.f,
					indexRef: this.template.i
				}).bind( this.model );
			}
		}

		// TODO same comment as before - WITH should be IF_WITH
		else if ( this.sectionType === SECTION_WITH ) {
			if ( this.fragment ) {
				this.fragment.update();
			} else {
				newFragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( this.model );
			}
		}

		else if ( this.sectionType === SECTION_IF_WITH ) {
			if ( this.fragment ) {
				if ( isEmpty( value ) ) {
					if ( this.rendered ) {
						this.fragment.unbind().unrender( true );
					}

					this.fragment = null;
				} else {
					this.fragment.update();
				}
			} else if ( !isEmpty( value ) ) {
				newFragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( this.model );
			}
		}

		else {
			const fragmentShouldExist = this.sectionType === SECTION_UNLESS ? isEmpty( value ) : !!value;

			if ( this.fragment ) {
				if ( fragmentShouldExist ) {
					this.fragment.update();
				} else {
					if ( this.rendered ) {
						this.fragment.unbind().unrender( true );
					}

					this.fragment = null;
				}
			} else if ( fragmentShouldExist ) {
				newFragment = new Fragment({
					owner: this,
					template: this.template.f
				}).bind( null );
			}
		}

		if ( newFragment ) {
			if ( this.rendered ) {
				const parentNode = this.parentFragment.findParentNode();
				const anchor = this.parentFragment.findNextNode( this );

				if ( anchor ) {
					const docFrag = createDocumentFragment();
					newFragment.render( docFrag );

					// we use anchor.parentNode, not parentNode, because the sibling
					// may be temporarily detached as a result of a shuffle
					anchor.parentNode.insertBefore( docFrag, anchor );
				} else {
					newFragment.render( parentNode );
				}
			}

			this.fragment = newFragment;
		}

		this.dirty = false;
	}
}
<|endoftext|>


<commit_before>var packager = require('electron-packager');

const options = {
  dir: './src',
  arch: 'x64',
  download: {
    cache: './electron-cache',
    out: './dist'
  },
  overwrite: 'true',
  platform: 'linux',
  tmpdir: './tmpdir',
  version: '1.2.1',
};

packager(options, (err, appPaths) => {
  if(err) {
    console.log(err);
  }
  else {
    console.log(appPaths);
  }
});<commit_msg>Changed source directory for electron-packager<commit_after>var packager = require('electron-packager');

const options = {
  dir: '.',
  arch: 'x64',
  download: {
    cache: './electron-cache',
    out: './dist'
  },
  overwrite: 'true',
  platform: 'linux',
  tmpdir: './tmpdir',
  version: '1.2.1',
};

packager(options, (err, appPaths) => {
  if(err) {
    console.log(err);
  }
  else {
    console.log(appPaths);
  }
});<|endoftext|>


<commit_before>'use strict'
var should = require("should");
var helper = require("../../../helper");
var PurchaseOrderManager = require("../../../../src/managers/purchasing/purchase-order-manager");
var PurchaseRequestManager = require("../../../../src/managers/purchasing/purchase-request-manager");
var purchaseRequestDataUtil = require("../../../data-util/purchasing/purchase-request-data-util");
var purchaseOrderDataUtil = require("../../../data-util/purchasing/purchase-order-data-util");
var purchaseOrderManager;
var purchaseRequestManager;
var purchaseRequest;
var purchaseOrderFromDataUtil;
var purchaseOrderId;
var purchaseOrder;

before('#00. connect db & create instance', function (done) {
    helper.getDb()
        .then(db => {
            purchaseRequestManager = new PurchaseRequestManager(db, {
                username: 'dev'
            });
            purchaseOrderManager = new PurchaseOrderManager(db, {
                username: 'dev'
            });

            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#01. Create Purchase Request', function (done) {
    purchaseRequestDataUtil.getNewTestData()
        .then(data => {
            purchaseRequest = data;

            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#02. should failed when create new purchase-order with unposted purchase-request', function (done) {
    purchaseOrderDataUtil.getNewData(purchaseRequest)
        .then((purchaseOrder) => {
            return purchaseOrderManager.create(purchaseOrder);
        })
        .then(po => {
            done(purchaseRequest, "purchase-request cannot be used to create purchase-order due unposted status");
        })
        .catch(e => {
            e.errors.should.have.property('purchaseRequestId');
            done();
        });
});

it('#03. should success when get new purchase-order data from util with posted purchase-request', function (done) {
    purchaseRequestManager.post([purchaseRequest])
        .then(pr => {
            purchaseRequest = pr[0];
            purchaseOrderDataUtil.getNewData(purchaseRequest)
                .then((data) => {
                    purchaseOrderFromDataUtil = data;
                    done();
                })
                .catch(e => {
                    done(e);
                });

        })
        .catch(e => {
            done(e);
        });
});

it('#04. should success when create new purchase-order with posted purchase-request', function (done) {
    purchaseOrderManager.create(purchaseOrderFromDataUtil)
        .then(id => {
            purchaseOrderId = id;
            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#05. should success when validate purchase-order with posted purchase-request', function (done) {
    purchaseOrderManager.getSingleById(purchaseOrderId)
    .then(data => {
        purchaseOrder = data;
        done();
    })
    .catch(e => {
        done(e);
    });
});<commit_msg>update new test<commit_after>require("should");
var helper = require("../../../helper");
var PurchaseOrderManager = require("../../../../src/managers/purchasing/purchase-order-manager");
var PurchaseRequestManager = require("../../../../src/managers/purchasing/purchase-request-manager");
var purchaseRequestDataUtil = require("../../../data-util/purchasing/purchase-request-data-util");
var purchaseOrderDataUtil = require("../../../data-util/purchasing/purchase-order-data-util");
var purchaseOrderManager;
var purchaseRequestManager;
var purchaseRequest;
var purchaseOrderFromDataUtil;
var purchaseOrderId;
var purchaseOrder;

before('#00. connect db & create instance', function (done) {
    helper.getDb()
        .then(db => {
            purchaseRequestManager = new PurchaseRequestManager(db, {
                username: 'dev'
            });
            purchaseOrderManager = new PurchaseOrderManager(db, {
                username: 'dev'
            });

            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#01. Create Purchase Request', function (done) {
    purchaseRequestDataUtil.getNewTestData()
        .then(data => {
            purchaseRequest = data;

            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#02. should failed when create new purchase-order with unposted purchase-request', function (done) {
    purchaseOrderDataUtil.getNewData(purchaseRequest)
        .then((purchaseOrder) => {
            return purchaseOrderManager.create(purchaseOrder);
        })
        .then(po => {
            done(purchaseRequest, "purchase-request cannot be used to create purchase-order due unposted status");
        })
        .catch(e => {
            e.errors.should.have.property('purchaseRequestId');
            done();
        });
});

it('#03. should success when get new purchase-order data from util with posted purchase-request', function (done) {
    purchaseRequestManager.post([purchaseRequest])
        .then(pr => {
            purchaseRequest = pr[0];
            purchaseOrderDataUtil.getNewData(purchaseRequest)
                .then((data) => {
                    purchaseOrderFromDataUtil = data;
                    done();
                })
                .catch(e => {
                    done(e);
                });

        })
        .catch(e => {
            done(e);
        });
});

it('#04. should success when create new purchase-order with posted purchase-request', function (done) {
    purchaseOrderManager.create(purchaseOrderFromDataUtil)
        .then(id => {
            purchaseOrderId = id;
            done();
        })
        .catch(e => {
            done(e);
        });
});

it('#05. should success when validate purchase-order with posted purchase-request', function (done) {
    purchaseOrderManager.getSingleById(purchaseOrderId)
    .then(data => {
        purchaseOrder = data;
        done();
    })
    .catch(e => {
        done(e);
    });
});<|endoftext|>


<commit_before>/*
 * 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 styled from 'styled-components';
import PropTypes from 'prop-types';
import { rgba } from 'polished';
import { v4 as uuidv4 } from 'uuid';
import { useMemo } from 'react';

/**
 * Internal dependencies
 */
import { Radio as Selected, RadioSelected as UnSelected } from '../../icons';

const RadioButton = styled.label`
  display: block;
  min-height: 30px;
  margin: 10px 0;
`;

const RADIO_SIZE = 24;
const TEXT_OFFSET = 30;

const Label = styled.span`
  display: flex;
  position: relative;
  margin-bottom: 0;
  cursor: pointer;
  svg {
    width: ${RADIO_SIZE}px;
    height: ${RADIO_SIZE}px;
    margin-right: ${TEXT_OFFSET - RADIO_SIZE}px;
  }
`;

const Name = styled.span`
  line-height: 24px;
`;

// Class should contain "mousetrap" to enable keyboard shortcuts on inputs.
const Radio = styled.input.attrs({ className: 'mousetrap' })`
  opacity: 0;
  position: absolute;
  :focus + ${Label} {
    outline: -webkit-focus-ring-color auto 5px;
  }
`;

const Helper = styled.div`
  margin-left: ${TEXT_OFFSET}px;
  color: ${({ theme }) => rgba(theme.colors.fg.v1, 0.54)};
  font-size: 12px;
  line-height: ${({ theme }) => theme.fonts.label.lineHeight};
`;

function RadioGroup({ onChange, value: selectedValue, options }) {
  const radioGroupId = useMemo(() => uuidv4(), []);
  return (

      {options.map(({ value, name, helper }, i) => (
        <RadioButton key={value}>
          <Radio
            onChange={(evt) => onChange(evt.target.value, evt)}
            value={value}
            type="radio"
            checked={value === selectedValue}
            aria-labelledby={`radio-${i}-${radioGroupId}`}
          />

            {value === selectedValue ? <Selected /> : <UnSelected />}
            <Name id={`radio-${i}-${radioGroupId}`}>{name}</Name>

          {helper && <Helper>{helper}</Helper>}
        </RadioButton>
      ))}

  );
}

RadioGroup.propTypes = {
  value: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  options: PropTypes.array.isRequired,
};

export default RadioGroup;
<commit_msg>Use correct icons for radios.<commit_after>/*
 * 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 styled from 'styled-components';
import PropTypes from 'prop-types';
import { rgba } from 'polished';
import { v4 as uuidv4 } from 'uuid';
import { useMemo } from 'react';

/**
 * Internal dependencies
 */
import { Radio as UnSelected, RadioSelected as Selected } from '../../icons';

const RadioButton = styled.label`
  display: block;
  min-height: 30px;
  margin: 10px 0;
`;

const RADIO_SIZE = 24;
const TEXT_OFFSET = 30;

const Label = styled.span`
  display: flex;
  position: relative;
  margin-bottom: 0;
  cursor: pointer;
  svg {
    width: ${RADIO_SIZE}px;
    height: ${RADIO_SIZE}px;
    margin-right: ${TEXT_OFFSET - RADIO_SIZE}px;
  }
`;

const Name = styled.span`
  line-height: 24px;
`;

const Radio = styled.input`
  opacity: 0;
  position: absolute;
  :focus + ${Label} {
    outline: -webkit-focus-ring-color auto 5px;
  }
`;

const Helper = styled.div`
  margin-left: ${TEXT_OFFSET}px;
  color: ${({ theme }) => rgba(theme.colors.fg.v1, 0.54)};
  font-size: 12px;
  line-height: ${({ theme }) => theme.fonts.label.lineHeight};
`;

function RadioGroup({ onChange, value: selectedValue, options }) {
  const radioGroupId = useMemo(() => uuidv4(), []);
  return (

      {options.map(({ value, name, helper }, i) => (
        <RadioButton key={value}>
          <Radio
            onChange={(evt) => onChange(evt.target.value, evt)}
            value={value}
            type="radio"
            checked={value === selectedValue}
            aria-labelledby={`radio-${i}-${radioGroupId}`}
          />

            {value === selectedValue ? <Selected /> : <UnSelected />}
            <Name id={`radio-${i}-${radioGroupId}`}>{name}</Name>

          {helper && <Helper>{helper}</Helper>}
        </RadioButton>
      ))}

  );
}

RadioGroup.propTypes = {
  value: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  options: PropTypes.array.isRequired,
};

export default RadioGroup;
<|endoftext|>


<commit_before>({
    name:"∀",
    depends:["∨"],
    axioms:[
        {Core:[[0],[0,1,0],[]], // generify
         Skin:{TermNames:["∀"]},
         FreeMaps:[[[]]]},
        {Core:[[],[0,[1,0,1],1],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
        {Core:[[],[0,[1,0,[0,1,2]],[0,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
        {Core:[[],[0,[1,0,[1,1,2]],[1,1,[1,0,2]]],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
        {Core:[[],[0,[1,[2,0,1]],[2,0,[1,[2,0,1]]]],[]],
         Skin:{TermNames:["→","¬","∀"]},
         FreeMaps:[[],[],[[]]]},
        {Core:[[],[0,0,[1,1,0]],[[0,1]]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
    ],
    goals:[
        {Core:[[],[0,[1,0,[2,1,2]],[0,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀","↔"]},
         FreeMaps:[[],[[]],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀","↔","∧"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[1,0,1]],[]],
         Skin:{TermNames:["→","∀","∧"]},
         FreeMaps:[[],[[]],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀","∧"]},
         FreeMaps:[[],[[]],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["↔","∀","∧","→"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[1,0,[1,1,2]],[1,1,[1,0,2]]],[]],
         Skin:{TermNames:["↔","∀","→","∧"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[0,0,1],[0,[1,1],[1,0]]],[]],
         Skin:{TermNames:["↔","¬","→","∧"]},
         FreeMaps:[[],[],[],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,[3,2]],[1,0,[3,1]]]],[]],
         Skin:{TermNames:["→","∀","↔","¬"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[0,[1,0,[0,1,1]],2],2],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
    ],
})
<commit_msg>Remove unneeded forall axiom.<commit_after>({
    name:"∀",
    depends:["∨"],
    axioms:[
        {Core:[[0],[0,1,0],[]], // generify
         Skin:{TermNames:["∀"]},
         FreeMaps:[[[]]]},
        {Core:[[],[0,[1,0,1],1],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
        {Core:[[],[0,[1,0,[0,1,2]],[0,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
        {Core:[[],[0,[1,0,[1,1,2]],[1,1,[1,0,2]]],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
        {Core:[[],[0,0,[1,1,0]],[[0,1]]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
    ],
    goals:[
        {Core:[[],[0,[1,0,[2,1,2]],[0,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀","↔"]},
         FreeMaps:[[],[[]],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀","↔","∧"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[1,0,1]],[]],
         Skin:{TermNames:["→","∀","∧"]},
         FreeMaps:[[],[[]],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["→","∀","∧"]},
         FreeMaps:[[],[[]],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,1],[1,0,2]]],[]],
         Skin:{TermNames:["↔","∀","∧","→"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[1,0,[1,1,2]],[1,1,[1,0,2]]],[]],
         Skin:{TermNames:["↔","∀","→","∧"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[0,0,1],[0,[1,1],[1,0]]],[]],
         Skin:{TermNames:["↔","¬","→","∧"]},
         FreeMaps:[[],[],[],[]]},
        {Core:[[],[0,[1,0,[2,1,2]],[2,[1,0,[3,2]],[1,0,[3,1]]]],[]],
         Skin:{TermNames:["→","∀","↔","¬"]},
         FreeMaps:[[],[[]],[],[]]},
        {Core:[[],[0,[0,[1,0,[0,1,1]],2],2],[]],
         Skin:{TermNames:["→","∀"]},
         FreeMaps:[[],[[]]]},
    ],
})
<|endoftext|>


<commit_before>// @flow

import _ from 'lodash';
import React from 'react';

import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
import { getConferenceNameForTitle } from '../../../base/conference';
import { connect, disconnect } from '../../../base/connection';
import { translate } from '../../../base/i18n';
import { connect as reactReduxConnect } from '../../../base/redux';
import { Chat } from '../../../chat';
import { Filmstrip } from '../../../filmstrip';
import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { KnockingParticipantList } from '../../../lobby';
import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
import {
    Toolbox,
    fullScreenChanged,
    setToolboxAlwaysVisible,
    showToolbox
} from '../../../toolbox';
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
import { maybeShowSuboptimalExperienceNotification } from '../../functions';
import {
    AbstractConference,
    abstractMapStateToProps
} from '../AbstractConference';
import type { AbstractProps } from '../AbstractConference';

import InviteMore from './InviteMore';
import Labels from './Labels';
import { default as Notice } from './Notice';
import { default as Subject } from './Subject';

declare var APP: Object;
declare var config: Object;
declare var interfaceConfig: Object;

/**
 * DOM events for when full screen mode has changed. Different browsers need
 * different vendor prefixes.
 *
 * @private
 * @type {Array<string>}
 */
const FULL_SCREEN_EVENTS = [
    'webkitfullscreenchange',
    'mozfullscreenchange',
    'fullscreenchange'
];

/**
 * The CSS class to apply to the root element of the conference so CSS can
 * modify the app layout.
 *
 * @private
 * @type {Object}
 */
const LAYOUT_CLASSNAMES = {
    [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip',
    [LAYOUTS.TILE_VIEW]: 'tile-view',
    [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip'
};

/**
 * The type of the React {@code Component} props of {@link Conference}.
 */
type Props = AbstractProps & {

    /**
     * Whether the local participant is recording the conference.
     */
    _iAmRecorder: boolean,

    /**
     * The CSS class to apply to the root of {@link Conference} to modify the
     * application layout.
     */
    _layoutClassName: string,

    /**
     * Name for this conference room.
     */
    _roomName: string,

    /**
     * If prejoin page is visible or not.
     */
    _showPrejoin: boolean,

    dispatch: Function,
    t: Function
}

/**
 * The conference page of the Web application.
 */
class Conference extends AbstractConference<Props, *> {
    _onFullScreenChange: Function;
    _onShowToolbar: Function;
    _originalOnShowToolbar: Function;

    /**
     * Initializes a new Conference instance.
     *
     * @param {Object} props - The read-only properties with which the new
     * instance is to be initialized.
     */
    constructor(props) {
        super(props);

        // Throttle and bind this component's mousemove handler to prevent it
        // from firing too often.
        this._originalOnShowToolbar = this._onShowToolbar;
        this._onShowToolbar = _.throttle(
            () => this._originalOnShowToolbar(),
            100,
            {
                leading: true,
                trailing: false
            });

        // Bind event handler so it is only bound once for every instance.
        this._onFullScreenChange = this._onFullScreenChange.bind(this);
    }

    /**
     * Start the connection and get the UI ready for the conference.
     *
     * @inheritdoc
     */
    componentDidMount() {
        document.title = `${this.props._roomName} | ${interfaceConfig.APP_NAME}`;
        this._start();
    }

    /**
     * Calls into legacy UI to update the application layout, if necessary.
     *
     * @inheritdoc
     * returns {void}
     */
    componentDidUpdate(prevProps) {
        if (this.props._shouldDisplayTileView
            === prevProps._shouldDisplayTileView) {
            return;
        }

        // TODO: For now VideoLayout is being called as LargeVideo and Filmstrip
        // sizing logic is still handled outside of React. Once all components
        // are in react they should calculate size on their own as much as
        // possible and pass down sizings.
        VideoLayout.refreshLayout();
    }

    /**
     * Disconnect from the conference when component will be
     * unmounted.
     *
     * @inheritdoc
     */
    componentWillUnmount() {
        APP.UI.unbindEvents();

        FULL_SCREEN_EVENTS.forEach(name =>
            document.removeEventListener(name, this._onFullScreenChange));

        APP.conference.isJoined() && this.props.dispatch(disconnect());
    }

    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        const {
            // XXX The character casing of the name filmStripOnly utilized by
            // interfaceConfig is obsolete but legacy support is required.
            filmStripOnly: filmstripOnly
        } = interfaceConfig;
        const {
            _iAmRecorder,
            _layoutClassName,
            _showPrejoin
        } = this.props;
        const hideLabels = filmstripOnly || _iAmRecorder;

        return (

                <Notice />
                <Subject />
                <InviteMore />

                    <LargeVideo />
                    <KnockingParticipantList />
                    <Filmstrip filmstripOnly = { filmstripOnly } />
                    { hideLabels || <Labels /> }

                { filmstripOnly || _showPrejoin || <Toolbox /> }
                { filmstripOnly || <Chat /> }

                { this.renderNotificationsContainer() }

                <CalleeInfoContainer />

                { !filmstripOnly && _showPrejoin && <Prejoin />}

        );
    }

    /**
     * Updates the Redux state when full screen mode has been enabled or
     * disabled.
     *
     * @private
     * @returns {void}
     */
    _onFullScreenChange() {
        this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
    }

    /**
     * Displays the toolbar.
     *
     * @private
     * @returns {void}
     */
    _onShowToolbar() {
        this.props.dispatch(showToolbox());
    }

    /**
     * Until we don't rewrite UI using react components
     * we use UI.start from old app. Also method translates
     * component right after it has been mounted.
     *
     * @inheritdoc
     */
    _start() {
        APP.UI.start();

        APP.UI.registerListeners();
        APP.UI.bindEvents();

        FULL_SCREEN_EVENTS.forEach(name =>
            document.addEventListener(name, this._onFullScreenChange));

        const { dispatch, t } = this.props;

        dispatch(connect());

        maybeShowSuboptimalExperienceNotification(dispatch, t);

        interfaceConfig.filmStripOnly
            && dispatch(setToolboxAlwaysVisible(true));
    }
}

/**
 * Maps (parts of) the Redux state to the associated props for the
 * {@code Conference} component.
 *
 * @param {Object} state - The Redux state.
 * @private
 * @returns {Props}
 */
function _mapStateToProps(state) {
    return {
        ...abstractMapStateToProps(state),
        _iAmRecorder: state['features/base/config'].iAmRecorder,
        _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
        _roomName: getConferenceNameForTitle(state),
        _showPrejoin: isPrejoinPageVisible(state)
    };
}

export default reactReduxConnect(_mapStateToProps)(translate(Conference));
<commit_msg>fix: Show audio/video options on lobby screen<commit_after>// @flow

import _ from 'lodash';
import React from 'react';

import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
import { getConferenceNameForTitle } from '../../../base/conference';
import { connect, disconnect } from '../../../base/connection';
import { translate } from '../../../base/i18n';
import { connect as reactReduxConnect } from '../../../base/redux';
import { Chat } from '../../../chat';
import { Filmstrip } from '../../../filmstrip';
import { CalleeInfoContainer } from '../../../invite';
import { LargeVideo } from '../../../large-video';
import { KnockingParticipantList, LobbyScreen } from '../../../lobby';
import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
import {
    Toolbox,
    fullScreenChanged,
    setToolboxAlwaysVisible,
    showToolbox
} from '../../../toolbox';
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
import { maybeShowSuboptimalExperienceNotification } from '../../functions';
import {
    AbstractConference,
    abstractMapStateToProps
} from '../AbstractConference';
import type { AbstractProps } from '../AbstractConference';

import InviteMore from './InviteMore';
import Labels from './Labels';
import { default as Notice } from './Notice';
import { default as Subject } from './Subject';

declare var APP: Object;
declare var config: Object;
declare var interfaceConfig: Object;

/**
 * DOM events for when full screen mode has changed. Different browsers need
 * different vendor prefixes.
 *
 * @private
 * @type {Array<string>}
 */
const FULL_SCREEN_EVENTS = [
    'webkitfullscreenchange',
    'mozfullscreenchange',
    'fullscreenchange'
];

/**
 * The CSS class to apply to the root element of the conference so CSS can
 * modify the app layout.
 *
 * @private
 * @type {Object}
 */
const LAYOUT_CLASSNAMES = {
    [LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip',
    [LAYOUTS.TILE_VIEW]: 'tile-view',
    [LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip'
};

/**
 * The type of the React {@code Component} props of {@link Conference}.
 */
type Props = AbstractProps & {

    /**
     * Whether the local participant is recording the conference.
     */
    _iAmRecorder: boolean,

    /**
     * Returns true if the 'lobby screen' is visible.
     */
    _isLobbyScreenVisible: boolean,

    /**
     * The CSS class to apply to the root of {@link Conference} to modify the
     * application layout.
     */
    _layoutClassName: string,

    /**
     * Name for this conference room.
     */
    _roomName: string,

    /**
     * If prejoin page is visible or not.
     */
    _showPrejoin: boolean,

    dispatch: Function,
    t: Function
}

/**
 * The conference page of the Web application.
 */
class Conference extends AbstractConference<Props, *> {
    _onFullScreenChange: Function;
    _onShowToolbar: Function;
    _originalOnShowToolbar: Function;

    /**
     * Initializes a new Conference instance.
     *
     * @param {Object} props - The read-only properties with which the new
     * instance is to be initialized.
     */
    constructor(props) {
        super(props);

        // Throttle and bind this component's mousemove handler to prevent it
        // from firing too often.
        this._originalOnShowToolbar = this._onShowToolbar;
        this._onShowToolbar = _.throttle(
            () => this._originalOnShowToolbar(),
            100,
            {
                leading: true,
                trailing: false
            });

        // Bind event handler so it is only bound once for every instance.
        this._onFullScreenChange = this._onFullScreenChange.bind(this);
    }

    /**
     * Start the connection and get the UI ready for the conference.
     *
     * @inheritdoc
     */
    componentDidMount() {
        document.title = `${this.props._roomName} | ${interfaceConfig.APP_NAME}`;
        this._start();
    }

    /**
     * Calls into legacy UI to update the application layout, if necessary.
     *
     * @inheritdoc
     * returns {void}
     */
    componentDidUpdate(prevProps) {
        if (this.props._shouldDisplayTileView
            === prevProps._shouldDisplayTileView) {
            return;
        }

        // TODO: For now VideoLayout is being called as LargeVideo and Filmstrip
        // sizing logic is still handled outside of React. Once all components
        // are in react they should calculate size on their own as much as
        // possible and pass down sizings.
        VideoLayout.refreshLayout();
    }

    /**
     * Disconnect from the conference when component will be
     * unmounted.
     *
     * @inheritdoc
     */
    componentWillUnmount() {
        APP.UI.unbindEvents();

        FULL_SCREEN_EVENTS.forEach(name =>
            document.removeEventListener(name, this._onFullScreenChange));

        APP.conference.isJoined() && this.props.dispatch(disconnect());
    }

    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render() {
        const {
            // XXX The character casing of the name filmStripOnly utilized by
            // interfaceConfig is obsolete but legacy support is required.
            filmStripOnly: filmstripOnly
        } = interfaceConfig;
        const {
            _iAmRecorder,
            _isLobbyScreenVisible,
            _layoutClassName,
            _showPrejoin
        } = this.props;
        const hideLabels = filmstripOnly || _iAmRecorder;

        return (

                <Notice />
                <Subject />
                <InviteMore />

                    <LargeVideo />
                    <KnockingParticipantList />
                    <Filmstrip filmstripOnly = { filmstripOnly } />
                    { hideLabels || <Labels /> }

                { filmstripOnly || _showPrejoin || _isLobbyScreenVisible || <Toolbox /> }
                { filmstripOnly || <Chat /> }

                { this.renderNotificationsContainer() }

                <CalleeInfoContainer />

                { !filmstripOnly && _showPrejoin && <Prejoin />}

        );
    }

    /**
     * Updates the Redux state when full screen mode has been enabled or
     * disabled.
     *
     * @private
     * @returns {void}
     */
    _onFullScreenChange() {
        this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
    }

    /**
     * Displays the toolbar.
     *
     * @private
     * @returns {void}
     */
    _onShowToolbar() {
        this.props.dispatch(showToolbox());
    }

    /**
     * Until we don't rewrite UI using react components
     * we use UI.start from old app. Also method translates
     * component right after it has been mounted.
     *
     * @inheritdoc
     */
    _start() {
        APP.UI.start();

        APP.UI.registerListeners();
        APP.UI.bindEvents();

        FULL_SCREEN_EVENTS.forEach(name =>
            document.addEventListener(name, this._onFullScreenChange));

        const { dispatch, t } = this.props;

        dispatch(connect());

        maybeShowSuboptimalExperienceNotification(dispatch, t);

        interfaceConfig.filmStripOnly
            && dispatch(setToolboxAlwaysVisible(true));
    }
}

/**
 * Maps (parts of) the Redux state to the associated props for the
 * {@code Conference} component.
 *
 * @param {Object} state - The Redux state.
 * @private
 * @returns {Props}
 */
function _mapStateToProps(state) {
    return {
        ...abstractMapStateToProps(state),
        _iAmRecorder: state['features/base/config'].iAmRecorder,
        _isLobbyScreenVisible: state['features/base/dialog']?.component === LobbyScreen,
        _layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
        _roomName: getConferenceNameForTitle(state),
        _showPrejoin: isPrejoinPageVisible(state)
    };
}

export default reactReduxConnect(_mapStateToProps)(translate(Conference));
<|endoftext|>


<commit_before>// LA VUE

var GameView = Backbone.View.extend({

	el: '#app',

	events: {

		'click .answer': "onAnswer",
	},

	initialize : function() {
		$('#homeBox').empty();

		var that = this;
		this.game = new Game();
		this.DuoCollection = new DuoCollection();
		this.DuoCollection.fetch({
		success: function(){that.render()},
  });
},

	pickNewDuo: function () {

		//Génére un numero alétoire entre 0 et le nombre total de duos dans la collection (this.DuoCollection.length)
		var randomNumber = Math.floor(Math.random() * (this.DuoCollection.length - 0));

		// Stocke dans une variable le duo choisi grâce au numero alétoire
		var duo = this.DuoCollection.at(randomNumber);

		return duo;
	},

	onAnswer: function (e) {
		e.preventDefault();

		var button = $(e.currentTarget);
		// récupère la réponse cliquée
		var answer = button.attr('data-answer');
		// récupère l'id du duo concerné
		var duoCid = button.attr('data-cid');
		// récupère le duo dans la collection
		var duo = this.DuoCollection.get(duoCid).toJSON();
		// récupère la bonne réponse
		var rightAnswer = duo.actor.isPresent.toString();
		var score = this.game.get('score');

		// test if right answer
		if (answer == rightAnswer) {
			this.game.set('score',score+1);
		} else {
		}

		// console.log(score);
		// console.log(answer);
		// console.log(rightAnswer);

		// TODO Save score in model (game.score)
		// TODO Save score in localStorage

		// display next question
		this.render();
	},

	getQuestionBoxTemplate: function (duo) {

		var movie = duo.toJSON().movie;
		var actor = duo.toJSON().actor;

		var questionBoxTemplate = '\

\

\

Was '+actor.name+' in '+movie.title+' ?
\

\

\

'+actor.name+'
\

\

\

'+movie.title+'
\

\

\

\
						YES\
						NO\

\

\

\
		';

		return $(questionBoxTemplate);
	},

	render : function () {

		var duo = this.pickNewDuo();
		$questionBoxTemplate = this.getQuestionBoxTemplate(duo);

		$questionBox = this.$('#questionBox');

		$questionBox.empty();
		$questionBox.append($questionBoxTemplate);

	}
});
<commit_msg>Added score display in template<commit_after>// LA VUE

var GameView = Backbone.View.extend({

	el: '#app',

	events: {

		'click .answer': "onAnswer",
	},

	initialize : function() {
		$('#homeBox').empty();

		var that = this;
		this.game = new Game();
		this.DuoCollection = new DuoCollection();
		this.DuoCollection.fetch({
		success: function(){that.render()},
  });
},

	pickNewDuo: function () {

		//Génére un numero alétoire entre 0 et le nombre total de duos dans la collection (this.DuoCollection.length)
		var randomNumber = Math.floor(Math.random() * (this.DuoCollection.length - 0));

		// Stocke dans une variable le duo choisi grâce au numero alétoire
		var duo = this.DuoCollection.at(randomNumber);

		return duo;
	},

	onAnswer: function (e) {
		e.preventDefault();

		var button = $(e.currentTarget);
		// récupère la réponse cliquée
		var answer = button.attr('data-answer');
		// récupère l'id du duo concerné
		var duoCid = button.attr('data-cid');
		// récupère le duo dans la collection
		var duo = this.DuoCollection.get(duoCid).toJSON();
		// récupère la bonne réponse
		var rightAnswer = duo.actor.isPresent.toString();
		var score = this.game.get('score');

		// test if right answer
		if (answer == rightAnswer) {
			this.game.set('score',score+1);
		} else {
		}

		// console.log(score);
		// console.log(answer);
		// console.log(rightAnswer);

		// TODO Save score in model (game.score)
		// TODO Save score in localStorage

		// display next question
		this.render();
	},

	getQuestionBoxTemplate: function (duo) {

		var movie = duo.toJSON().movie;
		var actor = duo.toJSON().actor;
		var score = this.game.toJSON().score;

		var questionBoxTemplate = '\

\

\

Was '+actor.name+' in '+movie.title+' ?
\

 Your score is '+score+'
\

\

\

'+actor.name+'
\

\

\

'+movie.title+'
\

\

\

\
						YES\
						NO\

\

\

\
		';

		return $(questionBoxTemplate);
	},

	render : function () {

		var duo = this.pickNewDuo();
		$questionBoxTemplate = this.getQuestionBoxTemplate(duo);

		$questionBox = this.$('#questionBox');

		$questionBox.empty();
		$questionBox.append($questionBoxTemplate);

	}
});
<|endoftext|>


<commit_before>module.exports = {
  0: 'Canadian birth certificate',
  1: 'Canadian passport',
  2: 'Canadian citizenship card or certificate',
  3: 'Record of Landing',
  4: 'Permanent Resident Card (front and back)',
  5: 'Work permit',
  6: 'Study permit',
  7: 'Visitor permit',
  8: 'Passport with diplomatic foil',
  9: 'Marriage Certificate',
  10: 'Legal Name Change Certificate'
}
/**
 * ID documents

enum Documents {
  CanadianBirthCertificate,
    CanadianPassport,
    CanadianCitizenCard,
    RecordOfLanding,
    PermanentResidentCard,
    WorkPermit,
    StudyPermit,
    VisitorVisa,
    PassportWithDiplomaticFoil
} */
<commit_msg>Changed copy from 'Record of Landing' to 'Confirmation of Permanent Residence'<commit_after>module.exports = {
  0: 'Canadian birth certificate',
  1: 'Canadian passport',
  2: 'Canadian citizenship card or certificate',
  3: 'Confirmation of Permanent Residence',
  4: 'Permanent Resident Card (front and back)',
  5: 'Work permit',
  6: 'Study permit',
  7: 'Visitor permit',
  8: 'Passport with diplomatic foil',
  9: 'Marriage Certificate',
  10: 'Legal Name Change Certificate'
}
/**
 * ID documents

enum Documents {
  CanadianBirthCertificate,
    CanadianPassport,
    CanadianCitizenCard,
    RecordOfLanding,
    PermanentResidentCard,
    WorkPermit,
    StudyPermit,
    VisitorVisa,
    PassportWithDiplomaticFoil
} */
<|endoftext|>


<commit_before>/* @flow */

import React from 'react';

import {
  Animated,
  StyleSheet,
  View,
} from 'react-native';

import invariant from 'fbjs/lib/invariant';

import NavigationScenesReducer from './ScenesReducer';
import TransitionConfigs from './TransitionConfigs';
import addNavigationHelpers from '../addNavigationHelpers';
import NavigationPropTypes from '../PropTypes';

import type {
  NavigationAnimatedValue,
  NavigationLayout,
  NavigationScene,
  NavigationRoute,
  NavigationAction,
  NavigationScreenProp,
  NavigationTransitionProps,
  NavigationTransitionSpec,
} from '../TypeDefinition';

type Props = {
  configureTransition: (
    transitionProps: NavigationTransitionProps,
    prevTransitionProps: ?NavigationTransitionProps,
  ) => NavigationTransitionSpec,
  navigation: NavigationScreenProp<NavigationRoute, NavigationAction>,
  onTransitionEnd: () => void,
  onTransitionStart: () => void,
  render: (
    transitionProps: NavigationTransitionProps,
    prevTransitionProps: ?NavigationTransitionProps
  ) => any,
  style: any,
};

type State = {
  layout: NavigationLayout,
  position: NavigationAnimatedValue,
  progress: NavigationAnimatedValue,
  scenes: Array<NavigationScene>,
};

const { PropTypes } = React;

const DefaultTransitionSpec = TransitionConfigs.DefaultTransitionSpec;

class Transitioner extends React.Component<*, Props, State> {
  _onLayout: (event: any) => void;
  _onTransitionEnd: () => void;
  _prevTransitionProps: ?NavigationTransitionProps;
  _transitionProps: NavigationTransitionProps;
  _isMounted: boolean;

  props: Props;
  state: State;

  static propTypes = {
    configureTransition: PropTypes.func,
    navigation: PropTypes.shape({
      state: NavigationPropTypes.navigationState.isRequired,
    }).isRequired,
    onTransitionEnd: PropTypes.func,
    onTransitionStart: PropTypes.func,
    render: PropTypes.func.isRequired,
  };

  constructor(props: Props, context: any) {
    super(props, context);

    // The initial layout isn't measured. Measured layout will be only available
    // when the component is mounted.
    const layout = {
      height: new Animated.Value(0),
      initHeight: 0,
      initWidth: 0,
      isMeasured: false,
      width: new Animated.Value(0),
    };

    this.state = {
      layout,
      position: new Animated.Value(this.props.navigation.state.index),
      progress: new Animated.Value(1),
      scenes: NavigationScenesReducer([], this.props.navigation.state),
    };

    this._prevTransitionProps = null;
    this._transitionProps = buildTransitionProps(props, this.state);
    this._isMounted = false;
  }

  componentWillMount(): void {
    this._onLayout = this._onLayout.bind(this);
    this._onTransitionEnd = this._onTransitionEnd.bind(this);
  }

  componentDidMount(): void {
    this._isMounted = true;
  }

  componentWillUnmount(): void {
    this._isMounted = false;
  }

  componentWillReceiveProps(nextProps: Props): void {
    const nextScenes = NavigationScenesReducer(
      this.state.scenes,
      nextProps.navigation.state,
      this.props.navigation.state
    );

    if (nextScenes === this.state.scenes) {
      return;
    }

    const nextState = {
      ...this.state,
      scenes: nextScenes,
    };

    const {
      position,
      progress,
    } = nextState;

    progress.setValue(0);

    this._prevTransitionProps = this._transitionProps;
    this._transitionProps = buildTransitionProps(nextProps, nextState);

     // get the transition spec.
    const transitionUserSpec = nextProps.configureTransition ?
      nextProps.configureTransition(
        this._transitionProps,
        this._prevTransitionProps,
      ) :
      null;

    const transitionSpec = {
      ...DefaultTransitionSpec,
      ...transitionUserSpec,
    };

    const { timing } = transitionSpec;
    delete transitionSpec.timing;

    const animations = [
      timing(
        progress,
        {
          ...transitionSpec,
          toValue: 1,
        },
      ),
    ];

    if (nextProps.navigation.state.index !== this.props.navigation.state.index) {
      animations.push(
        timing(
          position,
          {
            ...transitionSpec,
            toValue: nextProps.navigation.state.index,
          },
        ),
      );
    }

    // update scenes and play the transition
    this.setState(nextState, () => {
      nextProps.onTransitionStart && nextProps.onTransitionStart(
        this._transitionProps,
        this._prevTransitionProps,
      );
      Animated.parallel(animations).start(this._onTransitionEnd);
    });
  }

  render() {
    return (
      <View
        onLayout={this._onLayout}
        style={[styles.main, this.props.style]}
      >
        {this.props.render(this._transitionProps, this._prevTransitionProps)}
      </View>
    );
  }

  _onLayout(event: any): void {
    const { height, width } = event.nativeEvent.layout;
    if (this.state.layout.initWidth === width &&
      this.state.layout.initHeight === height) {
      return;
    }
    const layout = {
      ...this.state.layout,
      initHeight: height,
      initWidth: width,
      isMeasured: true,
    };

    layout.height.setValue(height);
    layout.width.setValue(width);

    const nextState = {
      ...this.state,
      layout,
    };

    this._transitionProps = buildTransitionProps(this.props, nextState);
    this.setState(nextState);
  }

  _onTransitionEnd(): void {
    if (!this._isMounted) {
      return;
    }
    const prevTransitionProps = this._prevTransitionProps;
    this._prevTransitionProps = null;

    const nextState = {
      ...this.state,
      scenes: this.state.scenes.filter(isSceneNotStale),
    };

    this._transitionProps = buildTransitionProps(this.props, nextState);

    this.setState(nextState, () => {
      this.props.onTransitionEnd && this.props.onTransitionEnd(
        this._transitionProps,
        prevTransitionProps,
      );
    });
  }
}

function buildTransitionProps(
  props: Props,
  state: State,
): NavigationTransitionProps {
  const {
    navigation,
  } = props;

  const {
    layout,
    position,
    progress,
    scenes,
  } = state;

  const scene = scenes.find(isSceneActive);

  invariant(scene, 'Could not find active scene');

  return {
    layout,
    navigationState: navigation.state,
    navigation: addNavigationHelpers({
      ...navigation,
      state: scene.route,
    }),
    position,
    progress,
    scenes,
    scene,
    index: scene.index,
  };
}

function isSceneNotStale(scene: NavigationScene): boolean {
  return !scene.isStale;
}

function isSceneActive(scene: NavigationScene): boolean {
  return scene.isActive;
}

const styles = StyleSheet.create({
  main: {
    flex: 1,
  },
});

export default Transitioner;
<commit_msg>Allow Transitioner to queue single animation (#191)<commit_after>/* @flow */

import React from 'react';

import {
  Animated,
  StyleSheet,
  View,
} from 'react-native';

import invariant from 'fbjs/lib/invariant';

import NavigationScenesReducer from './ScenesReducer';
import TransitionConfigs from './TransitionConfigs';
import addNavigationHelpers from '../addNavigationHelpers';
import NavigationPropTypes from '../PropTypes';

import type {
  NavigationAnimatedValue,
  NavigationLayout,
  NavigationScene,
  NavigationRoute,
  NavigationAction,
  NavigationScreenProp,
  NavigationTransitionProps,
  NavigationTransitionSpec,
} from '../TypeDefinition';

type Props = {
  configureTransition: (
    transitionProps: NavigationTransitionProps,
    prevTransitionProps: ?NavigationTransitionProps,
  ) => NavigationTransitionSpec,
  navigation: NavigationScreenProp<NavigationRoute, NavigationAction>,
  onTransitionEnd: () => void,
  onTransitionStart: () => void,
  render: (
    transitionProps: NavigationTransitionProps,
    prevTransitionProps: ?NavigationTransitionProps
  ) => any,
  style: any,
};

type State = {
  layout: NavigationLayout,
  position: NavigationAnimatedValue,
  progress: NavigationAnimatedValue,
  scenes: Array<NavigationScene>,
};

const { PropTypes } = React;

const DefaultTransitionSpec = TransitionConfigs.DefaultTransitionSpec;

class Transitioner extends React.Component<*, Props, State> {
  _onLayout: (event: any) => void;
  _onTransitionEnd: () => void;
  _prevTransitionProps: ?NavigationTransitionProps;
  _transitionProps: NavigationTransitionProps;
  _isMounted: boolean;

  props: Props;
  state: State;

  static propTypes = {
    configureTransition: PropTypes.func,
    navigation: PropTypes.shape({
      state: NavigationPropTypes.navigationState.isRequired,
    }).isRequired,
    onTransitionEnd: PropTypes.func,
    onTransitionStart: PropTypes.func,
    render: PropTypes.func.isRequired,
  };

  constructor(props: Props, context: any) {
    super(props, context);

    // The initial layout isn't measured. Measured layout will be only available
    // when the component is mounted.
    const layout = {
      height: new Animated.Value(0),
      initHeight: 0,
      initWidth: 0,
      isMeasured: false,
      width: new Animated.Value(0),
    };

    this.state = {
      layout,
      position: new Animated.Value(this.props.navigation.state.index),
      progress: new Animated.Value(1),
      scenes: NavigationScenesReducer([], this.props.navigation.state),
    };

    this._prevTransitionProps = null;
    this._transitionProps = buildTransitionProps(props, this.state);
    this._isMounted = false;
    this._isTransitionRunning = false;
    this._queuedTransition = null;
  }

  componentWillMount(): void {
    this._onLayout = this._onLayout.bind(this);
    this._onTransitionEnd = this._onTransitionEnd.bind(this);
  }

  componentDidMount(): void {
    this._isMounted = true;
  }

  componentWillUnmount(): void {
    this._isMounted = false;
  }

  componentWillReceiveProps(nextProps: Props): void {
    const nextScenes = NavigationScenesReducer(
      this.state.scenes,
      nextProps.navigation.state,
      this.props.navigation.state
    );

    if (nextScenes === this.state.scenes) {
      return;
    }

    const indexHasChanged = nextProps.navigation.state.index !== this.props.navigation.state.index;
    if (this._isTransitionRunning) {
      this._queuedTransition = { nextProps, nextScenes, indexHasChanged };
      return;
    }

    this._startTransition(nextProps, nextScenes, indexHasChanged);
  }

  _startTransition(nextProps: Props, nextScenes: Array<NavigationScene>, indexHasChanged: boolean) {
    const nextState = {
      ...this.state,
      scenes: nextScenes,
    };

    const {
      position,
      progress,
    } = nextState;

    progress.setValue(0);

    this._prevTransitionProps = this._transitionProps;
    this._transitionProps = buildTransitionProps(nextProps, nextState);

     // get the transition spec.
    const transitionUserSpec = nextProps.configureTransition ?
      nextProps.configureTransition(
        this._transitionProps,
        this._prevTransitionProps,
      ) :
      null;

    const transitionSpec = {
      ...DefaultTransitionSpec,
      ...transitionUserSpec,
    };

    const { timing } = transitionSpec;
    delete transitionSpec.timing;

    const animations = [
      timing(
        progress,
        {
          ...transitionSpec,
          toValue: 1,
        },
      ),
    ];

    if (indexHasChanged) {
      animations.push(
        timing(
          position,
          {
            ...transitionSpec,
            toValue: nextProps.navigation.state.index,
          },
        ),
      );
    }
    // update scenes and play the transition
    this._isTransitionRunning = true;
    this.setState(nextState, () => {
      nextProps.onTransitionStart && nextProps.onTransitionStart(
        this._transitionProps,
        this._prevTransitionProps,
      );
      Animated.parallel(animations).start(this._onTransitionEnd);
    });
  }

  render() {
    return (
      <View
        onLayout={this._onLayout}
        style={[styles.main, this.props.style]}
      >
        {this.props.render(this._transitionProps, this._prevTransitionProps)}
      </View>
    );
  }

  _onLayout(event: any): void {
    const { height, width } = event.nativeEvent.layout;
    if (this.state.layout.initWidth === width &&
      this.state.layout.initHeight === height) {
      return;
    }
    const layout = {
      ...this.state.layout,
      initHeight: height,
      initWidth: width,
      isMeasured: true,
    };

    layout.height.setValue(height);
    layout.width.setValue(width);

    const nextState = {
      ...this.state,
      layout,
    };

    this._transitionProps = buildTransitionProps(this.props, nextState);
    this.setState(nextState);
  }

  _onTransitionEnd(): void {
    if (!this._isMounted) {
      return;
    }
    const prevTransitionProps = this._prevTransitionProps;
    this._prevTransitionProps = null;

    const nextState = {
      ...this.state,
      scenes: this.state.scenes.filter(isSceneNotStale),
    };

    this._transitionProps = buildTransitionProps(this.props, nextState);

    this.setState(nextState, () => {
      this.props.onTransitionEnd && this.props.onTransitionEnd(
        this._transitionProps,
        prevTransitionProps,
      );
      if (this._queuedTransition) {
        this._startTransition(
            this._queuedTransition.nextProps,
            this._queuedTransition.nextScenes,
            this._queuedTransition.indexHasChanged
        );
        this._queuedTransition = null;
      } else {
        this._isTransitionRunning = false;
      }
    });
  }
}

function buildTransitionProps(
  props: Props,
  state: State,
): NavigationTransitionProps {
  const {
    navigation,
  } = props;

  const {
    layout,
    position,
    progress,
    scenes,
  } = state;

  const scene = scenes.find(isSceneActive);

  invariant(scene, 'Could not find active scene');

  return {
    layout,
    navigationState: navigation.state,
    navigation: addNavigationHelpers({
      ...navigation,
      state: scene.route,
    }),
    position,
    progress,
    scenes,
    scene,
    index: scene.index,
  };
}

function isSceneNotStale(scene: NavigationScene): boolean {
  return !scene.isStale;
}

function isSceneActive(scene: NavigationScene): boolean {
  return scene.isActive;
}

const styles = StyleSheet.create({
  main: {
    flex: 1,
  },
});

export default Transitioner;
<|endoftext|>


<commit_before>
var myApp = angular.module('progressApp', [
           'mm.foundation'
    ])
    .controller('progressCtrl', function ($rootScope) {

        $rootScope.isReadonly = false;

        $rootScope.dynamicObject = {
            value: 0,
            text : "",
            type: ''
        };

        $rootScope.loadData = function() {
            $rootScope.isReadonly = true;

            //using play framework we get the absolute URL
            var wsUrl = jsRoutes.controllers.api.ImportData.progress().absoluteURL();
            //replace the protocol to http ws
            wsUrl = wsUrl.replace("http", "ws");

            // Create our websocket object with the address to the websocket
            var ws = new WebSocket(wsUrl);

            ws.onopen = function () {
                console.log("Socket has been opened!");
                var msg = { url: angular.element(document.querySelector('#url')).val() };
                ws.send(JSON.stringify(msg))
                console.log("Sent data to websocket: ", msg);
            };

            ws.onmessage = function (message) {
                listener(JSON.parse(message.data));
            };

            function listener(data) {
                var messageObj = data;
                console.log("Received data from websocket: ", messageObj);
                //update the progress bar
                $rootScope.dynamicObject.type = messageObj.success ? '' : 'alert';
                $rootScope.dynamicObject.text = messageObj.text;
                $rootScope.isReadonly = !messageObj.done;
                $rootScope.$apply()
            }
        }

    })

<commit_msg>changed ws protocol to wss for https<commit_after>
var myApp = angular.module('progressApp', [
           'mm.foundation'
    ])
    .controller('progressCtrl', function ($rootScope) {

        $rootScope.isReadonly = false;

        $rootScope.dynamicObject = {
            value: 0,
            text : "",
            type: ''
        };

        $rootScope.loadData = function() {
            $rootScope.isReadonly = true;

            //using play framework we get the absolute URL
            var wsUrl = jsRoutes.controllers.api.ImportData.progress().absoluteURL();
            //replace the protocol to http ws
            wsUrl = wsUrl.replace("https", "wss");
//            wsUrl = wsUrl.replace("http", "ws");

            // Create our websocket object with the address to the websocket
            var ws = new WebSocket(wsUrl);

            ws.onopen = function () {
                console.log("Socket has been opened!");
                var msg = { url: angular.element(document.querySelector('#url')).val() };
                ws.send(JSON.stringify(msg))
                console.log("Sent data to websocket: ", msg);
            };

            ws.onmessage = function (message) {
                listener(JSON.parse(message.data));
            };

            function listener(data) {
                var messageObj = data;
                console.log("Received data from websocket: ", messageObj);
                //update the progress bar
                $rootScope.dynamicObject.type = messageObj.success ? '' : 'alert';
                $rootScope.dynamicObject.text = messageObj.text;
                $rootScope.isReadonly = !messageObj.done;
                $rootScope.$apply()
            }
        }

    })

<|endoftext|>