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

/**
 * Conductivity tester. Light bulb connected to a battery, with draggable probes.
 * When the probes are both immersed in solution, the circuit is completed, and the bulb glows.
 *
 * @author Andrey Zelenkov (Mlearner)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LightBulbNode = require( 'SCENERY_PHET/LightBulbNode' );
  var MinusNode = require( 'SCENERY_PHET/MinusNode' );
  var ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var PlusNode = require( 'SCENERY_PHET/PlusNode' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );

  // images
  var batteryImage = require( 'image!SCENERY_PHET/battery-D-cell.png' );

  // strings
  var shortCircuitString = require( 'string!SCENERY_PHET/shortCircuit' );

  // constants
  var SHOW_TESTER_ORIGIN = false; // draws a red circle at the tester's origin, for debugging
  var SHOW_PROBE_ORIGIN = false; // draws a red circle at the origin of probes, for debugging

  /**
   * @param {Property.<number>} brightnessProperty brightness of bulb varies from 0 (off) to 1 (full on)
   * @param {Property.<Vector2>} locationProperty location of the tester, at bottom-center of the bulb (model coordinate frame)
   * @param {Property.<Vector2>} positiveProbeLocationProperty location of bottom-center of the positive probe (model coordinate frame)
   * @param {Property.<Vector2>} negativeProbeLocationProperty location of bottom-center of the negative probe (model coordinate frame)
   * @param {Object} [options]
   * @constructor
   */
  function ConductivityTesterNode( brightnessProperty, locationProperty, positiveProbeLocationProperty, negativeProbeLocationProperty, options ) {

    var thisNode = this;

    options = _.extend( {
      modelViewTransform: ModelViewTransform2.createIdentity(),
      interactive: true, // set to false if you're creating an icon
      bulbImageScale: 0.33,
      batteryImageScale: 0.6,
      // common to both probes
      probeSize: new Dimension2( 20, 68 ), // {Dimension2} probe dimensions, in view coordinates
      probeLineWidth: 0.5,
      probeDragYRange: null, // {DOT.Range} y-axis drag range, relative to locationProperty, in view coordinates. null means no constraint.
      probeCursor: 'pointer',
      // positive probe
      positiveProbeFill: 'red',
      positiveProbeStroke: 'black',
      positiveLabelFill: 'white',
      // negative probe
      negativeProbeFill: 'black',
      negativeProbeStroke: 'black',
      negativeLabelFill: 'white',
      // wires
      wireStroke: 'black',
      wireLineWidth: 1.5,
      bulbToBatteryWireLength: 40, // length of the wire between bulb and battery, in view coordinates
      // short-circuit indicator
      shortCircuitFont: new PhetFont( 14 ),
      shortCircuitFill: 'black'
    }, options );

    // @private bulb, origin at bottom center of base
    this.lightBulbNode = new LightBulbNode( brightnessProperty, {
      bulbImageScale: options.bulbImageScale
    } );

    // @private short-circuit indicator, centered above the light bulb
    assert && assert( brightnessProperty.get() === 0 ); //TODO layout will be wrong if lightBulbNode has rays
    this.shortCircuitNode = new Text( shortCircuitString, {
      font: options.shortCircuitFont,
      fill: options.shortCircuitFill,
      centerX: this.lightBulbNode.centerX,
      bottom: this.lightBulbNode.top,
      visible: false // initial state is no short circuit
    } );

    // battery
    var battery = new Image( batteryImage, {
      scale: options.batteryImageScale,
      left: options.bulbToBatteryWireLength,
      centerY: 0
    } );

    // wire from bulb base to battery
    var bulbBatteryWire = new Path( new Shape().moveTo( 0, 0 ).lineTo( options.bulbToBatteryWireLength, 0 ), {
      stroke: options.wireStroke,
      lineWidth: options.wireLineWidth
    } );

    // apparatus (bulb + battery), origin at bottom center of bulb's base
    var apparatusNode = new Node( {
      children: [
        bulbBatteryWire,
        battery,
        this.lightBulbNode,
        this.shortCircuitNode
      ]
    } );
    if ( SHOW_TESTER_ORIGIN ) {
      apparatusNode.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // wire from battery terminal to positive probe
    var positiveWire = new WireNode(
      battery.getGlobalBounds().right,
      battery.getGlobalBounds().centerY,
      options.modelViewTransform.modelToViewX( positiveProbeLocationProperty.get().x ) - options.modelViewTransform.modelToViewX( locationProperty.get().x ),
      options.modelViewTransform.modelToViewY( positiveProbeLocationProperty.get().y ) - options.modelViewTransform.modelToViewY( locationProperty.get().y ) - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // wire from base of bulb (origin) to negative probe
    var negativeWire = new WireNode(
      -5, -5, // specific to bulb image file
      options.modelViewTransform.modelToViewX( negativeProbeLocationProperty.get().x ) - options.modelViewTransform.modelToViewX( locationProperty.get().x ),
      options.modelViewTransform.modelToViewY( negativeProbeLocationProperty.get().y ) - options.modelViewTransform.modelToViewY( locationProperty.get().y ) - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // drag handler for probes
    var probeDragHandler = new SimpleDragHandler( {

      clickYOffset: 0,

      start: function( e ) {
        this.clickYOffset = e.currentTarget.globalToParentPoint( e.pointer.point ).y - e.currentTarget.y;
      },

      // probes move together
      drag: function( e ) {
        // do dragging in view coordinate frame
        var locationView = options.modelViewTransform.modelToViewPosition( locationProperty.get() );
        var yView = e.currentTarget.globalToParentPoint( e.pointer.point ).y + locationView.y - this.clickYOffset;
        if ( options.probeDragYRange ) {
          yView = Util.clamp( yView, locationView.y + options.probeDragYRange.min, locationView.y + options.probeDragYRange.max );
        }
        // convert to model coordinate frame
        var yModel = options.modelViewTransform.viewToModelY( yView );
        positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x, yModel ) );
        negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x, yModel ) );
      }
    } );

    // probes
    var positiveProbe = new ProbeNode( new PlusNode( { fill: options.positiveLabelFill } ), {
      size: options.probeSize,
      fill: options.positiveProbeFill,
      stroke: options.positiveProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    var negativeProbe = new ProbeNode( new MinusNode( { fill: options.negativeLabelFill } ), {
      size: options.probeSize,
      fill: options.negativeProbeFill,
      stroke: options.negativeProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    if ( options.interactive ) {
      positiveProbe.cursor = options.probeCursor;
      positiveProbe.addInputListener( probeDragHandler );
      negativeProbe.cursor = options.probeCursor;
      negativeProbe.addInputListener( probeDragHandler );
    }

    Node.call( this, { children: [ positiveWire, negativeWire, positiveProbe, negativeProbe, apparatusNode ] } );

    // @private when the location changes ...
    this.locationObserver = function( location, oldLocation ) {
      // move the entire tester
      thisNode.translation = options.modelViewTransform.modelToViewPosition( location );
      // probes move with the tester
      if ( oldLocation ) {
        var dx = location.x - oldLocation.x;
        var dy = location.y - oldLocation.y;
        positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x + dx, positiveProbeLocationProperty.get().y + dy ) );
        negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x + dx, negativeProbeLocationProperty.get().y + dy ) );
      }
    };
    this.locationProperty = locationProperty; // @private
    this.locationProperty.link( this.locationObserver );

    // @private update positive wire if end point was changed
    this.positiveProbeObserver = function( positiveProbeLocation ) {
      positiveProbe.centerX = options.modelViewTransform.modelToViewX( positiveProbeLocation.x ) - options.modelViewTransform.modelToViewX( thisNode.locationProperty.get().x );
      positiveProbe.bottom = options.modelViewTransform.modelToViewY( positiveProbeLocation.y ) - options.modelViewTransform.modelToViewY( thisNode.locationProperty.get().y );
      positiveWire.setEndPoint( positiveProbe.x, positiveProbe.y - options.probeSize.height );
    };
    this.positiveProbeLocationProperty = positiveProbeLocationProperty; // @private
    this.positiveProbeLocationProperty.link( this.positiveProbeObserver );

    // @private update negative wire if end point was changed
    this.negativeProbeObserver = function( negativeProbeLocation ) {
      negativeProbe.centerX = options.modelViewTransform.modelToViewX( negativeProbeLocation.x ) - options.modelViewTransform.modelToViewX( thisNode.locationProperty.get().x );
      negativeProbe.bottom =  options.modelViewTransform.modelToViewY( negativeProbeLocation.y ) - options.modelViewTransform.modelToViewY( thisNode.locationProperty.get().y );
      negativeWire.setEndPoint( negativeProbe.x, negativeProbe.y - options.probeSize.height );
    };
    this.negativeProbeLocationProperty = negativeProbeLocationProperty; // @private
    this.negativeProbeLocationProperty.link( this.negativeProbeObserver );

    // Since locationProperty determines translation, avoid options related to translation!
    this.mutate( options );
  }

  sceneryPhet.register( 'ConductivityTesterNode', ConductivityTesterNode );

  inherit( Node, ConductivityTesterNode, {

    // @public Ensures that this object is eligible for GC
    dispose: function() {

      // unlink from axon properties
      this.locationProperty.unlink( this.locationObserver );
      this.positiveProbeLocationProperty.unlink( this.positiveProbeObserver );
      this.negativeProbeLocationProperty.unlink( this.negativeProbeObserver );

      // dispose of sub-components
      this.lightBulbNode.dispose();
      this.lightBulbNode = null;
    },

    // @public @override
    setVisible: function( visible ) {
      Node.prototype.setVisible.call( this, visible );
      this.lightBulbNode.visible = visible; // to prevent light from updating when invisible
    },

    /**
     * Determines whether 'Short circuit' is shown above the light bulb. Note that it is the client's responsibility
     * to ensure that the bulb's brightness (as set by brightnessProperty) is appropriate for a short circuit.
     * @param {boolean} value
     * @public
     */
    set shortCircuit( value ) { this.shortCircuitNode.visible = value; },

    /**
     * Is 'Short circuit' shown above the light bulb?
     * @returns {boolean}
     * @public
     */
    get shortCircuit() { return this.shortCircuitNode.visible; }
  } );

  /**
   * Conductivity probe, origin at bottom center.
   *
   * @param {Node} labelNode
   * @param {Object} [options]
   * @constructor
   */
  function ProbeNode( labelNode, options ) {

    options = _.extend( {
      size: new Dimension2( 20, 60 ),
      fill: 'white',
      stroke: 'black',
      lineWidth: 1.5
    }, options );

    Node.call( this );

    // plate
    var plateNode = new Rectangle( -options.size.width / 2, -options.size.height, options.size.width, options.size.height, {
      fill: options.fill,
      stroke: options.stroke,
      lineWidth: options.lineWidth
    } );

    // scale the label to fix, place it towards bottom center
    labelNode.setScaleMagnitude( 0.5 * options.size.width / labelNode.width );
    labelNode.centerX = plateNode.centerX;
    labelNode.bottom = plateNode.bottom - 10;

    // rendering order
    this.addChild( plateNode );
    this.addChild( labelNode );
    if ( SHOW_PROBE_ORIGIN ) {
      this.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // expand touch area
    this.touchArea = this.localBounds.dilatedXY( 10, 10 );

    this.mutate( options );
  }

  sceneryPhet.register( 'ConductivityTesterNode.ProbeNode', ProbeNode );

  inherit( Node, ProbeNode );

  /**
   * Wires that connect to the probes.
   *
   * @param startX
   * @param startY
   * @param endX
   * @param endY
   * @param {Object} [options]
   * @constructor
   */
  function WireNode( startX, startY, endX, endY, options ) {

    Path.call( this );

    this.startPoint = { x: startX, y: startY }; // @private
    // control point offsets for when probe is to left of light bulb
    this.controlPointOffset = { x: 30, y: -50 }; // @private
    if ( endX < startX ) {
      // probe is to right of light bulb, flip sign on control point x-offset
      this.controlPointOffset.x = -this.controlPointOffset.x;
    }

    this.setEndPoint( endX, endY );

    this.mutate( options );
  }

  sceneryPhet.register( 'ConductivityTesterNode.WireNode', WireNode );

  inherit( Path, WireNode, {

    // @private Sets the end point coordinates, the point attached to the probe.
    setEndPoint: function( endX, endY ) {

      var startX = this.startPoint.x;
      var startY = this.startPoint.y;
      var controlPointXOffset = this.controlPointOffset.x;
      var controlPointYOffset = this.controlPointOffset.y;

      this.setShape( new Shape()
          .moveTo( startX, startY )
          .cubicCurveTo( startX + controlPointXOffset, startY, endX, endY + controlPointYOffset, endX, endY )
      );
    }
  } );

  /**
   * Convenience function for creating an icon.
   * @param {number} brightness 0-1 (off to full on)
   * @param {number} positiveProbeXOffset x-offset of the positive probe, relative to the bulb's tip
   * @param {number} negativeProbeXOffset x-offset of the negative probe, relative to the bulb's tip
   * @param {number} bothProbesYOffset y-offset of both probes, relative to the bulb's tip
   * @param {Object} [options] same options as ConductivityTesterNode constructor
   * @returns {ConductivityTesterNode}
   */
  ConductivityTesterNode.createIcon = function( brightness, positiveProbeXOffset, negativeProbeXOffset, bothProbesYOffset, options ) {

    options = options || {};
    options.interactive = false;

    return new ConductivityTesterNode(
      new Property( brightness ),
      new Property( new Vector2( 0, 0 ) ),
      new Property( new Vector2( positiveProbeXOffset, bothProbesYOffset ) ),
      new Property( new Vector2( negativeProbeXOffset, bothProbesYOffset ) ),
      options
    );
  };

  return ConductivityTesterNode;
} );
<commit_msg>fix SimpleDragHandler in ConductivityTesterNode, https://github.com/phetsims/scenery/issues/513<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Conductivity tester. Light bulb connected to a battery, with draggable probes.
 * When the probes are both immersed in solution, the circuit is completed, and the bulb glows.
 *
 * @author Andrey Zelenkov (Mlearner)
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var Circle = require( 'SCENERY/nodes/Circle' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var Image = require( 'SCENERY/nodes/Image' );
  var inherit = require( 'PHET_CORE/inherit' );
  var LightBulbNode = require( 'SCENERY_PHET/LightBulbNode' );
  var MinusNode = require( 'SCENERY_PHET/MinusNode' );
  var ModelViewTransform2 = require( 'PHETCOMMON/view/ModelViewTransform2' );
  var Node = require( 'SCENERY/nodes/Node' );
  var Path = require( 'SCENERY/nodes/Path' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var PlusNode = require( 'SCENERY_PHET/PlusNode' );
  var Property = require( 'AXON/Property' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var Shape = require( 'KITE/Shape' );
  var SimpleDragHandler = require( 'SCENERY/input/SimpleDragHandler' );
  var Text = require( 'SCENERY/nodes/Text' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );

  // images
  var batteryImage = require( 'image!SCENERY_PHET/battery-D-cell.png' );

  // strings
  var shortCircuitString = require( 'string!SCENERY_PHET/shortCircuit' );

  // constants
  var SHOW_TESTER_ORIGIN = false; // draws a red circle at the tester's origin, for debugging
  var SHOW_PROBE_ORIGIN = false; // draws a red circle at the origin of probes, for debugging

  /**
   * @param {Property.<number>} brightnessProperty brightness of bulb varies from 0 (off) to 1 (full on)
   * @param {Property.<Vector2>} locationProperty location of the tester, at bottom-center of the bulb (model coordinate frame)
   * @param {Property.<Vector2>} positiveProbeLocationProperty location of bottom-center of the positive probe (model coordinate frame)
   * @param {Property.<Vector2>} negativeProbeLocationProperty location of bottom-center of the negative probe (model coordinate frame)
   * @param {Object} [options]
   * @constructor
   */
  function ConductivityTesterNode( brightnessProperty, locationProperty, positiveProbeLocationProperty, negativeProbeLocationProperty, options ) {

    var thisNode = this;

    options = _.extend( {
      modelViewTransform: ModelViewTransform2.createIdentity(),
      interactive: true, // set to false if you're creating an icon
      bulbImageScale: 0.33,
      batteryImageScale: 0.6,
      // common to both probes
      probeSize: new Dimension2( 20, 68 ), // {Dimension2} probe dimensions, in view coordinates
      probeLineWidth: 0.5,
      probeDragYRange: null, // {DOT.Range} y-axis drag range, relative to locationProperty, in view coordinates. null means no constraint.
      probeCursor: 'pointer',
      // positive probe
      positiveProbeFill: 'red',
      positiveProbeStroke: 'black',
      positiveLabelFill: 'white',
      // negative probe
      negativeProbeFill: 'black',
      negativeProbeStroke: 'black',
      negativeLabelFill: 'white',
      // wires
      wireStroke: 'black',
      wireLineWidth: 1.5,
      bulbToBatteryWireLength: 40, // length of the wire between bulb and battery, in view coordinates
      // short-circuit indicator
      shortCircuitFont: new PhetFont( 14 ),
      shortCircuitFill: 'black'
    }, options );

    // @private bulb, origin at bottom center of base
    this.lightBulbNode = new LightBulbNode( brightnessProperty, {
      bulbImageScale: options.bulbImageScale
    } );

    // @private short-circuit indicator, centered above the light bulb
    assert && assert( brightnessProperty.get() === 0 ); //TODO layout will be wrong if lightBulbNode has rays
    this.shortCircuitNode = new Text( shortCircuitString, {
      font: options.shortCircuitFont,
      fill: options.shortCircuitFill,
      centerX: this.lightBulbNode.centerX,
      bottom: this.lightBulbNode.top,
      visible: false // initial state is no short circuit
    } );

    // battery
    var battery = new Image( batteryImage, {
      scale: options.batteryImageScale,
      left: options.bulbToBatteryWireLength,
      centerY: 0
    } );

    // wire from bulb base to battery
    var bulbBatteryWire = new Path( new Shape().moveTo( 0, 0 ).lineTo( options.bulbToBatteryWireLength, 0 ), {
      stroke: options.wireStroke,
      lineWidth: options.wireLineWidth
    } );

    // apparatus (bulb + battery), origin at bottom center of bulb's base
    var apparatusNode = new Node( {
      children: [
        bulbBatteryWire,
        battery,
        this.lightBulbNode,
        this.shortCircuitNode
      ]
    } );
    if ( SHOW_TESTER_ORIGIN ) {
      apparatusNode.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // wire from battery terminal to positive probe
    var positiveWire = new WireNode(
      battery.getGlobalBounds().right,
      battery.getGlobalBounds().centerY,
      options.modelViewTransform.modelToViewX( positiveProbeLocationProperty.get().x ) - options.modelViewTransform.modelToViewX( locationProperty.get().x ),
      options.modelViewTransform.modelToViewY( positiveProbeLocationProperty.get().y ) - options.modelViewTransform.modelToViewY( locationProperty.get().y ) - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // wire from base of bulb (origin) to negative probe
    var negativeWire = new WireNode(
      -5, -5, // specific to bulb image file
      options.modelViewTransform.modelToViewX( negativeProbeLocationProperty.get().x ) - options.modelViewTransform.modelToViewX( locationProperty.get().x ),
      options.modelViewTransform.modelToViewY( negativeProbeLocationProperty.get().y ) - options.modelViewTransform.modelToViewY( locationProperty.get().y ) - options.probeSize.height,
      { stroke: options.wireStroke, lineWidth: options.wireLineWidth }
    );

    // drag handler for probes
    var clickYOffset = 0;
    var probeDragHandler = new SimpleDragHandler( {

      start: function( e ) {
        clickYOffset = e.currentTarget.globalToParentPoint( e.pointer.point ).y - e.currentTarget.y;
      },

      // probes move together
      drag: function( e ) {
        // do dragging in view coordinate frame
        var locationView = options.modelViewTransform.modelToViewPosition( locationProperty.get() );
        var yView = e.currentTarget.globalToParentPoint( e.pointer.point ).y + locationView.y - clickYOffset;
        if ( options.probeDragYRange ) {
          yView = Util.clamp( yView, locationView.y + options.probeDragYRange.min, locationView.y + options.probeDragYRange.max );
        }
        // convert to model coordinate frame
        var yModel = options.modelViewTransform.viewToModelY( yView );
        positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x, yModel ) );
        negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x, yModel ) );
      }
    } );

    // probes
    var positiveProbe = new ProbeNode( new PlusNode( { fill: options.positiveLabelFill } ), {
      size: options.probeSize,
      fill: options.positiveProbeFill,
      stroke: options.positiveProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    var negativeProbe = new ProbeNode( new MinusNode( { fill: options.negativeLabelFill } ), {
      size: options.probeSize,
      fill: options.negativeProbeFill,
      stroke: options.negativeProbeStroke,
      lineWidth: options.probeLineWidth
    } );
    if ( options.interactive ) {
      positiveProbe.cursor = options.probeCursor;
      positiveProbe.addInputListener( probeDragHandler );
      negativeProbe.cursor = options.probeCursor;
      negativeProbe.addInputListener( probeDragHandler );
    }

    Node.call( this, { children: [ positiveWire, negativeWire, positiveProbe, negativeProbe, apparatusNode ] } );

    // @private when the location changes ...
    this.locationObserver = function( location, oldLocation ) {
      // move the entire tester
      thisNode.translation = options.modelViewTransform.modelToViewPosition( location );
      // probes move with the tester
      if ( oldLocation ) {
        var dx = location.x - oldLocation.x;
        var dy = location.y - oldLocation.y;
        positiveProbeLocationProperty.set( new Vector2( positiveProbeLocationProperty.get().x + dx, positiveProbeLocationProperty.get().y + dy ) );
        negativeProbeLocationProperty.set( new Vector2( negativeProbeLocationProperty.get().x + dx, negativeProbeLocationProperty.get().y + dy ) );
      }
    };
    this.locationProperty = locationProperty; // @private
    this.locationProperty.link( this.locationObserver );

    // @private update positive wire if end point was changed
    this.positiveProbeObserver = function( positiveProbeLocation ) {
      positiveProbe.centerX = options.modelViewTransform.modelToViewX( positiveProbeLocation.x ) - options.modelViewTransform.modelToViewX( thisNode.locationProperty.get().x );
      positiveProbe.bottom = options.modelViewTransform.modelToViewY( positiveProbeLocation.y ) - options.modelViewTransform.modelToViewY( thisNode.locationProperty.get().y );
      positiveWire.setEndPoint( positiveProbe.x, positiveProbe.y - options.probeSize.height );
    };
    this.positiveProbeLocationProperty = positiveProbeLocationProperty; // @private
    this.positiveProbeLocationProperty.link( this.positiveProbeObserver );

    // @private update negative wire if end point was changed
    this.negativeProbeObserver = function( negativeProbeLocation ) {
      negativeProbe.centerX = options.modelViewTransform.modelToViewX( negativeProbeLocation.x ) - options.modelViewTransform.modelToViewX( thisNode.locationProperty.get().x );
      negativeProbe.bottom =  options.modelViewTransform.modelToViewY( negativeProbeLocation.y ) - options.modelViewTransform.modelToViewY( thisNode.locationProperty.get().y );
      negativeWire.setEndPoint( negativeProbe.x, negativeProbe.y - options.probeSize.height );
    };
    this.negativeProbeLocationProperty = negativeProbeLocationProperty; // @private
    this.negativeProbeLocationProperty.link( this.negativeProbeObserver );

    // Since locationProperty determines translation, avoid options related to translation!
    this.mutate( options );
  }

  sceneryPhet.register( 'ConductivityTesterNode', ConductivityTesterNode );

  inherit( Node, ConductivityTesterNode, {

    // @public Ensures that this object is eligible for GC
    dispose: function() {

      // unlink from axon properties
      this.locationProperty.unlink( this.locationObserver );
      this.positiveProbeLocationProperty.unlink( this.positiveProbeObserver );
      this.negativeProbeLocationProperty.unlink( this.negativeProbeObserver );

      // dispose of sub-components
      this.lightBulbNode.dispose();
      this.lightBulbNode = null;
    },

    // @public @override
    setVisible: function( visible ) {
      Node.prototype.setVisible.call( this, visible );
      this.lightBulbNode.visible = visible; // to prevent light from updating when invisible
    },

    /**
     * Determines whether 'Short circuit' is shown above the light bulb. Note that it is the client's responsibility
     * to ensure that the bulb's brightness (as set by brightnessProperty) is appropriate for a short circuit.
     * @param {boolean} value
     * @public
     */
    set shortCircuit( value ) { this.shortCircuitNode.visible = value; },

    /**
     * Is 'Short circuit' shown above the light bulb?
     * @returns {boolean}
     * @public
     */
    get shortCircuit() { return this.shortCircuitNode.visible; }
  } );

  /**
   * Conductivity probe, origin at bottom center.
   *
   * @param {Node} labelNode
   * @param {Object} [options]
   * @constructor
   */
  function ProbeNode( labelNode, options ) {

    options = _.extend( {
      size: new Dimension2( 20, 60 ),
      fill: 'white',
      stroke: 'black',
      lineWidth: 1.5
    }, options );

    Node.call( this );

    // plate
    var plateNode = new Rectangle( -options.size.width / 2, -options.size.height, options.size.width, options.size.height, {
      fill: options.fill,
      stroke: options.stroke,
      lineWidth: options.lineWidth
    } );

    // scale the label to fix, place it towards bottom center
    labelNode.setScaleMagnitude( 0.5 * options.size.width / labelNode.width );
    labelNode.centerX = plateNode.centerX;
    labelNode.bottom = plateNode.bottom - 10;

    // rendering order
    this.addChild( plateNode );
    this.addChild( labelNode );
    if ( SHOW_PROBE_ORIGIN ) {
      this.addChild( new Circle( 2, { fill: 'red' } ) );
    }

    // expand touch area
    this.touchArea = this.localBounds.dilatedXY( 10, 10 );

    this.mutate( options );
  }

  sceneryPhet.register( 'ConductivityTesterNode.ProbeNode', ProbeNode );

  inherit( Node, ProbeNode );

  /**
   * Wires that connect to the probes.
   *
   * @param startX
   * @param startY
   * @param endX
   * @param endY
   * @param {Object} [options]
   * @constructor
   */
  function WireNode( startX, startY, endX, endY, options ) {

    Path.call( this );

    this.startPoint = { x: startX, y: startY }; // @private
    // control point offsets for when probe is to left of light bulb
    this.controlPointOffset = { x: 30, y: -50 }; // @private
    if ( endX < startX ) {
      // probe is to right of light bulb, flip sign on control point x-offset
      this.controlPointOffset.x = -this.controlPointOffset.x;
    }

    this.setEndPoint( endX, endY );

    this.mutate( options );
  }

  sceneryPhet.register( 'ConductivityTesterNode.WireNode', WireNode );

  inherit( Path, WireNode, {

    // @private Sets the end point coordinates, the point attached to the probe.
    setEndPoint: function( endX, endY ) {

      var startX = this.startPoint.x;
      var startY = this.startPoint.y;
      var controlPointXOffset = this.controlPointOffset.x;
      var controlPointYOffset = this.controlPointOffset.y;

      this.setShape( new Shape()
          .moveTo( startX, startY )
          .cubicCurveTo( startX + controlPointXOffset, startY, endX, endY + controlPointYOffset, endX, endY )
      );
    }
  } );

  /**
   * Convenience function for creating an icon.
   * @param {number} brightness 0-1 (off to full on)
   * @param {number} positiveProbeXOffset x-offset of the positive probe, relative to the bulb's tip
   * @param {number} negativeProbeXOffset x-offset of the negative probe, relative to the bulb's tip
   * @param {number} bothProbesYOffset y-offset of both probes, relative to the bulb's tip
   * @param {Object} [options] same options as ConductivityTesterNode constructor
   * @returns {ConductivityTesterNode}
   */
  ConductivityTesterNode.createIcon = function( brightness, positiveProbeXOffset, negativeProbeXOffset, bothProbesYOffset, options ) {

    options = options || {};
    options.interactive = false;

    return new ConductivityTesterNode(
      new Property( brightness ),
      new Property( new Vector2( 0, 0 ) ),
      new Property( new Vector2( positiveProbeXOffset, bothProbesYOffset ) ),
      new Property( new Vector2( negativeProbeXOffset, bothProbesYOffset ) ),
      options
    );
  };

  return ConductivityTesterNode;
} );
<|endoftext|>


<commit_before>'use strict';
/*
Scraper script searching store for apps and archiving app data said apps.

NOTE: 120 is max number of apps to receive at once through list method...
NOTE: this has a permission section could inspect when throw this into folder
NOTE: cronjob...

TODO: to avoid the situation of askign for a captcha use a throttle keyword
, all methods now support a throttle property, which defines an upper bound to
the amount of requests that will be attempted per second. Once that limit is reached,
further requests will be held  until the second passes.
*/
const config = require('/etc/xray/config.json'); //See example_config.json
const gplay = require('google-play-scraper');
//Reading from folder of csv files
const fs = require('fs');
const fsEx = require('fs-extra');
const path = require('path');
const fs_promise = require('fs-readdir-promise');
const readline = require('readline');

//Unix pipeline setup
const unix = require('unix-dgram');
const client = unix.createSocket('unix_dgram');

const logger = require('./logger.js');

let appsSaveDir = path.join(config.datadir, 'apps');

function mkdirp(path) {
    path.split(path.sep).reduce((parentDir, childDir) => {
        const curDir = path.join(parentDir, childDir);
        if (!fs.existsSync(curDir)) {
            fs.mkdirSync(curDir);
        }

        return curDir;
    }, path.isAbsolute ? path.sep : '');
}

try {
    let dirStats = fs.statSync(appsSaveDir);
    if (!dirStats.isDirectory()) {
        logger.err('The app directory %s exists but is not a directory!', appsSaveDir);
        process.exit(1);
    }
} catch (err) {
    if (err.code == 'ENOENT') {
        logger.info('Creating apps folder ' + appsSaveDir);
        mkdirp(appsSaveDir);
    } else {
        logger.err('Unknown error opening appsSaveDir:', err.message);
        process.exit(1);
    }
}

let waitForPipe = () => {
    if (!fs.existsSync(config.sockpath)) {
        logger.err('Could not bind to socket... trying again in 1000s');
        setTimeout(waitForPipe, 1000);
    } else {
        main();
    }
}; waitForPipe();

//TODO: move region to config or section to iterate over
let region = 'us';
let appStore = 'play';

function resolveAPKDir(appData) {
    //console.log('appdir:'+ config.datadir, '\nappId'+ appData.appId, '\nappStore'+ appStore, '\nregion'+ region, '\nversion'+ appData.version);

    //log('appdir:'+ config.appdir, '\nappId'+ appData.appId, '\nappStore'+ appStore, '\nregion'+ region, '\nversion'+ appData.version);
    //NOTE: If app version is undefined setting to  date
    if (!appData.version || appData.version === 'Varies with device') {
        logger.debug('Version not found defaulting too', appData.updated);
        let formatDate = appData.updated.replace(/\s+/g, '').replace(',', '/');
        appData.version = formatDate;
    }

    let appSavePath = path.join(appsSaveDir, appData.appId, appStore, region, appData.version);
    logger.info('App desired save dir ' + appSavePath);

    return Promise.all([fsEx.pathExists(appSavePath), Promise.resolve(appSavePath)]);
}

function downloadApp(appData, appSavePath) {
    const args = ['-pd', appData.appId, '-f', appSavePath, '-c', config.credDownload]; /* Command line args for gplay cli */
    const spw = require('child-process-promise').spawn;
    logger.info('Passing args to downloader' + args);
    const apkDownloader = spw('gplaycli', args);

    let downloadProcess = apkDownloader.childProcess;

    mkdirp(appSavePath);
    logger.info('DL process %d for %s-%s started.', downloadProcess.pid, appData.appId, appData.version);

    downloadProcess.stdout.on('data', data => {
        logger.debug('DL process %d stdout:', downloadProcess.pid, data);
    });

    downloadProcess.stderr.on('data', data => {
        logger.warning('DL process %d stderr:', downloadProcess.pid, data);
    });

    return apkDownloader;
}

//TODO: check dir setup before attempting to search on that word

function extractAppData(appData) {
    //Check appData state
    if (!appData.appId) { return Promise.reject('Invalid appdata', appData.appId); }

    return resolveAPKDir(appData).then(appPathInfo => {
        let exists = appPathInfo[0],
            appSavePath = appPathInfo[1];

        if (exists) {
            logger.info('App version %s-%s already exists', appData.appId, appData.version);
            return Promise.reject();
        } else {
            logger.info('New app version %s-%s', appData.appId, appData.version);

            //TODO: append previous and write seperately
            appData.isDownloaded = true;
            return downloadApp(appData, appSavePath).catch((err) => {
                try {
                    fs.rmdir(appSavePath);
                } catch (err) {
                    // TODO: something
                }
                logger.warning('Downloading failed with error:', err.message);
                appData.isDownloaded = false;
            });
        }
    }).then(() => {
        logger.info('Download process complete for ' + appData.appId);

        // TODO: DB Comms... this can be factorised.
        let db = require('./db');
        return db.insertPlayApp(appData, region).catch((err) => {
            logger.err('Inserting play app failed', err, region);
            return err;
        }).catch((err) => logger.err('Could not write to db:', err.message));
    }).then(async (dbId) => {
        // TODO: Check that '-' won't mess things up on the DB side... eg if region was something like 'en-gb'
        let message = Buffer(dbId + '-' + appData.appId + '-' + appStore + '-' + region + '-' + appData.version);

        //client.on('error', logger.err);
        return client.send(message, 0, message.length, config.sockpath).catch((err) => logger.err('Could not connect to socket:', err.message));
    }).catch(function() {
        return;
    });
}

//Base scrapes array apps based on google-play-scraper app json format - PROMISE FORMAT
// function scrape(appsData) {
//     return appsData.map((val) => {
//         return gplay.app({ appId: val.appId }).then(function(some_other_val) {
//             return extractAppData(val).then(() => {
//                 logger('finished downloading', val.appId);
//                 return val; // whatever you return here will get passed on to the next val in the promise chain..
//             }).catch((e) => {
//                 logger.err('error downloading ', val.appId, e.toString());
//                 throw e;
//             });
//         }).catch(function(err) {
//             logger.err('Could not save app ', err);
//             return Promise.reject(appData.appId);
//         });
//     });
// }

/* Get an array of gplay Search results */
function scrapeWord(word) {
    return gplay.search({
        term: word,
        num: 4,
        region: region,
        price: 'free',
        fullDetail: true,
        throttle: 0.01,
    });
}

function reader(filepath) {
    return readline.createInterface({
        input: fs.createReadStream(filepath)
    });
}

// //Do processing syncrounously do prevent gplay having a moan
// function processAppData(appsData, processFn) {
//     let index = 0;

//     function next() {
//         if (index < appsData.length) {
//             logger.logger('Processing ', index);
//             processFn(appsData[index++])
//                 .then(next)
//                 .catch((err) => { logger.warning('downloading app failure:', err) });
//         }
//     }
//     next();
// }

function wipe_scraped_word() {
    fs.writeFile(config.datadir + '/scraped_words.txt', '', function(err) {
        if (err) {
            logger.err('Unable wipe the scraped word file');
        }
    });
}

function write_latest_word(word) {
    fs.appendFile(config.datadir + '/scraped_words.txt', word + '\n', function(err) {
        if (err) {
            logger.err('Unable to log to the scraped word file' + err.message);
        }
    });
}

// function topApps() {
//     gplay.category.forEach( cat => {
//         gplay.collection.forEach( coll => {
//             //TODO: this might all happen at once... review owrdStashFiles
//             //finish below off then scrape data + download
//             gplay.list({
//                 collection
//                 category
//                 num: 12,
//                 region: region,
//                 fullDetail: true,
//                 throttle: 0.01
//             }).then( app => {

//                 //TODO: later before begin scraping you do a similar search here

//                 return extractAppData(app);
//             }).catch( err => {
//                 log('Err with word stash',err.message);
//             });

//         });
//     });
// }

function main() {
    wipe_scraped_word();

    let wordStash = config.wordStashDir;
    let wordStashFiles = fs_promise(wordStash);

    // TODO: Refactor this....
    wordStashFiles.then(files => {
        let q = Promise.resolve();

        files.map(file => {
            q = q.then(() => {
                return new Promise((resolve) => {
                    logger.info('Resolving word stash' + wordStash);
                    let filepath = require('path').join(wordStash, file);

                    let rd = reader(filepath);

                    let p = Promise.resolve();

                    rd.on('line', (word) => {
                        p = p.then(() => {
                            logger.info('searching on word:' + word);
                            write_latest_word(word);
                            return scrapeWord(word).catch((err) => logger.err('scraping app on word failed:' + err));
                        }).then(function(appsData) {
                            logger.info('Search apps total: ' + appsData.length);
                            let r = Promise.resolve();

                            appsData.forEach(app => {
                                r = r.then(() => {
                                    logger.info('Attempting to download:' + app.appId);
                                    return extractAppData(app);
                                }, (err) => logger.warning('downloading app failed:' + err));
                            });
                            //processAppData(appsData,extractAppData);

                        }, (err) => logger.err('going through word list failed:' + err));
                    });

                    rd.on('end', () => {
                        p.then(() => resolve());
                        p.catch((err) => logger.err('last data word failed:' + err));
                    });
                });
            }, (err) => logger.err('could no iterate through words in file:' + err));
        }, (err) => logger.err('iterating through dir word list failed::' + err));
    }).catch(function(err) {
        logger.err('Err with word stash' + err.message);
    });
}

//let parse = require('csv-parse');
//let async = require('async');
//TODO: Promise that you'll change this to promises.
/* parse 'Top Words' and download apps based on the search results. */
// NOTE: word csv's are not comma seperated, actually '\n'...
// let parser = parse({ delimiter: ',' }, function(err, data) {
//     async.eachSeries(data, function(currWord, callback) {
//         scrapeWord(currWord).then(appsScraped => {

//             appsScraped.map(app => {
//                 log('Downloading app: '+ app.appId);
//                 downloadAppApk(app);
//             });

//             // when processing finishes invoke the callback to move to the next one
//             callback();
//         });
//     })
// });

// // Loop through all the files in the word stash
// fs.readdir(wordStash, function(err, files) {
//     if (err) {
//         logger.err('Could not list the directory.'+ err);
//         process.exit(1);
//     }

//     files.forEach(file => {
//         let p = require('path');
//         fs.createReadStream(p.join(wordStash,file)).pipe(parser);
//     });
// });
<commit_msg>add promise reject return to word scraping to prevent further errors<commit_after>'use strict';
/*
Scraper script searching store for apps and archiving app data said apps.

NOTE: 120 is max number of apps to receive at once through list method...
NOTE: this has a permission section could inspect when throw this into folder
NOTE: cronjob...

TODO: to avoid the situation of askign for a captcha use a throttle keyword
, all methods now support a throttle property, which defines an upper bound to
the amount of requests that will be attempted per second. Once that limit is reached,
further requests will be held  until the second passes.
*/
const config = require('/etc/xray/config.json'); //See example_config.json
const gplay = require('google-play-scraper');
//Reading from folder of csv files
const fs = require('fs');
const fsEx = require('fs-extra');
const path = require('path');
const fs_promise = require('fs-readdir-promise');
const readline = require('readline');

//Unix pipeline setup
const unix = require('unix-dgram');
const client = unix.createSocket('unix_dgram');

const logger = require('./logger.js');

let appsSaveDir = path.join(config.datadir, 'apps');

function mkdirp(path) {
    path.split(path.sep).reduce((parentDir, childDir) => {
        const curDir = path.join(parentDir, childDir);
        if (!fs.existsSync(curDir)) {
            fs.mkdirSync(curDir);
        }

        return curDir;
    }, path.isAbsolute ? path.sep : '');
}

try {
    let dirStats = fs.statSync(appsSaveDir);
    if (!dirStats.isDirectory()) {
        logger.err('The app directory %s exists but is not a directory!', appsSaveDir);
        process.exit(1);
    }
} catch (err) {
    if (err.code == 'ENOENT') {
        logger.info('Creating apps folder ' + appsSaveDir);
        mkdirp(appsSaveDir);
    } else {
        logger.err('Unknown error opening appsSaveDir:', err.message);
        process.exit(1);
    }
}

let waitForPipe = () => {
    if (!fs.existsSync(config.sockpath)) {
        logger.err('Could not bind to socket... trying again in 1000s');
        setTimeout(waitForPipe, 1000);
    } else {
        main();
    }
}; waitForPipe();

//TODO: move region to config or section to iterate over
let region = 'us';
let appStore = 'play';

function resolveAPKDir(appData) {
    //console.log('appdir:'+ config.datadir, '\nappId'+ appData.appId, '\nappStore'+ appStore, '\nregion'+ region, '\nversion'+ appData.version);

    //log('appdir:'+ config.appdir, '\nappId'+ appData.appId, '\nappStore'+ appStore, '\nregion'+ region, '\nversion'+ appData.version);
    //NOTE: If app version is undefined setting to  date
    if (!appData.version || appData.version === 'Varies with device') {
        logger.debug('Version not found defaulting too', appData.updated);
        let formatDate = appData.updated.replace(/\s+/g, '').replace(',', '/');
        appData.version = formatDate;
    }

    let appSavePath = path.join(appsSaveDir, appData.appId, appStore, region, appData.version);
    logger.info('App desired save dir ' + appSavePath);

    return Promise.all([fsEx.pathExists(appSavePath), Promise.resolve(appSavePath)]);
}

function downloadApp(appData, appSavePath) {
    const args = ['-pd', appData.appId, '-f', appSavePath, '-c', config.credDownload]; /* Command line args for gplay cli */
    const spw = require('child-process-promise').spawn;
    logger.info('Passing args to downloader' + args);
    const apkDownloader = spw('gplaycli', args);

    let downloadProcess = apkDownloader.childProcess;

    mkdirp(appSavePath);
    logger.info('DL process %d for %s-%s started.', downloadProcess.pid, appData.appId, appData.version);

    downloadProcess.stdout.on('data', data => {
        logger.debug('DL process %d stdout:', downloadProcess.pid, data);
    });

    downloadProcess.stderr.on('data', data => {
        logger.warning('DL process %d stderr:', downloadProcess.pid, data);
    });

    return apkDownloader;
}

//TODO: check dir setup before attempting to search on that word

function extractAppData(appData) {
    //Check appData state
    if (!appData.appId) { return Promise.reject('Invalid appdata', appData.appId); }

    return resolveAPKDir(appData).then(appPathInfo => {
        let exists = appPathInfo[0],
            appSavePath = appPathInfo[1];

        if (exists) {
            logger.info('App version %s-%s already exists', appData.appId, appData.version);
            return Promise.reject();
        } else {
            logger.info('New app version %s-%s', appData.appId, appData.version);

            //TODO: append previous and write seperately
            appData.isDownloaded = true;
            return downloadApp(appData, appSavePath).catch((err) => {
                try {
                    fs.rmdir(appSavePath);
                } catch (err) {
                    // TODO: something
                }
                logger.warning('Downloading failed with error:', err.message);
                appData.isDownloaded = false;
            });
        }
    }).then(() => {
        logger.info('Download process complete for ' + appData.appId);

        // TODO: DB Comms... this can be factorised.
        let db = require('./db');
        return db.insertPlayApp(appData, region).catch((err) => {
            logger.err('Inserting play app failed', err, region);
            return err;
        }).catch((err) => logger.err('Could not write to db:', err.message));
    }).then(async (dbId) => {
        // TODO: Check that '-' won't mess things up on the DB side... eg if region was something like 'en-gb'
        let message = Buffer(dbId + '-' + appData.appId + '-' + appStore + '-' + region + '-' + appData.version);

        //client.on('error', logger.err);
        return client.send(message, 0, message.length, config.sockpath).catch((err) => logger.err('Could not connect to socket:', err.message));
    }).catch(function() {
        return;
    });
}

//Base scrapes array apps based on google-play-scraper app json format - PROMISE FORMAT
// function scrape(appsData) {
//     return appsData.map((val) => {
//         return gplay.app({ appId: val.appId }).then(function(some_other_val) {
//             return extractAppData(val).then(() => {
//                 logger('finished downloading', val.appId);
//                 return val; // whatever you return here will get passed on to the next val in the promise chain..
//             }).catch((e) => {
//                 logger.err('error downloading ', val.appId, e.toString());
//                 throw e;
//             });
//         }).catch(function(err) {
//             logger.err('Could not save app ', err);
//             return Promise.reject(appData.appId);
//         });
//     });
// }

/* Get an array of gplay Search results */
function scrapeWord(word) {
    return gplay.search({
        term: word,
        num: 4,
        region: region,
        price: 'free',
        fullDetail: true,
        throttle: 0.01,
    });
}

function reader(filepath) {
    return readline.createInterface({
        input: fs.createReadStream(filepath)
    });
}

// //Do processing syncrounously do prevent gplay having a moan
// function processAppData(appsData, processFn) {
//     let index = 0;

//     function next() {
//         if (index < appsData.length) {
//             logger.logger('Processing ', index);
//             processFn(appsData[index++])
//                 .then(next)
//                 .catch((err) => { logger.warning('downloading app failure:', err) });
//         }
//     }
//     next();
// }

function wipe_scraped_word() {
    fs.writeFile(config.datadir + '/scraped_words.txt', '', function(err) {
        if (err) {
            logger.err('Unable wipe the scraped word file');
        }
    });
}

function write_latest_word(word) {
    fs.appendFile(config.datadir + '/scraped_words.txt', word + '\n', function(err) {
        if (err) {
            logger.err('Unable to log to the scraped word file' + err.message);
        }
    });
}

// function topApps() {
//     gplay.category.forEach( cat => {
//         gplay.collection.forEach( coll => {
//             //TODO: this might all happen at once... review owrdStashFiles
//             //finish below off then scrape data + download
//             gplay.list({
//                 collection
//                 category
//                 num: 12,
//                 region: region,
//                 fullDetail: true,
//                 throttle: 0.01
//             }).then( app => {

//                 //TODO: later before begin scraping you do a similar search here

//                 return extractAppData(app);
//             }).catch( err => {
//                 log('Err with word stash',err.message);
//             });

//         });
//     });
// }

function main() {
    wipe_scraped_word();

    let wordStash = config.wordStashDir;
    let wordStashFiles = fs_promise(wordStash);

    // TODO: Refactor this....
    wordStashFiles.then(files => {
        let q = Promise.resolve();

        files.map(file => {
            q = q.then(() => {
                return new Promise((resolve) => {
                    logger.info('Resolving word stash' + wordStash);
                    let filepath = require('path').join(wordStash, file);

                    let rd = reader(filepath);

                    let p = Promise.resolve();

                    rd.on('line', (word) => {
                        p = p.then(() => {
                            logger.info('searching on word:' + word);
                            write_latest_word(word);
                            return scrapeWord(word).catch((err) => {
                                logger.err('scraping app on word failed:' + err);
                                return Promise.reject();
                            });
                        }).then(function(appsData) {
                            logger.info('Search apps total: ' + appsData.length);
                            let r = Promise.resolve();

                            appsData.forEach(app => {
                                r = r.then(() => {
                                    logger.info('Attempting to download:' + app.appId);
                                    return extractAppData(app);
                                }, (err) => logger.warning('downloading app failed:' + err));
                            });
                            //processAppData(appsData,extractAppData);

                        }, (err) => logger.err('going through word list failed:' + err));
                    });

                    rd.on('end', () => {
                        p.then(() => resolve());
                        p.catch((err) => logger.err('last data word failed:' + err));
                    });
                });
            }, (err) => logger.err('could no iterate through words in file:' + err));
        }, (err) => logger.err('iterating through dir word list failed::' + err));
    }).catch(function(err) {
        logger.err('Err with word stash' + err.message);
    });
}

//let parse = require('csv-parse');
//let async = require('async');
//TODO: Promise that you'll change this to promises.
/* parse 'Top Words' and download apps based on the search results. */
// NOTE: word csv's are not comma seperated, actually '\n'...
// let parser = parse({ delimiter: ',' }, function(err, data) {
//     async.eachSeries(data, function(currWord, callback) {
//         scrapeWord(currWord).then(appsScraped => {

//             appsScraped.map(app => {
//                 log('Downloading app: '+ app.appId);
//                 downloadAppApk(app);
//             });

//             // when processing finishes invoke the callback to move to the next one
//             callback();
//         });
//     })
// });

// // Loop through all the files in the word stash
// fs.readdir(wordStash, function(err, files) {
//     if (err) {
//         logger.err('Could not list the directory.'+ err);
//         process.exit(1);
//     }

//     files.forEach(file => {
//         let p = require('path');
//         fs.createReadStream(p.join(wordStash,file)).pipe(parser);
//     });
// });
<|endoftext|>


<commit_before>// Authentication module for AngularJS
// Copyright (C) 2013 Qloo Inc., Michael Diolosa <michael.diolosa@gmail.com>
// License: MIT

/*global angular:true, browser: true */

(function () {
  'use strict';

  // Private variable for storing identity information.
  var _identity = {},

    // Stores whether the user has been authenticated
    _authenticated = false,

    // authentication
    // --------------
    // Provides the interface for conversing with the authentication API and
    // generating a principal from the authenticated entity's information.
    mod = angular.module('authentication', [])

      // Properties
      // ----------
      // Version
      .constant('version', '1.0.4')

      // principal
      // ---------
      // The authenticated entity, it's identity object is dynamically
      // generated when authentication is successful. It's properties are
      // generated by looping through the data that was returned by the
      // authentication API using the white list of property values that can
      // be configured through `authServiceProvider.identityKeys`.
      .factory('principal', function () {
        return {
          identity: function () { return _identity; },
          isAuthenticated: function () { return _authenticated; }
        };
      })

      // authority
      // ---------
      // The `authority` provides a means for authentication
      .provider('authority', function () {
        // An object that describes how the keys from the API's result are to be
        // translated into the identity object. The key of this object is the
        // key that is returned from the `authorize` method's data; the value of
        // this key is the key that it will exist as on the object that's
        // returned from `principal.identity()`.
        //
        // An easier way to explain this is through some code:
        //
        // We will assume that the authentication API returns a JSON object on
        // success that contains the following value:
        //
        //     {
        //       "username": "mbrio",
        //       "id:": 1001
        //       "date": 1363784671793
        //     }
        //
        // With the default values set to identityKeys then the object
        // returned from `principal.identity()` would be structured as:
        //
        //     {
        //       name: function () { return "mbrio"; },
        //       id: function () { return 1001; }
        //     }
        //
        // Which then could be used in an angular template if set to a `$scope`
        // property:
        //
        //     $scope.user = principal // In the controller
        //
        //     {{user.identity().name}} // In the template
        //
        // As you see the `username` property on the JSON data was interpolated
        // to just `name`, `id` stayed the same, and `date` was ignored.
        this.identityKeys = {
          username: 'name',
          id: 'id'
        };

        // Define the provider's instance
        this.$get = ['$rootScope',
          function ($rootScope) {
            var idKeys = this.identityKeys;

            return {
              // `authorize` is meant to be called from a controller once the
              // user has been authenticated by an external API. It wires up the
              // `principal.identity()` object and then broadcasts the
              // `authority-authorized` event from the `$rootScope`
              authorize: function (data) {
                var key, propertyFn;
                _authenticated = true;

                // Fill our `principal.identity()` object with getter methods
                // to protect the underlying values.
                propertyFn = function (k) {
                  var value = idKeys[k],
                    retval = data[k],
                    ident = {};

                  _identity[value] = function () { return retval; };
                };

                for (key in idKeys) {
                  if (idKeys.hasOwnProperty(key) && data.hasOwnProperty(key)) {
                    propertyFn(key);
                  }
                }

                // Broadcast the authorized event
                $rootScope.$broadcast('event:authority-authorized');
              },

              // `deauthorize` resets the `principal` and `identity`
              deauthorize: function () {
                var key, value;
                _authenticated = false;

                // Delete all the properties of `_identity` for a clean slate.
                for (key in idKeys) {
                  value = idKeys[key];
                  if (idKeys.hasOwnProperty(key) && _identity.hasOwnProperty(value)) {
                    delete _identity[value];
                  }
                }

                // Broadcast the deauthorized event
                $rootScope.$broadcast('event:authority-deauthorized');
              }
            };
          }];
      });
}());
<commit_msg>Remove unnecessary variable<commit_after>// Authentication module for AngularJS
// Copyright (C) 2013 Qloo Inc., Michael Diolosa <michael.diolosa@gmail.com>
// License: MIT

/*global angular:true, browser: true */

(function () {
  'use strict';

  // Private variable for storing identity information.
  var _identity = {},

    // Stores whether the user has been authenticated
    _authenticated = false;

  // authentication
  // --------------
  // Provides the interface for conversing with the authentication API and
  // generating a principal from the authenticated entity's information.
  angular.module('authentication', [])

    // Properties
    // ----------
    // Version
    .constant('version', '1.0.4')

    // principal
    // ---------
    // The authenticated entity, it's identity object is dynamically
    // generated when authentication is successful. It's properties are
    // generated by looping through the data that was returned by the
    // authentication API using the white list of property values that can
    // be configured through `authServiceProvider.identityKeys`.
    .factory('principal', function () {
      return {
        identity: function () { return _identity; },
        isAuthenticated: function () { return _authenticated; }
      };
    })

    // authority
    // ---------
    // The `authority` provides a means for authentication
    .provider('authority', function () {
      // An object that describes how the keys from the API's result are to be
      // translated into the identity object. The key of this object is the
      // key that is returned from the `authorize` method's data; the value of
      // this key is the key that it will exist as on the object that's
      // returned from `principal.identity()`.
      //
      // An easier way to explain this is through some code:
      //
      // We will assume that the authentication API returns a JSON object on
      // success that contains the following value:
      //
      //     {
      //       "username": "mbrio",
      //       "id:": 1001
      //       "date": 1363784671793
      //     }
      //
      // With the default values set to identityKeys then the object
      // returned from `principal.identity()` would be structured as:
      //
      //     {
      //       name: function () { return "mbrio"; },
      //       id: function () { return 1001; }
      //     }
      //
      // Which then could be used in an angular template if set to a `$scope`
      // property:
      //
      //     $scope.user = principal // In the controller
      //
      //     {{user.identity().name}} // In the template
      //
      // As you see the `username` property on the JSON data was interpolated
      // to just `name`, `id` stayed the same, and `date` was ignored.
      this.identityKeys = {
        username: 'name',
        id: 'id'
      };

      // Define the provider's instance
      this.$get = ['$rootScope',
        function ($rootScope) {
          var idKeys = this.identityKeys;

          return {
            // `authorize` is meant to be called from a controller once the
            // user has been authenticated by an external API. It wires up the
            // `principal.identity()` object and then broadcasts the
            // `authority-authorized` event from the `$rootScope`
            authorize: function (data) {
              var key, propertyFn;
              _authenticated = true;

              // Fill our `principal.identity()` object with getter methods
              // to protect the underlying values.
              propertyFn = function (k) {
                var value = idKeys[k],
                  retval = data[k],
                  ident = {};

                _identity[value] = function () { return retval; };
              };

              for (key in idKeys) {
                if (idKeys.hasOwnProperty(key) && data.hasOwnProperty(key)) {
                  propertyFn(key);
                }
              }

              // Broadcast the authorized event
              $rootScope.$broadcast('event:authority-authorized');
            },

            // `deauthorize` resets the `principal` and `identity`
            deauthorize: function () {
              var key, value;
              _authenticated = false;

              // Delete all the properties of `_identity` for a clean slate.
              for (key in idKeys) {
                value = idKeys[key];
                if (idKeys.hasOwnProperty(key) && _identity.hasOwnProperty(value)) {
                  delete _identity[value];
                }
              }

              // Broadcast the deauthorized event
              $rootScope.$broadcast('event:authority-deauthorized');
            }
          };
        }];
    });
}());
<|endoftext|>


<commit_before>"use strict";

/*  ------------------------------------------------------------------------ */

const { isObject, isNumber, isDictionary, isArray } = require ('./type')

/*  ------------------------------------------------------------------------ */

const keys = Object.keys

    , values = x => !isArray (x)  // don't copy arrays if they're already arrays!
                        ? Object.values (x)
                        : x

    , index = x => new Set (values (x))

    , extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free

    , clone = x => isArray (x)
                        ? Array.from (x) // clones arrays
                        : extend (x)     // clones objects

/*  ------------------------------------------------------------------------ */

module.exports =

    { keys
    , values
    , extend
    , clone
    , index
    , ordered: x => x // a stub to keep assoc keys in order (in JS it does nothing, it's mostly for Python)
    , unique:  x => Array.from (index (x))

    /*  .............................................   */

    , inArray (needle, haystack) {

        return haystack.includes (needle)
    }

    , toArray (object) {

        return Object.values (object)
    }

    , isEmpty (object) {
        if (!object)
            return true;
        return (Array.isArray (object) ? object : Object.keys (object)).length < 1;
    }

/*  .............................................   */

    , keysort (x, out = {}) {

        for (const k of keys (x).sort ())
            out[k] = x[k]

        return out
    }

/*  .............................................   */

    /*
       Accepts a map/array of objects and a key name to be used as an index:
       array = [
          { someKey: 'value1', anotherKey: 'anotherValue1' },
          { someKey: 'value2', anotherKey: 'anotherValue2' },
          { someKey: 'value3', anotherKey: 'anotherValue3' },
       ]
       key = 'someKey'

       Returns a map:
      {
          value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
          value2: { someKey: 'value2', anotherKey: 'anotherValue2' },
          value3: { someKey: 'value3', anotherKey: 'anotherValue3' },
      }
    */

    , indexBy (x, k, out = {}) {

        for (const v of values (x))
            if (k in v)
                out[v[k]] = v

        return out
    }

/*  .............................................   */

    /*
       Accepts a map/array of objects and a key name to be used as a grouping parameter:
       array = [
          { someKey: 'value1', anotherKey: 'anotherValue1' },
          { someKey: 'value1', anotherKey: 'anotherValue2' },
          { someKey: 'value3', anotherKey: 'anotherValue3' },
       ]
       key = 'someKey'

       Returns a map:
      {
          value1: [
            { someKey: 'value1', anotherKey: 'anotherValue1' },
            { someKey: 'value1', anotherKey: 'anotherValue2' },
          ]
          value3: [
            { someKey: 'value3', anotherKey: 'anotherValue3' }
          ],
      }
    */

    , groupBy (x, k, out = {}) {

        for (const v of values (x)) {
            if (k in v) {
                const p = v[k]
                out[p] = out[p] || []
                out[p].push (v)
            }
        }
        return out
    }

/*  .............................................   */

    /*
       Accepts a map/array of objects, a key name and a key value to be used as a filter:
       array = [
          { someKey: 'value1', anotherKey: 'anotherValue1' },
          { someKey: 'value2', anotherKey: 'anotherValue2' },
          { someKey: 'value3', anotherKey: 'anotherValue3' },
       ]
       key = 'someKey'
       value = 'value1'

       Returns an array:
      [
          value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
      ]
    */

    , filterBy (x, k, value = undefined, out = []) {

        for (const v of values (x))
            if (v[k] === value)
                out.push (v)

        return out
    }

/*  .............................................   */

    , sortBy: (array, // NB: MUTATES ARRAY!
               key,
               descending = false,
               direction  = descending ? -1 : 1) => array.sort ((a, b) =>
                                                                ((a[key] < b[key]) ? -direction :
                                                                ((a[key] > b[key]) ?  direction : 0)))

/*  .............................................   */

    , flatten: function flatten (x, out = []) {

        for (const v of x) {
            if (isArray (v)) flatten (v, out)
            else out.push (v)
        }

        return out
    }

/*  .............................................   */

    , pluck: (x, k) => values (x)
                        .filter (v => k in v)
                        .map (v => v[k])

/*  .............................................   */

    , omit (x, ...args) {

        const out = clone (x)

        for (const k of args) {

            if (isArray (k)) // omit (x, ['a', 'b'])
                for (const kk of k)
                    delete out[kk]

            else delete out[k] // omit (x, 'a', 'b')
        }

        return out
    }

/*  .............................................   */

    , sum (...xs) {

        const ns = xs.filter (isNumber) // leave only numbers

        return (ns.length > 0)
                    ? ns.reduce ((a, b) => a + b, 0)
                    : undefined
    }

/*  .............................................   */

    , deepExtend: function deepExtend (...xs) {

        let out = undefined

        for (const x of xs) {

            if (isDictionary (x)) {

                if (!isObject (out))
                    out = {}

                for (const k in x)
                    out[k] = deepExtend (out[k], x[k])

            } else out = x
        }

        return out
    }

/*  ------------------------------------------------------------------------ */

}
<commit_msg>generic.js omit guard<commit_after>"use strict";

/*  ------------------------------------------------------------------------ */

const { isObject, isNumber, isDictionary, isArray } = require ('./type')

/*  ------------------------------------------------------------------------ */

const keys = Object.keys

    , values = x => !isArray (x)  // don't copy arrays if they're already arrays!
                        ? Object.values (x)
                        : x

    , index = x => new Set (values (x))

    , extend = (...args) => Object.assign ({}, ...args) // NB: side-effect free

    , clone = x => isArray (x)
                        ? Array.from (x) // clones arrays
                        : extend (x)     // clones objects

/*  ------------------------------------------------------------------------ */

module.exports =

    { keys
    , values
    , extend
    , clone
    , index
    , ordered: x => x // a stub to keep assoc keys in order (in JS it does nothing, it's mostly for Python)
    , unique:  x => Array.from (index (x))

    /*  .............................................   */

    , inArray (needle, haystack) {

        return haystack.includes (needle)
    }

    , toArray (object) {

        return Object.values (object)
    }

    , isEmpty (object) {
        if (!object)
            return true;
        return (Array.isArray (object) ? object : Object.keys (object)).length < 1;
    }

/*  .............................................   */

    , keysort (x, out = {}) {

        for (const k of keys (x).sort ())
            out[k] = x[k]

        return out
    }

/*  .............................................   */

    /*
       Accepts a map/array of objects and a key name to be used as an index:
       array = [
          { someKey: 'value1', anotherKey: 'anotherValue1' },
          { someKey: 'value2', anotherKey: 'anotherValue2' },
          { someKey: 'value3', anotherKey: 'anotherValue3' },
       ]
       key = 'someKey'

       Returns a map:
      {
          value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
          value2: { someKey: 'value2', anotherKey: 'anotherValue2' },
          value3: { someKey: 'value3', anotherKey: 'anotherValue3' },
      }
    */

    , indexBy (x, k, out = {}) {

        for (const v of values (x))
            if (k in v)
                out[v[k]] = v

        return out
    }

/*  .............................................   */

    /*
       Accepts a map/array of objects and a key name to be used as a grouping parameter:
       array = [
          { someKey: 'value1', anotherKey: 'anotherValue1' },
          { someKey: 'value1', anotherKey: 'anotherValue2' },
          { someKey: 'value3', anotherKey: 'anotherValue3' },
       ]
       key = 'someKey'

       Returns a map:
      {
          value1: [
            { someKey: 'value1', anotherKey: 'anotherValue1' },
            { someKey: 'value1', anotherKey: 'anotherValue2' },
          ]
          value3: [
            { someKey: 'value3', anotherKey: 'anotherValue3' }
          ],
      }
    */

    , groupBy (x, k, out = {}) {

        for (const v of values (x)) {
            if (k in v) {
                const p = v[k]
                out[p] = out[p] || []
                out[p].push (v)
            }
        }
        return out
    }

/*  .............................................   */

    /*
       Accepts a map/array of objects, a key name and a key value to be used as a filter:
       array = [
          { someKey: 'value1', anotherKey: 'anotherValue1' },
          { someKey: 'value2', anotherKey: 'anotherValue2' },
          { someKey: 'value3', anotherKey: 'anotherValue3' },
       ]
       key = 'someKey'
       value = 'value1'

       Returns an array:
      [
          value1: { someKey: 'value1', anotherKey: 'anotherValue1' },
      ]
    */

    , filterBy (x, k, value = undefined, out = []) {

        for (const v of values (x))
            if (v[k] === value)
                out.push (v)

        return out
    }

/*  .............................................   */

    , sortBy: (array, // NB: MUTATES ARRAY!
               key,
               descending = false,
               direction  = descending ? -1 : 1) => array.sort ((a, b) =>
                                                                ((a[key] < b[key]) ? -direction :
                                                                ((a[key] > b[key]) ?  direction : 0)))

/*  .............................................   */

    , flatten: function flatten (x, out = []) {

        for (const v of x) {
            if (isArray (v)) flatten (v, out)
            else out.push (v)
        }

        return out
    }

/*  .............................................   */

    , pluck: (x, k) => values (x)
                        .filter (v => k in v)
                        .map (v => v[k])

/*  .............................................   */

    , omit (x, ...args) {

        if (!Array.isArray (x)) {

            const out = clone (x)

            for (const k of args) {

                if (isArray (k)) { // omit (x, ['a', 'b'])

                    for (const kk of k) {
                        delete out[kk]
                    }
                }

                else delete out[k] // omit (x, 'a', 'b')
            }

            return out
        }

        return x
    }

/*  .............................................   */

    , sum (...xs) {

        const ns = xs.filter (isNumber) // leave only numbers

        return (ns.length > 0)
                    ? ns.reduce ((a, b) => a + b, 0)
                    : undefined
    }

/*  .............................................   */

    , deepExtend: function deepExtend (...xs) {

        let out = undefined

        for (const x of xs) {

            if (isDictionary (x)) {

                if (!isObject (out))
                    out = {}

                for (const k in x)
                    out[k] = deepExtend (out[k], x[k])

            } else out = x
        }

        return out
    }

/*  ------------------------------------------------------------------------ */

}
<|endoftext|>


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

/**
 * Reset All button.  This version is drawn in code using shapes, gradients,
 * and such, and does not use any image files.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // modules
  var Color = require( 'SCENERY/util/Color' );
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var RoundPushButton = require( 'SUN/buttons/RoundPushButton' );
  var ResetAllShape = require( 'SCENERY_PHET/ResetAllShape' );

  var resetAllNameString = require( 'string!SCENERY_PHET/ResetAllButton.name' );

  // Constants
  var DEFAULT_RADIUS = 24; // Derived from images initially used for reset button.

  /**
   * @param {Object} [options]
   * @constructor
   */
  function ResetAllButton( options ) {
    var buttonRadius = ( options && options.radius ) ? options.radius : DEFAULT_RADIUS;
    options = _.extend( {
      // Default values
      radius: DEFAULT_RADIUS,
      minXMargin: buttonRadius * 0.2,

      // Default orange color scheme, standard for PhET reset buttons
      baseColor: new Color( 247, 151, 34 ),

      // The arrow shape doesn't look right when perfectly centered, account
      // for that here, and see docs in RoundButtonView.  The multiplier
      // values were empirically determined.
      xContentOffset: buttonRadius * 0.03,
      yContentOffset: buttonRadius * ( -0.0125 ),
      textDescription: 'Reset All Button',

      // Marker entry to indicate that tandem is supported (in the parent)
      tandem: null
    }, options );

    var icon = new Path( new ResetAllShape( options.radius ), { fill: 'white' } );

    RoundPushButton.call( this, _.extend( {
      content: icon,
      accessibleContent: {
        focusHighlight: new Shape().circle( 0, 0, buttonRadius ),
        createPeer: function( accessibleInstance ) {
          // will look like <input value="Reset" type="button" tabindex="0">
          var domElement = document.createElement( 'input' );
          domElement.value = resetAllNameString;
          domElement.type = 'button';

          domElement.tabIndex = '0';

          domElement.addEventListener( 'click', function() {
            options.listener();
          } );

          return new AccessiblePeer( accessibleInstance, domElement );
        }
      }
    }, options ) );
  }

  return inherit( RoundPushButton, ResetAllButton );
} );
<commit_msg>Change button type in ResetAllButton accessible content.<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Reset All button.  This version is drawn in code using shapes, gradients,
 * and such, and does not use any image files.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // modules
  var Color = require( 'SCENERY/util/Color' );
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Path = require( 'SCENERY/nodes/Path' );
  var Shape = require( 'KITE/Shape' );
  var RoundPushButton = require( 'SUN/buttons/RoundPushButton' );
  var ResetAllShape = require( 'SCENERY_PHET/ResetAllShape' );

  var resetAllNameString = require( 'string!SCENERY_PHET/ResetAllButton.name' );

  // Constants
  var DEFAULT_RADIUS = 24; // Derived from images initially used for reset button.

  /**
   * @param {Object} [options]
   * @constructor
   */
  function ResetAllButton( options ) {
    var buttonRadius = ( options && options.radius ) ? options.radius : DEFAULT_RADIUS;
    options = _.extend( {
      // Default values
      radius: DEFAULT_RADIUS,
      minXMargin: buttonRadius * 0.2,

      // Default orange color scheme, standard for PhET reset buttons
      baseColor: new Color( 247, 151, 34 ),

      // The arrow shape doesn't look right when perfectly centered, account
      // for that here, and see docs in RoundButtonView.  The multiplier
      // values were empirically determined.
      xContentOffset: buttonRadius * 0.03,
      yContentOffset: buttonRadius * ( -0.0125 ),
      textDescription: 'Reset All Button',

      // Marker entry to indicate that tandem is supported (in the parent)
      tandem: null
    }, options );

    var icon = new Path( new ResetAllShape( options.radius ), { fill: 'white' } );

    RoundPushButton.call( this, _.extend( {
      content: icon,
      accessibleContent: {
        focusHighlight: new Shape().circle( 0, 0, buttonRadius ),
        createPeer: function( accessibleInstance ) {
          // will look like <input value="Reset" type="reset" tabindex="0">
          var domElement = document.createElement( 'input' );
          domElement.value = resetAllNameString;
          domElement.type = 'reset';

          domElement.tabIndex = '0';

          domElement.addEventListener( 'click', function() {
            options.listener();
          } );

          return new AccessiblePeer( accessibleInstance, domElement );
        }
      }
    }, options ) );
  }

  return inherit( RoundPushButton, ResetAllButton );
} );
<|endoftext|>


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

/**
 * Push button with text on a rectangle.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Font = require( 'SCENERY/util/Font' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var sun = require( 'SUN/sun' );
  var Text = require( 'SCENERY/nodes/Text' );

  /**
   * @param {String} text
   * @param {Object} [options]
   * @constructor
   */
  function TextPushButton( text, options ) {

    options = _.extend( {
      font: new Font( 20 ),
      textFill: 'black',
      maxTextWidth: null
    }, options );

    var textNode = new Text( text, { font: options.font, fill: options.textFill, maxWidth: options.maxTextWidth } );
    RectangularPushButton.call( this, _.extend( { content: textNode }, options ) );
  }

  sun.register( 'TextPushButton', TextPushButton );

  return inherit( RectangularPushButton, TextPushButton );
} );
<commit_msg>Capitalization of types in JSDoc (https://github.com/phetsims/tasks/issues/683)<commit_after>// Copyright 2014-2015, University of Colorado Boulder

/**
 * Push button with text on a rectangle.
 *
 * @author John Blanco
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var Font = require( 'SCENERY/util/Font' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var sun = require( 'SUN/sun' );
  var Text = require( 'SCENERY/nodes/Text' );

  /**
   * @param {string} text
   * @param {Object} [options]
   * @constructor
   */
  function TextPushButton( text, options ) {

    options = _.extend( {
      font: new Font( 20 ),
      textFill: 'black',
      maxTextWidth: null
    }, options );

    var textNode = new Text( text, { font: options.font, fill: options.textFill, maxWidth: options.maxTextWidth } );
    RectangularPushButton.call( this, _.extend( { content: textNode }, options ) );
  }

  sun.register( 'TextPushButton', TextPushButton );

  return inherit( RectangularPushButton, TextPushButton );
} );
<|endoftext|>


<commit_before>import React, {
    Component, PropTypes as PT
}                               from "react"
import {
    ListView, Text, View,
    Image, TouchableOpacity
}                               from "react-native"
import moment				   from "moment"
import myTheme                  from '../../themes/base-theme'
import styles                   from "./style"

class Movie extends Component {
    static propTypes = {
        image:  PT.string,
        title:  PT.string,
        hours:  PT.array,
        detail: PT.func
    }

    render() {
        const { image, title, hours, detail } = this.props

        return (
            <TouchableOpacity
                onPress={() => detail()}
                activeOpacity={OPACITY}
                underlayColor={myTheme.primary}>
                <View style={styles.containerList}>
                    <Image
                        source={{uri: image}}
                        defaultSource={require('../../images/backdrop.png')}
                        style={styles.image}>
                        <View style={styles.textIntoImage}>
                            <Text
                                style={styles.title}
                                numberOfLines={1}>
                                {title}
                            </Text>
                            <View style={styles.hours}>
                                <Text
                                    style={styles.hour}
                                    numberOfLines={1}>
                                    { hours.map((hour, i) => `${moment(hour).format('HH:mm')}  `) }
                                </Text>
                            </View>
                        </View>
                    </Image>
                </View>
            </TouchableOpacity>
        )
    }
}

const OPACITY = 0.75

export default Movie
<commit_msg>Adjusted indentation<commit_after>import React, {
    Component, PropTypes as PT
} from "react"
import {
    ListView, Text, View,
    Image, TouchableOpacity
} from "react-native"
import moment from "moment"
import myTheme from '../../themes/base-theme'
import styles from "./style"

class Movie extends Component {
    static propTypes = {
        image: PT.string,
        title: PT.string,
        hours: PT.array,
        detail: PT.func
    }

    render() {
        const { image, title, hours, detail } = this.props

        return (
            <TouchableOpacity
                onPress={() => detail()}
                activeOpacity={OPACITY}
                underlayColor={myTheme.primary}>
                <View style={styles.containerList}>
                    <Image
                        source={{uri: image}}
                        defaultSource={require('../../images/backdrop.png')}
                        style={styles.image}>
                        <View style={styles.textIntoImage}>
                            <Text
                                style={styles.title}
                                numberOfLines={1}>
                                {title}
                            </Text>
                            <View style={styles.hours}>
                                <Text
                                    style={styles.hour}>
                                    { hours.map((hour, i) => `${moment(hour).format('HH:mm')}  `) }
                                </Text>
                            </View>
                        </View>
                    </Image>
                </View>
            </TouchableOpacity>
        )
    }
}

const OPACITY = 0.75

export default Movie
<|endoftext|>


<commit_before>'use strict';

define(['./module', 'qrcodejs'], function (directives, QRCode) {
  directives.directive('qr', ['$window', function($window) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var qrcode = new QRCode(element[0], {
          width : 1000,
          height : 1000,
          correctLevel : QRCode.CorrectLevel.H,
        });
        qrcode.makeCode(attrs.data);
        if (attrs.width) element[0].lastChild.setAttribute('width', attrs.width);
        if (attrs.height) element[0].lastChild.setAttribute('height', attrs.height);
      }
    };
  }]);
});
<commit_msg>Using width of 512 instead 1000px in qr.<commit_after>'use strict';

define(['./module', 'qrcodejs'], function (directives, QRCode) {
  directives.directive('qr', ['$window', function($window) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        var qrcode = new QRCode(element[0], {
          width : 512,
          height : 512,
          correctLevel : QRCode.CorrectLevel.H,
        });
        qrcode.makeCode(attrs.data);
        if (attrs.width) element[0].lastChild.setAttribute('width', attrs.width);
        if (attrs.height) element[0].lastChild.setAttribute('height', attrs.height);
      }
    };
  }]);
});
<|endoftext|>