<commit_before>/**
 * core/modules data store: module info.
 *
 * 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';

/**
 * WordPress dependencies
 */
import { WPElement } from '@wordpress/element';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './constants';
import { STORE_NAME as CORE_SITE } from '../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as CORE_USER } from '../../datastore/user/constants';
import { actions as registryKeyActions } from '../../datastore/site/registry-key';
import { createFetchStore } from '../../data/create-fetch-store';
import DefaultModuleSettings from '../components/DefaultModuleSettings';
import { sortByProperty } from '../../../util/sort-by-property';
import { convertArrayListToKeyedObjectMap } from '../../../util/convert-array-to-keyed-object-map';

const { commonActions, createRegistrySelector, createRegistryControl } = Data;

/**
 * Store our module components by registry, then by module `slug`. We do this because
 * we can't store React components in our data store.
 *
 * @private
 * @since n.e.x.t
 */
export const ModuleComponents = {};

// Actions.
const REFETCH_AUTHENTICATION = 'REFETCH_AUTHENTICATION';
const REGISTER_MODULE = 'REGISTER_MODULE';

const fetchGetModulesStore = createFetchStore( {
	baseName: 'getModules',
	controlCallback: () => {
		return API.get( 'core', 'modules', 'list', null, {
			useCache: false,
		} );
	},
	reducerCallback: ( state, modules ) => {
		return {
			...state,
			isAwaitingModulesRefresh: false,
			modules: modules.reduce( ( acc, module ) => {
				return { ...acc, [ module.slug ]: module };
			}, {} ),
		};
	},
} );

const fetchSetModuleActivationStore = createFetchStore( {
	baseName: 'setModuleActivation',
	controlCallback: ( { slug, active } ) => {
		return API.set( 'core', 'modules', 'activation', {
			slug,
			active,
		} );
	},
	reducerCallback: ( state ) => {
		// Updated module activation state is handled by re-fetching module
		// data instead, so this reducer just sets the below flag.
		return {
			...state,
			isAwaitingModulesRefresh: true,
		};
	},
	argsToParams: ( slug, active ) => {
		invariant( slug, 'slug is required.' );
		invariant( active !== undefined, 'active is required.' );
		return {
			slug,
			active,
		};
	},
} );

const BASE_INITIAL_STATE = {
	modules: undefined,
	// This value is to indicate that modules data needs to be refreshed after
	// a module activation update, since the activation is technically complete
	// before this data has been refreshed.
	isAwaitingModulesRefresh: false,
};

const baseActions = {
	/**
	 * Activates a module on the server.
	 *
	 * Activate a module (based on the slug provided).
	 *
	 * @since 1.8.0
	 *
	 * @param {string} slug Slug of the module to activate.
	 * @return {Object}      Object with {response, error}
	 */
	*activateModule( slug ) {
		const { response, error } = yield baseActions.setModuleActivation( slug, true );

		yield {
			payload: {},
			type: REFETCH_AUTHENTICATION,
		};

		return { response, error };
	},

	/**
	 * Deactivates a module on the server.
	 *
	 * Deactivate a module (based on the slug provided).
	 *
	 * @since 1.8.0
	 *
	 * @param {string} slug Slug of the module to activate.
	 * @return {Object}      Object with {response, error}
	 */
	*deactivateModule( slug ) {
		const { response, error } = yield baseActions.setModuleActivation( slug, false );

		yield {
			payload: {},
			type: REFETCH_AUTHENTICATION,
		};

		return { response, error };
	},

	/**
	 * (De)activates a module on the server.
	 *
	 * POSTs to the `core/modules/activation` endpoint to set the `active` status
	 * supplied for the give `slug`.
	 *
	 * @since 1.8.0
	 * @private
	 *
	 * @param {string}  slug   Slug of the module to activate/deactivate.
	 * @param {boolean} active `true` to activate; `false` to deactivate.
	 * @return {Object}         Object with {response, error}
	 */
	*setModuleActivation( slug, active ) {
		invariant( slug, 'slug is required.' );
		invariant( active !== undefined, 'active is required.' );

		const { response, error } = yield fetchSetModuleActivationStore.actions.fetchSetModuleActivation( slug, active );
		if ( response?.success === true ) {
			// Fetch (or re-fetch) all modules, with their updated status.
			yield fetchGetModulesStore.actions.fetchGetModules();
			yield {
				payload: {},
				type: REFETCH_AUTHENTICATION,
			};
		}

		return { response, error };
	},

	/**
	 * Registers a module.
	 *
	 * @since n.e.x.t
	 *
	 * @param {string}    slug                         Module slug.
	 * @param {Object}    [settings]                   Optional. Module settings.
	 * @param {string}    [settings.name]              Optional. Module name. Default is the slug.
	 * @param {string}    [settings.description]       Optional. Module description. Default empty string.
	 * @param {string}    [settings.icon]              Optional. Module icon. Default empty string.
	 * @param {number}    [settings.order]             Optional. Numeric indicator for module order. Default 10.
	 * @param {string}    [settings.homepage]          Optional. Module homepage URL. Default empty string.
	 * @param {WPElement} [settings.settingsComponent] React component to render the settings panel. Default is the DefaultModuleSettings component.
	 * @return {Object} Redux-style action.
	 */
	*registerModule( slug, { settingsComponent = DefaultModuleSettings, ...settings } = {} ) {
		invariant( slug, 'module slug is required' );

		const registry = yield commonActions.getRegistry();
		yield registryKeyActions.waitForRegistryKey();
		const registryKey = registry.select( CORE_SITE ).getRegistryKey();

		// We do this assignment in the action rather than the reducer because we can't send a
		// payload that includes a React component to the reducer; we'll get an error about
		// payloads needing to be plain objects.
		if ( ModuleComponents[ registryKey ] === undefined ) {
			ModuleComponents[ registryKey ] = {};
		}
		if ( ModuleComponents[ registryKey ][ slug ] === undefined ) {
			ModuleComponents[ registryKey ][ slug ] = settingsComponent;
		}
		// Ensure that active and connected properties are not passed here.
		delete settings.active;
		delete settings.connected;

		const mergedModuleSettings = {
			slug,
			...settings,
		};

		return {
			payload: { slug, settings: mergedModuleSettings },
			type: REGISTER_MODULE,
		};
	},
};

export const baseControls = {
	[ REFETCH_AUTHENTICATION ]: createRegistryControl( ( { dispatch } ) => () => {
		return dispatch( CORE_USER ).fetchGetAuthentication();
	} ),
};

const baseReducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case REGISTER_MODULE: {
			const { slug, settings } = payload;
			const { modules: existingModules } = state;
			const defaults = {
				description: null,
				icon: null,
				order: 10,
				homepage: null,
				internal: false,
				active: false,
				connected: false,
				name: slug,
			};
			return {
				...state,
				modules: {
					...existingModules,
					[ slug ]: {
						...defaults,
						...( existingModules?.[ slug ] || {} ),
						...settings,
					},
				},
			};
		}

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

const baseResolvers = {
	*getModules() {
		const registry = yield Data.commonActions.getRegistry();

		const existingModules = registry.select( STORE_NAME ).getModules();

		if ( ! existingModules ) {
			yield fetchGetModulesStore.actions.fetchGetModules();
		}
	},
};

const baseSelectors = {
	/**
	 * Gets the list of modules registered for use with Site Kit.
	 *
	 * A module is a section of Site Kit that relates to a particular service,
	 * like Google Analytics or Google PageSpeed modules. They can provide
	 * admin-only features (like PageSpeed Insights), frontend-only features,
	 * or both (eg. Analytics, which can install Analytics <script> tags in the
	 * frontend, and show dashboards in the WordPress Admin).
	 *
	 * Returns an Object/map of objects, keyed by slug, with the following shape when successful:
	 * ```
	 * slug: {
	 *   "slug": "tagmanager",
	 *   "name": "Tag Manager",
	 *   "description": "Tag Manager creates an easy to manage way to create tags on your site without updating code.",
	 *   "homepage": "https://tagmanager.google.com/",
	 *   "internal": false,
	 *   "active": false,
	 *   "connected": false,
	 *   "dependencies": [
	 *     "analytics"
	 *   ],
	 *   "dependents": []
	 * }
	 * ```
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Modules available on the site.
	 */
	getModules: createRegistrySelector( ( select ) => ( state ) => {
		const { modules } = state;

		// Return `undefined` if modules haven't been loaded yet.
		if ( modules === undefined ) {
			return undefined;
		}

		const registryKey = select( CORE_SITE ).getRegistryKey();
		// Sorting the modules object by order property.
		const sortedModules = sortByProperty( Object.values( modules ), 'order' );
		const mappedModules = sortedModules.map( ( module ) => {
			const moduleWithComponent = { ...module };
			if ( ModuleComponents[ registryKey ] ) {
				// If there is a settingsComponent that was passed use it, otherwise set to the default.
				if ( ModuleComponents[ registryKey ][ module.slug ] ) {
					moduleWithComponent.settingsComponent = ModuleComponents[ registryKey ][ module.slug ];
				} else {
					moduleWithComponent.settingsComponent = DefaultModuleSettings;
				}
			}

			return moduleWithComponent;
		} );
		return convertArrayListToKeyedObjectMap( mappedModules, 'slug' );
	} ),

	/**
	 * Gets a specific module by slug.
	 *
	 * Returns a specific module by its slug.
	 * Returns `undefined` if state is still loading or if said module doesn't exist.
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} slug  Module slug.
	 * @return {(Object|undefined)} A specific module object; `undefined` if state is still loading or if said module doesn't exist.
	 */
	getModule: createRegistrySelector( ( select ) => ( state, slug ) => {
		const modules = select( STORE_NAME ).getModules();

		// Return `undefined` if modules haven't been loaded yet.
		if ( modules === undefined ) {
			return undefined;
		}

		// A module with this slug couldn't be found; return `null` to signify the
		// "not found" state.
		if ( modules[ slug ] === undefined ) {
			return null;
		}

		// This module exists, so let's return it.
		return modules[ slug ];
	} ),

	/**
	 * Checks a module's activation status.
	 *
	 * Returns `true` if the module exists and is active.
	 * Returns `false` if the module exists but is not active.
	 * Returns `undefined` if state is still loading or if no module with that slug exists.
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} slug  Module slug.
	 * @return {(Object|undefined)} A specific module object; `undefined` if state is still loading or if said module doesn't exist.
	 */
	isModuleActive: createRegistrySelector( ( select ) => ( state, slug ) => {
		const module = select( STORE_NAME ).getModule( slug );

		// Return `undefined` if modules haven't been loaded yet.
		if ( module === undefined ) {
			return undefined;
		}

		// A module with this slug couldn't be found; return `null` to signify the
		// "not found" state.
		if ( module === null ) {
			return null;
		}

		return module.active;
	} ),

	/**
	 * Checks if a module's status is changing.
	 *
	 * Returns `true` if the module exists and is changing its `active` flag.
	 * Returns `false` if the module exists but is not changing its `active` flag.
	 * Returns `undefined` if state is still loading or if no module with that slug exists.
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} slug  Module slug.
	 * @return {(boolean|undefined)} Activation change status; `undefined` if state is still loading or if no module with that slug exists.
	 */
	isSettingModuleActivation: createRegistrySelector( ( select ) => ( state, slug ) => {
		// Return undefined if modules not loaded or invalid slug.
		if ( ! select( STORE_NAME ).getModule( slug ) ) {
			return undefined;
		}

		// Check if the module is being activated.
		if ( select( STORE_NAME ).isFetchingSetModuleActivation( slug, true ) ) {
			return true;
		}

		// Check if the module is being deactivated.
		if ( select( STORE_NAME ).isFetchingSetModuleActivation( slug, false ) ) {
			return true;
		}

		// Check if modules data still needs to be refreshed after activation
		// update.
		return state.isAwaitingModulesRefresh;
	} ),
};

const store = Data.combineStores(
	fetchGetModulesStore,
	fetchSetModuleActivationStore,
	{
		INITIAL_STATE: BASE_INITIAL_STATE,
		actions: baseActions,
		controls: baseControls,
		reducer: baseReducer,
		resolvers: baseResolvers,
		selectors: baseSelectors,
	}
);

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

export default store;
<commit_msg>Add waitForModules action.<commit_after>/**
 * core/modules data store: module info.
 *
 * 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';

/**
 * WordPress dependencies
 */
import { WPElement } from '@wordpress/element';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './constants';
import { STORE_NAME as CORE_SITE } from '../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as CORE_USER } from '../../datastore/user/constants';
import { actions as registryKeyActions } from '../../datastore/site/registry-key';
import { createFetchStore } from '../../data/create-fetch-store';
import DefaultModuleSettings from '../components/DefaultModuleSettings';
import { sortByProperty } from '../../../util/sort-by-property';
import { convertArrayListToKeyedObjectMap } from '../../../util/convert-array-to-keyed-object-map';

const { commonActions, createRegistrySelector, createRegistryControl } = Data;

/**
 * Store our module components by registry, then by module `slug`. We do this because
 * we can't store React components in our data store.
 *
 * @private
 * @since n.e.x.t
 */
export const ModuleComponents = {};

// Actions.
const REFETCH_AUTHENTICATION = 'REFETCH_AUTHENTICATION';
const REGISTER_MODULE = 'REGISTER_MODULE';
const WAIT_FOR_MODULES = 'WAIT_FOR_MODULES';

const fetchGetModulesStore = createFetchStore( {
	baseName: 'getModules',
	controlCallback: () => {
		return API.get( 'core', 'modules', 'list', null, {
			useCache: false,
		} );
	},
	reducerCallback: ( state, modules ) => {
		return {
			...state,
			isAwaitingModulesRefresh: false,
			modules: modules.reduce( ( acc, module ) => {
				return { ...acc, [ module.slug ]: module };
			}, {} ),
		};
	},
} );

const fetchSetModuleActivationStore = createFetchStore( {
	baseName: 'setModuleActivation',
	controlCallback: ( { slug, active } ) => {
		return API.set( 'core', 'modules', 'activation', {
			slug,
			active,
		} );
	},
	reducerCallback: ( state ) => {
		// Updated module activation state is handled by re-fetching module
		// data instead, so this reducer just sets the below flag.
		return {
			...state,
			isAwaitingModulesRefresh: true,
		};
	},
	argsToParams: ( slug, active ) => {
		invariant( slug, 'slug is required.' );
		invariant( active !== undefined, 'active is required.' );
		return {
			slug,
			active,
		};
	},
} );

const BASE_INITIAL_STATE = {
	modules: undefined,
	// This value is to indicate that modules data needs to be refreshed after
	// a module activation update, since the activation is technically complete
	// before this data has been refreshed.
	isAwaitingModulesRefresh: false,
};

const baseActions = {
	/**
	 * Wait for the modules to be loaded
	 *
	 * @since n.e.x.t
	 *
	 * @return {Object} Redux-style action.
	 */
	waitForModules() {
		return {
			payload: {},
			type: WAIT_FOR_MODULES,
		};
	},
	/**
	 * Activates a module on the server.
	 *
	 * Activate a module (based on the slug provided).
	 *
	 * @since 1.8.0
	 *
	 * @param {string} slug Slug of the module to activate.
	 * @return {Object}      Object with {response, error}
	 */
	*activateModule( slug ) {
		const { response, error } = yield baseActions.setModuleActivation( slug, true );

		yield {
			payload: {},
			type: REFETCH_AUTHENTICATION,
		};

		return { response, error };
	},

	/**
	 * Deactivates a module on the server.
	 *
	 * Deactivate a module (based on the slug provided).
	 *
	 * @since 1.8.0
	 *
	 * @param {string} slug Slug of the module to activate.
	 * @return {Object}      Object with {response, error}
	 */
	*deactivateModule( slug ) {
		const { response, error } = yield baseActions.setModuleActivation( slug, false );

		yield {
			payload: {},
			type: REFETCH_AUTHENTICATION,
		};

		return { response, error };
	},

	/**
	 * (De)activates a module on the server.
	 *
	 * POSTs to the `core/modules/activation` endpoint to set the `active` status
	 * supplied for the give `slug`.
	 *
	 * @since 1.8.0
	 * @private
	 *
	 * @param {string}  slug   Slug of the module to activate/deactivate.
	 * @param {boolean} active `true` to activate; `false` to deactivate.
	 * @return {Object}         Object with {response, error}
	 */
	*setModuleActivation( slug, active ) {
		invariant( slug, 'slug is required.' );
		invariant( active !== undefined, 'active is required.' );

		const { response, error } = yield fetchSetModuleActivationStore.actions.fetchSetModuleActivation( slug, active );
		if ( response?.success === true ) {
			// Fetch (or re-fetch) all modules, with their updated status.
			yield fetchGetModulesStore.actions.fetchGetModules();
			yield {
				payload: {},
				type: REFETCH_AUTHENTICATION,
			};
		}

		return { response, error };
	},

	/**
	 * Registers a module.
	 *
	 * @since n.e.x.t
	 *
	 * @param {string}    slug                         Module slug.
	 * @param {Object}    [settings]                   Optional. Module settings.
	 * @param {string}    [settings.name]              Optional. Module name. Default is the slug.
	 * @param {string}    [settings.description]       Optional. Module description. Default empty string.
	 * @param {string}    [settings.icon]              Optional. Module icon. Default empty string.
	 * @param {number}    [settings.order]             Optional. Numeric indicator for module order. Default 10.
	 * @param {string}    [settings.homepage]          Optional. Module homepage URL. Default empty string.
	 * @param {WPElement} [settings.settingsComponent] React component to render the settings panel. Default is the DefaultModuleSettings component.
	 * @return {Object} Redux-style action.
	 */
	*registerModule( slug, { settingsComponent = DefaultModuleSettings, ...settings } = {} ) {
		invariant( slug, 'module slug is required' );

		const registry = yield commonActions.getRegistry();
		yield actions.waitForModules();
		registry.select( STORE_NAME ).getModules();
		yield registryKeyActions.waitForRegistryKey();
		const registryKey = registry.select( CORE_SITE ).getRegistryKey();

		// We do this assignment in the action rather than the reducer because we can't send a
		// payload that includes a React component to the reducer; we'll get an error about
		// payloads needing to be plain objects.
		if ( ModuleComponents[ registryKey ] === undefined ) {
			ModuleComponents[ registryKey ] = {};
		}
		if ( ModuleComponents[ registryKey ][ slug ] === undefined ) {
			ModuleComponents[ registryKey ][ slug ] = settingsComponent;
		}
		// Ensure that active and connected properties are not passed here.
		delete settings.active;
		delete settings.connected;

		const mergedModuleSettings = {
			slug,
			...settings,
		};

		return {
			payload: { slug, settings: mergedModuleSettings },
			type: REGISTER_MODULE,
		};
	},
};

export const baseControls = {
	[ REFETCH_AUTHENTICATION ]: createRegistryControl( ( { dispatch } ) => () => {
		return dispatch( CORE_USER ).fetchGetAuthentication();
	} ),
	[ WAIT_FOR_MODULES ]: createRegistryControl( ( registry ) => ( { payload: {} } ) => {
		// Select first to ensure resolution is always triggered.
		const { getModules, hasFinishedResolution } = registry.select( STORE_NAME );
		getModules();
		const modulesAreLoaded = () => hasFinishedResolution( 'getModules', [] );
		if ( modulesAreLoaded() ) {
			return;
		}
		return new Promise( ( resolve ) => {
			const unsubscribe = registry.subscribe( () => {
				if ( modulesAreLoaded() ) {
					unsubscribe();
					resolve();
				}
			} );
		} );
	} ),
};

const baseReducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case REGISTER_MODULE: {
			const { slug, settings } = payload;
			const { modules: existingModules } = state;
			const defaults = {
				description: null,
				icon: null,
				order: 10,
				homepage: null,
				internal: false,
				active: false,
				connected: false,
				name: slug,
			};
			return {
				...state,
				modules: {
					...existingModules,
					[ slug ]: {
						...defaults,
						...( existingModules?.[ slug ] || {} ),
						...settings,
					},
				},
			};
		}

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

const baseResolvers = {
	*getModules() {
		const registry = yield Data.commonActions.getRegistry();

		const existingModules = registry.select( STORE_NAME ).getModules();

		if ( ! existingModules ) {
			yield fetchGetModulesStore.actions.fetchGetModules();
		}
	},
};

const baseSelectors = {
	/**
	 * Gets the list of modules registered for use with Site Kit.
	 *
	 * A module is a section of Site Kit that relates to a particular service,
	 * like Google Analytics or Google PageSpeed modules. They can provide
	 * admin-only features (like PageSpeed Insights), frontend-only features,
	 * or both (eg. Analytics, which can install Analytics <script> tags in the
	 * frontend, and show dashboards in the WordPress Admin).
	 *
	 * Returns an Object/map of objects, keyed by slug, with the following shape when successful:
	 * ```
	 * slug: {
	 *   "slug": "tagmanager",
	 *   "name": "Tag Manager",
	 *   "description": "Tag Manager creates an easy to manage way to create tags on your site without updating code.",
	 *   "homepage": "https://tagmanager.google.com/",
	 *   "internal": false,
	 *   "active": false,
	 *   "connected": false,
	 *   "dependencies": [
	 *     "analytics"
	 *   ],
	 *   "dependents": []
	 * }
	 * ```
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Modules available on the site.
	 */
	getModules: createRegistrySelector( ( select ) => ( state ) => {
		const { modules } = state;

		// Return `undefined` if modules haven't been loaded yet.
		if ( modules === undefined ) {
			return undefined;
		}

		const registryKey = select( CORE_SITE ).getRegistryKey();
		if ( registryKey === undefined ) {
			return undefined;
		}
		// Sorting the modules object by order property.
		const sortedModules = sortByProperty( Object.values( modules ), 'order' );
		const mappedModules = sortedModules.map( ( module ) => {
			const moduleWithComponent = { ...module };
			if ( ModuleComponents[ registryKey ] ) {
				// If there is a settingsComponent that was passed use it, otherwise set to the default.
				if ( ModuleComponents[ registryKey ][ module.slug ] ) {
					moduleWithComponent.settingsComponent = ModuleComponents[ registryKey ][ module.slug ];
				} else {
					moduleWithComponent.settingsComponent = DefaultModuleSettings;
				}
			}

			return moduleWithComponent;
		} );
		return convertArrayListToKeyedObjectMap( mappedModules, 'slug' );
	} ),

	/**
	 * Gets a specific module by slug.
	 *
	 * Returns a specific module by its slug.
	 * Returns `undefined` if state is still loading or if said module doesn't exist.
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} slug  Module slug.
	 * @return {(Object|undefined)} A specific module object; `undefined` if state is still loading or if said module doesn't exist.
	 */
	getModule: createRegistrySelector( ( select ) => ( state, slug ) => {
		const modules = select( STORE_NAME ).getModules();

		// Return `undefined` if modules haven't been loaded yet.
		if ( modules === undefined ) {
			return undefined;
		}

		// A module with this slug couldn't be found; return `null` to signify the
		// "not found" state.
		if ( modules[ slug ] === undefined ) {
			return null;
		}

		// This module exists, so let's return it.
		return modules[ slug ];
	} ),

	/**
	 * Checks a module's activation status.
	 *
	 * Returns `true` if the module exists and is active.
	 * Returns `false` if the module exists but is not active.
	 * Returns `undefined` if state is still loading or if no module with that slug exists.
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} slug  Module slug.
	 * @return {(Object|undefined)} A specific module object; `undefined` if state is still loading or if said module doesn't exist.
	 */
	isModuleActive: createRegistrySelector( ( select ) => ( state, slug ) => {
		const module = select( STORE_NAME ).getModule( slug );

		// Return `undefined` if modules haven't been loaded yet.
		if ( module === undefined ) {
			return undefined;
		}

		// A module with this slug couldn't be found; return `null` to signify the
		// "not found" state.
		if ( module === null ) {
			return null;
		}

		return module.active;
	} ),

	/**
	 * Checks if a module's status is changing.
	 *
	 * Returns `true` if the module exists and is changing its `active` flag.
	 * Returns `false` if the module exists but is not changing its `active` flag.
	 * Returns `undefined` if state is still loading or if no module with that slug exists.
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {string} slug  Module slug.
	 * @return {(boolean|undefined)} Activation change status; `undefined` if state is still loading or if no module with that slug exists.
	 */
	isSettingModuleActivation: createRegistrySelector( ( select ) => ( state, slug ) => {
		// Return undefined if modules not loaded or invalid slug.
		if ( ! select( STORE_NAME ).getModule( slug ) ) {
			return undefined;
		}

		// Check if the module is being activated.
		if ( select( STORE_NAME ).isFetchingSetModuleActivation( slug, true ) ) {
			return true;
		}

		// Check if the module is being deactivated.
		if ( select( STORE_NAME ).isFetchingSetModuleActivation( slug, false ) ) {
			return true;
		}

		// Check if modules data still needs to be refreshed after activation
		// update.
		return state.isAwaitingModulesRefresh;
	} ),
};

const store = Data.combineStores(
	fetchGetModulesStore,
	fetchSetModuleActivationStore,
	{
		INITIAL_STATE: BASE_INITIAL_STATE,
		actions: baseActions,
		controls: baseControls,
		reducer: baseReducer,
		resolvers: baseResolvers,
		selectors: baseSelectors,
	}
);

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

export default store;
<|endoftext|>


<commit_before>/*
 * 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.
 */

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';

/**
 * External dependencies
 */
import { useEffect, useRef, useCallback, useState, useContext } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

/**
 * Internal dependencies
 */
import { Modal, TypographyPresets, Button } from '../../../components';
import { WPBODY_ID, BUTTON_TYPES } from '../../../constants';
import dashboardTheme from '../../../theme';
import { Close as CloseIcon } from '../../../icons';
import { StoryPropType } from '../../../types';
import { useResizeEffect } from '../../../utils';
import { ApiContext } from '../../api/apiProvider';

const CLOSE_BUTTON_SIZE = {
  HEIGHT: 30,
  WIDTH: 30,
};
const PREVIEW_CONTAINER_ID = 'previewContainer';

const CloseButton = styled.button`
  align-self: flex-end;
  color: ${({ theme }) => theme.colors.white};
  margin-top: 20px;
  margin-right: 11px;
  width: ${CLOSE_BUTTON_SIZE.WIDTH}px;
  height: ${CLOSE_BUTTON_SIZE.HEIGHT}px;
  border: ${({ theme }) => theme.borders.transparent};
  background-color: transparent;
  z-index: 10;
`;

const IframeContainer = styled.div`
  width: ${({ dimensions }) => `${dimensions.width}px`};
  height: ${({ dimensions }) =>
    `${dimensions.height - CLOSE_BUTTON_SIZE.HEIGHT}px`};
`;

const HelperText = styled.p`
  ${TypographyPresets.Large};
  margin: 0;
  padding-bottom: 20px;
  color: ${({ theme }) => theme.colors.white};
`;

const HelperContainer = styled.div`
  bottom: 0;
  width: 100%;
  min-height: 90vh;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
`;

const PreviewStory = ({ story, handleClose, isTemplate }) => {
  const {
    state: {
      stories: { previewMarkup, isLoading, error },
    },
    actions: {
      storyApi: { createStoryPreviewFromTemplate, clearStoryPreview },
    },
  } = useContext(ApiContext);

  const containerRef = useRef(document.getElementById(WPBODY_ID));

  const [isReadyToRenderPreview, setIsReadyToRenderPreview] = useState(false);
  const [modalDimensions, setModalDimensions] = useState({
    width: containerRef.current?.offsetWidth || window.innerWidth,
    height: containerRef.current?.offsetHeight || window.innerHeight,
  });
  const [previewError, setPreviewError] = useState(error.message?.title);

  const handleReadyToRenderPreview = useCallback(() => {
    setIsReadyToRenderPreview(true);
  }, [setIsReadyToRenderPreview]);

  useEffect(() => {
    // this is necessary for the #previewContainer to be findable by document
    if (previewMarkup.length > 0) {
      handleReadyToRenderPreview();
    }
  }, [handleReadyToRenderPreview, previewMarkup.length]);

  useEffect(() => {
    if (isReadyToRenderPreview) {
      let iframe = document.createElement('iframe');
      document.getElementById(PREVIEW_CONTAINER_ID).appendChild(iframe);
      iframe.setAttribute('style', 'height:100%;width:100%;border:none;');
      iframe.contentWindow.document.open();
      iframe.contentWindow.document.write(previewMarkup);
      iframe.contentWindow.document.close();
    }
  }, [previewMarkup, isReadyToRenderPreview]);

  useEffect(() => {
    if (!story) {
      setPreviewError(__('Unable to Render Preview', 'web-stories'));
    } else if (isTemplate) {
      createStoryPreviewFromTemplate(story);
    } else {
      // TODO handle story previews. Should be the default
    }
    return () => {
      clearStoryPreview();
    };
  }, [isTemplate, story, createStoryPreviewFromTemplate, clearStoryPreview]);

  useResizeEffect(
    containerRef,
    ({ width, height }) => {
      setModalDimensions({ width, height });
    },
    [setModalDimensions]
  );

  return (
    <Modal
      contentLabel={
        (story?.title &&
          sprintf(
            /* translators: %s: name of story or template getting previewed */
            __('preview of %s', 'web-stories'),
            story.title
          )) ||
        __('Story Preview', 'web-stories')
      }
      isOpen
      onClose={handleClose}
      contentStyles={{
        height: `${modalDimensions.height}px`,
        width: `${modalDimensions.width}px`,
        display: 'flex',
        flexDirection: 'column',
      }}
      overlayStyles={{
        alignItems: 'flex-end',
        justifyContent: 'flex-end',
        backgroundColor: dashboardTheme.colors.storyPreviewBackground,
      }}
    >
      <>
        <CloseButton
          onClick={handleClose}
          aria-label={__('close', 'web-stories')}
        >
          <CloseIcon aria-hidden={true} />
        </CloseButton>

        {!previewError && (
          <IframeContainer
            dimensions={modalDimensions}
            id={PREVIEW_CONTAINER_ID}
            data-testid="preview-iframe"
          />
        )}

        {isLoading && !previewError && (
          <HelperContainer>
            <HelperText>{__('Loading\u2026', 'web-stories')}</HelperText>
          </HelperContainer>
        )}

        {previewError && (
          <HelperContainer>
            <HelperText>{previewError}</HelperText>
            <Button type={BUTTON_TYPES.CTA} onClick={handleClose}>
              {__('Close Preview', 'web-stories')}
            </Button>
          </HelperContainer>
        )}
      </>
    </Modal>
  );
};

export default PreviewStory;

PreviewStory.propTypes = {
  handleClose: PropTypes.func.isRequired,
  isTemplate: PropTypes.bool,
  story: StoryPropType.isRequired,
};
<commit_msg>removing 1 extra useEffect to signal iframe as ready<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.
 */

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';

/**
 * External dependencies
 */
import { useContext, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';

/**
 * Internal dependencies
 */
import { Modal, TypographyPresets, Button } from '../../../components';
import { WPBODY_ID, BUTTON_TYPES } from '../../../constants';
import dashboardTheme from '../../../theme';
import { Close as CloseIcon } from '../../../icons';
import { StoryPropType } from '../../../types';
import { useResizeEffect } from '../../../utils';
import { ApiContext } from '../../api/apiProvider';

const CLOSE_BUTTON_SIZE = {
  HEIGHT: 30,
  WIDTH: 30,
};
const PREVIEW_CONTAINER_ID = 'previewContainer';

const CloseButton = styled.button`
  align-self: flex-end;
  color: ${({ theme }) => theme.colors.white};
  margin-top: 20px;
  margin-right: 11px;
  width: ${CLOSE_BUTTON_SIZE.WIDTH}px;
  height: ${CLOSE_BUTTON_SIZE.HEIGHT}px;
  border: ${({ theme }) => theme.borders.transparent};
  background-color: transparent;
  z-index: 10;
`;

const IframeContainer = styled.div`
  width: ${({ dimensions }) => `${dimensions.width}px`};
  height: ${({ dimensions }) =>
    `${dimensions.height - CLOSE_BUTTON_SIZE.HEIGHT}px`};
`;

const HelperText = styled.p`
  ${TypographyPresets.Large};
  margin: 0;
  padding-bottom: 20px;
  color: ${({ theme }) => theme.colors.white};
`;

const HelperContainer = styled.div`
  bottom: 0;
  width: 100%;
  min-height: 90vh;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
`;

const PreviewStory = ({ story, handleClose, isTemplate }) => {
  const {
    state: {
      stories: { previewMarkup, isLoading, error },
    },
    actions: {
      storyApi: { createStoryPreviewFromTemplate, clearStoryPreview },
    },
  } = useContext(ApiContext);

  const containerRef = useRef(document.getElementById(WPBODY_ID));

  const [modalDimensions, setModalDimensions] = useState({
    width: containerRef.current?.offsetWidth || window.innerWidth,
    height: containerRef.current?.offsetHeight || window.innerHeight,
  });
  const [previewError, setPreviewError] = useState(error.message?.title);

  useEffect(() => {
    if (previewMarkup.length > 0) {
      let iframe = document.createElement('iframe');
      document.getElementById(PREVIEW_CONTAINER_ID).appendChild(iframe);
      iframe.setAttribute('style', 'height:100%;width:100%;border:none;');
      iframe.contentWindow.document.open();
      iframe.contentWindow.document.write(previewMarkup);
      iframe.contentWindow.document.close();
    }
  }, [previewMarkup]);

  useEffect(() => {
    if (!story) {
      setPreviewError(__('Unable to Render Preview', 'web-stories'));
    } else if (isTemplate) {
      createStoryPreviewFromTemplate(story);
    } else {
      // TODO handle story previews. Should be the default
    }
    return () => {
      clearStoryPreview();
    };
  }, [isTemplate, story, createStoryPreviewFromTemplate, clearStoryPreview]);

  useResizeEffect(
    containerRef,
    ({ width, height }) => {
      setModalDimensions({ width, height });
    },
    [setModalDimensions]
  );

  return (
    <Modal
      contentLabel={
        (story?.title &&
          sprintf(
            /* translators: %s: name of story or template getting previewed */
            __('preview of %s', 'web-stories'),
            story.title
          )) ||
        __('Story Preview', 'web-stories')
      }
      isOpen
      onClose={handleClose}
      contentStyles={{
        height: `${modalDimensions.height}px`,
        width: `${modalDimensions.width}px`,
        display: 'flex',
        flexDirection: 'column',
      }}
      overlayStyles={{
        alignItems: 'flex-end',
        justifyContent: 'flex-end',
        backgroundColor: dashboardTheme.colors.storyPreviewBackground,
      }}
    >
      <>
        <CloseButton
          onClick={handleClose}
          aria-label={__('close', 'web-stories')}
        >
          <CloseIcon aria-hidden={true} />
        </CloseButton>

        {!previewError && (
          <IframeContainer
            dimensions={modalDimensions}
            id={PREVIEW_CONTAINER_ID}
            data-testid="preview-iframe"
          />
        )}

        {isLoading && !previewError && (
          <HelperContainer>
            <HelperText>{__('Loading\u2026', 'web-stories')}</HelperText>
          </HelperContainer>
        )}

        {previewError && (
          <HelperContainer>
            <HelperText>{previewError}</HelperText>
            <Button type={BUTTON_TYPES.CTA} onClick={handleClose}>
              {__('Close Preview', 'web-stories')}
            </Button>
          </HelperContainer>
        )}
      </>
    </Modal>
  );
};

export default PreviewStory;

PreviewStory.propTypes = {
  handleClose: PropTypes.func.isRequired,
  isTemplate: PropTypes.bool,
  story: StoryPropType.isRequired,
};
<|endoftext|>


<commit_before>// report/table

require.def(

    'report/table',

    ['rest/datasource', 'rest/renderer', 'report/templates'],

    function(m_datasource, m_renderer, m_templates) {

        // TODO remove hardcoded URL
        var EMPTY_RESULTS = '
No results match your conditions. ' +
            'Refine your conditions.
';

        function init() {
            var firstRequest = true;

            var body = $('body'),                       // event binding
                content = $('#content'),                // resizing
                report = $('#report'),                  // event binding, resizing, unhiding
                info = $('#report-info'),               // unhiding
                conditionsText = $('#conditions-text'), // unhiding
                toolbars = report.find('.toolbar'),     // unhiding
                table = $('#table'),                    // resizing, unhiding
                thead = table.find('thead tr'),         // event delegation, data loading
                tbody = table.find('tbody'),            // data loading
                per_page = $('.per-page'),              // data loading
                pages = $('.page-links'),               // data loading
                unique = $('.unique-count'),            // data loading
                count = $('.count');                    // data loading

            /*
             * The renderers necessary for rendering the response data into HTML
             * templates.
             *
             * ``table_header`` - renders the table header that displays column
             * names and the column ordering.
             *
             * ``table_rows`` - renders the table rows that displays the data.
             *
             * ``pages`` - renders the page links relative the entire result
             * set.
             */
            var rnd = {
                table_header: new m_renderer.template({
                    target: thead,
                    template: m_templates.header
//                    replace: 'append'
                }),
                table_rows: new m_renderer.template({
                    target: tbody,
                    template: m_templates.row
                }),
                pages: new m_renderer.template({
                    target: pages,
                    template: m_templates.pages
                })
            };

            /*
             * The data sources which make requests and handles the body of the
             * response.
             *
             * ``table_header`` - requests the latest header data.
             *
             * ``table_rows`` - requests the latest results for the report in
             * view.
             */
            var src = {
                scope_info: new m_datasource.ajax({
                    uri: API_URLS.scope,
                    success: function(json) {
                        if (json.text) {
                            var t = '';
                            if (typeof json.text === 'string') {
                                t = '
' + json.text + '
';
                            } else {
                                var conditions = json.text.conditions;
                                for (var i=0; i < conditions.length; i++)
                                    t += '
' + conditions[i] + '
';
                            }
                            conditionsText.html('
' + t + '
');
                        }
                    }
                }),

                table_header: new m_datasource.ajax({
                    uri: API_URLS.perspective,
                    success: function(json) {
//                        rnd.table_header.target.html('<input type="checkbox">	');
                        rnd.table_header.render(json.header);
                    }
                }),

                table_rows: new m_datasource.ajax({
                    uri: API_URLS.report,
                    success: function(json) {
                        if (json.rows.length == 0) {
                            $('.content').html(EMPTY_RESULTS);
                            return;
                        }

                        rnd.table_rows.render(json.rows);

                        if (json.pages) {
                            rnd.pages.render(json.pages);
                        } else {
                            if (firstRequest)
                                per_page.hide();
                            rnd.pages.target.hide();
                        }

                        if (json.unique)
                            unique.html(json.unique);

                        if (json.count)
                            count.html(json.count);

                        report.trigger('resize-report');

                        /*
                         * Unhide necessary elements on first request.
                         */
                        if (firstRequest) {
                            firstRequest = false;
                            setTimeout(function() {
                                info.show();
                                toolbars.show();
                                table.slideDown();
                            }, 300);
                        }
                    }
                })
            };

            /*
             * Make initial requests on initialization.
             */
            src.scope_info.get();
            src.table_header.get();
            src.table_rows.get();

            report.bind('resize-report', function(evt) {
                var minWidth = 900,
                    rInnerWidth = report.innerWidth(),
                    tOuterWidth = table.outerWidth(true)+20; // padding is not usable

                // do not grow past window width. take into account the padding
                nInnerWidth = Math.min(tOuterWidth, window.innerWidth-30);
                nInnerWidth = Math.max(minWidth, nInnerWidth);

                // determine the difference. half of it needs to be added
                // to the margin-right offset
                if (nInnerWidth == rInnerWidth)
                    return;

                half = (nInnerWidth - rInnerWidth) / 2.0;

                nLeft = parseInt(content.css('margin-left').match(/-?\d+/)[0]) - half;

                content.animate({
                    'margin-left': nLeft
                });

                report.animate({
                    'width': nInnerWidth
                });
            });

            var resizeTimeOut;
            $(window).resize(function() {
                clearTimeout(resizeTimeOut);
                resizeTimeOut = setTimeout(function() {
                    report.trigger('resize-report');
                }, 200);
            });

             /*
             * Define primary event that handles fetching data.
             */
            body.bind('update.report', function(evt, params) {
                src.table_rows.get(params);
                return false;
            });

            body.bind('update.perspective', function(evt, params) {
                $.putJSON(API_URLS.perspective, JSON.stringify(params), function() {
                    body.trigger('update.report');
                    if ('columns' in params)
                        src.table_header.get();
                });
                return false;
            });

            /*
             * Hook up the elements that change the number of rows per page.
             */
            report.delegate('.per-page', 'change', function(evt) {
                if (this.value) {
                    body.trigger('update.report', {'n': this.value});
                    this.value = '';
                }
                return false;
            });

            /*
             * This implementation does *not* currently support multiple column
             * sorting. The only change needed is to grab all of the header
             * directions and pass it to the server.
             */
            thead.delegate('.header', 'click', function(evt) {
                var dir, target = $(this), id = target.attr('data-id'),
                    siblings = target.siblings();

                // reset siblings to no ordering
                siblings.removeClass('asc').removeClass('desc');

                if (target.hasClass('asc')) {
                    target.removeClass('asc').addClass('desc');
                    dir = 'desc';
                } else if (target.hasClass('desc')) {
                    target.removeClass('desc');
                    dir = '';
                } else {
                    target.addClass('asc');
                    dir = 'asc';
                }

                body.trigger('update.perspective', [{'ordering': [[id, dir]]}]);
                return false;
            });

            /*
             * Hook up the elements that change the page.
             */
            pages.delegate('a', 'click', function(evt) {
                body.trigger('update.report', {'p': this.hash.substr(1)});
                return false;
            });
        };

        return {init: init};
    }
);
<commit_msg>fixed bugzid:631<commit_after>// report/table

require.def(

    'report/table',

    ['rest/datasource', 'rest/renderer', 'report/templates'],

    function(m_datasource, m_renderer, m_templates) {

        // TODO remove hardcoded URL
        var EMPTY_RESULTS = '
No results match your conditions. ' +
            'Refine your conditions.
';

        function init() {
            var firstRequest = true;

            var body = $('body'),                       // event binding
                content = $('#content'),                // resizing
                report = $('#report'),                  // event binding, resizing, unhiding
                info = $('#report-info'),               // unhiding
                conditionsText = $('#conditions-text'), // unhiding
                toolbars = report.find('.toolbar'),     // unhiding
                table = $('#table'),                    // resizing, unhiding
                thead = table.find('thead tr'),         // event delegation, data loading
                tbody = table.find('tbody'),            // data loading
                per_page = $('.per-page'),              // data loading
                pages = $('.page-links'),               // data loading
                unique = $('.unique-count'),            // data loading
                count = $('.count');                    // data loading

            /*
             * The renderers necessary for rendering the response data into HTML
             * templates.
             *
             * ``table_header`` - renders the table header that displays column
             * names and the column ordering.
             *
             * ``table_rows`` - renders the table rows that displays the data.
             *
             * ``pages`` - renders the page links relative the entire result
             * set.
             */
            var rnd = {
                table_header: new m_renderer.template({
                    target: thead,
                    template: m_templates.header
//                    replace: 'append'
                }),
                table_rows: new m_renderer.template({
                    target: tbody,
                    template: m_templates.row
                }),
                pages: new m_renderer.template({
                    target: pages,
                    template: m_templates.pages
                })
            };

            /*
             * The data sources which make requests and handles the body of the
             * response.
             *
             * ``table_header`` - requests the latest header data.
             *
             * ``table_rows`` - requests the latest results for the report in
             * view.
             */
            var src = {
                scope_info: new m_datasource.ajax({
                    uri: API_URLS.scope,
                    success: function(json) {
                        if (json.text) {
                            var t = '';
                            if (typeof json.text === 'string') {
                                t = '
' + json.text + '
';
                            } else {
                                var conditions = json.text.conditions;
                                for (var i=0; i < conditions.length; i++)
                                    t += '
' + conditions[i] + '
';
                            }
                            conditionsText.html('
' + t + '
');
                        }
                    }
                }),

                table_header: new m_datasource.ajax({
                    uri: API_URLS.perspective,
                    success: function(json) {
//                        rnd.table_header.target.html('<input type="checkbox">	');
                        rnd.table_header.render(json.header);
                    }
                }),

                table_rows: new m_datasource.ajax({
                    uri: API_URLS.report,
                    success: function(json) {
                        if (json.rows.length === 0) {
                            $('.content').html(EMPTY_RESULTS);
                            return;
                        }

                        rnd.table_rows.render(json.rows);

                        if (json.pages) {
                            rnd.pages.render(json.pages);
                        } else {
                            if (firstRequest)
                                per_page.hide();
                            rnd.pages.target.hide();
                        }

                        if (json.unique)
                            unique.html(json.unique);

                        if (json.count)
                            count.html(json.count);

                        if (json.per_page)
                            per_page.val(json.per_page);

                        report.trigger('resize-report');

                        /*
                         * Unhide necessary elements on first request.
                         */
                        if (firstRequest) {
                            firstRequest = false;
                            setTimeout(function() {
                                info.show();
                                toolbars.show();
                                table.slideDown();
                            }, 300);
                        }
                    }
                })
            };

            /*
             * Make initial requests on initialization.
             */
            src.scope_info.get();
            src.table_header.get();
            src.table_rows.get();

            report.bind('resize-report', function(evt) {
                var minWidth = 900,
                    rInnerWidth = report.innerWidth(),
                    tOuterWidth = table.outerWidth(true)+20; // padding is not usable

                // do not grow past window width. take into account the padding
                nInnerWidth = Math.min(tOuterWidth, window.innerWidth-30);
                nInnerWidth = Math.max(minWidth, nInnerWidth);

                // determine the difference. half of it needs to be added
                // to the margin-right offset
                if (nInnerWidth == rInnerWidth)
                    return;

                half = (nInnerWidth - rInnerWidth) / 2.0;

                nLeft = parseInt(content.css('margin-left').match(/-?\d+/)[0]) - half;

                content.animate({
                    marginLeft: nLeft
                });

                report.animate({
                    width: nInnerWidth
                });
            });

            var resizeTimeOut;
            $(window).resize(function() {
                clearTimeout(resizeTimeOut);
                resizeTimeOut = setTimeout(function() {
                    report.trigger('resize-report');
                }, 200);
            });

             /*
             * Define primary event that handles fetching data.
             */
            body.bind('update.report', function(evt, params) {
                src.table_rows.get(params);
                return false;
            });

            body.bind('update.perspective', function(evt, params) {
                $.putJSON(API_URLS.perspective, JSON.stringify(params), function() {
                    body.trigger('update.report');
                    if ('columns' in params)
                        src.table_header.get();
                });
                return false;
            });

            /*
             * Hook up the elements that change the number of rows per page.
             */
            report.delegate('.per-page', 'change', function(evt) {
                if (this.value)
                    body.trigger('update.report', {'n': this.value});
                return false;
            });

            /*
             * This implementation does *not* currently support multiple column
             * sorting. The only change needed is to grab all of the header
             * directions and pass it to the server.
             */
            thead.delegate('.header', 'click', function(evt) {
                var dir, target = $(this), id = target.attr('data-id'),
                    siblings = target.siblings();

                // reset siblings to no ordering
                siblings.removeClass('asc').removeClass('desc');

                if (target.hasClass('asc')) {
                    target.removeClass('asc').addClass('desc');
                    dir = 'desc';
                } else if (target.hasClass('desc')) {
                    target.removeClass('desc');
                    dir = '';
                } else {
                    target.addClass('asc');
                    dir = 'asc';
                }

                body.trigger('update.perspective', [{'ordering': [[id, dir]]}]);
                return false;
            });

            /*
             * Hook up the elements that change the page.
             */
            pages.delegate('a', 'click', function(evt) {
                body.trigger('update.report', {'p': this.hash.substr(1)});
                return false;
            });
        };

        return {init: init};
    }
);
<|endoftext|>


<commit_before>'use strict';

const campaignFields = `
  campaign {
    id
    endDate
    internalTitle
  }
`;

const fetchConversationTriggers = `
  {
    conversationTriggers {
      trigger
      reply
      topic {
        id
        ... on AutoReplySignupTopic {
          campaign {
            id
            endDate
          }
        }
        ... on PhotoPostTopic {
          campaign {
            id
            endDate
          }
        }
        ... on TextPostTopic {
          campaign {
            id
            endDate
          }
        }
      }
    }
  }
`;

const fetchTopicById = `
  query getTopicById($id: String!) {
    topic(id: $id) {
      id
      contentType
      ... on AskYesNoBroadcastTopic {
        invalidAskYesNoResponse
        saidNo
        saidNoTopic {
          id
        }
        saidYes
        saidYesTopic {
          id
          ...autoReplySignupCampaign
          ...photoPostCampaign
          ...textPostCampaign
        }
      }
      ... on AutoReplySignupTopic {
        ...autoReplySignupCampaign
        autoReply
      }
      ... on AutoReplyTopic {
        autoReply
      }
      ... on PhotoPostTopic {
        ...photoPostCampaign
        askCaption
        askPhoto
        askQuantity
        askWhyParticipated
        invalidCaption
        invalidPhoto
        invalidQuantity
        invalidWhyParticipated
        completedPhotoPost
        completedPhotoPostAutoReply
        startPhotoPostAutoReply
      }
      ... on TextPostTopic {
        ...textPostCampaign
        invalidText
        completedTextPost
      }
    }
  }
  fragment autoReplySignupCampaign on AutoReplySignupTopic {
    ${campaignFields}
  }
  fragment photoPostCampaign on PhotoPostTopic {
    ${campaignFields}
  }
  fragment textPostCampaign on TextPostTopic {
    ${campaignFields}
  }
`;

module.exports = {
  queries: {
    fetchConversationTriggers,
    fetchTopicById,
  },
  url: process.env.DS_GRAPHQL_API_BASEURI,
};
<commit_msg>Cleanup - DRY<commit_after>'use strict';

const campaignFields = `
  campaign {
    id
    endDate
    internalTitle
  }
`;

const fetchConversationTriggers = `
  {
    conversationTriggers {
      trigger
      reply
      topic {
        id
        ... on AutoReplySignupTopic {
          ${campaignFields}
        }
        ... on PhotoPostTopic {
          ${campaignFields}
        }
        ... on TextPostTopic {
          ${campaignFields}
        }
      }
    }
  }
`;

const fetchTopicById = `
  query getTopicById($id: String!) {
    topic(id: $id) {
      id
      contentType
      ... on AskYesNoBroadcastTopic {
        invalidAskYesNoResponse
        saidNo
        saidNoTopic {
          id
        }
        saidYes
        saidYesTopic {
          id
          ...autoReplySignupCampaign
          ...photoPostCampaign
          ...textPostCampaign
        }
      }
      ... on AutoReplySignupTopic {
        ...autoReplySignupCampaign
        autoReply
      }
      ... on AutoReplyTopic {
        autoReply
      }
      ... on PhotoPostTopic {
        ...photoPostCampaign
        askCaption
        askPhoto
        askQuantity
        askWhyParticipated
        invalidCaption
        invalidPhoto
        invalidQuantity
        invalidWhyParticipated
        completedPhotoPost
        completedPhotoPostAutoReply
        startPhotoPostAutoReply
      }
      ... on TextPostTopic {
        ...textPostCampaign
        invalidText
        completedTextPost
      }
    }
  }
  fragment autoReplySignupCampaign on AutoReplySignupTopic {
    ${campaignFields}
  }
  fragment photoPostCampaign on PhotoPostTopic {
    ${campaignFields}
  }
  fragment textPostCampaign on TextPostTopic {
    ${campaignFields}
  }
`;

module.exports = {
  queries: {
    fetchConversationTriggers,
    fetchTopicById,
  },
  url: process.env.DS_GRAPHQL_API_BASEURI,
};
<|endoftext|>


<commit_before>(function($) {
	$.fn.actions = function(opts) {
		var options = $.extend({}, $.fn.actions.defaults, opts);
		var actionCheckboxes = $(this);
		var list_editable_changed = false;
		var checker = function(checked) {
			if (checked) {
				showQuestion();
			} else {
				reset();
			}
			$(actionCheckboxes).attr("checked", checked)
				.parent().parent().toggleClass(options.selectedClass, checked);
		},
		updateCounter = function() {
			var sel = $(actionCheckboxes).filter(":checked").length;
			$(options.counterContainer).html(interpolate(
			ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
				sel: sel,
				cnt: _actions_icnt
			}, true));
			$(options.allToggle).attr("checked", function() {
				if (sel == actionCheckboxes.length) {
					value = true;
					showQuestion();
				} else {
					value = false;
					clearAcross();
				}
				return value;
			});
		},
		showQuestion = function() {
			$(options.acrossClears).hide();
			$(options.acrossQuestions).show();
			$(options.allContainer).hide();
		},
		showClear = function() {
			$(options.acrossClears).show();
			$(options.acrossQuestions).hide();
			$(options.actionContainer).toggleClass(options.selectedClass);
			$(options.allContainer).show();
			$(options.counterContainer).hide();
		},
		reset = function() {
			$(options.acrossClears).hide();
			$(options.acrossQuestions).hide();
			$(options.allContainer).hide();
			$(options.counterContainer).show();
		},
		clearAcross = function() {
			reset();
			$(options.acrossInput).val(0);
			$(options.actionContainer).removeClass(options.selectedClass);
		};
		// Show counter by default
		$(options.counterContainer).show();
		// Check state of checkboxes and reinit state if needed
		$(this).filter(":checked").each(function(i) {
			$(this).parent().parent().toggleClass(options.selectedClass);
			updateCounter();
			if ($(options.acrossInput).val() == 1) {
				showClear();
			}
		});
		$(options.allToggle).show().click(function() {
			checker($(this).attr("checked"));
			updateCounter();
		});
		$("div.actions span.question a").click(function(event) {
			event.preventDefault();
			$(options.acrossInput).val(1);
			showClear();
		});
		$("div.actions span.clear a").click(function(event) {
			event.preventDefault();
			$(options.allToggle).attr("checked", false);
			clearAcross();
			checker(0);
			updateCounter();
		});
		lastChecked = null;
		$(actionCheckboxes).click(function(event) {
			if (!event) { event = window.event; }
			var target = event.target ? event.target : event.srcElement;
			if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) {
				var inrange = false;
				$(lastChecked).attr("checked", target.checked)
					.parent().parent().toggleClass(options.selectedClass, target.checked);
				$(actionCheckboxes).each(function() {
					if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) {
						inrange = (inrange) ? false : true;
					}
					if (inrange) {
						$(this).attr("checked", target.checked)
							.parent().parent().toggleClass(options.selectedClass, target.checked);
					}
				});
			}
			$(target).parent().parent().toggleClass(options.selectedClass, target.checked);
			lastChecked = target;
			updateCounter();
		});
		$('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() {
			list_editable_changed = true;
		});
		$('form#changelist-form button[name="index"]').click(function(event) {
			if (list_editable_changed) {
				return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
			}
		});
		$('form#changelist-form input[name="_save"]').click(function(event) {
			var action_changed = false;
			$('div.actions select option:selected').each(function() {
				if ($(this).val()) {
					action_changed = true;
				}
			});
			if (action_changed) {
				if (list_editable_changed) {
					return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action."));
				} else {
					return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."));
				}
			}
		});
	}
	/* Setup plugin defaults */
	$.fn.actions.defaults = {
		actionContainer: "div.actions",
		counterContainer: "span.action-counter",
		allContainer: "div.actions span.all",
		acrossInput: "div.actions input.select-across",
		acrossQuestions: "div.actions span.question",
		acrossClears: "div.actions span.clear",
		allToggle: "#action-toggle",
		selectedClass: "selected"
	}
})(django.jQuery);
<commit_msg>Add missing semi-colons<commit_after>(function($) {
	$.fn.actions = function(opts) {
		var options = $.extend({}, $.fn.actions.defaults, opts);
		var actionCheckboxes = $(this);
		var list_editable_changed = false;
		var checker = function(checked) {
			if (checked) {
				showQuestion();
			} else {
				reset();
			}
			$(actionCheckboxes).attr("checked", checked)
				.parent().parent().toggleClass(options.selectedClass, checked);
		},
		updateCounter = function() {
			var sel = $(actionCheckboxes).filter(":checked").length;
			$(options.counterContainer).html(interpolate(
			ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), {
				sel: sel,
				cnt: _actions_icnt
			}, true));
			$(options.allToggle).attr("checked", function() {
				if (sel == actionCheckboxes.length) {
					value = true;
					showQuestion();
				} else {
					value = false;
					clearAcross();
				}
				return value;
			});
		},
		showQuestion = function() {
			$(options.acrossClears).hide();
			$(options.acrossQuestions).show();
			$(options.allContainer).hide();
		},
		showClear = function() {
			$(options.acrossClears).show();
			$(options.acrossQuestions).hide();
			$(options.actionContainer).toggleClass(options.selectedClass);
			$(options.allContainer).show();
			$(options.counterContainer).hide();
		},
		reset = function() {
			$(options.acrossClears).hide();
			$(options.acrossQuestions).hide();
			$(options.allContainer).hide();
			$(options.counterContainer).show();
		},
		clearAcross = function() {
			reset();
			$(options.acrossInput).val(0);
			$(options.actionContainer).removeClass(options.selectedClass);
		};
		// Show counter by default
		$(options.counterContainer).show();
		// Check state of checkboxes and reinit state if needed
		$(this).filter(":checked").each(function(i) {
			$(this).parent().parent().toggleClass(options.selectedClass);
			updateCounter();
			if ($(options.acrossInput).val() == 1) {
				showClear();
			}
		});
		$(options.allToggle).show().click(function() {
			checker($(this).attr("checked"));
			updateCounter();
		});
		$("div.actions span.question a").click(function(event) {
			event.preventDefault();
			$(options.acrossInput).val(1);
			showClear();
		});
		$("div.actions span.clear a").click(function(event) {
			event.preventDefault();
			$(options.allToggle).attr("checked", false);
			clearAcross();
			checker(0);
			updateCounter();
		});
		lastChecked = null;
		$(actionCheckboxes).click(function(event) {
			if (!event) { event = window.event; }
			var target = event.target ? event.target : event.srcElement;
			if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey == true) {
				var inrange = false;
				$(lastChecked).attr("checked", target.checked)
					.parent().parent().toggleClass(options.selectedClass, target.checked);
				$(actionCheckboxes).each(function() {
					if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) {
						inrange = (inrange) ? false : true;
					}
					if (inrange) {
						$(this).attr("checked", target.checked)
							.parent().parent().toggleClass(options.selectedClass, target.checked);
					}
				});
			}
			$(target).parent().parent().toggleClass(options.selectedClass, target.checked);
			lastChecked = target;
			updateCounter();
		});
		$('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() {
			list_editable_changed = true;
		});
		$('form#changelist-form button[name="index"]').click(function(event) {
			if (list_editable_changed) {
				return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
			}
		});
		$('form#changelist-form input[name="_save"]').click(function(event) {
			var action_changed = false;
			$('div.actions select option:selected').each(function() {
				if ($(this).val()) {
					action_changed = true;
				}
			});
			if (action_changed) {
				if (list_editable_changed) {
					return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action."));
				} else {
					return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."));
				}
			}
		});
	};
	/* Setup plugin defaults */
	$.fn.actions.defaults = {
		actionContainer: "div.actions",
		counterContainer: "span.action-counter",
		allContainer: "div.actions span.all",
		acrossInput: "div.actions input.select-across",
		acrossQuestions: "div.actions span.question",
		acrossClears: "div.actions span.clear",
		allToggle: "#action-toggle",
		selectedClass: "selected"
	};
})(django.jQuery);
<|endoftext|>


<commit_before>var cronJob = require('cron').CronJob;
var fs = require('fs');

var alreadyRead = 0;

// Confirm this line is whether end of file or not
function isEnd(line) {
	if (line.trim() == '') {
		return true;
	}
	else {
		return false;
	}
}

// Initialization of buffer of null character
function initBuffer(buf, size) {
	for (var i = 0; i < size; i++) {
		buf[i] = '\0';
	}
}

function Oranch(options) {
	var self        = this;
	self.schedule   = options.schedule;
	self.logfile    = options.logfile;
	self.match      = options.match;
	self.task       = options.task;
	self.bufSize    = options.bufSize ? options.bufSize : 1000;
	self.onComplete = options.onComplete;
	self.job        = new cronJob(self.schedule, grabLog, self.onComplete, false);
	self.job.oranch = self;
}

Oranch.prototype.start = function() {
	var self = this;
	self.job.start();
}

Oranch.prototype.stop = function() {
	var self = this;
	self.job.stop();
}

function grabLog() {
	var self    = this;
	var oranch  = self.oranch;
	var logfile = oranch.logfile;
	var match   = oranch.match;
	var task    = oranch.task;
	var bufSize = oranch.bufSize;
    fs.open(logfile, 'r', function(err, fd) {
		if (err) {
			return err;
		}
		var buf = new Buffer(bufSize);
		initBuffer(buf, bufSize);
		var offset = 0;
		fs.read(fd, buf, offset, bufSize, alreadyRead, function(err, bytesRead, buffer) {
			var lines = buffer.toString().split('\n');
			for( var lineIndex = 0; lineIndex < lines.length-1; lineIndex++ ) {
				var curLine = lines[lineIndex];
				if (isEnd(curLine)) {
					break;
				}
				if (curLine.match(match)) {
					task(curLine);
				}
				alreadyRead += lines[lineIndex].length + 1;
			}
		});
	});
}

exports.Oranch = Oranch;

<commit_msg>Default matching<commit_after>var cronJob = require('cron').CronJob;
var fs = require('fs');

var alreadyRead = 0;

// Confirm this line is whether end of file or not
function isEnd(line) {
	if (line.trim() == '') {
		return true;
	}
	else {
		return false;
	}
}

// Initialization of buffer of null character
function initBuffer(buf, size) {
	for (var i = 0; i < size; i++) {
		buf[i] = '\0';
	}
}

function Oranch(options) {
	var self        = this;
	self.schedule   = options.schedule;
	self.logfile    = options.logfile;
	self.match      = options.match ? options.match : /.*/;
	self.task       = options.task;
	self.bufSize    = options.bufSize ? options.bufSize : 1000;
	self.onComplete = options.onComplete;
	self.job        = new cronJob(self.schedule, grabLog, self.onComplete, false);
	self.job.oranch = self;
}

Oranch.prototype.start = function() {
	var self = this;
	self.job.start();
}

Oranch.prototype.stop = function() {
	var self = this;
	self.job.stop();
}

function grabLog() {
	var self    = this;
	var oranch  = self.oranch;
	var logfile = oranch.logfile;
	var match   = oranch.match;
	var task    = oranch.task;
	var bufSize = oranch.bufSize;
    fs.open(logfile, 'r', function(err, fd) {
		if (err) {
			return err;
		}
		var buf = new Buffer(bufSize);
		initBuffer(buf, bufSize);
		var offset = 0;
		fs.read(fd, buf, offset, bufSize, alreadyRead, function(err, bytesRead, buffer) {
			var lines = buffer.toString().split('\n');
			for( var lineIndex = 0; lineIndex < lines.length-1; lineIndex++ ) {
				var curLine = lines[lineIndex];
				if (isEnd(curLine)) {
					break;
				}
				if (curLine.match(match)) {
					task(curLine);
				}
				alreadyRead += lines[lineIndex].length + 1;
			}
		});
	});
}

exports.Oranch = Oranch;

<|endoftext|>


<commit_before>'use strict';

var docpadConfig;

docpadConfig = {
  poweredByDocPad: true,
  maxAge: 3600000,
  ignorePaths: ['/ignore'],
  templateData: {
    site: {
      title: "JTWebMan",
      url: "http://www.jtwebman.com"
    },
    getPreparedTitle: function () {
      if (this.document.title) {
        return this.document.title + " | " + this.site.title;
      }
      return this.site.title;
    },
    getCuttedContent: function (content) {
      var i = content.search('<!-- Read more -->');
      if (i >= 0) {
        return content.slice(0, +(i - 1) + 1 || 9e9);
      }
      return content;
    },
    hasReadMore: function (content) {
      return content.search('<!-- Read more -->') >= 0;
    },
    getProject: function (projectTitle) {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'project' },
        title: { $has: projectTitle }
      }, [ { sort: 1 } ]).toJSON()[0];
    },
    getProjectsDetails: function (projectTitle) {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'project-detail' },
        project: { $has: projectTitle }
      }, [ { sort: 1 } ]).toJSON();
    }
  },
  collections: {
    pages: function () {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'page' }
      }, [
        { sort: 1 }
      ]);
    },
    posts: function () {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'post' }
      }, [
        { date: -1 }
      ]);
    },
    projects: function () {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'project' }
      }, [
        { sort: 1 }
      ]);
    }
  },
  plugins: {
    cleanurls: {
      trailingSlashes: true
    },
    grunt: {
      warnOnError: true
    },
    sitemap: {
      cachetime: 600000,
      changefreq: 'daily',
      priority: 0.5,
      filePath: 'sitemap.xml'
    }
  }
};

module.exports = docpadConfig;
<commit_msg>Fixed blog in nav twice<commit_after>'use strict';

var docpadConfig;

docpadConfig = {
  poweredByDocPad: true,
  maxAge: 3600000,
  ignorePaths: ['/ignore'],
  templateData: {
    site: {
      title: "JTWebMan",
      url: "http://www.jtwebman.com"
    },
    getPreparedTitle: function () {
      if (this.document.title) {
        return this.document.title + " | " + this.site.title;
      }
      return this.site.title;
    },
    getCuttedContent: function (content) {
      var i = content.search('<!-- Read more -->');
      if (i >= 0) {
        return content.slice(0, +(i - 1) + 1 || 9e9);
      }
      return content;
    },
    hasReadMore: function (content) {
      return content.search('<!-- Read more -->') >= 0;
    },
    getProject: function (projectTitle) {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'project' },
        title: { $has: projectTitle }
      }, [ { sort: 1 } ]).toJSON()[0];
    },
    getProjectsDetails: function (projectTitle) {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'project-detail' },
        project: { $has: projectTitle }
      }, [ { sort: 1 } ]).toJSON();
    }
  },
  collections: {
    pages: function () {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'page' },
        isPagedAuto: { $ne: true }
      }, [
        { sort: 1 }
      ]);
    },
    posts: function () {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'post' }
      }, [
        { date: -1 }
      ]);
    },
    projects: function () {
      return this.getCollection("html").findAllLive({
        layout: { $has: 'project' }
      }, [
        { sort: 1 }
      ]);
    }
  },
  plugins: {
    cleanurls: {
      trailingSlashes: true
    },
    grunt: {
      warnOnError: true
    },
    sitemap: {
      cachetime: 600000,
      changefreq: 'daily',
      priority: 0.5,
      filePath: 'sitemap.xml'
    }
  }
};

module.exports = docpadConfig;
<|endoftext|>


<commit_before>module.exports = {

	// These are variables will be accessible via our templates

	templateData: {
		authors: require("./authors.js"),

		site: {
			title: "",
			description: "Um lugar para discutir sobre as melhores práticas de desenvolvimento",
			url: "http://devsnorte.com.br",
			image: "http://devsnorte.com.br/images/logo.png"
		},

		github: {
			user: "pvhjs",
			repo: "devsnorte.com.br"
		},

		/* Helpers
		===================================================================== */
		getAuthor: function(author) {
			if(author)
				return this.authors[author];

			return this.authors["Comunidade"];
		},

		getAuthorImage: function(author) {
			if(this.authors[author].gravatar)
				return "https://2.gravatar.com/avatar/" + this.authors[authors].gravatar;

			return this.authors[author].image;
		},

		getAuthorGooglePlus: function(author) {
			return "https://plus.google.com/" + this.authors[author].gplus + "/";
		},

		getAuthorTwitter: function(author) {
			return "https://twitter.com/" + this.authors[author].twitter;
		},

		getGitgubUrl: function() {
			return "https://github.com/" + this.github.user + "/" + this.github.repo;
		},

		getGithubEditUrl: function(path) {
			return this.getGithubUrl() + "/edit/master/src/documents/" + path;
		},

		getTitle: function() {
			if(this.document.title)
				return "" + this.document.title + "—" + this.site.title;

			return "" + this.site.title;
		},

		getDescription: function() {
			if(this.document.description)
				return this.document.description;

			return this.site.description;
		},

		getImage: function() {
			if(this.document.image)
				return "" + this.site.url + "/images/stories/" + this.document.image;

			return this.site.image;
		},

		/* Development Environment
		===================================================================== */
		environments: {
			development: {
				templateData: {
					site: {
						url: "http://localhost:9778"
					}
				}
			}
		},

		/* Collections
		===================================================================== */
		collections: {

		},

		/* Plugins Configurations
		===================================================================== */
		plugins: {

		}
	}
}
<commit_msg>Adiciona titulo do site e configuração do grunt<commit_after>module.exports = {

	// These are variables will be accessible via our templates

	templateData: {
		authors: require("./authors.js"),

		site: {
			title: "DevsNorte",
			description: "Um lugar para discutir sobre as melhores práticas de desenvolvimento",
			url: "http://devsnorte.com.br",
			image: "http://devsnorte.com.br/images/logo.png"
		},

		github: {
			user: "pvhjs",
			repo: "devsnorte.com.br"
		},

		/* Helpers
		===================================================================== */
		getAuthor: function(author) {
			if(author)
				return this.authors[author];

			return this.authors["Comunidade"];
		},

		getAuthorImage: function(author) {
			if(this.authors[author].gravatar)
				return "https://2.gravatar.com/avatar/" + this.authors[authors].gravatar;

			return this.authors[author].image;
		},

		getAuthorGooglePlus: function(author) {
			return "https://plus.google.com/" + this.authors[author].gplus + "/";
		},

		getAuthorTwitter: function(author) {
			return "https://twitter.com/" + this.authors[author].twitter;
		},

		getGitgubUrl: function() {
			return "https://github.com/" + this.github.user + "/" + this.github.repo;
		},

		getGithubEditUrl: function(path) {
			return this.getGithubUrl() + "/edit/master/src/documents/" + path;
		},

		getTitle: function() {
			if(this.document.title)
				return "" + this.document.title + "—" + this.site.title;

			return "" + this.site.title;
		},

		getDescription: function() {
			if(this.document.description)
				return this.document.description;

			return this.site.description;
		},

		getImage: function() {
			if(this.document.image)
				return "" + this.site.url + "/images/stories/" + this.document.image;

			return this.site.image;
		},

		/* Development Environment
		===================================================================== */
		environments: {
			development: {
				templateData: {
					site: {
						url: "http://localhost:9778"
					}
				}
			}
		},

		/* Collections
		===================================================================== */
		collections: {

		},

		/* Plugins Configurations
		===================================================================== */
		plugins: {
			grunt: {
				writeAfter: false,
				generateAfter: ["cssmin"]
			}
		}
	}
}
<|endoftext|>


<commit_before>// @flow
import BigNumber from 'bignumber.js';
import { get } from 'lodash';
import AdaApi from '../api';
import { getNodeInfo } from '../nodes/requests/getNodeInfo';
import { GenericApiError } from '../common/errors';
import { Logger } from '../../utils/logging';
import { RedeemAdaError } from '../transactions/errors';
import type { RedeemAdaParams } from '../transactions/requests/redeemAda';
import type { RedeemPaperVendedAdaParams } from '../transactions/requests/redeemPaperVendedAda';
import type { NodeQueryParams } from '../nodes/requests/getNodeInfo';
import type { NodeInfo, GetNetworkStatusResponse } from '../nodes/types';
import { stringifyData, stringifyError } from '../../../../common/utils/logging';

// ========== LOGGING =========

let LOCAL_TIME_DIFFERENCE = 0;
let NEXT_ADA_UPDATE = null;

export default (api: AdaApi) => {
  // Since we cannot test ada redemption in dev mode, just resolve the requests
  api.redeemAda = (
    request: RedeemAdaParams
  ): Promise<any> => new Promise((resolve) => {
    try {
      Logger.debug('AdaApi::redeemAda (PATCHED) called', { request });
      const { redemptionCode } = request;
      const isValidRedemptionCode = api.isValidRedemptionKey(redemptionCode);
      if (!isValidRedemptionCode) {
        Logger.debug('AdaApi::redeemAda (PATCHED) failed: not a valid redemption key!');
        throw new RedeemAdaError();
      }
      Logger.debug('AdaApi::redeemAda (PATCHED) success');
      resolve({ amount: new BigNumber(1000) });
    } catch (error) {
      Logger.debug('AdaApi::redeemAda (PATCHED) error', { error: `${stringifyError(error)}` });
      throw new RedeemAdaError();
    }
  });

  api.redeemPaperVendedAda = (
    request: RedeemPaperVendedAdaParams
  ): Promise<any> => new Promise((resolve) => {
    try {
      Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) called', { request });
      const { redemptionCode, mnemonics } = request;
      const isValidKey = api.isValidPaperVendRedemptionKey(redemptionCode);
      const isValidMnemonic = api.isValidRedemptionMnemonic(mnemonics.join(' '));
      if (!isValidKey) Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) failed: not a valid redemption key!');
      if (!isValidMnemonic) Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) failed: not a valid mnemonic!');
      if (!isValidKey || !isValidMnemonic) {
        throw new RedeemAdaError();
      }
      Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) success');
      resolve({ amount: new BigNumber(1000) });
    } catch (error) {
      Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) error', { error: `${stringifyError(error)}` });
      throw new RedeemAdaError();
    }
  });

  api.getLocalTimeDifference = async () => (
    Promise.resolve(LOCAL_TIME_DIFFERENCE)
  );

  api.getNetworkStatus = async (
    queryParams?: NodeQueryParams
  ): Promise<GetNetworkStatusResponse> => {
    Logger.debug('AdaApi::getNetworkStatus (PATCHED) called');
    try {
      const status: NodeInfo = await getNodeInfo(api.config, queryParams);
      Logger.debug('AdaApi::getNetworkStatus (PATCHED) success', { status });

      const {
        blockchainHeight,
        subscriptionStatus,
        syncProgress,
        localBlockchainHeight,
      } = status;

      // extract relevant data before sending to NetworkStatusStore
      return {
        subscriptionStatus,
        syncProgress: syncProgress.quantity,
        blockchainHeight: get(blockchainHeight, 'quantity', 0),
        localBlockchainHeight: localBlockchainHeight.quantity,
        localTimeDifference: LOCAL_TIME_DIFFERENCE,
      };
    } catch (error) {
      Logger.error('AdaApi::getNetworkStatus (PATCHED) error', { error: `${stringifyError(error)}` });
      throw new GenericApiError();
    }
  };

  api.setLocalTimeDifference = async (timeDifference) => {
    LOCAL_TIME_DIFFERENCE = timeDifference;
  };

  api.nextUpdate = async () => (
    Promise.resolve(NEXT_ADA_UPDATE)
  );

  api.setNextUpdate = async (nextUpdate) => {
    NEXT_ADA_UPDATE = nextUpdate;
  };
};
<commit_msg>[DDW-517] Fixes ESlint warning<commit_after>// @flow
import BigNumber from 'bignumber.js';
import { get } from 'lodash';
import AdaApi from '../api';
import { getNodeInfo } from '../nodes/requests/getNodeInfo';
import { GenericApiError } from '../common/errors';
import { Logger } from '../../utils/logging';
import { RedeemAdaError } from '../transactions/errors';
import type { RedeemAdaParams } from '../transactions/requests/redeemAda';
import type { RedeemPaperVendedAdaParams } from '../transactions/requests/redeemPaperVendedAda';
import type { NodeQueryParams } from '../nodes/requests/getNodeInfo';
import type { NodeInfo, GetNetworkStatusResponse } from '../nodes/types';
import { stringifyError } from '../../../../common/utils/logging';

// ========== LOGGING =========

let LOCAL_TIME_DIFFERENCE = 0;
let NEXT_ADA_UPDATE = null;

export default (api: AdaApi) => {
  // Since we cannot test ada redemption in dev mode, just resolve the requests
  api.redeemAda = (
    request: RedeemAdaParams
  ): Promise<any> => new Promise((resolve) => {
    try {
      Logger.debug('AdaApi::redeemAda (PATCHED) called', { request });
      const { redemptionCode } = request;
      const isValidRedemptionCode = api.isValidRedemptionKey(redemptionCode);
      if (!isValidRedemptionCode) {
        Logger.debug('AdaApi::redeemAda (PATCHED) failed: not a valid redemption key!');
        throw new RedeemAdaError();
      }
      Logger.debug('AdaApi::redeemAda (PATCHED) success');
      resolve({ amount: new BigNumber(1000) });
    } catch (error) {
      Logger.debug('AdaApi::redeemAda (PATCHED) error', { error: `${stringifyError(error)}` });
      throw new RedeemAdaError();
    }
  });

  api.redeemPaperVendedAda = (
    request: RedeemPaperVendedAdaParams
  ): Promise<any> => new Promise((resolve) => {
    try {
      Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) called', { request });
      const { redemptionCode, mnemonics } = request;
      const isValidKey = api.isValidPaperVendRedemptionKey(redemptionCode);
      const isValidMnemonic = api.isValidRedemptionMnemonic(mnemonics.join(' '));
      if (!isValidKey) Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) failed: not a valid redemption key!');
      if (!isValidMnemonic) Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) failed: not a valid mnemonic!');
      if (!isValidKey || !isValidMnemonic) {
        throw new RedeemAdaError();
      }
      Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) success');
      resolve({ amount: new BigNumber(1000) });
    } catch (error) {
      Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) error', { error: `${stringifyError(error)}` });
      throw new RedeemAdaError();
    }
  });

  api.getLocalTimeDifference = async () => (
    Promise.resolve(LOCAL_TIME_DIFFERENCE)
  );

  api.getNetworkStatus = async (
    queryParams?: NodeQueryParams
  ): Promise<GetNetworkStatusResponse> => {
    Logger.debug('AdaApi::getNetworkStatus (PATCHED) called');
    try {
      const status: NodeInfo = await getNodeInfo(api.config, queryParams);
      Logger.debug('AdaApi::getNetworkStatus (PATCHED) success', { status });

      const {
        blockchainHeight,
        subscriptionStatus,
        syncProgress,
        localBlockchainHeight,
      } = status;

      // extract relevant data before sending to NetworkStatusStore
      return {
        subscriptionStatus,
        syncProgress: syncProgress.quantity,
        blockchainHeight: get(blockchainHeight, 'quantity', 0),
        localBlockchainHeight: localBlockchainHeight.quantity,
        localTimeDifference: LOCAL_TIME_DIFFERENCE,
      };
    } catch (error) {
      Logger.error('AdaApi::getNetworkStatus (PATCHED) error', { error: `${stringifyError(error)}` });
      throw new GenericApiError();
    }
  };

  api.setLocalTimeDifference = async (timeDifference) => {
    LOCAL_TIME_DIFFERENCE = timeDifference;
  };

  api.nextUpdate = async () => (
    Promise.resolve(NEXT_ADA_UPDATE)
  );

  api.setNextUpdate = async (nextUpdate) => {
    NEXT_ADA_UPDATE = nextUpdate;
  };
};
<|endoftext|>


<commit_before>import BigNumber from 'bignumber.js';
import { Logger } from '../../../../common/logging';
import { RedeemAdaError } from '../errors';
import AdaApi from '../api';
import type {
  RedeemAdaParams,
  RedeemPaperVendedAdaParams
} from '../types';

// ========== LOGGING =========

let LOCAL_TIME_DIFFERENCE = 0;
let NEXT_ADA_UPDATE = null;

const stringifyData = (data) => JSON.stringify(data, null, 2);

export default (api: AdaApi) => {
  // Since we cannot test ada redemption in dev mode, just resolve the requests
  api.redeemAda = (request: RedeemAdaParams) => {
    Logger.debug('AdaApi::redeemAda (PATCHED) called: ' + stringifyData(request));
    const { redemptionCode } = request;
    const isValidRedemptionCode = api.isValidRedemptionKey(redemptionCode);
    if (!isValidRedemptionCode) {
      Logger.debug('AdaApi::redeemAda failed: not a valid redemption key!');
      throw new RedeemAdaError();
    }
    return { amount: new BigNumber(1000) };
  };

  api.redeemPaperVendedAda = (request: RedeemPaperVendedAdaParams) => {
    Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) called: ' + stringifyData(request));
    const { shieldedRedemptionKey, mnemonics } = request;
    const isValidKey = api.isValidPaperVendRedemptionKey(shieldedRedemptionKey);
    const isValidMnemonic = api.isValidRedemptionMnemonic(mnemonics);
    if (!isValidKey) Logger.debug('AdaApi::redeemPaperVendedAda failed: not a valid redemption key!');
    if (!isValidMnemonic) Logger.debug('AdaApi::redeemPaperVendedAda failed: not a valid mnemonic!');
    if (!isValidKey || !isValidMnemonic) {
      throw new RedeemAdaError();
    }
    return { amount: new BigNumber(1000) };
  };

  api.getLocalTimeDifference = async () => (
    Promise.resolve(LOCAL_TIME_DIFFERENCE)
  );

  api.setLocalTimeDifference = async (timeDifference) => {
    LOCAL_TIME_DIFFERENCE = timeDifference;
  };

  api.nextUpdate = async () => (
    Promise.resolve(NEXT_ADA_UPDATE)
  );

  api.setNextUpdate = async (nextUpdate) => {
    NEXT_ADA_UPDATE = nextUpdate;
  };
};
<commit_msg>[DDW-321] Updates name of flow types in patchAdaApi.js<commit_after>import BigNumber from 'bignumber.js';
import AdaApi from '../api';
import { Logger } from '../../../../common/logging';
import { RedeemAdaError } from '../transactions/errors';
import type { RedeemAdaParams } from '../transactions/requests/redeemAda';
import type { RedeemPaperVendedAdaParams } from '../transactions/requests/redeemPaperVendedAda';

// ========== LOGGING =========

let LOCAL_TIME_DIFFERENCE = 0;
let NEXT_ADA_UPDATE = null;

const stringifyData = (data) => JSON.stringify(data, null, 2);

export default (api: AdaApi) => {
  // Since we cannot test ada redemption in dev mode, just resolve the requests
  api.redeemAda = (request: RedeemAdaParams) => {
    Logger.debug('AdaApi::redeemAda (PATCHED) called: ' + stringifyData(request));
    const { redemptionCode } = request;
    const isValidRedemptionCode = api.isValidRedemptionKey(redemptionCode);
    if (!isValidRedemptionCode) {
      Logger.debug('AdaApi::redeemAda failed: not a valid redemption key!');
      throw new RedeemAdaError();
    }
    return { amount: new BigNumber(1000) };
  };

  api.redeemPaperVendedAda = (request: RedeemPaperVendedAdaParams) => {
    Logger.debug('AdaApi::redeemPaperVendedAda (PATCHED) called: ' + stringifyData(request));
    const { shieldedRedemptionKey, mnemonics } = request;
    const isValidKey = api.isValidPaperVendRedemptionKey(shieldedRedemptionKey);
    const isValidMnemonic = api.isValidRedemptionMnemonic(mnemonics);
    if (!isValidKey) Logger.debug('AdaApi::redeemPaperVendedAda failed: not a valid redemption key!');
    if (!isValidMnemonic) Logger.debug('AdaApi::redeemPaperVendedAda failed: not a valid mnemonic!');
    if (!isValidKey || !isValidMnemonic) {
      throw new RedeemAdaError();
    }
    return { amount: new BigNumber(1000) };
  };

  api.getLocalTimeDifference = async () => (
    Promise.resolve(LOCAL_TIME_DIFFERENCE)
  );

  api.setLocalTimeDifference = async (timeDifference) => {
    LOCAL_TIME_DIFFERENCE = timeDifference;
  };

  api.nextUpdate = async () => (
    Promise.resolve(NEXT_ADA_UPDATE)
  );

  api.setNextUpdate = async (nextUpdate) => {
    NEXT_ADA_UPDATE = nextUpdate;
  };
};
<|endoftext|>


<commit_before>import { Credentials, App } from 'realm-web'

const APP_ID = process.env.NEXT_PUBLIC_REALM_APP_ID
export const REALM_GRAPHQL_ENDPOINT = `https://realm.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`

const app = new App({
  id: APP_ID,
  baseUrl: 'https://realm.mongodb.com',
})

export const generateAuthHeader = async () => {
  if (!app.currentUser) {
    // If no user is logged in, log in an anonymous user
    await app.logIn(Credentials.anonymous())
  }
  // Get a valid access token for the current user
  const { accessToken } = app.currentUser

  // Set the Authorization header, preserving any other headers
  return {
    Authorization: `Bearer ${accessToken}`,
  }
}
<commit_msg>update with-realm-web example (#24241)<commit_after>import { Credentials, App } from 'realm-web'

const APP_ID = process.env.NEXT_PUBLIC_REALM_APP_ID
export const REALM_GRAPHQL_ENDPOINT = `https://realm.mongodb.com/api/client/v2.0/app/${APP_ID}/graphql`

const app = new App({
  id: APP_ID,
  baseUrl: 'https://realm.mongodb.com',
})

export const generateAuthHeader = async () => {
  if (!app.currentUser) {
    // If no user is logged in, log in an anonymous user
    await app.logIn(Credentials.anonymous())
  } else {
    // An already logged in user's access token might be stale. To guarantee that the token is
    // valid, we refresh the user's custom data which also refreshes their access token.
    await app.currentUser.refreshCustomData()
  }
  // Get a valid access token for the current user
  const { accessToken } = app.currentUser

  // Set the Authorization header, preserving any other headers
  return {
    Authorization: `Bearer ${accessToken}`,
  }
}
<|endoftext|>