<commit_before>var _id = 0,
    _toString = Object.prototype.toString;

var _ = {
    uniqueId: function() {
        return _id++;
    },

    exists: function(obj) {
        return obj !== null && obj !== undefined;
    },

    parseInt: function(num) {
        return parseInt(num, 10);
    },

    coerceToNum: function(val) {
        return _.isNumber(val) ? val : // Its a number!
                _.isString(val) ? (_.parseInt(val) || 0) : // Avoid NaN
                0; // Default to zero
    },

    toPx: function(num) {
        return num + 'px';
    },

    isElement: function(obj) {
        return !!(obj && obj.nodeType === 1);
    },

    isArray: Array.isArray || function(obj) {
        return _toString.call(obj) === '[object Array]';
    },

    // NodeList check. For our purposes, a node list
    // and an HTMLCollection are the same
    isNodeList: function(obj) {
        return obj instanceof NodeList || obj instanceof HTMLCollection;
    },

    // Window check
    isWindow: function(obj) {
        return obj && obj === obj.window;
    },

    // Flatten that also checks if value is a NodeList
    flatten: function(arr) {
        var result = [];

        var idx = 0, length = arr.length,
            value;
        for (; idx < length; idx++) {
            value = arr[idx];

            if (_.isArray(value) || _.isNodeList(value)) {
                result = result.concat(_.flatten(value));
            } else {
                result.push(value);
            }
        }

        return result;
    },

    // Concat flat for a single array of arrays
    concatFlat: (function(concat) {

        return function(nestedArrays) {
            return concat.apply([], nestedArrays);
        };

    }([].concat)),

    // No-context every; strip each()
    every: function(arr, iterator) {
        if (!_.exists(arr)) { return true; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (!iterator(value, idx)) { return false; }
        }

        return true;
    },

    // Faster extend; strip each()
    extend: function() {
        var args = arguments,
            obj = args[0],
            idx = 1, length = args.length;

        if (!obj) { return obj; }

        for (; idx < length; idx++) {
            var source = args[idx];
            if (source) {
                for (var prop in source) {
                    obj[prop] = source[prop];
                }
            }
        }

        return obj;
    },

    // Standard map
    map: function(arr, iterator) {
        var results = [];
        if (!arr) { return results; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            results.push(iterator(arr[idx], idx));
        }

        return results;
    },

    // Array-preserving map
    // http://jsperf.com/push-map-vs-index-replacement-map
    fastmap: function(arr, iterator) {
        if (!arr) { return []; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            arr[idx] = iterator(arr[idx], idx);
        }

        return arr;
    },

    filter: function(arr, iterator) {
        var results = [];
        if (!arr || !arr.length) { return results; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (iterator(arr[idx], idx)) {
                results.push(value);
            }
        }

        return results;
    },

    any: function(arr, iterator) {
        var result = false;
        if (!arr || !arr.length) { return result; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (result || (result = iterator(arr[idx], idx))) { break; }
        }

        return !!result;
    }
};

// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
var types = ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'],
    idx = types.length,
    generateCheck = function(name) {
        return function(obj) {
            return _toString.call(obj) === '[object ' + name + ']';
        };
    },
    name;
while (idx--) {
    name = types[idx];
    _['is' + name] = generateCheck(name);
}

// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
        return typeof obj === 'function';
    };
}

// Optimize `isString` if appropriate.
if (typeof ('') === 'string') {
    _.isString = function(obj) {
        return typeof obj === 'string';
    };
}

module.exports = _;
<commit_msg>Added default truthy test to _.filter()<commit_after>var _id = 0,
    _toString = Object.prototype.toString,
    _isTruthy = function(arg) { return !!arg; };

var _ = {
    uniqueId: function() {
        return _id++;
    },

    exists: function(obj) {
        return obj !== null && obj !== undefined;
    },

    parseInt: function(num) {
        return parseInt(num, 10);
    },

    coerceToNum: function(val) {
        return _.isNumber(val) ? val : // Its a number!
                _.isString(val) ? (_.parseInt(val) || 0) : // Avoid NaN
                0; // Default to zero
    },

    toPx: function(num) {
        return num + 'px';
    },

    isElement: function(obj) {
        return !!(obj && obj.nodeType === 1);
    },

    isArray: Array.isArray || function(obj) {
        return _toString.call(obj) === '[object Array]';
    },

    // NodeList check. For our purposes, a node list
    // and an HTMLCollection are the same
    isNodeList: function(obj) {
        return obj instanceof NodeList || obj instanceof HTMLCollection;
    },

    // Window check
    isWindow: function(obj) {
        return obj && obj === obj.window;
    },

    // Flatten that also checks if value is a NodeList
    flatten: function(arr) {
        var result = [];

        var idx = 0, length = arr.length,
            value;
        for (; idx < length; idx++) {
            value = arr[idx];

            if (_.isArray(value) || _.isNodeList(value)) {
                result = result.concat(_.flatten(value));
            } else {
                result.push(value);
            }
        }

        return result;
    },

    // Concat flat for a single array of arrays
    concatFlat: (function(concat) {

        return function(nestedArrays) {
            return concat.apply([], nestedArrays);
        };

    }([].concat)),

    // No-context every; strip each()
    every: function(arr, iterator) {
        if (!_.exists(arr)) { return true; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (!iterator(value, idx)) { return false; }
        }

        return true;
    },

    // Faster extend; strip each()
    extend: function() {
        var args = arguments,
            obj = args[0],
            idx = 1, length = args.length;

        if (!obj) { return obj; }

        for (; idx < length; idx++) {
            var source = args[idx];
            if (source) {
                for (var prop in source) {
                    obj[prop] = source[prop];
                }
            }
        }

        return obj;
    },

    // Standard map
    map: function(arr, iterator) {
        var results = [];
        if (!arr) { return results; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            results.push(iterator(arr[idx], idx));
        }

        return results;
    },

    // Array-preserving map
    // http://jsperf.com/push-map-vs-index-replacement-map
    fastmap: function(arr, iterator) {
        if (!arr) { return []; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            arr[idx] = iterator(arr[idx], idx);
        }

        return arr;
    },

    filter: function(arr, iterator) {
        var results = [];
        if (!arr || !arr.length) { return results; }
        iterator = iterator || _isTruthy;

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (iterator(arr[idx], idx)) {
                results.push(value);
            }
        }

        return results;
    },

    any: function(arr, iterator) {
        var result = false;
        if (!arr || !arr.length) { return result; }

        var idx = 0, length = arr.length;
        for (; idx < length; idx++) {
            if (result || (result = iterator(arr[idx], idx))) { break; }
        }

        return !!result;
    }
};

// Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp.
var types = ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'],
    idx = types.length,
    generateCheck = function(name) {
        return function(obj) {
            return _toString.call(obj) === '[object ' + name + ']';
        };
    },
    name;
while (idx--) {
    name = types[idx];
    _['is' + name] = generateCheck(name);
}

// Optimize `isFunction` if appropriate.
if (typeof (/./) !== 'function') {
    _.isFunction = function(obj) {
        return typeof obj === 'function';
    };
}

// Optimize `isString` if appropriate.
if (typeof ('') === 'string') {
    _.isString = function(obj) {
        return typeof obj === 'string';
    };
}

module.exports = _;
<|endoftext|>


<commit_before>import { DELETE_NOTE_SUCCESS, CREATE_NOTE_SUCCESS, UPDATE_NOTE_SUCCESS } from './../../actions/notes';
import { MINUS_ONE } from './../../constants/Constants';

export default (state, action) => {
  switch (action.type) {
    case CREATE_NOTE_SUCCESS: {
      const articleIndex = state.get('article').findIndex((el) => el.get('id') === action.meta.bookmarkId);

      if (articleIndex === MINUS_ONE) {
        return state;
      }

      const newArticle = state.getIn(['article', articleIndex]).update('notes', (old) => old.push(action.payload));

      return state.setIn(['article', articleIndex], newArticle);
    }
    case DELETE_NOTE_SUCCESS: {
      const articleIndex = state.get('article').findIndex((el) => el.get('id') === action.meta.bookmarkId);

      if (articleIndex === MINUS_ONE) {
        return state;
      }

      const newArticle = state.getIn(['article', articleIndex]).update('notes', (old) =>
        old.filterNot((note) => note.id === action.meta.noteId));

      return state.setIn(['article', articleIndex], newArticle);
    }
    case UPDATE_NOTE_SUCCESS: {
      const articleIndex = state.get('article').findIndex((el) => el.get('id') === action.meta.bookmarkId);

      if (articleIndex === MINUS_ONE) {
        return state;
      }

      const newArticle = state.getIn(['article', articleIndex]).update('notes', (old) =>
        old.map((note) => (note.id === action.meta.noteId ? action.payload : note)));

      return state.setIn(['article', articleIndex], newArticle);
    }
    default: {
      return state;
    }
  }
};
<commit_msg>Fix JS/Immutable conversion<commit_after>import Immutable from 'immutable';

import { DELETE_NOTE_SUCCESS, CREATE_NOTE_SUCCESS, UPDATE_NOTE_SUCCESS } from './../../actions/notes';
import { MINUS_ONE } from './../../constants/Constants';

export default (state, action) => {
  switch (action.type) {
    case CREATE_NOTE_SUCCESS: {
      const articleIndex = state.get('article').findIndex((el) => el.get('id') === action.meta.bookmarkId);

      if (articleIndex === MINUS_ONE) {
        return state;
      }

      const newArticle = state.getIn(['article', articleIndex]).update('notes', (old) =>
        old.push(Immutable.fromJS(action.payload)));

      return state.setIn(['article', articleIndex], newArticle);
    }
    case DELETE_NOTE_SUCCESS: {
      const articleIndex = state.get('article').findIndex((el) => el.get('id') === action.meta.bookmarkId);

      if (articleIndex === MINUS_ONE) {
        return state;
      }

      const newArticle = state.getIn(['article', articleIndex]).update('notes', (old) =>
        old.filterNot((note) => note.get('id') === action.meta.noteId));

      return state.setIn(['article', articleIndex], newArticle);
    }
    case UPDATE_NOTE_SUCCESS: {
      const articleIndex = state.get('article').findIndex((el) => el.get('id') === action.meta.bookmarkId);

      if (articleIndex === MINUS_ONE) {
        return state;
      }

      const newArticle = state.getIn(['article', articleIndex]).update('notes', (old) =>
        old.map((note) => (note.id === action.meta.noteId ? Immutable.fromJS(action.payload) : note)));

      return state.setIn(['article', articleIndex], newArticle);
    }
    default: {
      return state;
    }
  }
};
<|endoftext|>


<commit_before>import SpaceWorker from './space.worker';
import { WEBGL } from 'three/examples/jsm/WebGL.js';
const gameCanvas = document.getElementById('game');

if (WEBGL.isWebGL2Available() === true) {
  if (!('transferControlToOffscreen' in gameCanvas)) {

    const onWindowResize = () => {
      gameCanvas.setAttribute('width', window.innerWidth);
      gameCanvas.setAttribute('height', window.innerHeight);
    };

    onWindowResize(gameCanvas);

    const offscreen = gameCanvas.transferControlToOffscreen();

    const worker = new SpaceWorker();

    worker.postMessage({ topic: 'canvas', canvas: offscreen }, [offscreen]);

    gameCanvas.addEventListener('wheel', (event) => {
      const deltaY = event.deltaY > 0 ? 1 : -1;
      worker.postMessage({
        topic: 'wheel',
        deltaY,
      });
    });
    /*gameCanvas.addEventListener('mousedown', onMouseDown);
    gameCanvas.addEventListener('mouseup', onMouseUp);
    gameCanvas.addEventListener('mousemove', onMouseMove);*/

    window.addEventListener('resize', () => {
      onWindowResize();
      worker.postMessage({
        topic: 'resize',
        size: {
          width: window.innerWidth,
          height: window.innerHeight,
        },
      });
    });
  } else {
    document
      .querySelector('#no-offscreen-canvas')
      .classList
      .remove('hide');
  }
} else {
  document.body.appendChild(WEBGL.getWebGL2ErrorMessage());
}

<commit_msg>Fixed logic in checking for transferControlToOffscreen<commit_after>import SpaceWorker from './space.worker';
import { WEBGL } from 'three/examples/jsm/WebGL.js';
const gameCanvas = document.getElementById('game');

if (WEBGL.isWebGL2Available() === true) {
  if ('transferControlToOffscreen' in gameCanvas) {

    const onWindowResize = () => {
      gameCanvas.setAttribute('width', window.innerWidth);
      gameCanvas.setAttribute('height', window.innerHeight);
    };

    onWindowResize(gameCanvas);

    const offscreen = gameCanvas.transferControlToOffscreen();

    const worker = new SpaceWorker();

    worker.postMessage({ topic: 'canvas', canvas: offscreen }, [offscreen]);

    gameCanvas.addEventListener('wheel', (event) => {
      const deltaY = event.deltaY > 0 ? 1 : -1;
      worker.postMessage({
        topic: 'wheel',
        deltaY,
      });
    });
    /*gameCanvas.addEventListener('mousedown', onMouseDown);
    gameCanvas.addEventListener('mouseup', onMouseUp);
    gameCanvas.addEventListener('mousemove', onMouseMove);*/

    window.addEventListener('resize', () => {
      onWindowResize();
      worker.postMessage({
        topic: 'resize',
        size: {
          width: window.innerWidth,
          height: window.innerHeight,
        },
      });
    });
  } else {
    document
      .querySelector('#no-offscreen-canvas')
      .classList
      .remove('hide');
  }
} else {
  document.body.appendChild(WEBGL.getWebGL2ErrorMessage());
}

<|endoftext|>


<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';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './constants';
import { createFetchStore } from '../../data/create-fetch-store';

const { createRegistrySelector } = Data;

// Actions.
const REFETCH_AUTHENICATION = 'REFETCH_AUTHENICATION';

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_AUTHENICATION,
		};

		return { response, error };
	},

	/**
	 * Dectivates a module on the server.
	 *
	 * Dectivate 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_AUTHENICATION,
		};

		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();
		}

		return { response, error };
	},
};

export const baseControls = {
	[ REFETCH_AUTHENICATION ]: () => {
		return API.get( 'core', 'user', 'authentication', { timestamp: Date.now() }, { useCache: false } );
	},
};

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"
	 *   ],
	 *   "dependants": []
	 * }
	 * ```
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Modules available on the site.
	 */
	getModules( state ) {
		const { modules } = state;

		return modules;
	},

	/**
	 * 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,
		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>Re-add action after refactor to createFetchStore.<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';

/**
 * Internal dependencies
 */
import API from 'googlesitekit-api';
import Data from 'googlesitekit-data';
import { STORE_NAME } from './constants';
import { createFetchStore } from '../../data/create-fetch-store';
import DefaultModuleSettings from '../components/DefaultModuleSettings';

const { createRegistrySelector } = Data;

// Actions.
const REFETCH_AUTHENICATION = 'REFETCH_AUTHENICATION';
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_AUTHENICATION,
		};

		return { response, error };
	},

	/**
	 * Dectivates a module on the server.
	 *
	 * Dectivate 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_AUTHENICATION,
		};

		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();
		}

		return { response, error };
	},

	/**
	 * @param {string}          slug                         Module slug.
	 * @param {Object}          settings                     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 {boolean}         [settings.internal]          Optional. Whether the module is considered internal. Default false.
	 * @param {React.Component} [settings.settingsComponent] React component to render the settings panel. Default is the DefaultModuleSettings component.
	 * @return {Object} Redux-style action.
	 */
	registerModule( slug, settings = {} ) {
		invariant( slug, 'module slug is required' );

		const mergedModuleSettings = {
			name: slug,
			description: null,
			icon: null,
			order: 10,
			homepage: null,
			internal: false,
			settingsComponent: DefaultModuleSettings,
			...settings,
		};

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

export const baseControls = {
	[ REFETCH_AUTHENICATION ]: () => {
		return API.get( 'core', 'user', 'authentication', { timestamp: Date.now() }, { useCache: false } );
	},
};

const baseReducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case REGISTER_MODULE: {
			const { modules: existingModules } = state;
			const { slug, settings } = payload;
			return {
				...state,
				modules: {
					...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"
	 *   ],
	 *   "dependants": []
	 * }
	 * ```
	 *
	 * @since 1.8.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Modules available on the site.
	 */
	getModules( state ) {
		const { modules } = state;

		return modules;
	},

	/**
	 * 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>// Copy ZenProspect contacts with email replies into Close.io leads

'use strict';
if (process.argv.length !== 4) {
  console.log("Usage: node <script> <Close.io general API key> <ZenProspect auth token>");
  process.exit();
}

const closeIoApiKey = process.argv[2];
const zpAuthToken = process.argv[3];

const scriptStartTime = new Date();

const async = require('async');
const request = require('request');

const zpPageSize = 100;

getZPContacts((err, emailContactMap) => {
  if (err) {
    console.log(err);
    return;
  }
  const tasks = [];
  for (const email in emailContactMap) {
    const contact = emailContactMap[email];
    tasks.push(createUpsertCloseLeadFn(contact));
  }
  async.parallel(tasks, (err, results) => {
    if (err) console.log(err);
    log("Script runtime: " + (new Date() - scriptStartTime));
  });
});

function createCloseLead(zpContact, done) {
  const postData = {
    name: zpContact.organization,
    status: 'Contacted',
    contacts: [
      {
        name: zpContact.name,
        title: zpContact.title,
        emails: [{email: zpContact.email}]
      }
    ],
    custom: {
      lastUpdated: new Date(),
      'Lead Origin': 'outbound campaign'
    }
  };
  if (zpContact.phone) {
    postData.contacts[0].phones = [{phone: zpContact.phone}];
  }
  if (zpContact.district) {
    postData.custom['demo_nces_district'] = zpContact.district;
    postData.custom['demo_nces_name'] = zpContact.organization;
  }
  if (zpContact.nces_district_id) {
    postData.custom['demo_nces_district_id'] = zpContact.nces_district_id;
  }
  if (zpContact.nces_school_id) {
    postData.custom['demo_nces_id'] = zpContact.nces_school_id;
  }
  const options = {
    uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/`,
    body: JSON.stringify(postData)
  };
  request.post(options, (error, response, body) => {
    if (error) return done(error);
    const newLead = JSON.parse(body);
    if (newLead.errors || newLead['field-errors']) {
      console.error(`New lead POST error for ${zpContact.name} ${zpContact.organization}`);
      return done(newLead.errors || newLead['field-errors']);
    }
    return done();
  });
}

function updateCloseLead(zpContact, existingLead, done) {
  // console.log(`DEBUG: updateCloseLead ${existingLead.id} ${zpContact.email}`);
  const putData = {
    status: 'Contacted',
    'custom.lastUpdated': new Date(),
    'custom.Lead Origin': 'outbound campaign'
  };
  const options = {
    uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/${existingLead.id}/`,
    body: JSON.stringify(putData)
  };
  request.put(options, (error, response, body) => {
    if (error) return done(error);
    const result = JSON.parse(body);
    if (result.errors || result['field-errors']) {
      return done(`Update existing lead PUT error for ${existingLead.id} ${zpContact.email} ${result.errors || result['field-errors']}`);
    }
    const postData = {
      lead_id: existingLead.id,
      name: zpContact.name,
      title: zpContact.title,
      emails: [{email: zpContact.email}]
    };
    if (zpContact.phone) {
      postData.phones = [{phone: zpContact.phone}];
    }
    const options = {
      uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/contact/`,
      body: JSON.stringify(postData)
    };
    request.post(options, (error, response, body) => {
      if (error) return done(error);
      const result = JSON.parse(body);
      if (result.errors || result['field-errors']) {
        return done(`New Contact POST error for ${existingLead.id} ${zpContact.email} ${result.errors || result['field-errors']}`);
      }
      return done();
    });
  });
}

function createUpsertCloseLeadFn(zpContact) {
  return (done) => {
    // console.log(`DEBUG: createUpsertCloseLeadFn ${zpContact.organization} ${zpContact.email}`);
    const query = `email:${zpContact.email}`;
    const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
    request.get(url, (error, response, body) => {
      if (error) return done(error);
      const data = JSON.parse(body);
      if (data.total_results != 0) return done();
      const query = `name:${zpContact.organization}`;
      const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
      request.get(url, (error, response, body) => {
        if (error) return done(error);
        const data = JSON.parse(body);
        if (data.total_results === 0) {
          console.log(`DEBUG: Creating lead for ${zpContact.organization} ${zpContact.email}`);
          return createCloseLead(zpContact, done);
        }
        else {
          const existingLead = data.data[0];
          console.log(`DEBUG: Adding ${zpContact.organization} ${zpContact.email} to ${existingLead.id}`);
          return updateCloseLead(zpContact, existingLead, done);
        }
      });
    });
  };
}

function getZPContactsPage(contacts, searchQuery, done) {
  const options = {
    url: `https://www.zenprospect.com/api/v1/contacts/search?${searchQuery}`,
    headers: {
      'Accept': 'application/json'
    }
  };
  request.get(options, (err, response, body) => {
    if (err) return done(err);
    const data = JSON.parse(body);
    for (let contact of data.contacts) {
      const newContact = {
        organization: contact.organization_name,
        name: contact.name,
        title: contact.title,
        email: contact.email,
        phone: contact.phone,
        data: contact
      };
      // Check custom fields, school_name set means organization_name is district name
      if (contact.custom_fields) {
        if (contact.custom_fields.school_name) {
          newContact.district = contact.organization_name;
          newContact.organization = contact.custom_fields.school_name;
          // console.log(`DEBUG: found contact with school name ${newContact.email} ${contact.custom_fields.school_name}`);
        }
        if (contact.custom_fields.nces_district_id) {
          newContact.nces_district_id = contact.custom_fields.nces_district_id;
          // console.log(`DEBUG: found contact with district id ${newContact.email} ${newContact.nces_district_id}`);
        }
        if (contact.custom_fields.nces_school_id) {
          newContact.nces_school_id = contact.custom_fields.nces_school_id;
          // console.log(`DEBUG: found contact with school id ${newContact.email} ${newContact.nces_school_id}`);
        }
      }
      contacts.push(newContact);
    }
    return done(null, data.pipeline_total);
  });
}

function createGetZPAutoResponderContactsPage(contacts, page) {
  return (done) => {
    // console.log(`DEBUG: Fetching autoresponder page ${page} ${zpPageSize}...`);
    let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&contact_email_autoresponder=true`;
    getZPContactsPage(contacts, searchQuery, done);
  };
}

function createGetZPRepliedContactsPage(contacts, page) {
  return (done) => {
    // console.log(`DEBUG: Fetching email reply page ${page} ${zpPageSize}...`);
    let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&contact_email_replied=true`;
    getZPContactsPage(contacts, searchQuery, done);
  };
}

function getZPContacts(done) {
  // Get first page to get total contact count for future parallized page fetches
  const contacts = [];
  createGetZPAutoResponderContactsPage(contacts, 0)((err, autoResponderTotal) => {
    if (err) return done(err);
    createGetZPRepliedContactsPage(contacts, 0)((err, repliedTotal) => {
      if (err) return done(err);

      const tasks = [];
      for (let i = 1; (i - 1) * zpPageSize < autoResponderTotal; i++) {
        tasks.push(createGetZPAutoResponderContactsPage(contacts, i));
      }
      for (let i = 1; (i - 1) * zpPageSize < repliedTotal; i++) {
        tasks.push(createGetZPRepliedContactsPage(contacts, i));
      }

      async.series(tasks, (err, results) => {
        if (err) return done(err);
        const emailContactMap = {};
        for (const contact of contacts) {
          if (!contact.organization || !contact.name || !contact.title || !contact.email) {
            console.log(JSON.stringify(contact, null, 2));
            return done(`DEBUG: missing data for zp contact:`);
          }
          if (!emailContactMap[contact.email]) {
            emailContactMap[contact.email] = contact;
          }
          // else {
          //   console.log(`DEBUG: already have contact ${contact.email}`);
          // }
        }
        log(`(${autoResponderTotal + repliedTotal}) ${autoResponderTotal} autoresponder ZP contacts ${repliedTotal} ZP contacts ${Object.keys(emailContactMap).length} contacts mapped`);
        return done(null, emailContactMap);
      });
    });
  });
}

function log(str) {
  console.log(new Date().toISOString() + " " + str);
}
<commit_msg>Update ZP -> Close lead importing to only query for last 30 days<commit_after>// Copy ZenProspect contacts with email replies into Close.io leads

'use strict';
if (process.argv.length !== 4) {
  console.log("Usage: node <script> <Close.io general API key> <ZenProspect auth token>");
  process.exit();
}

// NOTE: last_activity_date_range is the contacted at date, UTC

// TODO: Only looking at contacts created in last 30 days.  How do we catch replies for contacts older than 30 days?

const closeIoApiKey = process.argv[2];
const zpAuthToken = process.argv[3];

const scriptStartTime = new Date();

const async = require('async');
const request = require('request');

const zpPageSize = 100;
let zpMinActivityDate = new Date();
zpMinActivityDate.setUTCDate(zpMinActivityDate.getUTCDate() - 30);
zpMinActivityDate = zpMinActivityDate.toISOString().substring(0, 10);

getZPContacts((err, emailContactMap) => {
  if (err) {
    console.log(err);
    return;
  }
  const tasks = [];
  for (const email in emailContactMap) {
    const contact = emailContactMap[email];
    tasks.push(createUpsertCloseLeadFn(contact));
  }
  async.parallel(tasks, (err, results) => {
    if (err) console.log(err);
    log("Script runtime: " + (new Date() - scriptStartTime));
  });
});

function createCloseLead(zpContact, done) {
  const postData = {
    name: zpContact.organization,
    status: 'Contacted',
    contacts: [
      {
        name: zpContact.name,
        emails: [{email: zpContact.email}]
      }
    ],
    custom: {
      lastUpdated: new Date(),
      'Lead Origin': 'outbound campaign'
    }
  };
  if (zpContact.phone) {
    postData.contacts[0].phones = [{phone: zpContact.phone}];
  }
  if (zpContact.title) {
    postData.contacts[0].title = zpContact.title;
  }
  if (zpContact.district) {
    postData.custom['demo_nces_district'] = zpContact.district;
    postData.custom['demo_nces_name'] = zpContact.organization;
  }
  if (zpContact.nces_district_id) {
    postData.custom['demo_nces_district_id'] = zpContact.nces_district_id;
  }
  if (zpContact.nces_school_id) {
    postData.custom['demo_nces_id'] = zpContact.nces_school_id;
  }
  const options = {
    uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/`,
    body: JSON.stringify(postData)
  };
  request.post(options, (error, response, body) => {
    if (error) return done(error);
    const newLead = JSON.parse(body);
    if (newLead.errors || newLead['field-errors']) {
      console.error(`New lead POST error for ${zpContact.name} ${zpContact.organization}`);
      return done(newLead.errors || newLead['field-errors']);
    }
    return done();
  });
}

function updateCloseLead(zpContact, existingLead, done) {
  // console.log(`DEBUG: updateCloseLead ${existingLead.id} ${zpContact.email}`);
  const putData = {
    status: 'Contacted',
    'custom.lastUpdated': new Date(),
    'custom.Lead Origin': 'outbound campaign'
  };
  const options = {
    uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/${existingLead.id}/`,
    body: JSON.stringify(putData)
  };
  request.put(options, (error, response, body) => {
    if (error) return done(error);
    const result = JSON.parse(body);
    if (result.errors || result['field-errors']) {
      return done(`Update existing lead PUT error for ${existingLead.id} ${zpContact.email} ${result.errors || result['field-errors']}`);
    }
    const postData = {
      lead_id: existingLead.id,
      name: zpContact.name,
      title: zpContact.title,
      emails: [{email: zpContact.email}]
    };
    if (zpContact.phone) {
      postData.phones = [{phone: zpContact.phone}];
    }
    const options = {
      uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/contact/`,
      body: JSON.stringify(postData)
    };
    request.post(options, (error, response, body) => {
      if (error) return done(error);
      const result = JSON.parse(body);
      if (result.errors || result['field-errors']) {
        return done(`New Contact POST error for ${existingLead.id} ${zpContact.email} ${result.errors || result['field-errors']}`);
      }
      return done();
    });
  });
}

function createUpsertCloseLeadFn(zpContact) {
  return (done) => {
    // console.log(`DEBUG: createUpsertCloseLeadFn ${zpContact.organization} ${zpContact.email}`);
    const query = `email:${zpContact.email}`;
    const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
    request.get(url, (error, response, body) => {
      if (error) return done(error);
      const data = JSON.parse(body);
      if (data.total_results != 0) return done();
      const query = `name:${zpContact.organization}`;
      const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
      request.get(url, (error, response, body) => {
        if (error) return done(error);
        const data = JSON.parse(body);
        if (data.total_results === 0) {
          console.log(`DEBUG: Creating lead for ${zpContact.organization} ${zpContact.email}`);
          return createCloseLead(zpContact, done);
        }
        else {
          const existingLead = data.data[0];
          console.log(`DEBUG: Adding ${zpContact.organization} ${zpContact.email} to ${existingLead.id}`);
          return updateCloseLead(zpContact, existingLead, done);
        }
      });
    });
  };
}

function getZPContactsPage(contacts, searchQuery, done) {
  const options = {
    url: `https://www.zenprospect.com/api/v1/contacts/search?${searchQuery}`,
    headers: {
      'Accept': 'application/json'
    }
  };
  request.get(options, (err, response, body) => {
    if (err) return done(err);
    const data = JSON.parse(body);
    for (let contact of data.contacts) {
      const newContact = {
        organization: contact.organization_name,
        name: contact.name,
        title: contact.title,
        email: contact.email,
        phone: contact.phone,
        data: contact
      };
      // Check custom fields, school_name set means organization_name is district name
      if (contact.custom_fields) {
        if (contact.custom_fields.school_name) {
          newContact.district = contact.organization_name;
          newContact.organization = contact.custom_fields.school_name;
          // console.log(`DEBUG: found contact with school name ${newContact.email} ${contact.custom_fields.school_name}`);
        }
        if (contact.custom_fields.nces_district_id) {
          newContact.nces_district_id = contact.custom_fields.nces_district_id;
          // console.log(`DEBUG: found contact with district id ${newContact.email} ${newContact.nces_district_id}`);
        }
        if (contact.custom_fields.nces_school_id) {
          newContact.nces_school_id = contact.custom_fields.nces_school_id;
          // console.log(`DEBUG: found contact with school id ${newContact.email} ${newContact.nces_school_id}`);
        }
      }
      contacts.push(newContact);
    }
    return done(null, data.pipeline_total);
  });
}

function createGetZPAutoResponderContactsPage(contacts, page) {
  return (done) => {
    // console.log(`DEBUG: Fetching autoresponder page ${page} ${zpPageSize}...`);
    let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&last_activity_date_range[min]=${zpMinActivityDate}&contact_email_autoresponder=true`;
    getZPContactsPage(contacts, searchQuery, done);
  };
}

function createGetZPRepliedContactsPage(contacts, page) {
  return (done) => {
    // console.log(`DEBUG: Fetching email reply page ${page} ${zpPageSize}...`);
    let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&last_activity_date_range[min]=${zpMinActivityDate}&contact_email_replied=true`;
    getZPContactsPage(contacts, searchQuery, done);
  };
}

function getZPContacts(done) {
  // Get first page to get total contact count for future parallized page fetches
  const contacts = [];
  createGetZPAutoResponderContactsPage(contacts, 0)((err, autoResponderTotal) => {
    if (err) return done(err);
    createGetZPRepliedContactsPage(contacts, 0)((err, repliedTotal) => {
      if (err) return done(err);

      const tasks = [];
      for (let i = 1; (i - 1) * zpPageSize < autoResponderTotal; i++) {
        tasks.push(createGetZPAutoResponderContactsPage(contacts, i));
      }
      for (let i = 1; (i - 1) * zpPageSize < repliedTotal; i++) {
        tasks.push(createGetZPRepliedContactsPage(contacts, i));
      }

      async.series(tasks, (err, results) => {
        if (err) return done(err);
        const emailContactMap = {};
        for (const contact of contacts) {
          if (!contact.organization || !contact.name || !contact.email) {
            console.log(JSON.stringify(contact, null, 2));
            return done(`DEBUG: missing data for zp contact:`);
          }
          if (!emailContactMap[contact.email]) {
            emailContactMap[contact.email] = contact;
          }
          // else {
          //   console.log(`DEBUG: already have contact ${contact.email}`);
          // }
        }
        log(`(${autoResponderTotal + repliedTotal}) ${autoResponderTotal} autoresponder ZP contacts ${repliedTotal} ZP contacts ${Object.keys(emailContactMap).length} contacts mapped`);
        return done(null, emailContactMap);
      });
    });
  });
}

function log(str) {
  console.log(new Date().toISOString() + " " + str);
}
<|endoftext|>


<commit_before>const diplomacy = require('js-diplomacy')
const vizdip = require('./../../src/vizdip')

const Season = diplomacy.standardBoard.Season
const Result = diplomacy.standardRule.Result
const OrderType = diplomacy.standardRule.Order.OrderType
const Phase = diplomacy.standardRule.Phase
const Power = diplomacy.standardMap.Power
const Helper = diplomacy.standardRule.StandardRuleHelper
const Utils = diplomacy.standardRule.StandardRuleUtils

const variant = diplomacy.standard.variant

let viz = null
let board = variant.initialBoard
let $$ = new Helper(board)

function stringify (order) {
  function s (militaryBranch) {
    if (militaryBranch === diplomacy.standardRule.MilitaryBranch.Army) {
      return 'A'
    } else {
      return 'F'
    }
  }

  switch (order.tpe) {
    case OrderType.Hold:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} H`
    case OrderType.Retreat:
    case OrderType.Move:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} &#x2192 ${order.destination}`
    case OrderType.Support:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} S ${stringify(order.target)}`
    case OrderType.Convoy:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} C ${stringify(order.target)}`
    case OrderType.Disband:
      return `Disband ${s(order.unit.militaryBranch)} ${order.unit.location}`
    case OrderType.Build:
      return `Build ${s(order.unit.militaryBranch)} ${order.unit.location}`
  }
}

function getOrders () {
  const retval = []
  Array.from(document.getElementsByClassName('order')).forEach(orderElem => {
    try {
      const tgt = orderElem.children[0].options[orderElem.children[0].selectedIndex].innerHTML
      const tpe = orderElem.children[1].options[orderElem.children[1].selectedIndex].value
      const dest = orderElem.children[2].options[orderElem.children[2].selectedIndex]

      const loc = [...board.map.locations].find(u => u.toString() === tgt)

      if (!loc) return

      switch (tpe) {
        case 'M':
          (() => {
            const d1 = [...board.map.locations].find(l => l.toString() === dest.innerHTML)
            if (!d1) return
            retval.push($$.U(loc).move(d1))
          })()
          break
        case 'S':
          (() => {
            if (!dest) {
              return
            }

            const target = JSON.parse(dest.value)
            const targetLoc = [...board.map.locations].find(u => u.toString() === target[0])
            if (target.length === 1) {
              // Hold
              retval.push($$.U(loc).support($$.U(targetLoc).hold()))
            } else if (target.length === 2) {
              // Move
              const d1 = [...board.map.locations].find(l => l.toString() === target[1])
              if (d1) {
                retval.push($$.U(loc).support($$.U(targetLoc).move(d1)))
              }
            }
          })()
          break
        case 'C':
          (() => {
            if (!dest) {
              return
            }
            const target = JSON.parse(dest.value)
            const targetLoc = [...board.map.locations].find(u => u.toString() === target[0])
            if (target.length === 2) {
              // Move
              const d1 = [...board.map.locations].find(l => l.toString() === target[1])
              if (d1) {
                retval.push($$.U(loc).convoy($$.U(targetLoc).move(d1)))
              }
            }
          })()
          break
        case 'R':
          (() => {
            const d2 = [...board.map.locations].find(l => l.toString() === dest.innerHTML)
            if (d2) {
              retval.push($$.U(loc).retreat(d2))
            }
          })()
          break
        case 'B':
          (() => {
            if (dest.innerHTML === 'Fleet') {
              retval.push($$.F(loc).build())
            } else if (dest.innerHTML === 'Army') {
              retval.push($$.A(loc).build())
            }
          })()
          break
        case 'H':
          (() => {
            retval.push($$.U(loc).hold())
          })()
          break
        case 'D':
          (() => {
            retval.push($$.U(loc).disband())
          })()
          break
      }
    } catch (e) {
      console.log(e)
    }
  })
  return retval
}

function visualizeOrders () {
  viz.visualizeOrders(getOrders())
}

function updateDestLists (orderElem) {
  const targetElem = orderElem.getElementsByClassName('order-target')[0]
  const tpeElem = orderElem.getElementsByClassName('order-type')[0]
  const destElem = orderElem.getElementsByClassName('order-dest')[0]

  const target = targetElem.options[targetElem.selectedIndex].text
  const tpe = tpeElem.options[tpeElem.selectedIndex].value

  const loc = [...board.map.locations].find(x => x.toString() === target)

  if (!loc) {
    return
  }

  if (tpe !== '') {
    while (destElem.firstChild) destElem.removeChild(destElem.firstChild)
  }

  destElem.style.display = 'inline'
  switch (tpe) {
    case 'M':
      Utils.movableLocationsOf(board, $$.U(loc).unit).forEach(location => {
        const el = document.createElement('option')
        el.innerHTML = location.toString()
        destElem.appendChild(el)
      })
      break
    case 'S':
      getOrders().forEach(order => {
        // TODO
        const el = document.createElement('option')
        if (order.tpe === OrderType.Move) {
          el.innerHTML = stringify(order)
          el.value = JSON.stringify([order.unit.location.toString(), order.destination.toString()])
        } else {
          el.innerHTML = `${order.unit} H`
          el.value = JSON.stringify([order.unit.location.toString()])
        }
        destElem.appendChild(el)
      })
      break
    case 'C':
      getOrders().forEach(order => {
        if (order.tpe === 'Move') {
          const el = document.createElement('option')
          el.innerHTML = stringify(order)
          el.value = JSON.stringify([order.unit.location.toString(), order.destination.toString()])
          destElem.appendChild(el)
        }
      })
      break
    case 'R':
      // TODO consider `attackedFrom`
      Utils.locationsToRetreat(board, $$.U(loc).unit, null).forEach(location => {
        const el = document.createElement('option')
        el.innerHTML = location.toString()
        destElem.appendChild(el)
      })
      break
    case 'B':
      destElem.innerHTML += '<option></option>'
      loc.militaryBranches.forEach(x => {
        destElem.innerHTML += `<option>${x.name}</option>`
      })
      break
    default:
      destElem.style.display = 'none'
      break
  }
}

function createOrderItem () {
  const elem = document.createElement('li')
  elem.classList.add('list-group-item')
  elem.classList.add('order')
  return elem
}
function createTargetUnitList (elem) {
  const target = document.createElement('select')
  target.classList.add('order-target')
  target.classList.add('select-picker')
  if (elem.length === 1) {
    target.setAttribute('disabled', 'true')
    target.innerHTML = `<option>${elem[0]}</option>`
  } else {
    target.innerHTML = '<option></option>'
    elem.forEach(el => {
      target.innerHTML += `<option>${el}</option>`
    })
  }
  target.addEventListener('change', (e) => {
    updateDestLists(e.target.parentNode)
    visualizeOrders()
  })
  return target
}
function createOrderTypeList (types) {
  const target = document.createElement('select')
  target.classList.add('order-type')
  target.classList.add('select-picker')
  if (types.length === 1) {
    target.setAttribute('disabled', 'true')
    target.innerHTML = `<option value="${types[0][0]}">${types[0][1]}</option>`
  } else {
    target.innerHTML = '<option></option>'
    types.forEach(el => {
      target.innerHTML += `<option value="${el[0]}">${el[1]}</option>`
    })
  }
  target.addEventListener('change', (e) => {
    updateDestLists(e.target.parentNode)
    visualizeOrders()
  })
  return target
}
function createDestList () {
  const target = document.createElement('select')
  target.classList.add('order-dest')
  target.classList.add('select-picker')
  target.addEventListener('change', () => visualizeOrders())
  return target
}

function createOrderList () {
  // Clear list
  board.map.powers.forEach(power => {
    const powerPanel = document.getElementById(`order-${power}`)
    while (powerPanel.firstChild) powerPanel.removeChild(powerPanel.firstChild)
  })

  // Create list
  board.map.powers.forEach(power => {
    const powerPanel = document.getElementById(`order-${power}`)
    switch (board.state.phase) {
      case Phase.Movement:
        (() => {
          const us = [...board.units].filter(x => x.power === power)
          us.forEach(unit => {
            const elem = createOrderItem()
            const target = createTargetUnitList([unit.location])
            const tpe =
              createOrderTypeList([['M', '&#x2192'], ['S', 'S'], ['C', 'C'], ['H', 'H']])
            const dest = createDestList()
            elem.appendChild(target)
            elem.appendChild(tpe)
            elem.appendChild(dest)
            powerPanel.appendChild(elem)
          })
        })()
        break
      case Phase.Retreat:
        (() => {
          const us = [...board.units].filter(x => x.power === power)
          us.forEach(unit => {
            if (!board.unitStatuses.get(unit)) return
            const elem = createOrderItem()
            const target = createTargetUnitList([unit.location])
            const tpe =
              createOrderTypeList([['R', '&#x2192'], ['D', 'Disband']])
            const dest = createDestList()
            elem.appendChild(target)
            elem.appendChild(tpe)
            elem.appendChild(dest)
            powerPanel.appendChild(elem)
          })
        })()
        break
      case Phase.Build:
        (() => {
          const num = Utils.numberOfBuildableUnits(board).get(power) || 0
          if (num > 0) {
            // Build
            const candidates =
              [...board.map.locations].filter(l => {
                return l.province.isSupplyCenter && l.province.homeOf === power
              })
            for (let i = 0; i < num; i++) {
              const elem = createOrderItem()
              const target = createTargetUnitList(candidates)
              const tpe =
                createOrderTypeList([['B', 'Build']])
              const dest = createDestList()
              elem.appendChild(target)
              elem.appendChild(tpe)
              elem.appendChild(dest)
              powerPanel.appendChild(elem)
            }
          } else if (num < 0) {
            // Disband
            const us = [...board.units].filter(x => x.power === power)
            for (let i = 0; i < -num; i++) {
              const elem = createOrderItem()
              const target = createTargetUnitList(us.map(u => u.location))
              const tpe =
                createOrderTypeList([['D', 'Disband']])
              const dest = createDestList()
              dest.style.display = 'none'
              elem.appendChild(target)
              elem.appendChild(tpe)
              elem.appendChild(dest)
              powerPanel.appendChild(elem)
            }
          }
        })()
        break
    }
  })
}

function createOrderPanel () {
  const ordersElem = document.getElementById('orders')
  board.map.powers.forEach(power => {
    const panel = document.createElement('div')
    panel.classList.add('panel')
    panel.classList.add('panel-default')
    const header = document.createElement('div')
    header.classList.add('panel-heading')
    header.innerHTML = power
    const body = document.createElement('div')
    body.classList.add('pael-body')
    body.innerHTML = `<ul class="list-group" id="order-${power}"></ul>`

    panel.appendChild(header)
    panel.appendChild(body)
    ordersElem.appendChild(panel)
  })
}

function resolve () {
  // Resolve orders
  const r = variant.rule.resolve(board, getOrders())

  const resultBody = document.getElementById('result-body')
  while (resultBody.firstChild) resultBody.removeChild(resultBody.firstChild)

  // Show error message
  if (r.err) {
    const errorElem = document.getElementById('result-error')
    errorElem.innerHTML = r.err.toString()
    $('#error-dialog').modal('show')
    return
  }

  const result = r.result

  // Show results
  result.results.forEach(result => {
    const tr = document.createElement('tr')
    const u = document.createElement('td')
    u.setAttribute('scope', 'row')
    u.innerHTML = stringify(result.target)
    const r = document.createElement('td')
    if (result.result === Result.Success) {
      r.innerHTML += `<span class="label label-success">${Result[result.result]}</span>`
    } else if (result.result === Result.Dislodged) {
      r.innerHTML += `<span class="label label-danger">${Result[result.result]}</span>`
    } else {
      r.innerHTML += `<span class="label label-warning">${Result[result.result]}</span>`
    }

    if (result.replacedBy) {
      r.innerHTML += ` (replaced by ${result.replacedBy}: ${result.invalidReason})`
    }
    tr.appendChild(u)
    tr.appendChild(r)
    resultBody.appendChild(tr)
  })

  // Visualize the next board
  if (result.isFinished) {
    alert('Game is finished')
    return
  }

  board = result.board
  $$ = new Helper(board)

  // Update the UI
  viz.visualizeBoard(result.board)
  createOrderList()
  visualizeOrders()
}

function adjustMap () {
  const mapElem = document.getElementById('map')
  const rect = document.getElementById('map-col').getBoundingClientRect()
  const width = rect.right - rect.left - 30
  mapElem.width = width
}

function initialize () {
  const mapElem = document.getElementById('map')

  // Get svg images
  const map = mapElem.contentDocument
  const tmp = map.createElementNS('http://www.w3.org/2000/svg', 'g')
  const armyHTML = document.getElementById('army').contentDocument.getElementsByTagName('g')[0].outerHTML
  tmp.innerHTML = armyHTML
  const army = tmp.children[0].cloneNode(true)
  const fleetHTML = document.getElementById('fleet').contentDocument.getElementsByTagName('g')[0].outerHTML
  tmp.innerHTML = fleetHTML
  const fleet = tmp.children[0].cloneNode(true)
  tmp.remove()

  // Create visualizer instance
  const svgs = {
    map: map,
    army: army,
    fleet: fleet
  }
  const colors = {
    power: (power) => {
      switch (power) {
        case Power.England:
          return 'rgb(239, 154, 154)'
        case Power.Russia:
          return 'rgb(206, 147, 216)'
        case Power.France:
          return 'rgb(144, 202, 249)'
        case Power.Germany:
          return 'rgb(150, 150, 150)'
        case Power.Italy:
          return 'rgb(159, 168, 218)'
        case Power.Austria:
          return 'rgb(255, 224, 130)'
        case Power.Turkey:
          return 'rgb(255, 171, 145)'
      }
    }
  }
  const stringify = {
    fromState: (state) => `${state.turn.year}-${Season[state.turn.season]} (${Phase[state.phase]})`
  }
  const configs = {}
  viz = new vizdip.StandardRuleVisualizer(svgs, board.map, colors, stringify, configs)

  // Initialize interfaces
  createOrderPanel()
  createOrderList()
  viz.visualizeBoard(board)

  // Add event listeners
  document.getElementById('resolve').addEventListener('click', () => resolve())

  // Adjust width of map image
  document.getElementById('map-col').addEventListener('resize', adjustMap)
  adjustMap()

  document.getElementById('unit-images').style.display = 'none'
}

window.initialize = initialize
<commit_msg>Fix class name<commit_after>const diplomacy = require('js-diplomacy')
const vizdip = require('./../../src/vizdip')

const Season = diplomacy.standardBoard.Season
const Result = diplomacy.standardRule.Result
const OrderType = diplomacy.standardRule.Order.OrderType
const Phase = diplomacy.standardRule.Phase
const Power = diplomacy.standardMap.Power
const Helper = diplomacy.standardRule.Helper
const Utils = diplomacy.standardRule.Utils

const variant = diplomacy.standard.variant

let viz = null
let board = variant.initialBoard
let $$ = new Helper(board)

function stringify (order) {
  function s (militaryBranch) {
    if (militaryBranch === diplomacy.standardRule.MilitaryBranch.Army) {
      return 'A'
    } else {
      return 'F'
    }
  }

  switch (order.tpe) {
    case OrderType.Hold:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} H`
    case OrderType.Retreat:
    case OrderType.Move:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} &#x2192 ${order.destination}`
    case OrderType.Support:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} S ${stringify(order.target)}`
    case OrderType.Convoy:
      return `${s(order.unit.militaryBranch)} ${order.unit.location} C ${stringify(order.target)}`
    case OrderType.Disband:
      return `Disband ${s(order.unit.militaryBranch)} ${order.unit.location}`
    case OrderType.Build:
      return `Build ${s(order.unit.militaryBranch)} ${order.unit.location}`
  }
}

function getOrders () {
  const retval = []
  Array.from(document.getElementsByClassName('order')).forEach(orderElem => {
    try {
      const tgt = orderElem.children[0].options[orderElem.children[0].selectedIndex].innerHTML
      const tpe = orderElem.children[1].options[orderElem.children[1].selectedIndex].value
      const dest = orderElem.children[2].options[orderElem.children[2].selectedIndex]

      const loc = [...board.map.locations].find(u => u.toString() === tgt)

      if (!loc) return

      switch (tpe) {
        case 'M':
          (() => {
            const d1 = [...board.map.locations].find(l => l.toString() === dest.innerHTML)
            if (!d1) return
            retval.push($$.U(loc).move(d1))
          })()
          break
        case 'S':
          (() => {
            if (!dest) {
              return
            }

            const target = JSON.parse(dest.value)
            const targetLoc = [...board.map.locations].find(u => u.toString() === target[0])
            if (target.length === 1) {
              // Hold
              retval.push($$.U(loc).support($$.U(targetLoc).hold()))
            } else if (target.length === 2) {
              // Move
              const d1 = [...board.map.locations].find(l => l.toString() === target[1])
              if (d1) {
                retval.push($$.U(loc).support($$.U(targetLoc).move(d1)))
              }
            }
          })()
          break
        case 'C':
          (() => {
            if (!dest) {
              return
            }
            const target = JSON.parse(dest.value)
            const targetLoc = [...board.map.locations].find(u => u.toString() === target[0])
            if (target.length === 2) {
              // Move
              const d1 = [...board.map.locations].find(l => l.toString() === target[1])
              if (d1) {
                retval.push($$.U(loc).convoy($$.U(targetLoc).move(d1)))
              }
            }
          })()
          break
        case 'R':
          (() => {
            const d2 = [...board.map.locations].find(l => l.toString() === dest.innerHTML)
            if (d2) {
              retval.push($$.U(loc).retreat(d2))
            }
          })()
          break
        case 'B':
          (() => {
            if (dest.innerHTML === 'Fleet') {
              retval.push($$.F(loc).build())
            } else if (dest.innerHTML === 'Army') {
              retval.push($$.A(loc).build())
            }
          })()
          break
        case 'H':
          (() => {
            retval.push($$.U(loc).hold())
          })()
          break
        case 'D':
          (() => {
            retval.push($$.U(loc).disband())
          })()
          break
      }
    } catch (e) {
      console.log(e)
    }
  })
  return retval
}

function visualizeOrders () {
  viz.visualizeOrders(getOrders())
}

function updateDestLists (orderElem) {
  const targetElem = orderElem.getElementsByClassName('order-target')[0]
  const tpeElem = orderElem.getElementsByClassName('order-type')[0]
  const destElem = orderElem.getElementsByClassName('order-dest')[0]

  const target = targetElem.options[targetElem.selectedIndex].text
  const tpe = tpeElem.options[tpeElem.selectedIndex].value

  const loc = [...board.map.locations].find(x => x.toString() === target)

  if (!loc) {
    return
  }

  if (tpe !== '') {
    while (destElem.firstChild) destElem.removeChild(destElem.firstChild)
  }

  destElem.style.display = 'inline'
  switch (tpe) {
    case 'M':
      Utils.movableLocationsOf(board, $$.U(loc).unit).forEach(location => {
        const el = document.createElement('option')
        el.innerHTML = location.toString()
        destElem.appendChild(el)
      })
      break
    case 'S':
      getOrders().forEach(order => {
        // TODO
        const el = document.createElement('option')
        if (order.tpe === OrderType.Move) {
          el.innerHTML = stringify(order)
          el.value = JSON.stringify([order.unit.location.toString(), order.destination.toString()])
        } else {
          el.innerHTML = `${order.unit} H`
          el.value = JSON.stringify([order.unit.location.toString()])
        }
        destElem.appendChild(el)
      })
      break
    case 'C':
      getOrders().forEach(order => {
        if (order.tpe === 'Move') {
          const el = document.createElement('option')
          el.innerHTML = stringify(order)
          el.value = JSON.stringify([order.unit.location.toString(), order.destination.toString()])
          destElem.appendChild(el)
        }
      })
      break
    case 'R':
      // TODO consider `attackedFrom`
      Utils.locationsToRetreat(board, $$.U(loc).unit, null).forEach(location => {
        const el = document.createElement('option')
        el.innerHTML = location.toString()
        destElem.appendChild(el)
      })
      break
    case 'B':
      destElem.innerHTML += '<option></option>'
      loc.militaryBranches.forEach(x => {
        destElem.innerHTML += `<option>${x.name}</option>`
      })
      break
    default:
      destElem.style.display = 'none'
      break
  }
}

function createOrderItem () {
  const elem = document.createElement('li')
  elem.classList.add('list-group-item')
  elem.classList.add('order')
  return elem
}
function createTargetUnitList (elem) {
  const target = document.createElement('select')
  target.classList.add('order-target')
  target.classList.add('select-picker')
  if (elem.length === 1) {
    target.setAttribute('disabled', 'true')
    target.innerHTML = `<option>${elem[0]}</option>`
  } else {
    target.innerHTML = '<option></option>'
    elem.forEach(el => {
      target.innerHTML += `<option>${el}</option>`
    })
  }
  target.addEventListener('change', (e) => {
    updateDestLists(e.target.parentNode)
    visualizeOrders()
  })
  return target
}
function createOrderTypeList (types) {
  const target = document.createElement('select')
  target.classList.add('order-type')
  target.classList.add('select-picker')
  if (types.length === 1) {
    target.setAttribute('disabled', 'true')
    target.innerHTML = `<option value="${types[0][0]}">${types[0][1]}</option>`
  } else {
    target.innerHTML = '<option></option>'
    types.forEach(el => {
      target.innerHTML += `<option value="${el[0]}">${el[1]}</option>`
    })
  }
  target.addEventListener('change', (e) => {
    updateDestLists(e.target.parentNode)
    visualizeOrders()
  })
  return target
}
function createDestList () {
  const target = document.createElement('select')
  target.classList.add('order-dest')
  target.classList.add('select-picker')
  target.addEventListener('change', () => visualizeOrders())
  return target
}

function createOrderList () {
  // Clear list
  board.map.powers.forEach(power => {
    const powerPanel = document.getElementById(`order-${power}`)
    while (powerPanel.firstChild) powerPanel.removeChild(powerPanel.firstChild)
  })

  // Create list
  board.map.powers.forEach(power => {
    const powerPanel = document.getElementById(`order-${power}`)
    switch (board.state.phase) {
      case Phase.Movement:
        (() => {
          const us = [...board.units].filter(x => x.power === power)
          us.forEach(unit => {
            const elem = createOrderItem()
            const target = createTargetUnitList([unit.location])
            const tpe =
              createOrderTypeList([['M', '&#x2192'], ['S', 'S'], ['C', 'C'], ['H', 'H']])
            const dest = createDestList()
            elem.appendChild(target)
            elem.appendChild(tpe)
            elem.appendChild(dest)
            powerPanel.appendChild(elem)
          })
        })()
        break
      case Phase.Retreat:
        (() => {
          const us = [...board.units].filter(x => x.power === power)
          us.forEach(unit => {
            if (!board.unitStatuses.get(unit)) return
            const elem = createOrderItem()
            const target = createTargetUnitList([unit.location])
            const tpe =
              createOrderTypeList([['R', '&#x2192'], ['D', 'Disband']])
            const dest = createDestList()
            elem.appendChild(target)
            elem.appendChild(tpe)
            elem.appendChild(dest)
            powerPanel.appendChild(elem)
          })
        })()
        break
      case Phase.Build:
        (() => {
          const num = Utils.numberOfBuildableUnits(board).get(power) || 0
          if (num > 0) {
            // Build
            const candidates =
              [...board.map.locations].filter(l => {
                return l.province.isSupplyCenter && l.province.homeOf === power
              })
            for (let i = 0; i < num; i++) {
              const elem = createOrderItem()
              const target = createTargetUnitList(candidates)
              const tpe =
                createOrderTypeList([['B', 'Build']])
              const dest = createDestList()
              elem.appendChild(target)
              elem.appendChild(tpe)
              elem.appendChild(dest)
              powerPanel.appendChild(elem)
            }
          } else if (num < 0) {
            // Disband
            const us = [...board.units].filter(x => x.power === power)
            for (let i = 0; i < -num; i++) {
              const elem = createOrderItem()
              const target = createTargetUnitList(us.map(u => u.location))
              const tpe =
                createOrderTypeList([['D', 'Disband']])
              const dest = createDestList()
              dest.style.display = 'none'
              elem.appendChild(target)
              elem.appendChild(tpe)
              elem.appendChild(dest)
              powerPanel.appendChild(elem)
            }
          }
        })()
        break
    }
  })
}

function createOrderPanel () {
  const ordersElem = document.getElementById('orders')
  board.map.powers.forEach(power => {
    const panel = document.createElement('div')
    panel.classList.add('panel')
    panel.classList.add('panel-default')
    const header = document.createElement('div')
    header.classList.add('panel-heading')
    header.innerHTML = power
    const body = document.createElement('div')
    body.classList.add('pael-body')
    body.innerHTML = `<ul class="list-group" id="order-${power}"></ul>`

    panel.appendChild(header)
    panel.appendChild(body)
    ordersElem.appendChild(panel)
  })
}

function resolve () {
  // Resolve orders
  const r = variant.rule.resolve(board, getOrders())

  const resultBody = document.getElementById('result-body')
  while (resultBody.firstChild) resultBody.removeChild(resultBody.firstChild)

  // Show error message
  if (r.err) {
    const errorElem = document.getElementById('result-error')
    errorElem.innerHTML = r.err.toString()
    $('#error-dialog').modal('show')
    return
  }

  const result = r.result

  // Show results
  result.results.forEach(result => {
    const tr = document.createElement('tr')
    const u = document.createElement('td')
    u.setAttribute('scope', 'row')
    u.innerHTML = stringify(result.target)
    const r = document.createElement('td')
    if (result.result === Result.Success) {
      r.innerHTML += `<span class="label label-success">${Result[result.result]}</span>`
    } else if (result.result === Result.Dislodged) {
      r.innerHTML += `<span class="label label-danger">${Result[result.result]}</span>`
    } else {
      r.innerHTML += `<span class="label label-warning">${Result[result.result]}</span>`
    }

    if (result.replacedBy) {
      r.innerHTML += ` (replaced by ${result.replacedBy}: ${result.invalidReason})`
    }
    tr.appendChild(u)
    tr.appendChild(r)
    resultBody.appendChild(tr)
  })

  // Visualize the next board
  if (result.isFinished) {
    alert('Game is finished')
    return
  }

  board = result.board
  $$ = new Helper(board)

  // Update the UI
  viz.visualizeBoard(result.board)
  createOrderList()
  visualizeOrders()
}

function adjustMap () {
  const mapElem = document.getElementById('map')
  const rect = document.getElementById('map-col').getBoundingClientRect()
  const width = rect.right - rect.left - 30
  mapElem.width = width
}

function initialize () {
  const mapElem = document.getElementById('map')

  // Get svg images
  const map = mapElem.contentDocument
  const tmp = map.createElementNS('http://www.w3.org/2000/svg', 'g')
  const armyHTML = document.getElementById('army').contentDocument.getElementsByTagName('g')[0].outerHTML
  tmp.innerHTML = armyHTML
  const army = tmp.children[0].cloneNode(true)
  const fleetHTML = document.getElementById('fleet').contentDocument.getElementsByTagName('g')[0].outerHTML
  tmp.innerHTML = fleetHTML
  const fleet = tmp.children[0].cloneNode(true)
  tmp.remove()

  // Create visualizer instance
  const svgs = {
    map: map,
    army: army,
    fleet: fleet
  }
  const colors = {
    power: (power) => {
      switch (power) {
        case Power.England:
          return 'rgb(239, 154, 154)'
        case Power.Russia:
          return 'rgb(206, 147, 216)'
        case Power.France:
          return 'rgb(144, 202, 249)'
        case Power.Germany:
          return 'rgb(150, 150, 150)'
        case Power.Italy:
          return 'rgb(159, 168, 218)'
        case Power.Austria:
          return 'rgb(255, 224, 130)'
        case Power.Turkey:
          return 'rgb(255, 171, 145)'
      }
    }
  }
  const stringify = {
    fromState: (state) => `${state.turn.year}-${Season[state.turn.season]} (${Phase[state.phase]})`
  }
  const configs = {}
  viz = new vizdip.StandardRuleVisualizer(svgs, board.map, colors, stringify, configs)

  // Initialize interfaces
  createOrderPanel()
  createOrderList()
  viz.visualizeBoard(board)

  // Add event listeners
  document.getElementById('resolve').addEventListener('click', () => resolve())

  // Adjust width of map image
  document.getElementById('map-col').addEventListener('resize', adjustMap)
  adjustMap()

  document.getElementById('unit-images').style.display = 'none'
}

window.initialize = initialize
<|endoftext|>


<commit_before>$( document ).ready(function() {
	Map.init();
	Map.makeInfo();
});

/**
 * Orienting my object.
 */
var Map = new Object();

Map.map = null; // Google Maps map object, which can be acted on
Map.dom = null; // handle to the element containing the generated map

/**
 * Generate a map centered on GCCIS at RIT with minimal UI elements.
 */
Map.init = function() {
	var mapOptions = {
		center: new google.maps.LatLng(43.084710, -77.679780),
		zoom: 12,
		disableDefaultUI: true,
		mapTypeControl: true,
		streetViewControl: true
	};

	// generate the map and insert it in #map-canvas
	Map.map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

	// get ahold of the map element
	Map.dom = document.getElementById('map-canvas');
}

/**
 * Generate a map Marker.
 *
 * Pretty boring, just for demonstration. Will most likely be removed at release.
 */
Map.makeMarker = function() {
	// setup the Marker
	var marker = new google.maps.Marker({
        position: new google.maps.LatLng(43.084710, -77.679780),
        title: "Hello world"
    });

    // create the Marker
    marker.setMap(Map.map);
}

/**
 * Generate an InfoWindow, which is sort of an extended Marker.
 *
 * It doesn't do many tricks though, so this is only temporary/for demonstration.
 * This will be replaced with an InfoBox.
 */
Map.makeInfo = function() {
	// the content that goes in the InfoWindow
	var infocontent = '\

\
    		\

\
    ';

    // setup the InfoWindow
    var info = new google.maps.InfoWindow({
    	disableAutoPan: true,
    	hasCloseButton: false,
    	position: new google.maps.LatLng(43.084710, -77.679780),
    	content: infocontent
    });

    // create the InfoWindow
    info.open(Map.map);
}<commit_msg>Google Maps' InfoWindow replaced with InfoBox.<commit_after>$( document ).ready(function() {
	Map.init();
	Map.makeInfo();
});

/**
 * Orienting my object.
 */
var Map = new Object();

Map.map = null; // Google Maps map object, which can be acted on
Map.dom = null; // handle to the element containing the generated map

/**
 * Generate a map centered on GCCIS at RIT with minimal UI elements.
 */
Map.init = function() {
	var mapOptions = {
		center: new google.maps.LatLng(43.084710, -77.679780),
		zoom: 12,
		disableDefaultUI: true,
		mapTypeControl: true,
		streetViewControl: true
	};

	// generate the map and insert it in #map-canvas
	Map.map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);

	// get ahold of the map element
	Map.dom = document.getElementById('map-canvas');
}

/**
 * Generate a map Marker.
 *
 * Pretty boring, just for demonstration. Will most likely be removed at release.
 */
Map.makeMarker = function() {
	// setup the Marker
	var marker = new google.maps.Marker({
        position: new google.maps.LatLng(43.084710, -77.679780),
        title: "Hello world"
    });

    // create the Marker
    marker.setMap(Map.map);
}

/**
 * Generate an InfoBox, which is like a Marker holding an image thumbnail.
 */
Map.makeInfo = function() {
	// the content that goes in the InfoBox
	var infocontent = '\

\
    		\

\
    ';

    // setup the InfoBox
    var info = new InfoBox({
    	disableAutoPan: true,
    	hasCloseButton: false,
    	position: new google.maps.LatLng(43.084710, -77.679780),
    	content: infocontent
    });

    // create the InfoBox
    info.open(Map.map);
}<|endoftext|>


<commit_before>var ASTRAL = {}

// sets up the Astral flash streaming module and instructs it to upload to and
function previewStream(streamSlug) {
    $.ajax({
        url: "http://localhost:8000/settings",
        success: function(data) {
            ASTRAL.astral_streaming_module.setupAndStream(
                ASTRAL.userRole,
                streamSlug,
                "",
                "rtmp://localhost:" + data.rtmp_port + "/"
                    + data.rtmp_resource,
                "");
        },
        dataType: 'jsonp'
    });
}

function changeStreamStatus(streamSlug, streamingStatus) {
    $.ajax({
        type: "PUT",
        data: JSON.stringify({"streaming": streamingStatus}),
        url: "http://localhost:8000/stream/" + streamSlug,
        dataType: 'json'
    });
}

function stopStream() {
    ASTRAL.astral_streaming_module.stopStreaming();
    $.ajax({
        type: "DELETE",
        url: "http://localhost:8000/stream/" + streamSlug,
        dataType: 'jsonp'
    });
}

// sets up the Astral flash streaming module and instructs it to connect to a
// specific port on localhost, an HTTP tunnel to a remote RTMP server
function startConsuming(streamSlug) {
    $.ajax({
        type: "POST",
        url: "http://localhost:8000/stream/" + streamSlug + "/tickets",
        success: function(data) {
            $.ajax({
                url: "http://localhost:8000/settings",
                success: function(data) {
                    ASTRAL.astral_streaming_module.setupAndStream(
                        ASTRAL.userRole,
                        streamSlug,
                        "",
                        "",
                        "rtmp://localhost:" + data.rtmp_tunnel_port + "/"
                            + data.rtmp_resource);
                },
                dataType: 'jsonp'
            });
        }
    });
}

function stopConsuming(streamSlug) {
    $.ajax({
        type: "DELETE",
        url: "http://localhost:8000/stream/" + streamSlug + "/ticket",
        dataType: 'jsonp'
    });
}

// used by flash to display error messages
function displayFromFlash(msg) {
    $("div#streaming_flash_error").text(msg);
}

$(document).ready(function() {
    WebFont.load({
        custom: {
            families: ['star-lit-night'],
            urls: ['/css/font.css']
        }
    });

    ASTRAL.astral_streaming_module = document.getElementById("astral");
    // Either publisher or consumer
    ASTRAL.userRole = $("#publish_start").length != 0 ?
            "publisher" : "consumer";
    if (ASTRAL.astral_streaming_module) {
        // get the stream's unique identifier on the network
        var streamSlug = $("div#slug").text();
        if(streamSlug && ASTRAL.userRole === "publisher") {
            previewStream(streamSlug);
            $("#streaming_notice").text("This is a preview - the video " +
                "will not be streaming until you being publishing.");
        }

        $("#consume_start").removeClass("hidden");
        $("#publish_start").removeClass("hidden");
        $("#publish_stop").removeClass("hidden");

        $("#publish_start").click(function(e) {
            $("#publish_pause").removeClass("hidden");
            $("#publish_start").addClass("hidden");
            $("#streaming_notice").text("Video is streaming to the network.");

            changeStreamStatus(streamSlug, true);
            return false;
        });

        $("#publish_pause").click(function(e) {
            $("#publish_start").removeClass("hidden");
            $("#publish_pause").addClass("hidden");
            $("#streaming_notice").text("Streaming is now paused.");
            changeStreamStatus(streamSlug, false);
            return false;
        });

        $("#publish_stop").click(function(e) {
            $("#consume_start").removeClass("hidden");
            $("#publish_stop").addClass("hidden");
            $("#publish_pause").addClass("hidden");
            $("#publish_start").addClass("hidden");
            $("#streaming_notice").text(
                    "Stopped publishing. Please reload page.");
            stopStream();
            return false;
        });

        $("#consume_stop").click(function(e) {
            $("#consume_start").removeClass("hidden");
            $("#consume_stop").addClass("hidden");
            $("#streaming_notice").text("Stopped streaming.");
            stopConsuming(streamSlug);
            return false;
        });

        $("#consume_start").click(function(e) {
            $("#streaming_notice").text("Streaming from network.");
            $("#consume_start").addClass("hidden");
            $("#consume_stop").removeClass("hidden");
            startConsuming(streamSlug);
            return false;
        });
    }
});
<commit_msg>Load ticket port from the node.<commit_after>var ASTRAL = {}

// sets up the Astral flash streaming module and instructs it to upload to and
function previewStream(streamSlug) {
    $.ajax({
        url: "http://localhost:8000/settings",
        success: function(data) {
            ASTRAL.astral_streaming_module.setupAndStream(
                ASTRAL.userRole,
                streamSlug,
                "",
                "rtmp://localhost:" + data.rtmp_port + "/"
                    + data.rtmp_resource,
                "");
        },
        dataType: 'jsonp'
    });
}

function changeStreamStatus(streamSlug, streamingStatus) {
    $.ajax({
        type: "PUT",
        data: JSON.stringify({"streaming": streamingStatus}),
        url: "http://localhost:8000/stream/" + streamSlug,
        dataType: 'json'
    });
}

function stopStream() {
    ASTRAL.astral_streaming_module.stopStreaming();
    $.ajax({
        type: "DELETE",
        url: "http://localhost:8000/stream/" + streamSlug,
        dataType: 'jsonp'
    });
}

// sets up the Astral flash streaming module and instructs it to connect to a
// specific port on localhost, an HTTP tunnel to a remote RTMP server
function startConsuming(streamSlug) {
    $.ajax({
        type: "POST",
        url: "http://localhost:8000/stream/" + streamSlug + "/tickets",
        async: false
    });

    $.ajax({
        url: "http://localhost:8000/stream/" + streamSlug + "/ticket",
        success: function(ticketData) {
            $.ajax({
                url: "http://localhost:8000/settings",
                success: function(settings) {
                    ASTRAL.astral_streaming_module.setupAndStream(
                        ASTRAL.userRole,
                        streamSlug,
                        "",
                        "",
                        "rtmp://localhost:" + ticketData.ticket.source_port + "/"
                            + settings.rtmp_resource);
                },
                dataType: 'jsonp'
            });
        },
        dataType: "jsonp"
    });
}

function stopConsuming(streamSlug) {
    $.ajax({
        type: "DELETE",
        url: "http://localhost:8000/stream/" + streamSlug + "/ticket",
        dataType: 'jsonp'
    });
}

// used by flash to display error messages
function displayFromFlash(msg) {
    $("div#streaming_flash_error").text(msg);
}

$(document).ready(function() {
    WebFont.load({
        custom: {
            families: ['star-lit-night'],
            urls: ['/css/font.css']
        }
    });

    ASTRAL.astral_streaming_module = document.getElementById("astral");
    // Either publisher or consumer
    ASTRAL.userRole = $("#publish_start").length != 0 ?
            "publisher" : "consumer";
    if (ASTRAL.astral_streaming_module) {
        // get the stream's unique identifier on the network
        var streamSlug = $("div#slug").text();
        if(streamSlug && ASTRAL.userRole === "publisher") {
            previewStream(streamSlug);
            $("#streaming_notice").text("This is a preview - the video " +
                "will not be streaming until you being publishing.");
        }

        $("#consume_start").removeClass("hidden");
        $("#publish_start").removeClass("hidden");
        $("#publish_stop").removeClass("hidden");

        $("#publish_start").click(function(e) {
            $("#publish_pause").removeClass("hidden");
            $("#publish_start").addClass("hidden");
            $("#streaming_notice").text("Video is streaming to the network.");

            changeStreamStatus(streamSlug, true);
            return false;
        });

        $("#publish_pause").click(function(e) {
            $("#publish_start").removeClass("hidden");
            $("#publish_pause").addClass("hidden");
            $("#streaming_notice").text("Streaming is now paused.");
            changeStreamStatus(streamSlug, false);
            return false;
        });

        $("#publish_stop").click(function(e) {
            $("#consume_start").removeClass("hidden");
            $("#publish_stop").addClass("hidden");
            $("#publish_pause").addClass("hidden");
            $("#publish_start").addClass("hidden");
            $("#streaming_notice").text(
                    "Stopped publishing. Please reload page.");
            stopStream();
            return false;
        });

        $("#consume_stop").click(function(e) {
            $("#consume_start").removeClass("hidden");
            $("#consume_stop").addClass("hidden");
            $("#streaming_notice").text("Stopped streaming.");
            stopConsuming(streamSlug);
            return false;
        });

        $("#consume_start").click(function(e) {
            $("#streaming_notice").text("Streaming from network.");
            $("#consume_start").addClass("hidden");
            $("#consume_stop").removeClass("hidden");
            startConsuming(streamSlug);
            return false;
        });
    }
});
<|endoftext|>