<commit_before>var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
var MetricsTracker = require('../../../../../javascripts/cartodb3/components/metrics/metrics-tracker');
var UserActions = require('../../../../../javascripts/cartodb3/data/user-actions');
var StyleContentView = require('../../../../../javascripts/cartodb3/editor/style/style-content-view');
var StyleDefinitionModel = require('../../../../../javascripts/cartodb3/editor/style/style-definition-model');
var QueryGeometryModel = require('../../../../../javascripts/cartodb3/data/query-geometry-model');
var QuerySchemaModel = require('../../../../../javascripts/cartodb3/data/query-schema-model');

describe('editor/style/style-content-view', function () {
  beforeEach(function () {
    this.overlayModel = new Backbone.Model();
    this.model = new StyleDefinitionModel({
      autogenerated: true
    });

    spyOn(MetricsTracker, 'track');

    var attrs = {
      query: 'SELECT * FROM table',
      status: 'unfetched'
    };
    var opts = {configModel: {}};
    this.querySchemaModel = new QuerySchemaModel(attrs, opts);
    this.queryGeometryModel = new QueryGeometryModel(attrs, opts);

    this.layerDefinitionModel = new Backbone.Model();
    spyOn(this.layerDefinitionModel, 'save');
    spyOn(this.querySchemaModel, 'fetch');
    spyOn(this.queryGeometryModel, 'fetch');
    spyOn(StyleContentView.prototype, 'render').and.callThrough();

    this.userActions = UserActions({
      userModel: new Backbone.Model({
        id: 'whatever'
      }),
      analysisDefinitionsCollection: {},
      analysisDefinitionNodesCollection: {},
      layerDefinitionsCollection: {},
      widgetDefinitionsCollection: {}
    });
    this.promise = $.Deferred();
    spyOn(this.userActions, 'saveLayer').and.returnValue(this.promise);

    this.view = new StyleContentView({
      configModel: {},
      userActions: this.userActions,
      layerDefinitionsCollection: new Backbone.Collection(),
      layerDefinitionModel: this.layerDefinitionModel,
      queryGeometryModel: this.queryGeometryModel,
      querySchemaModel: this.querySchemaModel,
      modals: {},
      styleModel: this.model,
      overlayModel: this.overlayModel,
      editorModel: new Backbone.Model(),
      freezeTorgeAggregation: jasmine.createSpy('freezeTorgeAggregation')
    });
    this.view.render();
  });

  it('should fetch query schema model if it is unfetched', function () {
    expect(this.querySchemaModel.fetch).toHaveBeenCalled();
  });

  it('should fetch geometry if it unfetched', function () {
    expect(this.queryGeometryModel.fetch).toHaveBeenCalled();
  });

  describe('._initBinds', function () {
    it('should render when query schema is fetched', function () {
      StyleContentView.prototype.render.calls.reset();

      this.querySchemaModel.set('status', 'fetching');
      this.queryGeometryModel.set('status', 'fetched');
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled(); // not yet

      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set('status', 'fetched');
      expect(StyleContentView.prototype.render).toHaveBeenCalled();
    });

    it('should not render when query schema is loading/fetching', function () {
      StyleContentView.prototype.render.calls.reset();
      this.querySchemaModel.set('status', 'fetching');
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled();
      this.queryGeometryModel.set('status', 'fetching');
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled();
    });

    it('should render when styleModel is undone or redone', function () {
      StyleContentView.prototype.render.calls.reset();
      var fill = _.clone(this.model.get('fill'));
      fill.size = 34;
      this.model.set('fill', fill);
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled();
      this.model.undo();
      expect(StyleContentView.prototype.render).toHaveBeenCalled();
      this.model.redo();
      expect(StyleContentView.prototype.render.calls.count()).toBe(2);
    });

    it('should change cartocss_custom property when styleModel changes', function () {
      var fill = _.clone(this.model.get('fill'));
      fill.size = 34;
      this.model.set('fill', fill);
      expect(this.layerDefinitionModel.get('cartocss_custom')).toBe(false);
      expect(this.userActions.saveLayer).toHaveBeenCalledWith(this.layerDefinitionModel);
    });

    it('should change autogenerated property when styleModel changes', function () {
      var fill = _.clone(this.model.get('fill'));
      fill.size = 34;
      this.model.set('fill', fill);
      expect(this.model.get('autogenerated')).toBeFalsy();
    });
  });

  describe('.render', function () {
    it('should render "placeholder" if state is loading', function () {
      this.view.modelView.set({state: 'loading'});
      expect(this.view.$('.FormPlaceholder-paragraph').length).toBe(4);
      expect(_.size(this.view._subviews)).toBe(0);
    });

    it('should render properly when models are fetched', function () {
      this.view.modelView.set({state: 'ready'});
      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set('status', 'fetched');
      this.queryGeometryModel.set('simple_geom', 'point');
      this.view.render();
      expect(this.view.$('.Carousel').length).toBe(1);
      expect(this.view.$('.Editor-HeaderInfo').length).toBe(2);
      expect(_.size(this.view._subviews)).toBe(3); // overlay counts
    });

    it('should not render carousel view when geometry is not point', function () {
      this.view.modelView.set({state: 'ready'});
      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set({
        status: 'fetched',
        simple_geom: 'polygon'
      });
      this.view.render();
      expect(this.view.$('.Carousel').length).toBe(0);
      expect(this.view.$('.Editor-HeaderInfo').length).toBe(1);
      expect(_.size(this.view._subviews)).toBe(2);
    });
  });

  describe('._renderForm', function () {
    it('should not render when data has no geometry', function () {
      StyleContentView.prototype.render.calls.reset();
      this.queryGeometryModel.set({
        status: 'fetched',
        simple_geom: ''
      });

      this.view.render();
      expect(_.size(this.view._subviews)).toBe(0);
    });
  });

  it('should not have leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });
});
<commit_msg>Improvements in style-content-view tests<commit_after>var $ = require('jquery');
var Backbone = require('backbone');
var _ = require('underscore');
var MetricsTracker = require('../../../../../javascripts/cartodb3/components/metrics/metrics-tracker');
var UserActions = require('../../../../../javascripts/cartodb3/data/user-actions');
var StyleContentView = require('../../../../../javascripts/cartodb3/editor/style/style-content-view');
var StyleDefinitionModel = require('../../../../../javascripts/cartodb3/editor/style/style-definition-model');
var QueryGeometryModel = require('../../../../../javascripts/cartodb3/data/query-geometry-model');
var QuerySchemaModel = require('../../../../../javascripts/cartodb3/data/query-schema-model');

describe('editor/style/style-content-view', function () {
  beforeEach(function () {
    this.overlayModel = new Backbone.Model();
    this.model = new StyleDefinitionModel({
      autogenerated: true
    });

    spyOn(MetricsTracker, 'track');

    var attrs = {
      query: 'SELECT * FROM table',
      status: 'unfetched'
    };
    var opts = {configModel: {}};
    this.querySchemaModel = new QuerySchemaModel(attrs, opts);
    this.queryGeometryModel = new QueryGeometryModel(attrs, opts);

    this.layerDefinitionModel = new Backbone.Model();
    spyOn(this.layerDefinitionModel, 'save');
    spyOn(this.querySchemaModel, 'fetch');
    spyOn(this.queryGeometryModel, 'fetch');
    spyOn(StyleContentView.prototype, 'render').and.callThrough();

    this.userActions = UserActions({
      userModel: new Backbone.Model({
        id: 'whatever'
      }),
      analysisDefinitionsCollection: {},
      analysisDefinitionNodesCollection: {},
      layerDefinitionsCollection: {},
      widgetDefinitionsCollection: {}
    });
    this.promise = $.Deferred();
    spyOn(this.userActions, 'saveLayer').and.returnValue(this.promise);

    this.view = new StyleContentView({
      configModel: {},
      userActions: this.userActions,
      layerDefinitionsCollection: new Backbone.Collection(),
      layerDefinitionModel: this.layerDefinitionModel,
      queryGeometryModel: this.queryGeometryModel,
      querySchemaModel: this.querySchemaModel,
      modals: {},
      styleModel: this.model,
      overlayModel: this.overlayModel,
      editorModel: new Backbone.Model(),
      freezeTorgeAggregation: jasmine.createSpy('freezeTorgeAggregation')
    });
    this.view.render();
  });

  it('should fetch query schema model if it is unfetched', function () {
    expect(this.querySchemaModel.fetch).toHaveBeenCalled();
  });

  it('should fetch geometry if it unfetched', function () {
    expect(this.queryGeometryModel.fetch).toHaveBeenCalled();
  });

  describe('._initBinds', function () {
    it('should render when query schema is fetched', function () {
      StyleContentView.prototype.render.calls.reset();

      this.querySchemaModel.set('status', 'fetching');
      this.queryGeometryModel.set('status', 'fetched');
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled(); // not yet

      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set('status', 'fetched');
      expect(StyleContentView.prototype.render).toHaveBeenCalled();
    });

    it('should not render when query schema is loading/fetching', function () {
      StyleContentView.prototype.render.calls.reset();
      this.querySchemaModel.set('status', 'fetching');
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled();
      this.queryGeometryModel.set('status', 'fetching');
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled();
    });

    it('should render when styleModel is undone or redone', function () {
      StyleContentView.prototype.render.calls.reset();
      var fill = _.clone(this.model.get('fill'));
      fill.size = 34;
      this.model.set('fill', fill);
      expect(StyleContentView.prototype.render).not.toHaveBeenCalled();
      this.model.undo();
      expect(StyleContentView.prototype.render).toHaveBeenCalled();
      this.model.redo();
      expect(StyleContentView.prototype.render.calls.count()).toBe(2);
    });

    it('should change cartocss_custom property when styleModel changes', function () {
      var fill = _.clone(this.model.get('fill'));
      fill.size = 34;
      this.model.set('fill', fill);
      expect(this.layerDefinitionModel.get('cartocss_custom')).toBe(false);
      expect(this.userActions.saveLayer).toHaveBeenCalledWith(this.layerDefinitionModel);
    });

    it('should change autogenerated property when styleModel changes', function () {
      var fill = _.clone(this.model.get('fill'));
      fill.size = 34;
      this.model.set('fill', fill);
      expect(this.model.get('autogenerated')).toBeFalsy();
    });
  });

  describe('.render', function () {
    it('should render "placeholder" if state is loading', function () {
      this.view.modelView.set({state: 'loading'});
      expect(this.view.$('.FormPlaceholder-paragraph').length).toBe(4);
      expect(_.size(this.view._subviews)).toBe(0);
    });

    it('should render properly when models are fetched', function () {
      this.view.modelView.set({state: 'ready'});
      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set('status', 'fetched');
      this.queryGeometryModel.set('simple_geom', 'point');
      this.view.render();
      expect(this.view.$('.Carousel').length).toBe(1);
      expect(this.view.$('.Editor-HeaderInfo').length).toBe(2);
      expect(_.size(this.view._subviews)).toBe(3); // overlay counts
    });

    it('should not render carousel view when geometry is not point', function () {
      this.view.modelView.set({state: 'ready'});
      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set({
        status: 'fetched',
        simple_geom: 'polygon'
      });
      this.view.render();
      expect(this.view.$('.Carousel').length).toBe(0);
      expect(this.view.$('.Editor-HeaderInfo').length).toBe(1);
      expect(_.size(this.view._subviews)).toBe(2);
    });

    it('should not render _renderForm when data has no geometry', function () {
      this.view.modelView.set({state: 'ready'});
      this.querySchemaModel.set('status', 'fetched');
      this.queryGeometryModel.set({
        status: 'fetched',
        simple_geom: ''
      });

      this.view.render();
      expect(this.view.$el.html()).toContain('editor.style.style-form.properties.placeholder-text');
    });
  });

  it('should not have leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });
});
<|endoftext|>


<commit_before>/**
 * External dependencies
 */
import { every } from 'lodash';

/**
 * WordPress dependencies
 */
import { addFilter } from '@wordpress/hooks';
import { render } from '@wordpress/element';
import domReady from '@wordpress/dom-ready';
import { select, subscribe, dispatch } from '@wordpress/data';
import { createBlock, getDefaultBlockName, setDefaultBlockName, getBlockTypes, unregisterBlockType } from '@wordpress/blocks';
const { moveBlockToPosition, updateBlockAttributes } = dispatch( 'core/editor' );

/**
 * Internal dependencies
 */
import {
	withAmpStorySettings,
	withAnimationControls,
	withPageNumber,
	withWrapperProps,
	withActivePageState,
	withStoryBlockDropZone,
	BlockNavigation,
	EditorCarousel,
	StoryControls,
	Shortcuts,
} from './components';
import { ALLOWED_BLOCKS, ALLOWED_CHILD_BLOCKS } from './constants';
import { maybeEnqueueFontStyle, setBlockParent, addAMPAttributes, addAMPExtraProps } from './helpers';
import store from './stores/amp-story';

/**
 * Initialize editor integration.
 */
domReady( () => {
	const { getBlocksByClientId, getClientIdsWithDescendants, getBlockRootClientId } = select( 'core/editor' );
	const { addAnimation, setCurrentPage } = dispatch( 'amp/story' );

	// Ensure that the default block is page when no block is selected.
	setDefaultBlockName( 'amp/amp-story-page' );

	// Remove all blocks that aren't whitelisted.
	const disallowedBlockTypes = getBlockTypes().filter( ( { name } ) => ! ALLOWED_BLOCKS.includes( name ) );

	for ( const blockType of disallowedBlockTypes ) {
		unregisterBlockType( blockType.name );
	}

	const allBlocks = getBlocksByClientId( getClientIdsWithDescendants() );

	// Set initially shown page.
	const firstPage = allBlocks.find( ( { name } ) => name === 'amp/amp-story-page' );
	setCurrentPage( firstPage ? firstPage.clientId : undefined );

	for ( const block of allBlocks ) {
		const page = getBlockRootClientId( block.clientId );

		// Set initial animation order state.
		if ( page ) {
			const ampAnimationAfter = block.attributes.ampAnimationAfter;
			const predecessor = allBlocks.find( ( b ) => b.attributes.anchor === ampAnimationAfter );

			addAnimation( page, block.clientId, predecessor ? predecessor.clientId : undefined );
		}

		// Load all needed fonts.
		if ( block.attributes.ampFontFamily ) {
			maybeEnqueueFontStyle( block.attributes.ampFontFamily );
		}
	}

	renderStoryComponents();

	// Prevent WritingFlow component from focusing on last text field when clicking below the carousel.
	document.querySelector( '.editor-writing-flow__click-redirect' ).remove();
} );

/**
 * Add some additional elements needed to render our custom UI controls.
 */
function renderStoryComponents() {
	const editorBlockList = document.querySelector( '.editor-block-list__layout' );
	const editorBlockNavigation = document.querySelector( '.editor-block-navigation' );

	if ( editorBlockList ) {
		const ampStoryWrapper = document.createElement( 'div' );
		ampStoryWrapper.id = 'amp-story-editor';

		const blockNavigation = document.createElement( 'div' );
		blockNavigation.id = 'amp-root-navigation';

		const editorCarousel = document.createElement( 'div' );
		editorCarousel.id = 'amp-story-editor-carousel';

		const storyControls = document.createElement( 'div' );
		storyControls.id = 'amp-story-controls';

		/**
		 * The intended layout is as follows:
		 *
		 * - Post title
		 * - AMP story wrapper element (needed for overflow styling)
		 * - - Story controls
		 * - - Block list
		 * - - Block navigation
		 * - - Carousel controls
		 */
		editorBlockList.parentNode.replaceChild( ampStoryWrapper, editorBlockList );
		ampStoryWrapper.appendChild( storyControls );
		ampStoryWrapper.appendChild( editorBlockList );
		ampStoryWrapper.appendChild( blockNavigation );
		ampStoryWrapper.appendChild( editorCarousel );

		render(
			<StoryControls />,
			storyControls
		);

		render(

				<BlockNavigation />

,
			blockNavigation
		);

		render(

				<EditorCarousel />

,
			editorCarousel
		);
	}

	if ( editorBlockNavigation ) {
		const shortcuts = document.createElement( 'div' );
		shortcuts.id = 'amp-story-shortcuts';

		editorBlockNavigation.parentNode.parentNode.insertBefore( shortcuts, editorBlockNavigation.parentNode.nextSibling );

		render(
			<Shortcuts />,
			shortcuts
		);
	}
}

const { getBlockOrder, getBlock, getBlockRootClientId } = select( 'core/editor' );

const positionTopLimit = 75;
const positionTopHighest = 5;
const positionTopGap = 10;

/**
 * Set initial positioning if the selected block is an unmodified block.
 *
 * @param {number} selectedBlockClientId Selected block's ID.
 */
function maybeSetInitialPositioning( selectedBlockClientId ) {
	const block = getBlock( selectedBlockClientId );
	if ( ! ALLOWED_CHILD_BLOCKS.includes( block.name ) ) {
		return;
	}

	const parentBlock = getBlock( getBlockRootClientId( selectedBlockClientId ) );
	// Short circuit if the positions are already set or the parent block doesn't have any inner blocks.
	if (
		0 !== block.attributes.positionTop ||
		0 !== block.attributes.positionLeft ||
		! parentBlock.innerBlocks.length
	) {
		return;
	}

	// Check if it's a new block.
	const newBlock = createBlock( block.name );
	const isUnmodified = every( newBlock.attributes, ( value, key ) =>
		value === block.attributes[ key ]
	);

	// Only set the position if the block was unmodifed before.
	if ( isUnmodified ) {
		let highestTop = 0;
		// Get all child blocks of the parent block and get the highest "positionTop" value.
		parentBlock.innerBlocks.forEach( ( childBlock ) => {
			if ( childBlock.attributes.positionTop > highestTop ) {
				highestTop = childBlock.attributes.positionTop;
			}
		} );
		// If it's more than the limit, set the new one.
		const newPositionTop = highestTop > positionTopLimit ? positionTopHighest : highestTop + positionTopGap;

		updateBlockAttributes( selectedBlockClientId, { positionTop: newPositionTop } );
	}
}

let blockOrder = getBlockOrder();

subscribe( () => {
	const { getSelectedBlockClientId } = select( 'core/editor' );
	const { setCurrentPage, removePage } = dispatch( 'amp/story' );
	const defaultBlockName = getDefaultBlockName();
	const selectedBlockClientId = getSelectedBlockClientId();

	// Switch default block depending on context
	if ( selectedBlockClientId ) {
		const selectedBlock = getBlock( selectedBlockClientId );

		if ( 'amp/amp-story-page' === selectedBlock.name && 'amp/amp-story-page' !== defaultBlockName ) {
			setDefaultBlockName( 'amp/amp-story-page' );
		} else if ( 'amp/amp-story-page' !== selectedBlock.name && 'amp/amp-story-text' !== defaultBlockName ) {
			setDefaultBlockName( 'amp/amp-story-text' );
		}

		// If newly added blocks we'll also position them.
		maybeSetInitialPositioning( selectedBlockClientId );
	} else if ( ! selectedBlockClientId && 'amp/amp-story-page' !== defaultBlockName ) {
		setDefaultBlockName( 'amp/amp-story-page' );
	}

	const newBlockOrder = getBlockOrder();
	const deletedPages = blockOrder.filter( ( block ) => ! newBlockOrder.includes( block ) );
	const newlyAddedPages = newBlockOrder.find( ( block ) => ! blockOrder.includes( block ) );

	blockOrder = newBlockOrder;

	// If a new page has been inserted, make it the current one.
	if ( newlyAddedPages ) {
		setCurrentPage( newlyAddedPages );
	}

	// Remove stale data from store.
	for ( const oldPage of deletedPages ) {
		removePage( oldPage );
	}
} );

const { isReordering, getBlockOrder: getCustomBlockOrder, getAnimationOrder } = select( 'amp/story' );

store.subscribe( () => {
	const editorBlockOrder = getBlockOrder();
	const customBlockOrder = getCustomBlockOrder();

	// The block order has changed, let's re-order.
	if ( ! isReordering() && customBlockOrder.length > 0 && editorBlockOrder !== customBlockOrder ) {
		for ( const [ index, page ] of customBlockOrder.entries() ) {
			moveBlockToPosition( page, '', '', index );
		}
	}

	const animationOrder = getAnimationOrder();

	for ( const page in animationOrder ) {
		if ( ! animationOrder.hasOwnProperty( page ) ) {
			continue;
		}

		for ( const item of animationOrder[ page ] ) {
			const parentBlock = item.parent ? getBlock( item.parent ) : undefined;

			updateBlockAttributes( item.id, { ampAnimationAfter: parentBlock ? parentBlock.attributes.anchor : undefined } );
		}
	}
} );

// These do not reliably work at domReady.
addFilter( 'blocks.registerBlockType', 'ampStoryEditorBlocks/setBlockParent', setBlockParent );
addFilter( 'blocks.registerBlockType', 'ampStoryEditorBlocks/addAttributes', addAMPAttributes );
addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addAnimationControls', withAnimationControls );
addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addStorySettings', withAmpStorySettings );
addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addPageNumber', withPageNumber );
addFilter( 'editor.BlockListBlock', 'ampStoryEditorBlocks/withActivePageState', withActivePageState );
addFilter( 'editor.BlockListBlock', 'ampStoryEditorBlocks/addWrapperProps', withWrapperProps );
addFilter( 'blocks.getSaveContent.extraProps', 'ampStoryEditorBlocks/addExtraAttributes', addAMPExtraProps );
addFilter( 'editor.BlockDropZone', 'ampStoryEditorBlocks/withStoryBlockDropZone', withStoryBlockDropZone );
<commit_msg>Remove comment that got added back with merge.<commit_after>/**
 * External dependencies
 */
import { every } from 'lodash';

/**
 * WordPress dependencies
 */
import { addFilter } from '@wordpress/hooks';
import { render } from '@wordpress/element';
import domReady from '@wordpress/dom-ready';
import { select, subscribe, dispatch } from '@wordpress/data';
import { createBlock, getDefaultBlockName, setDefaultBlockName, getBlockTypes, unregisterBlockType } from '@wordpress/blocks';
const { moveBlockToPosition, updateBlockAttributes } = dispatch( 'core/editor' );

/**
 * Internal dependencies
 */
import {
	withAmpStorySettings,
	withAnimationControls,
	withPageNumber,
	withWrapperProps,
	withActivePageState,
	withStoryBlockDropZone,
	BlockNavigation,
	EditorCarousel,
	StoryControls,
	Shortcuts,
} from './components';
import { ALLOWED_BLOCKS, ALLOWED_CHILD_BLOCKS } from './constants';
import { maybeEnqueueFontStyle, setBlockParent, addAMPAttributes, addAMPExtraProps } from './helpers';
import store from './stores/amp-story';

/**
 * Initialize editor integration.
 */
domReady( () => {
	const { getBlocksByClientId, getClientIdsWithDescendants, getBlockRootClientId } = select( 'core/editor' );
	const { addAnimation, setCurrentPage } = dispatch( 'amp/story' );

	// Ensure that the default block is page when no block is selected.
	setDefaultBlockName( 'amp/amp-story-page' );

	// Remove all blocks that aren't whitelisted.
	const disallowedBlockTypes = getBlockTypes().filter( ( { name } ) => ! ALLOWED_BLOCKS.includes( name ) );

	for ( const blockType of disallowedBlockTypes ) {
		unregisterBlockType( blockType.name );
	}

	const allBlocks = getBlocksByClientId( getClientIdsWithDescendants() );

	// Set initially shown page.
	const firstPage = allBlocks.find( ( { name } ) => name === 'amp/amp-story-page' );
	setCurrentPage( firstPage ? firstPage.clientId : undefined );

	for ( const block of allBlocks ) {
		const page = getBlockRootClientId( block.clientId );

		// Set initial animation order state.
		if ( page ) {
			const ampAnimationAfter = block.attributes.ampAnimationAfter;
			const predecessor = allBlocks.find( ( b ) => b.attributes.anchor === ampAnimationAfter );

			addAnimation( page, block.clientId, predecessor ? predecessor.clientId : undefined );
		}

		// Load all needed fonts.
		if ( block.attributes.ampFontFamily ) {
			maybeEnqueueFontStyle( block.attributes.ampFontFamily );
		}
	}

	renderStoryComponents();

	// Prevent WritingFlow component from focusing on last text field when clicking below the carousel.
	document.querySelector( '.editor-writing-flow__click-redirect' ).remove();
} );

/**
 * Add some additional elements needed to render our custom UI controls.
 */
function renderStoryComponents() {
	const editorBlockList = document.querySelector( '.editor-block-list__layout' );
	const editorBlockNavigation = document.querySelector( '.editor-block-navigation' );

	if ( editorBlockList ) {
		const ampStoryWrapper = document.createElement( 'div' );
		ampStoryWrapper.id = 'amp-story-editor';

		const blockNavigation = document.createElement( 'div' );
		blockNavigation.id = 'amp-root-navigation';

		const editorCarousel = document.createElement( 'div' );
		editorCarousel.id = 'amp-story-editor-carousel';

		const storyControls = document.createElement( 'div' );
		storyControls.id = 'amp-story-controls';

		/**
		 * The intended layout is as follows:
		 *
		 * - Post title
		 * - AMP story wrapper element (needed for overflow styling)
		 * - - Story controls
		 * - - Block list
		 * - - Block navigation
		 * - - Carousel controls
		 */
		editorBlockList.parentNode.replaceChild( ampStoryWrapper, editorBlockList );
		ampStoryWrapper.appendChild( storyControls );
		ampStoryWrapper.appendChild( editorBlockList );
		ampStoryWrapper.appendChild( blockNavigation );
		ampStoryWrapper.appendChild( editorCarousel );

		render(
			<StoryControls />,
			storyControls
		);

		render(

				<BlockNavigation />

,
			blockNavigation
		);

		render(

				<EditorCarousel />

,
			editorCarousel
		);
	}

	if ( editorBlockNavigation ) {
		const shortcuts = document.createElement( 'div' );
		shortcuts.id = 'amp-story-shortcuts';

		editorBlockNavigation.parentNode.parentNode.insertBefore( shortcuts, editorBlockNavigation.parentNode.nextSibling );

		render(
			<Shortcuts />,
			shortcuts
		);
	}
}

const { getBlockOrder, getBlock, getBlockRootClientId } = select( 'core/editor' );

const positionTopLimit = 75;
const positionTopHighest = 5;
const positionTopGap = 10;

/**
 * Set initial positioning if the selected block is an unmodified block.
 *
 * @param {number} selectedBlockClientId Selected block's ID.
 */
function maybeSetInitialPositioning( selectedBlockClientId ) {
	const block = getBlock( selectedBlockClientId );
	if ( ! ALLOWED_CHILD_BLOCKS.includes( block.name ) ) {
		return;
	}

	const parentBlock = getBlock( getBlockRootClientId( selectedBlockClientId ) );
	// Short circuit if the positions are already set or the parent block doesn't have any inner blocks.
	if (
		0 !== block.attributes.positionTop ||
		0 !== block.attributes.positionLeft ||
		! parentBlock.innerBlocks.length
	) {
		return;
	}

	// Check if it's a new block.
	const newBlock = createBlock( block.name );
	const isUnmodified = every( newBlock.attributes, ( value, key ) =>
		value === block.attributes[ key ]
	);

	// Only set the position if the block was unmodifed before.
	if ( isUnmodified ) {
		let highestTop = 0;
		// Get all child blocks of the parent block and get the highest "positionTop" value.
		parentBlock.innerBlocks.forEach( ( childBlock ) => {
			if ( childBlock.attributes.positionTop > highestTop ) {
				highestTop = childBlock.attributes.positionTop;
			}
		} );
		// If it's more than the limit, set the new one.
		const newPositionTop = highestTop > positionTopLimit ? positionTopHighest : highestTop + positionTopGap;

		updateBlockAttributes( selectedBlockClientId, { positionTop: newPositionTop } );
	}
}

let blockOrder = getBlockOrder();

subscribe( () => {
	const { getSelectedBlockClientId } = select( 'core/editor' );
	const { setCurrentPage, removePage } = dispatch( 'amp/story' );
	const defaultBlockName = getDefaultBlockName();
	const selectedBlockClientId = getSelectedBlockClientId();

	// Switch default block depending on context
	if ( selectedBlockClientId ) {
		const selectedBlock = getBlock( selectedBlockClientId );

		if ( 'amp/amp-story-page' === selectedBlock.name && 'amp/amp-story-page' !== defaultBlockName ) {
			setDefaultBlockName( 'amp/amp-story-page' );
		} else if ( 'amp/amp-story-page' !== selectedBlock.name && 'amp/amp-story-text' !== defaultBlockName ) {
			setDefaultBlockName( 'amp/amp-story-text' );
		}

		// If newly added blocks we'll also position them.
		maybeSetInitialPositioning( selectedBlockClientId );
	} else if ( ! selectedBlockClientId && 'amp/amp-story-page' !== defaultBlockName ) {
		setDefaultBlockName( 'amp/amp-story-page' );
	}

	const newBlockOrder = getBlockOrder();
	const deletedPages = blockOrder.filter( ( block ) => ! newBlockOrder.includes( block ) );
	const newlyAddedPages = newBlockOrder.find( ( block ) => ! blockOrder.includes( block ) );

	blockOrder = newBlockOrder;

	// If a new page has been inserted, make it the current one.
	if ( newlyAddedPages ) {
		setCurrentPage( newlyAddedPages );
	}

	// Remove stale data from store.
	for ( const oldPage of deletedPages ) {
		removePage( oldPage );
	}
} );

const { isReordering, getBlockOrder: getCustomBlockOrder, getAnimationOrder } = select( 'amp/story' );

store.subscribe( () => {
	const editorBlockOrder = getBlockOrder();
	const customBlockOrder = getCustomBlockOrder();

	// The block order has changed, let's re-order.
	if ( ! isReordering() && customBlockOrder.length > 0 && editorBlockOrder !== customBlockOrder ) {
		for ( const [ index, page ] of customBlockOrder.entries() ) {
			moveBlockToPosition( page, '', '', index );
		}
	}

	const animationOrder = getAnimationOrder();

	for ( const page in animationOrder ) {
		if ( ! animationOrder.hasOwnProperty( page ) ) {
			continue;
		}

		for ( const item of animationOrder[ page ] ) {
			const parentBlock = item.parent ? getBlock( item.parent ) : undefined;

			updateBlockAttributes( item.id, { ampAnimationAfter: parentBlock ? parentBlock.attributes.anchor : undefined } );
		}
	}
} );

addFilter( 'blocks.registerBlockType', 'ampStoryEditorBlocks/setBlockParent', setBlockParent );
addFilter( 'blocks.registerBlockType', 'ampStoryEditorBlocks/addAttributes', addAMPAttributes );
addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addAnimationControls', withAnimationControls );
addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addStorySettings', withAmpStorySettings );
addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addPageNumber', withPageNumber );
addFilter( 'editor.BlockListBlock', 'ampStoryEditorBlocks/withActivePageState', withActivePageState );
addFilter( 'editor.BlockListBlock', 'ampStoryEditorBlocks/addWrapperProps', withWrapperProps );
addFilter( 'blocks.getSaveContent.extraProps', 'ampStoryEditorBlocks/addExtraAttributes', addAMPExtraProps );
addFilter( 'editor.BlockDropZone', 'ampStoryEditorBlocks/withStoryBlockDropZone', withStoryBlockDropZone );
<|endoftext|>


<commit_before>'use strict'

const locale = {}

locale.en = require('./en-US.json')
locale.de = require('./de-DE.json')
locale.fr = require('./fr-FR.json')
locale.ja = require('./ja-JA.json')

alias('en', 'AU', 'CA', 'GB', 'NZ', 'SA', 'US')
alias('de', 'AT', 'CH', 'DE')
alias('fr', 'CH', 'FR')

function alias(lc, ...args) {
  for (const ct of args) locale[`${lc}-${ct}`] = locale[lc]
}

module.exports = locale
<commit_msg>Load Italian locale<commit_after>'use strict'

const locale = {}

locale.en = require('./en-US.json')
locale.de = require('./de-DE.json')
locale.fr = require('./fr-FR.json')
locale.it = require('./it-IT.json')
locale.ja = require('./ja-JA.json')

alias('en', 'AU', 'CA', 'GB', 'NZ', 'SA', 'US')
alias('de', 'AT', 'CH', 'DE')
alias('fr', 'CH', 'FR')

function alias(lc, ...args) {
  for (const ct of args) locale[`${lc}-${ct}`] = locale[lc]
}

module.exports = locale
<|endoftext|>


<commit_before>import {exec} from 'node-promise-es6/child-process';
import fs from 'node-promise-es6/fs';

async function run() {
  const {linkDependencies = {}} = await fs.readJson('package.json');
  for (const dependencyName of Object.keys(linkDependencies)) {
    const dependencyPath = linkDependencies[dependencyName];

    const {stdout: diff} = await exec(
      'git diff-index HEAD', {cwd: dependencyPath});
    if (diff.trim().indexOf('package.json') === -1) { continue; }

    process.stdout.write(diff + '\n');
    process.stdout.write(`Committing 'vinsonchuong/${dependencyName}'\n`);

    await exec(
      `git config user.email 'vinsonchuong@gmail.com'`,
      {cwd: dependencyPath}
    );
    await exec(
      `git config user.name 'Vinson Chuong'`,
      {cwd: dependencyPath}
    );
    await exec(
      [
        'git commit',
        `-m 'Automatically update dependencies'`,
        'package.json'
      ].join(' '),
      {cwd: dependencyPath}
    );
    await exec(
      [
        'git push',
        `https://${process.env.GITHUB_TOKEN}@github.com/vinsonchuong/${dependencyName}`,
        'master'
      ].join(' '),
      {cwd: dependencyPath}
    );
  }
}

run().catch(e => {
  process.stderr.write(`${e.stack}\n`);
  process.exit(1);
});
<commit_msg>Use git diff instead of git diff-index<commit_after>import {exec} from 'node-promise-es6/child-process';
import fs from 'node-promise-es6/fs';

async function run() {
  const {linkDependencies = {}} = await fs.readJson('package.json');
  for (const dependencyName of Object.keys(linkDependencies)) {
    const dependencyPath = linkDependencies[dependencyName];

    const {stdout: diff} = await exec('git diff', {cwd: dependencyPath});
    if (!diff.trim()) { continue; }

    process.stdout.write(`Committing 'vinsonchuong/${dependencyName}'\n`);
    process.stdout.write(diff + '\n');

    await exec(
      `git config user.email 'vinsonchuong@gmail.com'`,
      {cwd: dependencyPath}
    );
    await exec(
      `git config user.name 'Vinson Chuong'`,
      {cwd: dependencyPath}
    );
    await exec(
      [
        'git commit',
        `-m 'Automatically update dependencies'`,
        'package.json'
      ].join(' '),
      {cwd: dependencyPath}
    );
    await exec(
      [
        'git push',
        `https://${process.env.GITHUB_TOKEN}@github.com/vinsonchuong/${dependencyName}`,
        'master'
      ].join(' '),
      {cwd: dependencyPath}
    );
  }
}

run().catch(e => {
  process.stderr.write(`${e.stack}\n`);
  process.exit(1);
});
<|endoftext|>


<commit_before>import { Picker } from 'meteor/meteorhacks:picker';
import Files from './files';
import Settings from './settings';

/**
 * @summary Route to support antiquated file_open.php from old website
 *   to continue to allow old download links to work
 */
Picker.route('/file_open.php', (params, req, res) => {
  const fileId = Number(params.query.id);
  const file = Files.collection.findOne({ fileId });

  if (file) {
    const { bucket, region } = Settings.get('aws');

    // HTTP 301 Permanent Redirect
    res.writeHead(
      301,
      { Location: `https://s3.${region}.amazonaws.com/${bucket}/old-website-resources/${file.fileName}` },
    );
    res.end();
  } else { res.end('This file does not exist.'); }
});
<commit_msg>Encode file_open.php file names so that S3 returns the proper file instead of throwing a 403 forbidden.<commit_after>import { Picker } from 'meteor/meteorhacks:picker';
import Files from './files';
import Settings from './settings';

/**
 * @summary Route to support antiquated file_open.php from old website
 *   to continue to allow old download links to work
 */
Picker.route('/file_open.php', (params, req, res) => {
  const fileId = Number(params.query.id);
  const file = Files.collection.findOne({ fileId });

  if (file) {
    const { bucket, region } = Settings.get('aws');

    // HTTP 301 Permanent Redirect
    res.writeHead(301, {
      Location: `https://s3.${region}.amazonaws.com/${bucket}/old-website-resources/${
        encodeURIComponent(file.fileName)}`,
    });
    res.end();
  } else { res.end('This file does not exist.'); }
});
<|endoftext|>


<commit_before>Hull.component({
  templates: ['default'],

  refreshEvents: ['model.hull.me.change'],

  linkTagInjected: false,

  initialize: function() {
    this.isLoading = false;

    this.sandbox.on('hull.shopify.loading.start', this.startLoading, this);
    this.sandbox.on('hull.shopify.loading.stop', this.stopLoading, this);

    this.injectLinkTag();
  },

  startLoading: function() {
    if (this.isLoading) { return; }

    this.$el.addClass('hull-loading');
    this.isLoading = true;
  },

  stopLoading: function() {
    if (!this.isLoading) { return; }

    this.$el.removeClass('hull-loading');
    this.isLoading = false;
  },

  beforeRender: function(data) {
    var _ = this.sandbox.util._;

    this.template = this.options.theme && _.contains(this.templates, this.options.theme) ? this.options.theme : 'default';

    var classes = [];
    classes.push('hull-theme-' + this.template);
    classes.push('hull-' + (this.options.inline ? 'inline' : 'block'));
    if (this.isLoading) { classes.push('hull-loading'); }
    data.classes = classes.join(' ');

    var l = this.loggedIn();
    data.providers = _.reduce(this.authServices(), function(m, p) {
      m[p] = !!l[p] && { isUnlinkable: l && data.me.main_identity !== p };
      return m;
    }, {});

    data.showLinkIdentity = this.options.showLinkIdentity !== false;
  },

  afterRender: function() {
    this.$errorContainer = this.$('.hull-error-container');
  },

  injectLinkTag: function() {
    if (this.linkTagInjected || this.options.injectLinkTag === false) { return; }

    var e = document.createElement('link');
    e.href = this.options.baseUrl + '/style.min.css';
    e.rel = 'stylesheet';

    document.getElementsByTagName('head')[0].appendChild(e);

    this.linkTagInjected = true;
  },

  callAndStartLoading: function(methodName, provider, handleSuccess) {
    this.$errorContainer.html('');

    this.startLoading();

    this.$el.addClass('hull-' + methodName);

    var self = this;
    this.sandbox[methodName](provider).then(function() {
      if (handleSuccess) { self.stopLoading(); }
    }, function(error) {
      self.showErrorMessage(error.message);
      self.stopLoading();
    });
  },

  showErrorMessage: function(message) {
    if (this.options.showErrors === false) { return; }

    var $error = $(document.createElement('p')).addClass('hull-error').text(message);
    this.$errorContainer.html($error);
  },

  actions: {
    _login: function(event, action) {
      this.callAndStartLoading('login', action.data.provider);
    },

    _logout: function(event, action) {
      this.callAndStartLoading('logout', action.data.provider);
    },

    _linkIdentity: function(event, action) {
      this.callAndStartLoading('linkIdentity', action.data.provider, true);
    },

    _unlinkIdentity: function(event, action) {
      this.callAndStartLoading('unlinkIdentity', action.data.provider, true);
    }
  }
});
<commit_msg>Better error message for Shopify component<commit_after>Hull.component({
  templates: ['default'],

  refreshEvents: ['model.hull.me.change'],

  linkTagInjected: false,

  defaultErrorMessages: {
    identityTakenMessage: '"{{provider}}" identity is already linked to another User',
    emailTakenMessage: '"{{email}}" is already taken',
    authFailedMessage: 'You did not fully authorize or "{{provider}}" app is not well configured',
    windowClosedMessage: 'Authorization window has been closed'
  },

  initialize: function() {
    this.isLoading = false;

    this.errorMessages = this.sandbox.util._.reduce(this.defaultErrorMessages, function(memo, v, k) {
      memo[k] = Hull.util.Handlebars.compile(this.options[k] || v);
      return memo;
    }, {}, this);

    this.sandbox.on('hull.shopify.loading.start', this.startLoading, this);
    this.sandbox.on('hull.shopify.loading.stop', this.stopLoading, this);

    this.injectLinkTag();
  },

  startLoading: function() {
    if (this.isLoading) { return; }

    this.$el.addClass('hull-loading');
    this.isLoading = true;
  },

  stopLoading: function() {
    if (!this.isLoading) { return; }

    this.$el.removeClass('hull-loading');
    this.isLoading = false;
  },

  beforeRender: function(data) {
    var _ = this.sandbox.util._;

    this.template = this.options.theme && _.contains(this.templates, this.options.theme) ? this.options.theme : 'default';

    var classes = [];
    classes.push('hull-theme-' + this.template);
    classes.push('hull-' + (this.options.inline ? 'inline' : 'block'));
    if (this.isLoading) { classes.push('hull-loading'); }
    data.classes = classes.join(' ');

    var l = this.loggedIn();
    data.providers = _.reduce(this.authServices(), function(m, p) {
      m[p] = !!l[p] && { isUnlinkable: l && data.me.main_identity !== p };
      return m;
    }, {});

    data.showLinkIdentity = this.options.showLinkIdentity !== false;
  },

  afterRender: function() {
    this.$errorContainer = this.$('.hull-error-container');
  },

  injectLinkTag: function() {
    if (this.linkTagInjected || this.options.injectLinkTag === false) { return; }

    var e = document.createElement('link');
    e.href = this.options.baseUrl + '/style.min.css';
    e.rel = 'stylesheet';

    document.getElementsByTagName('head')[0].appendChild(e);

    this.linkTagInjected = true;
  },

  callAndStartLoading: function(methodName, provider, handleSuccess) {
    this.$errorContainer.html('');

    this.startLoading();

    this.$el.addClass('hull-' + methodName);

    var self = this;
    this.sandbox[methodName](provider).then(function() {
      if (handleSuccess) { self.stopLoading(); }
    }, function(error) {
      var t = self.errorMessages[self.sandbox.util._.string.camelize(error.reason + '_message')];
      var message = t ? t(self.sandbox.util._.extend({ provider: provider }, error)) : (error.message || self.options.fallbackMessage);

      self.showErrorMessage(message);
      self.stopLoading();
    });
  },

  showErrorMessage: function(message) {
    if (this.options.showErrors === false) { return; }

    var $error = $(document.createElement('p')).addClass('hull-error').text(message);
    this.$errorContainer.html($error);
  },

  actions: {
    _login: function(event, action) {
      this.callAndStartLoading('login', action.data.provider);
    },

    _logout: function(event, action) {
      this.callAndStartLoading('logout', action.data.provider);
    },

    _linkIdentity: function(event, action) {
      this.callAndStartLoading('linkIdentity', action.data.provider, true);
    },

    _unlinkIdentity: function(event, action) {
      this.callAndStartLoading('unlinkIdentity', action.data.provider, true);
    }
  }
});
<|endoftext|>


<commit_before>/**
 * @file: slide-show and grid-display manager.
 */

(function() {
 /***
  *
  */
 Slides = function (config) {
   var S = this; //that

   this.pre = [];
   //default configuration
   this.conf = config || {};
   this.conf = {
     slider: S.conf.slider || null,
     images: S.conf.images || null,
     current: S.conf.current || 0,
     currentPage: S.conf.currentPage || 1, //1-based index
     pageSize: S.conf.pageSize || 3,
     slideTag: S.conf.slideTag || 'li',
     viewerID: S.conf.viewerID || 'viewer',
     filmStrip: S.conf.filmStrip || false,
     slideClass: S.conf.slideClass || 'slide',
     jqc: S.conf.context || window.document,
     jq: S.conf.jq || (function () {
       if ('jquery' in S.conf.slider) {
         return S.conf.slider.constructor
       }
       else {
         return null;
       }
     })(),
   }

   //sanity check
   if (S.conf.images == null || S.conf.slider == null) {
     console.error('Must pass "slider" and "images" properties to configuration oject of Slides().');
     return false;
   }

   //initialize each slide in our grid
   this.init();

   return this;
 }

 /**
  * Create necessary grid, DOM elements, and initialize event bindings as
  * necessary.
  */
 Slides.prototype.init = function () {
   var slide, S = this;

   //
   //create the entire slide grid, empty.
   //
   this.initSlides();

   //
   //bind to various events
   //
   this.conf.jq('.' + this.conf.slideClass, this.conf.slider)
     .click(function (event) {
       S.createViewer(S.conf.jq(this).attr('data-slide'));
     });
   window.onkeyup = function (e) {
     switch (e.keyCode) {
       //close modal window
       case 27: // ESC
         S.destroyViewer();
         break;

       //previous slide
       case 37: // left-arrow
       case 75: // k
       case 80: // p
         S.previous();
         break;

       //next slide
       case 39: // right-arrow
       case 74: // j
       case 78: // n
         S.next();
         break;
     }
   };
   return this;
 }

 /**
  *
  */
 Slides.prototype.initSlides = function () {
   $slides = this.conf.jq('.slide', this.conf.slider);
   if ('length' in $slides && $slides.length > 0) {
     return this;
   }

   for (var i in this.conf.images) {
     //get our slide markup
     slide = this.getSlideMarkup(i);

     $slide = this.conf.jq(slide, this.conf.jqc);
     if ($slide.attr('data-page') == this.conf.currentPage) {
       this.preLoad(i);
     }
     else {
       $slide.hide();
     }

     //append our slide to the DOM, hidden or not.
     $slide.appendTo(this.conf.slider);
   }
   this.conf.jq('

').appendTo(this.conf.slider);
   this.conf.jq('

').appendTo(window.document);
 }

 /**
  * Preload image somewhere on the DOM, primarily so that we will have
  * dimension information on the image we're dealing with, before we show it.
  *
  * @TODO: this is probably hacky and sloppy. What to do, what to do??
  */
 Slides.prototype.preLoad = function (index) {
   var $img = this.conf.jq(this.getImgTag(index, 'medium'));
   this.pre[index] = $img.get(0);
 }

 /**
  * Build the correct markup for a slide at index.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getSlideMarkup = function (index, preload) {
   var slide = '';

   //keep track of the page we're rendering slides for
   var page = Math.floor(index / this.conf.pageSize);
   if (index % this.conf.pageSize) {
     ++page;
   }

   //build our markup
   slide += '<' + this.conf.slideTag;
   slide += ' class="' + this.conf.slideClass + '"';
   slide += ' data-page="' + page + '"';
   slide += ' data-slide="' + index + '"';
   slide += ' title="' + this.conf.images[index].name + '"';
   slide += '>';
   if (page == this.conf.currentPage) {
     slide += this.getImgTag(index);
   }
   slide += '</' + this.conf.slideTag + '>'

   return slide;
 }

 /**
  * Build the correct markup for a slide at index.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getImgTag = function (index, version) {
   var slide = '';
   version = (typeof(version) == 'undefined')? 'thumb' : version;

   slide += ''

   return slide;
 }

 /**
  * Initialize the viewer and create scroll locks.
  */
 Slides.prototype.createViewer = function(index) {
   this.current = index;

   this.getModalLock();
   var $viewer = this.conf.jq(this.getViewerMarkup(index));
   $viewer.appendTo(this.conf.jq('body'));

   var S = this;
   //allow user to click their way out of viewer
   $viewer.click(function (e) {
     e.preventDefault();
     S.destroyViewer();
   });
   //user clicks on image, that doesn't count.
   var $img = this.conf.jq('#' + this.conf.viewerID + ' .viewing img');
   $img.click(function (e) {
     return false;
   });
 }

 /**
  * Destroy the slider viewer we've used to take over the DOM.
  */
 Slides.prototype.destroyViewer = function () {
   this.current = null;

   this.breakModalLock();
   this.conf.jq('#' + this.conf.viewerID + '', this.conf.jqc).remove();
 }

 /**
  * Retain and lock scroll settings for later.
  *
  * @note: this is only necessary if this.createViewer is using a modal window
  * that floats in the DOM, as opposed to inline elements on the page.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getModalLock = function () {
    var scrollPos = [
      self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
    ];
    var $html = this.conf.jq('html');

    //retain current settings
    this.modal = this.modal || {
      lock: {
        scrollPosition: scrollPos,
        overflow: $html.css('overflow'),
      }
    };

    //lock our scroll
    window.scrollTo(scrollPos[0], scrollPos[1]);
    $html.css('overflow', 'hidden');
 }

 /**
  * Un-lock scroll position.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.breakModalLock = function () {
   if (!('modal' in this)) {
     return false;
   }

   var scrollPos = this.modal.lock.scrollPosition;

   //restore previous settings
   this.conf.jq('html').css('overflow', this.modal.lock.overFlow);
   window.scrollTo(scrollPos[0], scrollPos[1])

   //delete previous settings
   delete this.modal;
 }

 /**
  * Build the correct markup for modal window.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getViewerMarkup = function (index) {
   var modal = '', style, left = 0, top = 0;

   //
   //viewer container
   //
   style = [];
   style.push('position: absolute');
   style.push('background-color: rgba(0, 0, 0, 0.05)');
   style.push('width: 100%');
   style.push('height: 100%');
   style.push('z-index: 999');
   style.push('top: 0');
   style.push('left: 0');
   viewerStyle = this.conf.jq.trim(style.join('; '));

   //
   //image container
   //
   style = [];
   //determine useful positoins/sizes based on the given image
   top = (this.conf.jq(window).height() - this.pre[index].naturalHeight) / 2;
   left = this.pre[index].naturalWidth / 2;
   style.push('position: relative');
   style.push('top: ' + top + 'px');
   style.push('left: 50%');
   style.push('margin-left: -' + left + 'px');
   viewingStyle = this.conf.jq.trim(style.join('; '));

   //build the actual markup
   modal += '
';
   modal += '
';
   modal += this.getImgTag(index, 'medium');
   modal += '
';
   modal += '
';

   return modal;
 }

 /**
  *
  */
 Slides.prototype.view = function (index) {
   this.current = index;

   var $viewing = this.conf.jq('#' + this.conf.viewerID + ' .viewing', this.conf.jqc);
   var requested = this.getImgTag(index, 'medium');
   var $current = this.conf.jq('img', $viewing);

   var S = this;
   $current.fadeOut(function () {
     $current.remove();
     S.conf.jq(requested).hide().appendTo($viewing);
     S.conf.jq('img', $viewing).fadeIn();
   });
   return this;
 }

 /**
  * Load the requested page of slides within the grid.
  */
 Slides.prototype.loadPage = function (page) {
   //@TODO: code this

   return this;
 }

 /**
  *
  */
 Slides.prototype.next = function () {
   this.view((this.current * 1) + 1);
   return this;
 }

 /**
  *
  */
 Slides.prototype.previous = function () {
   this.view((this.current * 1) - 1);
   return this;
 }

 /**
  *
  */
 Slides.prototype.pausePlay = function () {
   //@TODO: code this

   return this;
 }
})()

<commit_msg>no real change: cleaned things up.<commit_after>/**
 * @file: slide-show and grid-display manager.
 */

(function() {
 /***
  *
  */
 Slides = function (config) {
   this.pre = [];

   //intialize config
   this.initConfig(config);

   //sanity check
   if (this.conf.images == null || this.conf.slider == null) {
     console.error('Must pass "slider" and "images" properties to configuration oject of Slides().');
     return false;
   }

   //initialize each slide in our grid
   this.initGrid();

   //bind to events in DOM for our features
   this.initBindings();

   return this;
 }

 /**
  * Initialize configuration with our own defaults.
  */
 Slides.prototype.initConfig = function (config) {
   var S = this;

   //default configuration
   this.conf = config || {};
   this.conf = {
     slider: S.conf.slider || null,
     images: S.conf.images || null,
     current: S.conf.current || 0,
     currentPage: S.conf.currentPage || 1, //1-based index
     pageSize: S.conf.pageSize || 3,
     slideTag: S.conf.slideTag || 'li',
     viewerID: S.conf.viewerID || 'viewer',
     filmStrip: S.conf.filmStrip || false,
     slideClass: S.conf.slideClass || 'slide',
     jqc: S.conf.context || window.document,
     jq: S.conf.jq || (function () {
       if ('jquery' in S.conf.slider) {
         return S.conf.slider.constructor
       }
       else {
         return null;
       }
     })(),
   }
 }

 /**
  * Create necessary grid, DOM elements, and initialize event bindings as
  * necessary.
  */
 Slides.prototype.initGrid = function () {
   var slide, S = this;

   //sanity check
   var $slides = this.conf.jq('.slide', this.conf.slider);
   if ('length' in $slides && $slides.length > 0) {
     return this;
   }

   for (var i in this.conf.images) {
     //get our slide markup
     slide = this.getSlideMarkup(i);

     $slide = this.conf.jq(slide, this.conf.jqc);
     if ($slide.attr('data-page') == this.conf.currentPage) {
       this.preLoad(i);
     }
     else {
       $slide.hide();
     }

     //append our slide to the DOM, hidden or not.
     $slide.appendTo(this.conf.slider);
   }
   this.conf.jq('

').appendTo(this.conf.slider);
   this.conf.jq('

').appendTo(window.document);

   return this;
 }

 /**
  * Bind to various events to make our show work.
  */
 Slides.prototype.initBindings = function () {
   var S = this;

   this.conf.jq('.' + this.conf.slideClass, this.conf.slider)
     .click(function (event) {
       S.createViewer(S.conf.jq(this).attr('data-slide'));
     });
   window.onkeyup = function (e) {
     switch (e.keyCode) {
       //close modal window
       case 27: // ESC
         S.destroyViewer();
         break;

       //previous slide
       case 37: // left-arrow
       case 75: // k
       case 80: // p
         S.previous();
         break;

       //next slide
       case 39: // right-arrow
       case 74: // j
       case 78: // n
         S.next();
         break;
     }
   };
 }

 /**
  * Preload image somewhere on the DOM, primarily so that we will have
  * dimension information on the image we're dealing with, before we show it.
  *
  * @TODO: this is probably hacky and sloppy. What to do, what to do??
  */
 Slides.prototype.preLoad = function (index) {
   var $img = this.conf.jq(this.getImgTag(index, 'medium'));
   this.pre[index] = $img.get(0);
 }

 /**
  * Build the correct markup for a slide at index.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getSlideMarkup = function (index, preload) {
   var slide = '';

   //keep track of the page we're rendering slides for
   var page = Math.floor(index / this.conf.pageSize);
   if (index % this.conf.pageSize) {
     ++page;
   }

   //build our markup
   slide += '<' + this.conf.slideTag;
   slide += ' class="' + this.conf.slideClass + '"';
   slide += ' data-page="' + page + '"';
   slide += ' data-slide="' + index + '"';
   slide += ' title="' + this.conf.images[index].name + '"';
   slide += '>';
   if (page == this.conf.currentPage) {
     slide += this.getImgTag(index);
   }
   slide += '</' + this.conf.slideTag + '>'

   return slide;
 }

 /**
  * Build the correct markup for a slide at index.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getImgTag = function (index, version) {
   var slide = '';
   version = (typeof(version) == 'undefined')? 'thumb' : version;

   slide += ''

   return slide;
 }

 /**
  * Initialize the viewer and create scroll locks.
  */
 Slides.prototype.createViewer = function(index) {
   this.current = index;

   this.getModalLock();
   var $viewer = this.conf.jq(this.getViewerMarkup(index));
   $viewer.appendTo(this.conf.jq('body'));

   var S = this;
   //allow user to click their way out of viewer
   $viewer.click(function (e) {
     e.preventDefault();
     S.destroyViewer();
   });
   //user clicks on image, that doesn't count.
   var $img = this.conf.jq('#' + this.conf.viewerID + ' .viewing img');
   $img.click(function (e) {
     return false;
   });
 }

 /**
  * Destroy the slider viewer we've used to take over the DOM.
  */
 Slides.prototype.destroyViewer = function () {
   this.current = null;

   this.breakModalLock();
   this.conf.jq('#' + this.conf.viewerID + '', this.conf.jqc).remove();
 }

 /**
  * Retain and lock scroll settings for later.
  *
  * @note: this is only necessary if this.createViewer is using a modal window
  * that floats in the DOM, as opposed to inline elements on the page.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getModalLock = function () {
    var scrollPos = [
      self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
    ];
    var $html = this.conf.jq('html');

    //retain current settings
    this.modal = this.modal || {
      lock: {
        scrollPosition: scrollPos,
        overflow: $html.css('overflow'),
      }
    };

    //lock our scroll
    window.scrollTo(scrollPos[0], scrollPos[1]);
    $html.css('overflow', 'hidden');
 }

 /**
  * Un-lock scroll position.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.breakModalLock = function () {
   if (!('modal' in this)) {
     return false;
   }

   var scrollPos = this.modal.lock.scrollPosition;

   //restore previous settings
   this.conf.jq('html').css('overflow', this.modal.lock.overFlow);
   window.scrollTo(scrollPos[0], scrollPos[1])

   //delete previous settings
   delete this.modal;
 }

 /**
  * Build the correct markup for modal window.
  *
  * @TODO: this should be "protected/private".
  */
 Slides.prototype.getViewerMarkup = function (index) {
   var modal = '', style, left = 0, top = 0;

   //
   //viewer container
   //
   style = [];
   style.push('position: absolute');
   style.push('background-color: rgba(0, 0, 0, 0.05)');
   style.push('width: 100%');
   style.push('height: 100%');
   style.push('z-index: 999');
   style.push('top: 0');
   style.push('left: 0');
   viewerStyle = this.conf.jq.trim(style.join('; '));

   //
   //image container
   //
   style = [];
   //determine useful positoins/sizes based on the given image
   top = (this.conf.jq(window).height() - this.pre[index].naturalHeight) / 2;
   left = this.pre[index].naturalWidth / 2;
   style.push('position: relative');
   style.push('top: ' + top + 'px');
   style.push('left: 50%');
   style.push('margin-left: -' + left + 'px');
   viewingStyle = this.conf.jq.trim(style.join('; '));

   //build the actual markup
   modal += '
';
   modal += '
';
   modal += this.getImgTag(index, 'medium');
   modal += '
';
   modal += '
';

   return modal;
 }

 /**
  *
  */
 Slides.prototype.view = function (index) {
   this.current = index;

   var $viewing = this.conf.jq('#' + this.conf.viewerID + ' .viewing', this.conf.jqc);
   var requested = this.getImgTag(index, 'medium');
   var $current = this.conf.jq('img', $viewing);

   var S = this;
   $current.fadeOut(function () {
     $current.remove();
     S.conf.jq(requested).hide().appendTo($viewing);
     S.conf.jq('img', $viewing).fadeIn();
   });
   return this;
 }

 /**
  * Load the requested page of slides within the grid.
  */
 Slides.prototype.loadPage = function (page) {
   //@TODO: code this

   return this;
 }

 /**
  *
  */
 Slides.prototype.next = function () {
   this.view((this.current * 1) + 1);
   return this;
 }

 /**
  *
  */
 Slides.prototype.previous = function () {
   this.view((this.current * 1) - 1);
   return this;
 }

 /**
  *
  */
 Slides.prototype.pausePlay = function () {
   //@TODO: code this

   return this;
 }
})()

<|endoftext|>


<commit_before>(function() {
  var maps = {};

  var cartogramBinding = new Shiny.OutputBinding();
  $.extend(cartogramBinding, {
    find: function(scope) {
      return $(scope).find(".cartogram-map-output");
    },
    renderValue: function(el, data) {
      var $el = $(el);

      var map = $el.data('cartogram-map');
      if (!map) {
        // A new map was detected. Create it, initialize supporting data
        // structures, and hook up event handlers.
        var id = this.getId(el);
        maps[id] = $el;

        var svg = d3.select('#' + id + ' svg'),
          zoom = d3.behavior.zoom()
          .translate([-38, 32])
          .scale(0.94)
          .scaleExtent([0.5, 10.0])
          .on("zoom", updateZoom),
          layer = svg.append("g"),
          states = layer.append("g")
          .selectAll("path");

        updateZoom();

        function updateZoom() {
          var scale = zoom.scale();
          layer.attr("transform",
            "translate(" + zoom.translate() + ") " +
            "scale(" + [scale, scale] + ")");
        }

        var proj = d3.geo.albersUsa(),
          topology,
          geometries,
          carto = d3.cartogram()
          .projection(proj)
          .properties(function(d) {
            var data = $el.data('cartogram-data') || {};
            return data[d.id];
          })
          .value(function(d) {
            return +d.properties.scale;
          });

        $el.data('cartogram-map', {});

        d3.json($el.data('topojson'), function(topo) {
          topology = topo;
          geometries = topology.objects.states.geometries;

          var features = carto.features(topology, geometries),
            path = d3.geo.path()
            .projection(proj);

          states = states.data(features)
            .enter()
            .append("path")
            .attr("class", "state")
            .attr("id", function(d) {
              //return d.properties.NAME;
              return d.id;
            })
            .attr("fill", "#fafafa")
            .attr("d", path);

          states.append("title");

          $el.data('cartogram-map', {
            topology: topology,
            geometries: geometries,
            states: states,
            zoom: zoom,
            carto: carto,
            proj: proj
          });
        });
      }
    },
    reset: function($el) {
      var map = $el.data('cartogram-map');
      var carto = map.carto,
        topology = map.topology,
        geometries = map.geometries,
        proj = map.proj,
        states = map.states;

      if(topology === undefined || geometries === undefined) return;

      var features = carto.features(topology, geometries),
        path = d3.geo.path()
        .projection(proj);

      states.data(features)
        .transition()
        .duration(750)
        .ease("linear")
        .attr("fill", "#fafafa")
        .attr("d", path);

      states.select("title")
        .text(function(d) {
          return d.properties.name;
        });
    },
    getFormat: function(format) {
      if (typeof format === 'function') {
        return format;
      }

      var f = eval('f = ' + format);
      if (typeof f !== 'function') {
        return d3.format(f || ",");
      } else {
        return f;
      }
    },
    getColumn: function($el, key) {
      if (!key || typeof key !== 'string') return;

      var columns = $el.data('cartogram-columns');
      var column = {
        name: key
      };
      if (columns.hasOwnProperty(key)) {
        column = columns[key];
      }
      column.format = this.getFormat(column.format);
      return column;
    },
    getValue: function(column) {
      return function(d) {
        return +d.properties[column.name];
      };
    },
    update: function($el) {
      var map = $el.data('cartogram-map');
      var carto = map.carto,
        topology = map.topology,
        geometries = map.geometries,
        proj = map.proj,
        states = map.states;

      if(states === undefined) return;

      var scaleColumn = this.getColumn($el, $el.data('cartogram-scaleBy')),
        scaleValue = this.getValue(scaleColumn),
        colorColumn = this.getColumn($el, $el.data('cartogram-colorBy')) || scaleColumn,
        colorValue = this.getValue(colorColumn);

      if(scaleColumn === undefined) this.reset($el);

      var scaleValues = states.data()
        .map(scaleValue)
        .filter(function(n) {
          return isFinite(n);
        })
        .sort(d3.ascending),
        colorValues = states.data()
        .map(colorValue)
        .filter(function(n) {
          return isFinite(n);
        })
        .sort(d3.ascending),
        lo = colorValues[0],
        hi = colorValues[colorValues.length - 1];

      if(scaleValues.length * colorValues.length === 0) {
        return this.reset($el);
      }

      var color = d3.scale.linear()
        .range($el.data('colors'))
        .domain(lo < 0 ? [lo, 0, hi] : [lo, d3.mean(colorValues), hi]);

      // normalize the scale to positive numbers
      var scale = d3.scale.linear()
        .domain([scaleValues[0], scaleValues[scaleValues.length - 1]])
        .range([1, 1000]);

      // tell the cartogram to use the scaled values
      carto.value(function(d) {
        return scale(scaleValue(d));
      });

      // generate the new features, pre-projected
      var features = carto(topology, geometries).features;

      // update the data
      states.data(features)
        .select("title")
        .text(function(d) {
           var arr = [
              d.properties.name,
              scaleColumn.title + ": " + scaleColumn.format(scaleValue(d))
            ];
          if(scaleColumn !== colorColumn) {
            arr.push(colorColumn.title + ": " + scaleColumn.format(colorValue(d)));
          }
          return arr.join("\n");
        });

      states.transition()
        .duration(750)
        .ease("linear")
        .attr("fill", function(d) {
          return color(colorValue(d));
        })
        .attr("d", carto.path);
    }
  });
  Shiny.outputBindings.register(cartogramBinding, "cartogram-output-binding");

  Shiny.addCustomMessageHandler('cartogram', function(data) {
    var mapId = data.mapId;
    var map = maps[mapId];
    if (!map)
      return;

    if (methods[data.method]) {
      methods[data.method].apply(map, data.args);
    } else {
      throw new Error('Unknown method ' + data.method);
    }
  });

  var methods = {};

  methods.setView = function(lat, lng, zoom, forceReset) {
    this.data('cartogram-map').zoom.translate([lat, lng]).scale(zoom);
    if (forceRest) {
      cartogramBinding.reset(this);
    }
  };

  var nestRollup = function(data) {
      var result = {};

      data.name.forEach(function(name, i) {
        var elem = {};
        Object.keys(data).forEach(function(key) {
          elem[key] = data[key][i];
        });
        result[name] = elem;
      });

      return result;
    },
    updateAttribute = function($el, data, attribute) {
      $el.data('cartogram-' + attribute, data);
      if(attribute === 'data') cartogramBinding.reset($el);
      cartogramBinding.update($el);
    };

  methods.setData = function(data) {
    updateAttribute(this, nestRollup(data), 'data');
  };

  methods.defineColumns = function(data) {
    updateAttribute(this, nestRollup(data), 'columns');
  };

  methods.scaleBy = function(column) {
    updateAttribute(this, column, 'scaleBy');
  };

  methods.colorBy = function(column) {
    updateAttribute(this, column, 'colorBy');
  };
})();
<commit_msg>use default area as default scale<commit_after>(function() {
  var maps = {};

  var cartogramBinding = new Shiny.OutputBinding();
  $.extend(cartogramBinding, {
    find: function(scope) {
      return $(scope).find(".cartogram-map-output");
    },
    renderValue: function(el, data) {
      var $el = $(el);

      var map = $el.data('cartogram-map');
      if (!map) {
        // A new map was detected. Create it, initialize supporting data
        // structures, and hook up event handlers.
        var id = this.getId(el);
        maps[id] = $el;

        var svg = d3.select('#' + id + ' svg'),
          zoom = d3.behavior.zoom()
          .translate([-38, 32])
          .scale(0.94)
          .scaleExtent([0.5, 10.0])
          .on("zoom", updateZoom),
          layer = svg.append("g"),
          states = layer.append("g")
          .selectAll("path");

        updateZoom();

        function updateZoom() {
          var scale = zoom.scale();
          layer.attr("transform",
            "translate(" + zoom.translate() + ") " +
            "scale(" + [scale, scale] + ")");
        }

        var proj = d3.geo.albersUsa(),
          topology,
          geometries,
          carto = d3.cartogram()
          .projection(proj)
          .properties(function(d) {
            var data = $el.data('cartogram-data') || {};
            return data[d.id];
          })
          .value(function(d) {
            return +d.properties.scale;
          });

        $el.data('cartogram-map', {});

        d3.json($el.data('topojson'), function(topo) {
          topology = topo;
          geometries = topology.objects.states.geometries;

          var features = carto.features(topology, geometries),
            path = d3.geo.path()
            .projection(proj);

          states = states.data(features)
            .enter()
            .append("path")
            .attr("class", "state")
            .attr("id", function(d) {
              return d.id;
            })
            .attr("fill", "#fafafa")
            .attr("d", path);

          states.append("title");

          $el.data('cartogram-map', {
            topology: topology,
            geometries: geometries,
            states: states,
            zoom: zoom,
            carto: carto,
            proj: proj
          });
        });
      }
    },
    reset: function($el) {
      var map = $el.data('cartogram-map');
      var carto = map.carto,
        topology = map.topology,
        geometries = map.geometries,
        proj = map.proj,
        states = map.states;

      if(topology === undefined || geometries === undefined) return;

      var features = carto.features(topology, geometries),
        path = d3.geo.path()
        .projection(proj);

      states.data(features)
        .transition()
        .duration(750)
        .ease("linear")
        .attr("fill", "#fafafa")
        .attr("d", path);

      states.select("title")
        .text(function(d) {
          return d.properties.name;
        });
    },
    getFormat: function(format) {
      if (typeof format === 'function') {
        return format;
      }

      var f = eval('f = ' + format);
      if (typeof f !== 'function') {
        return d3.format(f || ",");
      } else {
        return f;
      }
    },
    getColumn: function($el, key) {
      if (!key || typeof key !== 'string') return;

      var columns = $el.data('cartogram-columns');
      var column = {
        name: key
      };
      if (columns.hasOwnProperty(key)) {
        column = columns[key];
      }
      column.format = this.getFormat(column.format);
      return column;
    },
    getValue: function(column) {
      return function(d) {
        return +d.properties[column.name];
      };
    },
    update: function($el) {
      var map = $el.data('cartogram-map');
      var carto = map.carto,
        topology = map.topology,
        geometries = map.geometries,
        proj = map.proj,
        states = map.states;

      if(states === undefined) return;

      var scaleColumn = this.getColumn($el, $el.data('cartogram-scaleBy')),
        scaleValue = this.getValue(scaleColumn),
        colorColumn = this.getColumn($el, $el.data('cartogram-colorBy')) || scaleColumn,
        colorValue = this.getValue(colorColumn);

      if(scaleColumn === undefined) {
        if(colorColumn === undefined) {
          return this.reset($el);
        }

        scaleValue = function(d) { return 1; };
        carto.iterations(0);
      } else {
        carto.iterations(8);
      }

      var scaleValues = states.data()
        .map(scaleValue)
        .filter(function(n) {
          return isFinite(n);
        }),
        colorValues = states.data()
        .map(colorValue)
        .filter(function(n) {
          return isFinite(n);
        })
        .sort(d3.ascending),
        lo = colorValues[0],
        hi = colorValues[colorValues.length - 1];

      if(scaleValues.length * colorValues.length === 0) {
        return this.reset($el);
      }

      var color = d3.scale.linear()
        .range($el.data('colors'))
        .domain(lo < 0 ? [lo, 0, hi] : [lo, d3.mean(colorValues), hi]);

      // normalize the scale to positive numbers
      var scale = d3.scale.linear()
        .domain([d3.min(scaleValues), d3.max(scaleValues)])
        .range([1, 1000]);

      // tell the cartogram to use the scaled values
      carto.value(function(d) {
        return scale(scaleValue(d));
      });

      // generate the new features, pre-projected
      var features = carto(topology, geometries).features;

      // update the data
      states.data(features)
        .select("title")
        .text(function(d) {
           var arr = [ d.properties.name ];
           if(scaleColumn !== undefined) {
             arr.push(scaleColumn.title + ": " + scaleColumn.format(scaleValue(d)));
           }
           if(scaleColumn !== colorColumn) {
             arr.push(colorColumn.title + ": " + colorColumn.format(colorValue(d)));
           }
           return arr.join("\n");
        });

      states.transition()
        .duration(750)
        .ease("linear")
        .attr("fill", function(d) {
          return color(colorValue(d));
        })
        .attr("d", carto.path);
    }
  });
  Shiny.outputBindings.register(cartogramBinding, "cartogram-output-binding");

  Shiny.addCustomMessageHandler('cartogram', function(data) {
    var mapId = data.mapId;
    var map = maps[mapId];
    if (!map)
      return;

    if (methods[data.method]) {
      methods[data.method].apply(map, data.args);
    } else {
      throw new Error('Unknown method ' + data.method);
    }
  });

  var methods = {};

  methods.setView = function(lat, lng, zoom, forceReset) {
    this.data('cartogram-map').zoom.translate([lat, lng]).scale(zoom);
    if (forceRest) {
      cartogramBinding.reset(this);
    }
  };

  var nestRollup = function(data) {
      var result = {};

      data.name.forEach(function(name, i) {
        var elem = {};
        Object.keys(data).forEach(function(key) {
          elem[key] = data[key][i];
        });
        result[name] = elem;
      });

      return result;
    },
    updateAttribute = function($el, data, attribute) {
      $el.data('cartogram-' + attribute, data);
      if(attribute === 'data') cartogramBinding.reset($el);
      cartogramBinding.update($el);
    };

  methods.setData = function(data) {
    updateAttribute(this, nestRollup(data), 'data');
  };

  methods.defineColumns = function(data) {
    updateAttribute(this, nestRollup(data), 'columns');
  };

  methods.scaleBy = function(column) {
    updateAttribute(this, column, 'scaleBy');
  };

  methods.colorBy = function(column) {
    updateAttribute(this, column, 'colorBy');
  };
})();
<|endoftext|>


<commit_before>import React, { Component, PropTypes  } from 'react';
import _ from 'lodash';
import { Grid, Form, Row, Col, FormControl, FormGroup, ControlLabel, Button, HelpBlock } from 'react-bootstrap';
import Validator from '../../validator/validatorBasicInfo';
import ProvideActions from '../actions/ProvideActions';

class Calculator extends Component {
  constructor (props){
    super(props);
  }

  render () {
      if (_.isNil(this.props.simulatorInfo)) {
          return null;
      }
      const validateNameBool = Validator.validateName(this.props.simulatorInfo.name) ;
      const validateCarValueBool = Validator.validateCarValue(this.props.simulatorInfo.carValue);
      const validateName = validateNameBool ? "success" : "error";
      const validateCarValue = validateCarValueBool ? "success" : "error";

      const carOption = _.map(this.props.carOptionList, carName => {
          return (
              <option key={carName.value} value={carName.value}>carName.label</option>
          )
      });

      // Button disable
    const disableCalculate = validateNameBool && validateCarValueBool ? false : true;
    const buttonCalculate = (
        <Button
            bsStyle='primary'
            disabled={ disableCalculate }
            onClick={!disableCalculate ? this.props.handleSimulateClick : null}>
            GET PRICE
        </Button>
    );

    return (
        <Form horizontal>
            <Grid>
                <Row>
                   <Col lg={12}>
                       <FormGroup controlId="formHorizontalName" validationState={validateName}>
                         <Col componentClass={ControlLabel} md={2} smHidden xsHidden>
                           Name
                         </Col>
                         <Col sm={6} md={4}>
                           <FormControl type="text" placeholder="Family name"
                               onChange={e => ProvideActions.updateSimulator({name: e.target.value})}  value={this.props.simulatorInfo.name}/>
                               <HelpBlock>Name must higher than 5 characters</HelpBlock>
                         </Col>
                       </FormGroup>
                       <FormGroup controlId="formControlsCarSelect">
                           <Col componentClass={ControlLabel} md={2} smHidden xsHidden>
                             Select a car
                           </Col>
                           <Col sm={6} md={4}>
                              <FormControl componentClass="select"
                              onChange={e => ProvideActions.updateSimulator({carName: e.target.value})}
                              placeholder="Select a car" value={this.props.simulatorInfo.carName}>
                                { carOption }
                              </FormControl>
                          </Col>
                        </FormGroup>
                        <FormGroup controlId="formHorizontalCarValue" validationState={validateCarValue}>
                          <Col componentClass={ControlLabel} md={2} smHidden xsHidden>
                            Car Value
                          </Col>
                          <Col sm={6} md={4}>
                            <FormControl type="number" placeholder="Car value"
                                onChange={e => ProvideActions.updateSimulator({carValue: e.target.value})}  value={this.props.simulatorInfo.carValue}/>
                          </Col>
                        </FormGroup>
                   </Col>
                   <Col md={2}>
                   { buttonCalculate }
                   </Col>
                </Row>
            </Grid>
        </Form>
    );
  }
}

Calculator.propTypes = {
  handleSimulateClick: PropTypes.func.isRequired,
};

export default Calculator;
<commit_msg>Update Calculator.js<commit_after>import React, { Component, PropTypes  } from 'react';
import _ from 'lodash';
import { Grid, Form, Row, Col, FormControl, FormGroup, ControlLabel, Button, HelpBlock } from 'react-bootstrap';
import Validator from '../../validator/validatorBasicInfo';
import ProvideActions from '../actions/ProvideActions';

class Calculator extends Component {
  constructor (props){
    super(props);
  }

  render () {
      if (_.isNil(this.props.simulatorInfo)) {
          return null;
      }
      const validateNameBool = Validator.validateName(this.props.simulatorInfo.name) ;
      const validateCarValueBool = Validator.validateCarValue(this.props.simulatorInfo.carValue);
      const validateName = validateNameBool ? "success" : "error";
      const validateCarValue = validateCarValueBool ? "success" : "error";

      const carOption = _.map(this.props.carOptionList, carName => {
          return (
              <option key={carName.value} value={carName.value}>carName.label</option>
          )
      });

      // Button disable
    const disableCalculate = validateNameBool && validateCarValueBool ? false : true;
    const buttonCalculate = (
        <Button
            bsStyle='primary'
            disabled={ disableCalculate }
            onClick={!disableCalculate ? this.props.handleSimulateClick : null}>
            GET PRICE
        </Button>
    );

    return (
        <Form horizontal>
            <Grid>
                <Row>
                   <Col lg={12}>
                       <FormGroup controlId="formHorizontalName" validationState={validateName}>
                         <Col componentClass={ControlLabel} md={2} smHidden xsHidden>
                           Name
                         </Col>
                         <Col sm={6} md={4}>
                           <FormControl type="text" placeholder="Family name"
                               onChange={e => ProvideActions.updateSimulator({name: e.target.value})}  value={this.props.simulatorInfo.name}/>
                               <HelpBlock>Name must at least 5 characters</HelpBlock>
                         </Col>
                       </FormGroup>
                       <FormGroup controlId="formControlsCarSelect">
                           <Col componentClass={ControlLabel} md={2} smHidden xsHidden>
                             Select a car
                           </Col>
                           <Col sm={6} md={4}>
                              <FormControl componentClass="select"
                              onChange={e => ProvideActions.updateSimulator({carName: e.target.value})}
                              placeholder="Select a car" value={this.props.simulatorInfo.carName}>
                                { carOption }
                              </FormControl>
                          </Col>
                        </FormGroup>
                        <FormGroup controlId="formHorizontalCarValue" validationState={validateCarValue}>
                          <Col componentClass={ControlLabel} md={2} smHidden xsHidden>
                            Car Value
                          </Col>
                          <Col sm={6} md={4}>
                            <FormControl type="number" placeholder="Car value"
                                onChange={e => ProvideActions.updateSimulator({carValue: e.target.value})}  value={this.props.simulatorInfo.carValue}/>
                          </Col>
                        </FormGroup>
                   </Col>
                   <Col md={2}>
                   { buttonCalculate }
                   </Col>
                </Row>
            </Grid>
        </Form>
    );
  }
}

Calculator.propTypes = {
  handleSimulateClick: PropTypes.func.isRequired,
};

export default Calculator;
<|endoftext|>


<commit_before>'use strict';

// TechEvents controller
angular.module('techEvents').controller('TechEventsController', ['$scope', '$stateParams', '$location', 'Authentication', 'TechEvents',
  function ($scope, $stateParams, $location, Authentication, TechEvents) {
    $scope.authentication = Authentication;
    /* config object */
    $scope.uiConfig = {
      calendar:{
        height: 450,
        editable: true,
        header:{
          left: 'month basicWeek basicDay agendaWeek agendaDay',
          center: 'title',
          right: 'today prev,next'
        },
        dayClick: $scope.alertEventOnClick,
        eventDrop: $scope.alertOnDrop,
        eventResize: $scope.alertOnResize
      }
    };
    // get the list of event to display in the interface
    $scope.eventSources = [];

    // Create new TechEvent
    $scope.create = function (isValid) {
      $scope.error = null;

      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'techEventForm');

        return false;
      }

      // Create new TechEvent object
      var techEvent = new TechEvents({
        title: this.title,
        content: this.content
      });

      // Redirect after save
      techEvent.$save(function (response) {
        $location.path('techEvents/' + response._id);

        // Clear form fields
        $scope.title = '';
        $scope.content = '';
      }, function (errorResponse) {
        $scope.error = errorResponse.data.message;
      });
    };

    // Remove existing TechEvent
    $scope.remove = function (techEvent) {
      if (techEvent) {
        techEvent.$remove();

        for (var i in $scope.techEvents) {
          if ($scope.techEvents[i] === techEvent) {
            $scope.techEvents.splice(i, 1);
          }
        }
      } else {
        $scope.techEvent.$remove(function () {
          $location.path('techEvents');
        });
      }
    };

    // Update existing TechEvent
    $scope.update = function (isValid) {
      $scope.error = null;

      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'techEventForm');

        return false;
      }

      var techEvent = $scope.techEvent;

      techEvent.$update(function () {
        $location.path('techEvents/' + techEvent._id);
      }, function (errorResponse) {
        $scope.error = errorResponse.data.message;
      });
    };

    // Find a list of TechEvents
    $scope.find = function () {
      $scope.techEvents = TechEvents.query();
    };

    // Find existing TechEvent
    $scope.findOne = function () {
      $scope.techEvent = TechEvents.get({
        techEventId: $stateParams.techEventId
      });
    };
  }
]);
<commit_msg>add function to calendar<commit_after>'use strict';

// TechEvents controller
angular.module('techEvents').controller('TechEventsController', ['$scope', '$stateParams', '$location', 'Authentication', 'TechEvents',
  function ($scope, $stateParams, $location, Authentication, TechEvents) {
    $scope.authentication = Authentication;

// CALENDAR EXAMPLE
    var date = new Date();
    var d = date.getDate();
    var m = date.getMonth();
    var y = date.getFullYear();

    $scope.changeTo = 'Hungarian';
    /* event source that pulls from google.com
    $scope.eventSource = {
            url: "http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic",
            className: 'gcal-event',           // an option!
            currentTimezone: 'America/Chicago' // an option!
    };*/
    /* event source that contains custom events on the scope */
    $scope.events = [
      { title: 'All Day Event',start: new Date(y, m, 1) },
      { title: 'Long Event',start: new Date(y, m, d - 5),end: new Date(y, m, d - 2) },
      { id: 999,title: 'Repeating Event',start: new Date(y, m, d - 3, 16, 0),allDay: false },
      { id: 999,title: 'Repeating Event',start: new Date(y, m, d + 4, 16, 0),allDay: false },
      { title: 'Birthday Party',start: new Date(y, m, d + 1, 19, 0),end: new Date(y, m, d + 1, 22, 30),allDay: false },
      { title: 'Click for Google',start: new Date(y, m, 28),end: new Date(y, m, 29),url: 'http://google.com/' }
    ];
    /* event source that calls a function on every view switch */
    $scope.eventsF = function (start, end, timezone, callback) {
      var s = new Date(start).getTime() / 1000;
      var e = new Date(end).getTime() / 1000;
      var m = new Date(start).getMonth();
      var events = [ { title: 'Feed Me ' + m,start: s + (50000),end: s + (100000),allDay: false, className: ['customFeed'] } ];
      callback(events);
    };

/*
    $scope.calEventsExt = {
       color: '#f00',
       textColor: 'yellow',
       events: [
          {type:'party',title: 'Lunch',start: new Date(y, m, d, 12, 0),end: new Date(y, m, d, 14, 0),allDay: false},
          {type:'party',title: 'Lunch 2',start: new Date(y, m, d, 12, 0),end: new Date(y, m, d, 14, 0),allDay: false},
          {type:'party',title: 'Click for Google',start: new Date(y, m, 28),end: new Date(y, m, 29),url: 'http://google.com/'}
        ]
    };
*/
    /* alert on eventClick */
    $scope.alertOnEventClick = function(date, jsEvent, view){
//      $scope.alertMessage = (date.title + ' was clicked ');
      alert(date.title + ' was clicked ');
    };
    /* alert on Drop
     $scope.alertOnDrop = function(event, delta, revertFunc, jsEvent, ui, view){
       $scope.alertMessage = ('Event Droped to make dayDelta ' + delta);
    };*/
    /* alert on Resize
    $scope.alertOnResize = function(event, delta, revertFunc, jsEvent, ui, view ){
       $scope.alertMessage = ('Event Resized to make dayDelta ' + delta);
    };*/
    /* add and removes an event source of choice
    $scope.addRemoveEventSource = function(sources,source) {
      var canAdd = 0;
      angular.forEach(sources,function(value, key){
        if(sources[key] === source){
          sources.splice(key,1);
          canAdd = 1;
        }
      });
      if(canAdd === 0){
        sources.push(source);
      }
    };*/
    /* add custom event
    $scope.addEvent = function() {
      $scope.events.push({
        title: 'Open Sesame',
        start: new Date(y, m, 28),
        end: new Date(y, m, 29),
        className: ['openSesame']
      });
    };*/
    /* remove event
    $scope.remove = function(index) {
      $scope.events.splice(index,1);
    };*/
    /* Change View
    $scope.changeView = function(view,calendar) {
      uiCalendarConfig.calendars[calendar].fullCalendar('changeView',view);
    };*/
    /* Change View
    $scope.renderCalender = function(calendar) {
      if(uiCalendarConfig.calendars[calendar]){
        uiCalendarConfig.calendars[calendar].fullCalendar('render');
      }
    };*/
     /* Render Tooltip
    $scope.eventRender = function( event, element, view ) {
        element.attr({'tooltip': event.title,
                     'tooltip-append-to-body': true});
        $compile(element)($scope);
    };*/
    /* config object */
    $scope.uiConfig = {
      calendar:{
        height: 450,
        editable: true,
        header:{
          left: 'month basicWeek basicDay agendaWeek agendaDay',
          center: 'title',
          right: 'today prev,next'
        },
        eventClick: $scope.alertOnEventClick,
        eventDrop: $scope.alertOnDrop,
        eventResize: $scope.alertOnResize,
        eventRender: $scope.eventRender
      }
    };

/*
    $scope.changeLang = function() {
      if($scope.changeTo === 'Hungarian'){
        $scope.uiConfig.calendar.dayNames = ["Vasárnap", "Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat"];
        $scope.uiConfig.calendar.dayNamesShort = ["Vas", "Hét", "Kedd", "Sze", "Csüt", "Pén", "Szo"];
        $scope.changeTo= 'English';
      } else {
        $scope.uiConfig.calendar.dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
        $scope.uiConfig.calendar.dayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
        $scope.changeTo = 'Hungarian';
      }
    };
*/
    /* event sources array*/
    $scope.eventSources = [$scope.events, /*$scope.eventSource,*/ $scope.eventsF];
//    $scope.eventSources2 = [$scope.calEventsExt, $scope.eventsF, $scope.events];

// END OF CALENDAR EXAMPLE

    // Create new TechEvent
    $scope.create = function (isValid) {
      $scope.error = null;

      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'techEventForm');

        return false;
      }

      // Create new TechEvent object
      var techEvent = new TechEvents({
        title: this.title,
        content: this.content
      });

      // Redirect after save
      techEvent.$save(function (response) {
        $location.path('techEvents/' + response._id);

        // Clear form fields
        $scope.title = '';
        $scope.content = '';
      }, function (errorResponse) {
        $scope.error = errorResponse.data.message;
      });
    };

    // Remove existing TechEvent
    $scope.remove = function (techEvent) {
      if (techEvent) {
        techEvent.$remove();

        for (var i in $scope.techEvents) {
          if ($scope.techEvents[i] === techEvent) {
            $scope.techEvents.splice(i, 1);
          }
        }
      } else {
        $scope.techEvent.$remove(function () {
          $location.path('techEvents');
        });
      }
    };

    // Update existing TechEvent
    $scope.update = function (isValid) {
      $scope.error = null;

      if (!isValid) {
        $scope.$broadcast('show-errors-check-validity', 'techEventForm');

        return false;
      }

      var techEvent = $scope.techEvent;

      techEvent.$update(function () {
        $location.path('techEvents/' + techEvent._id);
      }, function (errorResponse) {
        $scope.error = errorResponse.data.message;
      });
    };

    // Find a list of TechEvents
    $scope.find = function () {
      $scope.techEvents = TechEvents.query();
    };

    // Find existing TechEvent
    $scope.findOne = function () {
      $scope.techEvent = TechEvents.get({
        techEventId: $stateParams.techEventId
      });
    };
  }
]);
<|endoftext|>