<commit_before>import { storiesOf } from '@storybook/html';
import { withHits } from '../.storybook/decorators';

storiesOf('CurrentRefinements', module)
  .add(
    'default',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const refinementListContainer = document.createElement('div');
      container.appendChild(refinementListContainer);
      const numericMenuContainer = document.createElement('div');
      container.appendChild(numericMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );

      search.addWidget(
        instantsearch.widgets.refinementList({
          container: refinementListContainer,
          attribute: 'brand',
        })
      );

      search.addWidget(
        instantsearch.widgets.numericMenu({
          container: numericMenuContainer,
          attribute: 'price',
          items: [
            { label: 'All' },
            { end: 10, label: '≤ $10' },
            { start: 10, end: 100, label: '$10–$100' },
            { start: 100, end: 500, label: '$100–$500' },
            { start: 500, label: '≥ $500' },
          ],
        })
      );
    })
  )
  .add(
    'with refinementList',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const refinementListContainer = document.createElement('div');
      container.appendChild(refinementListContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: {
            brand: ['Google', 'Apple', 'Samsung'],
          },
          disjunctiveFacets: ['brand'],
        })
      );

      search.addWidget(
        instantsearch.widgets.refinementList({
          container: refinementListContainer,
          attribute: 'brand',
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with menu',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const menuContainer = document.createElement('div');
      container.appendChild(menuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          hierarchicalFacetsRefinements: {
            brand: ['Samsung'],
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.menu({
          container: menuContainer,
          attribute: 'brand',
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with hierarchicalMenu',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const hierarchicalMenuContainer = document.createElement('div');
      container.appendChild(hierarchicalMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          hierarchicalFacetsRefinements: {
            'hierarchicalCategories.lvl0': [
              'Cameras & Camcorders > Digital Cameras',
            ],
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.hierarchicalMenu({
          container: hierarchicalMenuContainer,
          attributes: [
            'hierarchicalCategories.lvl0',
            'hierarchicalCategories.lvl1',
          ],
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with toggleRefinement',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const toggleRefinementContainer = document.createElement('div');
      container.appendChild(toggleRefinementContainer);

      search.addWidget(
        instantsearch.widgets.toggleRefinement({
          container: toggleRefinementContainer,
          attribute: 'free_shipping',
          templates: {
            labelText: 'Free Shipping',
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with numericMenu',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const numericMenuContainer = document.createElement('div');
      container.appendChild(numericMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          numericRefinements: { price: { '<=': [10] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.numericMenu({
          container: numericMenuContainer,
          attribute: 'price',
          items: [
            { label: 'All' },
            { end: 10, label: '≤ $10' },
            { start: 10, end: 100, label: '$10–$100' },
            { start: 100, end: 500, label: '$100–$500' },
            { start: 500, label: '≥ $500' },
          ],
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with rangeInput',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const rangeInputContainer = document.createElement('div');
      container.appendChild(rangeInputContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          numericRefinements: { price: { '>=': [100], '<=': [500] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.rangeInput({
          container: rangeInputContainer,
          attribute: 'price',
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with only price included',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const toggleRefinementContainer = document.createElement('div');
      container.appendChild(toggleRefinementContainer);
      const numericMenuContainer = document.createElement('div');
      container.appendChild(numericMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          numericRefinements: { price: { '<=': [10] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.numericMenu({
          container: numericMenuContainer,
          attribute: 'price',
          items: [
            { label: 'All' },
            { end: 10, label: '≤ $10' },
            { start: 10, end: 100, label: '$10–$100' },
            { start: 100, end: 500, label: '$100–$500' },
            { start: 500, label: '≥ $500' },
          ],
        })
      );

      search.addWidget(
        instantsearch.widgets.toggleRefinement({
          container: toggleRefinementContainer,
          attribute: 'free_shipping',
          templates: {
            labelText: 'Free Shipping',
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          includedAttributes: ['price'],
        })
      );
    })
  )
  .add(
    'with price and query excluded',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const refinementListContainer = document.createElement('div');
      container.appendChild(refinementListContainer);
      const numericMenuContainer = document.createElement('div');
      container.appendChild(numericMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          excludedAttributes: ['query', 'price'],
        })
      );

      search.addWidget(
        instantsearch.widgets.refinementList({
          container: refinementListContainer,
          attribute: 'brand',
        })
      );

      search.addWidget(
        instantsearch.widgets.numericMenu({
          container: numericMenuContainer,
          attribute: 'price',
          items: [
            { label: 'All' },
            { end: 10, label: '≤ $10' },
            { start: 10, end: 100, label: '$10–$100' },
            { start: 100, end: 500, label: '$100–$500' },
            { start: 500, label: '≥ $500' },
          ],
        })
      );
    })
  )
  .add(
    'with query',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const refinementListContainer = document.createElement('div');
      container.appendChild(refinementListContainer);
      const numericMenuContainer = document.createElement('div');
      container.appendChild(numericMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          excludedAttributes: [],
        })
      );

      search.addWidget(
        instantsearch.widgets.refinementList({
          container: refinementListContainer,
          attribute: 'brand',
        })
      );

      search.addWidget(
        instantsearch.widgets.numericMenu({
          container: numericMenuContainer,
          attribute: 'price',
          items: [
            { label: 'All' },
            { end: 10, label: '≤ $10' },
            { start: 10, end: 100, label: '$10–$100' },
            { start: 100, end: 500, label: '$100–$500' },
            { start: 500, label: '≥ $500' },
          ],
        })
      );
    })
  )
  .add(
    'with transformed items',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);
      const refinementListContainer = document.createElement('div');
      container.appendChild(refinementListContainer);
      const numericMenuContainer = document.createElement('div');
      container.appendChild(numericMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          transformItems: items =>
            items.map(refinementItem => ({
              ...refinementItem,
              refinements: refinementItem.refinements.map(item => ({
                ...item,
                label: item.label.toUpperCase(),
              })),
            })),
        })
      );

      search.addWidget(
        instantsearch.widgets.refinementList({
          container: refinementListContainer,
          attribute: 'brand',
        })
      );

      search.addWidget(
        instantsearch.widgets.numericMenu({
          container: numericMenuContainer,
          attribute: 'price',
          items: [
            { label: 'All' },
            { end: 10, label: '≤ $10' },
            { start: 10, end: 100, label: '$10–$100' },
            { start: 100, end: 500, label: '$100–$500' },
            { start: 500, label: '≥ $500' },
          ],
        })
      );
    })
  );
<commit_msg>refactor(currentRefinements): update stories (#3781)<commit_after>import { storiesOf } from '@storybook/html';
import { withHits } from '../.storybook/decorators';

storiesOf('CurrentRefinements', module)
  .add(
    'default',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with refinementList',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: {
            brand: ['Google', 'Apple', 'Samsung'],
          },
          disjunctiveFacets: ['brand'],
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with menu',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      currentRefinementsContainer.style.marginBottom = '10px';
      container.appendChild(currentRefinementsContainer);
      const menuContainer = document.createElement('div');
      container.appendChild(menuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          hierarchicalFacetsRefinements: {
            categories: ['Audio'],
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.menu({
          container: menuContainer,
          attribute: 'categories',
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with hierarchicalMenu',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      currentRefinementsContainer.style.marginBottom = '10px';
      container.appendChild(currentRefinementsContainer);
      const hierarchicalMenuContainer = document.createElement('div');
      container.appendChild(hierarchicalMenuContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          hierarchicalFacetsRefinements: {
            'hierarchicalCategories.lvl0': [
              'Cameras & Camcorders > Digital Cameras',
            ],
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.hierarchicalMenu({
          container: hierarchicalMenuContainer,
          attributes: [
            'hierarchicalCategories.lvl0',
            'hierarchicalCategories.lvl1',
          ],
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with toggleRefinement',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      currentRefinementsContainer.style.marginBottom = '10px';
      container.appendChild(currentRefinementsContainer);
      const toggleRefinementContainer = document.createElement('div');
      container.appendChild(toggleRefinementContainer);

      search.addWidget(
        instantsearch.widgets.toggleRefinement({
          container: toggleRefinementContainer,
          attribute: 'free_shipping',
          templates: {
            labelText: 'Free Shipping',
          },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with numericMenu',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          numericRefinements: { price: { '<=': [10] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with rangeInput',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      currentRefinementsContainer.style.marginBottom = '10px';
      container.appendChild(currentRefinementsContainer);
      const rangeInputContainer = document.createElement('div');
      container.appendChild(rangeInputContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          numericRefinements: { price: { '>=': [100], '<=': [500] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.rangeInput({
          container: rangeInputContainer,
          attribute: 'price',
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
        })
      );
    })
  )
  .add(
    'with only price included',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          numericRefinements: { price: { '<=': [10] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          includedAttributes: ['price'],
        })
      );
    })
  )
  .add(
    'with price and query excluded',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          excludedAttributes: ['query', 'price'],
        })
      );
    })
  )
  .add(
    'with query',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          excludedAttributes: [],
        })
      );
    })
  )
  .add(
    'with transformed items',
    withHits(({ search, container, instantsearch }) => {
      const currentRefinementsContainer = document.createElement('div');
      container.appendChild(currentRefinementsContainer);

      search.addWidget(
        instantsearch.widgets.configure({
          disjunctiveFacetsRefinements: { brand: ['Apple', 'Samsung'] },
          disjunctiveFacets: ['brand'],
          numericRefinements: { price: { '>=': [100] } },
        })
      );

      search.addWidget(
        instantsearch.widgets.currentRefinements({
          container: currentRefinementsContainer,
          transformItems: items =>
            items.map(refinementItem => ({
              ...refinementItem,
              refinements: refinementItem.refinements.map(item => ({
                ...item,
                label: item.label.toUpperCase(),
              })),
            })),
        })
      );
    })
  );
<|endoftext|>


<commit_before>
var mongoose = require('./../node_modules/mongoose');

module.exports = mongoose;

var Schema = mongoose.Schema;

var Node_Schema = Schema({
  _id: Number,
  type: String,
  loc: {
    type: Object,
    coordinates: [Number],
    index: "2dsphere"
  },
  version: Number,
  uid: Number,
  user: String,
  changeset: Number,
  timestamp: Date,
  visible: Boolean,
  tags: {}
},{collection: "nodes" });
mongoose.model('node', Node_Schema);

var Way_Schema = Schema({
  _id: Number,
  type: String,
  loc: [{type: Number, ref: "node"}],
  version: Number,
  uid: Number,
  user: String,
  changeset: Number,
  timestamp: Date,
  visible: Boolean,
  tags: {}
},{collection: "ways" });
mongoose.model('way', Way_Schema);

var Relation_Schema = Schema({
  _id: Number,
  type: String,
  loc: [{}],
  version: Number,
  uid: Number,
  user: String,
  changeset: Number,
  timestamp: Date,
  visible: Boolean,
  tags: {}
},{collection: "relations" });
mongoose.model('relation', Relation_Schema);

<commit_msg>changing from ref to string _id<commit_after>
var mongoose = require('./../node_modules/mongoose');

module.exports = mongoose;

var Schema = mongoose.Schema;

var Node_Schema = Schema({
  _id: Number,
  type: String,
  loc: {
    type: Object,
    coordinates: [Number],
    index: "2dsphere"
  },
  version: Number,
  uid: Number,
  user: String,
  changeset: Number,
  timestamp: Date,
  visible: Boolean,
  tags: {}
},{collection: "nodes" });
mongoose.model('node', Node_Schema);

var Way_Schema = Schema({
  _id: Number,
  type: String,
  loc: [String],
  version: Number,
  uid: Number,
  user: String,
  changeset: Number,
  timestamp: Date,
  visible: Boolean,
  tags: {}
},{collection: "ways" });
mongoose.model('way', Way_Schema);

var Relation_Schema = Schema({
  _id: Number,
  type: String,
  loc: [String],
  version: Number,
  uid: Number,
  user: String,
  changeset: Number,
  timestamp: Date,
  visible: Boolean,
  tags: {}
},{collection: "relations" });
mongoose.model('relation', Relation_Schema);

<|endoftext|>


<commit_before>/**
 * `modules/tagmanager` data store: settings.
 *
 * 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 as CORE_FORMS } from '../../../googlesitekit/datastore/forms';
import { TYPE_MODULES } from '../../../components/data/constants';
import { invalidateCacheGroup } from '../../../components/data/invalidate-cache-group';
import {
	isValidAccountID,
	isValidContainerID,
	isValidInternalContainerID,
	isValidContainerSelection,
	isValidContainerName,
	isUniqueContainerName,
} from '../util';
import { STORE_NAME, CONTAINER_CREATE, CONTEXT_WEB, CONTEXT_AMP, FORM_SETUP } from './constants';
import { STORE_NAME as CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { STORE_NAME as CORE_SITE } from '../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as MODULES_ANALYTICS } from '../../analytics/datastore/constants';
import { createStrictSelect } from '../../../googlesitekit/data/utils';

const { createRegistrySelector, createRegistryControl } = Data;

// Actions
const SUBMIT_CHANGES = 'SUBMIT_CHANGES';
const START_SUBMIT_CHANGES = 'START_SUBMIT_CHANGES';
const FINISH_SUBMIT_CHANGES = 'FINISH_SUBMIT_CHANGES';

export const initialState = {
	isDoingSubmitChanges: false,
};

export const actions = {
	/**
	 * Submits all changes currently present in the client, persisting them on the server.
	 *
	 * @since 1.11.0
	 *
	 * @return {Object} Empty object on success, object with `error` property on failure.
	 */
	*submitChanges() {
		const registry = yield Data.commonActions.getRegistry();

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

		const result = yield {
			payload: {},
			type: SUBMIT_CHANGES,
		};

		if ( result.error ) {
			yield registry.dispatch( STORE_NAME ).receiveError( result.error, 'submitChanges', [] );
		}

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

		return result;
	},
};

export const controls = {
	[ SUBMIT_CHANGES ]: createRegistryControl( ( { select, dispatch } ) => async () => {
		const accountID = select( STORE_NAME ).getAccountID();
		const containerID = select( STORE_NAME ).getContainerID();

		if ( containerID === CONTAINER_CREATE ) {
			const containerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'containerName' );
			const { response: container, error } = await dispatch( STORE_NAME ).createContainer( accountID, CONTEXT_WEB, { containerName } );

			if ( error ) {
				return { error };
			}

			await dispatch( STORE_NAME ).setContainerID( container.publicId ); // eslint-disable-line sitekit/camelcase-acronyms
			await dispatch( STORE_NAME ).setInternalContainerID( container.containerId ); // eslint-disable-line sitekit/camelcase-acronyms
		}

		const ampContainerID = select( STORE_NAME ).getAMPContainerID();

		if ( ampContainerID === CONTAINER_CREATE ) {
			const containerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'ampContainerName' );
			const { response: container, error } = await dispatch( STORE_NAME ).createContainer( accountID, CONTEXT_AMP, { containerName } );

			if ( error ) {
				return { error };
			}

			await dispatch( STORE_NAME ).setAMPContainerID( container.publicId ); // eslint-disable-line sitekit/camelcase-acronyms
			await dispatch( STORE_NAME ).setInternalAMPContainerID( container.containerId ); // eslint-disable-line sitekit/camelcase-acronyms
		}

		// This action shouldn't be called if settings haven't changed,
		// but this prevents errors in tests.
		if ( select( STORE_NAME ).haveSettingsChanged() ) {
			const { error } = await dispatch( STORE_NAME ).saveSettings();

			if ( error ) {
				return { error };
			}
		}

		await API.invalidateCache( 'modules', 'tagmanager' );
		// TODO: Remove once legacy dataAPI is no longer used.
		invalidateCacheGroup( TYPE_MODULES, 'tagmanager' );

		return {};
	} ),
};

export const reducer = ( state, { type } ) => {
	switch ( type ) {
		case START_SUBMIT_CHANGES: {
			return {
				...state,
				isDoingSubmitChanges: true,
			};
		}

		case FINISH_SUBMIT_CHANGES: {
			return {
				...state,
				isDoingSubmitChanges: false,
			};
		}

		default:
			return state;
	}
};

export const resolvers = {};

export const selectors = {
	/**
	 * Checks if changes can be submitted.
	 *
	 * @since 1.11.0
	 *
	 * @return {boolean} `true` if can submit changes, otherwise false.
	 */
	canSubmitChanges: createRegistrySelector( ( select ) => () => {
		try {
			validateCanSubmitChanges( select );
			return true;
		} catch {
			return false;
		}
	} ),

	/**
	 * Checks whether changes are currently being submitted.
	 *
	 * @since 1.11.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {boolean} `true` if submitting, `false` if not.
	 */
	isDoingSubmitChanges( state ) {
		return !! state.isDoingSubmitChanges;
	},
};

/**
 * Validates whether changes can be submitted or not.
 *
 * If changes cannot be submitted, an appropriate error is thrown.
 *
 * @since 1.18.0
 * @private
 *
 * @param {Function} select The current registry.select instance.
 */
export const validateCanSubmitChanges = ( select ) => {
	const strictSelect = createStrictSelect( select );
	// Strict select will cause all selector functions to throw an error
	// if `undefined` is returned, otherwise it behaves the same as `select`.
	// This ensures that the selector returns `false` until all data dependencies are resolved.
	const {
		getAccountID,
		getContainerID,
		getAMPContainers,
		getAMPContainerID,
		getInternalContainerID,
		getInternalAMPContainerID,
		getSingleAnalyticsPropertyID,
		getWebContainers,
		hasAnyAnalyticsPropertyID,
		hasExistingTag,
		hasExistingTagPermission,
		hasMultipleAnalyticsPropertyIDs,
		haveSettingsChanged,
		isDoingSubmitChanges,
	} = strictSelect( STORE_NAME );
	const {
		isAMP,
		isSecondaryAMP,
	} = strictSelect( CORE_SITE );
	const { isModuleActive } = strictSelect( CORE_MODULES );
	const { getPropertyID } = strictSelect( MODULES_ANALYTICS );

	const accountID = getAccountID();
	const ampContainerID = getAMPContainerID();

	// Note: these error messages are referenced in test assertions.
	invariant( ! isDoingSubmitChanges(), 'cannot submit changes while submitting changes' );
	invariant( haveSettingsChanged(), 'cannot submit changes if settings have not changed' );
	invariant( isValidAccountID( accountID ), 'a valid accountID is required to submit changes' );

	const containerID = getContainerID();
	if ( containerID === CONTAINER_CREATE ) {
		const containerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'containerName' );
		invariant( isValidContainerName( containerName ), `a container name is invalid` );

		const webContainers = getWebContainers( accountID );
		invariant( isUniqueContainerName( containerName, webContainers ), `a container with "${ containerName }" name already exists` );
	}

	if ( ampContainerID === CONTAINER_CREATE ) {
		const ampContainerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'ampContainerName' );
		invariant( isValidContainerName( ampContainerName ), `a container name is invalid` );

		const ampContainers = getAMPContainers( accountID );
		invariant( isUniqueContainerName( ampContainerName, ampContainers ), `an AMP container with "${ ampContainerName }" name already exists` );
	}

	if ( isAMP() ) {
		// If AMP is active, the AMP container ID must be valid, regardless of mode.
		invariant( isValidContainerSelection( ampContainerID ), 'a valid ampContainerID selection is required to submit changes' );
		// If AMP is active, and a valid AMP container ID is selected, the internal ID must also be valid.
		if ( isValidContainerID( ampContainerID ) ) {
			invariant( isValidInternalContainerID( getInternalAMPContainerID() ), 'a valid internalAMPContainerID is required to submit changes' );
		}
	}

	if ( ! isAMP() || isSecondaryAMP() ) {
		// If AMP is not active, or in a secondary mode, validate the web container IDs.
		invariant( isValidContainerSelection( getContainerID() ), 'a valid containerID selection is required to submit changes' );
		// If a valid container ID is selected, the internal ID must also be valid.
		if ( isValidContainerID( getContainerID() ) ) {
			invariant( isValidInternalContainerID( getInternalContainerID() ), 'a valid internalContainerID is required to submit changes' );
		}
	}

	invariant( ! hasMultipleAnalyticsPropertyIDs(), 'containers with Analytics tags must reference a single property ID to submit changes' );

	if ( isModuleActive( 'analytics' ) && getPropertyID() && hasAnyAnalyticsPropertyID() ) {
		invariant( getSingleAnalyticsPropertyID() === getPropertyID(), 'single GTM Analytics property ID must match Analytics property ID' );
	}

	// Do existing tag check last.
	if ( hasExistingTag() ) {
		invariant( hasExistingTagPermission(), 'existing tag permission is required to submit changes' );
	}
};

export default {
	initialState,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Update assets/js/modules/tagmanager/datastore/settings.js<commit_after>/**
 * `modules/tagmanager` data store: settings.
 *
 * 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 as CORE_FORMS } from '../../../googlesitekit/datastore/forms/constants';
import { TYPE_MODULES } from '../../../components/data/constants';
import { invalidateCacheGroup } from '../../../components/data/invalidate-cache-group';
import {
	isValidAccountID,
	isValidContainerID,
	isValidInternalContainerID,
	isValidContainerSelection,
	isValidContainerName,
	isUniqueContainerName,
} from '../util';
import { STORE_NAME, CONTAINER_CREATE, CONTEXT_WEB, CONTEXT_AMP, FORM_SETUP } from './constants';
import { STORE_NAME as CORE_MODULES } from '../../../googlesitekit/modules/datastore/constants';
import { STORE_NAME as CORE_SITE } from '../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as MODULES_ANALYTICS } from '../../analytics/datastore/constants';
import { createStrictSelect } from '../../../googlesitekit/data/utils';

const { createRegistrySelector, createRegistryControl } = Data;

// Actions
const SUBMIT_CHANGES = 'SUBMIT_CHANGES';
const START_SUBMIT_CHANGES = 'START_SUBMIT_CHANGES';
const FINISH_SUBMIT_CHANGES = 'FINISH_SUBMIT_CHANGES';

export const initialState = {
	isDoingSubmitChanges: false,
};

export const actions = {
	/**
	 * Submits all changes currently present in the client, persisting them on the server.
	 *
	 * @since 1.11.0
	 *
	 * @return {Object} Empty object on success, object with `error` property on failure.
	 */
	*submitChanges() {
		const registry = yield Data.commonActions.getRegistry();

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

		const result = yield {
			payload: {},
			type: SUBMIT_CHANGES,
		};

		if ( result.error ) {
			yield registry.dispatch( STORE_NAME ).receiveError( result.error, 'submitChanges', [] );
		}

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

		return result;
	},
};

export const controls = {
	[ SUBMIT_CHANGES ]: createRegistryControl( ( { select, dispatch } ) => async () => {
		const accountID = select( STORE_NAME ).getAccountID();
		const containerID = select( STORE_NAME ).getContainerID();

		if ( containerID === CONTAINER_CREATE ) {
			const containerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'containerName' );
			const { response: container, error } = await dispatch( STORE_NAME ).createContainer( accountID, CONTEXT_WEB, { containerName } );

			if ( error ) {
				return { error };
			}

			await dispatch( STORE_NAME ).setContainerID( container.publicId ); // eslint-disable-line sitekit/camelcase-acronyms
			await dispatch( STORE_NAME ).setInternalContainerID( container.containerId ); // eslint-disable-line sitekit/camelcase-acronyms
		}

		const ampContainerID = select( STORE_NAME ).getAMPContainerID();

		if ( ampContainerID === CONTAINER_CREATE ) {
			const containerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'ampContainerName' );
			const { response: container, error } = await dispatch( STORE_NAME ).createContainer( accountID, CONTEXT_AMP, { containerName } );

			if ( error ) {
				return { error };
			}

			await dispatch( STORE_NAME ).setAMPContainerID( container.publicId ); // eslint-disable-line sitekit/camelcase-acronyms
			await dispatch( STORE_NAME ).setInternalAMPContainerID( container.containerId ); // eslint-disable-line sitekit/camelcase-acronyms
		}

		// This action shouldn't be called if settings haven't changed,
		// but this prevents errors in tests.
		if ( select( STORE_NAME ).haveSettingsChanged() ) {
			const { error } = await dispatch( STORE_NAME ).saveSettings();

			if ( error ) {
				return { error };
			}
		}

		await API.invalidateCache( 'modules', 'tagmanager' );
		// TODO: Remove once legacy dataAPI is no longer used.
		invalidateCacheGroup( TYPE_MODULES, 'tagmanager' );

		return {};
	} ),
};

export const reducer = ( state, { type } ) => {
	switch ( type ) {
		case START_SUBMIT_CHANGES: {
			return {
				...state,
				isDoingSubmitChanges: true,
			};
		}

		case FINISH_SUBMIT_CHANGES: {
			return {
				...state,
				isDoingSubmitChanges: false,
			};
		}

		default:
			return state;
	}
};

export const resolvers = {};

export const selectors = {
	/**
	 * Checks if changes can be submitted.
	 *
	 * @since 1.11.0
	 *
	 * @return {boolean} `true` if can submit changes, otherwise false.
	 */
	canSubmitChanges: createRegistrySelector( ( select ) => () => {
		try {
			validateCanSubmitChanges( select );
			return true;
		} catch {
			return false;
		}
	} ),

	/**
	 * Checks whether changes are currently being submitted.
	 *
	 * @since 1.11.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {boolean} `true` if submitting, `false` if not.
	 */
	isDoingSubmitChanges( state ) {
		return !! state.isDoingSubmitChanges;
	},
};

/**
 * Validates whether changes can be submitted or not.
 *
 * If changes cannot be submitted, an appropriate error is thrown.
 *
 * @since 1.18.0
 * @private
 *
 * @param {Function} select The current registry.select instance.
 */
export const validateCanSubmitChanges = ( select ) => {
	const strictSelect = createStrictSelect( select );
	// Strict select will cause all selector functions to throw an error
	// if `undefined` is returned, otherwise it behaves the same as `select`.
	// This ensures that the selector returns `false` until all data dependencies are resolved.
	const {
		getAccountID,
		getContainerID,
		getAMPContainers,
		getAMPContainerID,
		getInternalContainerID,
		getInternalAMPContainerID,
		getSingleAnalyticsPropertyID,
		getWebContainers,
		hasAnyAnalyticsPropertyID,
		hasExistingTag,
		hasExistingTagPermission,
		hasMultipleAnalyticsPropertyIDs,
		haveSettingsChanged,
		isDoingSubmitChanges,
	} = strictSelect( STORE_NAME );
	const {
		isAMP,
		isSecondaryAMP,
	} = strictSelect( CORE_SITE );
	const { isModuleActive } = strictSelect( CORE_MODULES );
	const { getPropertyID } = strictSelect( MODULES_ANALYTICS );

	const accountID = getAccountID();
	const ampContainerID = getAMPContainerID();

	// Note: these error messages are referenced in test assertions.
	invariant( ! isDoingSubmitChanges(), 'cannot submit changes while submitting changes' );
	invariant( haveSettingsChanged(), 'cannot submit changes if settings have not changed' );
	invariant( isValidAccountID( accountID ), 'a valid accountID is required to submit changes' );

	const containerID = getContainerID();
	if ( containerID === CONTAINER_CREATE ) {
		const containerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'containerName' );
		invariant( isValidContainerName( containerName ), `a container name is invalid` );

		const webContainers = getWebContainers( accountID );
		invariant( isUniqueContainerName( containerName, webContainers ), `a container with "${ containerName }" name already exists` );
	}

	if ( ampContainerID === CONTAINER_CREATE ) {
		const ampContainerName = select( CORE_FORMS ).getValue( FORM_SETUP, 'ampContainerName' );
		invariant( isValidContainerName( ampContainerName ), `a container name is invalid` );

		const ampContainers = getAMPContainers( accountID );
		invariant( isUniqueContainerName( ampContainerName, ampContainers ), `an AMP container with "${ ampContainerName }" name already exists` );
	}

	if ( isAMP() ) {
		// If AMP is active, the AMP container ID must be valid, regardless of mode.
		invariant( isValidContainerSelection( ampContainerID ), 'a valid ampContainerID selection is required to submit changes' );
		// If AMP is active, and a valid AMP container ID is selected, the internal ID must also be valid.
		if ( isValidContainerID( ampContainerID ) ) {
			invariant( isValidInternalContainerID( getInternalAMPContainerID() ), 'a valid internalAMPContainerID is required to submit changes' );
		}
	}

	if ( ! isAMP() || isSecondaryAMP() ) {
		// If AMP is not active, or in a secondary mode, validate the web container IDs.
		invariant( isValidContainerSelection( getContainerID() ), 'a valid containerID selection is required to submit changes' );
		// If a valid container ID is selected, the internal ID must also be valid.
		if ( isValidContainerID( getContainerID() ) ) {
			invariant( isValidInternalContainerID( getInternalContainerID() ), 'a valid internalContainerID is required to submit changes' );
		}
	}

	invariant( ! hasMultipleAnalyticsPropertyIDs(), 'containers with Analytics tags must reference a single property ID to submit changes' );

	if ( isModuleActive( 'analytics' ) && getPropertyID() && hasAnyAnalyticsPropertyID() ) {
		invariant( getSingleAnalyticsPropertyID() === getPropertyID(), 'single GTM Analytics property ID must match Analytics property ID' );
	}

	// Do existing tag check last.
	if ( hasExistingTag() ) {
		invariant( hasExistingTagPermission(), 'existing tag permission is required to submit changes' );
	}
};

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


<commit_before>var parseArgs = require('minimist');
var fs = require('fs');
var path = require('path');
var lodash = require('lodash');
/**
* main-import-tool
* -f: Filename for main import file.
* -d: Base source directory.
* -a: Append missing imports, or generate new list of imports if main import file does not exist.
* -x: A comma-separated list of paths to exclude from the output.
* (Default behavior): Print error and return non-zero if not all files are referenced.
*/

/* Cribbed from https://gist.github.com/kethinov/6658166 */
var walkSync = function(dir, excluded, filelist) {
	var files = fs.readdirSync(dir);
	files.forEach(function(file) {
		var fullPath = path.posix.join(dir, file);
		if (lodash.includes(excluded, fullPath))
		{
			return;
		}
		if (fs.statSync(fullPath).isDirectory())
		{
			walkSync(fullPath, excluded, filelist);
		}
		else {
			filelist.push(fullPath);
		}
	});
	return filelist;
};

var args = parseArgs(process.argv, {string: ["d", "f", "x"], boolean: "a"});
var sourceDir = args.d;
var importFileName = args.f;
var appendImports = args.a;
var excludedFiles = args.x ? args.x.split(",") : [];

excludedFiles.push(importFileName);

if (!sourceDir || !fs.statSync(sourceDir).isDirectory())
{
	console.error("A base source directory must be specified with -d.");
	process.exit(-1);
}

if (!importFileName)
{
	console.error("A main import file must be specified.");
	process.exit(-1);
}

var filePaths = new Set(walkSync(sourceDir, excludedFiles, []).map(function (filePath) {
	return "./" + path.posix.relative(path.posix.dirname(importFileName), filePath);
}));

/* Get the existing references from the master file */

var referencePattern = /\/\/\/[\s+]<reference path="(.+)"\/>/g;
var importFileContent = "";

if (fs.existsSync(importFileName))
{
	importFileContent = fs.readFileSync(importFileName, "utf8");
	while ((match = referencePattern.exec(importFileContent)) !== null) {
		var referencePath = match[1];
		filePaths.delete(referencePath);
	}
}
else if (!args.a)
{
	console.error(`No main import file found. Rerun this command with the -a flag to generate one.`);
	process.exit(1);
}

if (filePaths.size)
{
	if (args.a)
	{
		console.error(`References to the following files were not found in the main import file at '${importFileName}':`)
		filePaths.forEach(function(filePath) {
			console.error(`\t${filePath}`);
		});
		console.error('Adding these files to the main import file.');

		var stream = fs.createWriteStream(importFileName, {flags: 'w'});
		stream.write(importFileContent);
		filePaths.forEach(
			function (filePath) {
				stream.write(`/// <reference path="${filePath}"/>\n`);
			}
		);
		stream.end();
	}
	else
	{
		console.error(`References to the following files were not found in the main import file at '${importFileName}':`)
		filePaths.forEach(function(filePath) {
			console.error(`\t${filePath}`);
		});
		console.error('To naively append these references to the main import file, rerun this command with the -a flag.');
		process.exit(1);
	}
}<commit_msg>Added extension filter to main-import-tool.<commit_after>var parseArgs = require('minimist');
var fs = require('fs');
var path = require('path');
var lodash = require('lodash');
/**
* main-import-tool
* -f: Filename for main import file.
* -d: Base source directory.
* -a: Append missing imports, or generate new list of imports if main import file does not exist.
* -x: A comma-separated list of paths to exclude from the output.
* --ext: A comma-separated list of extensions to include in the output. Default: .tsx,.ts
* (Default behavior): Print error and return non-zero if not all files are referenced.
*/

/* Cribbed from https://gist.github.com/kethinov/6658166 */
var walkSync = function(dir, excluded, extensions, filelist) {
	var files = fs.readdirSync(dir);
	files.forEach(function(file) {
		var fullPath = path.posix.join(dir, file);
		if (lodash.includes(excluded, fullPath))
		{
			return;
		}
		if (fs.statSync(fullPath).isDirectory())
		{
			walkSync(fullPath, excluded, extensions, filelist);
		}
		else {
			var extension = path.extname(file);
			if (lodash.includes(extensions, extension))
			{
				filelist.push(fullPath);
			}
		}
	});
	return filelist;
};

var args = parseArgs(process.argv, {string: ["d", "f", "x", "ext"], boolean: "a"});
var sourceDir = args.d;
var importFileName = args.f;
var appendImports = args.a;
var excludedFiles = args.x ? args.x.split(",") : [];
var extensions = args.ext ? args.ext.split(",") : [".tsx", ".ts"];

excludedFiles.push(importFileName);

if (!sourceDir || !fs.statSync(sourceDir).isDirectory())
{
	console.error("A base source directory must be specified with -d.");
	process.exit(-1);
}

if (!importFileName)
{
	console.error("A main import file must be specified.");
	process.exit(-1);
}

var filePaths = new Set(walkSync(sourceDir, excludedFiles, extensions, []).map(function (filePath) {
	return "./" + path.posix.relative(path.posix.dirname(importFileName), filePath);
}));

/* Get the existing references from the master file */

var referencePattern = /\/\/\/[\s+]<reference path="(.+)"\/>/g;
var importFileContent = "";

if (fs.existsSync(importFileName))
{
	importFileContent = fs.readFileSync(importFileName, "utf8");
	while ((match = referencePattern.exec(importFileContent)) !== null) {
		var referencePath = match[1];
		filePaths.delete(referencePath);
	}
}
else if (!args.a)
{
	console.error(`No main import file found. Rerun this command with the -a flag to generate one.`);
	process.exit(1);
}

if (filePaths.size)
{
	if (args.a)
	{
		console.error(`References to the following files were not found in the main import file at '${importFileName}':`)
		filePaths.forEach(function(filePath) {
			console.error(`\t${filePath}`);
		});
		console.error('Adding these files to the main import file.');

		var stream = fs.createWriteStream(importFileName, {flags: 'w'});
		stream.write(importFileContent);
		filePaths.forEach(
			function (filePath) {
				stream.write(`/// <reference path="${filePath}"/>\n`);
			}
		);
		stream.end();
	}
	else
	{
		console.error(`References to the following files were not found in the main import file at '${importFileName}':`)
		filePaths.forEach(function(filePath) {
			console.error(`\t${filePath}`);
		});
		console.error('To naively append these references to the main import file, rerun this command with the -a flag.');
		process.exit(1);
	}
}<|endoftext|>


<commit_before>;(function(context, MM) {
    var easey = function() {
        var easey = {},
            running = false,
            abort = false, // killswitch for transitions
            abortCallback; // callback called when aborted

        var easings = {
            easeIn: function(t) { return t * t; },
            easeOut: function(t) { return Math.sin(t * Math.PI / 2); },
            easeInOut: function(t) { return (1 - Math.cos(Math.PI * t)) / 2; },
            linear: function(t) { return t; }
        };
        var easing = easings.easeOut;

        // to is the singular coordinate that any transition is based off
        // three dimensions:
        //
        // * to
        // * time
        // * path
        var from, to, map;

        easey.stop = function(callback) {
            abort = true;
            from = null;
            abortCallback = callback;
        };

        easey.running = function() {
            return running;
        };

        easey.point = function(x) {
            to = map.pointCoordinate(x);
            return easey;
        };

        easey.zoom = function(x) {
            to = map.enforceZoomLimits(to.zoomTo(x));
            return easey;
        };

        easey.location = function(x) {
            to = map.locationCoordinate(x);
            return easey;
        };

        easey.from = function(x) {
            if (!arguments.length) return from.copy();
            from = x.copy();
            return easey;
        };

        easey.to = function(x) {
            if (!arguments.length) return to.copy();
            to = map.enforceZoomLimits(x.copy());
            return easey;
        };

        easey.path = function(x) {
            path = paths[x];
            return easey;
        };

        easey.easing = function(x) {
            easing = easings[x];
            return easey;
        };

        easey.map = function(x) {
            if (!arguments.length) return map;
            map = x;
            to = map.coordinate.copy();
            return easey;
        };

        function interp(a, b, p) {
            if (p === 0) return a;
            if (p === 1) return b;
            return a + ((b - a) * p);
        }

        var paths = {};

        // The screen path simply moves between
        // coordinates in a non-geographical way
        paths.screen = function(a, b, t) {
            var zoom_lerp = interp(a.zoom, b.zoom, t);
            var az = a.copy();
            var bz = b.copy().zoomTo(a.zoom);
            return (new MM.Coordinate(
                interp(az.row, bz.row, t),
                interp(az.column, bz.column, t),
                az.zoom)).zoomTo(zoom_lerp);
        };

        function ptWithCoords(a, b) {
            // distance from the center of the map
            var point = new MM.Point(map.dimensions.x / 2, map.dimensions.y / 2);
            point.x += map.tileSize.x * (b.column - a.column);
            point.y += map.tileSize.y * (b.row - a.row);
            return point;
        }

        // The screen path means that the b
        // coordinate should maintain its point on screen
        // throughout the transition, but the map
        // should move to its zoom level
        paths.about = function(a, b, t) {
            var zoom_lerp = interp(a.zoom, b.zoom, t);

            var bs = b.copy().zoomTo(a.zoom);
            var az = a.copy().zoomTo(zoom_lerp);
            var bz = b.copy().zoomTo(zoom_lerp);
            var start = ptWithCoords(a, bs);
            var end = ptWithCoords(az, bz);

            az.column -= (start.x - end.x) / map.tileSize.x;
            az.row -= (start.y - end.y) / map.tileSize.y;

            return az;
        };

        var path = paths.screen;

        easey.t = function(t) {
            map.coordinate = path(from, to, easing(t));
            map.draw();
            return easey;
        };

        easey.future = function(parts) {
            var futures = [];
            for (var t = 0; t < parts; t++) {
                futures.push(path(from, to, t / (parts - 1)));
            }
            return futures;
        };

        var start;
        easey.resetRun = function () {
            start = (+ new Date());
            return easey;
        };

        easey.run = function(time, callback) {

            if (running) return easey.stop(function() {
                easey.run(time, callback);
            });

            if (!from) from = map.coordinate.copy();

            time = time || 1000;

            start = (+new Date());

            running = true;

            function tick() {
                var delta = (+new Date()) - start;
                if (abort) {
                    abort = running = false;
                    abortCallback();
                    return abortCallback = undefined;
                } else if (delta > time) {
                    running = false;
                    map.coordinate = path(from, to, 1);
                    from = null;
                    map.draw();
                    if (callback) return callback(map);
                } else {
                    map.coordinate = path(from, to, easing(delta / time));
                    map.draw();
                    MM.getFrame(tick);
                }
            }

            MM.getFrame(tick);
        };

        // Optimally smooth (constant perceived velocity) and
        // efficient (minimal path distance) zooming and panning.
        //
        // Based on "Smooth and efficient zooming and panning"
        // by Jarke J. van Wijk and Wim A.A. Nuij
        //
        // Model described in section 3, equations 1 through 5
        // Derived equation (9) of optimal path implemented below
        easey.optimal = function(V, rho, callback) {
            // Section 6 describes user testing of these tunable values
            V = V || 0.9;
            rho = rho || 1.42;

            function sqr(n) { return n*n; }
            function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
            function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
            function tanh(n) { return sinh(n) / cosh(n); }

            if (from) map.coordinate = from; // For when `from` not current coordinate
            else from = map.coordinate.copy();

            // Width is measured in coordinate units at zoom 0
            var TL = map.pointCoordinate(new MM.Point(0, 0)).zoomTo(0),
                BR = map.pointCoordinate(map.dimensions).zoomTo(0),
                w0 = Math.max(BR.column - TL.column, BR.row - TL.row),
                w1 = w0 * Math.pow(2, from.zoom - to.zoom),
                start = from.zoomTo(0),
                end = to.zoomTo(0),
                c0 = {x: start.column, y: start.row},
                c1 = {x: end.column, y: end.row},
                u0 = 0,
                u1 = Math.sqrt(sqr(c1.x - c0.x) + sqr(c1.y - c0.y));

            function b(i) {
                var n = sqr(w1) - sqr(w0) + (i ? -1: 1) * Math.pow(rho, 4) * sqr(u1 - u0),
                    d = 2 * (i ? w1 : w0) * sqr(rho) * (u1 - u0);
                return n/d;
            }

            function r(i) {
                return Math.log(-b(i) + Math.sqrt(sqr(b(i)) + 1));
            }

            var r0 = r(0),
                r1 = r(1),
                S = (r1 - r0) / rho;

            // Width
            var w = function(s) {
                return w0 * cosh(r0) / cosh (rho * s + r0);
            };

            // Zoom
            var u = function(s) {
                return (w0 / sqr(rho)) * cosh(r0) * tanh(rho * s + r0) - (w0 / sqr(rho)) * sinh(r0) + u0;
            };

            // Special case, when no panning necessary
            if (Math.abs(u1) < 0.000001) {
                if (Math.abs(w0 - w1) < 0.000001) return;

                // Based on section 4
                var k = w1 < w0 ? -1 : 1;
                S = Math.abs(Math.log(w1/w0)) / rho;
                u = function(s) {
                    return u0;
                };
                w = function(s) {
                    return w0 * Math.exp(k * rho * s);
                };
            }

            var path = function (a, b, t) {
                if (t == 1) return to;
                var s = t * S,
                    us = u(s),
                    z = a.zoom + (Math.log(w0/w(s)) / Math.LN2),
                    x = interp(c0.x, c1.x, us/u1 || 1),
                    y = interp(c0.y, c1.y, us/u1 || 1);
                return new MM.Coordinate(y, x, 0).zoomTo(z);
            };

            easey.easing('linear');
            easey.run(S / V * 1000, callback);
        };

        return easey;
    };

    this.easey = easey;
})(this, MM);
<commit_msg>easey.optimal respoects easing<commit_after>;(function(context, MM) {
    var easey = function() {
        var easey = {},
            running = false,
            abort = false, // killswitch for transitions
            abortCallback; // callback called when aborted

        var easings = {
            easeIn: function(t) { return t * t; },
            easeOut: function(t) { return Math.sin(t * Math.PI / 2); },
            easeInOut: function(t) { return (1 - Math.cos(Math.PI * t)) / 2; },
            linear: function(t) { return t; }
        };
        var easing = easings.easeOut;

        // to is the singular coordinate that any transition is based off
        // three dimensions:
        //
        // * to
        // * time
        // * path
        var from, to, map;

        easey.stop = function(callback) {
            abort = true;
            from = null;
            abortCallback = callback;
        };

        easey.running = function() {
            return running;
        };

        easey.point = function(x) {
            to = map.pointCoordinate(x);
            return easey;
        };

        easey.zoom = function(x) {
            to = map.enforceZoomLimits(to.zoomTo(x));
            return easey;
        };

        easey.location = function(x) {
            to = map.locationCoordinate(x);
            return easey;
        };

        easey.from = function(x) {
            if (!arguments.length) return from.copy();
            from = x.copy();
            return easey;
        };

        easey.to = function(x) {
            if (!arguments.length) return to.copy();
            to = map.enforceZoomLimits(x.copy());
            return easey;
        };

        easey.path = function(x) {
            path = paths[x];
            return easey;
        };

        easey.easing = function(x) {
            easing = easings[x];
            return easey;
        };

        easey.map = function(x) {
            if (!arguments.length) return map;
            map = x;
            to = map.coordinate.copy();
            return easey;
        };

        function interp(a, b, p) {
            if (p === 0) return a;
            if (p === 1) return b;
            return a + ((b - a) * p);
        }

        var paths = {};

        // The screen path simply moves between
        // coordinates in a non-geographical way
        paths.screen = function(a, b, t) {
            var zoom_lerp = interp(a.zoom, b.zoom, t);
            var az = a.copy();
            var bz = b.copy().zoomTo(a.zoom);
            return (new MM.Coordinate(
                interp(az.row, bz.row, t),
                interp(az.column, bz.column, t),
                az.zoom)).zoomTo(zoom_lerp);
        };

        function ptWithCoords(a, b) {
            // distance from the center of the map
            var point = new MM.Point(map.dimensions.x / 2, map.dimensions.y / 2);
            point.x += map.tileSize.x * (b.column - a.column);
            point.y += map.tileSize.y * (b.row - a.row);
            return point;
        }

        // The screen path means that the b
        // coordinate should maintain its point on screen
        // throughout the transition, but the map
        // should move to its zoom level
        paths.about = function(a, b, t) {
            var zoom_lerp = interp(a.zoom, b.zoom, t);

            var bs = b.copy().zoomTo(a.zoom);
            var az = a.copy().zoomTo(zoom_lerp);
            var bz = b.copy().zoomTo(zoom_lerp);
            var start = ptWithCoords(a, bs);
            var end = ptWithCoords(az, bz);

            az.column -= (start.x - end.x) / map.tileSize.x;
            az.row -= (start.y - end.y) / map.tileSize.y;

            return az;
        };

        var path = paths.screen;

        easey.t = function(t) {
            map.coordinate = path(from, to, easing(t));
            map.draw();
            return easey;
        };

        easey.future = function(parts) {
            var futures = [];
            for (var t = 0; t < parts; t++) {
                futures.push(path(from, to, t / (parts - 1)));
            }
            return futures;
        };

        var start;
        easey.resetRun = function () {
            start = (+ new Date());
            return easey;
        };

        easey.run = function(time, callback) {

            if (running) return easey.stop(function() {
                easey.run(time, callback);
            });

            if (!from) from = map.coordinate.copy();

            time = time || 1000;

            start = (+new Date());

            running = true;

            function tick() {
                var delta = (+new Date()) - start;
                if (abort) {
                    abort = running = false;
                    abortCallback();
                    return abortCallback = undefined;
                } else if (delta > time) {
                    running = false;
                    map.coordinate = path(from, to, 1);
                    from = null;
                    map.draw();
                    if (callback) return callback(map);
                } else {
                    map.coordinate = path(from, to, easing(delta / time));
                    map.draw();
                    MM.getFrame(tick);
                }
            }

            MM.getFrame(tick);
        };

        // Optimally smooth (constant perceived velocity) and
        // efficient (minimal path distance) zooming and panning.
        //
        // Based on "Smooth and efficient zooming and panning"
        // by Jarke J. van Wijk and Wim A.A. Nuij
        //
        // Model described in section 3, equations 1 through 5
        // Derived equation (9) of optimal path implemented below
        easey.optimal = function(V, rho, callback) {
            // Section 6 describes user testing of these tunable values
            V = V || 0.9;
            rho = rho || 1.42;

            function sqr(n) { return n*n; }
            function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
            function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
            function tanh(n) { return sinh(n) / cosh(n); }

            if (from) map.coordinate = from; // For when `from` not current coordinate
            else from = map.coordinate.copy();

            // Width is measured in coordinate units at zoom 0
            var TL = map.pointCoordinate(new MM.Point(0, 0)).zoomTo(0),
                BR = map.pointCoordinate(map.dimensions).zoomTo(0),
                w0 = Math.max(BR.column - TL.column, BR.row - TL.row),
                w1 = w0 * Math.pow(2, from.zoom - to.zoom),
                start = from.zoomTo(0),
                end = to.zoomTo(0),
                c0 = {x: start.column, y: start.row},
                c1 = {x: end.column, y: end.row},
                u0 = 0,
                u1 = Math.sqrt(sqr(c1.x - c0.x) + sqr(c1.y - c0.y));

            function b(i) {
                var n = sqr(w1) - sqr(w0) + (i ? -1: 1) * Math.pow(rho, 4) * sqr(u1 - u0),
                    d = 2 * (i ? w1 : w0) * sqr(rho) * (u1 - u0);
                return n/d;
            }

            function r(i) {
                return Math.log(-b(i) + Math.sqrt(sqr(b(i)) + 1));
            }

            var r0 = r(0),
                r1 = r(1),
                S = (r1 - r0) / rho;

            // Width
            var w = function(s) {
                return w0 * cosh(r0) / cosh (rho * s + r0);
            };

            // Zoom
            var u = function(s) {
                return (w0 / sqr(rho)) * cosh(r0) * tanh(rho * s + r0) - (w0 / sqr(rho)) * sinh(r0) + u0;
            };

            // Special case, when no panning necessary
            if (Math.abs(u1) < 0.000001) {
                if (Math.abs(w0 - w1) < 0.000001) return;

                // Based on section 4
                var k = w1 < w0 ? -1 : 1;
                S = Math.abs(Math.log(w1/w0)) / rho;
                u = function(s) {
                    return u0;
                };
                w = function(s) {
                    return w0 * Math.exp(k * rho * s);
                };
            }

            var path = function (a, b, t) {
                if (t == 1) return to;
                var s = t * S,
                    us = u(s),
                    z = a.zoom + (Math.log(w0/w(s)) / Math.LN2),
                    x = interp(c0.x, c1.x, us/u1 || 1),
                    y = interp(c0.y, c1.y, us/u1 || 1);
                return new MM.Coordinate(y, x, 0).zoomTo(z);
            };

            easey.run(S / V * 1000, callback);
        };

        return easey;
    };

    this.easey = easey;
})(this, MM);
<|endoftext|>


<commit_before>elation.require([
    'utils.events',
    'utils.worker',
    'engine.engine',
    'engine.assets',
    'engine.external.pako',
    'engine.external.three.three', 'engine.external.three.FBXLoader', 'engine.external.three.ColladaLoader2', 'engine.external.xmldom',
    'engine.external.three.OBJLoader', 'engine.external.three.MTLLoader', 'engine.external.three.VRMLLoader', 'engine.external.three.GLTFLoader', 'engine.external.three.PLYLoader'
  ], function() {

  elation.define('engine.assetworker', {
    _construct: function() {
      var srcmap = {};
      // Compatibility for ColladaLoader
      Image = function(src) { this.src = src; };
      Image.prototype.toDataURL = function() { return this.src; }

      THREE.Cache.enabled = true;
      THREE.ImageLoader.prototype.load = function ( url, onLoad, onProgress, onError ) {

        var scope = this;

        var uuid = srcmap[url];
        if (!uuid) {
          srcmap[url] = uuid = THREE.Math.generateUUID();
        }
        var img = { uuid: uuid, src: url, toDataURL: function() { return url; } };
        scope.manager.itemStart( url );
        if ( onLoad ) {
          onLoad( img );
        }
        scope.manager.itemEnd( url );

        return img;
      }
      this.loader = new elation.engine.assets.loaders.model();
      elation.engine.assets.init(true);
    },
    onmessage: function(ev) {
      var msg = ev.data;
      if (msg.type == 'job') {
        if (msg.data) {
          this.loader.load(msg.data);
        }
      } else if (msg.type == 'setcorsproxy') {
        elation.engine.assets.setCORSProxy(msg.data);
      }
    }
  }, elation.worker.base);

  elation.define('engine.assets.loaders.model', {
    _construct: function(args) {
      elation.class.call(this, args);
      this.parsers = {
        'collada': new elation.engine.assets.loaders.model_collada(),
        'obj': new elation.engine.assets.loaders.model_obj(),
        'fbx': new elation.engine.assets.loaders.model_fbx(),
        'ply': new elation.engine.assets.loaders.model_ply(),
        'wrl': new elation.engine.assets.loaders.model_wrl(),
        'gltf': new elation.engine.assets.loaders.model_gltf(),
      }
    },
    getFullURL: function(src) {
      var corsproxy = elation.engine.assets.corsproxy || '';
      var baseurl = src.substr( 0, src.lastIndexOf( "/" ) + 1 )
      var fullurl = src;
      if (src.match(/^(https?:)?\/\//) && !src.match(/^(https?:)?\/\/localhost/) && fullurl.substr(0,corsproxy.length) != corsproxy) {
        fullurl = corsproxy + fullurl;
      }
      return fullurl;
    },
    load: function(job) {
      if (job.data.srcdata) {
        this.onload(job, job.data.srcdata);
      } else if (job.data.src) {
        var fullurl = this.getFullURL(job.data.src);
        elation.net.get(fullurl, null, {
          responseType: 'arraybuffer',
          callback: elation.bind(this, this.onload, job),
          onprogress: elation.bind(this, this.onprogress, job),
          onerror: elation.bind(this, this.onerror, job),
          headers: {
            'X-Requested-With': 'Elation Engine asset loader'
          }
        });
      }
    },
    contentIsGzipped: function(databuf) {
      var c1 = databuf[0], c2 = databuf[1];
      //console.log('gzip check?', [c1, c2], [0x1f, 0x8b]);
      if (c1 == 0x1f && c2 == 0x8b) {
        return true;
      }
      return false;
    },
    detectContentType: function(content) {
      var foo = '';
      for (var i = 0; i < 500; i++) {
        foo += content[i];
      }
      //content = foo;
      var tests = [
        [/<COLLADA/im, 'collada'],
        [/^\s*v\s+-?[\d\.]+\s+-?[\d\.]+\s+-?[\d\.]+\s*$/im, 'obj'],
        [/FBXHeader/i, 'fbx'],
        [/^ply$/im, 'ply'],
        [/^\#VRML/, 'wrl'],
        [/^\s*{/, 'gltf']
      ];

      var type = false;
      tests.forEach(function(t) {
        if (content.match(t[0])) {
          type = t[1];
        }
      });
      return type;
    },
    parse: function(data, job) {
      var type = this.detectContentType(data);
      if (this.parsers[type]) {
        return this.parsers[type].parse(data, job);
      }
      return new Promise(function(resolve, reject) {
        resolve(false);
      });
    },
    onload: function(job, rawdata, xhr) {
      var databuf = new Uint8Array(rawdata),
          modeldata = false;

      if (this.contentIsGzipped(databuf)) {
        modeldata = window.pako.inflate(databuf, {to: 'string'});
      } else {
        var dataview = new DataView(rawdata);
        if (false && typeof TextDecoder !== 'undefined') {
          var decoder = new TextDecoder('utf-8');
          modeldata = decoder.decode(dataview);
        } else {
          // FIXME - do we really need to do this?  It should be handled on a per-modeltype basis, at least
          modeldata = this.convertArrayBufferToString(rawdata);
        }
      }
      if (modeldata) {
        this.parse(modeldata, job).then(function(data) {
          var transferrables = [];
          // Convert BufferGeometry arrays back to Float32Arrays so they can be transferred efficiently
          try {
            for (var i = 0; i < data.geometries.length; i++) {
              var geo = data.geometries[i];
              for (var k in geo.data.attributes) {
                //var arr = Float32Array.from(geo.data.attributes[k].array);
                var src = geo.data.attributes[k].array;
                var arr = new Float32Array(src.length);
                for (var j = 0; j < src.length; j++) {
                  arr[j] = src[j];
                }
                transferrables.push(arr.buffer);
                geo.data.attributes[k].array = arr;
              }
            }
            postMessage({message: 'finished', id: job.id, data: data}, transferrables);
          } catch (e) {
            postMessage({message: 'error', id: job.id, data: e.toString()});
          }
        }, function(d) {
          postMessage({message: 'error', id: job.id, data: d.toString()});
        });
      } else {
        postMessage({message: 'error', id: job.id, data: 'no modeldata found'});
      }
    },
    onprogress: function(job, ev) {
      //console.log('progress', job, ev);
    },
    onerror: function(job, ev) {
      console.log('error', job, ev);
      postMessage({message: 'error', id: job.id, data: 'unknown error'});
    },
    convertArrayBufferToString: function(rawdata) {
      var converted = '';

      var bufview = new Uint8Array(rawdata);
      var l = bufview.length;
      var str = '';
      for (var i = 0; i < l; i++) {
        str += String.fromCharCode(bufview[i]);
      }

      // convert binary data to string encoding
      try {
        converted = decodeURIComponent(escape(str));
      } catch (e) {
        converted = str;
      }
      return converted;
    }
  });
  elation.define('engine.assets.loaders.model_obj', {
    convertArrayBufferToString: elation.engine.assets.loaders.model.prototype.convertArrayBufferToString,
    parse: function(data, job) {
      return new Promise(elation.bind(this, function(resolve, reject) {
        var mtl = job.data.mtl || false;

        var baseurl = job.data.src.substr( 0, job.data.src.lastIndexOf( "/" ) + 1 )
/*
        if (!mtl) {
          var re = /^mtllib (.*)$/im;
          var m = data.match(re);
          if (m) {
            mtl = m[1];
          }
        }
*/
        //var loader = (mtl ? new THREE.OBJMTLLoader() : new THREE.OBJLoader());
        var loader = new THREE.OBJLoader();
        var modeldata = false;

        if (mtl) {
          var createMaterials = elation.bind(this, function ( materials ) {
                materials.preload();
                loader.setMaterials(materials);
                //loader.setPath(mtlLoader.path);
                modeldata = loader.parse(data);
                resolve(this.convertToJSON(modeldata));
              }),

          mtl = this.getFullURL(mtl, baseurl);
          if (elation.engine.assets.corsproxy && mtl.indexOf(elation.engine.assets.corsproxy) != 0) {
            mtl = elation.engine.assets.corsproxy + mtl;
          }
          var mtlpath = mtl.substr( 0, mtl.lastIndexOf( "/" ) + 1 );
          var mtlfile = mtl.substr(mtl.lastIndexOf( "/" ) + 1);

          var mtlLoader = new THREE.MTLLoader( );
          mtlLoader.setPath( mtlpath );
          mtlLoader.setCrossOrigin( 'anonymous' );
          if (job.data.mtldata) {
            var mtldata = this.convertArrayBufferToString(job.data.mtldata);
            var materials = mtlLoader.parse(mtldata);
            createMaterials(materials);
          } else {
            mtlLoader.load( mtlfile,
              createMaterials,
              function(ev) {
                //console.log('progress?', ev);
              },
              elation.bind(this, function(ev) {
                resolve(this.convertToJSON(modeldata));
              })
            );
          }
        } else {
          modeldata = loader.parse(data);
          resolve(this.convertToJSON(modeldata));
        }
        return modeldata;
      }));
    },
    convertToJSON: function(scene) {
      var json;
      if (scene) {
        // Convert Geometries to BufferGeometries
        scene.traverse(function(n) {
          if (n.geometry && n.geometry instanceof THREE.Geometry) {
            var bufgeo = new THREE.BufferGeometry().fromGeometry(n.geometry);
            n.geometry = bufgeo;
          }
        });
        json = scene.toJSON();
      }
      return json;

    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_collada', {
    parser: new DOMParser(),

    parse: function(xml, job) {
      return new Promise(elation.bind(this, function(resolve, reject) {
        var data = false;
        var baseurl = job.data.src.substr( 0, job.data.src.lastIndexOf( "/" ) + 1 )
        var loader = new THREE.ColladaLoader();
        loader.options.convertUpAxis = true;
        loader.options.upAxis = 'Y';
        var parsed = loader.parse(xml);
        var imageids = Object.keys(parsed.library.images);
        for (var i = 0; i < imageids.length; i++) {
          var img = parsed.library.images[imageids[i]].build;
          img.src = this.getProxiedURL(baseurl + img.src);
        }
        parsed.scene.traverse(function(n) {
          if ((n.geometry instanceof THREE.BufferGeometry && !n.geometry.attributes.normals) ||
              (n.geometry instanceof THREE.Geometry && !n.geometry.faceVertexNormals)) {
            n.geometry.computeFaceNormals();
            n.geometry.computeVertexNormals();
          }
          // Convert to BufferGeometry for better loading efficiency
          if (n.geometry && n.geometry instanceof THREE.Geometry) {
            var bufgeo = new THREE.BufferGeometry().fromGeometry(n.geometry);
            n.geometry = bufgeo;
          }
        });
        data = parsed;

        data.scene.rotation.z = 0;
        data.scene.updateMatrix();
        resolve(data.scene.toJSON());
      }));
    },
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_wrl', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var loader = new THREE.VRMLLoader();
        var modeldata = loader.parse(data);

        modeldata.traverse(function(n) {
          if (n.geometry && n.geometry instanceof THREE.Geometry) {
            var bufgeo = new THREE.BufferGeometry().fromGeometry(n.geometry);
            n.geometry = bufgeo;
          }
        });

        resolve(modeldata.toJSON());
      });
    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_fbx', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var loader = new THREE.FBXLoader();
        var modeldata = loader.parse(data);
        resolve(modeldata.toJSON());
      });
    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_gltf', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var json = JSON.parse(data);
        var path = THREE.Loader.prototype.extractUrlBase( job.data.src );
        var proxypath = elation.engine.assets.corsproxy + path;

        THREE.GLTFLoader.Shaders.removeAll();
        var loader = new THREE.GLTFLoader();
        loader.parse(json, elation.bind(this, function(modeldata) {
          if (modeldata.scene) {
            var encoded = modeldata.scene.toJSON();
            resolve(encoded);
          } else {
            reject();
          }
        }), proxypath);
      });
    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_ply', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var path = THREE.Loader.prototype.extractUrlBase( job.data.src );
        var proxypath = elation.engine.assets.corsproxy + path;

        var loader = new THREE.PLYLoader();
        loader.setPropertyNameMapping({
          diffuse_red: 'red',
          diffuse_green: 'green',
          diffuse_blue: 'blue'
        });

        var geometry = loader.parse(data);
        var encoded = false;

        if (!geometry.index || geometry.index.length == 0) {
          // No face data, render as point cloud
          geometry.index = null;
          var points = new THREE.Points(geometry, new THREE.PointsMaterial({
            color: 0xffffff,
            size: 0.02,
            sizeAttenuation: true,
            vertexColors: (geometry.attributes.color && geometry.attributes.color.length > 0 ? THREE.VertexColors : THREE.NoColors)
          }));
          encoded = points.toJSON();
        } else {
          // Has face data, render as mesh.  Compute normals if necessary
          if (!geometry.attributes.normal) {
            geometry.computeVertexNormals();
          }
          var mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial());
          encoded = mesh.toJSON();
        }
        resolve(encoded);

        //reject();
      });
    },

  }, elation.engine.assets.base);

});

<commit_msg>Better exception handler in asset worker<commit_after>elation.require([
    'utils.events',
    'utils.worker',
    'engine.engine',
    'engine.assets',
    'engine.external.pako',
    'engine.external.three.three', 'engine.external.three.FBXLoader', 'engine.external.three.ColladaLoader2', 'engine.external.xmldom',
    'engine.external.three.OBJLoader', 'engine.external.three.MTLLoader', 'engine.external.three.VRMLLoader', 'engine.external.three.GLTFLoader', 'engine.external.three.PLYLoader'
  ], function() {

  elation.define('engine.assetworker', {
    _construct: function() {
      var srcmap = {};
      // Compatibility for ColladaLoader
      Image = function(src) { this.src = src; };
      Image.prototype.toDataURL = function() { return this.src; }

      THREE.Cache.enabled = true;
      THREE.ImageLoader.prototype.load = function ( url, onLoad, onProgress, onError ) {

        var scope = this;

        var uuid = srcmap[url];
        if (!uuid) {
          srcmap[url] = uuid = THREE.Math.generateUUID();
        }
        var img = { uuid: uuid, src: url, toDataURL: function() { return url; } };
        scope.manager.itemStart( url );
        if ( onLoad ) {
          onLoad( img );
        }
        scope.manager.itemEnd( url );

        return img;
      }
      this.loader = new elation.engine.assets.loaders.model();
      elation.engine.assets.init(true);
    },
    onmessage: function(ev) {
      var msg = ev.data;
      if (msg.type == 'job') {
        if (msg.data) {
          this.loader.load(msg.data);
        }
      } else if (msg.type == 'setcorsproxy') {
        elation.engine.assets.setCORSProxy(msg.data);
      }
    }
  }, elation.worker.base);

  elation.define('engine.assets.loaders.model', {
    _construct: function(args) {
      elation.class.call(this, args);
      this.parsers = {
        'collada': new elation.engine.assets.loaders.model_collada(),
        'obj': new elation.engine.assets.loaders.model_obj(),
        'fbx': new elation.engine.assets.loaders.model_fbx(),
        'ply': new elation.engine.assets.loaders.model_ply(),
        'wrl': new elation.engine.assets.loaders.model_wrl(),
        'gltf': new elation.engine.assets.loaders.model_gltf(),
      }
    },
    getFullURL: function(src) {
      var corsproxy = elation.engine.assets.corsproxy || '';
      var baseurl = src.substr( 0, src.lastIndexOf( "/" ) + 1 )
      var fullurl = src;
      if (src.match(/^(https?:)?\/\//) && !src.match(/^(https?:)?\/\/localhost/) && fullurl.substr(0,corsproxy.length) != corsproxy) {
        fullurl = corsproxy + fullurl;
      }
      return fullurl;
    },
    load: function(job) {
      if (job.data.srcdata) {
        this.onload(job, job.data.srcdata);
      } else if (job.data.src) {
        var fullurl = this.getFullURL(job.data.src);
        elation.net.get(fullurl, null, {
          responseType: 'arraybuffer',
          callback: elation.bind(this, this.onload, job),
          onprogress: elation.bind(this, this.onprogress, job),
          onerror: elation.bind(this, this.onerror, job),
          headers: {
            'X-Requested-With': 'Elation Engine asset loader'
          }
        });
      }
    },
    contentIsGzipped: function(databuf) {
      var c1 = databuf[0], c2 = databuf[1];
      //console.log('gzip check?', [c1, c2], [0x1f, 0x8b]);
      if (c1 == 0x1f && c2 == 0x8b) {
        return true;
      }
      return false;
    },
    detectContentType: function(content) {
      var foo = '';
      for (var i = 0; i < 500; i++) {
        foo += content[i];
      }
      //content = foo;
      var tests = [
        [/<COLLADA/im, 'collada'],
        [/^\s*v\s+-?[\d\.]+\s+-?[\d\.]+\s+-?[\d\.]+\s*$/im, 'obj'],
        [/FBXHeader/i, 'fbx'],
        [/^ply$/im, 'ply'],
        [/^\#VRML/, 'wrl'],
        [/^\s*{/, 'gltf']
      ];

      var type = false;
      tests.forEach(function(t) {
        if (content.match(t[0])) {
          type = t[1];
        }
      });
      return type;
    },
    parse: function(data, job) {
      var type = this.detectContentType(data);
      if (this.parsers[type]) {
        return this.parsers[type].parse(data, job);
      }
      return new Promise(function(resolve, reject) {
        resolve(false);
      });
    },
    onload: function(job, rawdata, xhr) {
      var databuf = new Uint8Array(rawdata),
          modeldata = false;

      if (this.contentIsGzipped(databuf)) {
        modeldata = window.pako.inflate(databuf, {to: 'string'});
      } else {
        var dataview = new DataView(rawdata);
        if (false && typeof TextDecoder !== 'undefined') {
          var decoder = new TextDecoder('utf-8');
          modeldata = decoder.decode(dataview);
        } else {
          // FIXME - do we really need to do this?  It should be handled on a per-modeltype basis, at least
          modeldata = this.convertArrayBufferToString(rawdata);
        }
      }
      if (modeldata) {
        this.parse(modeldata, job).then(function(data) {
          var transferrables = [];
          // Convert BufferGeometry arrays back to Float32Arrays so they can be transferred efficiently
          try {
            for (var i = 0; i < data.geometries.length; i++) {
              var geo = data.geometries[i];
              for (var k in geo.data.attributes) {
                //var arr = Float32Array.from(geo.data.attributes[k].array);
                var src = geo.data.attributes[k].array;
                var arr = new Float32Array(src.length);
                for (var j = 0; j < src.length; j++) {
                  arr[j] = src[j];
                }
                transferrables.push(arr.buffer);
                geo.data.attributes[k].array = arr;
              }
            }
            postMessage({message: 'finished', id: job.id, data: data}, transferrables);
          } catch (e) {
            postMessage({message: 'error', id: job.id, data: e.toString()});
          }
        }, function(d) {
          postMessage({message: 'error', id: job.id, data: d.toString()});
        });
      } else {
        postMessage({message: 'error', id: job.id, data: 'no modeldata found'});
      }
    },
    onprogress: function(job, ev) {
      //console.log('progress', job, ev);
    },
    onerror: function(job, ev) {
      console.log('error', job, ev);
      postMessage({message: 'error', id: job.id, data: 'unknown error'});
    },
    convertArrayBufferToString: function(rawdata) {
      var converted = '';

      var bufview = new Uint8Array(rawdata);
      var l = bufview.length;
      var str = '';
      for (var i = 0; i < l; i++) {
        str += String.fromCharCode(bufview[i]);
      }

      // convert binary data to string encoding
      try {
        converted = decodeURIComponent(escape(str));
      } catch (e) {
        converted = str;
      }
      return converted;
    }
  });
  elation.define('engine.assets.loaders.model_obj', {
    convertArrayBufferToString: elation.engine.assets.loaders.model.prototype.convertArrayBufferToString,
    parse: function(data, job) {
      return new Promise(elation.bind(this, function(resolve, reject) {
        var mtl = job.data.mtl || false;

        var baseurl = job.data.src.substr( 0, job.data.src.lastIndexOf( "/" ) + 1 )
/*
        if (!mtl) {
          var re = /^mtllib (.*)$/im;
          var m = data.match(re);
          if (m) {
            mtl = m[1];
          }
        }
*/
        //var loader = (mtl ? new THREE.OBJMTLLoader() : new THREE.OBJLoader());
        var loader = new THREE.OBJLoader();
        var modeldata = false;

        if (mtl) {
          var createMaterials = elation.bind(this, function ( materials ) {
                materials.preload();
                loader.setMaterials(materials);
                //loader.setPath(mtlLoader.path);
                modeldata = loader.parse(data);
                resolve(this.convertToJSON(modeldata));
              }),

          mtl = this.getFullURL(mtl, baseurl);
          if (elation.engine.assets.corsproxy && mtl.indexOf(elation.engine.assets.corsproxy) != 0) {
            mtl = elation.engine.assets.corsproxy + mtl;
          }
          var mtlpath = mtl.substr( 0, mtl.lastIndexOf( "/" ) + 1 );
          var mtlfile = mtl.substr(mtl.lastIndexOf( "/" ) + 1);

          var mtlLoader = new THREE.MTLLoader( );
          mtlLoader.setPath( mtlpath );
          mtlLoader.setCrossOrigin( 'anonymous' );
          if (job.data.mtldata) {
            var mtldata = this.convertArrayBufferToString(job.data.mtldata);
            var materials = mtlLoader.parse(mtldata);
            createMaterials(materials);
          } else {
            mtlLoader.load( mtlfile,
              createMaterials,
              function(ev) {
                //console.log('progress?', ev);
              },
              elation.bind(this, function(ev) {
                resolve(this.convertToJSON(modeldata));
              })
            );
          }
        } else {
          modeldata = loader.parse(data);
          resolve(this.convertToJSON(modeldata));
        }
        return modeldata;
      }));
    },
    convertToJSON: function(scene) {
      var json;
      if (scene) {
        // Convert Geometries to BufferGeometries
        scene.traverse(function(n) {
          if (n.geometry && n.geometry instanceof THREE.Geometry) {
            var bufgeo = new THREE.BufferGeometry().fromGeometry(n.geometry);
            n.geometry = bufgeo;
          }
        });
        json = scene.toJSON();
      }
      return json;

    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_collada', {
    parser: new DOMParser(),

    parse: function(xml, job) {
      return new Promise(elation.bind(this, function(resolve, reject) {
        try {
          var data = false;
          var baseurl = job.data.src.substr( 0, job.data.src.lastIndexOf( "/" ) + 1 )
          var loader = new THREE.ColladaLoader();
          loader.options.convertUpAxis = true;
          loader.options.upAxis = 'Y';
          var parsed = loader.parse(xml);
          var imageids = Object.keys(parsed.library.images);
          for (var i = 0; i < imageids.length; i++) {
            var img = parsed.library.images[imageids[i]].build;
            img.src = this.getProxiedURL(baseurl + img.src);
          }
          parsed.scene.traverse(function(n) {
            if ((n.geometry instanceof THREE.BufferGeometry && !n.geometry.attributes.normals) ||
                (n.geometry instanceof THREE.Geometry && !n.geometry.faceVertexNormals)) {
              n.geometry.computeFaceNormals();
              n.geometry.computeVertexNormals();
            }
            // Convert to BufferGeometry for better loading efficiency
            if (n.geometry && n.geometry instanceof THREE.Geometry) {
              var bufgeo = new THREE.BufferGeometry().fromGeometry(n.geometry);
              n.geometry = bufgeo;
            }
          });
          data = parsed;

          data.scene.rotation.z = 0;
          data.scene.updateMatrix();
          resolve(data.scene.toJSON());
        } catch (e) {
          console.error(e);
          reject(e);
        }
      }));
    },
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_wrl', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var loader = new THREE.VRMLLoader();
        var modeldata = loader.parse(data);

        modeldata.traverse(function(n) {
          if (n.geometry && n.geometry instanceof THREE.Geometry) {
            var bufgeo = new THREE.BufferGeometry().fromGeometry(n.geometry);
            n.geometry = bufgeo;
          }
        });

        resolve(modeldata.toJSON());
      });
    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_fbx', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var loader = new THREE.FBXLoader();
        var modeldata = loader.parse(data);
        resolve(modeldata.toJSON());
      });
    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_gltf', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var json = JSON.parse(data);
        var path = THREE.Loader.prototype.extractUrlBase( job.data.src );
        var proxypath = elation.engine.assets.corsproxy + path;

        THREE.GLTFLoader.Shaders.removeAll();
        var loader = new THREE.GLTFLoader();
        loader.parse(json, elation.bind(this, function(modeldata) {
          if (modeldata.scene) {
            var encoded = modeldata.scene.toJSON();
            resolve(encoded);
          } else {
            reject();
          }
        }), proxypath);
      });
    }
  }, elation.engine.assets.base);
  elation.define('engine.assets.loaders.model_ply', {
    parse: function(data, job) {
      return new Promise(function(resolve, reject) {
        var path = THREE.Loader.prototype.extractUrlBase( job.data.src );
        var proxypath = elation.engine.assets.corsproxy + path;

        var loader = new THREE.PLYLoader();
        loader.setPropertyNameMapping({
          diffuse_red: 'red',
          diffuse_green: 'green',
          diffuse_blue: 'blue'
        });

        var geometry = loader.parse(data);
        var encoded = false;

        if (!geometry.index || geometry.index.length == 0) {
          // No face data, render as point cloud
          geometry.index = null;
          var points = new THREE.Points(geometry, new THREE.PointsMaterial({
            color: 0xffffff,
            size: 0.02,
            sizeAttenuation: true,
            vertexColors: (geometry.attributes.color && geometry.attributes.color.length > 0 ? THREE.VertexColors : THREE.NoColors)
          }));
          encoded = points.toJSON();
        } else {
          // Has face data, render as mesh.  Compute normals if necessary
          if (!geometry.attributes.normal) {
            geometry.computeVertexNormals();
          }
          var mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial());
          encoded = mesh.toJSON();
        }
        resolve(encoded);

        //reject();
      });
    },

  }, elation.engine.assets.base);

});

<|endoftext|>