<commit_before>import './Event.scss';

import React, { Component, PropTypes } from 'react';
import heart from '../../images/heart.svg';
import star from '../../images/star.svg';

export default class Event extends Component {
  render() {
    const {event} = this.props;
    const recommendedStyle = {
      backgroundColor: '#0F2043',
      color: '#FFF',
      padding: '10px',
      margin: '1px'
    }
    const likedStyle = {
      backgroundColor: '#EF3B46',
      color: '#FFF',
      padding: '10px',
      margin: '1px'
    }
    const style = event.tag === 'recommended' ? recommendedStyle : likedStyle;
    const icon = event.tag === 'recommended' ? star : heart;

    return

{event.name.toUpperCase()}

{event.time.start} - {event.time.end}

;
  }
}
<commit_msg>Style event blocks<commit_after>import './Event.scss';

import React, { Component, PropTypes } from 'react';
import heart from '../../images/heart.svg';
import star from '../../images/star.svg';

export default class Event extends Component {
  render() {
    const {event} = this.props;
    const recommendedStyle = {
      backgroundColor: '#0F2043',
      color: '#FFF',
      padding: '10px',
      margin: '1px'
    };
    const likedStyle = {
      backgroundColor: '#EF3B46',
      color: '#FFF',
      padding: '10px',
      margin: '1px'
    };
    const nullStyle = {
      backgroundColor: '#D9770B',
      color: '#F9A722',
      padding: '10px',
      margin: '1px'
    };
    let style;
    let icon;

    if (!event.tag) {
      style = nullStyle;
      icon = '';
    } else {
      style = event.tag === 'recommended' ? recommendedStyle : likedStyle;
      icon = event.tag === 'recommended' ? star : heart;
    }

    return

{event.name.toUpperCase()}

{event.time.start} - {event.time.end}

;
  }
}
<|endoftext|>


<commit_before>/*jshint esversion: 6*/
var OVERHEADS = ['tip', 'tax', 'fee'];

window.onload = init;

function init() {
  // check for URL query parameters
  if (window.location.search) {
    try {
      var input = this.parseQueryStringInput(window.location.search);
      this.split(input);
    } catch (err) {
      console.error(err);
    }
  }
}

/**
 * Parses a URL query string.
 *
 * Below is an example of input:
 * ?tax=0.30&fee=1.50&tip=15&Gus=5.00
 */
function parseQueryStringInput(input) {
  var map = {
    persons: {}
  };

  input = input.substring(1); // remove prefixing '?'
  var searchArray = input.split('&');
  for (var i = 0; i < searchArray.length; i++) {
    var pair = searchArray[i].split('=');
    if (OVERHEADS.indexOf(pair[0]) > -1) {
      map[pair[0]] = Number(pair[1]);
    } else {
      map.persons[pair[0]] = Number(pair[1]);
    }
  }

  if (!map.hasOwnProperty('tax') ||
      !map.hasOwnProperty('fee') ||
      !map.hasOwnProperty('tip')) {
    throw 'Found URL query string but not all required fields are present (tax, tip, and fee)';
  }

  map.tipPercent = map.tip / 100;
  delete map.tip;

  return map;
}

/**
 * Parses the confirmation summary from an OrderUp.com order
 */
function parseOrderUpInput(text) {
  // TODO: check if the number at the beginning of the line affects the item cost
  // example: 2 Chicken $4.00
  //   should the cost for the person be $4 or $8?

  var LABEL = 'Label for:';

  var map = {
    persons: {}
  };
  var itemCost = null;
  var array = text.split('\n');

  for (var i = 0; i < array.length; i++) {
    var line = array[i].trim();
    line = line.replace(/\s+/g, ' '); // replace all whitespace with single space

    if (!itemCost) {
      var dollarIndex = line.indexOf('$');
      if (dollarIndex > -1) {
        itemCost = Number(line.substring(dollarIndex + 1, line.length));
      }
      continue;
    }

    var labelIndex = line.indexOf(LABEL);
    if (labelIndex > -1) {
      var person = line.substring(labelIndex + LABEL.length, line.length);

      if (!map.persons[person]) {
        map.persons[person] = 0;
      }

      map.persons[person] += itemCost;
      itemCost = null;
    }
  }

  return map;
}

function onSplitButtonClick() {
  var text = document.getElementById('textarea').value;

  var input = this.parseOrderUpInput(text);
  input.tax = Number(document.getElementById('taxes').value);
  input.fee = Number(document.getElementById('fees').value);
  input.tipPercent = Number(document.getElementById('tip').value) / 100;

  this.split(input);
}

function split(input) {
  var subtotal = 0;
  for (var person in input.persons) {
    subtotal += input.persons[person];
  }

  var taxPercent = input.tax / subtotal;
  var feesPerPerson = input.fee / Object.keys(input.persons).length;
  var tip = input.tipPercent * subtotal;
  var total = subtotal + input.tax + input.fee + tip;

  var costs = {};
  for (person in input.persons) {
    costs[person] = input.persons[person] /* item cost */ +
        input.persons[person] * taxPercent /*+ tax on items */ +
        input.persons[person] * input.tipPercent /* tip on items */ +
        feesPerPerson;
  }

  input.subtotal = subtotal;
  input.taxPercent = taxPercent;
  input.feesPerPerson = feesPerPerson;
  input.tip = tip;
  input.total = total;
  input.costs = costs;

  this.display(input);
}

function display(input) {

  var calculationsTable = '
' +
      '
Subtotal:	$' + this.prettifyNumber(input.subtotal) + '	(user input; sum of item costs)
' +
      '
Tax:	$' + this.prettifyNumber(input.tax) + '	(user input)
' +
      '
Fees:	$' + this.prettifyNumber(input.fee) + '	(user input)
' +
      '
Tip:	$' + this.prettifyNumber(input.tip) + '	(tip percent * subtotal)
' +
      '
Total:	$' + this.prettifyNumber(input.total) + '	(subtotal + tax + fees + tip)
' +
      '
Fees per Person:	$' + this.prettifyNumber(input.feesPerPerson) + '	(fees / number of people)
' +
      '
Tax (Percent):	' + input.taxPercent * 100 + '%	(tax / subtotal)
' +
      '
Tip (Percent):	' + input.tipPercent * 100 + '%	(user input)
' +
      '
';

  var html =
      '
' +
      calculationsTable + '
' +
      this.makeBreakdown(input) + '
' +
      'Publish the following:
' +
      '
' + this.makeTable(input.costs) + '
' +
      this.makeHyperlink(input.tax, input.fee, input.tipPercent, input.persons);

  document.getElementById('result').innerHTML = html;
}

/**
 * Returns a string of a number in the format "#.##"
 */
function prettifyNumber(n) {
  return pad(round(n));
}

/**
 * Returns a number rounded to 2 decimals.
 */
function round(n) {
  return Math.round(n * 100) / 100;
}

/**
 * Returns a string of a number padded to 2 decimal places
 */
function pad(n) {
  var s = n.toString();

  if (s.indexOf('.') == -1) {
    s += '.';
  }

  while (s.length < s.indexOf('.') + 3) {
    s += '0';
  }

  return s;
}

function makeTable(object) {
  var names = Object.keys(object);
  var costs = names.map(name => { return object[name];});

  var maxNameLength = names.reduce((maxNameLength, name) => {
    return Math.max(maxNameLength, name.length);
  }, 0);

  names = names.map(name => {
    return name + Array(maxNameLength+2 - name.length).join(" ");
  });

  zip = rows=>rows[0].map((_,c)=>rows.map(row=>row[c])); // http://stackoverflow.com/questions/4856717/javascript-equivalent-of-pythons-zip-function

  var rows = zip([names,costs]).map(row => { return row[0]+row[1]; });

  var table = '
';
  table += rows.join("\n");
  table += '
';
  return table;
}

/**
 * Returns a hyperlink to this split order.
 * @param {number} tax - amount of taxes
 * @param {fee} fee - amount of fees
 * @param {number} tipPercent - tip as a percentage
 * @param {Object} personItemCosts - map of person name to item costs
 * @return {string} the hyperlink to this order
 */
function makeHyperlink(tax, fee, tipPercent, personItemCosts) {
  var link = window.location.origin + window.location.pathname;
  if (link.indexOf('index.html') == -1) {
    link += 'index.html';
  }

  link += '?tax=' + tax + '&fee=' + fee + '&tip=' + tipPercent * 100;

  for (var p in personItemCosts) {
    link += '&' + p + '=' + personItemCosts[p];
  }

  return '' + link + '';
}

function makeBreakdown(input) {
  var breakdown = '
';
  breakdown += '
Person	Item Costs	Tax	Tip	Fees Per Person	Person Total
';
  for (var person in input.persons) {
    breakdown += '
' + person + '	' +
        input.persons[person] + '	 + ' + // item costs
        input.persons[person] + ' * ' + input.taxPercent + '	 + ' + // taxes
        input.persons[person] + ' * ' + input.tipPercent + '	 + ' + // tip
        input.feesPerPerson + '	 = ' +
        input.costs[person] + '
';
  }

  breakdown += '
';
  return breakdown;
}
<commit_msg>Remove trailing spaces<commit_after>/*jshint esversion: 6*/
var OVERHEADS = ['tip', 'tax', 'fee'];

window.onload = init;

function init() {
  // check for URL query parameters
  if (window.location.search) {
    try {
      var input = this.parseQueryStringInput(window.location.search);
      this.split(input);
    } catch (err) {
      console.error(err);
    }
  }
}

/**
 * Parses a URL query string.
 *
 * Below is an example of input:
 * ?tax=0.30&fee=1.50&tip=15&Gus=5.00
 */
function parseQueryStringInput(input) {
  var map = {
    persons: {}
  };

  input = input.substring(1); // remove prefixing '?'
  var searchArray = input.split('&');
  for (var i = 0; i < searchArray.length; i++) {
    var pair = searchArray[i].split('=');
    if (OVERHEADS.indexOf(pair[0]) > -1) {
      map[pair[0]] = Number(pair[1]);
    } else {
      map.persons[pair[0]] = Number(pair[1]);
    }
  }

  if (!map.hasOwnProperty('tax') ||
      !map.hasOwnProperty('fee') ||
      !map.hasOwnProperty('tip')) {
    throw 'Found URL query string but not all required fields are present (tax, tip, and fee)';
  }

  map.tipPercent = map.tip / 100;
  delete map.tip;

  return map;
}

/**
 * Parses the confirmation summary from an OrderUp.com order
 */
function parseOrderUpInput(text) {
  // TODO: check if the number at the beginning of the line affects the item cost
  // example: 2 Chicken $4.00
  //   should the cost for the person be $4 or $8?

  var LABEL = 'Label for:';

  var map = {
    persons: {}
  };
  var itemCost = null;
  var array = text.split('\n');

  for (var i = 0; i < array.length; i++) {
    var line = array[i].trim();
    line = line.replace(/\s+/g, ' '); // replace all whitespace with single space

    if (!itemCost) {
      var dollarIndex = line.indexOf('$');
      if (dollarIndex > -1) {
        itemCost = Number(line.substring(dollarIndex + 1, line.length));
      }
      continue;
    }

    var labelIndex = line.indexOf(LABEL);
    if (labelIndex > -1) {
      var person = line.substring(labelIndex + LABEL.length, line.length);

      if (!map.persons[person]) {
        map.persons[person] = 0;
      }

      map.persons[person] += itemCost;
      itemCost = null;
    }
  }

  return map;
}

function onSplitButtonClick() {
  var text = document.getElementById('textarea').value;

  var input = this.parseOrderUpInput(text);
  input.tax = Number(document.getElementById('taxes').value);
  input.fee = Number(document.getElementById('fees').value);
  input.tipPercent = Number(document.getElementById('tip').value) / 100;

  this.split(input);
}

function split(input) {
  var subtotal = 0;
  for (var person in input.persons) {
    subtotal += input.persons[person];
  }

  var taxPercent = input.tax / subtotal;
  var feesPerPerson = input.fee / Object.keys(input.persons).length;
  var tip = input.tipPercent * subtotal;
  var total = subtotal + input.tax + input.fee + tip;

  var costs = {};
  for (person in input.persons) {
    costs[person] = input.persons[person] /* item cost */ +
        input.persons[person] * taxPercent /*+ tax on items */ +
        input.persons[person] * input.tipPercent /* tip on items */ +
        feesPerPerson;
  }

  input.subtotal = subtotal;
  input.taxPercent = taxPercent;
  input.feesPerPerson = feesPerPerson;
  input.tip = tip;
  input.total = total;
  input.costs = costs;

  this.display(input);
}

function display(input) {

  var calculationsTable = '
' +
      '
Subtotal:	$' + this.prettifyNumber(input.subtotal) + '	(user input; sum of item costs)
' +
      '
Tax:	$' + this.prettifyNumber(input.tax) + '	(user input)
' +
      '
Fees:	$' + this.prettifyNumber(input.fee) + '	(user input)
' +
      '
Tip:	$' + this.prettifyNumber(input.tip) + '	(tip percent * subtotal)
' +
      '
Total:	$' + this.prettifyNumber(input.total) + '	(subtotal + tax + fees + tip)
' +
      '
Fees per Person:	$' + this.prettifyNumber(input.feesPerPerson) + '	(fees / number of people)
' +
      '
Tax (Percent):	' + input.taxPercent * 100 + '%	(tax / subtotal)
' +
      '
Tip (Percent):	' + input.tipPercent * 100 + '%	(user input)
' +
      '
';

  var html =
      '
' +
      calculationsTable + '
' +
      this.makeBreakdown(input) + '
' +
      'Publish the following:
' +
      '
' + this.makeTable(input.costs) + '
' +
      this.makeHyperlink(input.tax, input.fee, input.tipPercent, input.persons);

  document.getElementById('result').innerHTML = html;
}

/**
 * Returns a string of a number in the format "#.##"
 */
function prettifyNumber(n) {
  return pad(round(n));
}

/**
 * Returns a number rounded to 2 decimals.
 */
function round(n) {
  return Math.round(n * 100) / 100;
}

/**
 * Returns a string of a number padded to 2 decimal places
 */
function pad(n) {
  var s = n.toString();

  if (s.indexOf('.') == -1) {
    s += '.';
  }

  while (s.length < s.indexOf('.') + 3) {
    s += '0';
  }

  return s;
}

function makeTable(object) {
  var names = Object.keys(object);
  var costs = names.map(name => { return object[name];});

  var maxNameLength = names.reduce((maxNameLength, name) => {
    return Math.max(maxNameLength, name.length);
  }, 0);

  names = names.map(name => {
    return name + Array(maxNameLength+2 - name.length).join(" ");
  });

  zip = rows=>rows[0].map((_,c)=>rows.map(row=>row[c])); // http://stackoverflow.com/questions/4856717/javascript-equivalent-of-pythons-zip-function

  var rows = zip([names,costs]).map(row => { return row[0]+row[1]; });

  var table = '
';
  table += rows.join("\n");
  table += '
';
  return table;
}

/**
 * Returns a hyperlink to this split order.
 * @param {number} tax - amount of taxes
 * @param {fee} fee - amount of fees
 * @param {number} tipPercent - tip as a percentage
 * @param {Object} personItemCosts - map of person name to item costs
 * @return {string} the hyperlink to this order
 */
function makeHyperlink(tax, fee, tipPercent, personItemCosts) {
  var link = window.location.origin + window.location.pathname;
  if (link.indexOf('index.html') == -1) {
    link += 'index.html';
  }

  link += '?tax=' + tax + '&fee=' + fee + '&tip=' + tipPercent * 100;

  for (var p in personItemCosts) {
    link += '&' + p + '=' + personItemCosts[p];
  }

  return '' + link + '';
}

function makeBreakdown(input) {
  var breakdown = '
';
  breakdown += '
Person	Item Costs	Tax	Tip	Fees Per Person	Person Total
';
  for (var person in input.persons) {
    breakdown += '
' + person + '	' +
        input.persons[person] + '	 + ' + // item costs
        input.persons[person] + ' * ' + input.taxPercent + '	 + ' + // taxes
        input.persons[person] + ' * ' + input.tipPercent + '	 + ' + // tip
        input.feesPerPerson + '	 = ' +
        input.costs[person] + '
';
  }

  breakdown += '
';
  return breakdown;
}
<|endoftext|>


<commit_before>// Copyright 2014-2015, University of Colorado Boulder

/**
 * View for the 'Energy Systems' screen of the Energy Forms And Changes simulation.
 *
 * @author  John Blanco
 * @author  Martin Veillette (Berea College)
 * @author  Jesse Greenberg
 * @author  Andrew Adare
 */
define( function( require ) {
  'use strict';

  // Modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var CheckBox = require( 'SUN/CheckBox' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunkLegend = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/view/EnergyChunkLegend' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergySystemElementSelector = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/view/EnergySystemElementSelector' );
  var EnergyChunkNode = require( 'ENERGY_FORMS_AND_CHANGES/common/view/EnergyChunkNode' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HSlider = require( 'SUN/HSlider' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LayoutBox = require( 'SCENERY/nodes/LayoutBox' );
  var ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Panel = require( 'SUN/Panel' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var ResetAllButton = require( 'SCENERY_PHET/buttons/ResetAllButton' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var SunNode = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/view/SunNode' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Vector2 = require( 'DOT/Vector2' );

  // Images
  var mockupImage = require( 'image!ENERGY_FORMS_AND_CHANGES/mockup_energy_systems.png' );

  // Strings
  var energySymbolsString = require( 'string!ENERGY_FORMS_AND_CHANGES/energySymbols' );

  // Constants
  var EDGE_INSET = 10;

  /**
   * @param {EnergySystemsModel} model
   * @constructor
   */
  function EnergySystemsScreenView( model ) {

    ScreenView.call( this, {
      layoutBounds: new Bounds2( 0, 0, 1008, 679 )
    } );

    // Bounds2 object for use as primary geometric reference
    var stage = this.layoutBounds;

    // Node for back-most layer
    var backLayer = new Node();
    this.addChild( backLayer );

    // ScreenView handle for use inside functions
    var thisScreenView = this;

    // Create the model-view transform.  The primary units used in the model are
    // meters, so significant zoom is used. The multipliers for the 2nd parameter
    // can be used to adjust where the point (0, 0) in the model, which is on the
    // middle of the screen above the counter as located in the view.
    // Final arg is zoom factor - smaller zooms out, larger zooms in.
    var mvtOriginX = Math.round( stage.width * 0.5 );
    var mvtOriginY = Math.round( stage.height * 0.475 );
    var modelViewTransform = ModelViewTransform2.createSinglePointScaleInvertedYMapping(
      Vector2.ZERO, new Vector2( mvtOriginX, mvtOriginY ), 2200 );

    // Add beige background rectangle
    function addBackground() {
      backLayer.addChild( new Rectangle( stage, {
        fill: EFACConstants.SECOND_TAB_BACKGROUND_COLOR
      } ) );
    }

    // Create a background rectangle for the play/pause controls.
    function addPlayControls() {
      var bottomPanel = new Rectangle( 0, stage.maxY - 114, stage.width, stage.height, 0, 0, {
        fill: EFACConstants.CLOCK_CONTROL_BACKGROUND_COLOR,
        stroke: 'black'
      } );
      backLayer.addChild( bottomPanel );
    }

    //Show the mock-up and a slider to change its transparency
    function addMockupImage() {
      var opacity = new Property( 0.8 );
      var image = new Image( mockupImage, {
        pickable: false
      } );
      image.scale( stage.width / image.width );
      opacity.linkAttribute( image, 'opacity' );
      thisScreenView.addChild( image );
      thisScreenView.addChild( new HSlider( opacity, {
        min: 0,
        max: 1
      }, {
        top: 10,
        left: 10
      } ) );
    }

    // Create the legend for energy chunk types
    function addEnergyChunkLegend() {
      var legend = new EnergyChunkLegend();
      legend.center = new Vector2( 0.9 * stage.width;, 0.5 * stage.height );
      thisScreenView.addChild( legend );
    }

    // Check box panel to display energy chunks
    function addCheckBoxPanel() {
      var label = new Text( energySymbolsString, {
        font: new PhetFont( 20 )
      } );

      var energyChunkNode = EnergyChunkNode.createEnergyChunkNode( EnergyType.THERMAL );
      energyChunkNode.scale( 1.0 );
      energyChunkNode.pickable = false;

      var checkBox = new CheckBox( new LayoutBox( {
        children: [ label, energyChunkNode ],
        orientation: 'horizontal',
        spacing: 5
      } ), model.energyChunksVisibleProperty );

      var panel = new Panel( checkBox, {
        fill: EFACConstants.CONTROL_PANEL_BACKGROUND_COLOR,
        stroke: EFACConstants.CONTROL_PANEL_OUTLINE_STROKE,
        lineWidth: EFACConstants.CONTROL_PANEL_OUTLINE_LINE_WIDTH
      } );
      panel.rightTop = new Vector2( stage.width - EDGE_INSET, EDGE_INSET );
      thisScreenView.addChild( panel );
    }

    // Create and add the Reset All Button in the bottom right, which resets the model
    function addResetButton() {
      var resetAllButton = new ResetAllButton( {
        listener: function() {
          model.reset();
        },
        right: stage.maxX - 10,
        bottom: stage.maxY - 10
      } );
      thisScreenView.addChild( resetAllButton );
    }

    function addSun() {
      var sun = new SunNode( model.sun, model.energyChunksVisibleProperty, modelViewTransform );
      thisScreenView.addChild( sun );
    }

    // Create the carousel control nodes.
    function addCarousels() {
      var sourcesCarousel = new EnergySystemElementSelector( model.energySourcesCarousel );
      // var convertersCarousel = new EnergySystemElementSelector( model.energyConvertersCarousel );
      // var usersCarousel = new EnergySystemElementSelector( model.energyUsersCarousel );

      thisScreenView.addChild( sourcesCarousel );
      // thisScreenView.addChild( convertersCarousel );
      // thisScreenView.addChild( usersCarousel );
    }

    addBackground();
    addPlayControls();
    addMockupImage();
    addEnergyChunkLegend();
    addCheckBoxPanel();
    addResetButton();
    addSun();
    addCarousels();
  }

  energyFormsAndChanges.register( 'EnergySystemsScreenView', EnergySystemsScreenView );

  return inherit( ScreenView, EnergySystemsScreenView );
} );
<commit_msg>Remove spurious semicolon<commit_after>// Copyright 2014-2015, University of Colorado Boulder

/**
 * View for the 'Energy Systems' screen of the Energy Forms And Changes simulation.
 *
 * @author  John Blanco
 * @author  Martin Veillette (Berea College)
 * @author  Jesse Greenberg
 * @author  Andrew Adare
 */
define( function( require ) {
  'use strict';

  // Modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var CheckBox = require( 'SUN/CheckBox' );
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunkLegend = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/view/EnergyChunkLegend' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergySystemElementSelector = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/view/EnergySystemElementSelector' );
  var EnergyChunkNode = require( 'ENERGY_FORMS_AND_CHANGES/common/view/EnergyChunkNode' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var HSlider = require( 'SUN/HSlider' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LayoutBox = require( 'SCENERY/nodes/LayoutBox' );
  var ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Panel = require( 'SUN/Panel' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var ResetAllButton = require( 'SCENERY_PHET/buttons/ResetAllButton' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var SunNode = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/view/SunNode' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Vector2 = require( 'DOT/Vector2' );

  // Images
  var mockupImage = require( 'image!ENERGY_FORMS_AND_CHANGES/mockup_energy_systems.png' );

  // Strings
  var energySymbolsString = require( 'string!ENERGY_FORMS_AND_CHANGES/energySymbols' );

  // Constants
  var EDGE_INSET = 10;

  /**
   * @param {EnergySystemsModel} model
   * @constructor
   */
  function EnergySystemsScreenView( model ) {

    ScreenView.call( this, {
      layoutBounds: new Bounds2( 0, 0, 1008, 679 )
    } );

    // Bounds2 object for use as primary geometric reference
    var stage = this.layoutBounds;

    // Node for back-most layer
    var backLayer = new Node();
    this.addChild( backLayer );

    // ScreenView handle for use inside functions
    var thisScreenView = this;

    // Create the model-view transform.  The primary units used in the model are
    // meters, so significant zoom is used. The multipliers for the 2nd parameter
    // can be used to adjust where the point (0, 0) in the model, which is on the
    // middle of the screen above the counter as located in the view.
    // Final arg is zoom factor - smaller zooms out, larger zooms in.
    var mvtOriginX = Math.round( stage.width * 0.5 );
    var mvtOriginY = Math.round( stage.height * 0.475 );
    var modelViewTransform = ModelViewTransform2.createSinglePointScaleInvertedYMapping(
      Vector2.ZERO, new Vector2( mvtOriginX, mvtOriginY ), 2200 );

    // Add beige background rectangle
    function addBackground() {
      backLayer.addChild( new Rectangle( stage, {
        fill: EFACConstants.SECOND_TAB_BACKGROUND_COLOR
      } ) );
    }

    // Create a background rectangle for the play/pause controls.
    function addPlayControls() {
      var bottomPanel = new Rectangle( 0, stage.maxY - 114, stage.width, stage.height, 0, 0, {
        fill: EFACConstants.CLOCK_CONTROL_BACKGROUND_COLOR,
        stroke: 'black'
      } );
      backLayer.addChild( bottomPanel );
    }

    //Show the mock-up and a slider to change its transparency
    function addMockupImage() {
      var opacity = new Property( 0.8 );
      var image = new Image( mockupImage, {
        pickable: false
      } );
      image.scale( stage.width / image.width );
      opacity.linkAttribute( image, 'opacity' );
      thisScreenView.addChild( image );
      thisScreenView.addChild( new HSlider( opacity, {
        min: 0,
        max: 1
      }, {
        top: 10,
        left: 10
      } ) );
    }

    // Create the legend for energy chunk types
    function addEnergyChunkLegend() {
      var legend = new EnergyChunkLegend();
      legend.center = new Vector2( 0.9 * stage.width, 0.5 * stage.height );
      thisScreenView.addChild( legend );
    }

    // Check box panel to display energy chunks
    function addCheckBoxPanel() {
      var label = new Text( energySymbolsString, {
        font: new PhetFont( 20 )
      } );

      var energyChunkNode = EnergyChunkNode.createEnergyChunkNode( EnergyType.THERMAL );
      energyChunkNode.scale( 1.0 );
      energyChunkNode.pickable = false;

      var checkBox = new CheckBox( new LayoutBox( {
        children: [ label, energyChunkNode ],
        orientation: 'horizontal',
        spacing: 5
      } ), model.energyChunksVisibleProperty );

      var panel = new Panel( checkBox, {
        fill: EFACConstants.CONTROL_PANEL_BACKGROUND_COLOR,
        stroke: EFACConstants.CONTROL_PANEL_OUTLINE_STROKE,
        lineWidth: EFACConstants.CONTROL_PANEL_OUTLINE_LINE_WIDTH
      } );
      panel.rightTop = new Vector2( stage.width - EDGE_INSET, EDGE_INSET );
      thisScreenView.addChild( panel );
    }

    // Create and add the Reset All Button in the bottom right, which resets the model
    function addResetButton() {
      var resetAllButton = new ResetAllButton( {
        listener: function() {
          model.reset();
        },
        right: stage.maxX - 10,
        bottom: stage.maxY - 10
      } );
      thisScreenView.addChild( resetAllButton );
    }

    function addSun() {
      var sun = new SunNode( model.sun, model.energyChunksVisibleProperty, modelViewTransform );
      thisScreenView.addChild( sun );
    }

    // Create the carousel control nodes.
    function addCarousels() {
      var sourcesCarousel = new EnergySystemElementSelector( model.energySourcesCarousel );
      // var convertersCarousel = new EnergySystemElementSelector( model.energyConvertersCarousel );
      // var usersCarousel = new EnergySystemElementSelector( model.energyUsersCarousel );

      thisScreenView.addChild( sourcesCarousel );
      // thisScreenView.addChild( convertersCarousel );
      // thisScreenView.addChild( usersCarousel );
    }

    addBackground();
    addPlayControls();
    addMockupImage();
    addEnergyChunkLegend();
    addCheckBoxPanel();
    addResetButton();
    addSun();
    addCarousels();
  }

  energyFormsAndChanges.register( 'EnergySystemsScreenView', EnergySystemsScreenView );

  return inherit( ScreenView, EnergySystemsScreenView );
} );
<|endoftext|>


<commit_before>'use strict';

const transforms = (Terms) => {

  const methods = {

    clone: function () {
      let terms = this.terms.map((t) => {
        return t.clone();
      });
      return new Terms(terms, this.lexicon, null, null); //this.refText, this.refTerms
    },
    hyphenate: function () {
      this.terms.forEach((t, i) => {
        if (i !== this.terms.length - 1) {
          t.whitespace.after = '-';
        }
        if (i !== 0) {
          t.whitespace.before = '';
        }
      });
      return this;
    },
    dehyphenate: function () {
      this.terms.forEach((t) => {
        if (t.whitespace.after === '-') {
          t.whitespace.after = ' ';
        }
      });
      return this;
    }
  };

  //hook them into result.proto
  Object.keys(methods).forEach((k) => {
    Terms.prototype[k] = methods[k];
  });
  return Terms;
};

module.exports = transforms;
<commit_msg>clone keep refText<commit_after>'use strict';

const transforms = (Terms) => {

  const methods = {

    clone: function () {
      let terms = this.terms.map((t) => {
        return t.clone();
      });
      return new Terms(terms, this.lexicon, this.refText, null); //, this.refTerms
    },
    hyphenate: function () {
      this.terms.forEach((t, i) => {
        if (i !== this.terms.length - 1) {
          t.whitespace.after = '-';
        }
        if (i !== 0) {
          t.whitespace.before = '';
        }
      });
      return this;
    },
    dehyphenate: function () {
      this.terms.forEach((t) => {
        if (t.whitespace.after === '-') {
          t.whitespace.after = ' ';
        }
      });
      return this;
    }
  };

  //hook them into result.proto
  Object.keys(methods).forEach((k) => {
    Terms.prototype[k] = methods[k];
  });
  return Terms;
};

module.exports = transforms;
<|endoftext|>


<commit_before>(function(){
    var isotope;
    var projectDataArray = [];
    var initIdArray = []; //holding original order of project ids
    var currentSlug = "";
    var currentName = "";
    var currentPageID = 0;
    var $images;
    var $projectContainer = $('#projects-page--ajax');
    var projectContainerVisible = false;
    var title = document.title;
    var isMobile = false;

    if($(window).width()<560){
        isMobile = true;
    }

    init();

    function init(){
        initListeners();
        initDeepLinking();
        $('.projects-page--project').each(function(){
            var project = {};
            project.id = $(this).data('pageid');
            project.title = $(this).attr('title');
            project.categories = $(this).data('categories');
            projectDataArray.push(project);
            $(this).find('.project-preview-text').css("visibility", "hidden");
        });

        initIdArray = getIdArray(); //for reset

        initIsotope();
        updateProjectListeners();
    }

    function initDeepLinking(){
        $.address.change(function(event) {
            if(event.parameters.category){
                closeProjectContainer();
                var slug = event.parameters.category;
                updateCurrentSlug(slug);
                var name = $(".category-link[data-slug='" + currentSlug + "']").data("name");
                $.address.title(name + " " + pageTitle); //pagetitle is set in Page.ss
            }
            if(event.parameters.page){
                var pageid = event.parameters.page;
                $project = $(".projects-page--project[data-pageid='" + pageid + "']");
                var projectTitle = $project.attr("title");
                $.address.title(projectTitle + " " + pageTitle);
                loadProject($project);
            }
        });
    }

    function initListeners(){
        $(window).resize(function() {
            if(this.resizeTO) {
                clearTimeout(this.resizeTO);
            }
            this.resizeTO = setTimeout(function() {
                resizeReadyIsotope();
            }, 500);
        });

        $(".projects-page--project").click(function(){
            onProjectClick($(this));
        });
    }

    function removeListeners(){
        $(window).unbind("resize");
    }

    function updateProjectListeners(){
        if(isMobile!=true){
            $('.projects-page--project').unbind("mouseenter");
            $('.projects-page--project').not('.knockout').mouseenter(function(){
                onProjectMouseOver($(this));
            });

            $('.projects-page--project').mouseleave(function(){
                onProjectMouseOut($(this));
            });
        }
    }

    function onProjectMouseOver($project){
        $project.addClass("mouseover");
        $project.find('.project-preview-text').css("visibility", "visible");
        var $image = $project.find("img");
        var $previewText = $project.find(".project-preview-text");
        $image.animate({top: -83}, 300, ANIMATION.easeOutQuart);
        $previewText.animate({bottom: -$previewText.height()+83}, 300, ANIMATION.easeOutQuart);
    }

    function onProjectMouseOut($project){
        $project.find('.project-preview-text').css("visibility", "hidden");
        var $image = $project.find("img");
        var $previewText = $project.find(".project-preview-text");
        $image.animate({top: 0}, 100, ANIMATION.easeOutQuart);
        $previewText.animate({bottom: 0}, 100, function(){$project.removeClass("mouseover");});
    }

    function onProjectClick($project){
        loadProject($project);
    }

    //AJAX
    function loadProject($project){
        var pageID = $project.data("pageid");
        if(currentPageID!=pageID){
            currentPageID = pageID;
            var absoluteLink = $project.data("absolutelink");
            var link = absoluteLink+"ajax";
            $projectContainer.load(link, onProjectAjaxComplete);
        }
    }

    function onProjectAjaxComplete(){
        initAjaxListeners();
        $imagesContainer = $(".project-page .images-container");
        $imagesContainer.hide();
        $projectContainer.slideDown(500, "swing", function(){
            window.scrollTo(0, 0);
        });
        showImages();
    }

    function showImages(){
        var imagesLoaded = 0;
        var numberOfImages = $imagesContainer.find("img").length;

        $imagesContainer.find("img").load(function(){
            if(imagesLoaded == numberOfImages-1){
                $imagesContainer.show();
                var width = "-" + $imagesContainer.width().toString()  + "px";
                var cssObj = {
                    "left": width
                }
                $imagesContainer.css(cssObj);
                $imagesContainer.animate({left: 0}, 500, function(){
                    ScrollMedia.init();
                });
            }
            imagesLoaded++;
        });
    }

    function initAjaxListeners(){
        $('a.btn-close, a.btn-all').click(function(e){
             closeProjectContainer();
             currentPageID = 0;
             e.preventDefault();
        });
        $('a.btn-prev, a.btn-next').click(function(e){
            $.address.value("?page="+$(this).data("id"));
            e.preventDefault();
        });
    }

    //SORT/FILTER
    function updateCurrentSlug(slug){
        currentSlug = slug;
        updateMenu();
        if(slug=="all"){
            reset();
        }else{
            filterProjectsByCategory();
            projectDataArray.sort(sortByCategory);
            updateIsotope();
        }
    }

    function sortByTitle(a, b) {
        return a.title.localeCompare(b.title);
    }

    function sortByCategory(a, b){
        if(checkA()<checkB()){
            return 1;
        }
        if(checkA()>checkB()){
            return -1;
        }
        function checkA(){
            for(var i=0; i<a.categories.length; i++){
                 var category = a.categories[i];
                 if(category == currentSlug){
                     return 1;
                 }
            }
            return 0;
        }
        function checkB(){
            for(var i=0; i<b.categories.length; i++){
                 var category = b.categories[i];
                 if(category == currentSlug){
                     return 1;
                 }
            }
            return 0;
        }
        return 0;
    }

    //KNOCKOUT
    function filterProjectsByCategory(){
        var activeIdArray = [];
        $('.projects-page--project').each(function(){
            var categories = $(this).data('categories');
            var id = $(this).data('pageid');
            for(var i=0; i<categories.length; i++){
                var category = categories[i];
                if(category==currentSlug){
                    if(idAlreadyExists(id)!=true){
                        activeIdArray.push(id);
                    }
                }
            }
        });

        function idAlreadyExists(id){
            for(var j=0; j<activeIdArray.length; j++){
                if(activeIdArray[j] == id){
                    return true;
                }
            }
        }
        knockout(activeIdArray);
    }

    function knockout(activeIdArray){
        $('.projects-page--project').each(function(){
            $(this).addClass("knockout");
            var id = $(this).data('pageid');
            for(var j=0; j<activeIdArray.length; j++){
                if(activeIdArray[j] == id){
                    $(this).removeClass("knockout");
                }
            }
        });
        updateProjectListeners();
    }

    function reset(){
        $('.projects-page--project').each(function(){
            $(this).removeClass("knockout");
        });
        resetIsotope();
    }

    //MENU
    function updateMenu(){
        var $menuItem = $(".category-link[data-slug='" + currentSlug + "']").parent();
        $('.current').removeClass('current'); //update menu visually
        $menuItem.addClass("current");
    }

    //ISOTOPE
    function initIsotope(){
        isotope = new Isotope();
        isotope.updateIdArray(initIdArray);
        resizeIsotope();
      //  resizeReadyIsotope();
    }

    function resetIsotope(){
        isotope.updateIdArray(initIdArray);
        isotope.reposition();
    }

    function updateIsotope(){
        removeListeners();
        isotope.updateIdArray(getIdArray());
        isotope.reposition(initListeners);
    }

    function resizeIsotope(){
        isotope.updateImgWidth();
        isotope.position();
    }

    function resizeReadyIsotope(){
        isotope.updateImgWidth();
        isotope.reposition();
    }

    function getIdArray(){
        var idArray = [];
        for(var i=0; i<projectDataArray.length; i++){
            idArray.push(projectDataArray[i].id);
        }
        return idArray;
    }

    //ANIMATION
    function closeProjectContainer(){
        window.scrollTo(0, 0);
        $projectContainer.slideUp(function(){
            projectContainerVisible = false;
        });
    }

    var ANIMATION = {
        easeOutQuart: function (x, t, b, c, d) {
            return -c * ((t=t/d-1)*t*t*t - 1) + b;
        }
    }
})();

<commit_msg>fix bug: reopen same project after category filtering<commit_after>(function(){
    var isotope;
    var projectDataArray = [];
    var initIdArray = []; //holding original order of project ids
    var currentSlug = "";
    var currentName = "";
    var currentPageID = 0;
    var $images;
    var $projectContainer = $('#projects-page--ajax');
    var projectContainerVisible = false;
    var title = document.title;
    var isMobile = false;

    if($(window).width()<560){
        isMobile = true;
    }

    init();

    function init(){
        initListeners();
        initDeepLinking();
        $('.projects-page--project').each(function(){
            var project = {};
            project.id = $(this).data('pageid');
            project.title = $(this).attr('title');
            project.categories = $(this).data('categories');
            projectDataArray.push(project);
            $(this).find('.project-preview-text').css("visibility", "hidden");
        });

        initIdArray = getIdArray(); //for reset

        initIsotope();
        updateProjectListeners();
    }

    function initDeepLinking(){
        $.address.change(function(event) {
            if(event.parameters.category){
                closeProjectContainer();
                var slug = event.parameters.category;
                updateCurrentSlug(slug);
                var name = $(".category-link[data-slug='" + currentSlug + "']").data("name");
                $.address.title(name + " " + pageTitle); //pagetitle is set in Page.ss
            }
            if(event.parameters.page){
                var pageid = event.parameters.page;
                $project = $(".projects-page--project[data-pageid='" + pageid + "']");
                var projectTitle = $project.attr("title");
                $.address.title(projectTitle + " " + pageTitle);
                loadProject($project);
            }
        });
    }

    function initListeners(){
        $(window).resize(function() {
            if(this.resizeTO) {
                clearTimeout(this.resizeTO);
            }
            this.resizeTO = setTimeout(function() {
                resizeReadyIsotope();
            }, 500);
        });

        $(".projects-page--project").click(function(){
            onProjectClick($(this));
        });
    }

    function removeListeners(){
        $(window).unbind("resize");
    }

    function updateProjectListeners(){
        if(isMobile!=true){
            $('.projects-page--project').unbind("mouseenter");
            $('.projects-page--project').not('.knockout').mouseenter(function(){
                onProjectMouseOver($(this));
            });

            $('.projects-page--project').mouseleave(function(){
                onProjectMouseOut($(this));
            });
        }
    }

    function onProjectMouseOver($project){
        $project.addClass("mouseover");
        $project.find('.project-preview-text').css("visibility", "visible");
        var $image = $project.find("img");
        var $previewText = $project.find(".project-preview-text");
        $image.animate({top: -83}, 300, ANIMATION.easeOutQuart);
        $previewText.animate({bottom: -$previewText.height()+83}, 300, ANIMATION.easeOutQuart);
    }

    function onProjectMouseOut($project){
        $project.find('.project-preview-text').css("visibility", "hidden");
        var $image = $project.find("img");
        var $previewText = $project.find(".project-preview-text");
        $image.animate({top: 0}, 100, ANIMATION.easeOutQuart);
        $previewText.animate({bottom: 0}, 100, function(){$project.removeClass("mouseover");});
    }

    function onProjectClick($project){
        loadProject($project);
    }

    //AJAX
    function loadProject($project){
        var pageID = $project.data("pageid");
        if(currentPageID!=pageID){ //prevent loading same content again
            currentPageID = pageID;
            var absoluteLink = $project.data("absolutelink");
            var link = absoluteLink+"ajax";
            $projectContainer.load(link, onProjectAjaxComplete);
        }
    }

    function onProjectAjaxComplete(){
        initAjaxListeners();
        $imagesContainer = $(".project-page .images-container");
        $imagesContainer.hide();
        $projectContainer.slideDown(500, "swing", function(){
            window.scrollTo(0, 0);
        });
        showImages();
    }

    function showImages(){
        var imagesLoaded = 0;
        var numberOfImages = $imagesContainer.find("img").length;

        $imagesContainer.find("img").load(function(){
            if(imagesLoaded == numberOfImages-1){
                $imagesContainer.show();
                var width = "-" + $imagesContainer.width().toString()  + "px";
                var cssObj = {
                    "left": width
                }
                $imagesContainer.css(cssObj);
                $imagesContainer.animate({left: 0}, 500, function(){
                    ScrollMedia.init();
                });
            }
            imagesLoaded++;
        });
    }

    function initAjaxListeners(){
        $('a.btn-close, a.btn-all').click(function(e){
             closeProjectContainer();
             e.preventDefault();
        });
        $('a.btn-prev, a.btn-next').click(function(e){
            $.address.value("?page="+$(this).data("id"));
            e.preventDefault();
        });
    }

    //SORT/FILTER
    function updateCurrentSlug(slug){
        currentSlug = slug;
        updateMenu();
        if(slug=="all"){
            reset();
        }else{
            filterProjectsByCategory();
            projectDataArray.sort(sortByCategory);
            updateIsotope();
        }
    }

    function sortByTitle(a, b) {
        return a.title.localeCompare(b.title);
    }

    function sortByCategory(a, b){
        if(checkA()<checkB()){
            return 1;
        }
        if(checkA()>checkB()){
            return -1;
        }
        function checkA(){
            for(var i=0; i<a.categories.length; i++){
                 var category = a.categories[i];
                 if(category == currentSlug){
                     return 1;
                 }
            }
            return 0;
        }
        function checkB(){
            for(var i=0; i<b.categories.length; i++){
                 var category = b.categories[i];
                 if(category == currentSlug){
                     return 1;
                 }
            }
            return 0;
        }
        return 0;
    }

    //KNOCKOUT
    function filterProjectsByCategory(){
        var activeIdArray = [];
        $('.projects-page--project').each(function(){
            var categories = $(this).data('categories');
            var id = $(this).data('pageid');
            for(var i=0; i<categories.length; i++){
                var category = categories[i];
                if(category==currentSlug){
                    if(idAlreadyExists(id)!=true){
                        activeIdArray.push(id);
                    }
                }
            }
        });

        function idAlreadyExists(id){
            for(var j=0; j<activeIdArray.length; j++){
                if(activeIdArray[j] == id){
                    return true;
                }
            }
        }
        knockout(activeIdArray);
    }

    function knockout(activeIdArray){
        $('.projects-page--project').each(function(){
            $(this).addClass("knockout");
            var id = $(this).data('pageid');
            for(var j=0; j<activeIdArray.length; j++){
                if(activeIdArray[j] == id){
                    $(this).removeClass("knockout");
                }
            }
        });
        updateProjectListeners();
    }

    function reset(){
        $('.projects-page--project').each(function(){
            $(this).removeClass("knockout");
        });
        resetIsotope();
    }

    //MENU
    function updateMenu(){
        var $menuItem = $(".category-link[data-slug='" + currentSlug + "']").parent();
        $('.current').removeClass('current'); //update menu visually
        $menuItem.addClass("current");
    }

    //ISOTOPE
    function initIsotope(){
        isotope = new Isotope();
        isotope.updateIdArray(initIdArray);
        resizeIsotope();
      //  resizeReadyIsotope();
    }

    function resetIsotope(){
        isotope.updateIdArray(initIdArray);
        isotope.reposition();
    }

    function updateIsotope(){
        removeListeners();
        isotope.updateIdArray(getIdArray());
        isotope.reposition(initListeners);
    }

    function resizeIsotope(){
        isotope.updateImgWidth();
        isotope.position();
    }

    function resizeReadyIsotope(){
        isotope.updateImgWidth();
        isotope.reposition();
    }

    function getIdArray(){
        var idArray = [];
        for(var i=0; i<projectDataArray.length; i++){
            idArray.push(projectDataArray[i].id);
        }
        return idArray;
    }

    //ANIMATION
    function closeProjectContainer() {
        currentPageID = 0; // reset to allow content load again
        window.scrollTo(0, 0);
        $projectContainer.slideUp(function(){
            projectContainerVisible = false;
        });
    }

    var ANIMATION = {
        easeOutQuart: function (x, t, b, c, d) {
            return -c * ((t=t/d-1)*t*t*t - 1) + b;
        }
    }
})();

<|endoftext|>


<commit_before>
/**
 * @constructor FORGE.ControllerManager
 * @param {FORGE.Viewer} viewer - viewer reference.
 * @extends {FORGE.BaseObject}
 */
FORGE.ControllerManager = function(viewer)
{
    /**
     * Viewer reference.
     * @name FORGE.ControllerManager#_viewer
     * @type {FORGE.Viewer}
     * @private
     */
    this._viewer = viewer;

    /**
     * Array of controllers.
     * @name FORGE.ControllerManager#_controllers
     * @type {Array<FORGE.ControllerBase>}
     * @private
     */
    this._controllers = [];

    /**
     * Enabled flag.
     * @name  FORGE.ControllerManager#_enabled
     * @type {boolean}
     * @private
     */
    this._enabled = true;

    /**
     * Event dispatcher for control start.
     * @name FORGE.ControllerManager#_onControlStart
     * @type {FORGE.EventDispatcher}
     * @private
     */
    this._onControlStart = null;

    /**
     * Event dispatcher for control end.
     * @name FORGE.ControllerManager#_onControlEnd
     * @type {FORGE.EventDispatcher}
     * @private
     */
    this._onControlEnd = null;

    FORGE.BaseObject.call(this, "ControllerManager");
};

FORGE.ControllerManager.prototype = Object.create(FORGE.BaseObject.prototype);
FORGE.ControllerManager.prototype.constructor = FORGE.ControllerManager;

/**
 * Controllers default configuration
 * @name  FORGE.ControllerManager.DEFAULT_CONFIG
 * @const
 */
FORGE.ControllerManager.DEFAULT_CONFIG =
{
    enabled: true,

    instances:
    [
        {
            type: FORGE.ControllerType.POINTER,
            enabled: true
        },

        {
            type: FORGE.ControllerType.KEYBOARD,
            enabled: true
        },

        {
            type: FORGE.ControllerType.GYROSCOPE,
            enabled: true
        },

        {
            type: FORGE.ControllerType.GAMEPAD,
            enabled: true
        }
    ]
};

/**
 * Parse configuration object
 * @method FORGE.ControllerManager#_parseConfig
 * @param {ControllersConfig} config - The config you want to add.
 * @private
 */
FORGE.ControllerManager.prototype._parseConfig = function(config)
{
    this._enabled = (typeof config.enabled === "boolean") ? config.enabled : true;

    if(typeof config.instances !== "undefined")
    {
        var controllerConfig;
        var controller;

        for(var i = 0, ii = config.instances.length; i < ii; i++)
        {
            controllerConfig = config.instances[i];

            switch(controllerConfig.type)
            {
                case FORGE.ControllerType.POINTER:
                    controller = new FORGE.ControllerPointer(this._viewer, controllerConfig);
                    break;

                case FORGE.ControllerType.KEYBOARD:
                    controller = new FORGE.ControllerKeyboard(this._viewer, controllerConfig);
                    break;

                case FORGE.ControllerType.GYROSCOPE:
                    controller = new FORGE.ControllerGyroscope(this._viewer, controllerConfig);
                    break;

                case FORGE.ControllerType.GAMEPAD:
                    controller = new FORGE.ControllerGamepad(this._viewer, controllerConfig);
                    break;

                default:
                    controller = null;
                    break;
            }

            if(controller !== null)
            {
                this._controllers.push(controller);
            }
        }
    }
};

/**
 * Controllers will call this method to notify the manager that a control start.
 * @method FORGE.ControllerManager#notifyControlStart
 * @param  {FORGE.ControllerBase} controller - The controller that notifies the manager.
 */
FORGE.ControllerManager.prototype.notifyControlStart = function(controller)
{
    if(this._onControlStart !== null)
    {
        this._onControlStart.dispatch({"controller": controller});
    }
};

/**
 * Controllers will call this method to notify the manager that a controller ends.
 * @method FORGE.ControllerManager#notifyControlEnd
 * @param  {FORGE.ControllerBase} controller - The controller that notifies the manager.
 */
FORGE.ControllerManager.prototype.notifyControlEnd = function(controller)
{
    if(this._onControlEnd !== null)
    {
        this._onControlEnd.dispatch({"controller": controller});
    }
};

/**
 * Controllers update routine
 * @method FORGE.ControllerManager#update
 */
FORGE.ControllerManager.prototype.update = function()
{
    if (this._controllers === null)
    {
        return;
    }

    for(var i = 0, ii = this._controllers.length; i < ii; i++)
    {
        if (typeof this._controllers[i].update === "function")
        {
            this._controllers[i].update();
        }
    }
};

/**
 * Add a config to the manager.
 * @method FORGE.ControllerManager#addConfig
 * @param {ControllersConfig} config - The config you want to add.
 */
FORGE.ControllerManager.prototype.addConfig = function(config)
{
    config = (typeof config !== "undefined") ? config : FORGE.ControllerManager.DEFAULT_CONFIG;

    this._parseConfig(config);
};

/**
 * Get a controller by its type.
 * @method FORGE.ControllerManager#getByType
 * @param {string} type - The type of the controller you want to get.
 * @return {?FORGE.ControllerBase} return the desired type controller, null if not found
 */
FORGE.ControllerManager.prototype.getByType = function(type)
{
    var controller;

    for(var i = 0, ii = this._controllers.length; i < ii; i++)
    {
        controller = this._controllers[i];

        if(controller.type === type)
        {
            return controller;
        }
    }

    return null;
};

/**
 * Destroy method.
 */
FORGE.ControllerManager.prototype.destroy = function()
{
    var count = this._controllers.length - 1;
    var ctrl;
    while(count--)
    {
        ctrl = this._controllers[count];
        if (ctrl !== null && typeof ctrl !== "undefined")
        {
            ctrl.destroy();
            this._controllers[count] = null;
        }
    }
    this._controllers = null;

    if(this._onControlStart !== null)
    {
        this._onControlStart.destroy();
        this._onControlStart = null;
    }

    if(this._onControlEnd !== null)
    {
        this._onControlEnd.destroy();
        this._onControlEnd = null;
    }

    FORGE.BaseObject.prototype.destroy.call(this);
};

/**
 * Get all controllers.
 * @name FORGE.ControllerManager#all
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "all",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        return this._controllers;
    }
});

/**
 * Get or set the global enabled flag.
 * @name FORGE.ControllerManager#enabled
 * @type {boolean}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "enabled",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        return this._enabled;
    },

    /** @this {FORGE.ControllerManager} */
    set: function(value)
    {
        this._enabled = Boolean(value);
    }
});

/**
 * know if any of the controllers is currently in use
 * @name FORGE.ControllerManager#active
 * @readonly
 * @type {boolean}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "active",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        for(var i = 0, ii = this._controllers.length; i < ii; i++)
        {
            if(this._controllers[i].active === true)
            {
                return true;
            }
        }

        return false;
    }
});

/**
 * Get the "onControlStart" {@link FORGE.EventDispatcher} of the camera controller.
 * @name FORGE.ControllerManager#onControlStart
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "onControlStart",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        if(this._onControlStart === null)
        {
            this._onControlStart = new FORGE.EventDispatcher(this);
        }

        return this._onControlStart;
    }
});

/**
 * Get the "onControlEnd" {@link FORGE.EventDispatcher} of the camera controller.
 * @name FORGE.ControllerManager#onControlEnd
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "onControlEnd",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        if(this._onControlEnd === null)
        {
            this._onControlEnd = new FORGE.EventDispatcher(this);
        }

        return this._onControlEnd;
    }
});
<commit_msg>Fix Controllers deleting in ControllerManager, there was always one kept.<commit_after>
/**
 * @constructor FORGE.ControllerManager
 * @param {FORGE.Viewer} viewer - viewer reference.
 * @extends {FORGE.BaseObject}
 */
FORGE.ControllerManager = function(viewer)
{
    /**
     * Viewer reference.
     * @name FORGE.ControllerManager#_viewer
     * @type {FORGE.Viewer}
     * @private
     */
    this._viewer = viewer;

    /**
     * Array of controllers.
     * @name FORGE.ControllerManager#_controllers
     * @type {Array<FORGE.ControllerBase>}
     * @private
     */
    this._controllers = [];

    /**
     * Enabled flag.
     * @name  FORGE.ControllerManager#_enabled
     * @type {boolean}
     * @private
     */
    this._enabled = true;

    /**
     * Event dispatcher for control start.
     * @name FORGE.ControllerManager#_onControlStart
     * @type {FORGE.EventDispatcher}
     * @private
     */
    this._onControlStart = null;

    /**
     * Event dispatcher for control end.
     * @name FORGE.ControllerManager#_onControlEnd
     * @type {FORGE.EventDispatcher}
     * @private
     */
    this._onControlEnd = null;

    FORGE.BaseObject.call(this, "ControllerManager");
};

FORGE.ControllerManager.prototype = Object.create(FORGE.BaseObject.prototype);
FORGE.ControllerManager.prototype.constructor = FORGE.ControllerManager;

/**
 * Controllers default configuration
 * @name  FORGE.ControllerManager.DEFAULT_CONFIG
 * @const
 */
FORGE.ControllerManager.DEFAULT_CONFIG =
{
    enabled: true,

    instances:
    [
        {
            type: FORGE.ControllerType.POINTER,
            enabled: true
        },

        {
            type: FORGE.ControllerType.KEYBOARD,
            enabled: true
        },

        {
            type: FORGE.ControllerType.GYROSCOPE,
            enabled: true
        },

        {
            type: FORGE.ControllerType.GAMEPAD,
            enabled: true
        }
    ]
};

/**
 * Parse configuration object
 * @method FORGE.ControllerManager#_parseConfig
 * @param {ControllersConfig} config - The config you want to add.
 * @private
 */
FORGE.ControllerManager.prototype._parseConfig = function(config)
{
    this._enabled = (typeof config.enabled === "boolean") ? config.enabled : true;

    if(typeof config.instances !== "undefined")
    {
        var controllerConfig;
        var controller;

        for(var i = 0, ii = config.instances.length; i < ii; i++)
        {
            controllerConfig = config.instances[i];

            switch(controllerConfig.type)
            {
                case FORGE.ControllerType.POINTER:
                    controller = new FORGE.ControllerPointer(this._viewer, controllerConfig);
                    break;

                case FORGE.ControllerType.KEYBOARD:
                    controller = new FORGE.ControllerKeyboard(this._viewer, controllerConfig);
                    break;

                case FORGE.ControllerType.GYROSCOPE:
                    controller = new FORGE.ControllerGyroscope(this._viewer, controllerConfig);
                    break;

                case FORGE.ControllerType.GAMEPAD:
                    controller = new FORGE.ControllerGamepad(this._viewer, controllerConfig);
                    break;

                default:
                    controller = null;
                    break;
            }

            if(controller !== null)
            {
                this._controllers.push(controller);
            }
        }
    }
};

/**
 * Controllers will call this method to notify the manager that a control start.
 * @method FORGE.ControllerManager#notifyControlStart
 * @param  {FORGE.ControllerBase} controller - The controller that notifies the manager.
 */
FORGE.ControllerManager.prototype.notifyControlStart = function(controller)
{
    if(this._onControlStart !== null)
    {
        this._onControlStart.dispatch({"controller": controller});
    }
};

/**
 * Controllers will call this method to notify the manager that a controller ends.
 * @method FORGE.ControllerManager#notifyControlEnd
 * @param  {FORGE.ControllerBase} controller - The controller that notifies the manager.
 */
FORGE.ControllerManager.prototype.notifyControlEnd = function(controller)
{
    if(this._onControlEnd !== null)
    {
        this._onControlEnd.dispatch({"controller": controller});
    }
};

/**
 * Controllers update routine
 * @method FORGE.ControllerManager#update
 */
FORGE.ControllerManager.prototype.update = function()
{
    if (this._controllers === null)
    {
        return;
    }

    for(var i = 0, ii = this._controllers.length; i < ii; i++)
    {
        if (typeof this._controllers[i].update === "function")
        {
            this._controllers[i].update();
        }
    }
};

/**
 * Add a config to the manager.
 * @method FORGE.ControllerManager#addConfig
 * @param {ControllersConfig} config - The config you want to add.
 */
FORGE.ControllerManager.prototype.addConfig = function(config)
{
    config = (typeof config !== "undefined") ? config : FORGE.ControllerManager.DEFAULT_CONFIG;

    this._parseConfig(config);
};

/**
 * Get a controller by its type.
 * @method FORGE.ControllerManager#getByType
 * @param {string} type - The type of the controller you want to get.
 * @return {?FORGE.ControllerBase} return the desired type controller, null if not found
 */
FORGE.ControllerManager.prototype.getByType = function(type)
{
    var controller;

    for(var i = 0, ii = this._controllers.length; i < ii; i++)
    {
        controller = this._controllers[i];

        if(controller.type === type)
        {
            return controller;
        }
    }

    return null;
};

/**
 * Destroy method.
 */
FORGE.ControllerManager.prototype.destroy = function()
{
    var ctrl;

    while(this._controllers.length)
    {
        ctrl = this._controllers.pop();
        if (ctrl !== null && typeof ctrl !== "undefined")
        {
            ctrl.destroy();
        }
    }

    this._controllers = null;

    if(this._onControlStart !== null)
    {
        this._onControlStart.destroy();
        this._onControlStart = null;
    }

    if(this._onControlEnd !== null)
    {
        this._onControlEnd.destroy();
        this._onControlEnd = null;
    }

    FORGE.BaseObject.prototype.destroy.call(this);
};

/**
 * Get all controllers.
 * @name FORGE.ControllerManager#all
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "all",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        return this._controllers;
    }
});

/**
 * Get or set the global enabled flag.
 * @name FORGE.ControllerManager#enabled
 * @type {boolean}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "enabled",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        return this._enabled;
    },

    /** @this {FORGE.ControllerManager} */
    set: function(value)
    {
        this._enabled = Boolean(value);
    }
});

/**
 * know if any of the controllers is currently in use
 * @name FORGE.ControllerManager#active
 * @readonly
 * @type {boolean}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "active",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        for(var i = 0, ii = this._controllers.length; i < ii; i++)
        {
            if(this._controllers[i].active === true)
            {
                return true;
            }
        }

        return false;
    }
});

/**
 * Get the "onControlStart" {@link FORGE.EventDispatcher} of the camera controller.
 * @name FORGE.ControllerManager#onControlStart
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "onControlStart",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        if(this._onControlStart === null)
        {
            this._onControlStart = new FORGE.EventDispatcher(this);
        }

        return this._onControlStart;
    }
});

/**
 * Get the "onControlEnd" {@link FORGE.EventDispatcher} of the camera controller.
 * @name FORGE.ControllerManager#onControlEnd
 * @readonly
 * @type {FORGE.EventDispatcher}
 */
Object.defineProperty(FORGE.ControllerManager.prototype, "onControlEnd",
{
    /** @this {FORGE.ControllerManager} */
    get: function()
    {
        if(this._onControlEnd === null)
        {
            this._onControlEnd = new FORGE.EventDispatcher(this);
        }

        return this._onControlEnd;
    }
});
<|endoftext|>


<commit_before>var gulp = require('gulp');
var sass = require('gulp-ruby-sass');
var prefix = require('gulp-autoprefixer');
var minify = require('gulp-minify-css');
var sequence = require('run-sequence');
var uglify = require('gulp-uglifyjs');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var rsync = require('rsyncwrapper').rsync;
var gutil = require('gulp-util');
var imagemin = require('gulp-imagemin');
var cache = require('gulp-cache');
var args   = require('yargs').argv;
var gulpif = require('gulp-if');
var config = require('./config.json');
var src = 'app/';
var dest = 'build/';
var bower = 'components/';

var deployTarget = ( typeof args.target !== 'undefined' ? args.target : 'staging' );

gulp.task('styles', function () {

  return gulp.src([
      src + 'scss/main.scss',
      src + 'scss/editor.scss'
    ])
    .pipe(sass({
      precision: 10,
      loadPath: [
        process.cwd() + '/app/scss',
        process.cwd() + '/components'
      ]
    }))
    .pipe(prefix('last 1 version', 'ie 9'))
    .pipe(minify({ keepSpecialComments: 1 }))
    .pipe(gulp.dest(dest + 'css'));

});

gulp.task('scripts', function() {

  return gulp.src([
      bower + 'bootstrap-sass-official/assets/javascripts/bootstrap.js',
      bower + 'fitvids/jquery.fitvids.js',
      src + 'js/plugins/*.js',
      src + 'js/main.js'
    ])
    .pipe(uglify('main.js', {
      outSourceMap: true,
      basePath: 'build/js'
    }))
    .pipe(gulp.dest(dest + 'js'));

});

gulp.task('admin-scripts', function() {

  return gulp.src([
      src + 'js/admin.js'
    ])
    .pipe(uglify('admin.js', {
      outSourceMap: true,
      basePath: 'build/js'
    }))
    .pipe(gulp.dest(dest + 'js'));

});

gulp.task('lint', function() {

  return gulp.src(src + 'js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'))
    .pipe(jshint.reporter('fail'));
});

gulp.task('images', function() {
  return gulp.src(src + 'img/**/*')
    .pipe(cache(imagemin({
      optimizationLevel: 5,
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest(dest + 'img'));
});

gulp.task('fonts', function() {
  return gulp.src(src + 'font/*')
    .pipe(gulp.dest(dest + 'font'));
});

gulp.task('watch', function () {

  gulp.watch(src + 'scss/**/*.scss', ['styles']);
  gulp.watch(src + 'js/**/*.js', ['scripts']);
  gulp.watch(src + 'img/**/*', ['images']);

});

gulp.task('rsync', function() {

  if ( config.servers[deployTarget].user === '' ||
       config.servers[deployTarget].host === '' ) {
    return gutil.log(gutil.colors.red('ERROR:'), 'User, host and path not configured for ' + deployTarget + ' target in config.json');
  }

  rsync({
    ssh: true,
    src: './',
    dest: config.servers[deployTarget].user + '@' + config.servers[deployTarget].host + ':' + config.servers[deployTarget].path,
    recursive: true,
    syncDest: true,
    exclude: ['node_modules', '.sass-cache'],
    args: ['--verbose']
  }, function(error, stdout, stderr, cmd) {
      gutil.log(stdout);
  });

});

gulp.task('deploy', function(cb) {

  sequence('default', 'rsync', cb);

});

gulp.task('default', ['styles', 'scripts', 'admin-scripts', 'images', 'fonts']);
<commit_msg>fix svg optimization, add clear cache task<commit_after>var gulp = require('gulp');
var sass = require('gulp-ruby-sass');
var prefix = require('gulp-autoprefixer');
var minify = require('gulp-minify-css');
var sequence = require('run-sequence');
var uglify = require('gulp-uglifyjs');
var jshint = require('gulp-jshint');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var rsync = require('rsyncwrapper').rsync;
var gutil = require('gulp-util');
var imagemin = require('gulp-imagemin');
var cache = require('gulp-cache');
var args   = require('yargs').argv;
var gulpif = require('gulp-if');
var config = require('./config.json');
var src = 'app/';
var dest = 'build/';
var bower = 'components/';

var deployTarget = ( typeof args.target !== 'undefined' ? args.target : 'staging' );

gulp.task('styles', function () {

  return gulp.src([
      src + 'scss/main.scss',
      src + 'scss/editor.scss'
    ])
    .pipe(sass({
      precision: 10,
      loadPath: [
        process.cwd() + '/app/scss',
        process.cwd() + '/components'
      ]
    }))
    .pipe(prefix('last 1 version', 'ie 9'))
    .pipe(minify({ keepSpecialComments: 1 }))
    .pipe(gulp.dest(dest + 'css'));

});

gulp.task('scripts', function() {

  return gulp.src([
      bower + 'bootstrap-sass-official/assets/javascripts/bootstrap.js',
      bower + 'fitvids/jquery.fitvids.js',
      src + 'js/plugins/*.js',
      src + 'js/main.js'
    ])
    .pipe(uglify('main.js', {
      outSourceMap: true,
      basePath: 'build/js'
    }))
    .pipe(gulp.dest(dest + 'js'));

});

gulp.task('admin-scripts', function() {

  return gulp.src([
      src + 'js/admin.js'
    ])
    .pipe(uglify('admin.js', {
      outSourceMap: true,
      basePath: 'build/js'
    }))
    .pipe(gulp.dest(dest + 'js'));

});

gulp.task('lint', function() {

  return gulp.src(src + 'js/*.js')
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'))
    .pipe(jshint.reporter('fail'));
});

gulp.task('images', function() {
  return gulp.src(src + 'img/**/*')
    .pipe(cache(imagemin({
      optimizationLevel: 5,
      progressive: true,
      interlaced: true,
      svgoPlugins: [
        { removeUselessStrokeAndFill: false }
      ]
    })))
    .pipe(gulp.dest(dest + 'img'));
});

gulp.task('fonts', function() {
  return gulp.src(src + 'font/*')
    .pipe(gulp.dest(dest + 'font'));
});

gulp.task('watch', function () {

  gulp.watch(src + 'scss/**/*.scss', ['styles']);
  gulp.watch(src + 'js/**/*.js', ['scripts']);
  gulp.watch(src + 'img/**/*', ['images']);

});

gulp.task('rsync', function() {

  if ( config.servers[deployTarget].user === '' ||
       config.servers[deployTarget].host === '' ) {
    return gutil.log(gutil.colors.red('ERROR:'), 'User, host and path not configured for ' + deployTarget + ' target in config.json');
  }

  rsync({
    ssh: true,
    src: './',
    dest: config.servers[deployTarget].user + '@' + config.servers[deployTarget].host + ':' + config.servers[deployTarget].path,
    recursive: true,
    syncDest: true,
    exclude: ['node_modules', '.sass-cache'],
    args: ['--verbose']
  }, function(error, stdout, stderr, cmd) {
      gutil.log(stdout);
  });

});

gulp.task('clearCache', function() {
  cache.clearAll();
});

gulp.task('deploy', function(cb) {
  sequence('default', 'rsync', cb);
});

gulp.task('default', ['styles', 'scripts', 'admin-scripts', 'images', 'fonts']);
<|endoftext|>


<commit_before>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes, Children } from 'react';
import { findDOMNode } from 'react-dom';
import classnames from 'classnames';
import Props from '../utils/Props';
import Box from './Box';
import Button from './Button';
import SpinningIcon from './icons/Spinning';
import Scroll from '../utils/Scroll';
import InfiniteScroll from '../utils/InfiniteScroll';
import Selection from '../utils/Selection';

import LinkPreviousIcon from './icons/base/LinkPrevious';
import LinkNextIcon from './icons/base/LinkNext';
import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.TILES;
const TILE = CSSClassnames.TILE;
const SELECTED_CLASS = `${TILE}--selected`;
const MIN_COLUMN_SIZE_LARGE = 384;  // $tile-large-size = 384px
const MIN_COLUMN_SIZE = 192;  // $tile-size = 192px
const MIN_COLUMN_SIZE_SMALL = 92; // $tile-small-size = 92px

export default class Tiles extends Component {

  constructor (props) {
    super(props);
    this._onLeft = this._onLeft.bind(this);
    this._onRight = this._onRight.bind(this);
    this._onScrollHorizontal = this._onScrollHorizontal.bind(this);
    this._onWheel = this._onWheel.bind(this);
    this._onResize = this._onResize.bind(this);
    this._layout = this._layout.bind(this);
    this._onClick = this._onClick.bind(this);

    const tileSize = this.props.size;
    const minColumnWidth = (tileSize === 'small') ? MIN_COLUMN_SIZE_SMALL :
      ((tileSize === 'large') ? MIN_COLUMN_SIZE_LARGE : MIN_COLUMN_SIZE);
    this._minColumnWidths = Array.apply(null, Array(this.props.numColumns))
      .map((currentNumColumns, index) => (index + 1) * minColumnWidth);

    this.state = {
      overflow: false,
      selected: Selection.normalizeIndexes(props.selected),
      numColumns: this.props.numColumns
    };
  }

  componentDidMount () {
    this._setSelection();
    if (this.props.onMore) {
      this._scroll = InfiniteScroll.startListeningForScroll(this.refs.more, this.props.onMore);
    }
    if ('row' === this.props.direction) {
      window.addEventListener('resize', this._onResize);
      document.addEventListener('wheel', this._onWheel);
      this._trackHorizontalScroll();
      // give browser a chance to stabilize
      setTimeout(this._layout, 10);
    } else if (this.props.masonry) {
      window.addEventListener('resize', this._onResize);
      setTimeout(this._layout, 10);
    }
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.selected) {
      this.setState({ selected: Selection.normalizeIndexes(nextProps.selected) });
    }
    if (this._scroll) {
      InfiniteScroll.stopListeningForScroll(this._scroll);
      this._scroll = null;
    }
  }

  componentDidUpdate (prevProps, prevState) {
    if (JSON.stringify(this.state.selected) !== JSON.stringify(prevState.selected)) {
      this._setSelection();
    }
    if (this.props.onMore && !this._scroll) {
      this._scroll = InfiniteScroll.startListeningForScroll(this.refs.more, this.props.onMore);
    }
    if ('row' === this.props.direction) {
      this._trackHorizontalScroll();
    }
  }

  componentWillUnmount () {
    if (this._scroll) {
      InfiniteScroll.stopListeningForScroll(this._scroll);
    }
    if ('row' === this.props.direction) {
      window.removeEventListener('resize', this._onResize);
      document.removeEventListener('wheel', this._onWheel);
      if (this._tracking) {
        const tiles = findDOMNode(this.refs.tiles);
        tiles.removeEventListener('scroll', this._onScrollHorizontal);
      }
    } else if (this.props.masonry) {
      window.removeEventListener('resize', this._onResize);
    }
  }

  _onLeft () {
    const tiles = findDOMNode(this.refs.tiles);
    Scroll.scrollBy(tiles, 'scrollLeft', - tiles.offsetWidth);
  }

  _onRight () {
    const tiles = findDOMNode(this.refs.tiles);
    Scroll.scrollBy(tiles, 'scrollLeft', tiles.offsetWidth);
  }

  _onScrollHorizontal () {
    // debounce
    clearTimeout(this._scrollTimer);
    this._scrollTimer = setTimeout(this._layout, 50);
  }

  _onWheel (event) {
    if (Math.abs(event.deltaX) > 100) {
      clearInterval(this._scrollTimer);
    } else if (event.deltaX > 5) {
      this._onRight();
    } else if (event.deltaX < -5) {
      this._onLeft();
    }
  }

  _getNumberColumns () {
    const tiles = findDOMNode(this.refs.tiles);

    const maxColumnWidthIndex = this._minColumnWidths
      .filter((currentMin) => {
        return currentMin <= tiles.offsetWidth;
      })
      .reduce((maxIndex, currentMin, index, columnWidths) => {
        return (currentMin > columnWidths[maxIndex]) ? index : maxIndex;
      }, 0);

    return maxColumnWidthIndex + 1; // return appropriate number of columns
  }

  _layout () {
    const { direction, masonry } = this.props;
    if ('row' === direction) {
      // determine if we have more tiles than room to fit
      const tiles = findDOMNode(this.refs.tiles);
      // 20 is to allow some fuzziness as scrollbars come and go
      this.setState({
        overflow: (tiles.scrollWidth > (tiles.offsetWidth + 20)),
        overflowStart: (tiles.scrollLeft <= 20),
        overflowEnd: (tiles.scrollLeft >= (tiles.scrollWidth - tiles.offsetWidth))
      });

      // mark any tiles that might be clipped
      const rect = tiles.getBoundingClientRect();
      const children = tiles.querySelectorAll(`.${TILE}`);

      children.map((child, index) => {
        const childRect = child.getBoundingClientRect();
        // 12 accounts for padding
        if ((childRect.left + 12) < rect.left ||
          (childRect.right - 12) > rect.right) {
          child.classList.add(`${TILE}--eclipsed`);
        } else {
          child.classList.remove(`${TILE}--eclipsed`);
        }
      });
    } else if (masonry) {
      // check for appropriate number of columns, if using masonry option
      const { numColumns } = this.state;
      const newNumColumns = this._getNumberColumns();
      if (numColumns !== newNumColumns) {
        this.setState({ numColumns: newNumColumns });
      }
    }
  }

  _renderChild (element) {
    const { flush } = this.props;

    if (element) {
      const elementClone = React.cloneElement(element, {
        hoverBorder: !flush
      });

      return elementClone;
    }

    return undefined;
  }

  _renderMasonryColumns () {
    const { children } = this.props;
    const { numColumns } = this.state;
    let columnContents = {};

    Children.map(children, (element, index) => {
      let currentColumn = index % numColumns;

      if (!columnContents[`column-${currentColumn}`]) {
        columnContents[`column-${currentColumn}`] = [];
      }

      // place children into appropriate column
      let child = this._renderChild(element);
      if (child) {
        columnContents[`column-${currentColumn}`].push(child);
      }
    }, this);

    const columnsArray = Array.apply(null, Array(numColumns));
    let columns = columnsArray.map((current, i) => {
      return (
        <Box className={`${CLASS_ROOT}__masonry-column`} key={`column-${numColumns}-${i}`}>
          {columnContents[`column-${i}`]}
        </Box>
      );
    });

    return columns;
  }

  _onResize () {
    // debounce
    clearTimeout(this._resizeTimer);
    this._resizeTimer = setTimeout(this._layout, 50);
  }

  _trackHorizontalScroll () {
    if (this.state.overflow && ! this._tracking) {
      const tiles = findDOMNode(this.refs.tiles);
      tiles.addEventListener('scroll', this._onScrollHorizontal);
      this._tracking = true;
    }
  }

  _setSelection () {
    Selection.setClassFromIndexes({
      containerElement: findDOMNode(this.refs.tiles),
      childSelector: `.${TILE}`,
      selectedClass: SELECTED_CLASS,
      selectedIndexes: this.state.selected
    });
  }

  _onClick (event) {
    let selected = Selection.onClick(event, {
      containerElement: findDOMNode(this.refs.tiles),
      childSelector: `.${TILE}`,
      selectedClass: SELECTED_CLASS,
      multiSelect: ('multiple' === this.props.selectable),
      priorSelectedIndexes: this.state.selected
    });
    // only set the selected state and classes if the caller isn't managing it.
    if (! this.props.selected) {
      this.setState({ selected: selected }, this._setSelection);
    }

    if (this.props.onSelect) {
      // notify caller that the selection has changed
      if (selected.length === 1) {
        selected = selected[0];
      }
      this.props.onSelect(selected);
    }
  }

  // children should be an array of Tile
  render () {
    const { onMore, selectable, masonry, direction } = this.props;
    const { overflow } = this.state;

    const classes = classnames(
      CLASS_ROOT,
      this.props.className,
      {
        [`${CLASS_ROOT}--fill`]: this.props.fill,
        [`${CLASS_ROOT}--flush`]: this.props.flush,
        [`${CLASS_ROOT}--${this.props.size}`]: this.props.size,
        [`${CLASS_ROOT}--selectable`]: this.props.selectable,
        [`${CLASS_ROOT}--moreable`]: this.props.onMore,
        [`${CLASS_ROOT}--overflowed`]: this.state.overflow,
        [`${CLASS_ROOT}--masonry`]: this.props.masonry
      }
    );

    const other = Props.pick(this.props, Object.keys(Box.propTypes));

    let more = null;
    if (onMore) {
      more = (
        <div ref="more" className={`${CLASS_ROOT}__more`}>
          <SpinningIcon />

      );
    }

    let onClickHandler;
    if (selectable) {
      onClickHandler = this._onClick;
    }

    let children = this.props.children;
    if (masonry) {
      children = this._renderMasonryColumns();
    } else {
      children = Children.map(this.props.children, (element) => {
        return this._renderChild(element);
      }, this);
    }

    let contents = (
      <Box ref="tiles" {...other}
        wrap={direction ? false : true}
        direction={direction ? direction : 'row'}
        className={classes}
        onClick={onClickHandler}
        focusable={false}>
        {children}
        {more}
      </Box>
    );

    if (overflow) {
      let left;
      let right;

      if (! this.state.overflowStart) {
        left = (
          <Button className={`${CLASS_ROOT}__left`} icon={<LinkPreviousIcon />}
            onClick={this._onLeft} />
        );
      }
      if (! this.state.overflowEnd) {
        right = (
          <Button className={`${CLASS_ROOT}__right`} icon={<LinkNextIcon />}
            onClick={this._onRight} />
        );
      }

      contents = (
        <div className={`${CLASS_ROOT}__container`}>
          {left}
          {contents}
          {right}

      );
    }

    return contents;
  }

}

Tiles.propTypes = {
  fill: PropTypes.bool,
  flush: PropTypes.bool,
  onMore: PropTypes.func,
  onSelect: PropTypes.func,
  selectable: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.oneOf(['multiple'])
  ]),
  selected: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.arrayOf(PropTypes.number)
  ]),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  numColumns: PropTypes.number,
  masonry: PropTypes.bool,
  ...Box.propTypes
};

Tiles.defaultProps = {
  flush: true,
  justify: 'start',
  pad: 'small',
  numColumns: 1
};
<commit_msg>only check offsetWidth when this.refs.tiles is defined<commit_after>// (C) Copyright 2014-2015 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes, Children } from 'react';
import { findDOMNode } from 'react-dom';
import classnames from 'classnames';
import Props from '../utils/Props';
import Box from './Box';
import Button from './Button';
import SpinningIcon from './icons/Spinning';
import Scroll from '../utils/Scroll';
import InfiniteScroll from '../utils/InfiniteScroll';
import Selection from '../utils/Selection';

import LinkPreviousIcon from './icons/base/LinkPrevious';
import LinkNextIcon from './icons/base/LinkNext';
import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.TILES;
const TILE = CSSClassnames.TILE;
const SELECTED_CLASS = `${TILE}--selected`;
const MIN_COLUMN_SIZE_LARGE = 384;  // $tile-large-size = 384px
const MIN_COLUMN_SIZE = 192;  // $tile-size = 192px
const MIN_COLUMN_SIZE_SMALL = 92; // $tile-small-size = 92px

export default class Tiles extends Component {

  constructor (props) {
    super(props);
    this._onLeft = this._onLeft.bind(this);
    this._onRight = this._onRight.bind(this);
    this._onScrollHorizontal = this._onScrollHorizontal.bind(this);
    this._onWheel = this._onWheel.bind(this);
    this._onResize = this._onResize.bind(this);
    this._layout = this._layout.bind(this);
    this._onClick = this._onClick.bind(this);

    const tileSize = this.props.size;
    const minColumnWidth = (tileSize === 'small') ? MIN_COLUMN_SIZE_SMALL :
      ((tileSize === 'large') ? MIN_COLUMN_SIZE_LARGE : MIN_COLUMN_SIZE);
    this._minColumnWidths = Array.apply(null, Array(this.props.numColumns))
      .map((currentNumColumns, index) => (index + 1) * minColumnWidth);

    this.state = {
      overflow: false,
      selected: Selection.normalizeIndexes(props.selected),
      numColumns: this.props.numColumns
    };
  }

  componentDidMount () {
    this._setSelection();
    if (this.props.onMore) {
      this._scroll = InfiniteScroll.startListeningForScroll(this.refs.more, this.props.onMore);
    }
    if ('row' === this.props.direction) {
      window.addEventListener('resize', this._onResize);
      document.addEventListener('wheel', this._onWheel);
      this._trackHorizontalScroll();
      // give browser a chance to stabilize
      setTimeout(this._layout, 10);
    } else if (this.props.masonry) {
      window.addEventListener('resize', this._onResize);
      setTimeout(this._layout, 10);
    }
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.selected) {
      this.setState({ selected: Selection.normalizeIndexes(nextProps.selected) });
    }
    if (this._scroll) {
      InfiniteScroll.stopListeningForScroll(this._scroll);
      this._scroll = null;
    }
  }

  componentDidUpdate (prevProps, prevState) {
    if (JSON.stringify(this.state.selected) !== JSON.stringify(prevState.selected)) {
      this._setSelection();
    }
    if (this.props.onMore && !this._scroll) {
      this._scroll = InfiniteScroll.startListeningForScroll(this.refs.more, this.props.onMore);
    }
    if ('row' === this.props.direction) {
      this._trackHorizontalScroll();
    }
  }

  componentWillUnmount () {
    if (this._scroll) {
      InfiniteScroll.stopListeningForScroll(this._scroll);
    }
    if ('row' === this.props.direction) {
      window.removeEventListener('resize', this._onResize);
      document.removeEventListener('wheel', this._onWheel);
      if (this._tracking) {
        const tiles = findDOMNode(this.refs.tiles);
        tiles.removeEventListener('scroll', this._onScrollHorizontal);
      }
    } else if (this.props.masonry) {
      window.removeEventListener('resize', this._onResize);
    }
  }

  _onLeft () {
    const tiles = findDOMNode(this.refs.tiles);
    Scroll.scrollBy(tiles, 'scrollLeft', - tiles.offsetWidth);
  }

  _onRight () {
    const tiles = findDOMNode(this.refs.tiles);
    Scroll.scrollBy(tiles, 'scrollLeft', tiles.offsetWidth);
  }

  _onScrollHorizontal () {
    // debounce
    clearTimeout(this._scrollTimer);
    this._scrollTimer = setTimeout(this._layout, 50);
  }

  _onWheel (event) {
    if (Math.abs(event.deltaX) > 100) {
      clearInterval(this._scrollTimer);
    } else if (event.deltaX > 5) {
      this._onRight();
    } else if (event.deltaX < -5) {
      this._onLeft();
    }
  }

  _getNumberColumns () {
    const tiles = findDOMNode(this.refs.tiles);
    let maxColumnWidthIndex;

    if (tiles) {
      maxColumnWidthIndex = this._minColumnWidths
        .filter((currentMin) => {
          return currentMin <= tiles.offsetWidth;
        })
        .reduce((maxIndex, currentMin, index, columnWidths) => {
          return (currentMin > columnWidths[maxIndex]) ? index : maxIndex;
        }, 0);

      maxColumnWidthIndex += 1; // return appropriate number of columns
    }

    return maxColumnWidthIndex;
  }

  _layout () {
    const { direction, masonry } = this.props;
    if ('row' === direction) {
      // determine if we have more tiles than room to fit
      const tiles = findDOMNode(this.refs.tiles);
      // 20 is to allow some fuzziness as scrollbars come and go
      this.setState({
        overflow: (tiles.scrollWidth > (tiles.offsetWidth + 20)),
        overflowStart: (tiles.scrollLeft <= 20),
        overflowEnd: (tiles.scrollLeft >= (tiles.scrollWidth - tiles.offsetWidth))
      });

      // mark any tiles that might be clipped
      const rect = tiles.getBoundingClientRect();
      const children = tiles.querySelectorAll(`.${TILE}`);

      children.map((child, index) => {
        const childRect = child.getBoundingClientRect();
        // 12 accounts for padding
        if ((childRect.left + 12) < rect.left ||
          (childRect.right - 12) > rect.right) {
          child.classList.add(`${TILE}--eclipsed`);
        } else {
          child.classList.remove(`${TILE}--eclipsed`);
        }
      });
    } else if (masonry) {
      // check for appropriate number of columns, if using masonry option
      const { numColumns } = this.state;
      const newNumColumns = this._getNumberColumns();
      if (newNumColumns && (numColumns !== newNumColumns)) {
        this.setState({ numColumns: newNumColumns });
      }
    }
  }

  _renderChild (element) {
    const { flush } = this.props;

    if (element) {
      const elementClone = React.cloneElement(element, {
        hoverBorder: !flush
      });

      return elementClone;
    }

    return undefined;
  }

  _renderMasonryColumns () {
    const { children } = this.props;
    const { numColumns } = this.state;
    let columnContents = {};

    Children.map(children, (element, index) => {
      let currentColumn = index % numColumns;

      if (!columnContents[`column-${currentColumn}`]) {
        columnContents[`column-${currentColumn}`] = [];
      }

      // place children into appropriate column
      let child = this._renderChild(element);
      if (child) {
        columnContents[`column-${currentColumn}`].push(child);
      }
    }, this);

    const columnsArray = Array.apply(null, Array(numColumns));
    let columns = columnsArray.map((current, i) => {
      return (
        <Box className={`${CLASS_ROOT}__masonry-column`} key={`column-${numColumns}-${i}`}>
          {columnContents[`column-${i}`]}
        </Box>
      );
    });

    return columns;
  }

  _onResize () {
    // debounce
    clearTimeout(this._resizeTimer);
    this._resizeTimer = setTimeout(this._layout, 50);
  }

  _trackHorizontalScroll () {
    if (this.state.overflow && ! this._tracking) {
      const tiles = findDOMNode(this.refs.tiles);
      tiles.addEventListener('scroll', this._onScrollHorizontal);
      this._tracking = true;
    }
  }

  _setSelection () {
    Selection.setClassFromIndexes({
      containerElement: findDOMNode(this.refs.tiles),
      childSelector: `.${TILE}`,
      selectedClass: SELECTED_CLASS,
      selectedIndexes: this.state.selected
    });
  }

  _onClick (event) {
    let selected = Selection.onClick(event, {
      containerElement: findDOMNode(this.refs.tiles),
      childSelector: `.${TILE}`,
      selectedClass: SELECTED_CLASS,
      multiSelect: ('multiple' === this.props.selectable),
      priorSelectedIndexes: this.state.selected
    });
    // only set the selected state and classes if the caller isn't managing it.
    if (! this.props.selected) {
      this.setState({ selected: selected }, this._setSelection);
    }

    if (this.props.onSelect) {
      // notify caller that the selection has changed
      if (selected.length === 1) {
        selected = selected[0];
      }
      this.props.onSelect(selected);
    }
  }

  // children should be an array of Tile
  render () {
    const { onMore, selectable, masonry, direction } = this.props;
    const { overflow } = this.state;

    const classes = classnames(
      CLASS_ROOT,
      this.props.className,
      {
        [`${CLASS_ROOT}--fill`]: this.props.fill,
        [`${CLASS_ROOT}--flush`]: this.props.flush,
        [`${CLASS_ROOT}--${this.props.size}`]: this.props.size,
        [`${CLASS_ROOT}--selectable`]: this.props.selectable,
        [`${CLASS_ROOT}--moreable`]: this.props.onMore,
        [`${CLASS_ROOT}--overflowed`]: this.state.overflow,
        [`${CLASS_ROOT}--masonry`]: this.props.masonry
      }
    );

    const other = Props.pick(this.props, Object.keys(Box.propTypes));

    let more = null;
    if (onMore) {
      more = (
        <div ref="more" className={`${CLASS_ROOT}__more`}>
          <SpinningIcon />

      );
    }

    let onClickHandler;
    if (selectable) {
      onClickHandler = this._onClick;
    }

    let children = this.props.children;
    if (masonry) {
      children = this._renderMasonryColumns();
    } else {
      children = Children.map(this.props.children, (element) => {
        return this._renderChild(element);
      }, this);
    }

    let contents = (
      <Box ref="tiles" {...other}
        wrap={direction ? false : true}
        direction={direction ? direction : 'row'}
        className={classes}
        onClick={onClickHandler}
        focusable={false}>
        {children}
        {more}
      </Box>
    );

    if (overflow) {
      let left;
      let right;

      if (! this.state.overflowStart) {
        left = (
          <Button className={`${CLASS_ROOT}__left`} icon={<LinkPreviousIcon />}
            onClick={this._onLeft} />
        );
      }
      if (! this.state.overflowEnd) {
        right = (
          <Button className={`${CLASS_ROOT}__right`} icon={<LinkNextIcon />}
            onClick={this._onRight} />
        );
      }

      contents = (
        <div className={`${CLASS_ROOT}__container`}>
          {left}
          {contents}
          {right}

      );
    }

    return contents;
  }

}

Tiles.propTypes = {
  fill: PropTypes.bool,
  flush: PropTypes.bool,
  onMore: PropTypes.func,
  onSelect: PropTypes.func,
  selectable: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.oneOf(['multiple'])
  ]),
  selected: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.arrayOf(PropTypes.number)
  ]),
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  numColumns: PropTypes.number,
  masonry: PropTypes.bool,
  ...Box.propTypes
};

Tiles.defaultProps = {
  flush: true,
  justify: 'start',
  pad: 'small',
  numColumns: 1
};
<|endoftext|>