<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-tab sim, it shows the name of the sim at the left and the PhET Logo and options menu at the right.
 * For a multi-tab sim, it shows icons for all of the other tabs, with the tab name at the left and the PhET Logo and options menu at the right.
 *
 * @author Sam Reid
 */
define( function( require ) {
  "use strict";

  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var PanelNode = require( 'SUN/PanelNode' );
  var BoundsNode = require( 'SUN/BoundsNode' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var inherit = require( 'PHET_CORE/inherit' );
  var SimPopupMenu = require( 'JOIST/SimPopupMenu' );
  var Font = require( 'SCENERY/util/Font' );
  var Shape = require( 'KITE/Shape' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );

  var createHighlight = function( width, height ) {
    var leftBar = new Path( {shape: Shape.lineSegment( 0, 0, 0, height ), lineWidth: 1, stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, 'black' ).addColorStop( 0.5, 'white' ).addColorStop( 1, 'black' ) } );
    var rightBar = new Path( {shape: Shape.lineSegment( width, 0, width, height ), lineWidth: 1, stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, 'black' ).addColorStop( 0.5, 'white' ).addColorStop( 1, 'black' ) } );
    return new Node( {children: [leftBar, rightBar], visible: false} );
  };

  var createHighlightListener = function( node ) {
    return {//Highlight a button when mousing over it
      over: function( event ) { if ( event.pointer.isMouse ) {node.visible = true;} },
      out: function( event ) { if ( event.pointer.isMouse ) {node.visible = false;} }
    };
  };

  /**
   * Create a nav bar.  Layout assumes all of the tab widths are the same.
   * @param sim
   * @param tabs
   * @param model
   * @constructor
   */
  function NavigationBar( sim, tabs, model ) {
    var navigationBar = this;
    this.tabs = tabs;

    this.navBarHeight = 40;
    this.navBarScale = 1;
    this.navBarWidth = 768;

    Node.call( this, {renderer: 'svg'} );
    this.background = new Rectangle( 0, 0, 0, 0, {fill: 'black'} );
    this.addChild( this.background );

    var fontSize = 36;

    var phetLabel = new Text( "PhET", {fontSize: fontSize, fill: 'yellow'} );
    var optionsButton = new BoundsNode( new FontAwesomeNode( 'reorder', {fill: '#fff'} ), {cursor: 'pointer'} );

    var optionsHighlight = createHighlight( optionsButton.width + 6, optionsButton.height + 5 );
    optionsHighlight.bottom = optionsButton.bottom + 3;
    optionsHighlight.x = -3;
    optionsButton.addChild( optionsHighlight );

    //Creating the popup menu dynamically (when needed) causes a temporary black screen on the iPad (perhaps because of canvas accurate text bounds)
    var simPopupMenu = new SimPopupMenu( sim );
    var optionButtonPressed = function() {
      simPopupMenu.x = navigationBar.navBarWidth - simPopupMenu.width - 2;
      simPopupMenu.y = window.innerHeight - simPopupMenu.height - navigationBar.height / 2 + 4;
      var overlayScene = sim.createAndAddOverlay( simPopupMenu );
      overlayScene.addInputListener( {down: function() {
        sim.removeOverlay( overlayScene );
      }} );
    };

    // mousedown or touchstart (pointer pressed down over the node)
    optionsButton.addPeer( '<input type="button">', {click: optionButtonPressed, tabIndex: 101} );
    optionsButton.addInputListener( { down: optionButtonPressed} );
    optionsButton.addInputListener( createHighlightListener( optionsHighlight ) );

    this.phetLabelAndButton = new HBox( {spacing: 10, children: [phetLabel, optionsButton]} );
    this.addChild( this.phetLabelAndButton );

    this.titleLabel = new Text( sim.name, {fontSize: 18, fill: 'white'} );

    //Create the nodes to be used for the tab icons
    var index = 0;

    var selectedFont = new Font( { size: 10, weight: 'bold'} );
    var normalFont = new Font( { size: 10 } );

    this.addChild( this.titleLabel );

    if ( tabs.length > 1 ) {

      var iconAndTextArray = _.map( tabs, function( tab ) {
        var icon = new Node( {children: [tab.icon], scale: 25 / tab.icon.height} );
        var text = new Text( tab.name, { fill: 'white', visible: true} );

        //Put a panel around it to extend it horizontally so there is some distance from the highlight region to the text and some distance between adjacent texts.
        var textPanel = new PanelNode( text, {
          fill: null,
          stroke: null,
          lineWidth: 1, // width of the background border
          xMargin: 4,
          yMargin: 0,
          cornerRadius: 0, // radius of the rounded corners on the background
          resize: false // dynamically resize when content bounds change?
        } );
        var iconAndText = new VBox( {children: [icon, textPanel]} );
        iconAndText.icon = icon;
        iconAndText.text = text;
        iconAndText.textPanel = textPanel;
        iconAndText.index = index++;
        iconAndText.tab = tab;

        //On initialization and when the tab changes, update the size of the icons and the layout of the icons and text
        model.tabIndexProperty.link( function( tabIndex ) {
          var selected = iconAndText.index === tabIndex;
          iconAndText.text.fill = selected ? 'yellow' : 'white';
          iconAndText.text.font = selected ? selectedFont : normalFont;
          iconAndText.opacity = selected ? 1.0 : 0.5;
        } );

        return iconAndText;
      } );

      //Make all of the icons the same size so they will have equal hit areas and equal spacing
      var maxWidth = _.max( iconAndTextArray,function( iconAndText ) {return iconAndText.width;} ).width;
      var maxHeight = _.max( iconAndTextArray,function( iconAndText ) {return iconAndText.height;} ).height;

      this.buttonArray = iconAndTextArray.map( function( iconAndText ) {
        //Background area for layout and hit region
        var rectangle = new Rectangle( 0, 0, maxWidth, maxHeight );
        var highlight = createHighlight( maxWidth, maxHeight );
        iconAndText.centerX = maxWidth / 2;
        iconAndText.top = 0;
        var button = new Node( {children: [ rectangle, highlight, iconAndText]} );

        //In the nav bar, don't show highlight or cursor pointer when over a tab icon that is already selected
        model.tabIndexProperty.link( function( tabIndex ) { button.cursor = tabIndex === iconAndText.index ? 'default' : 'pointer'; } );

        var pressListener = function() {
          model.tabIndex = iconAndText.index;
          model.showHomeScreen = false;
        };
        button.addInputListener( {
          down: pressListener,

          //Highlight a button when mousing over it
          over: function( event ) {
            if ( event.pointer.isMouse ) {
              highlight.visible = model.tabIndex !== iconAndText.index;
            }
          },
          out: function( event ) {
            if ( event.pointer.isMouse ) {
              highlight.visible = false;
            }
          }
        } );

        button.addPeer( '<input type="button">', {click: pressListener, tabIndex: 99} );
        return  button;
      } );

      //TODO: This spacing is not always necessary, it depends on the relative width
      //TODO:   of the icon vs the text, if all of the buttons have the same dimensions.
      //TODO:   Currently this solves the simple case where all of the text is shorter than all of the icons (like in Build an Atom)
      var maxIconWidth = _.max( iconAndTextArray,function( i ) {return i.icon.width;} ).icon.width;
      var maxButtonWidth = _.max( iconAndTextArray,function( i ) {return i.width;} ).width;

      this.buttonHBox = new HBox( {spacing: maxIconWidth === maxButtonWidth ? 20 : 0, children: this.buttonArray} );
      this.addChild( this.buttonHBox );

      //add the home icon
      this.homeIcon = new BoundsNode( new FontAwesomeNode( 'home', {fill: '#fff'} ), {cursor: 'pointer'} );
      var homeHighlight = createHighlight( this.homeIcon.width + 6, this.homeIcon.height + 5 );
      homeHighlight.bottom = this.homeIcon.bottom + 3;
      homeHighlight.x = -3;
      this.homeIcon.addChild( homeHighlight );
      this.homeIcon.addInputListener( { down: function() { model.showHomeScreen = true; }} );
      this.homeIcon.addInputListener( createHighlightListener( homeHighlight ) );
      this.homeIcon.addPeer( '<input type="button">', {click: function() {model.showHomeScreen = true;}, tabIndex: 100} );
      this.addChild( this.homeIcon );
    }
  }

  inherit( Node, NavigationBar, {relayout: function() {
    var navigationBar = this;
    navigationBar.background.rectHeight = this.navBarHeight;
    navigationBar.background.rectWidth = this.navBarWidth;
    var tabIndex = navigationBar.tabIndex;

    if ( this.buttonHBox ) {
      this.buttonHBox.setScaleMagnitude( navigationBar.navBarScale );
    }

    this.titleLabel.setScaleMagnitude( this.navBarScale );
    this.titleLabel.centerY = this.navBarHeight / 2;
    this.titleLabel.left = 10;

    //Lay out the components from left to right
    if ( this.tabs.length !== 1 ) {

      //put the center right in the middle
      this.buttonHBox.centerX = this.navBarWidth / 2;
      this.buttonHBox.top = 2;

      navigationBar.homeIcon.setScaleMagnitude( this.navBarScale );
      navigationBar.homeIcon.bottom = this.navBarHeight;
      navigationBar.homeIcon.left = navigationBar.buttonHBox.right + 15;
    }
    this.phetLabelAndButton.setScaleMagnitude( this.navBarScale );
    this.phetLabelAndButton.right = this.navBarWidth - 5;
    this.phetLabelAndButton.centerY = this.navBarHeight / 2;
  },
    layout: function( scale, width, height, windowHeight ) {
      this.navBarScale = scale;
      this.navBarWidth = width;
      this.navBarHeight = height;
      this.relayout();
    }} );

  return NavigationBar;
} );
<commit_msg>In the nav bar, highlight but no hand when mousing over current tab<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-tab sim, it shows the name of the sim at the left and the PhET Logo and options menu at the right.
 * For a multi-tab sim, it shows icons for all of the other tabs, with the tab name at the left and the PhET Logo and options menu at the right.
 *
 * @author Sam Reid
 */
define( function( require ) {
  "use strict";

  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var FontAwesomeNode = require( 'SUN/FontAwesomeNode' );
  var PanelNode = require( 'SUN/PanelNode' );
  var BoundsNode = require( 'SUN/BoundsNode' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var inherit = require( 'PHET_CORE/inherit' );
  var SimPopupMenu = require( 'JOIST/SimPopupMenu' );
  var Font = require( 'SCENERY/util/Font' );
  var Shape = require( 'KITE/Shape' );
  var LinearGradient = require( 'SCENERY/util/LinearGradient' );

  var createHighlight = function( width, height ) {
    var leftBar = new Path( {shape: Shape.lineSegment( 0, 0, 0, height ), lineWidth: 1, stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, 'black' ).addColorStop( 0.5, 'white' ).addColorStop( 1, 'black' ) } );
    var rightBar = new Path( {shape: Shape.lineSegment( width, 0, width, height ), lineWidth: 1, stroke: new LinearGradient( 0, 0, 0, height ).addColorStop( 0, 'black' ).addColorStop( 0.5, 'white' ).addColorStop( 1, 'black' ) } );
    return new Node( {children: [leftBar, rightBar], visible: false} );
  };

  var createHighlightListener = function( node ) {
    return {//Highlight a button when mousing over it
      over: function( event ) { if ( event.pointer.isMouse ) {node.visible = true;} },
      out: function( event ) { if ( event.pointer.isMouse ) {node.visible = false;} }
    };
  };

  /**
   * Create a nav bar.  Layout assumes all of the tab widths are the same.
   * @param sim
   * @param tabs
   * @param model
   * @constructor
   */
  function NavigationBar( sim, tabs, model ) {
    var navigationBar = this;
    this.tabs = tabs;

    this.navBarHeight = 40;
    this.navBarScale = 1;
    this.navBarWidth = 768;

    Node.call( this, {renderer: 'svg'} );
    this.background = new Rectangle( 0, 0, 0, 0, {fill: 'black'} );
    this.addChild( this.background );

    var fontSize = 36;

    var phetLabel = new Text( "PhET", {fontSize: fontSize, fill: 'yellow'} );
    var optionsButton = new BoundsNode( new FontAwesomeNode( 'reorder', {fill: '#fff'} ), {cursor: 'pointer'} );

    var optionsHighlight = createHighlight( optionsButton.width + 6, optionsButton.height + 5 );
    optionsHighlight.bottom = optionsButton.bottom + 3;
    optionsHighlight.x = -3;
    optionsButton.addChild( optionsHighlight );

    //Creating the popup menu dynamically (when needed) causes a temporary black screen on the iPad (perhaps because of canvas accurate text bounds)
    var simPopupMenu = new SimPopupMenu( sim );
    var optionButtonPressed = function() {
      simPopupMenu.x = navigationBar.navBarWidth - simPopupMenu.width - 2;
      simPopupMenu.y = window.innerHeight - simPopupMenu.height - navigationBar.height / 2 + 4;
      var overlayScene = sim.createAndAddOverlay( simPopupMenu );
      overlayScene.addInputListener( {down: function() {
        sim.removeOverlay( overlayScene );
      }} );
    };

    // mousedown or touchstart (pointer pressed down over the node)
    optionsButton.addPeer( '<input type="button">', {click: optionButtonPressed, tabIndex: 101} );
    optionsButton.addInputListener( { down: optionButtonPressed} );
    optionsButton.addInputListener( createHighlightListener( optionsHighlight ) );

    this.phetLabelAndButton = new HBox( {spacing: 10, children: [phetLabel, optionsButton]} );
    this.addChild( this.phetLabelAndButton );

    this.titleLabel = new Text( sim.name, {fontSize: 18, fill: 'white'} );

    //Create the nodes to be used for the tab icons
    var index = 0;

    var selectedFont = new Font( { size: 10, weight: 'bold'} );
    var normalFont = new Font( { size: 10 } );

    this.addChild( this.titleLabel );

    if ( tabs.length > 1 ) {

      var iconAndTextArray = _.map( tabs, function( tab ) {
        var icon = new Node( {children: [tab.icon], scale: 25 / tab.icon.height} );
        var text = new Text( tab.name, { fill: 'white', visible: true} );

        //Put a panel around it to extend it horizontally so there is some distance from the highlight region to the text and some distance between adjacent texts.
        var textPanel = new PanelNode( text, {
          fill: null,
          stroke: null,
          lineWidth: 1, // width of the background border
          xMargin: 4,
          yMargin: 0,
          cornerRadius: 0, // radius of the rounded corners on the background
          resize: false // dynamically resize when content bounds change?
        } );
        var iconAndText = new VBox( {children: [icon, textPanel]} );
        iconAndText.icon = icon;
        iconAndText.text = text;
        iconAndText.textPanel = textPanel;
        iconAndText.index = index++;
        iconAndText.tab = tab;

        //On initialization and when the tab changes, update the size of the icons and the layout of the icons and text
        model.tabIndexProperty.link( function( tabIndex ) {
          var selected = iconAndText.index === tabIndex;
          iconAndText.text.fill = selected ? 'yellow' : 'white';
          iconAndText.text.font = selected ? selectedFont : normalFont;
          iconAndText.opacity = selected ? 1.0 : 0.5;
        } );

        return iconAndText;
      } );

      //Make all of the icons the same size so they will have equal hit areas and equal spacing
      var maxWidth = _.max( iconAndTextArray,function( iconAndText ) {return iconAndText.width;} ).width;
      var maxHeight = _.max( iconAndTextArray,function( iconAndText ) {return iconAndText.height;} ).height;

      this.buttonArray = iconAndTextArray.map( function( iconAndText ) {
        //Background area for layout and hit region
        var rectangle = new Rectangle( 0, 0, maxWidth, maxHeight );
        var highlight = createHighlight( maxWidth, maxHeight );
        iconAndText.centerX = maxWidth / 2;
        iconAndText.top = 0;
        var button = new Node( {children: [ rectangle, highlight, iconAndText]} );

        //In the nav bar, don't show highlight or cursor pointer when over a tab icon that is already selected
        model.tabIndexProperty.link( function( tabIndex ) { button.cursor = tabIndex === iconAndText.index ? 'default' : 'pointer'; } );

        var pressListener = function() {
          model.tabIndex = iconAndText.index;
          model.showHomeScreen = false;
        };
        button.addInputListener( {
          down: pressListener,

          //Highlight a button when mousing over it
          over: function( event ) {
            if ( event.pointer.isMouse ) {
              highlight.visible = true;
            }
          },
          out: function( event ) {
            if ( event.pointer.isMouse ) {
              highlight.visible = false;
            }
          }
        } );

        button.addPeer( '<input type="button">', {click: pressListener, tabIndex: 99} );
        return  button;
      } );

      //TODO: This spacing is not always necessary, it depends on the relative width
      //TODO:   of the icon vs the text, if all of the buttons have the same dimensions.
      //TODO:   Currently this solves the simple case where all of the text is shorter than all of the icons (like in Build an Atom)
      var maxIconWidth = _.max( iconAndTextArray,function( i ) {return i.icon.width;} ).icon.width;
      var maxButtonWidth = _.max( iconAndTextArray,function( i ) {return i.width;} ).width;

      this.buttonHBox = new HBox( {spacing: maxIconWidth === maxButtonWidth ? 20 : 0, children: this.buttonArray} );
      this.addChild( this.buttonHBox );

      //add the home icon
      this.homeIcon = new BoundsNode( new FontAwesomeNode( 'home', {fill: '#fff'} ), {cursor: 'pointer'} );
      var homeHighlight = createHighlight( this.homeIcon.width + 6, this.homeIcon.height + 5 );
      homeHighlight.bottom = this.homeIcon.bottom + 3;
      homeHighlight.x = -3;
      this.homeIcon.addChild( homeHighlight );
      this.homeIcon.addInputListener( { down: function() { model.showHomeScreen = true; }} );
      this.homeIcon.addInputListener( createHighlightListener( homeHighlight ) );
      this.homeIcon.addPeer( '<input type="button">', {click: function() {model.showHomeScreen = true;}, tabIndex: 100} );
      this.addChild( this.homeIcon );
    }
  }

  inherit( Node, NavigationBar, {relayout: function() {
    var navigationBar = this;
    navigationBar.background.rectHeight = this.navBarHeight;
    navigationBar.background.rectWidth = this.navBarWidth;
    var tabIndex = navigationBar.tabIndex;

    if ( this.buttonHBox ) {
      this.buttonHBox.setScaleMagnitude( navigationBar.navBarScale );
    }

    this.titleLabel.setScaleMagnitude( this.navBarScale );
    this.titleLabel.centerY = this.navBarHeight / 2;
    this.titleLabel.left = 10;

    //Lay out the components from left to right
    if ( this.tabs.length !== 1 ) {

      //put the center right in the middle
      this.buttonHBox.centerX = this.navBarWidth / 2;
      this.buttonHBox.top = 2;

      navigationBar.homeIcon.setScaleMagnitude( this.navBarScale );
      navigationBar.homeIcon.bottom = this.navBarHeight;
      navigationBar.homeIcon.left = navigationBar.buttonHBox.right + 15;
    }
    this.phetLabelAndButton.setScaleMagnitude( this.navBarScale );
    this.phetLabelAndButton.right = this.navBarWidth - 5;
    this.phetLabelAndButton.centerY = this.navBarHeight / 2;
  },
    layout: function( scale, width, height, windowHeight ) {
      this.navBarScale = scale;
      this.navBarWidth = width;
      this.navBarHeight = height;
      this.relayout();
    }} );

  return NavigationBar;
} );
<|endoftext|>


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

/**
 * Control for changing a Property of type {number}.
 * Consists of a labeled value, slider and arrow buttons.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var Color = require( 'SCENERY/util/Color' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HSlider = require( 'SUN/HSlider' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var NumberDisplay = require( 'SCENERY_PHET/NumberDisplay' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TandemText = require( 'TANDEM/scenery/nodes/TandemText' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // phet-io modules
  var TNumberControl = require( 'ifphetio!PHET_IO/types/scenery-phet/TNumberControl' );

  /**
   * @param {string} title
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberControl( title, numberProperty, numberRange, options ) {

    options = _.extend( {

      startCallback: function() {}, // called when interaction begins
      endCallback: function() {}, // called when interaction ends
      enabledProperty: new Property( true ), // {Property.<boolean>} is this control enabled?
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled

      // title
      titleFont: new PhetFont( 12 ),
      titleMaxWidth: null, // {null|string} maxWidth to use for title, to constrain width for i18n

      // value
      valueFont: new PhetFont( 12 ),
      valueMaxWidth: null, // {null|string} maxWidth to use for value display, to constrain width for i18n
      decimalPlaces: 0,

      // {string} Pattern used to format the value. Must contain '{0}'.
      // If you want units or other verbiage, add them to the pattern, e.g. '{0} L'
      valuePattern: '{0}',

      // arrow buttons
      delta: 1,

      // all HSlider options may be used, these are the ones that NumberControl overrides
      trackSize: new Dimension2( 180, 3 ),
      thumbSize: new Dimension2( 17, 34 ),
      majorTickLength: 20,
      minorTickStroke: 'rgba( 0, 0, 0, 0.3 )',

      // other slider options that are specific to NumberControl
      majorTicks: [], // array of objects with these fields: { value: {number}, label: {Node} }
      minorTickSpacing: 0, // zero indicates no minor ticks

      // A {function} that handles layout of subcomponents.
      // It has signature function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
      // and returns a Node. If you want to customize the layout, use one of the predefined creators
      // (see createLayoutFunction*) or create your own function.
      layoutFunction: NumberControl.createLayoutFunction1(),

      // phet-io
      tandem: null // {Tandem|null}

    }, options );

    // highlight color for thumb defaults to a brighter version of the thumb color
    if ( options.thumbFillEnabled && !options.thumbFillHighlighted ) {
      options.thumbFillHighlighted =  Color.toColor( options.thumbFillEnabled ).brighterColor();
    }

    // constrain the slider value to the provided range and the same delta as the arrow buttons
    options.constrainValue = options.constrainValue || function( value ) {
        var newValue = Util.roundSymmetric( value / options.delta ) * options.delta;
        return numberRange.constrainValue( newValue );
      };

    // validate options
    assert && assert( !options.startDrag, 'use options.startCallback instead of options.startDrag' );
    assert && assert( !options.endDrag, 'use options.endCallback instead of options.endDrag' );
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1, 'invalid disabledOpacity: ' + options.disabledOpacity );
    Tandem.validateOptions( options ); // The tandem is required when brand==='phet-io'

    var self = this;

    var delta = options.delta; // to improve readability

    var titleNode = new TandemText( title, {
      font: options.titleFont,
      maxWidth: options.titleMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'titleNode' )
    } );

    var numberDisplay = new NumberDisplay( numberProperty, numberRange, {
      valuePattern: options.valuePattern,
      font: options.valueFont,
      decimalPlaces: options.decimalPlaces,
      maxWidth: options.valueMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'numberDisplay' )
    } );

    var arrowButtonOptions = {
      delta: options.delta,
      startCallback: options.startCallback,
      endCallback: options.endCallback,
      scale: 0.85
    };

    var leftArrowButton = new ArrowButton( 'left', function() {
      var value = numberProperty.get() - delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.max( value, numberRange.min ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'leftArrowButton' )
    }, arrowButtonOptions ) );

    var rightArrowButton = new ArrowButton( 'right', function() {
      var value = numberProperty.get() + delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.min( value, numberRange.max ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'rightArrowButton' )
    }, arrowButtonOptions ) );

    var arrowEnabledListener = function( value ) {
      leftArrowButton.enabled = ( value > numberRange.min );
      rightArrowButton.enabled = ( value < numberRange.max );
    };
    numberProperty.link( arrowEnabledListener );

    var slider = new HSlider( numberProperty, numberRange, _.extend(

      // prevent supertype options from being passed, see https://github.com/phetsims/scenery-phet/issues/255
      _.omit( options, Node.prototype._mutatorKeys ),
      {
        startDrag: options.startCallback,
        endDrag: options.endCallback,
        tandem: options.tandem && options.tandem.createTandem( 'slider' )
      } ) );

    // major ticks
    var majorTicks = options.majorTicks;
    for ( var i = 0; i < majorTicks.length; i++ ) {
      slider.addMajorTick( majorTicks[ i ].value, majorTicks[ i ].label );
    }

    // minor ticks, exclude values where we already have major ticks
    if ( options.minorTickSpacing > 0 ) {
      for ( var minorTickValue = numberRange.min; minorTickValue <= numberRange.max; ) {
        if ( !_.find( majorTicks, function( majorTick ) { return majorTick.value === minorTickValue; } ) ) {
          slider.addMinorTick( minorTickValue );
        }
        minorTickValue += options.minorTickSpacing;
      }
    }

    options.children = [
      options.layoutFunction( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
    ];
    Node.call( this, options );

    // enabled/disable this control
    this.enabledProperty = options.enabledProperty; // @public
    var enabledObserver = function( enabled ) {
      self.pickable = enabled;
      self.opacity = enabled ? 1.0 : options.disabledOpacity;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledObserver );

    // @private
    this.disposeNumberControl = function() {

      numberDisplay.dispose();
      leftArrowButton.dispose();
      rightArrowButton.dispose();
      slider.dispose();

      numberProperty.unlink( arrowEnabledListener );
      self.enabledProperty.unlink( enabledObserver );

      options.tandem && options.tandem.removeInstance( self );
    };

    options.tandem && options.tandem.addInstance( this, TNumberControl );
  }

  sceneryPhet.register( 'NumberControl', NumberControl );

  return inherit( VBox, NumberControl, {

    // @public
    dispose: function() {
      this.disposeNumberControl();
      VBox.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }

  }, {

    /**
     * Creates a NumberControl with default tick marks for min and max values.
     * @param {string} label
     * @param {Property.<number>} property
     * @param {Range} range
     * @param {Object} [options]
     * @returns {NumberControl}
     * @static
     * @public
     */
    withMinMaxTicks: function( label, property, range, options ) {

      options = _.extend( {
        tickLabelFont: new PhetFont( 12 )
      }, options );

      options.majorTicks = [
        { value: range.min, label: new Text( range.min, { font: options.tickLabelFont } ) },
        { value: range.max, label: new Text( range.max, { font: options.tickLabelFont } ) }
      ];

      return new NumberControl( label, property, range, options );
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title number
     *  < ------|------ >
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    createLayoutFunction1: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        titleXSpacing: 5, // {number} horizontal spacing between title and number
        arrowButtonsXSpacing: 15, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          children: [
            new HBox( {
              spacing: options.titleXSpacing,
              children: [ titleNode, numberDisplay ]
            } ),
            new HBox( {
              spacing: options.arrowButtonsXSpacing,
              resize: false, // prevent slider from causing a resize when thumb is at min or max
              children: [ leftArrowButton, slider, rightArrowButton ]
            } )
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title < number >
     *  ------|------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    createLayoutFunction2: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing in top row
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          resize: false, // prevent slider from causing a resize when thumb is at min or max
          children: [
            new HBox( {
              spacing: options.xSpacing,
              children: [ titleNode, leftArrowButton, numberDisplay, rightArrowButton ]
            } ),
            slider
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title
     *  < number >
     *  -------|-------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    createLayoutFunction3: function( options ) {

      options = _.extend( {
        alignTitle: 'center', // {string} horizontal alignment of title, relative to slider, 'left'|'right'|'center'
        alignNumber: 'center', // {string} horizontal alignment of number display, relative to slider, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          spacing: options.ySpacing,
          resize: false, // prevent slider from causing a resize when thumb is at min or max
          align: options.alignTitle,
          children: [
            titleNode,
            new VBox( {
              spacing: options.ySpacing,
              resize: false, // prevent slider from causing a resize when thumb is at min or max
              align: options.alignNumber,
              children: [
                new HBox( {
                  spacing: options.xSpacing,
                  children: [ leftArrowButton, numberDisplay, rightArrowButton ]
                } ),
                slider
              ]
            } )
          ]
        } );
      };
    }
  } );
} );
<commit_msg>indicate which HSlider options are blacklisted, #255<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Control for changing a Property of type {number}.
 * Consists of a labeled value, slider and arrow buttons.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var ArrowButton = require( 'SUN/buttons/ArrowButton' );
  var Color = require( 'SCENERY/util/Color' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var HSlider = require( 'SUN/HSlider' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Node = require( 'SCENERY/nodes/Node' );
  var NumberDisplay = require( 'SCENERY_PHET/NumberDisplay' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var Property = require( 'AXON/Property' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Tandem = require( 'TANDEM/Tandem' );
  var TandemText = require( 'TANDEM/scenery/nodes/TandemText' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // phet-io modules
  var TNumberControl = require( 'ifphetio!PHET_IO/types/scenery-phet/TNumberControl' );

  /**
   * @param {string} title
   * @param {Property.<number>} numberProperty
   * @param {Range} numberRange
   * @param {Object} [options]
   * @constructor
   */
  function NumberControl( title, numberProperty, numberRange, options ) {

    options = _.extend( {

      startCallback: function() {}, // called when interaction begins
      endCallback: function() {}, // called when interaction ends
      enabledProperty: new Property( true ), // {Property.<boolean>} is this control enabled?
      disabledOpacity: 0.5, // {number} opacity used to make the control look disabled

      // title
      titleFont: new PhetFont( 12 ),
      titleMaxWidth: null, // {null|string} maxWidth to use for title, to constrain width for i18n

      // value
      valueFont: new PhetFont( 12 ),
      valueMaxWidth: null, // {null|string} maxWidth to use for value display, to constrain width for i18n
      decimalPlaces: 0,

      // {string} Pattern used to format the value. Must contain '{0}'.
      // If you want units or other verbiage, add them to the pattern, e.g. '{0} L'
      valuePattern: '{0}',

      // arrow buttons
      delta: 1,

      // With the exception of startDrag and endDrag (use startCallback and endCallback respectively),
      // all HSlider options may be used. These are the ones that NumberControl overrides:
      trackSize: new Dimension2( 180, 3 ),
      thumbSize: new Dimension2( 17, 34 ),
      majorTickLength: 20,
      minorTickStroke: 'rgba( 0, 0, 0, 0.3 )',

      // other slider options that are specific to NumberControl
      majorTicks: [], // array of objects with these fields: { value: {number}, label: {Node} }
      minorTickSpacing: 0, // zero indicates no minor ticks

      // A {function} that handles layout of subcomponents.
      // It has signature function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
      // and returns a Node. If you want to customize the layout, use one of the predefined creators
      // (see createLayoutFunction*) or create your own function.
      layoutFunction: NumberControl.createLayoutFunction1(),

      // phet-io
      tandem: null // {Tandem|null}

    }, options );

    // highlight color for thumb defaults to a brighter version of the thumb color
    if ( options.thumbFillEnabled && !options.thumbFillHighlighted ) {
      options.thumbFillHighlighted =  Color.toColor( options.thumbFillEnabled ).brighterColor();
    }

    // constrain the slider value to the provided range and the same delta as the arrow buttons
    options.constrainValue = options.constrainValue || function( value ) {
        var newValue = Util.roundSymmetric( value / options.delta ) * options.delta;
        return numberRange.constrainValue( newValue );
      };

    // validate options
    assert && assert( !options.startDrag, 'use options.startCallback instead of options.startDrag' );
    assert && assert( !options.endDrag, 'use options.endCallback instead of options.endDrag' );
    assert && assert( options.disabledOpacity > 0 && options.disabledOpacity < 1, 'invalid disabledOpacity: ' + options.disabledOpacity );
    Tandem.validateOptions( options ); // The tandem is required when brand==='phet-io'

    var self = this;

    var delta = options.delta; // to improve readability

    var titleNode = new TandemText( title, {
      font: options.titleFont,
      maxWidth: options.titleMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'titleNode' )
    } );

    var numberDisplay = new NumberDisplay( numberProperty, numberRange, {
      valuePattern: options.valuePattern,
      font: options.valueFont,
      decimalPlaces: options.decimalPlaces,
      maxWidth: options.valueMaxWidth,
      tandem: options.tandem && options.tandem.createTandem( 'numberDisplay' )
    } );

    var arrowButtonOptions = {
      delta: options.delta,
      startCallback: options.startCallback,
      endCallback: options.endCallback,
      scale: 0.85
    };

    var leftArrowButton = new ArrowButton( 'left', function() {
      var value = numberProperty.get() - delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.max( value, numberRange.min ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'leftArrowButton' )
    }, arrowButtonOptions ) );

    var rightArrowButton = new ArrowButton( 'right', function() {
      var value = numberProperty.get() + delta;
      value = Util.roundSymmetric( value / delta ) * delta; // constrain to delta
      value = Math.min( value, numberRange.max ); // constrain to range
      numberProperty.set( value );
    }, _.extend( {
      tandem: options.tandem && options.tandem.createTandem( 'rightArrowButton' )
    }, arrowButtonOptions ) );

    var arrowEnabledListener = function( value ) {
      leftArrowButton.enabled = ( value > numberRange.min );
      rightArrowButton.enabled = ( value < numberRange.max );
    };
    numberProperty.link( arrowEnabledListener );

    var slider = new HSlider( numberProperty, numberRange, _.extend(

      // prevent supertype options from being passed, see https://github.com/phetsims/scenery-phet/issues/255
      _.omit( options, Node.prototype._mutatorKeys ),
      {
        startDrag: options.startCallback,
        endDrag: options.endCallback,
        tandem: options.tandem && options.tandem.createTandem( 'slider' )
      } ) );

    // major ticks
    var majorTicks = options.majorTicks;
    for ( var i = 0; i < majorTicks.length; i++ ) {
      slider.addMajorTick( majorTicks[ i ].value, majorTicks[ i ].label );
    }

    // minor ticks, exclude values where we already have major ticks
    if ( options.minorTickSpacing > 0 ) {
      for ( var minorTickValue = numberRange.min; minorTickValue <= numberRange.max; ) {
        if ( !_.find( majorTicks, function( majorTick ) { return majorTick.value === minorTickValue; } ) ) {
          slider.addMinorTick( minorTickValue );
        }
        minorTickValue += options.minorTickSpacing;
      }
    }

    options.children = [
      options.layoutFunction( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton )
    ];
    Node.call( this, options );

    // enabled/disable this control
    this.enabledProperty = options.enabledProperty; // @public
    var enabledObserver = function( enabled ) {
      self.pickable = enabled;
      self.opacity = enabled ? 1.0 : options.disabledOpacity;
      //TODO if !enabled, cancel any interaction that is in progress, see scenery#218
    };
    this.enabledProperty.link( enabledObserver );

    // @private
    this.disposeNumberControl = function() {

      numberDisplay.dispose();
      leftArrowButton.dispose();
      rightArrowButton.dispose();
      slider.dispose();

      numberProperty.unlink( arrowEnabledListener );
      self.enabledProperty.unlink( enabledObserver );

      options.tandem && options.tandem.removeInstance( self );
    };

    options.tandem && options.tandem.addInstance( this, TNumberControl );
  }

  sceneryPhet.register( 'NumberControl', NumberControl );

  return inherit( VBox, NumberControl, {

    // @public
    dispose: function() {
      this.disposeNumberControl();
      VBox.prototype.dispose.call( this );
    },

    // @public
    setEnabled: function( enabled ) { this.enabledProperty.set( enabled ); },
    set enabled( value ) { this.setEnabled( value ); },

    // @public
    getEnabled: function() { return this.enabledProperty.get(); },
    get enabled() { return this.getEnabled(); }

  }, {

    /**
     * Creates a NumberControl with default tick marks for min and max values.
     * @param {string} label
     * @param {Property.<number>} property
     * @param {Range} range
     * @param {Object} [options]
     * @returns {NumberControl}
     * @static
     * @public
     */
    withMinMaxTicks: function( label, property, range, options ) {

      options = _.extend( {
        tickLabelFont: new PhetFont( 12 )
      }, options );

      options.majorTicks = [
        { value: range.min, label: new Text( range.min, { font: options.tickLabelFont } ) },
        { value: range.max, label: new Text( range.max, { font: options.tickLabelFont } ) }
      ];

      return new NumberControl( label, property, range, options );
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title number
     *  < ------|------ >
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    createLayoutFunction1: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        titleXSpacing: 5, // {number} horizontal spacing between title and number
        arrowButtonsXSpacing: 15, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          children: [
            new HBox( {
              spacing: options.titleXSpacing,
              children: [ titleNode, numberDisplay ]
            } ),
            new HBox( {
              spacing: options.arrowButtonsXSpacing,
              resize: false, // prevent slider from causing a resize when thumb is at min or max
              children: [ leftArrowButton, slider, rightArrowButton ]
            } )
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title < number >
     *  ------|------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    createLayoutFunction2: function( options ) {

      options = _.extend( {
        align: 'center', // {string} horizontal alignment of rows, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing in top row
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          align: options.align,
          spacing: options.ySpacing,
          resize: false, // prevent slider from causing a resize when thumb is at min or max
          children: [
            new HBox( {
              spacing: options.xSpacing,
              children: [ titleNode, leftArrowButton, numberDisplay, rightArrowButton ]
            } ),
            slider
          ]
        } );
      };
    },

    /**
     * Creates one of the pre-defined layout functions that can be used for options.layoutFunction.
     * Arranges subcomponents like this:
     *
     *  title
     *  < number >
     *  -------|-------
     *
     * @param {Object} [options]
     * @returns {function}
     * @public
     * @static
     */
    createLayoutFunction3: function( options ) {

      options = _.extend( {
        alignTitle: 'center', // {string} horizontal alignment of title, relative to slider, 'left'|'right'|'center'
        alignNumber: 'center', // {string} horizontal alignment of number display, relative to slider, 'left'|'right'|'center'
        xSpacing: 5, // {number} horizontal spacing between arrow buttons and slider
        ySpacing: 5 // {number} vertical spacing between rows
      }, options );

      return function( titleNode, numberDisplay, slider, leftArrowButton, rightArrowButton ) {
        return new VBox( {
          spacing: options.ySpacing,
          resize: false, // prevent slider from causing a resize when thumb is at min or max
          align: options.alignTitle,
          children: [
            titleNode,
            new VBox( {
              spacing: options.ySpacing,
              resize: false, // prevent slider from causing a resize when thumb is at min or max
              align: options.alignNumber,
              children: [
                new HBox( {
                  spacing: options.xSpacing,
                  children: [ leftArrowButton, numberDisplay, rightArrowButton ]
                } ),
                slider
              ]
            } )
          ]
        } );
      };
    }
  } );
} );
<|endoftext|>


<commit_before>(function($){
  $.fn.accordionize = function(options) {
    var core = {
      defaultOptions: $.extend({
        tabWidth: 80,
        scroll: {
          timeout: 7000,
          auto: true
        },
        classPrefix: 'accordionized'
      }, options),

      data: {
        containerClassName: '',
        instanceClassName: ''
      },

      parsedDOM: {
        elements: {
          tagName: [],
          src: [],
          alt: [],
          count: 0
        }
      },

      writeContainerNamesData: function($container){
        core.data.containerClassName = core.getContainerClassName($container);
        core.data.instanceClassName = core.getInstanceClassName($container);
      },

      grayscale: {
        createCanvas: function($container) {
          var $images = $container.find('img');

          $images.each(function(){
            var $image = $(this),
              imgWidth = $image.width(),
              imgHeight = $image.height(),
              imgName = $image.attr('alt'),
              $canvas = $('<canvas />')
                .attr({
                  'width': imgWidth,
                  'height': imgHeight,
                  'data-rel': imgName
                })
                .css({
                  position: 'absolute'
                });

            var context = $canvas.get(0).getContext('2d');

            context.drawImage($image.get(0), 0, 0);

            var imageData = context.getImageData(0, 0, imgWidth, imgHeight),
              pix = imageData.data;

            for (var i = 0; i < pix.length; i += 4) {
              var grayscale = pix[i] * .3 + pix[i + 1] * .59 + pix[i + 2] * .11;
              pix[i] = grayscale;
              pix[i + 1] = grayscale;
              pix[i + 2] = grayscale;
            }

            context.putImageData(imageData, 0, 0);
            $canvas.insertAfter($image);
          });
        }
      },

      getContainerClassName: function($container) {
        return $container.attr('class');
      },

      buildInstanceClassName: function($container) {
        var className = {
            prefix: core.defaultOptions.classPrefix,
            id: core.generateInstanceId(),
            postfix: '__' + core.getContainerClassName($container)
          }

        return className.prefix + className.id + className.postfix;
      },

      getInstanceClassName: function($container) {
        return core.buildInstanceClassName($container);
      },

      generateInstanceId: function() {
        return Math.floor(Math.random() * (99999 - 0 + 1)) + 0;
      },

      setLoop: function($container) {
        if(core.defaultOptions.scroll.auto) {
          clearInterval(window.interval);

          window.interval = setInterval(function() {
            var $activeElement = $container.children('.active');
            if($activeElement.is(':last-child')) {
              $container.children('.banner-item:first-child').trigger('mousedown');
            } else {
              $activeElement.next().trigger('mousedown');
            }
          }, core.defaultOptions.scroll.timeout);
        }
      },

      init: function($container, defaultOptions, externalOptions) {
        core.writeContainerNamesData($container);
        core.parse($container);
        core.buildDOM($container);
        core.grayscale.createCanvas($container);
        core.setEvents($container);
        $container.children('.banner-item').first().trigger('mousedown');
      },

      parse: function($container) {
        var images = $container.find('img');

        for (var i = 0; i < images.length; i++) {
          var $element = $(images[i]),
            tagName = $element.prop('tagName'),
            src = $element.attr('src'),
            alt = $element.attr('alt');

          core.parsedDOM.elements.tagName.push(tagName);
          core.parsedDOM.elements.src.push(src);
          core.parsedDOM.elements.alt.push(alt);
          core.parsedDOM.elements.count++;
        }
        images.remove();
      },

      buildDOM: function($container) {
        var builtElements = [];
        for(var i = 0; i < core.parsedDOM.elements.count; i++) {
          var title = core.parsedDOM.elements.alt[i],
            src = core.parsedDOM.elements.src[i],
            tagName = core.parsedDOM.elements.tagName[i],
            $wrapper = $('<div />',{
              'class': 'banner-item',
              'data-title': title
            }),
            $overlay = $('<div />', {
              'class': 'banner-item-overlay',
              'html': title
            }),
            $label = $('<div />', {
              'class': 'banner-item-label',
              'html': title
            }),
            $image = $('<img />', {
              'src': src,
              'alt': title
            });

          $image.css({
            'position': 'absolute',
            'display': 'none',
            'z-index': '1'
          });

          $wrapper
            .append($overlay)
            .append($label)
            .append($image);

          builtElements.push($wrapper);
        }
        $container.append(builtElements);
      },

      setEvents: function($container) {
        var $elements = $container.find('.banner-item'),
          slideWidth = $container.width() - (core.defaultOptions.tabWidth + 1) * (core.parsedDOM.elements.count - 1) - 1;

        $elements.each(function(){
          $(this).on('mousedown', function(){
            var $this = $(this);
            if(!$this.is('.active')) {
              if($container.attr('data-animated') != 'true') {
                $container.attr('data-animated', 'true');
                core.wrap($container, $this.siblings('.active'));
                core.unwrap($container, $this, slideWidth);
                core.setLoop($container);
              }
            } else {
              return false;
            }
          });
        });
      },

      unwrap: function($container, $slide, slideWidth) {
        setTimeout(function(){
          $slide
            .animate({
              'width': slideWidth + 'px'
            }, 400, function(){
              $container.attr('data-animated', 'false');
            })
            .addClass('active');

          $slide
            .find('.banner-item-overlay')
            .fadeOut(400);

          setTimeout(function(){
            $slide
              .find('.banner-item-label')
              .fadeIn(300);
            $slide
              .find('img')
              .fadeIn(300);
          }, 400)
        }, 300);

      },

      wrap: function($container, $slide) {
        setTimeout(function(){
          $slide
            .animate({
              'width': core.defaultOptions.tabWidth + 'px'
            }, 400, function(){
              $container.attr('data-animated', 'false');
            })
            .removeClass('active');

          setTimeout(function(){
            $slide
              .find('img')
              .fadeOut(300);

            $slide
              .find('.banner-item-overlay')
              .fadeIn(400);
          }, 400);

        }, 300);

        $slide
          .find('.banner-item-label')
          .fadeOut(300);
      }
     }

    core.init(this, core.defaultOptions, options);
  }
})(jQuery);
<commit_msg>Update parse method<commit_after>(function($){
  $.fn.accordionize = function(options) {
    function AccordionizeNode(tagName, src, alt) {
      this.tagName = tagName;
      this.src = src;
      this.alt = alt;
    }

    var propTagName = "tagName";
    var attrSrc = "src";
    var attrAlt = "alt";
    var tagImg = "img";

    var core = {
      defaultOptions: $.extend({
        tabWidth: 80,
        scroll: {
          timeout: 7000,
          auto: true
        },
        classPrefix: 'accordionized'
      }, options),

      data: {
        containerClassName: '',
        instanceClassName: ''
      },

      parsedDOM: {
        elements: []
      },

      writeContainerNamesData: function($container){
        core.data.containerClassName = core.getContainerClassName($container);
        core.data.instanceClassName = core.getInstanceClassName($container);
      },

      grayscale: {
        createCanvas: function($container) {
          var $images = $container.find('img');

          $images.each(function(){
            var $image = $(this),
              imgWidth = $image.width(),
              imgHeight = $image.height(),
              imgName = $image.attr('alt'),
              $canvas = $('<canvas />')
                .attr({
                  'width': imgWidth,
                  'height': imgHeight,
                  'data-rel': imgName
                })
                .css({
                  position: 'absolute'
                });

            var context = $canvas.get(0).getContext('2d');

            context.drawImage($image.get(0), 0, 0);

            var imageData = context.getImageData(0, 0, imgWidth, imgHeight),
              pix = imageData.data;

            for (var i = 0; i < pix.length; i += 4) {
              var grayscale = pix[i] * .3 + pix[i + 1] * .59 + pix[i + 2] * .11;
              pix[i] = grayscale;
              pix[i + 1] = grayscale;
              pix[i + 2] = grayscale;
            }

            context.putImageData(imageData, 0, 0);
            $canvas.insertAfter($image);
          });
        }
      },

      getContainerClassName: function($container) {
        return $container.attr('class');
      },

      buildInstanceClassName: function($container) {
        var className = {
            prefix: core.defaultOptions.classPrefix,
            id: core.generateInstanceId(),
            postfix: '__' + core.getContainerClassName($container)
          }

        return className.prefix + className.id + className.postfix;
      },

      getInstanceClassName: function($container) {
        return core.buildInstanceClassName($container);
      },

      generateInstanceId: function() {
        return Math.floor(Math.random() * (99999 - 0 + 1)) + 0;
      },

      setLoop: function($container) {
        if(core.defaultOptions.scroll.auto) {
          clearInterval(window.interval);

          window.interval = setInterval(function() {
            var $activeElement = $container.children('.active');
            if($activeElement.is(':last-child')) {
              $container.children('.banner-item:first-child').trigger('mousedown');
            } else {
              $activeElement.next().trigger('mousedown');
            }
          }, core.defaultOptions.scroll.timeout);
        }
      },

      init: function($container, defaultOptions, externalOptions) {
        core.writeContainerNamesData($container);
        core.findAndConvertBaseNode($container);
        core.buildDOM($container);
        core.grayscale.createCanvas($container);
        core.setEvents($container);
        $container.children('.banner-item').first().trigger('mousedown');
      },

      convertBaseNodeToAccordionizeNode: function(baseNodeArray) {
        for (var index in baseNodeArray) {
          var node = $(baseNodeArray[index]);

          var tagName = node.prop(propTagName);
          var src = node.attr(attrSrc);
          var alt = node.attr(attrAlt);
          var accordionizeNode = new AccordionizeNode(tagName, src, alt)
          core.parsedDOM.elements.push(accordionizeNode);
        }
      },

      findAndConvertBaseNode: function($container) {
        var objImages = $container.find(tagImg);
        var imageArray = objImages.toArray();

        core.convertBaseNodeToAccordionizeNode(imageArray);
        objImages.remove();
      },

      buildDOM: function($container) {
        var builtElements = [];
        for(var i = 0; i < core.parsedDOM.elements.length; i++) {
          var title = core.parsedDOM.elements[i].alt
            src = core.parsedDOM.elements[i].src,
            tagName = core.parsedDOM.elements[i].tagName,
            $wrapper = $('<div />',{
              'class': 'banner-item',
              'data-title': title
            }),
            $overlay = $('<div />', {
              'class': 'banner-item-overlay',
              'html': title
            }),
            $label = $('<div />', {
              'class': 'banner-item-label',
              'html': title
            }),
            $image = $('<img />', {
              'src': src,
              'alt': title
            });

          $image.css({
            'position': 'absolute',
            'display': 'none',
            'z-index': '1'
          });

          $wrapper
            .append($overlay)
            .append($label)
            .append($image);

          builtElements.push($wrapper);
        }
        $container.append(builtElements);
      },

      setEvents: function($container) {
        var $elements = $container.find('.banner-item'),
          slideWidth = $container.width() - (core.defaultOptions.tabWidth + 1) * (core.parsedDOM.elements.length - 1) - 1;

        $elements.each(function(){
          $(this).on('mousedown', function(){
            var $this = $(this);
            if(!$this.is('.active')) {
              if($container.attr('data-animated') != 'true') {
                $container.attr('data-animated', 'true');
                core.wrap($container, $this.siblings('.active'));
                core.unwrap($container, $this, slideWidth);
                core.setLoop($container);
              }
            } else {
              return false;
            }
          });
        });
      },

      unwrap: function($container, $slide, slideWidth) {
        setTimeout(function(){
          $slide
            .animate({
              'width': slideWidth + 'px'
            }, 400, function(){
              $container.attr('data-animated', 'false');
            })
            .addClass('active');

          $slide
            .find('.banner-item-overlay')
            .fadeOut(400);

          setTimeout(function(){
            $slide
              .find('.banner-item-label')
              .fadeIn(300);
            $slide
              .find('img')
              .fadeIn(300);
          }, 400)
        }, 300);

      },

      wrap: function($container, $slide) {
        setTimeout(function(){
          $slide
            .animate({
              'width': core.defaultOptions.tabWidth + 'px'
            }, 400, function(){
              $container.attr('data-animated', 'false');
            })
            .removeClass('active');

          setTimeout(function(){
            $slide
              .find('img')
              .fadeOut(300);

            $slide
              .find('.banner-item-overlay')
              .fadeIn(400);
          }, 400);

        }, 300);

        $slide
          .find('.banner-item-label')
          .fadeOut(300);
      }
     }

    core.init(this, core.defaultOptions, options);
  }
})(jQuery);
<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class coinmarketcap extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'coinmarketcap',
            'name': 'CoinMarketCap',
            'rateLimit': 10000,
            'version': 'v1',
            'countries': [ 'US' ],
            'has': {
                'cancelOrder': false,
                'CORS': true,
                'createLimitOrder': false,
                'createMarketOrder': false,
                'createOrder': false,
                'editOrder': false,
                'privateAPI': false,
                'fetchBalance': false,
                'fetchCurrencies': true,
                'fetchL2OrderBook': false,
                'fetchMarkets': true,
                'fetchOHLCV': false,
                'fetchOrderBook': false,
                'fetchTicker': true,
                'fetchTickers': true,
                'fetchTrades': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg',
                'api': {
                    'public': 'https://api.coinmarketcap.com',
                    'files': 'https://files.coinmarketcap.com',
                    'charts': 'https://graph.coinmarketcap.com',
                },
                'www': 'https://coinmarketcap.com',
                'doc': 'https://coinmarketcap.com/api',
            },
            'requiredCredentials': {
                'apiKey': false,
                'secret': false,
            },
            'api': {
                'files': {
                    'get': [
                        'generated/stats/global.json',
                    ],
                },
                'graphs': {
                    'get': [
                        'currencies/{name}/',
                    ],
                },
                'public': {
                    'get': [
                        'ticker/',
                        'ticker/{id}/',
                        'global/',
                    ],
                },
            },
            'currencyCodes': [
                'AUD',
                'BRL',
                'CAD',
                'CHF',
                'CNY',
                'EUR',
                'GBP',
                'HKD',
                'IDR',
                'INR',
                'JPY',
                'KRW',
                'MXN',
                'RUB',
                'USD',
                'BTC',
                'ETH',
                'LTC',
            ],
        });
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        throw new ExchangeError ('Fetching order books is not supported by the API of ' + this.id);
    }

    currencyCode (base, name) {
        const currencies = {
            'ACChain': 'ACChain',
            'AdCoin': 'AdCoin',
            'BatCoin': 'BatCoin',
            'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE)
            'Bitgem': 'Bitgem',
            'BlazeCoin': 'BlazeCoin',
            'BlockCAT': 'BlockCAT',
            'Blocktrade Token': 'Blocktrade Token',
            'Catcoin': 'Catcoin',
            'CanYaCoin': 'CanYaCoin', // conflict with CAN (Content and AD Network)
            'CryptoBossCoin': 'CryptoBossCoin', // conflict with CBC (CashBet Coin)
            'Comet': 'Comet', // conflict with CMT (CyberMiles)
            'CPChain': 'CPChain',
            'CrowdCoin': 'CrowdCoin', // conflict with CRC CryCash
            'Cryptaur': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081
            'Cubits': 'Cubits', // conflict with QBT (Qbao)
            'DAO.Casino': 'DAO.Casino', // conflict with BET (BetaCoin)
            'E-Dinar Coin': 'E-Dinar Coin', // conflict with EDR Endor Protocol and EDRCoin
            'EDRcoin': 'EDRcoin', // conflict with EDR Endor Protocol and E-Dinar Coin
            'ENTCash': 'ENTCash', // conflict with ENT (Eternity)
            'FairCoin': 'FairCoin', // conflict with FAIR (FairGame) https://github.com/ccxt/ccxt/pull/5865
            'Fabric Token': 'Fabric Token',
            // 'GET Protocol': 'GET Protocol',
            'GHOSTPRISM': 'GHOSTPRISM', // conflict with GHOST
            'Global Tour Coin': 'Global Tour Coin', // conflict with GTC (Game.com)
            'GuccioneCoin': 'GuccioneCoin', // conflict with GCC (Global Cryptocurrency)
            'HarmonyCoin': 'HarmonyCoin', // conflict with HMC (Hi Mutual Society)
            'Harvest Masternode Coin': 'Harvest Masternode Coin', // conflict with HC (HyperCash)
            'HOT Token': 'HOT Token',
            'Hydro Protocol': 'Hydro Protocol', // conflict with HOT (Holo)
            'Huncoin': 'Huncoin', // conflict with HNC (Helleniccoin)
            'iCoin': 'iCoin',
            'Infinity Economics': 'Infinity Economics', // conflict with XIN (Mixin)
            'KingN Coin': 'KingN Coin', // conflict with KNC (Kyber Network)
            'LiteBitcoin': 'LiteBitcoin', // conflict with LBTC (LightningBitcoin)
            'Maggie': 'Maggie',
            'Menlo One': 'Menlo One', // conflict with Harmony (ONE)
            'Monarch': 'Monarch', // conflict with MyToken (MT)
            'MTC Mesh Network': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025
            'IOTA': 'IOTA', // a special case, most exchanges list it as IOTA, therefore we change just the Coinmarketcap instead of changing them all
            'NetCoin': 'NetCoin',
            'PCHAIN': 'PCHAIN', // conflict with PAI (Project Pai)
            'Plair': 'Plair', // conflict with PLA (PLANET)
            'PlayChip': 'PlayChip', // conflict with PLA (PLANET)
            'Polcoin': 'Polcoin',
            'PutinCoin': 'PutinCoin', // conflict with PUT (Profile Utility Token)
            'Rcoin': 'Rcoin', // conflict with RCN (Ripio Credit Network)
            // https://github.com/ccxt/ccxt/issues/6081
            // https://github.com/ccxt/ccxt/issues/3365
            // https://github.com/ccxt/ccxt/issues/2873
            'TerraCredit': 'TerraCredit', // conflict with CREDIT (PROXI)
            'Themis': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol)
        };
        return this.safeValue (currencies, name, base);
    }

    async fetchMarkets (params = {}) {
        const request = {
            'limit': 0,
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        const result = [];
        for (let i = 0; i < response.length; i++) {
            const market = response[i];
            const currencies = this.currencyCodes;
            for (let j = 0; j < currencies.length; j++) {
                const quote = currencies[j];
                const quoteId = quote.toLowerCase ();
                const baseId = market['id'];
                const base = this.currencyCode (market['symbol'], market['name']);
                const symbol = base + '/' + quote;
                const id = baseId + '/' + quoteId;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'info': market,
                    'active': undefined,
                    'precision': this.precision,
                    'limits': this.limits,
                });
            }
        }
        return result;
    }

    async fetchGlobal (currency = 'USD') {
        await this.loadMarkets ();
        const request = {};
        if (currency) {
            request['convert'] = currency;
        }
        return await this.publicGetGlobal (request);
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.safeTimestamp (ticker, 'last_updated');
        if (timestamp === undefined) {
            timestamp = this.milliseconds ();
        }
        const change = this.safeFloat (ticker, 'percent_change_24h');
        let last = undefined;
        let symbol = undefined;
        let volume = undefined;
        if (market !== undefined) {
            symbol = market['symbol'];
            const priceKey = 'price_' + market['quoteId'];
            last = this.safeFloat (ticker, priceKey);
            const volumeKey = '24h_volume_' + market['quoteId'];
            volume = this.safeFloat (ticker, volumeKey);
        }
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': undefined,
            'bidVolume': undefined,
            'ask': undefined,
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': change,
            'average': undefined,
            'baseVolume': undefined,
            'quoteVolume': volume,
            'info': ticker,
        };
    }

    async fetchTickers (currency = 'USD', params = {}) {
        await this.loadMarkets ();
        const request = {
            'limit': 10000,
        };
        if (currency) {
            request['convert'] = currency;
        }
        const response = await this.publicGetTicker (this.extend (request, params));
        const result = {};
        for (let t = 0; t < response.length; t++) {
            const ticker = response[t];
            const currencyId = currency.toLowerCase ();
            const id = ticker['id'] + '/' + currencyId;
            let symbol = id;
            let market = undefined;
            if (id in this.markets_by_id) {
                market = this.markets_by_id[id];
                symbol = market['symbol'];
            }
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'convert': market['quote'],
            'id': market['baseId'],
        };
        const response = await this.publicGetTickerId (this.extend (request, params));
        const ticker = response[0];
        return this.parseTicker (ticker, market);
    }

    async fetchCurrencies (params = {}) {
        const request = {
            'limit': 0,
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        const result = {};
        for (let i = 0; i < response.length; i++) {
            const currency = response[i];
            const id = this.safeString (currency, 'symbol');
            const name = this.safeString (currency, 'name');
            // todo: will need to rethink the fees
            // to add support for multiple withdrawal/deposit methods and
            // differentiated fees for each particular method
            const precision = 8; // default precision, todo: fix "magic constants"
            const code = this.currencyCode (id, name);
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'active': true,
                'fee': undefined, // todo: redesign
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': Math.pow (10, -precision),
                        'max': Math.pow (10, precision),
                    },
                    'price': {
                        'min': Math.pow (10, -precision),
                        'max': Math.pow (10, precision),
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            };
        }
        return result;
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params);
        const query = this.omit (params, this.extractParams (path));
        if (Object.keys (query).length) {
            url += '?' + this.urlencode (query);
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        const response = await this.fetch2 (path, api, method, params, headers, body);
        if ('error' in response) {
            if (response['error']) {
                throw new ExchangeError (this.id + ' ' + this.json (response));
            }
        }
        return response;
    }
};
<commit_msg>CoinMarketCap Mobilian Coin mapping<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class coinmarketcap extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'coinmarketcap',
            'name': 'CoinMarketCap',
            'rateLimit': 10000,
            'version': 'v1',
            'countries': [ 'US' ],
            'has': {
                'cancelOrder': false,
                'CORS': true,
                'createLimitOrder': false,
                'createMarketOrder': false,
                'createOrder': false,
                'editOrder': false,
                'privateAPI': false,
                'fetchBalance': false,
                'fetchCurrencies': true,
                'fetchL2OrderBook': false,
                'fetchMarkets': true,
                'fetchOHLCV': false,
                'fetchOrderBook': false,
                'fetchTicker': true,
                'fetchTickers': true,
                'fetchTrades': false,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/51840849/87182086-1cd4cd00-c2ec-11ea-9ec4-d0cf2a2abf62.jpg',
                'api': {
                    'public': 'https://api.coinmarketcap.com',
                    'files': 'https://files.coinmarketcap.com',
                    'charts': 'https://graph.coinmarketcap.com',
                },
                'www': 'https://coinmarketcap.com',
                'doc': 'https://coinmarketcap.com/api',
            },
            'requiredCredentials': {
                'apiKey': false,
                'secret': false,
            },
            'api': {
                'files': {
                    'get': [
                        'generated/stats/global.json',
                    ],
                },
                'graphs': {
                    'get': [
                        'currencies/{name}/',
                    ],
                },
                'public': {
                    'get': [
                        'ticker/',
                        'ticker/{id}/',
                        'global/',
                    ],
                },
            },
            'currencyCodes': [
                'AUD',
                'BRL',
                'CAD',
                'CHF',
                'CNY',
                'EUR',
                'GBP',
                'HKD',
                'IDR',
                'INR',
                'JPY',
                'KRW',
                'MXN',
                'RUB',
                'USD',
                'BTC',
                'ETH',
                'LTC',
            ],
        });
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        throw new ExchangeError ('Fetching order books is not supported by the API of ' + this.id);
    }

    currencyCode (base, name) {
        const currencies = {
            'ACChain': 'ACChain',
            'AdCoin': 'AdCoin',
            'BatCoin': 'BatCoin',
            'BigONE Token': 'BigONE Token', // conflict with Harmony (ONE)
            'Bitgem': 'Bitgem',
            'BlazeCoin': 'BlazeCoin',
            'BlockCAT': 'BlockCAT',
            'Blocktrade Token': 'Blocktrade Token',
            'Catcoin': 'Catcoin',
            'CanYaCoin': 'CanYaCoin', // conflict with CAN (Content and AD Network)
            'CryptoBossCoin': 'CryptoBossCoin', // conflict with CBC (CashBet Coin)
            'Comet': 'Comet', // conflict with CMT (CyberMiles)
            'CPChain': 'CPChain',
            'CrowdCoin': 'CrowdCoin', // conflict with CRC CryCash
            'Cryptaur': 'Cryptaur', // conflict with CPT = Contents Protocol https://github.com/ccxt/ccxt/issues/4920 and https://github.com/ccxt/ccxt/issues/6081
            'Cubits': 'Cubits', // conflict with QBT (Qbao)
            'DAO.Casino': 'DAO.Casino', // conflict with BET (BetaCoin)
            'E-Dinar Coin': 'E-Dinar Coin', // conflict with EDR Endor Protocol and EDRCoin
            'EDRcoin': 'EDRcoin', // conflict with EDR Endor Protocol and E-Dinar Coin
            'ENTCash': 'ENTCash', // conflict with ENT (Eternity)
            'FairCoin': 'FairCoin', // conflict with FAIR (FairGame) https://github.com/ccxt/ccxt/pull/5865
            'Fabric Token': 'Fabric Token',
            // 'GET Protocol': 'GET Protocol',
            'GHOSTPRISM': 'GHOSTPRISM', // conflict with GHOST
            'Global Tour Coin': 'Global Tour Coin', // conflict with GTC (Game.com)
            'GuccioneCoin': 'GuccioneCoin', // conflict with GCC (Global Cryptocurrency)
            'HarmonyCoin': 'HarmonyCoin', // conflict with HMC (Hi Mutual Society)
            'Harvest Masternode Coin': 'Harvest Masternode Coin', // conflict with HC (HyperCash)
            'HOT Token': 'HOT Token',
            'Hydro Protocol': 'Hydro Protocol', // conflict with HOT (Holo)
            'Huncoin': 'Huncoin', // conflict with HNC (Helleniccoin)
            'iCoin': 'iCoin',
            'Infinity Economics': 'Infinity Economics', // conflict with XIN (Mixin)
            'KingN Coin': 'KingN Coin', // conflict with KNC (Kyber Network)
            'LiteBitcoin': 'LiteBitcoin', // conflict with LBTC (LightningBitcoin)
            'Maggie': 'Maggie',
            'Menlo One': 'Menlo One', // conflict with Harmony (ONE)
            'Mobilian Coin': 'Mobilian Coin', // conflict with Membrana (MBN)
            'Monarch': 'Monarch', // conflict with MyToken (MT)
            'MTC Mesh Network': 'MTC Mesh Network', // conflict with MTC Docademic doc.com Token https://github.com/ccxt/ccxt/issues/6081 https://github.com/ccxt/ccxt/issues/3025
            'IOTA': 'IOTA', // a special case, most exchanges list it as IOTA, therefore we change just the Coinmarketcap instead of changing them all
            'NetCoin': 'NetCoin',
            'PCHAIN': 'PCHAIN', // conflict with PAI (Project Pai)
            'Plair': 'Plair', // conflict with PLA (PLANET)
            'PlayChip': 'PlayChip', // conflict with PLA (PLANET)
            'Polcoin': 'Polcoin',
            'PutinCoin': 'PutinCoin', // conflict with PUT (Profile Utility Token)
            'Rcoin': 'Rcoin', // conflict with RCN (Ripio Credit Network)
            // https://github.com/ccxt/ccxt/issues/6081
            // https://github.com/ccxt/ccxt/issues/3365
            // https://github.com/ccxt/ccxt/issues/2873
            'TerraCredit': 'TerraCredit', // conflict with CREDIT (PROXI)
            'Themis': 'Themis', // conflict with GET (Guaranteed Entrance Token, GET Protocol)
        };
        return this.safeValue (currencies, name, base);
    }

    async fetchMarkets (params = {}) {
        const request = {
            'limit': 0,
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        const result = [];
        for (let i = 0; i < response.length; i++) {
            const market = response[i];
            const currencies = this.currencyCodes;
            for (let j = 0; j < currencies.length; j++) {
                const quote = currencies[j];
                const quoteId = quote.toLowerCase ();
                const baseId = market['id'];
                const base = this.currencyCode (market['symbol'], market['name']);
                const symbol = base + '/' + quote;
                const id = baseId + '/' + quoteId;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'info': market,
                    'active': undefined,
                    'precision': this.precision,
                    'limits': this.limits,
                });
            }
        }
        return result;
    }

    async fetchGlobal (currency = 'USD') {
        await this.loadMarkets ();
        const request = {};
        if (currency) {
            request['convert'] = currency;
        }
        return await this.publicGetGlobal (request);
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.safeTimestamp (ticker, 'last_updated');
        if (timestamp === undefined) {
            timestamp = this.milliseconds ();
        }
        const change = this.safeFloat (ticker, 'percent_change_24h');
        let last = undefined;
        let symbol = undefined;
        let volume = undefined;
        if (market !== undefined) {
            symbol = market['symbol'];
            const priceKey = 'price_' + market['quoteId'];
            last = this.safeFloat (ticker, priceKey);
            const volumeKey = '24h_volume_' + market['quoteId'];
            volume = this.safeFloat (ticker, volumeKey);
        }
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': undefined,
            'low': undefined,
            'bid': undefined,
            'bidVolume': undefined,
            'ask': undefined,
            'askVolume': undefined,
            'vwap': undefined,
            'open': undefined,
            'close': last,
            'last': last,
            'previousClose': undefined,
            'change': undefined,
            'percentage': change,
            'average': undefined,
            'baseVolume': undefined,
            'quoteVolume': volume,
            'info': ticker,
        };
    }

    async fetchTickers (currency = 'USD', params = {}) {
        await this.loadMarkets ();
        const request = {
            'limit': 10000,
        };
        if (currency) {
            request['convert'] = currency;
        }
        const response = await this.publicGetTicker (this.extend (request, params));
        const result = {};
        for (let t = 0; t < response.length; t++) {
            const ticker = response[t];
            const currencyId = currency.toLowerCase ();
            const id = ticker['id'] + '/' + currencyId;
            let symbol = id;
            let market = undefined;
            if (id in this.markets_by_id) {
                market = this.markets_by_id[id];
                symbol = market['symbol'];
            }
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        const market = this.market (symbol);
        const request = {
            'convert': market['quote'],
            'id': market['baseId'],
        };
        const response = await this.publicGetTickerId (this.extend (request, params));
        const ticker = response[0];
        return this.parseTicker (ticker, market);
    }

    async fetchCurrencies (params = {}) {
        const request = {
            'limit': 0,
        };
        const response = await this.publicGetTicker (this.extend (request, params));
        const result = {};
        for (let i = 0; i < response.length; i++) {
            const currency = response[i];
            const id = this.safeString (currency, 'symbol');
            const name = this.safeString (currency, 'name');
            // todo: will need to rethink the fees
            // to add support for multiple withdrawal/deposit methods and
            // differentiated fees for each particular method
            const precision = 8; // default precision, todo: fix "magic constants"
            const code = this.currencyCode (id, name);
            result[code] = {
                'id': id,
                'code': code,
                'info': currency,
                'name': name,
                'active': true,
                'fee': undefined, // todo: redesign
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': Math.pow (10, -precision),
                        'max': Math.pow (10, precision),
                    },
                    'price': {
                        'min': Math.pow (10, -precision),
                        'max': Math.pow (10, precision),
                    },
                    'cost': {
                        'min': undefined,
                        'max': undefined,
                    },
                    'withdraw': {
                        'min': undefined,
                        'max': undefined,
                    },
                },
            };
        }
        return result;
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'][api] + '/' + this.version + '/' + this.implodeParams (path, params);
        const query = this.omit (params, this.extractParams (path));
        if (Object.keys (query).length) {
            url += '?' + this.urlencode (query);
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        const response = await this.fetch2 (path, api, method, params, headers, body);
        if ('error' in response) {
            if (response['error']) {
                throw new ExchangeError (this.id + ' ' + this.json (response));
            }
        }
        return response;
    }
};
<|endoftext|>