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

/**
 * An immutable quadratic, described by the equation y = ax^2 + bx + c.
 *
 * Typically, a quadratic requires a !== 0. But this sim is required to support a === 0.
 * So there is some non-standard behavior herein that is not exactly mathematically correct.
 * Specifically:
 *
 * (1) When a === 0, Quadratic behaves like a straight line.  See for example solveX.
 *
 * (2) When a === 0, y = c (standard form) and y = k (vertex form) describe the same horizontal line.
 *     See for example createFromVertexForm.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Color = require( 'SCENERY/util/Color' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  const Util = require( 'DOT/Util' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const EPSILON = 0.01;  // how close is considered to be "on" the quadratic

  class Quadratic {

    /**
     * @param {number} a - coefficient for the quadratic term
     * @param {number} b - coefficient for the linear term
     * @param {number} c - constant term
     * @param {Object} [options]
     */
    constructor( a, b, c, options ) {

      options = _.extend( {
        color: 'black' // {Color|string} color used to render the curve
      }, options );

      // @public (read-only)
      this.a = a;
      this.b = b;
      this.c = c;
      this.color = options.color;

      // @public (read-only) {Vector2[]|null} null means that all points are roots (y = 0)
      this.roots = solveRoots( a, b, c );

      // Strictly speaking, we don't have a quadratic if a === 0.
      // If that's the case, then the fields in this if block will be undefined.
      if ( a !== 0 ) {

        // @public (read-only) derive coefficients for vertex form, y = (1/(4p))(x - h)^2 + k
        this.p = 1 / ( 4 * a );
        this.h = -b / ( 2 * a );
        this.k = c - ( ( b * b ) / ( 4 * a ) );

        // @public (read-only)
        this.vertex = new Vector2( this.h, this.k );
        this.focus = new Vector2( this.h, this.k + this.p );
        this.directrix = this.k - this.p; // y = directrix
        this.axisOfSymmetry = this.h; // x = h
      }
      else {
        this.k = c; // to support y = k when a === 0 in vertex form
      }
    }

    /**
     * Returns a copy of this Quadratic with a specified color.
     * @param {Color|string} color
     * @returns {Quadratic}
     * @public
     */
    withColor( color ) {
      return new Quadratic( this.a, this.b, this.c, { color: color } );
    }

    /**
     * Creates a Quadratic based on the vertex form equation y = a(x - h)^2 + k
     * @param {number} a
     * @param {number} h
     * @param {number} k
     * @param {Object} [options] - see Quadratic constructor
     * @returns {Quadratic}
     * @public
     */
    static createFromVertexForm( a, h, k, options ) {
      if ( a !== 0 ) {
        const b = -2 * a * h;
        const c = ( a * h * h ) + k;
        return new Quadratic( a, b, c, options );
      }
      else {
        // to support y = k when a === 0 in vertex form
        return new Quadratic( 0, 0, k, options );
      }
    }

    /**
     * Creates a Quadratic based on the equation y = (1/(4p))(x - h)^2 + k
     * This is an alternate vertex form, where 1/(4p) is substituted for a.
     * @param {number} p
     * @param {number} h
     * @param {number} k
     * @param {Object} [options] - see Quadratic constructor
     * @returns {Quadratic}
     * @public
     */
    static createFromAlternateVertexForm( p, h, k, options ) {
      const a = 1 / ( 4 * p );
      return Quadratic.createFromVertexForm( a, h, k, options );
    }

    /**
     * Encodes a Quadratic instance as a PhET-iO state object
     * @returns {*}
     * @public
     */
    toStateObject() {
      return {

        // These properties are sufficient to restore a Quadratic, see fromStateObject.
        a: this.a,
        b: this.b,
        c: this.c,
        color: Color.toColor( this.color ).toStateObject(),

        // These properties are desired in the event stream, but will be undefined for non-parabolas (a===0).
        // Because PhET-iO values are based on JSON.stringify, undefined properties will not be present in the
        // event stream.
        p: this.p,
        h: this.h,
        k: this.k,
        vertex: ( this.vertex ? this.vertex.toStateObject() : undefined ),
        focus: ( this.focus ? this.focus.toStateObject() : undefined ),
        directrix: this.directrix,
        axisOfSymmetry: this.axisOfSymmetry
      };
    }

    /**
     * Decodes a PhET-iO state object and produces a Quadratic instance
     * @param {*} object
     * @returns {Quadratic}
     * @public
     */
    static fromStateObject( object ) {
      return new Quadratic( object.a, object.b, object.c, {
        color: Color.fromStateObject( object.color )
      } );
    }

    /**
     * Returns a string representation of this Quadratic. For debugging only, do not rely on format!
     * @returns {string}
     * @public
     */
    toString() {
      return StringUtils.fillIn( 'Quadratic {{a}}x^2 + {{b}}x + {{c}}, color={{color}}', {
        a: this.a,
        b: this.b,
        c: this.c,
        color: this.color
      } );
    }

    /**
     * Tests whether this quadratic is equal to the given quadratic. Color is not significant.
     * @param {*} quadratic
     * @returns {boolean}
     * @public
     */
    equals( quadratic ) {
      return ( quadratic instanceof Quadratic ) &&
             ( this.a === quadratic.a ) && ( this.b === quadratic.b ) && ( this.c === quadratic.c );
    }

    /**
     * Gets the quadratic term, y = ax^2
     * @returns {Quadratic}
     * @public
     */
    getQuadraticTerm() {
      return new Quadratic( this.a, 0, 0, { color: GQColors.QUADRATIC_TERM } );
    }

    /**
     * Gets the linear term, y = bx
     * @returns {Quadratic}
     * @public
     */
    getLinearTerm() {
      return new Quadratic( 0, this.b, 0, { color: GQColors.LINEAR_TERM } );
    }

    /**
     * Gets the constant term, y = c
     * @returns {Quadratic}
     * @public
     */
    getConstantTerm() {
      return new Quadratic( 0, 0, this.c, { color: GQColors.CONSTANT_TERM } );
    }

    //TODO this appears to have problems, new Quadratic(1,1,1).solveX( 0.5 ) => [NaN,NaN]
    /**
     * Given y, solve for x.  If there is more than on solution, they will be in ascending order.
     * @param {number} y
     * @returns {number[]} - one or more solutions
     * @public
     */
    solveX( y ) {
      if ( this.a !== 0 ) {

        // For a parabola, use vertex form.
        // y = a(x - h)^2 + k => x = h +- Math.sqrt((y - k)/a)
        // This yields 2 solutions
        const commonTerm = Math.sqrt( ( y - this.k ) / this.a );
        const x0 = this.h - commonTerm;
        const x1 = this.h + commonTerm;
        return [ x0, x1 ].sort( ( x0, x1 ) => x0 - x1 ); // in ascending order
      }
      else {
        // For a straight line, use slope-intercept form.
        // y = bx + c => x = (y - c)/b
        // This yields one solution.
        const x0 = ( y - this.c ) / this.b;
        return [ x0 ];
      }
    }

    /**
     * Given x, solve for y.
     * @param {number} x
     * @returns {number}
     * @public
     */
    solveY( x ) {
      return ( this.a * x * x ) + ( this.b * x ) + this.c; // y = ax^2 + bx + c
    }

    /**
     * Gets the slope of the tangent line at point (x,f(x)) on the quadratic.
     * @param {number} x
     * @returns {number}
     */
    getTangentSlope( x ) {
      assert && assert( this.a !== 0, 'not supported for non-parabola' );
      return ( 2 * this.a * x ) + this.b; // first derivative
    }

    /**
     * Gets the control points for the Bezier curve that describes this quadratic.
     * See https://github.com/phetsims/graphing-quadratics/issues/1
     * @param {Range} xRange - range of the graph's x axis
     * @returns {startPoint:Vector2, controlPoint:Vector2, endPoint:Vector2}
     * @public
     */
    getControlPoints( xRange ) {

      const minX = xRange.min;
      const maxX = xRange.max;
      const length = xRange.getLength();

      const aPrime = this.a * length * length;
      const bPrime = ( 2 * this.a * minX * length ) + ( this.b * length );
      const cPrime = ( this.a * minX * minX ) + ( this.b * minX ) + this.c;

      const startPoint = new Vector2( minX, cPrime );
      const controlPoint = new Vector2( ( minX + maxX ) / 2, bPrime / 2 + cPrime );
      const endPoint = new Vector2( maxX, aPrime + bPrime + cPrime );

      return { startPoint, controlPoint, endPoint }; // caution! ES6 object shorthand
    }

    /**
     * Does a specified point lie on this quadratic?
     * @param {Vector2} point
     * @returns {boolean}
     * @public
     */
    isOnQuadratic( point ) {
      return ( Math.abs( this.solveY( point.x ) - point.y ) < EPSILON );
    }

    /**
     * Gets the point on this quadratic that is closest to a specified point.
     * @param {Vector2} point
     * @returns {Vector2}
     * @public
     */
    getClosestPoint( point ) {

      // to improve readability
      const x0 = point.x;
      const y0 = point.y;
      const a = this.a;
      const b = this.b;
      const c = this.c;

      // Finding the closest point requires solving the cubic equation
      // (2a^2)x^3 + (3ab)x^2 + (b^2 + 2ac - 2ay0 + 1)x + (bc - by0 - x0) = 0
      // See http://mathworld.wolfram.com/Point-QuadraticDistance.html
      const roots = Util.solveCubicRootsReal(
        2 * a * a,
        3 * a * b,
        b * b + 2 * a * c - 2 * a * y0 + 1,
        b * c - b * y0 - x0
      );
      assert && assert( roots, 'all values are roots' );
      assert && assert( roots.length > 0, 'unexpected number of roots: ' + roots.length );

      // Determine which solution is closest to point (x0,y0)
      let rootPoint;
      let nearestPoint = new Vector2( roots[ 0 ], this.solveY( roots[ 0 ] ) );
      for ( let i = 1; i < roots.length; i++ ) {
        rootPoint = new Vector2( roots[ i ], this.solveY( roots[ i ] ) );
        if ( rootPoint.distance( point ) < nearestPoint.distance( point ) ) {
          nearestPoint = rootPoint;
        }
      }

      return nearestPoint;
    }

    /**
     * Given x, find the closest (x,y) point on the quadratic that is in range.
     * @param {number} x
     * @param {Range} xRange
     * @param {Range} yRange
     * @returns {Vector2}
     */
    getClosestPointInRange( x, xRange, yRange ) {

      // constrain x and solve for y
      x = xRange.constrainValue( x );
      let y = this.solveY( x );

      if ( !yRange.contains( y ) ) {

        // y is outside range, constrain y and solve for x
        y = yRange.constrainValue( y );
        const xValues = this.solveX( y );

        if ( this.a !== 0 ) {

          //TODO does this work correctly when x === this.vertex.x?
          // parabola
          assert && assert( xValues.length === 2, 'unexpected number of xValues: ' + xValues );
          assert && assert( xValues[ 0 ] < xValues[ 1 ], 'unexpected order of xValues: ' + xValues );
          x = ( x < this.vertex.x ) ? xValues[ 0 ] : xValues[ 1 ];
        }
        else {

          //straight line
          assert && assert( xValues.length === 1, 'unexpected number of xValues: ' + xValues );
          x = xValues[ 0 ];
        }
      }

      return new Vector2( x, y );
    }
  }

  /**
   * Returns the real roots of the quadratic y = ax^2 + bx + c.
   * If there is more than one roots, they will be in ascending order of x coordinate.
   * @param {number} a
   * @param {number} b
   * @param {number} c
   * @returns {Vector2[]|null} null means that all points are roots (y = 0)
   */
  function solveRoots( a, b, c ) {
    let roots = null;
    let xCoordinates = Util.solveQuadraticRootsReal( a, b, c );
    if ( xCoordinates !== null ) {
      roots = [];
      xCoordinates = xCoordinates.sort( ( x0, x1 ) => x0 - x1 ); // in ascending order
      _.uniq( xCoordinates ).forEach( x => { roots.push( new Vector2( x, 0 ) ); } );
    }
    assert && assert( roots === null || ( roots.length >= 0 && roots.length <= 2 ), 'unexpected roots: ' + roots );
    return roots;
  }

  /**
   * Documentation for the association IO type, QuadraticIO.  This appears in PhET-iO Studio.
   * Defined here so that specifics about Quadratic appear in one place.
   * @type {string}
   * @public
   */
  Quadratic.documentationQuadraticIO =
    'QuadraticIO is a data structure that describes a quadratic equation in the model. ' +
    'Its properties are relevant to standard and vertex forms of the quadratic equation. ' +
    'Non-parabolas (a=0) will have a subset of the properties that parabolas have. ' +
    '
' +
    'Required properties are related to the standard form y = ax2 + bx + c, and include:' +
    '
' +
    '
' +
    '
a: {number} coefficient a
' +
    '
b: {number} coefficient b
' +
    '
c: {number} coefficient c
' +
    '
color: {Color} the color used to draw the associated line
' +
    '
' +
    'All coefficient values must respect the ranges for those coefficients.';

  return graphingQuadratics.register( 'Quadratic', Quadratic );
} );<commit_msg>use {NumberIO} and {ColorIO} in documentation for QuadraticIO, #38<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * An immutable quadratic, described by the equation y = ax^2 + bx + c.
 *
 * Typically, a quadratic requires a !== 0. But this sim is required to support a === 0.
 * So there is some non-standard behavior herein that is not exactly mathematically correct.
 * Specifically:
 *
 * (1) When a === 0, Quadratic behaves like a straight line.  See for example solveX.
 *
 * (2) When a === 0, y = c (standard form) and y = k (vertex form) describe the same horizontal line.
 *     See for example createFromVertexForm.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( require => {
  'use strict';

  // modules
  const Color = require( 'SCENERY/util/Color' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  const Util = require( 'DOT/Util' );
  const Vector2 = require( 'DOT/Vector2' );

  // constants
  const EPSILON = 0.01;  // how close is considered to be "on" the quadratic

  class Quadratic {

    /**
     * @param {number} a - coefficient for the quadratic term
     * @param {number} b - coefficient for the linear term
     * @param {number} c - constant term
     * @param {Object} [options]
     */
    constructor( a, b, c, options ) {

      options = _.extend( {
        color: 'black' // {Color|string} color used to render the curve
      }, options );

      // @public (read-only)
      this.a = a;
      this.b = b;
      this.c = c;
      this.color = options.color;

      // @public (read-only) {Vector2[]|null} null means that all points are roots (y = 0)
      this.roots = solveRoots( a, b, c );

      // Strictly speaking, we don't have a quadratic if a === 0.
      // If that's the case, then the fields in this if block will be undefined.
      if ( a !== 0 ) {

        // @public (read-only) derive coefficients for vertex form, y = (1/(4p))(x - h)^2 + k
        this.p = 1 / ( 4 * a );
        this.h = -b / ( 2 * a );
        this.k = c - ( ( b * b ) / ( 4 * a ) );

        // @public (read-only)
        this.vertex = new Vector2( this.h, this.k );
        this.focus = new Vector2( this.h, this.k + this.p );
        this.directrix = this.k - this.p; // y = directrix
        this.axisOfSymmetry = this.h; // x = h
      }
      else {
        this.k = c; // to support y = k when a === 0 in vertex form
      }
    }

    /**
     * Returns a copy of this Quadratic with a specified color.
     * @param {Color|string} color
     * @returns {Quadratic}
     * @public
     */
    withColor( color ) {
      return new Quadratic( this.a, this.b, this.c, { color: color } );
    }

    /**
     * Creates a Quadratic based on the vertex form equation y = a(x - h)^2 + k
     * @param {number} a
     * @param {number} h
     * @param {number} k
     * @param {Object} [options] - see Quadratic constructor
     * @returns {Quadratic}
     * @public
     */
    static createFromVertexForm( a, h, k, options ) {
      if ( a !== 0 ) {
        const b = -2 * a * h;
        const c = ( a * h * h ) + k;
        return new Quadratic( a, b, c, options );
      }
      else {
        // to support y = k when a === 0 in vertex form
        return new Quadratic( 0, 0, k, options );
      }
    }

    /**
     * Creates a Quadratic based on the equation y = (1/(4p))(x - h)^2 + k
     * This is an alternate vertex form, where 1/(4p) is substituted for a.
     * @param {number} p
     * @param {number} h
     * @param {number} k
     * @param {Object} [options] - see Quadratic constructor
     * @returns {Quadratic}
     * @public
     */
    static createFromAlternateVertexForm( p, h, k, options ) {
      const a = 1 / ( 4 * p );
      return Quadratic.createFromVertexForm( a, h, k, options );
    }

    /**
     * Encodes a Quadratic instance as a PhET-iO state object
     * @returns {*}
     * @public
     */
    toStateObject() {
      return {

        // These properties are sufficient to restore a Quadratic, see fromStateObject.
        a: this.a,
        b: this.b,
        c: this.c,
        color: Color.toColor( this.color ).toStateObject(),

        // These properties are desired in the event stream, but will be undefined for non-parabolas (a===0).
        // Because PhET-iO values are based on JSON.stringify, undefined properties will not be present in the
        // event stream.
        p: this.p,
        h: this.h,
        k: this.k,
        vertex: ( this.vertex ? this.vertex.toStateObject() : undefined ),
        focus: ( this.focus ? this.focus.toStateObject() : undefined ),
        directrix: this.directrix,
        axisOfSymmetry: this.axisOfSymmetry
      };
    }

    /**
     * Decodes a PhET-iO state object and produces a Quadratic instance
     * @param {*} object
     * @returns {Quadratic}
     * @public
     */
    static fromStateObject( object ) {
      return new Quadratic( object.a, object.b, object.c, {
        color: Color.fromStateObject( object.color )
      } );
    }

    /**
     * Returns a string representation of this Quadratic. For debugging only, do not rely on format!
     * @returns {string}
     * @public
     */
    toString() {
      return StringUtils.fillIn( 'Quadratic {{a}}x^2 + {{b}}x + {{c}}, color={{color}}', {
        a: this.a,
        b: this.b,
        c: this.c,
        color: this.color
      } );
    }

    /**
     * Tests whether this quadratic is equal to the given quadratic. Color is not significant.
     * @param {*} quadratic
     * @returns {boolean}
     * @public
     */
    equals( quadratic ) {
      return ( quadratic instanceof Quadratic ) &&
             ( this.a === quadratic.a ) && ( this.b === quadratic.b ) && ( this.c === quadratic.c );
    }

    /**
     * Gets the quadratic term, y = ax^2
     * @returns {Quadratic}
     * @public
     */
    getQuadraticTerm() {
      return new Quadratic( this.a, 0, 0, { color: GQColors.QUADRATIC_TERM } );
    }

    /**
     * Gets the linear term, y = bx
     * @returns {Quadratic}
     * @public
     */
    getLinearTerm() {
      return new Quadratic( 0, this.b, 0, { color: GQColors.LINEAR_TERM } );
    }

    /**
     * Gets the constant term, y = c
     * @returns {Quadratic}
     * @public
     */
    getConstantTerm() {
      return new Quadratic( 0, 0, this.c, { color: GQColors.CONSTANT_TERM } );
    }

    //TODO this appears to have problems, new Quadratic(1,1,1).solveX( 0.5 ) => [NaN,NaN]
    /**
     * Given y, solve for x.  If there is more than on solution, they will be in ascending order.
     * @param {number} y
     * @returns {number[]} - one or more solutions
     * @public
     */
    solveX( y ) {
      if ( this.a !== 0 ) {

        // For a parabola, use vertex form.
        // y = a(x - h)^2 + k => x = h +- Math.sqrt((y - k)/a)
        // This yields 2 solutions
        const commonTerm = Math.sqrt( ( y - this.k ) / this.a );
        const x0 = this.h - commonTerm;
        const x1 = this.h + commonTerm;
        return [ x0, x1 ].sort( ( x0, x1 ) => x0 - x1 ); // in ascending order
      }
      else {
        // For a straight line, use slope-intercept form.
        // y = bx + c => x = (y - c)/b
        // This yields one solution.
        const x0 = ( y - this.c ) / this.b;
        return [ x0 ];
      }
    }

    /**
     * Given x, solve for y.
     * @param {number} x
     * @returns {number}
     * @public
     */
    solveY( x ) {
      return ( this.a * x * x ) + ( this.b * x ) + this.c; // y = ax^2 + bx + c
    }

    /**
     * Gets the slope of the tangent line at point (x,f(x)) on the quadratic.
     * @param {number} x
     * @returns {number}
     */
    getTangentSlope( x ) {
      assert && assert( this.a !== 0, 'not supported for non-parabola' );
      return ( 2 * this.a * x ) + this.b; // first derivative
    }

    /**
     * Gets the control points for the Bezier curve that describes this quadratic.
     * See https://github.com/phetsims/graphing-quadratics/issues/1
     * @param {Range} xRange - range of the graph's x axis
     * @returns {startPoint:Vector2, controlPoint:Vector2, endPoint:Vector2}
     * @public
     */
    getControlPoints( xRange ) {

      const minX = xRange.min;
      const maxX = xRange.max;
      const length = xRange.getLength();

      const aPrime = this.a * length * length;
      const bPrime = ( 2 * this.a * minX * length ) + ( this.b * length );
      const cPrime = ( this.a * minX * minX ) + ( this.b * minX ) + this.c;

      const startPoint = new Vector2( minX, cPrime );
      const controlPoint = new Vector2( ( minX + maxX ) / 2, bPrime / 2 + cPrime );
      const endPoint = new Vector2( maxX, aPrime + bPrime + cPrime );

      return { startPoint, controlPoint, endPoint }; // caution! ES6 object shorthand
    }

    /**
     * Does a specified point lie on this quadratic?
     * @param {Vector2} point
     * @returns {boolean}
     * @public
     */
    isOnQuadratic( point ) {
      return ( Math.abs( this.solveY( point.x ) - point.y ) < EPSILON );
    }

    /**
     * Gets the point on this quadratic that is closest to a specified point.
     * @param {Vector2} point
     * @returns {Vector2}
     * @public
     */
    getClosestPoint( point ) {

      // to improve readability
      const x0 = point.x;
      const y0 = point.y;
      const a = this.a;
      const b = this.b;
      const c = this.c;

      // Finding the closest point requires solving the cubic equation
      // (2a^2)x^3 + (3ab)x^2 + (b^2 + 2ac - 2ay0 + 1)x + (bc - by0 - x0) = 0
      // See http://mathworld.wolfram.com/Point-QuadraticDistance.html
      const roots = Util.solveCubicRootsReal(
        2 * a * a,
        3 * a * b,
        b * b + 2 * a * c - 2 * a * y0 + 1,
        b * c - b * y0 - x0
      );
      assert && assert( roots, 'all values are roots' );
      assert && assert( roots.length > 0, 'unexpected number of roots: ' + roots.length );

      // Determine which solution is closest to point (x0,y0)
      let rootPoint;
      let nearestPoint = new Vector2( roots[ 0 ], this.solveY( roots[ 0 ] ) );
      for ( let i = 1; i < roots.length; i++ ) {
        rootPoint = new Vector2( roots[ i ], this.solveY( roots[ i ] ) );
        if ( rootPoint.distance( point ) < nearestPoint.distance( point ) ) {
          nearestPoint = rootPoint;
        }
      }

      return nearestPoint;
    }

    /**
     * Given x, find the closest (x,y) point on the quadratic that is in range.
     * @param {number} x
     * @param {Range} xRange
     * @param {Range} yRange
     * @returns {Vector2}
     */
    getClosestPointInRange( x, xRange, yRange ) {

      // constrain x and solve for y
      x = xRange.constrainValue( x );
      let y = this.solveY( x );

      if ( !yRange.contains( y ) ) {

        // y is outside range, constrain y and solve for x
        y = yRange.constrainValue( y );
        const xValues = this.solveX( y );

        if ( this.a !== 0 ) {

          //TODO does this work correctly when x === this.vertex.x?
          // parabola
          assert && assert( xValues.length === 2, 'unexpected number of xValues: ' + xValues );
          assert && assert( xValues[ 0 ] < xValues[ 1 ], 'unexpected order of xValues: ' + xValues );
          x = ( x < this.vertex.x ) ? xValues[ 0 ] : xValues[ 1 ];
        }
        else {

          //straight line
          assert && assert( xValues.length === 1, 'unexpected number of xValues: ' + xValues );
          x = xValues[ 0 ];
        }
      }

      return new Vector2( x, y );
    }
  }

  /**
   * Returns the real roots of the quadratic y = ax^2 + bx + c.
   * If there is more than one roots, they will be in ascending order of x coordinate.
   * @param {number} a
   * @param {number} b
   * @param {number} c
   * @returns {Vector2[]|null} null means that all points are roots (y = 0)
   */
  function solveRoots( a, b, c ) {
    let roots = null;
    let xCoordinates = Util.solveQuadraticRootsReal( a, b, c );
    if ( xCoordinates !== null ) {
      roots = [];
      xCoordinates = xCoordinates.sort( ( x0, x1 ) => x0 - x1 ); // in ascending order
      _.uniq( xCoordinates ).forEach( x => { roots.push( new Vector2( x, 0 ) ); } );
    }
    assert && assert( roots === null || ( roots.length >= 0 && roots.length <= 2 ), 'unexpected roots: ' + roots );
    return roots;
  }

  /**
   * Documentation for the association IO type, QuadraticIO.  This appears in PhET-iO Studio.
   * Defined here so that specifics about Quadratic appear in one place.
   * @type {string}
   * @public
   */
  Quadratic.documentationQuadraticIO =
    'QuadraticIO is a data structure that describes a quadratic equation in the model. ' +
    'Its properties are relevant to standard and vertex forms of the quadratic equation. ' +
    'Non-parabolas (a=0) will have a subset of the properties that parabolas have. ' +
    '
' +
    'Required properties are related to the standard form y = ax2 + bx + c, and include:' +
    '
' +
    '
' +
    '
a: {NumberIO} coefficient a
' +
    '
b: {NumberIO} coefficient b
' +
    '
c: {NumberIO} coefficient c
' +
    '
color: {ColorIO} the color used to draw the associated line
' +
    '
' +
    'All coefficient values must respect the ranges for those coefficients.';

  return graphingQuadratics.register( 'Quadratic', Quadratic );
} );<|endoftext|>


<commit_before>/**
 * File-system use goes here.
 *
 * We should try to reduce these, so logging when it happens is useful.
 *
 * It's also useful to mock it away in other components.
 */

'use strict';

var _ = require('lodash'),
  fs = require('fs'),
  path = require('path'),
  pkg = require(path.resolve('package.json'));

/**
 *
 * @param {string} dir
 * @returns {Array}
 */
function getFiles(dir) {
  try {
    return fs.readdirSync(dir)
      .filter(function (file) {
        return !fs.statSync(path.join(dir, file)).isDirectory() &&
          file.indexOf('.test.') === -1 && // ignore test files
          file.indexOf('.md') === -1; // ignore documentation files
      });
  } catch (ex) {
    return [];
  }
}

/**
 * Get folder names.
 *
 * Should only occur once per directory.
 *
 * @param  {string} dir enclosing folder
 * @return {[]}     array of folder names
 */
function getFolders(dir) {
  try {
    return fs.readdirSync(dir)
      .filter(function (file) {
        return fs.statSync(path.join(dir, file)).isDirectory();
      });
  } catch (ex) {
    return [];
  }
}

/**
 * Get array of component names, from node_modules and components folder.
 *
 * Should only occur once!
 * @return {[]}
 */
function getComponents() {
  var deps = pkg.dependencies,
    npmComponents = _(deps).map(function (version, name) {
      if (_.contains(name, 'byline-')) {
        // this is a byline component
        if (_.contains(name, path.sep)) {
          // this is a scoped npm component!
          // return the name without the scope/user
          return name.split(path.sep)[1];
        } else {
          // this is an unscoped npm component
          return name;
        }
      } // otherwise returns undefined, and is compacted below
    }).compact().value();

  return getFolders(path.resolve('components')).concat(npmComponents);
}

/**
 * get a component name, so we can look it up in the file system
 * @param {string} name
 * @returns {string|false}
 */
function getComponentName(name) {
  var allComponents = exports.getComponents();

  return _.contains(allComponents, name) && name;
}

/**
 * get the full name of a possibly-scoped npm component
 * @param {string} name, e.g. 'byline-editor'
 * @returns {string|undefined} e.g. '@nymdev/byline-editor'
 */
function getNPMName(name) {
  return _.findKey(pkg.dependencies, function (version, dep) {
    return _.contains(dep, name);
  });
}

/**
 * get path to component folder
 * @param  {string} name
 * @return {string}
 */
function getComponentPath(name) {
  var possInternalPath, possNpmPath;

  // make sure it's a component we have (either in /components or package.json)
  if (!getComponentName(name)) {
    /**
     * Cannot memoize an exception.
     *
     * Also, this isn't a programmer error, unless we're expecting them to check for existence some other way first, so
     * we should not throw an exception.
     */
    return null;
  } else {
    possInternalPath = path.resolve('components', name);
    possNpmPath = path.resolve('node_modules', getNPMName(name));

    if (fs.existsSync(possInternalPath)) {
      return possInternalPath;
    } else if (fs.existsSync(possNpmPath)) {
      return possNpmPath;
    }
  }
}

/**
 * Try to get a module, or return false.
 *
 * Should only occur once per name!
 *
 * @param {string} name
 * @param {[string]} paths  Possible paths to find their module.
 * @returns {object|false}
 */
function tryRequire(name, paths) {
  var result = _.find(paths, function (path) {
    try {
      result = require(path);
    } catch (ex) {
      if (ex.message && !ex.message.match(/Cannot find module/i)) {
        throw ex;
      }
      result = false;
    }
    return result;
  });

  if (result) {
    result = require(result);
  }

  return result;
}

/**
 * Load a component as a node module.
 *
 * Should only occur once per name!
 *
 * NOTE: This includes local components as well as npm components.
 *
 * @param {string} name
 * @returns {object|false}
 */
function getComponentModule(name) {
  var componentPath = getComponentPath(name);
  return componentPath && tryRequire(name, [componentPath, componentPath + '/server']);
}

exports.getFiles = _.memoize(getFiles);
exports.getFolders = _.memoize(getFolders);
exports.getComponents = _.memoize(getComponents);
exports.getComponentPath = _.memoize(getComponentPath); // memoize by _first_ parameter only (default)
exports.getComponentModule = _.memoize(getComponentModule); // memoize by _first_ parameter only (default)

// for testing
/**
 * @param {object} newPkg
 */
exports.usePackage = function (newPkg) {
  pkg = newPkg;
};
<commit_msg>path.resolve crashes when run greedily<commit_after>/**
 * File-system use goes here.
 *
 * We should try to reduce these, so logging when it happens is useful.
 *
 * It's also useful to mock it away in other components.
 */

'use strict';

var _ = require('lodash'),
  fs = require('fs'),
  path = require('path'),
  pkg = require(path.resolve('package.json'));

/**
 *
 * @param {string} dir
 * @returns {Array}
 */
function getFiles(dir) {
  try {
    return fs.readdirSync(dir)
      .filter(function (file) {
        return !fs.statSync(path.join(dir, file)).isDirectory() &&
          file.indexOf('.test.') === -1 && // ignore test files
          file.indexOf('.md') === -1; // ignore documentation files
      });
  } catch (ex) {
    return [];
  }
}

/**
 * Get folder names.
 *
 * Should only occur once per directory.
 *
 * @param  {string} dir enclosing folder
 * @return {[]}     array of folder names
 */
function getFolders(dir) {
  try {
    return fs.readdirSync(dir)
      .filter(function (file) {
        return fs.statSync(path.join(dir, file)).isDirectory();
      });
  } catch (ex) {
    return [];
  }
}

/**
 * Get array of component names, from node_modules and components folder.
 *
 * Should only occur once!
 * @return {[]}
 */
function getComponents() {
  var deps = pkg.dependencies,
    npmComponents = _(deps).map(function (version, name) {
      if (_.contains(name, 'byline-')) {
        // this is a byline component
        if (_.contains(name, path.sep)) {
          // this is a scoped npm component!
          // return the name without the scope/user
          return name.split(path.sep)[1];
        } else {
          // this is an unscoped npm component
          return name;
        }
      } // otherwise returns undefined, and is compacted below
    }).compact().value();

  return getFolders(path.resolve('components')).concat(npmComponents);
}

/**
 * get a component name, so we can look it up in the file system
 * @param {string} name
 * @returns {string|false}
 */
function getComponentName(name) {
  var allComponents = exports.getComponents();

  return _.contains(allComponents, name) && name;
}

/**
 * get the full name of a possibly-scoped npm component
 * @param {string} name, e.g. 'byline-editor'
 * @returns {string|undefined} e.g. '@nymdev/byline-editor'
 */
function getNPMName(name) {
  return _.findKey(pkg.dependencies, function (version, dep) {
    return _.contains(dep, name);
  });
}

/**
 * get path to component folder
 * @param  {string} name
 * @return {string}
 */
function getComponentPath(name) {
  // make sure it's a component we have (either in /components or package.json)
  if (!getComponentName(name)) {
    /**
     * Cannot memoize an exception.
     *
     * Also, this isn't a programmer error, unless we're expecting them to check for existence some other way first, so
     * we should not throw an exception.
     */
    return null;
  } else if (fs.existsSync(path.resolve('components', name))) {
    return path.resolve('components', name);
  } else if (fs.existsSync(path.resolve('node_modules', getNPMName(name)))) {
    return path.resolve('node_modules', getNPMName(name));
  }
}

/**
 * Try to get a module, or return false.
 *
 * Should only occur once per name!
 *
 * @param {string} name
 * @param {[string]} paths  Possible paths to find their module.
 * @returns {object|false}
 */
function tryRequire(name, paths) {
  var result = _.find(paths, function (path) {
    try {
      result = require(path);
    } catch (ex) {
      if (ex.message && !ex.message.match(/Cannot find module/i)) {
        throw ex;
      }
      result = false;
    }
    return result;
  });

  if (result) {
    result = require(result);
  }

  return result;
}

/**
 * Load a component as a node module.
 *
 * Should only occur once per name!
 *
 * NOTE: This includes local components as well as npm components.
 *
 * @param {string} name
 * @returns {object|false}
 */
function getComponentModule(name) {
  var componentPath = getComponentPath(name);
  return componentPath && tryRequire(name, [componentPath, componentPath + '/server']);
}

exports.getFiles = _.memoize(getFiles);
exports.getFolders = _.memoize(getFolders);
exports.getComponents = _.memoize(getComponents);
exports.getComponentPath = _.memoize(getComponentPath); // memoize by _first_ parameter only (default)
exports.getComponentModule = _.memoize(getComponentModule); // memoize by _first_ parameter only (default)

// for testing
/**
 * @param {object} newPkg
 */
exports.usePackage = function (newPkg) {
  pkg = newPkg;
};
<|endoftext|>


<commit_before>function addCollection() {
    syncTree(function() {
        var newCollection = {
            text: $('#addCollectionName').val(),
            showBookmarkDescription: true,
            showBookmarkIcon: true,
            showBookmarksAsCards: false,
            bookmarkIconSize: 16
        };

        var selectedCollection = getSelectedCollection();

        if (typeof selectedCollection == 'undefined' || selectedCollection.id === "tree") {
            selectedCollection = newCollection;
            updateTree(selectedCollection);
        } else {
            if (typeof selectedCollection.nodes == 'undefined') {
                selectedCollection.nodes = [];
            }

            selectedCollection.nodes.push(newCollection);
            updateTree();
        }

        $('#addCollectionName').val("");
    });
}

function addBookmark() {
    syncTree(function(){
        var selectedCollection = getSelectedCollection();

        if (typeof selectedCollection.bookmarks == 'undefined') {
            selectedCollection.bookmarks = [];
        }

        selectedCollection.bookmarks.push({
            text: $('#addBookmarkName').val(),
            url: $('#addBookmarkUrl').val(),
            description: $('#addBookmarkDescription').val(),
            icon: "http://logo.clearbit.com/" + (new URL($('#addBookmarkUrl').val())).hostname
        });

        $('#addBookmarkName').val("");
        $('#addBookmarkUrl').val("");
        $('#addBookmarkDescription').val("");

        selectedCollection.tags = [selectedCollection.bookmarks.length];

        updateTree();
    });
}

function deleteBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks.splice(bookmarkIndex, 1);

    updateTree();
}

function deleteCollection() {
    var collection = getSelectedCollection();

    if (typeof collection.parentId != 'undefined') {
        var parent = $('#tree').treeview('getNode', collection.parentId);

        $.each(parent.nodes, function (index, node) {
            if (node.nodeId === collection.nodeId) {
                parent.nodes.splice(index, 1);
                updateTree();
                return false;
            }
        });
    } else {
        var index = getIndex(collection);
        var parent = $('#tree').treeview('getParent', collection)[0];

        if (parent.id === "tree") {
            var collections = getCollections();

            collections.splice(index, 1);

            updateTree(collections, true);
        } else {
            parent.nodes.slice(index, 1);

            updateTree();
        }
    }
}

function editBookmark(node, isCard) {
    var bookmark;
    var bookmarkIndex;

    syncTree(function() {
        if (isCard) {
            bookmark = getSelectedBookmarkCard(node);
            bookmarkIndex = getIndexCard(node);
        } else {
            bookmark = getSelectedBookmark(node);
            bookmarkIndex = getIndex(node);
        }
        var selectedCollection = getSelectedCollection();

        $("#editName").val(bookmark.text);
        $("#editUrl").val(bookmark.url);
        $("#editIcon").val(bookmark.icon);
        $("#editIconAddon").attr("src", bookmark.icon);
        $("#editIndex").text(bookmarkIndex);
        $("#editDescription").val(bookmark.description);
        $("#editBookmarkCollection").val(selectedCollection.text);

        $('#editModal').modal('show');
    });
}

function editCollection() {
    var selectedCollection = getSelectedCollection();

    syncTree(function() {
        $("#editCollectionName").val(selectedCollection.text);
        $("#editCollectionIcon").val(selectedCollection.icon);
        $("#showBookmarkIconCheckbox").prop('checked', selectedCollection.showBookmarkIcon);
        $("#showBookmarkDescriptionCheckbox").prop('checked', selectedCollection.showBookmarkDescription);
        $("#bookmarkIconSizeSlider").val(selectedCollection.bookmarkIconSize);
        $("#showBookmarksAsCardsCheckbox").prop('checked', selectedCollection.showBookmarksAsCards);
        $('#editCollectionModal').modal('show');
    });
}

function saveBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks[bookmarkIndex].text = $("#editName").val();
    selectedCollection.bookmarks[bookmarkIndex].url = $("#editUrl").val();
    selectedCollection.bookmarks[bookmarkIndex].icon = $("#editIcon").val();
    selectedCollection.bookmarks[bookmarkIndex].description = $("#editDescription").val();

    var moveToCollection = $('#tree').treeview('getNode', $("#editMoveToCollection").text());

    if (typeof moveToCollection.nodeId != 'undefined' && selectedCollection !== moveToCollection) {
        var bookmark = selectedCollection.bookmarks[bookmarkIndex];
        selectedCollection.bookmarks.splice(bookmarkIndex, 1);
        moveToCollection.bookmarks.push(bookmark);
        moveToCollection.tags = [moveToCollection.bookmarks.length];
        selectedCollection.tags = [selectedCollection.bookmarks.length];
    }

    updateTree();
}

function saveCollection() {
    var selectedCollection = getSelectedCollection();
    selectedCollection.text = $("#editCollectionName").val();
    selectedCollection.icon = $("#editCollectionIcon").val();
    selectedCollection.showBookmarkIcon = $("#showBookmarkIconCheckbox").is(':checked');
    selectedCollection.showBookmarkDescription = $("#showBookmarkDescriptionCheckbox").is(':checked');
    selectedCollection.bookmarkIconSize = $("#bookmarkIconSizeSlider").val();
    selectedCollection.showBookmarksAsCards = $("#showBookmarksAsCardsCheckbox").is(':checked');

    updateTree();
}

function getAllBookmarks() {
    var bookmarks = [];
    return search(getCollections(), "bookmarks", bookmarks);
}

search = function(hay, needle, accumulator) {
  var accumulator = accumulator || [];
  if (typeof hay == 'object') {
    for (var i in hay) {
        if (i === needle) {
            $.merge(accumulator, hay[i]);
        }
        search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
    }
  }
  return new RegExp(needle).test(hay) || accumulator;
}

function showAllBookmarks() {
    $("#collectionName").attr("placeholder", "All bookmarks");

    $("#collectionIcon").removeClass();
    $("#collectionIcon").addClass("fa fa-heart");

    $("#accordion").empty();
    $('#bookmarks').empty();

    var collection = {
        bookmarks: getAllBookmarks(),
        showBookmarkIcon: true,
        showBookmarkDescription: true,
        bookmarkIconSize: 16
    };

    $("#bookmarks").html(showBookmarks(collection));
}

function showBookmarks(collection, showAsCards) {
    var bookmarksHtml = "";

    if (collection.showBookmarksAsCards) {
        $.each(collection.bookmarks, function (index, bookmark) {
            var item = "
";

            item += "";
            if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                item += "";
            } else {
                item += "";
            }
            item += "";

            item += "<button type='button' class='btn btn-primary btn-sm cardEditButton' onclick='editBookmark(this, true)'>" +
                "</button>";

            item += "
";

            bookmarksHtml += item;
        });

    } else {
        $.each(collection.bookmarks, function (index, bookmark) {

            var item = "
";
            item += "
";

            item += "
";

            if (collection.showBookmarkIcon) {
                if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                    item += "";
                    item += "" + bookmark.text + "";
                } else {
                    item += "";
                    item += "" + bookmark.text + "";
                }
            }

            if (collection.showBookmarkDescription && typeof bookmark.description != 'undefined' && bookmark.description != "") {
                item += " - " + bookmark.description + "";
            }

            item += "
";

            item += "
";
            item += "<button type='button' style='padding-right: 5px;' class='btn btn-outline-secondary btn-sm' onclick='editBookmark(this)'>" +
                "</button>";
            item += "
";

            item += "
";
            item += "
"

            bookmarksHtml += item;
        });
    }

    return bookmarksHtml;
}<commit_msg>Fix editBookmark, for now, must do proper fix later<commit_after>function addCollection() {
    syncTree(function() {
        var newCollection = {
            text: $('#addCollectionName').val(),
            showBookmarkDescription: true,
            showBookmarkIcon: true,
            showBookmarksAsCards: false,
            bookmarkIconSize: 16
        };

        var selectedCollection = getSelectedCollection();

        if (typeof selectedCollection == 'undefined' || selectedCollection.id === "tree") {
            selectedCollection = newCollection;
            updateTree(selectedCollection);
        } else {
            if (typeof selectedCollection.nodes == 'undefined') {
                selectedCollection.nodes = [];
            }

            selectedCollection.nodes.push(newCollection);
            updateTree();
        }

        $('#addCollectionName').val("");
    });
}

function addBookmark() {
    syncTree(function(){
        var selectedCollection = getSelectedCollection();

        if (typeof selectedCollection.bookmarks == 'undefined') {
            selectedCollection.bookmarks = [];
        }

        selectedCollection.bookmarks.push({
            text: $('#addBookmarkName').val(),
            url: $('#addBookmarkUrl').val(),
            description: $('#addBookmarkDescription').val(),
            icon: "http://logo.clearbit.com/" + (new URL($('#addBookmarkUrl').val())).hostname
        });

        $('#addBookmarkName').val("");
        $('#addBookmarkUrl').val("");
        $('#addBookmarkDescription').val("");

        selectedCollection.tags = [selectedCollection.bookmarks.length];

        updateTree();
    });
}

function deleteBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks.splice(bookmarkIndex, 1);

    updateTree();
}

function deleteCollection() {
    var collection = getSelectedCollection();

    if (typeof collection.parentId != 'undefined') {
        var parent = $('#tree').treeview('getNode', collection.parentId);

        $.each(parent.nodes, function (index, node) {
            if (node.nodeId === collection.nodeId) {
                parent.nodes.splice(index, 1);
                updateTree();
                return false;
            }
        });
    } else {
        var index = getIndex(collection);
        var parent = $('#tree').treeview('getParent', collection)[0];

        if (parent.id === "tree") {
            var collections = getCollections();

            collections.splice(index, 1);

            updateTree(collections, true);
        } else {
            parent.nodes.slice(index, 1);

            updateTree();
        }
    }
}

function editBookmark(node, isCard) {
    var bookmark;
    var bookmarkIndex;

    // syncTree(function() {
        if (isCard) {
            bookmark = getSelectedBookmarkCard(node);
            bookmarkIndex = getIndexCard(node);
        } else {
            bookmark = getSelectedBookmark(node);
            bookmarkIndex = getIndex(node);
        }
        var selectedCollection = getSelectedCollection();

        $("#editName").val(bookmark.text);
        $("#editUrl").val(bookmark.url);
        $("#editIcon").val(bookmark.icon);
        $("#editIconAddon").attr("src", bookmark.icon);
        $("#editIndex").text(bookmarkIndex);
        $("#editDescription").val(bookmark.description);
        $("#editBookmarkCollection").val(selectedCollection.text);

        $('#editModal').modal('show');
    // });
}

function editCollection() {
    var selectedCollection = getSelectedCollection();

    syncTree(function() {
        $("#editCollectionName").val(selectedCollection.text);
        $("#editCollectionIcon").val(selectedCollection.icon);
        $("#showBookmarkIconCheckbox").prop('checked', selectedCollection.showBookmarkIcon);
        $("#showBookmarkDescriptionCheckbox").prop('checked', selectedCollection.showBookmarkDescription);
        $("#bookmarkIconSizeSlider").val(selectedCollection.bookmarkIconSize);
        $("#showBookmarksAsCardsCheckbox").prop('checked', selectedCollection.showBookmarksAsCards);
        $('#editCollectionModal').modal('show');
    });
}

function saveBookmark() {
    var selectedCollection = getSelectedCollection();
    var bookmarkIndex = parseInt($("#editIndex").text());

    selectedCollection.bookmarks[bookmarkIndex].text = $("#editName").val();
    selectedCollection.bookmarks[bookmarkIndex].url = $("#editUrl").val();
    selectedCollection.bookmarks[bookmarkIndex].icon = $("#editIcon").val();
    selectedCollection.bookmarks[bookmarkIndex].description = $("#editDescription").val();

    var moveToCollection = $('#tree').treeview('getNode', $("#editMoveToCollection").text());

    if (typeof moveToCollection.nodeId != 'undefined' && selectedCollection !== moveToCollection) {
        var bookmark = selectedCollection.bookmarks[bookmarkIndex];
        selectedCollection.bookmarks.splice(bookmarkIndex, 1);
        moveToCollection.bookmarks.push(bookmark);
        moveToCollection.tags = [moveToCollection.bookmarks.length];
        selectedCollection.tags = [selectedCollection.bookmarks.length];
    }

    updateTree();
}

function saveCollection() {
    var selectedCollection = getSelectedCollection();
    selectedCollection.text = $("#editCollectionName").val();
    selectedCollection.icon = $("#editCollectionIcon").val();
    selectedCollection.showBookmarkIcon = $("#showBookmarkIconCheckbox").is(':checked');
    selectedCollection.showBookmarkDescription = $("#showBookmarkDescriptionCheckbox").is(':checked');
    selectedCollection.bookmarkIconSize = $("#bookmarkIconSizeSlider").val();
    selectedCollection.showBookmarksAsCards = $("#showBookmarksAsCardsCheckbox").is(':checked');

    updateTree();
}

function getAllBookmarks() {
    var bookmarks = [];
    return search(getCollections(), "bookmarks", bookmarks);
}

search = function(hay, needle, accumulator) {
  var accumulator = accumulator || [];
  if (typeof hay == 'object') {
    for (var i in hay) {
        if (i === needle) {
            $.merge(accumulator, hay[i]);
        }
        search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
    }
  }
  return new RegExp(needle).test(hay) || accumulator;
}

function showAllBookmarks() {
    $("#collectionName").attr("placeholder", "All bookmarks");

    $("#collectionIcon").removeClass();
    $("#collectionIcon").addClass("fa fa-heart");

    $("#accordion").empty();
    $('#bookmarks').empty();

    var collection = {
        bookmarks: getAllBookmarks(),
        showBookmarkIcon: true,
        showBookmarkDescription: true,
        bookmarkIconSize: 16
    };

    $("#bookmarks").html(showBookmarks(collection));
}

function showBookmarks(collection, showAsCards) {
    var bookmarksHtml = "";

    if (collection.showBookmarksAsCards) {
        $.each(collection.bookmarks, function (index, bookmark) {
            var item = "
";

            item += "";
            if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                item += "";
            } else {
                item += "";
            }
            item += "";

            item += "<button type='button' class='btn btn-primary btn-sm cardEditButton' onclick='editBookmark(this, true)'>" +
                "</button>";

            item += "
";

            bookmarksHtml += item;
        });

    } else {
        $.each(collection.bookmarks, function (index, bookmark) {

            var item = "
";
            item += "
";

            item += "
";

            if (collection.showBookmarkIcon) {
                if (typeof bookmark.icon != 'undefined' && bookmark.icon !== "" && !bookmark.icon.startsWith("fa")) {
                    item += "";
                    item += "" + bookmark.text + "";
                } else {
                    item += "";
                    item += "" + bookmark.text + "";
                }
            }

            if (collection.showBookmarkDescription && typeof bookmark.description != 'undefined' && bookmark.description != "") {
                item += " - " + bookmark.description + "";
            }

            item += "
";

            item += "
";
            item += "<button type='button' style='padding-right: 5px;' class='btn btn-outline-secondary btn-sm' onclick='editBookmark(this)'>" +
                "</button>";
            item += "
";

            item += "
";
            item += "
"

            bookmarksHtml += item;
        });
    }

    return bookmarksHtml;
}<|endoftext|>


<commit_before>module.exports = require('should');

var DataSource = require('loopback-datasource-juggler').DataSource;

var config = require('rc')('loopback', {test: {mongodb: {}}}).test.mongodb;

if (process.env.CI) {
  config = {
    host: process.env.MONGODB_HOST || 'localhost',
    port: process.env.MONGODB_PORT || 27017,
    database: 'lb-ds-mongodb-test-' + (
      process.env.TRAVIS_BUILD_NUMBER || process.env.BUILD_NUMBER || '1'
    ),
  };
}

global.config = config;

global.getDataSource = global.getSchema = function (customConfig) {
  var db = new DataSource(require('../'), customConfig || config);
  db.log = function (a) {
    console.log(a);
  };

  return db;
};

global.sinon = require('sinon');
<commit_msg>Allow runtime configurable test environment<commit_after>module.exports = require('should');

var DataSource = require('loopback-datasource-juggler').DataSource;

var TEST_ENV = process.env.TEST_ENV || 'test';
var config = require('rc')('loopback', {test: {mongodb: {}}})[TEST_ENV].mongodb;

if (process.env.CI) {
  config = {
    host: process.env.MONGODB_HOST || 'localhost',
    port: process.env.MONGODB_PORT || 27017,
    database: 'lb-ds-mongodb-test-' + (
      process.env.TRAVIS_BUILD_NUMBER || process.env.BUILD_NUMBER || '1'
    ),
  };
}

global.config = config;

global.getDataSource = global.getSchema = function (customConfig) {
  var db = new DataSource(require('../'), customConfig || config);
  db.log = function (a) {
    console.log(a);
  };

  return db;
};

global.sinon = require('sinon');
<|endoftext|>


<commit_before>//-------------------------------------------------------------------------------
// Requires
//-------------------------------------------------------------------------------

var buildbug            = require('buildbug');

//-------------------------------------------------------------------------------
// Simplify References
//-------------------------------------------------------------------------------

var buildProject        = buildbug.buildProject;
var buildProperties     = buildbug.buildProperties;
var buildTarget         = buildbug.buildTarget;
var enableModule        = buildbug.enableModule;
var parallel            = buildbug.parallel;
var series              = buildbug.series;
var targetTask          = buildbug.targetTask;

//-------------------------------------------------------------------------------
// Enable Modules
//-------------------------------------------------------------------------------

var aws                 = enableModule("aws");
var bugpack             = enableModule('bugpack');
var bugunit             = enableModule('bugunit');
var core                = enableModule('core');
var nodejs              = enableModule('nodejs');

//-------------------------------------------------------------------------------
// Values
//-------------------------------------------------------------------------------

var version             = "0.1.0";
var dependencies        = {
    bugpack: "0.1.0"
};

//-------------------------------------------------------------------------------
// BuildProperties
//-------------------------------------------------------------------------------

buildProperties({
    node: {
        packageJson: {
            name: "bugtrace",
            version: version,
            description: "Declarative async flow control for object oriented JavaScript provided by the bugtrace library",
            main: "./scripts/bugtrace-node-module.js",
            dependencies: dependencies,
            author: "Brian Neisler <brian@airbug.com>",
            repository: {
                type: "git",
                url: "https://github.com/airbug/bugtrace.git"
            },
            bugs: {
                url: "https://github.com/airbug/bugtrace/issues"
            },
            licenses: [
                {
                    type : "MIT",
                    url : "https://raw.githubusercontent.com/airbug/bugtrace/master/LICENSE"
                }
            ]
        },
        sourcePaths: [
            "../bugcore/projects/bugcore/js/src",
            "./projects/bugtrace/js/src"
        ],
        scriptPaths: [
            "./projects/bugtrace-node/js/scripts"
        ],
        readmePath: "./README.md",
        unitTest: {
            packageJson: {
                name: "bugtrace-test",
                version: version,
                main: "./scripts/bugtrace-node-module.js",
                dependencies: dependencies,
                scripts: {
                    test: "./scripts/bugunit-run.js"
                }
            },
            sourcePaths: [
                "../bugunit/projects/bugunit/js/src"
            ],
            scriptPaths: [
                "../bugunit/projects/bugunit/js/scripts"
            ],
            testPaths: [
                "./projects/bugtrace/js/test"
            ]
        }
    }
});

//-------------------------------------------------------------------------------
// BuildTargets
//-------------------------------------------------------------------------------

// Clean BuildTarget
//-------------------------------------------------------------------------------

buildTarget('clean').buildFlow(
    targetTask('clean')
);

// Local BuildTarget
//-------------------------------------------------------------------------------

buildTarget('local').buildFlow(
    series([
        targetTask('clean'),
        series([
            targetTask('createNodePackage', {
                properties: {
                    packageJson: buildProject.getProperty("node.packageJson"),
                    readmePath: buildProject.getProperty("node.readmePath"),
                    sourcePaths: buildProject.getProperty("node.sourcePaths").concat(
                        buildProject.getProperty("node.unitTest.sourcePaths")
                    ),
                    scriptPaths: buildProject.getProperty("node.scriptPaths").concat(
                        buildProject.getProperty("node.unitTest.scriptPaths")
                    ),
                    testPaths: buildProject.getProperty("node.unitTest.testPaths")
                }
            }),
            targetTask('generateBugPackRegistry', {
                init: function(task, buildProject, properties) {
                    var nodePackage = nodejs.findNodePackage(
                        buildProject.getProperty("node.packageJson.name"),
                        buildProject.getProperty("node.packageJson.version")
                    );
                    task.updateProperties({
                        sourceRoot: nodePackage.getBuildPath()
                    });
                }
            }),
            targetTask('packNodePackage', {
                properties: {
                    packageName: "{{node.packageJson.name}}",
                    packageVersion: "{{node.packageJson.version}}"
                }
            }),
            /*targetTask('startNodeModuleTests', {
             init: function(task, buildProject, properties) {
             var packedNodePackage = nodejs.findPackedNodePackage(
             buildProject.getProperty("node.packageJson.name"),
             buildProject.getProperty("node.packageJson.version")
             );
             task.updateProperties({
             modulePath: packedNodePackage.getFilePath()
             //checkCoverage: true
             });
             }
             }),*/
            targetTask("s3PutFile", {
                init: function(task, buildProject, properties) {
                    var packedNodePackage = nodejs.findPackedNodePackage(buildProject.getProperty("node.packageJson.name"),
                        buildProject.getProperty("node.packageJson.version"));
                    task.updateProperties({
                        file: packedNodePackage.getFilePath(),
                        options: {
                            acl: 'public-read',
                            encrypt: true
                        }
                    });
                },
                properties: {
                    bucket: "{{local-bucket}}"
                }
            })
        ])
    ])
).makeDefault();

// Prod BuildTarget
//-------------------------------------------------------------------------------

buildTarget('prod').buildFlow(
    series([
        targetTask('clean'),
        parallel([

            //Create test node bugtrace package

            series([
                targetTask('createNodePackage', {
                    properties: {
                        packageJson: buildProject.getProperty("node.unitTest.packageJson"),
                        sourcePaths: buildProject.getProperty("node.sourcePaths").concat(
                            buildProject.getProperty("node.unitTest.sourcePaths")
                        ),
                        scriptPaths: buildProject.getProperty("node.scriptPaths").concat(
                            buildProject.getProperty("node.unitTest.scriptPaths")
                        ),
                        testPaths: buildProject.getProperty("node.unitTest.testPaths")
                    }
                }),
                targetTask('generateBugPackRegistry', {
                    init: function(task, buildProject, properties) {
                        var nodePackage = nodejs.findNodePackage(
                            buildProject.getProperty("node.unitTest.packageJson.name"),
                            buildProject.getProperty("node.unitTest.packageJson.version")
                        );
                        task.updateProperties({
                            sourceRoot: nodePackage.getBuildPath()
                        });
                    }
                }),
                targetTask('packNodePackage', {
                    properties: {
                        packageName: "{{node.unitTest.packageJson.name}}",
                        packageVersion: "{{node.unitTest.packageJson.version}}"
                    }
                })/*,
                 targetTask('startNodeModuleTests', {
                 init: function(task, buildProject, properties) {
                 var packedNodePackage = nodejs.findPackedNodePackage(
                 buildProject.getProperty("node.unitTest.packageJson.name"),
                 buildProject.getProperty("node.unitTest.packageJson.version")
                 );
                 task.updateProperties({
                 modulePath: packedNodePackage.getFilePath(),
                 checkCoverage: true
                 });
                 }
                 })*/
            ]),

            // Create production node bugtrace package

            series([
                targetTask('createNodePackage', {
                    properties: {
                        packageJson: buildProject.getProperty("node.packageJson"),
                        readmePath: buildProject.getProperty("node.readmePath"),
                        sourcePaths: buildProject.getProperty("node.sourcePaths"),
                        scriptPaths: buildProject.getProperty("node.scriptPaths")
                    }
                }),
                targetTask('generateBugPackRegistry', {
                    init: function(task, buildProject, properties) {
                        var nodePackage = nodejs.findNodePackage(
                            buildProject.getProperty("node.packageJson.name"),
                            buildProject.getProperty("node.packageJson.version")
                        );
                        task.updateProperties({
                            sourceRoot: nodePackage.getBuildPath()
                        });
                    }
                }),
                targetTask('packNodePackage', {
                    properties: {
                        packageName: "{{node.packageJson.name}}",
                        packageVersion: "{{node.packageJson.version}}"
                    }
                }),
                targetTask("s3PutFile", {
                    init: function(task, buildProject, properties) {
                        var packedNodePackage = nodejs.findPackedNodePackage(buildProject.getProperty("node.packageJson.name"),
                            buildProject.getProperty("node.packageJson.version"));
                        task.updateProperties({
                            file: packedNodePackage.getFilePath(),
                            options: {
                                acl: 'public-read',
                                encrypt: true
                            }
                        });
                    },
                    properties: {
                        bucket: "{{prod-deploy-bucket}}"
                    }
                }),
                targetTask('npmConfigSet', {
                    properties: {
                        config: buildProject.getProperty("npmConfig")
                    }
                }),
                targetTask('npmAddUser'),
                targetTask('publishNodePackage', {
                    properties: {
                        packageName: "{{node.packageJson.name}}",
                        packageVersion: "{{node.packageJson.version}}"
                    }
                })
            ])
        ])
    ])
);
<commit_msg>Update description<commit_after>//-------------------------------------------------------------------------------
// Requires
//-------------------------------------------------------------------------------

var buildbug            = require('buildbug');

//-------------------------------------------------------------------------------
// Simplify References
//-------------------------------------------------------------------------------

var buildProject        = buildbug.buildProject;
var buildProperties     = buildbug.buildProperties;
var buildTarget         = buildbug.buildTarget;
var enableModule        = buildbug.enableModule;
var parallel            = buildbug.parallel;
var series              = buildbug.series;
var targetTask          = buildbug.targetTask;

//-------------------------------------------------------------------------------
// Enable Modules
//-------------------------------------------------------------------------------

var aws                 = enableModule("aws");
var bugpack             = enableModule('bugpack');
var bugunit             = enableModule('bugunit');
var core                = enableModule('core');
var nodejs              = enableModule('nodejs');

//-------------------------------------------------------------------------------
// Values
//-------------------------------------------------------------------------------

var version             = "0.1.0";
var dependencies        = {
    bugpack: "0.1.0"
};

//-------------------------------------------------------------------------------
// BuildProperties
//-------------------------------------------------------------------------------

buildProperties({
    node: {
        packageJson: {
            name: "bugtrace",
            version: version,
            description: "Async tracing tool for JavaScript making it easy to back trace through async breaks in JS code",
            main: "./scripts/bugtrace-node-module.js",
            dependencies: dependencies,
            author: "Brian Neisler <brian@airbug.com>",
            repository: {
                type: "git",
                url: "https://github.com/airbug/bugtrace.git"
            },
            bugs: {
                url: "https://github.com/airbug/bugtrace/issues"
            },
            licenses: [
                {
                    type : "MIT",
                    url : "https://raw.githubusercontent.com/airbug/bugtrace/master/LICENSE"
                }
            ]
        },
        sourcePaths: [
            "../bugcore/projects/bugcore/js/src",
            "./projects/bugtrace/js/src"
        ],
        scriptPaths: [
            "./projects/bugtrace-node/js/scripts"
        ],
        readmePath: "./README.md",
        unitTest: {
            packageJson: {
                name: "bugtrace-test",
                version: version,
                main: "./scripts/bugtrace-node-module.js",
                dependencies: dependencies,
                scripts: {
                    test: "./scripts/bugunit-run.js"
                }
            },
            sourcePaths: [
                "../bugunit/projects/bugunit/js/src"
            ],
            scriptPaths: [
                "../bugunit/projects/bugunit/js/scripts"
            ],
            testPaths: [
                "./projects/bugtrace/js/test"
            ]
        }
    }
});

//-------------------------------------------------------------------------------
// BuildTargets
//-------------------------------------------------------------------------------

// Clean BuildTarget
//-------------------------------------------------------------------------------

buildTarget('clean').buildFlow(
    targetTask('clean')
);

// Local BuildTarget
//-------------------------------------------------------------------------------

buildTarget('local').buildFlow(
    series([
        targetTask('clean'),
        series([
            targetTask('createNodePackage', {
                properties: {
                    packageJson: buildProject.getProperty("node.packageJson"),
                    readmePath: buildProject.getProperty("node.readmePath"),
                    sourcePaths: buildProject.getProperty("node.sourcePaths").concat(
                        buildProject.getProperty("node.unitTest.sourcePaths")
                    ),
                    scriptPaths: buildProject.getProperty("node.scriptPaths").concat(
                        buildProject.getProperty("node.unitTest.scriptPaths")
                    ),
                    testPaths: buildProject.getProperty("node.unitTest.testPaths")
                }
            }),
            targetTask('generateBugPackRegistry', {
                init: function(task, buildProject, properties) {
                    var nodePackage = nodejs.findNodePackage(
                        buildProject.getProperty("node.packageJson.name"),
                        buildProject.getProperty("node.packageJson.version")
                    );
                    task.updateProperties({
                        sourceRoot: nodePackage.getBuildPath()
                    });
                }
            }),
            targetTask('packNodePackage', {
                properties: {
                    packageName: "{{node.packageJson.name}}",
                    packageVersion: "{{node.packageJson.version}}"
                }
            }),
            /*targetTask('startNodeModuleTests', {
             init: function(task, buildProject, properties) {
             var packedNodePackage = nodejs.findPackedNodePackage(
             buildProject.getProperty("node.packageJson.name"),
             buildProject.getProperty("node.packageJson.version")
             );
             task.updateProperties({
             modulePath: packedNodePackage.getFilePath()
             //checkCoverage: true
             });
             }
             }),*/
            targetTask("s3PutFile", {
                init: function(task, buildProject, properties) {
                    var packedNodePackage = nodejs.findPackedNodePackage(buildProject.getProperty("node.packageJson.name"),
                        buildProject.getProperty("node.packageJson.version"));
                    task.updateProperties({
                        file: packedNodePackage.getFilePath(),
                        options: {
                            acl: 'public-read',
                            encrypt: true
                        }
                    });
                },
                properties: {
                    bucket: "{{local-bucket}}"
                }
            })
        ])
    ])
).makeDefault();

// Prod BuildTarget
//-------------------------------------------------------------------------------

buildTarget('prod').buildFlow(
    series([
        targetTask('clean'),
        parallel([

            //Create test node bugtrace package

            series([
                targetTask('createNodePackage', {
                    properties: {
                        packageJson: buildProject.getProperty("node.unitTest.packageJson"),
                        sourcePaths: buildProject.getProperty("node.sourcePaths").concat(
                            buildProject.getProperty("node.unitTest.sourcePaths")
                        ),
                        scriptPaths: buildProject.getProperty("node.scriptPaths").concat(
                            buildProject.getProperty("node.unitTest.scriptPaths")
                        ),
                        testPaths: buildProject.getProperty("node.unitTest.testPaths")
                    }
                }),
                targetTask('generateBugPackRegistry', {
                    init: function(task, buildProject, properties) {
                        var nodePackage = nodejs.findNodePackage(
                            buildProject.getProperty("node.unitTest.packageJson.name"),
                            buildProject.getProperty("node.unitTest.packageJson.version")
                        );
                        task.updateProperties({
                            sourceRoot: nodePackage.getBuildPath()
                        });
                    }
                }),
                targetTask('packNodePackage', {
                    properties: {
                        packageName: "{{node.unitTest.packageJson.name}}",
                        packageVersion: "{{node.unitTest.packageJson.version}}"
                    }
                })/*,
                 targetTask('startNodeModuleTests', {
                 init: function(task, buildProject, properties) {
                 var packedNodePackage = nodejs.findPackedNodePackage(
                 buildProject.getProperty("node.unitTest.packageJson.name"),
                 buildProject.getProperty("node.unitTest.packageJson.version")
                 );
                 task.updateProperties({
                 modulePath: packedNodePackage.getFilePath(),
                 checkCoverage: true
                 });
                 }
                 })*/
            ]),

            // Create production node bugtrace package

            series([
                targetTask('createNodePackage', {
                    properties: {
                        packageJson: buildProject.getProperty("node.packageJson"),
                        readmePath: buildProject.getProperty("node.readmePath"),
                        sourcePaths: buildProject.getProperty("node.sourcePaths"),
                        scriptPaths: buildProject.getProperty("node.scriptPaths")
                    }
                }),
                targetTask('generateBugPackRegistry', {
                    init: function(task, buildProject, properties) {
                        var nodePackage = nodejs.findNodePackage(
                            buildProject.getProperty("node.packageJson.name"),
                            buildProject.getProperty("node.packageJson.version")
                        );
                        task.updateProperties({
                            sourceRoot: nodePackage.getBuildPath()
                        });
                    }
                }),
                targetTask('packNodePackage', {
                    properties: {
                        packageName: "{{node.packageJson.name}}",
                        packageVersion: "{{node.packageJson.version}}"
                    }
                }),
                targetTask("s3PutFile", {
                    init: function(task, buildProject, properties) {
                        var packedNodePackage = nodejs.findPackedNodePackage(buildProject.getProperty("node.packageJson.name"),
                            buildProject.getProperty("node.packageJson.version"));
                        task.updateProperties({
                            file: packedNodePackage.getFilePath(),
                            options: {
                                acl: 'public-read',
                                encrypt: true
                            }
                        });
                    },
                    properties: {
                        bucket: "{{prod-deploy-bucket}}"
                    }
                }),
                targetTask('npmConfigSet', {
                    properties: {
                        config: buildProject.getProperty("npmConfig")
                    }
                }),
                targetTask('npmAddUser'),
                targetTask('publishNodePackage', {
                    properties: {
                        packageName: "{{node.packageJson.name}}",
                        packageVersion: "{{node.packageJson.version}}"
                    }
                })
            ])
        ])
    ])
);
<|endoftext|>


<commit_before>import $ from 'jquery';
import select2Options from 'components/select2';

/**
 * Property Post.
 *
 * Using Select2.
 */
class Post {

  /**
   * The option template to compile.
   *
   * @return {function}
   */
  get optionTemplate() {
    return window.wp.template('papi-property-post-option');
  }

  /**
   * The option placeholder template to compile.
   *
   * @return {function}
   */
  get optionPlaceholderTemplate() {
    return window.wp.template('papi-property-post-option-placeholder');
  }

  /**
   * Initialize Property Post.
   */
  static init() {
    new Post().binds();
  }

  /**
   * Bind elements with functions.
   */
  binds() {
    $(document).on('papi/property/repeater/added', '[data-property="post"]', this.update.bind(this));
    $(document).on('change', '.papi-property-post-left', this.change.bind(this));
    $(document).on('papi/iframe/submit', this.iframeSubmit.bind(this));
  }

  /**
   * Change post dropdown when selecting
   * a different post type.
   *
   * @param {object} e
   */
  change(e) {
    e.preventDefault();

    const self = this;
    const $this = $(e.currentTarget);
    const query = $this.data('post-query').length
      ? $this.data('post-query')
      : {};

    query.post_type = $this.val();

    const params  = {
      'action': 'get_posts',
      'fields': ['ID', 'post_title', 'post_type'],
      'query': query
    };
    const $prop   = $this.closest('.papi-property-post');
    const $select = $prop.find('.papi-property-post-right');

    $('[for="' + $select.attr('id') + '"]')
      .parent()
      .find('label')
      .text($this.data('select-item').replace('%s', $this.find('option:selected').text()));

    $.get(papi.ajaxUrl + '?' + $.param(params), function(posts) {
      $select.empty();

      if ($select.data('placeholder').length) {
        const optionPlaceholderTemplate = self.optionPlaceholderTemplate;
        const template1 = window._.template($.trim(optionPlaceholderTemplate()));

        $select.append(template1({
          type: posts[0].post_type
        }));
      }

      const optionTemplate = self.optionTemplate;
      const template2 = window._.template($.trim(optionTemplate()));

      $.each(posts, function(index, post) {
        $select.append(template2({
          id: post.ID,
          title: post.post_title,
          type: post.post_type
        }));
      });

      if ($select.hasClass('papi-component-select2') && 'select2' in $.fn) {
        $select.trigger('change');
      }
    });
  }

  /**
   * Update select when iframe is submitted.
   *
   * @param {object} e
   * @param {object} data
   */
  iframeSubmit(e, data) {
    if (!data.iframe) {
      return;
    }

    const $elm = $(data.iframe);
    const title = $elm.find('[name="post_title"]').val();
    const id = $elm.find('[name="post_ID"]').val();
    const $select = $('[name=' + data.selector + ']');

    if (data.url.indexOf('post-new') !== -1) {
      // new
      const optionTemplate = this.optionTemplate;
      const template = window._.template($.trim(optionTemplate()));

      if ($select.find('option[value=' + id + ']').length) {
        return;
      }

      $select.append(template({
        id: id,
        title: title,
      }));
    } else {
      // edit
      const $option = $select.find('option[data-edit-url="' + data.url + '"]');
      $option.removeData('data');
      $option.text(title);
    }

    $select.trigger('change');
    $select.val(id);
  }

  /**
   * Initialize select2 field when added to repeater.
   *
   * @param {object} e
   */
  update(e) {
    e.preventDefault();

    const $select = $(e.currentTarget).parent().find('select');

    if ($select.hasClass('papi-component-select2') && 'select2' in $.fn) {
      $select.select2(select2Options($select[0]));
    }
  }
}

export default Post;
<commit_msg>Check so posts array has any values<commit_after>import $ from 'jquery';
import select2Options from 'components/select2';

/**
 * Property Post.
 *
 * Using Select2.
 */
class Post {

  /**
   * The option template to compile.
   *
   * @return {function}
   */
  get optionTemplate() {
    return window.wp.template('papi-property-post-option');
  }

  /**
   * The option placeholder template to compile.
   *
   * @return {function}
   */
  get optionPlaceholderTemplate() {
    return window.wp.template('papi-property-post-option-placeholder');
  }

  /**
   * Initialize Property Post.
   */
  static init() {
    new Post().binds();
  }

  /**
   * Bind elements with functions.
   */
  binds() {
    $(document).on('papi/property/repeater/added', '[data-property="post"]', this.update.bind(this));
    $(document).on('change', '.papi-property-post-left', this.change.bind(this));
    $(document).on('papi/iframe/submit', this.iframeSubmit.bind(this));
  }

  /**
   * Change post dropdown when selecting
   * a different post type.
   *
   * @param {object} e
   */
  change(e) {
    e.preventDefault();

    const self = this;
    const $this = $(e.currentTarget);
    const query = $this.data('post-query').length
      ? $this.data('post-query')
      : {};

    query.post_type = $this.val();

    const params  = {
      'action': 'get_posts',
      'fields': ['ID', 'post_title', 'post_type'],
      'query': query
    };
    const $prop   = $this.closest('.papi-property-post');
    const $select = $prop.find('.papi-property-post-right');

    $('[for="' + $select.attr('id') + '"]')
      .parent()
      .find('label')
      .text($this.data('select-item').replace('%s', $this.find('option:selected').text()));

    $.get(papi.ajaxUrl + '?' + $.param(params), function(posts) {
      $select.empty();

      if ($select.data('placeholder').length && posts.length) {
        const optionPlaceholderTemplate = self.optionPlaceholderTemplate;
        const template1 = window._.template($.trim(optionPlaceholderTemplate()));

        $select.append(template1({
          type: posts[0].post_type
        }));
      }

      const optionTemplate = self.optionTemplate;
      const template2 = window._.template($.trim(optionTemplate()));

      $.each(posts, function(index, post) {
        $select.append(template2({
          id: post.ID,
          title: post.post_title,
          type: post.post_type
        }));
      });

      if ($select.hasClass('papi-component-select2') && 'select2' in $.fn) {
        $select.trigger('change');
      }
    });
  }

  /**
   * Update select when iframe is submitted.
   *
   * @param {object} e
   * @param {object} data
   */
  iframeSubmit(e, data) {
    if (!data.iframe) {
      return;
    }

    const $elm = $(data.iframe);
    const title = $elm.find('[name="post_title"]').val();
    const id = $elm.find('[name="post_ID"]').val();
    const $select = $('[name=' + data.selector + ']');

    if (data.url.indexOf('post-new') !== -1) {
      // new
      const optionTemplate = this.optionTemplate;
      const template = window._.template($.trim(optionTemplate()));

      if ($select.find('option[value=' + id + ']').length) {
        return;
      }

      $select.append(template({
        id: id,
        title: title,
      }));
    } else {
      // edit
      const $option = $select.find('option[data-edit-url="' + data.url + '"]');
      $option.removeData('data');
      $option.text(title);
    }

    $select.trigger('change');
    $select.val(id);
  }

  /**
   * Initialize select2 field when added to repeater.
   *
   * @param {object} e
   */
  update(e) {
    e.preventDefault();

    const $select = $(e.currentTarget).parent().find('select');

    if ($select.hasClass('papi-component-select2') && 'select2' in $.fn) {
      $select.select2(select2Options($select[0]));
    }
  }
}

export default Post;
<|endoftext|>


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

/**
 * Create a vertical slider with a central tick
 *
 * @author Andrea Lin
 */
define( function( require ) {
  'use strict';

  // modules
  const Dimension2 = require( 'DOT/Dimension2' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const HSlider = require( 'SUN/HSlider' );
  const Line = require( 'SCENERY/nodes/Line' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Text = require( 'SCENERY/nodes/Text' );
  const VBox = require( 'SCENERY/nodes/VBox' );
  const VStrut = require( 'SCENERY/nodes/VStrut' );

  // constants
  const TICK_COLOR = 'black';
  const TICK_LENGTH = 30;
  const TICK_WIDTH = 1;
  const TICK_LABEL_FONT = new PhetFont( GQConstants.SLIDER_TICK_LABEL_FONT_SIZE );
  const SYMBOL_FONT = new PhetFont( { size: GQConstants.INTERACTIVE_EQUATION_FONT_SIZE, weight: 'bold' } );

  class SliderUnit extends VBox {

    /**
     * @param {string} symbol
     * @param {NumberProperty} property
     * @param {number} decimalPlaces
     * @param {Object} [options]
     */
    constructor( symbol, property, decimalPlaces, options ) {

      options = _.extend( {

        showTicks: true,

        // HSlider options
        trackFill: 'black',
        trackSize: new Dimension2( 160, 1 ),
        thumbSize: new Dimension2( 20, 45 ),
        thumbTouchAreaYDilation: 8,

        // superclass options
        align: 'center'
      }, options );

      super( options );

      const slider = new HSlider( property, property.range, {
        trackFill: options.trackFill,
        trackSize: options.trackSize,
        thumbSize: options.thumbSize,
        thumbTouchAreaYDilation: options.thumbTouchAreaYDilation
      } );

      if ( options.showTicks ) {

        // HSlider does not support a tick that is centered on the track.  We need to use our own tick node here.
        const trackCenterX = options.trackSize.width / 2;
        const tickYOffset = options.trackSize.height / 2;

        const tickNode = new Line( trackCenterX, -TICK_LENGTH, trackCenterX, TICK_LENGTH + tickYOffset, {
          stroke: TICK_COLOR,
          lineWidth: TICK_WIDTH
        } );

        // label the zero tick mark
        const tickText = new Text( '0', {
          font: TICK_LABEL_FONT,
          bottom: tickNode.top - 5,
          centerX: tickNode.centerX + 1,
          rotation: Math.PI / 2
        } );
        slider.addChild( tickText );

        // to balance out the zero label
        const strutForSymmetry = new VStrut( tickText.width - 3, { top: tickNode.bottom + 5 } );
        slider.addChild( strutForSymmetry );

        // add the tick as a child and move it behind the slider thumb
        slider.addChild( tickNode );
        tickNode.moveToBack();

        this.addChild( new RichText( symbol, {
          font: SYMBOL_FONT,
          fill: GQColors.INTERACTIVE_CURVE
        } ) );
      }

      this.addChild( slider );

      // make vertical slider by rotating it
      slider.rotate( -Math.PI / 2 );
    }
  }

  return graphingQuadratics.register( 'SliderUnit', SliderUnit );
} );
<commit_msg>TODO #7<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * Create a vertical slider with a central tick
 *
 * @author Andrea Lin
 */
define( function( require ) {
  'use strict';

  // modules
  const Dimension2 = require( 'DOT/Dimension2' );
  const GQColors = require( 'GRAPHING_QUADRATICS/common/GQColors' );
  const GQConstants = require( 'GRAPHING_QUADRATICS/common/GQConstants' );
  const graphingQuadratics = require( 'GRAPHING_QUADRATICS/graphingQuadratics' );
  const HSlider = require( 'SUN/HSlider' );
  const Line = require( 'SCENERY/nodes/Line' );
  const PhetFont = require( 'SCENERY_PHET/PhetFont' );
  const RichText = require( 'SCENERY/nodes/RichText' );
  const Text = require( 'SCENERY/nodes/Text' );
  const VBox = require( 'SCENERY/nodes/VBox' );
  const VStrut = require( 'SCENERY/nodes/VStrut' );

  // constants
  const TICK_COLOR = 'black';
  const TICK_LENGTH = 30;
  const TICK_WIDTH = 1;
  const TICK_LABEL_FONT = new PhetFont( GQConstants.SLIDER_TICK_LABEL_FONT_SIZE );
  const SYMBOL_FONT = new PhetFont( { size: GQConstants.INTERACTIVE_EQUATION_FONT_SIZE, weight: 'bold' } );

  class SliderUnit extends VBox {

    /**
     * @param {string} symbol
     * @param {NumberProperty} property
     * @param {number} decimalPlaces
     * @param {Object} [options]
     */
    constructor( symbol, property, decimalPlaces, options ) {

      options = _.extend( {

        showTicks: true,

        // HSlider options
        trackFill: 'black',
        trackSize: new Dimension2( 160, 1 ),
        thumbSize: new Dimension2( 20, 45 ),
        thumbTouchAreaYDilation: 8,

        // superclass options
        align: 'center'
      }, options );

      super( options );

      const slider = new HSlider( property, property.range, {
        trackFill: options.trackFill,
        trackSize: options.trackSize,
        thumbSize: options.thumbSize,
        thumbTouchAreaYDilation: options.thumbTouchAreaYDilation
      } );

      //TODO this assumes that zero is in the middle of the range, and zero is the only tick
      if ( options.showTicks ) {

        // HSlider does not support a tick that is centered on the track.  We need to use our own tick node here.
        const trackCenterX = options.trackSize.width / 2;
        const tickYOffset = options.trackSize.height / 2;

        const tickNode = new Line( trackCenterX, -TICK_LENGTH, trackCenterX, TICK_LENGTH + tickYOffset, {
          stroke: TICK_COLOR,
          lineWidth: TICK_WIDTH
        } );

        // label the zero tick mark
        const tickText = new Text( '0', {
          font: TICK_LABEL_FONT,
          bottom: tickNode.top - 5,
          centerX: tickNode.centerX + 1,
          rotation: Math.PI / 2
        } );
        slider.addChild( tickText );

        // to balance out the zero label
        const strutForSymmetry = new VStrut( tickText.width - 3, { top: tickNode.bottom + 5 } );
        slider.addChild( strutForSymmetry );

        // add the tick as a child and move it behind the slider thumb
        slider.addChild( tickNode );
        tickNode.moveToBack();

        this.addChild( new RichText( symbol, {
          font: SYMBOL_FONT,
          fill: GQColors.INTERACTIVE_CURVE
        } ) );
      }

      this.addChild( slider );

      // make vertical slider by rotating it
      slider.rotate( -Math.PI / 2 );
    }
  }

  return graphingQuadratics.register( 'SliderUnit', SliderUnit );
} );
<|endoftext|>


<commit_before>import React from '../react/react'
import ReactDOM from '../react/react-dom'
(function () {
    class Menu extends React.Component {
        render() {
            return (

                        Screenshot
                        Screen Record
                        About

            )
        }
    }
})()<commit_msg>fixed react import path<commit_after>import React from 'react'
import ReactDOM from 'react-dom'
(function () {
    class Menu extends React.Component {
        render() {
            return (

                        Screenshot
                        Screen Record
                        About

            )
        }
    }
})()<|endoftext|>