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

/**
 * SphereBucket is a model of a bucket that can be used to store spherical objects.  It manages the addition and removal
 * of the spheres, stacks them as they are added, and manages the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the code to understand the 'contract' between the
 * bucket and the spheres.
 *
 * @author John Blanco
 */

import Utils from '../../../dot/js/Utils.js';
import Vector2 from '../../../dot/js/Vector2.js';
import cleanArray from '../../../phet-core/js/cleanArray.js';
import merge from '../../../phet-core/js/merge.js';
import Tandem from '../../../tandem/js/Tandem.js';
import ArrayIO from '../../../tandem/js/types/ArrayIO.js';
import IOType from '../../../tandem/js/types/IOType.js';
import StringIO from '../../../tandem/js/types/StringIO.js';
import phetcommon from '../phetcommon.js';
import Bucket from './Bucket.js';

class SphereBucket extends Bucket {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {

    options = merge( {
      sphereRadius: 10,  // expected radius of the spheres that will be placed in this bucket
      usableWidthProportion: 1.0,  // proportion of the bucket width that the spheres can occupy
      tandem: Tandem.OPTIONAL,
      phetioType: SphereBucket.SphereBucketIO
    }, options );

    super( options );

    // @private
    this.sphereBucketTandem = options.tandem;
    this._sphereRadius = options.sphereRadius;
    this._usableWidthProportion = options.usableWidthProportion;

    // @private - empirically determined, for positioning particles inside the bucket
    this._verticalParticleOffset = options.verticalParticleOffset || -this._sphereRadius * 0.4;

    // @private - particles managed by this bucket
    this._particles = [];
  }

  /**
   * add a particle to the first open position in the stacking order
   * @param {Particle} particle
   * @param {boolean} animate
   * @public
   */
  addParticleFirstOpen( particle, animate ) {
    particle.destinationProperty.set( this.getFirstOpenPosition() );
    this.addParticle( particle, animate );
  }

  /**
   * add a particle to the nearest open position in the particle stack
   * @param {Particle} particle
   * @param {boolean} animate
   * @public
   */
  addParticleNearestOpen( particle, animate ) {
    particle.destinationProperty.set( this.getNearestOpenPosition( particle.destinationProperty.get() ) );
    this.addParticle( particle, animate );
  }

  /**
   * add a particle to the bucket and set up listeners for when the particle is removed
   * @param {Particle} particle
   * @param {boolean} animate
   * @private
   */
  addParticle( particle, animate ) {
    if ( !animate ) {
      particle.positionProperty.set( particle.destinationProperty.get() );
    }
    this._particles.push( particle );

    // add a listener that will remove this particle from the bucket if the user grabs it
    const particleRemovedListener = () => {
      this.removeParticle( particle );

      // the process of removing the particle from the bucket should also disconnect removal listener
      assert && assert( !particle.bucketRemovalListener, 'listener still present after being removed from bucket' );
    };
    particle.userControlledProperty.lazyLink( particleRemovedListener );
    particle.bucketRemovalListener = particleRemovedListener; // Attach to the particle to aid unlinking in some cases.
  }

  /**
   * remove a particle from the bucket, updating listeners as necessary
   * @param {Particle} particle
   * @param {boolean} skipLayout
   * @public
   */
  removeParticle( particle, skipLayout ) {
    assert && assert( this.containsParticle( particle ), 'attempt made to remove particle that is not in bucket' );

    // remove the particle from the array
    this._particles = _.without( this._particles, particle );

    // remove the removal listener if it is still present
    if ( particle.bucketRemovalListener ) {
      particle.userControlledProperty.unlink( particle.bucketRemovalListener );
      delete particle.bucketRemovalListener;
    }

    // redo the layout of the particles if enabled
    if ( !skipLayout ) {
      this.relayoutBucketParticles();
    }
  }

  /**
   * @param {Particle} particle
   * @returns {boolean}
   * @public
   */
  containsParticle( particle ) {
    return this._particles.indexOf( particle ) !== -1;
  }

  /**
   * extract the particle that is closest to the provided position from the bucket
   * @param {Vector2} position
   * @returns {Particle}
   * @public
   */
  extractClosestParticle( position ) {
    let closestParticle = null;
    this._particles.forEach( particle => {
      if ( closestParticle === null ||
           closestParticle.positionProperty.get().distance( position ) > particle.positionProperty.get().distance( position ) ) {
        closestParticle = particle;
      }
    } );
    if ( closestParticle !== null ) {

      // The particle is removed by setting 'userControlled' to true.  This relies on the listener that was added when
      // the particle was placed into the bucket.
      closestParticle.userControlledProperty.set( true );
    }
    return closestParticle;
  }

  /**
   * get the list of particles currently contained within this bucket
   * @returns {Particle[]}
   * @public
   */
  getParticleList() { return this._particles; }

  /**
   * @public
   */
  reset() {
    this._particles.forEach( particle => {

      // Remove listeners that are watching for removal from bucket.
      if ( typeof ( particle.bucketRemovalListener ) === 'function' ) {
        particle.userControlledProperty.unlink( particle.bucketRemovalListener );
        delete particle.bucketRemovalListener;
      }
    } );
    cleanArray( this._particles );
  }

  /**
   * check if the provided position is open, i.e. unoccupied by a particle
   * @param {Vector2} position
   * @returns {boolean}
   * @private
   */
  isPositionOpen( position ) {
    let positionOpen = true;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const particle = this._particles[ i ];
      if ( particle.destinationProperty.get().equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  }

  /**
   * Find the first open position in the stacking order, which is a triangular stack starting from the lower left.
   * @returns {Vector2}
   * @private
   */
  getFirstOpenPosition() {
    let openPosition = Vector2.ZERO;
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );
    let row = 0;
    let positionInLayer = 0;
    let found = false;
    while ( !found ) {
      const testPosition = new Vector2(
        this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
        this.getYPositionForLayer( row )
      );
      if ( this.isPositionOpen( testPosition ) ) {

        // We found a position that is open.
        openPosition = testPosition;
        found = true;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this._sphereRadius;
          if ( numParticlesInLayer === 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this._sphereRadius;
          }
        }
      }
    }
    return openPosition;
  }

  /**
   * get the layer in the stacking order for the provided y (vertical) position
   * @param {number} yPosition
   * @returns {number}
   * @private
   */
  getLayerForYPosition( yPosition ) {
    return Math.abs( Utils.roundSymmetric( ( yPosition - ( this.position.y + this._verticalParticleOffset ) ) / ( this._sphereRadius * 2 * 0.866 ) ) );
  }

  /**
   * Get the nearest open position in the stacking order that would be supported if the particle were to be placed
   * there.  This is used for particle stacking.
   * @param {Vector2} position
   * @returns {Vector2}
   * @private
   */
  getNearestOpenPosition( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    let highestOccupiedLayer = 0;
    _.each( this._particles, particle => {
      const layer = this.getLayerForYPosition( particle.destinationProperty.get().y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open positions in the occupied layers.
    const openPositions = [];
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( let layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open positions in the current layer.
      for ( let positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        const testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
          this.getYPositionForLayer( layer ) );
        if ( this.isPositionOpen( testPosition ) ) {

          // We found a position that is unoccupied.
          if ( layer === 0 || this.countSupportingParticles( testPosition ) === 2 ) {

            // This is a valid open position.
            openPositions.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this._sphereRadius;
      if ( numParticlesInLayer === 0 ) {

        // If the stacking pyramid is full, meaning that there are no positions that are open within it, this algorithm
        // classifies the positions directly above the top of the pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this._sphereRadius;
      }
    }

    // Find the closest open position to the provided current position.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    let closestOpenPosition = openPositions[ 0 ] || Vector2.ZERO;

    _.each( openPositions, openPosition => {
      if ( openPosition.distance( position ) < closestOpenPosition.distance( position ) ) {
        // This openPosition is closer.
        closestOpenPosition = openPosition;
      }
    } );
    return closestOpenPosition;
  }

  /**
   * given a layer in the stack, calculate the corresponding Y position for a particle in that layer
   * @param {number} layer
   * @returns {number}
   * @private
   */
  getYPositionForLayer( layer ) {
    return this.position.y + this._verticalParticleOffset + layer * this._sphereRadius * 2 * 0.866;
  }

  /**
   * Determine whether a particle is 'dangling', i.e. hanging above an open space in the stack of particles.  Dangling
   * particles should be made to fall to a stable position.
   * @param {Particle} particle
   * @returns {boolean}
   * @private
   */
  isDangling( particle ) {
    const onBottomRow = particle.destinationProperty.get().y === this.position.y + this._verticalParticleOffset;
    return !onBottomRow && this.countSupportingParticles( particle.destinationProperty.get() ) < 2;
  }

  /**
   * count the number of particles that are positioned to support a particle in the provided position
   * @param {Vector2} position
   * @returns {number} - a number from 0 to 2, inclusive
   * @private
   */
  countSupportingParticles( position ) {
    let count = 0;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const p = this._particles[ i ];
      if ( p.destinationProperty.get().y < position.y && // Must be in a lower layer
           p.destinationProperty.get().distance( position ) < this._sphereRadius * 3 ) {

        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  }

  /**
   * Relayout the particles, generally done after a particle is removed and some other need to fall.
   * @private
   */
  relayoutBucketParticles() {
    let particleMoved;
    do {
      for ( let i = 0; i < this._particles.length; i++ ) {
        particleMoved = false;
        const particle = this._particles[ i ];
        if ( this.isDangling( particle ) ) {
          particle.destinationProperty.set( this.getNearestOpenPosition( particle.destinationProperty.get() ) );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  }
}

SphereBucket.SphereBucketIO = new IOType( 'SphereBucketIO', {
  valueType: SphereBucket,
  documentation: 'A model of a bucket into which spherical objects can be placed.',
  stateSchema: {
    particles: ArrayIO( StringIO )
  },
  toStateObject: sphereBucket => {
    return { particles: sphereBucket._particles.map( particle => particle.tandem.phetioID ) }; // TODO: https://github.com/phetsims/phet-io/issues/1774 use ReferenceIO?
  },
  applyState: ( sphereBucket, stateObject ) => {

    // remove all the particles from the observable arrays
    sphereBucket.reset();
    const particles = stateObject.particles.map( tandemID => phet.phetio.phetioEngine.getPhetioObject( tandemID ) );

    // add back the particles
    particles.forEach( particle => { sphereBucket.addParticle( particle ); } );
  }
} );

phetcommon.register( 'SphereBucket', SphereBucket );
export default SphereBucket;<commit_msg>Move TODO from https://github.com/phetsims/phet-io/issues/1774 to https://github.com/phetsims/phetcommon/issues/54<commit_after>// Copyright 2013-2021, University of Colorado Boulder

/**
 * SphereBucket is a model of a bucket that can be used to store spherical objects.  It manages the addition and removal
 * of the spheres, stacks them as they are added, and manages the stack as spheres are removed.
 *
 * This expects the spheres to have certain properties, please inspect the code to understand the 'contract' between the
 * bucket and the spheres.
 *
 * @author John Blanco
 */

import Utils from '../../../dot/js/Utils.js';
import Vector2 from '../../../dot/js/Vector2.js';
import cleanArray from '../../../phet-core/js/cleanArray.js';
import merge from '../../../phet-core/js/merge.js';
import Tandem from '../../../tandem/js/Tandem.js';
import ArrayIO from '../../../tandem/js/types/ArrayIO.js';
import IOType from '../../../tandem/js/types/IOType.js';
import StringIO from '../../../tandem/js/types/StringIO.js';
import phetcommon from '../phetcommon.js';
import Bucket from './Bucket.js';

class SphereBucket extends Bucket {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {

    options = merge( {
      sphereRadius: 10,  // expected radius of the spheres that will be placed in this bucket
      usableWidthProportion: 1.0,  // proportion of the bucket width that the spheres can occupy
      tandem: Tandem.OPTIONAL,
      phetioType: SphereBucket.SphereBucketIO
    }, options );

    super( options );

    // @private
    this.sphereBucketTandem = options.tandem;
    this._sphereRadius = options.sphereRadius;
    this._usableWidthProportion = options.usableWidthProportion;

    // @private - empirically determined, for positioning particles inside the bucket
    this._verticalParticleOffset = options.verticalParticleOffset || -this._sphereRadius * 0.4;

    // @private - particles managed by this bucket
    this._particles = [];
  }

  /**
   * add a particle to the first open position in the stacking order
   * @param {Particle} particle
   * @param {boolean} animate
   * @public
   */
  addParticleFirstOpen( particle, animate ) {
    particle.destinationProperty.set( this.getFirstOpenPosition() );
    this.addParticle( particle, animate );
  }

  /**
   * add a particle to the nearest open position in the particle stack
   * @param {Particle} particle
   * @param {boolean} animate
   * @public
   */
  addParticleNearestOpen( particle, animate ) {
    particle.destinationProperty.set( this.getNearestOpenPosition( particle.destinationProperty.get() ) );
    this.addParticle( particle, animate );
  }

  /**
   * add a particle to the bucket and set up listeners for when the particle is removed
   * @param {Particle} particle
   * @param {boolean} animate
   * @private
   */
  addParticle( particle, animate ) {
    if ( !animate ) {
      particle.positionProperty.set( particle.destinationProperty.get() );
    }
    this._particles.push( particle );

    // add a listener that will remove this particle from the bucket if the user grabs it
    const particleRemovedListener = () => {
      this.removeParticle( particle );

      // the process of removing the particle from the bucket should also disconnect removal listener
      assert && assert( !particle.bucketRemovalListener, 'listener still present after being removed from bucket' );
    };
    particle.userControlledProperty.lazyLink( particleRemovedListener );
    particle.bucketRemovalListener = particleRemovedListener; // Attach to the particle to aid unlinking in some cases.
  }

  /**
   * remove a particle from the bucket, updating listeners as necessary
   * @param {Particle} particle
   * @param {boolean} skipLayout
   * @public
   */
  removeParticle( particle, skipLayout ) {
    assert && assert( this.containsParticle( particle ), 'attempt made to remove particle that is not in bucket' );

    // remove the particle from the array
    this._particles = _.without( this._particles, particle );

    // remove the removal listener if it is still present
    if ( particle.bucketRemovalListener ) {
      particle.userControlledProperty.unlink( particle.bucketRemovalListener );
      delete particle.bucketRemovalListener;
    }

    // redo the layout of the particles if enabled
    if ( !skipLayout ) {
      this.relayoutBucketParticles();
    }
  }

  /**
   * @param {Particle} particle
   * @returns {boolean}
   * @public
   */
  containsParticle( particle ) {
    return this._particles.indexOf( particle ) !== -1;
  }

  /**
   * extract the particle that is closest to the provided position from the bucket
   * @param {Vector2} position
   * @returns {Particle}
   * @public
   */
  extractClosestParticle( position ) {
    let closestParticle = null;
    this._particles.forEach( particle => {
      if ( closestParticle === null ||
           closestParticle.positionProperty.get().distance( position ) > particle.positionProperty.get().distance( position ) ) {
        closestParticle = particle;
      }
    } );
    if ( closestParticle !== null ) {

      // The particle is removed by setting 'userControlled' to true.  This relies on the listener that was added when
      // the particle was placed into the bucket.
      closestParticle.userControlledProperty.set( true );
    }
    return closestParticle;
  }

  /**
   * get the list of particles currently contained within this bucket
   * @returns {Particle[]}
   * @public
   */
  getParticleList() { return this._particles; }

  /**
   * @public
   */
  reset() {
    this._particles.forEach( particle => {

      // Remove listeners that are watching for removal from bucket.
      if ( typeof ( particle.bucketRemovalListener ) === 'function' ) {
        particle.userControlledProperty.unlink( particle.bucketRemovalListener );
        delete particle.bucketRemovalListener;
      }
    } );
    cleanArray( this._particles );
  }

  /**
   * check if the provided position is open, i.e. unoccupied by a particle
   * @param {Vector2} position
   * @returns {boolean}
   * @private
   */
  isPositionOpen( position ) {
    let positionOpen = true;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const particle = this._particles[ i ];
      if ( particle.destinationProperty.get().equals( position ) ) {
        positionOpen = false;
        break;
      }
    }
    return positionOpen;
  }

  /**
   * Find the first open position in the stacking order, which is a triangular stack starting from the lower left.
   * @returns {Vector2}
   * @private
   */
  getFirstOpenPosition() {
    let openPosition = Vector2.ZERO;
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );
    let row = 0;
    let positionInLayer = 0;
    let found = false;
    while ( !found ) {
      const testPosition = new Vector2(
        this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
        this.getYPositionForLayer( row )
      );
      if ( this.isPositionOpen( testPosition ) ) {

        // We found a position that is open.
        openPosition = testPosition;
        found = true;
      }
      else {
        positionInLayer++;
        if ( positionInLayer >= numParticlesInLayer ) {
          // Move to the next layer.
          row++;
          positionInLayer = 0;
          numParticlesInLayer--;
          offsetFromBucketEdge += this._sphereRadius;
          if ( numParticlesInLayer === 0 ) {
            // This algorithm doesn't handle the situation where
            // more particles are added than can be stacked into
            // a pyramid of the needed size, but so far it hasn't
            // needed to.  If this requirement changes, the
            // algorithm will need to change too.
            numParticlesInLayer = 1;
            offsetFromBucketEdge -= this._sphereRadius;
          }
        }
      }
    }
    return openPosition;
  }

  /**
   * get the layer in the stacking order for the provided y (vertical) position
   * @param {number} yPosition
   * @returns {number}
   * @private
   */
  getLayerForYPosition( yPosition ) {
    return Math.abs( Utils.roundSymmetric( ( yPosition - ( this.position.y + this._verticalParticleOffset ) ) / ( this._sphereRadius * 2 * 0.866 ) ) );
  }

  /**
   * Get the nearest open position in the stacking order that would be supported if the particle were to be placed
   * there.  This is used for particle stacking.
   * @param {Vector2} position
   * @returns {Vector2}
   * @private
   */
  getNearestOpenPosition( position ) {
    // Determine the highest occupied layer.  The bottom layer is 0.
    let highestOccupiedLayer = 0;
    _.each( this._particles, particle => {
      const layer = this.getLayerForYPosition( particle.destinationProperty.get().y );
      if ( layer > highestOccupiedLayer ) {
        highestOccupiedLayer = layer;
      }
    } );

    // Make a list of all open positions in the occupied layers.
    const openPositions = [];
    const usableWidth = this.size.width * this._usableWidthProportion - 2 * this._sphereRadius;
    let offsetFromBucketEdge = ( this.size.width - usableWidth ) / 2 + this._sphereRadius;
    let numParticlesInLayer = Math.floor( usableWidth / ( this._sphereRadius * 2 ) );

    // Loop, searching for open positions in the particle stack.
    for ( let layer = 0; layer <= highestOccupiedLayer + 1; layer++ ) {

      // Add all open positions in the current layer.
      for ( let positionInLayer = 0; positionInLayer < numParticlesInLayer; positionInLayer++ ) {
        const testPosition = new Vector2( this.position.x - this.size.width / 2 + offsetFromBucketEdge + positionInLayer * 2 * this._sphereRadius,
          this.getYPositionForLayer( layer ) );
        if ( this.isPositionOpen( testPosition ) ) {

          // We found a position that is unoccupied.
          if ( layer === 0 || this.countSupportingParticles( testPosition ) === 2 ) {

            // This is a valid open position.
            openPositions.push( testPosition );
          }
        }
      }

      // Adjust variables for the next layer.
      numParticlesInLayer--;
      offsetFromBucketEdge += this._sphereRadius;
      if ( numParticlesInLayer === 0 ) {

        // If the stacking pyramid is full, meaning that there are no positions that are open within it, this algorithm
        // classifies the positions directly above the top of the pyramid as being open.  This would result in a stack
        // of particles with a pyramid base.  So far, this hasn't been a problem, but this limitation may limit
        // reusability of this algorithm.
        numParticlesInLayer = 1;
        offsetFromBucketEdge -= this._sphereRadius;
      }
    }

    // Find the closest open position to the provided current position.
    // Only the X-component is used for this determination, because if
    // the Y-component is used the particles often appear to fall sideways
    // when released above the bucket, which just looks weird.
    let closestOpenPosition = openPositions[ 0 ] || Vector2.ZERO;

    _.each( openPositions, openPosition => {
      if ( openPosition.distance( position ) < closestOpenPosition.distance( position ) ) {
        // This openPosition is closer.
        closestOpenPosition = openPosition;
      }
    } );
    return closestOpenPosition;
  }

  /**
   * given a layer in the stack, calculate the corresponding Y position for a particle in that layer
   * @param {number} layer
   * @returns {number}
   * @private
   */
  getYPositionForLayer( layer ) {
    return this.position.y + this._verticalParticleOffset + layer * this._sphereRadius * 2 * 0.866;
  }

  /**
   * Determine whether a particle is 'dangling', i.e. hanging above an open space in the stack of particles.  Dangling
   * particles should be made to fall to a stable position.
   * @param {Particle} particle
   * @returns {boolean}
   * @private
   */
  isDangling( particle ) {
    const onBottomRow = particle.destinationProperty.get().y === this.position.y + this._verticalParticleOffset;
    return !onBottomRow && this.countSupportingParticles( particle.destinationProperty.get() ) < 2;
  }

  /**
   * count the number of particles that are positioned to support a particle in the provided position
   * @param {Vector2} position
   * @returns {number} - a number from 0 to 2, inclusive
   * @private
   */
  countSupportingParticles( position ) {
    let count = 0;
    for ( let i = 0; i < this._particles.length; i++ ) {
      const p = this._particles[ i ];
      if ( p.destinationProperty.get().y < position.y && // Must be in a lower layer
           p.destinationProperty.get().distance( position ) < this._sphereRadius * 3 ) {

        // Must be a supporting particle.
        count++;
      }
    }
    return count;
  }

  /**
   * Relayout the particles, generally done after a particle is removed and some other need to fall.
   * @private
   */
  relayoutBucketParticles() {
    let particleMoved;
    do {
      for ( let i = 0; i < this._particles.length; i++ ) {
        particleMoved = false;
        const particle = this._particles[ i ];
        if ( this.isDangling( particle ) ) {
          particle.destinationProperty.set( this.getNearestOpenPosition( particle.destinationProperty.get() ) );
          particleMoved = true;
          break;
        }
      }
    } while ( particleMoved );
  }
}

SphereBucket.SphereBucketIO = new IOType( 'SphereBucketIO', {
  valueType: SphereBucket,
  documentation: 'A model of a bucket into which spherical objects can be placed.',
  stateSchema: {
    particles: ArrayIO( StringIO )
  },
  toStateObject: sphereBucket => {
    return { particles: sphereBucket._particles.map( particle => particle.tandem.phetioID ) }; // TODO: https://github.com/phetsims/phetcommon/issues/54 use ReferenceIO?
  },
  applyState: ( sphereBucket, stateObject ) => {

    // remove all the particles from the observable arrays
    sphereBucket.reset();
    const particles = stateObject.particles.map( tandemID => phet.phetio.phetioEngine.getPhetioObject( tandemID ) );

    // add back the particles
    particles.forEach( particle => { sphereBucket.addParticle( particle ); } );
  }
} );

phetcommon.register( 'SphereBucket', SphereBucket );
export default SphereBucket;<|endoftext|>


<commit_before>jest.dontMock('../transitiongroup.js');

describe('TransitionGroup', function() {
	it('reminds me not to close the PR before tests are written', function() {
    var React = require('react/addons');
    var TransitionGroup = require('../tarnsitiongroup.js');
    var TestUtils = React.addons.TestUtils;

    // Render a checkbox with label in the document
    var container = TestUtils.renderIntoDocument(
      <TransitionGroup>this time i'll break travis, I'm sure of it</TransitionGroup>
    );

    expect(anchor.getDOMNode().className).toContain('not-a-chance');
  });
});<commit_msg>make sure the test is free of spelling errors<commit_after>jest.dontMock('../TransitionGroup.js');

describe('TransitionGroup', function() {
	it('reminds me not to close the PR before tests are written', function() {
    var React = require('react/addons');
    var TransitionGroup = require('../TransitionGroup.js');
    var TestUtils = React.addons.TestUtils;

    // Render a checkbox with label in the document
    var container = TestUtils.renderIntoDocument(
      <TransitionGroup>this time i'll break travis, I'm sure of it</TransitionGroup>
    );

    expect(anchor.getDOMNode().className).toContain('not-a-chance');
  });
});<|endoftext|>


<commit_before>'use strict';

var async = require('async');
var chalk = require('chalk');
var yeoman = require('yeoman-generator');
var genUtils = require('../util');

function bangLog (msg, color) {
  console.log('[' + chalk.blue('bangular') + ']: ' + chalk[color](msg));
}

var BangularGenerator = yeoman.generators.NamedBase.extend({

  prompting: function () {

    var done = this.async();

    this.name = this._.slugify(this.name);
    this.types = {};

    this.prompt([{
      type: 'checkbox',
      name: 'types',
      message: 'What font file do you have ?',
      choices: [{
        value: 'otf',
        name: 'otf',
        checked: true
      }, {
        value: 'ttf',
        name: 'ttf',
        checked: false
      }, {
        value: 'eot',
        name: 'eot',
        checked: false
      }, {
        value: 'woff',
        name: 'woff',
        checked: false
      }, {
        value: 'svg',
        name: 'svg',
        checked: false
      }]
    }], function (props) {

      props.types.forEach(function (type) {
        this.types[type] = true;
      }.bind(this));

      done();
    }.bind(this));
  },

  writing: function () {

    this.mkdir('client/assets/fonts/' + this.name);

    if (!genUtils.fileExists('client/styles/fonts.scss')) {
      this.template('fonts.scss', 'client/styles/fonts.scss');

      genUtils.appendOnTop({
        dest: 'client/styles/app.scss',
        append: '@import \'fonts\';\n'
      }, function (err) {
        if (err) {
          bangLog('There was an error importing the font file.', 'red');
        } else {
          bangLog('Your font was successfully injected.', 'green');
        }
      });

    } else {

      this.template('fonts.scss', '.tmp/fonts.scss');

      // For now we will use this since the template is created asynchronously and there is no callback.
      async.retry(4, function (cb) {
        setTimeout(function () {
          try {
            genUtils.appendTo(this, { src: '.tmp/fonts.scss', dest: 'client/styles/fonts.scss' });
            cb(null, true);
          } catch (e) {
            cb(e, null);
          }
        }, 1);
      }, function (err) {
        if (err) {
          bangLog('There was an error copying the template to your font file.', 'red');
        } else {
          bangLog('Your font was successfully injected.', 'green');
        }
      });

    }

  }

});

module.exports = BangularGenerator;
<commit_msg>fix(tests): font timeout<commit_after>'use strict';

var async = require('async');
var chalk = require('chalk');
var yeoman = require('yeoman-generator');
var genUtils = require('../util');

function bangLog (msg, color) {
  console.log('[' + chalk.blue('bangular') + ']: ' + chalk[color](msg));
}

var BangularGenerator = yeoman.generators.NamedBase.extend({

  prompting: function () {

    var done = this.async();

    this.name = this._.slugify(this.name);
    this.types = {};

    this.prompt([{
      type: 'checkbox',
      name: 'types',
      message: 'What font file do you have ?',
      choices: [{
        value: 'otf',
        name: 'otf',
        checked: true
      }, {
        value: 'ttf',
        name: 'ttf',
        checked: false
      }, {
        value: 'eot',
        name: 'eot',
        checked: false
      }, {
        value: 'woff',
        name: 'woff',
        checked: false
      }, {
        value: 'svg',
        name: 'svg',
        checked: false
      }]
    }], function (props) {

      props.types.forEach(function (type) {
        this.types[type] = true;
      }.bind(this));

      done();
    }.bind(this));
  },

  writing: function () {

    this.mkdir('client/assets/fonts/' + this.name);

    if (!genUtils.fileExists('client/styles/fonts.scss')) {
      this.template('fonts.scss', 'client/styles/fonts.scss');

      genUtils.appendOnTop({
        dest: 'client/styles/app.scss',
        append: '@import \'fonts\';\n'
      }, function (err) {
        if (err) {
          bangLog('There was an error importing the font file.', 'red');
        } else {
          bangLog('Your font was successfully injected.', 'green');
        }
      });

    } else {

      this.template('fonts.scss', '.tmp/fonts.scss');

      // For now we will use this since the template is created asynchronously and there is no callback.
      async.retry(10, function (cb) {
        setTimeout(function () {
          try {
            genUtils.appendTo(this, { src: '.tmp/fonts.scss', dest: 'client/styles/fonts.scss' });
            cb(null, true);
          } catch (e) {
            cb(e, null);
          }
        }, 2);
      }, function (err) {
        if (err) {
          bangLog('There was an error copying the template to your font file.', 'red');
        } else {
          bangLog('Your font was successfully injected.', 'green');
        }
      });

    }

  }

});

module.exports = BangularGenerator;
<|endoftext|>


<commit_before>'use strict';
document.addEventListener('DOMContentLoaded', (e) => {

	const dobInput = document.getElementById('dob'),
				usage = document.getElementById('usage'),
				inputs = document.getElementById('inputs'),
				calender = document.getElementById('calender'),
				today = new Date(),
				maxWeeks = 4693

	if(inputs instanceof HTMLFormElement && dobInput instanceof HTMLInputElement && usage instanceof HTMLParagraphElement){

		const max = new Date(),
					min = new Date()

		max.setFullYear(max.getFullYear() - 5)
		min.setFullYear(min.getFullYear() - 90)

		dobInput.value = (max.getFullYear()) + '-' +
				(max.getMonth().toString().length > 1 ? max.getMonth() : '0' + max.getMonth()) + '-' +
				(max.getDate().toString().length > 1 ? max.getDate() : '0' + max.getDate())
		dobInput.max = dobInput.value
		dobInput.min = (min.getFullYear()) + '-' +
				(min.getMonth().toString().length > 1 ? min.getMonth() : '0' + min.getMonth()) + '-' +
				(min.getDate().toString().length > 1 ? min.getDate() : '0' + min.getDate())

		dobInput.addEventListener('change', (e) => {
			const dob = new Date(Date.parse(dobInput.value))
			if(isNaN(dob.getDate())){
				dobInput.setCustomValidity('Please input the date of birth in YYYY-MM-DD format')
			} else if(dob > max) {
				dobInput.setCustomValidity('Date of birth should be less than ' +
						(max.getFullYear()) + '-' + (max.getMonth().toString().length > 1 ? max.getMonth() : '0' + max.getMonth()) + '-' +
						(max.getDate().toString().length > 1 ? max.getDate() : '0' + max.getDate())
				)
			} else if(dob < min){
				dobInput.setCustomValidity('Date of birth should be greater than ' +
						(min.getFullYear()) + '-' + (min.getMonth().toString().length > 1 ? min.getMonth() : '0' + min.getMonth()) + '-' +
						(min.getDate().toString().length > 1 ? min.getDate() : '0' + min.getDate())
				)
			} else {
				dobInput.setCustomValidity('')
			}
			localStorage.setItem('dob', dobInput.value)
		})

		inputs.addEventListener('submit', (e) => {
			e.preventDefault()

			const dob = new Date(Date.parse(dobInput.value)),
						weeks = Math.round(Math.round((today - dob) / 8.64e7) / 7)

			while(calender.firstChild){
				calender.removeChild(calender.firstChild)
			}
			setupCalender(calender, weeks, maxWeeks)
			usage.innerHTML = '
You have used ' +
					((weeks / maxWeeks) * 100).toFixed(3) + '% of your life
'
		})
	}

	if(calender && calender instanceof HTMLDivElement){
		const weeks = Math.round(Math.round((today - (new Date(Date.parse(localStorage.getItem('dob'))))) / 8.64e7) / 7)
		setupCalender(calender, weeks, maxWeeks)
		usage.innerHTML = '
You have used ' +
			((weeks / maxWeeks) * 100).toFixed(3) + '% of your life
'
	}
	dobInput.value = localStorage.getItem('dob')
})

function setupCalender(calender, weeks = 0, maxWeeks){
	for(let i = 0; i <= maxWeeks; ++i){
		const span = document.createElement('span')

		switch (true){
			// already spent weeks
			case weeks > i:
				span.classList.add('black')
				break

			// first 0-7 years
			case i < 365:
				span.classList.add('darkblue')
				break

			// 7-12 years
			case i < 626:
				span.classList.add('blue')
				break

			// more than 85 years
			case i > 4432:
				span.classList.add('red')
				break

			default:

		}
		calender.appendChild(span)
	}
}
<commit_msg>Script: Change a few things<commit_after>'use strict';
document.addEventListener('DOMContentLoaded', (e) => {

	const dobInput = document.getElementById('dob'),
				usage = document.getElementById('usage'),
				inputs = document.getElementById('inputs'),
				calender = document.getElementById('calender'),
				today = new Date(),
				maxWeeks = 4693,
				savedDob = JSON.parse(self.localStorage.getItem('dob'))

	if(inputs instanceof HTMLFormElement && dobInput instanceof HTMLInputElement && usage instanceof HTMLParagraphElement){

		const max = new Date(),
					min = new Date()

		max.setFullYear(max.getFullYear() - 5)
		min.setFullYear(min.getFullYear() - 90)

		dobInput.value = (max.getFullYear()) + '-' +
				(max.getMonth().toString().length > 1 ? max.getMonth() : '0' + max.getMonth()) + '-' +
				(max.getDate().toString().length > 1 ? max.getDate() : '0' + max.getDate())
		dobInput.max = dobInput.value
		dobInput.min = (min.getFullYear()) + '-' +
				(min.getMonth().toString().length > 1 ? min.getMonth() : '0' + min.getMonth()) + '-' +
				(min.getDate().toString().length > 1 ? min.getDate() : '0' + min.getDate())

		dobInput.addEventListener('change', (e) => {
			const dob = new Date(Date.parse(dobInput.value))
			if(isNaN(dob.getDate())){
				dobInput.setCustomValidity('Please input the date of birth in YYYY-MM-DD format')
			} else if(dob > max) {
				dobInput.setCustomValidity('Date of birth should be less than ' +
						(max.getFullYear()) + '-' + (max.getMonth().toString().length > 1 ? max.getMonth() : '0' + max.getMonth()) + '-' +
						(max.getDate().toString().length > 1 ? max.getDate() : '0' + max.getDate())
				)
			} else if(dob < min){
				dobInput.setCustomValidity('Date of birth should be greater than ' +
						(min.getFullYear()) + '-' + (min.getMonth().toString().length > 1 ? min.getMonth() : '0' + min.getMonth()) + '-' +
						(min.getDate().toString().length > 1 ? min.getDate() : '0' + min.getDate())
				)
			} else {
				dobInput.setCustomValidity('')
			}
			self.localStorage.setItem('dob', JSON.stringify(dobInput.value))
		})

		inputs.addEventListener('submit', (e) => {
			e.preventDefault()

			const dob = new Date(Date.parse(dobInput.value)),
						weeks = Math.round(Math.round((today - dob) / 8.64e7) / 7)

			while(calender.firstChild){
				calender.removeChild(calender.firstChild)
			}
			setupCalender(calender, weeks, maxWeeks)
			usage.innerHTML = '
You have used ' +
					((weeks / maxWeeks) * 100).toFixed(3) + '% of your life
'
		})
	}

	dobInput.value = savedDob
	if(calender instanceof HTMLDivElement){
		let weeks = 0
		if (savedDob) {
			weeks =  Math.round(Math.round((today - (new Date(Date.parse(savedDob)))) / 8.64e7) / 7)
			usage.innerHTML = '
You have used ' +
				((weeks / maxWeeks) * 100).toFixed(3) + '% of your life
'
		}
		setupCalender(calender, weeks, maxWeeks)
	}
})

function setupCalender(calender, weeks = 0, maxWeeks){
	for(let i = 0; i <= maxWeeks; ++i){
		const span = document.createElement('span')

		switch (true){
			// already spent weeks
			case weeks > i:
				span.classList.add('black')
				break

			// first 0-7 years
			case i < 365:
				span.classList.add('darkblue')
				break

			// 7-12 years
			case i < 626:
				span.classList.add('blue')
				break

			// more than 85 years
			case i > 4432:
				span.classList.add('red')
				break

			default:

		}
		calender.appendChild(span)
	}
}
<|endoftext|>


<commit_before>VariableSettings = (function () {
  let varDialog;

  const toggleBarcodeOptions = (checked) => {
    if (checked) {
      document.querySelector('.barcode-options').removeAttribute('style');
    } else {
      document.querySelector('.barcode-options').setAttribute('style', 'display:none;');
    }
  };

  const save = (shape) => {
    if (shape.variableBoxType()) {
      const pos = Library.actualPosition(shape.group);

      const variableTypeValue = document.querySelector('#vars').selectr.getValue();
      const barcodeSymbology = document.querySelector('#barcode_symbology').value;
      const staticBarcode = document.querySelector('#static_barcode_value');
      const squared = ((barcodeSymbology === 'QR_CODE') || (barcodeSymbology === 'DATA_MATRIX'));

      const currentValues = {
        variableId: shape.shapeId,
        variableType: variableTypeValue,
        staticValue: variableTypeValue === 'Static Barcode' ? staticBarcode.value : null,
        rotationAngle: (shape.group.rotation() % 360),
        position: pos,
        startX: pos.x0,
        startY: pos.y0,
        width: (pos.x1 - pos.x0),
        height: (pos.y1 - pos.y0),
        fontSizePx: shape.textBox.fontSize(),
        fontSizePt: fontSizes[shape.textBox.fontSize()],
        alignment: shape.textBox.align(),
        fontFamily: shape.attrs.fontFamily,
        bold: shape.attrs.bold,
        italic: shape.attrs.italic,
        underline: (shape.textBox.textDecoration() === 'underline'),
        isBarcode: String(document.querySelector('#barcode_bool').checked),
        showBarcodeText: String(document.querySelector('#barcode_text').checked),
        barcodeTop: document.querySelector('#barcode_top').value,
        barcodeWidthFactor: document.querySelector('#barcode_width_factor').value,
        barcodeMargin: document.querySelector('#barcode_margin').value,
        barcodeSymbology,
      };
      Object.keys(currentValues).forEach((prop) => {
        shape.attrs[prop] = currentValues[prop];
      });
      if (squared) {
        DrawApp.ensureSquared(shape);
      } else {
        shape.attrs.squareConstraint = false;
      }
      UndoRedoModule.registerUndoEvent();
      // console.log(MyLabel.generateXMLFile());
    }
  };

  const dialogSaveButton = () => {
    const variableTypeValue = document.querySelector('#vars').selectr.getValue();
    if (variableTypeValue) {
      if (variableTypeValue === 'Static Barcode' && document.querySelector('#static_barcode_value').value === '') {
        document.querySelector('form.variable-info-form .notice').removeAttribute('style');
      } else {
        save(MyLabel.selectedShape());
        UndoRedoModule.registerUndoEvent();

        varDialog.hide();
        document.querySelector('form.variable-info-form .notice').setAttribute('style', 'display:none;');
      }
    } else {
      document.querySelector('form.variable-info-form .notice').removeAttribute('style');
    }
  };

  const openDialog = (shape) => {
    if (shape) {
      const varSelectr = document.querySelector('#vars').selectr;
      document.querySelector('form.variable-info-form').reset();
      varDialog.show();
      const info = shape.attrs;
      if (info) {
        varSelectr.setValue(info.variableType);
        document.querySelector('#static_barcode_value').value = info.staticValue ? info.staticValue : '';
        if (info.isBarcode === 'true') {
          document.querySelector('#barcode_bool').checked = true;
          toggleBarcodeOptions(true);
          document.querySelector('#barcode_text').checked = info.showBarcodeText;
          document.querySelector('#barcode_top').value = info.barcodeTop;
          document.querySelector('#barcode_width_factor').value = info.barcodeWidthFactor;
          document.querySelector('#barcode_symbology').value = info.barcodeSymbology;
          document.querySelector('#barcode_margin').value = info.barcodeMargin;
          document.querySelector('.barcode-options').removeAttribute('style');
        } else {
          document.querySelector('.barcode-options').setAttribute('style', 'display:none;');
        }
      } else {
        document.querySelector('.barcode-options').setAttribute('style', 'display:none;');
      }
    }
  };

  const update = (attribute) => {
    const shape = MyLabel.selectedShape();
    if (shape.variableBoxType()) {
      const settings = shape.attrs;
      const pos = Library.actualPosition(shape.group);
      settings.variableId = shape.shapeId;
      switch (attribute) {
        case 'position':
          settings.position = pos;
          settings.startX = pos.x0;
          settings.startY = pos.y0;
          settings.width = (pos.x1 - pos.x0);
          settings.height = (pos.y1 - pos.y0);
          break;
        case 'rotation':
          settings.rotationAngle = (shape.group.rotation() % 360);
          break;
        case 'font-size':
          settings.fontSizePx = shape.textBox.fontSize();
          settings.fontSizePt = fontSizes[shape.textBox.fontSize()];
          break;
        case 'alignment':
          settings.alignment = shape.textBox.align();
          break;
        default:
          break;
      }
    }
  };

  document.getElementById('barcode_bool').addEventListener('change', function barcodeChange() {
    toggleBarcodeOptions(this.checked);
  });

  const varChange = (value) => {
    const staticInput = document.querySelector('#static_barcode');
    const staticInputValue = document.querySelector('#static_barcode_value');
    const barcodeBool = document.getElementById('barcode_bool');
    if (value === 'Static Barcode') {
      staticInput.style.display = 'flex';
      staticInputValue.required = true;
      barcodeBool.checked = true;
      toggleBarcodeOptions(true);
      barcodeBool.disabled = true;
    } else {
      staticInputValue.required = false;
      staticInput.style.display = 'none';
      barcodeBool.disabled = false;
    }
  };

  document.addEventListener('DOMContentLoaded', () => {
    const variableTypes = document.querySelector('#vars');
    const holdSel = new Selectr(variableTypes, {
      customClass: 'cbl-input',
      defaultSelected: true,
      allowDeselect: false,
      clearable: true,
      // width: 'notset',
    });
    // Store a reference on the DOM node.
    variableTypes.selectr = holdSel;

    holdSel.on('selectr.change', (option) => {
      varChange(option === null ? null : option.value);
    });

    const dialogEl = document.getElementById('variable-info-dialog-form');
    varDialog = new window.A11yDialog(dialogEl); // Add reset to onclose event....
    varDialog.on('hide', () => {
      const shape = MyLabel.selectedShape();
      if (shape.attrs.variableType === '0') {
        varDialog.show();
        document.querySelector('form.variable-info-form .notice').removeAttribute('style');
      } else {
        document.querySelector('form.variable-info-form .notice').setAttribute('style', 'display:none;');
        document.querySelector('form.variable-info-form').reset();
        TextSettings.focusTextbox();
      }
    });
  });

  document.querySelector('.btn-variable-settings').addEventListener('click', () => {
    const shape = MyLabel.selectedShape();
    if (shape.variableBoxType()) {
      VariableSettings.openDialog(shape);
    } else {
      this.style = 'display:none;';
    }
  });

  document.querySelector('#variable-info-dialog-form button.save').addEventListener('click', () => {
    dialogSaveButton();
  });

  const preventOverlap = (shape) => {
    if (shape.variableBoxType() && Library.overlap(shape.outerBox)) {
      return DrawApp.setToTemporaryPosition(shape);
    }
    return false;
  };

  return {
    save,
    update,
    openDialog,
    preventOverlap,
  };
}());
<commit_msg>variable_settings.js: move variable declarations to top<commit_after>VariableSettings = (function () {
  let varDialog;

  // UI controls
  const variableSelect = document.getElementById('vars');
  const barcodeOptions = document.querySelector('.barcode-options');
  const staticInput = document.getElementById('static_barcode');
  const staticInputValue = document.getElementById('static_barcode_value');
  const barcodeSymbologySel = document.getElementById('barcode_symbology');
  const barcodeBool = document.getElementById('barcode_bool');
  const barcodeText = document.getElementById('barcode_text');
  const barcodeTop = document.getElementById('barcode_top');
  const barcodeWidthFactor = document.getElementById('barcode_width_factor');
  const barcodeMargin = document.getElementById('barcode_margin');
  const varForm = document.querySelector('form.variable-info-form');
  const errBox = document.querySelector('form.variable-info-form .notice');

  const noVarErr = 'Please ensure that a Variable type is saved';
  const noStaticErr = 'Please fill in text for a Static Barcode';

  const toggleBarcodeOptions = (checked) => {
    if (checked) {
      barcodeOptions.removeAttribute('style');
    } else {
      barcodeOptions.setAttribute('style', 'display:none;');
    }
  };

  const save = (shape) => {
    if (shape.variableBoxType()) {
      const pos = Library.actualPosition(shape.group);

      const variableTypeValue = variableSelect.selectr.getValue();
      const barcodeSymbology = barcodeSymbologySel.value;
      const squared = ((barcodeSymbology === 'QR_CODE') || (barcodeSymbology === 'DATA_MATRIX'));

      const currentValues = {
        variableId: shape.shapeId,
        variableType: variableTypeValue,
        staticValue: variableTypeValue === 'Static Barcode' ? staticInputValue.value : null,
        rotationAngle: (shape.group.rotation() % 360),
        position: pos,
        startX: pos.x0,
        startY: pos.y0,
        width: (pos.x1 - pos.x0),
        height: (pos.y1 - pos.y0),
        fontSizePx: shape.textBox.fontSize(),
        fontSizePt: fontSizes[shape.textBox.fontSize()],
        alignment: shape.textBox.align(),
        fontFamily: shape.attrs.fontFamily,
        bold: shape.attrs.bold,
        italic: shape.attrs.italic,
        underline: (shape.textBox.textDecoration() === 'underline'),
        isBarcode: String(barcodeBool.checked),
        showBarcodeText: String(barcodeText.checked),
        barcodeTop: barcodeTop.value,
        barcodeWidthFactor: barcodeWidthFactor.value,
        barcodeMargin: barcodeMargin.value,
        barcodeSymbology,
      };
      Object.keys(currentValues).forEach((prop) => {
        shape.attrs[prop] = currentValues[prop];
      });
      if (squared) {
        DrawApp.ensureSquared(shape);
      } else {
        shape.attrs.squareConstraint = false;
      }
      UndoRedoModule.registerUndoEvent();
      // console.log(MyLabel.generateXMLFile());
    }
  };

  const dialogSaveButton = () => {
    const variableTypeValue = variableSelect.selectr.getValue();
    if (variableTypeValue) {
      if (variableTypeValue === 'Static Barcode' && staticInputValue.value === '') {
        errBox.textContent = noStaticErr;
        errBox.removeAttribute('style');
      } else {
        save(MyLabel.selectedShape());
        UndoRedoModule.registerUndoEvent();

        varDialog.hide();
        errBox.setAttribute('style', 'display:none;');
      }
    } else {
      errBox.textContent = noVarErr;
      errBox.removeAttribute('style');
    }
  };

  const openDialog = (shape) => {
    if (shape) {
      const varSelectr = variableSelect.selectr;
      varForm.reset();
      varDialog.show();
      const info = shape.attrs;
      if (info) {
        varSelectr.setValue(info.variableType);
        staticInputValue.value = info.staticValue ? info.staticValue : '';
        if (info.isBarcode === 'true') {
          barcodeBool.checked = true;
          toggleBarcodeOptions(true);
          barcodeText.checked = info.showBarcodeText;
          barcodeTop.value = info.barcodeTop;
          barcodeWidthFactor.value = info.barcodeWidthFactor;
          barcodeSymbologySel.value = info.barcodeSymbology;
          barcodeMargin.value = info.barcodeMargin;
          barcodeOptions.removeAttribute('style');
        } else {
          barcodeOptions.setAttribute('style', 'display:none;');
        }
      } else {
        barcodeOptions.setAttribute('style', 'display:none;');
      }
    }
  };

  const update = (attribute) => {
    const shape = MyLabel.selectedShape();
    if (shape.variableBoxType()) {
      const settings = shape.attrs;
      const pos = Library.actualPosition(shape.group);
      settings.variableId = shape.shapeId;
      switch (attribute) {
        case 'position':
          settings.position = pos;
          settings.startX = pos.x0;
          settings.startY = pos.y0;
          settings.width = (pos.x1 - pos.x0);
          settings.height = (pos.y1 - pos.y0);
          break;
        case 'rotation':
          settings.rotationAngle = (shape.group.rotation() % 360);
          break;
        case 'font-size':
          settings.fontSizePx = shape.textBox.fontSize();
          settings.fontSizePt = fontSizes[shape.textBox.fontSize()];
          break;
        case 'alignment':
          settings.alignment = shape.textBox.align();
          break;
        default:
          break;
      }
    }
  };

  barcodeBool.addEventListener('change', function barcodeChange() {
    toggleBarcodeOptions(this.checked);
  });

  const varChange = (value) => {
    if (value === 'Static Barcode') {
      staticInput.style.display = 'flex';
      staticInputValue.required = true;
      barcodeBool.checked = true;
      toggleBarcodeOptions(true);
      barcodeBool.disabled = true;
    } else {
      staticInputValue.required = false;
      staticInput.style.display = 'none';
      barcodeBool.disabled = false;
    }
  };

  document.addEventListener('DOMContentLoaded', () => {
    const holdSel = new Selectr(variableSelect, {
      customClass: 'cbl-input',
      defaultSelected: true,
      allowDeselect: false,
      clearable: true,
      // width: 'notset',
    });
    // Store a reference on the DOM node.
    variableSelect.selectr = holdSel;

    holdSel.on('selectr.change', (option) => {
      varChange(option === null ? null : option.value);
    });

    const dialogEl = document.getElementById('variable-info-dialog-form');
    varDialog = new window.A11yDialog(dialogEl); // Add reset to onclose event....
    varDialog.on('hide', () => {
      const shape = MyLabel.selectedShape();
      if (shape.attrs.variableType === '0') {
        varDialog.show();
        errBox.textContent = noVarErr;
        errBox.removeAttribute('style');
      } else {
        errBox.setAttribute('style', 'display:none;');
        varForm.reset();
        TextSettings.focusTextbox();
      }
    });
  });

  document.querySelector('.btn-variable-settings').addEventListener('click', () => {
    const shape = MyLabel.selectedShape();
    if (shape.variableBoxType()) {
      VariableSettings.openDialog(shape);
    } else {
      this.style = 'display:none;';
    }
  });

  document.querySelector('#variable-info-dialog-form button.save').addEventListener('click', () => {
    dialogSaveButton();
  });

  const preventOverlap = (shape) => {
    if (shape.variableBoxType() && Library.overlap(shape.outerBox)) {
      return DrawApp.setToTemporaryPosition(shape);
    }
    return false;
  };

  return {
    save,
    update,
    openDialog,
    preventOverlap,
  };
}());
<|endoftext|>


<commit_before>function(args) {
/*
is_app(true)
control_type("VB")
display_name("3d text")
description("This will return the 3d text item control")
base_component_id("threedee_text_control")
load_once_from_file(true)
visibility("PRIVATE")
read_only(true)
properties(
    [
        {
            id:         "text",
            name:       "Text",
            default:    "Some text",
            type:       "String"
        }
        ,
        {
            id:         "backgroundColor",
            name:       "Background Color",
            default:    "lightgray",
            type:       "String"
        }
        ,
        {
            id:         "color",
            name:       "Color",
            default:    "black",
            type:       "String"
        }
        ,
        {
            id:         "x",
            name:       "X",
            type:       "Number",
            default:    -2
        }
        ,
        {
            id:         "y",
            name:       "Y",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:         "z",
            name:       "Z",
            type:       "Number",
            default:    -5
        }
        ,
        {
            id:         "rotation",
            name:       "Rotation",
            type:       "String",
            default:    "0 45 0"
        }
        ,
        {
            id:         "textWidth",
            name:       "Text Width",
            type:       "Number",
            default:    4
        }
        ,
        {
            id:         "textHeight",
            name:       "Text Height",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:     "click_event",
            name:   "Clicked event",
            type:   "Event",
            help:       `
Help text for
                            click_event event

`
        }
        ,
        {
            id:         "boxDepth",
            name:       "Box Depth",
            type:       "Number",
            default:    .1
        }
        ,
        {
            id:         "boxHeight",
            name:       "Box Height",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:         "boxWidth",
            name:       "Box Width",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:         "moveRight",
            snippet:    `moveRight(1)`,
            name:       "Move Right",
            type:       "Action"
        }
        ,
        {
            id:         "moveTo",
            snippet:    `moveTo("0 0 0")`,
            name:       "Move To",
            type:       "Action"
        }
    ]
)//properties
logo_url("/driver_icons/threedee_text_control.png")
*/

    Vue.component("threedee_text_control",{
        props: [  "meta", "args"  ,  "design_mode"  ,  "refresh"  ,  "name"  ]

        ,

        template: `

                        <a-entity v-if='args'
                                  v-bind:id='name'
                                  v-bind:rotation='args.rotation'
                                  v-bind:geometry='"primitive: box; depth: " + args.boxDepth + "; height: " + args.boxHeight + "; width: " + args.boxWidth + ";"'
                                  v-bind:material='"color: " + args.backgroundColor + "; side: double; "'
                                  v-bind:refresh='refresh'
                                  v-bind:position='args.x + " " + args.y + " " + args.z'>

                  `
            ,

            mounted: function() {
                var mm = this
                registerComponent(this)
                var dd = document.getElementById(this.name)
                dd.addEventListener('click', function() {
                    mm.event_callback()
                });
            }
            ,
            methods: {
                event_callback: function() {
                    console.log("----- 3d text, event_callback: function() = " + this.name)
                    //eval("(function(){" + this.args.click_event + "})")()

                    this.$emit('send', {
                                                    type:               "subcomponent_event",
                                                    form_name:           this.meta.form,
                                                    control_name:        this.meta.name,
                                                    sub_type:           "click",
                                                    code:                this.args.click_event
                                                })
                }
                ,
                moveRight: async function(amount) {
                    await this.moveTo({
                        x:     this.args.x + amount
                    })

                }

                ,
                moveTo: async function(opts) {
                debugger
                    var mm          = this
                    var animationId = "animation_" + name
                    var dd          =  document.querySelector("#" + this.name)
                    var anim        =  document.querySelector("#" + animationId)
                    if (anim) {
                        anim.parentElement.removeChild(anim);
                    }
                    var newX = this.args.x
                    var newY = this.args.y
                    var newZ = this.args.z
                    if (isValidObject(opts.x)) {
                        newX = opts.x
                    }
                    if (isValidObject(opts.y)) {
                        newY = opts.y
                    }
                    if (isValidObject(opts.z)) {
                        newZ = opts.z
                    }
                    var newPosition = newX + " " + newY + " " + newZ

                    var para = document.createElement("a-animation");
                    para.setAttribute("id",          animationId);
                    para.setAttribute("attribute",  "position");
                    para.setAttribute("dur",        "2000");
                    para.setAttribute("fill",       "forwards");
                    para.setAttribute("to",         newPosition );
                    para.setAttribute("repeat",     "0");
                    dd.appendChild(para)

                    setTimeout(function() {
                        mm.args.x = newX
                        mm.args.y = newY
                        mm.args.z = newZ
                    },2000)

                }

            }
        }

    )
}
<commit_msg>3d text items can now move in all directions<commit_after>function(args) {
/*
is_app(true)
control_type("VB")
display_name("3d text")
description("This will return the 3d text item control")
base_component_id("threedee_text_control")
load_once_from_file(true)
visibility("PRIVATE")
read_only(true)
properties(
    [
        {
            id:         "text",
            name:       "Text",
            default:    "Some text",
            type:       "String"
        }
        ,
        {
            id:         "backgroundColor",
            name:       "Background Color",
            default:    "lightgray",
            type:       "String"
        }
        ,
        {
            id:         "color",
            name:       "Color",
            default:    "black",
            type:       "String"
        }
        ,
        {
            id:         "x",
            name:       "X",
            type:       "Number",
            default:    -2
        }
        ,
        {
            id:         "y",
            name:       "Y",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:         "z",
            name:       "Z",
            type:       "Number",
            default:    -5
        }
        ,
        {
            id:         "rotation",
            name:       "Rotation",
            type:       "String",
            default:    "0 45 0"
        }
        ,
        {
            id:         "textWidth",
            name:       "Text Width",
            type:       "Number",
            default:    4
        }
        ,
        {
            id:         "textHeight",
            name:       "Text Height",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:     "click_event",
            name:   "Clicked event",
            type:   "Event",
            help:       `
Help text for
                            click_event event

`
        }
        ,
        {
            id:         "boxDepth",
            name:       "Box Depth",
            type:       "Number",
            default:    .1
        }
        ,
        {
            id:         "boxHeight",
            name:       "Box Height",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:         "boxWidth",
            name:       "Box Width",
            type:       "Number",
            default:    2
        }
        ,
        {
            id:         "moveRight",
            snippet:    `moveRight(1)`,
            name:       "Move Right",
            type:       "Action"
        }
        ,
        {
            id:         "moveUp",
            snippet:    `moveUp(1)`,
            name:       "Move Up",
            type:       "Action"
        }
        ,
        {
            id:         "moveLeft",
            snippet:    `moveLeft(1)`,
            name:       "Move Left",
            type:       "Action"
        }
        ,
        {
            id:         "moveDown",
            snippet:    `moveDown(1)`,
            name:       "Move Down",
            type:       "Action"
        }
        ,
        {
            id:         "moveBack",
            snippet:    `moveBack(1)`,
            name:       "Move Back",
            type:       "Action"
        }
        ,
        {
            id:         "moveForward",
            snippet:    `moveForward(1)`,
            name:       "Move Forward",
            type:       "Action"
        }
        ,
        {
            id:         "moveTo",
            snippet:    `moveTo("0 0 0")`,
            name:       "Move To",
            type:       "Action"
        }
    ]
)//properties
logo_url("/driver_icons/threedee_text_control.png")
*/

    Vue.component("threedee_text_control",{
        props: [  "meta", "args"  ,  "design_mode"  ,  "refresh"  ,  "name"  ]

        ,

        template: `

                        <a-entity v-if='args'
                                  v-bind:id='name'
                                  v-bind:rotation='args.rotation'
                                  v-bind:geometry='"primitive: box; depth: " + args.boxDepth + "; height: " + args.boxHeight + "; width: " + args.boxWidth + ";"'
                                  v-bind:material='"color: " + args.backgroundColor + "; side: double; "'
                                  v-bind:refresh='refresh'
                                  v-bind:position='args.x + " " + args.y + " " + args.z'>

                  `
            ,

            mounted: function() {
                var mm = this
                registerComponent(this)
                var dd = document.getElementById(this.name)
                dd.addEventListener('click', function() {
                    mm.event_callback()
                });
            }
            ,
            methods: {
                event_callback: function() {
                    console.log("----- 3d text, event_callback: function() = " + this.name)
                    //eval("(function(){" + this.args.click_event + "})")()

                    this.$emit('send', {
                                                    type:               "subcomponent_event",
                                                    form_name:           this.meta.form,
                                                    control_name:        this.meta.name,
                                                    sub_type:           "click",
                                                    code:                this.args.click_event
                                                })
                }
                ,
                moveRight: async function(amount) {
                    await this.moveTo({
                        x:     this.args.x + amount
                    })

                }
                ,
                moveLeft: async function(amount) {
                    await this.moveTo({
                        x:     this.args.x - amount
                    })

                }
                ,
                moveUp: async function(amount) {
                    await this.moveTo({
                        y:     this.args.y + amount
                    })

                }
                ,
                moveDown: async function(amount) {
                    await this.moveTo({
                        y:     this.args.y - amount
                    })

                }
                ,
                moveBack: async function(amount) {
                    await this.moveTo({
                        z:     this.args.z - amount
                    })
                }
                ,
                moveForward: async function(amount) {
                    await this.moveTo({
                        z:     this.args.z + amount
                    })

                }

                ,
                moveTo: async function(opts) {
                debugger
                    var mm          = this
                    var animationId = "animation_" + name
                    var dd          =  document.querySelector("#" + this.name)
                    var anim        =  document.querySelector("#" + animationId)
                    if (anim) {
                        anim.parentElement.removeChild(anim);
                    }
                    var newX = this.args.x
                    var newY = this.args.y
                    var newZ = this.args.z
                    if (isValidObject(opts.x)) {
                        newX = opts.x
                    }
                    if (isValidObject(opts.y)) {
                        newY = opts.y
                    }
                    if (isValidObject(opts.z)) {
                        newZ = opts.z
                    }
                    var newPosition = newX + " " + newY + " " + newZ

                    var para = document.createElement("a-animation");
                    para.setAttribute("id",          animationId);
                    para.setAttribute("attribute",  "position");
                    para.setAttribute("dur",        "2000");
                    para.setAttribute("fill",       "forwards");
                    para.setAttribute("to",         newPosition );
                    para.setAttribute("repeat",     "0");
                    dd.appendChild(para)

                    setTimeout(function() {
                        mm.args.x = newX
                        mm.args.y = newY
                        mm.args.z = newZ
                    },2000)

                }

            }
        }

    )
}
<|endoftext|>


<commit_before>var Komachan = require('../index.js');
var util = require('util');

function KomachanHandler() {
  var self = this;
  self.color;
  self.komachan = Komachan.newKomachan();
  self.socket = require('./socket').socket().socket;
  self.moved();
  self.socket.on('legal', function(lmoves){
    //console.log('legal', lmoves);
  })
  self.socket.on('win', function(mes){
    self.komachan.end();
  });
  self.socket.on('lose', function(mes) {
    self.komachan.end();
  });
  self.socket.on('err', function(err){
    self.komachan.end();
  });
}

KomachanHandler.prototype.getKomachan = function() {
  return this.komachan;
}

KomachanHandler.prototype.start = function(opt) {
  var self = this;
  self.komachan.start(opt);
  //util.console.log(require('util').inspect(self.komachan.getZobrist(), { depth: null }));
  self.socket.emit('new-game', {});
  self.socket.on('confirm', function(game) {
    if (opt.color == 0) {
      game.header.firstMove = 'client';
      game.header.secondMove = 'koma';
    }
    if (opt.color == 1) {
      game.header.firstMove = 'koma';
      game.header.secondMove = 'client';
    }
    self.socket.emit('agree', game);
  });
  self.socket.on('start', function(game) {
    //self.komachan.print();
    if (game.header.firstMove === 'client') {
      self.color = 0;
      self.move();
    } else {
      self.color = 1;
    }
    //self.komachan.print();
  });
}

KomachanHandler.prototype.manualStart = function(opt) {
  var self = this;
  self.komachan.start(opt);
}

KomachanHandler.prototype.move = function() {
  var self = this;
  var kmove = self.komachan.search();
  if (!kmove) {
    return;
  }
  self.socket.emit('move', kmove);
}

KomachanHandler.prototype.moved = function() {
  var self = this;
  self.socket.on('moved', function(move){
    console.log('moved', move);
    if (move.color !== self.color){
      self.komachan.move(move);
      self.komachan.print();
      self.move();
      self.komachan.print();
    }
  });
}

KomachanHandler.prototype.manualMove = function(move) {
    var self = this;
    self.komachan.move(move);
}

KomachanHandler.prototype.manualBack = function() {
    var self = this;
    self.komachan.back();
}

KomachanHandler.prototype.print = function() {
  var self = this;
  self.komachan.print();
}

KomachanHandler.prototype.end = function() {
  this.komachan.end();
}

KomachanHandler.prototype.finish = function() {
  var self = this;
  return new Promise(function(resolve){
    self.socket.on('win', function(wgame){
      resolve(wgame);
    });
    self.socket.on('lose', function(lgame){
      resolve(lgame);
    });
  });
}

module.exports = KomachanHandler;
<commit_msg>may be ok komachanhandler stop search<commit_after>var Komachan = require('../index.js');
var util = require('util');

function KomachanHandler() {
  var self = this;
  self.color;
  self.komachan = Komachan.newKomachan();
  self.socket = require('./socket').socket().socket;
  self.socket.on('legal', function(lmoves){
    //console.log('legal', lmoves);
  })
  self.socket.on('win', function(mes){
    self.komachan.end();
  });
  self.socket.on('lose', function(mes) {
    self.komachan.end();
  });
  self.socket.on('err', function(err){
    self.komachan.end();
  });
}

KomachanHandler.prototype.getKomachan = function() {
  return this.komachan;
}

KomachanHandler.prototype.start = function(opt) {
  var self = this;
  self.komachan.start(opt);
  self.socket.emit('new-game', {});
  self.socket.on('confirm', function(game) {
    if (opt.color == 0) {
      game.header.firstMove = 'client';
      game.header.secondMove = 'koma';
    }
    if (opt.color == 1) {
      game.header.firstMove = 'koma';
      game.header.secondMove = 'client';
    }
    self.socket.emit('agree', game);
  });
  self.socket.on('start', function(game) {
    if (game.header.firstMove === 'client') {
      self.color = 0;
    } else {
      self.color = 1;
    }
  });
  self.move();
  self.moved();
}

KomachanHandler.prototype.manualStart = function(opt) {
  var self = this;
  self.komachan.start(opt);
}

KomachanHandler.prototype.move = function() {
  var self = this;
  self.socket.on('legal', function(legal) {
    var kmove = self.komachan.search();
    if (!kmove) {
      return;
    }
    self.socket.emit('move', kmove);
    self.komachan.print();
  });
}

KomachanHandler.prototype.moved = function() {
  var self = this;
  self.socket.on('moved', function(move){
    if (move.color !== self.color){
      self.komachan.move(move);
      self.komachan.print();
    }
  });
}

KomachanHandler.prototype.manualMove = function(move) {
    var self = this;
    self.komachan.move(move);
}

KomachanHandler.prototype.manualBack = function() {
    var self = this;
    self.komachan.back();
}

KomachanHandler.prototype.print = function() {
  var self = this;
  self.komachan.print();
}

KomachanHandler.prototype.end = function() {
  this.komachan.end();
}

KomachanHandler.prototype.finish = function() {
  var self = this;
  return new Promise(function(resolve){
    self.socket.on('win', function(wgame){
      resolve(wgame);
    });
    self.socket.on('lose', function(lgame){
      resolve(lgame);
    });
  });
}

module.exports = KomachanHandler;
<|endoftext|>


<commit_before>class App extends React.Component  {
  render() {
    return (

            <Content activities={this.activities} />

    )
  }
}

class Header extends React.Component {
  render() {
    return (

              {this.props.title}

            <input
              type="text"
              className="searchInput"
              placeholder="Search ..." />

    )
  }
}

class Content extends React.Component {

  render() {
    const activities = [
      {
        timestamp: new Date().getTime(),
        text: "Ate lunch",
        user: {
          id: 1,
          name: "Nate",
          avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
        },
        comments: [
          {
            from: 'Ari',
            text: 'Me too!'
          }
        ]
      }
    ]
    return (

            {this.activities.user.name}

            {this.activities.timestamp}

{this.activities.text}

          {this.activities.comments.length}

    )
  }
}
var mount = document.querySelector('#app');
ReactDOM.render(<App />, mount);
<commit_msg>restructuring<commit_after>function App(props) {
    return (

            <Content />

    )
};

function Header(props) {
    return (

              {props.title}

            <input
              type="text"
              className="searchInput"
              placeholder="Search ..." />

    )
};

function Content(props) {
    return (

            {props.user.name}

            {props.timestamp}

{props.text}

          {props.comments.length}

    )
};

const info = [
  {
  title: "Howdy, React"
  }
];

const activities = [
  {
    timestamp: new Date().getTime(),
    text: "Ate lunch",
    user: {
      id: 1,
      name: "Nate",
      avatar: "http://www.croop.cl/UI/twitter/images/doug.jpg"
    },
    comments: [
      {
        from: 'Ari',
        text: 'Me too!'
      }
    ]
  }
];

var mount = document.querySelector('#app');
ReactDOM.render(
  <App
    title={info.title}
    timestamp={activities.timestamp}
    text={activities.text}
    user={activities.user}
    comments={activities.comments}
   />, mount);
<|endoftext|>


<commit_before>/**
 * Copyright 2017 Red Hat Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import React from 'react';

import { ConfirmationModal } from '../ui/Modals';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { InlineLoader } from '../ui/Loader';

const messages = defineMessages({
  deleteDeployment: {
    id: 'DeleteStackButton.deleteDeployment',
    defaultMessage: 'Delete Deployment'
  },
  deleteConfirmationQuestion: {
    id: 'DeleteStackButton.deleteConfirmationQuestion',
    defaultMessage: 'Are you sure you want to delete the stack?'
  }
});

class DeleteStackButton extends React.Component {
  constructor() {
    super();
    this.state = {
      showDeleteModal: false
    };
  }

  confirmDelete() {
    this.props.deleteStack(this.props.stack);
    this.setState({ showDeleteModal: false });
  }

  render() {
    const { formatMessage } = this.props.intl;

    return (

        <button
          onClick={() => this.setState({ showDeleteModal: true })}
          type="button"
          name="delete"
          disabled={this.props.disabled}
          className="link btn btn-danger"
        >
          <InlineLoader
            loaded={this.props.loaded}
            content={this.props.loaderContent}
            inverse
          >
             {this.props.content}
          </InlineLoader>
        </button>
        <ConfirmationModal
          show={this.state.showDeleteModal}
          title={formatMessage(messages.deleteDeployment)}
          question={formatMessage(messages.deleteConfirmationQuestion)}
          confirmActionName="delete"
          onConfirm={this.confirmDelete.bind(this)}
          onCancel={() => this.setState({ showDeleteModal: false })}
        />

    );
  }
}

DeleteStackButton.propTypes = {
  buttonIconClass: PropTypes.string.isRequired,
  content: PropTypes.string.isRequired,
  deleteStack: PropTypes.func.isRequired,
  disabled: PropTypes.bool.isRequired,
  intl: PropTypes.object,
  loaded: PropTypes.bool.isRequired,
  loaderContent: PropTypes.string.isRequired,
  stack: ImmutablePropTypes.record.isRequired
};
DeleteStackButton.defaultProps = {
  buttonIconClass: 'pficon pficon-delete'
};

export default injectIntl(DeleteStackButton);
<commit_msg>Update Delete Deployment Confirmation message<commit_after>/**
 * Copyright 2017 Red Hat Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import React from 'react';

import { ConfirmationModal } from '../ui/Modals';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { InlineLoader } from '../ui/Loader';

const messages = defineMessages({
  deleteDeployment: {
    id: 'DeleteStackButton.deleteDeployment',
    defaultMessage: 'Delete Deployment'
  },
  deleteConfirmationQuestion: {
    id: 'DeleteStackButton.deleteConfirmationQuestion',
    defaultMessage: 'Are you sure you want to delete the deployment?'
  }
});

class DeleteStackButton extends React.Component {
  constructor() {
    super();
    this.state = {
      showDeleteModal: false
    };
  }

  confirmDelete() {
    this.props.deleteStack(this.props.stack);
    this.setState({ showDeleteModal: false });
  }

  render() {
    const { formatMessage } = this.props.intl;

    return (

        <button
          onClick={() => this.setState({ showDeleteModal: true })}
          type="button"
          name="delete"
          disabled={this.props.disabled}
          className="link btn btn-danger"
        >
          <InlineLoader
            loaded={this.props.loaded}
            content={this.props.loaderContent}
            inverse
          >
            {this.props.content}
          </InlineLoader>
        </button>
        <ConfirmationModal
          show={this.state.showDeleteModal}
          title={formatMessage(messages.deleteDeployment)}
          question={formatMessage(messages.deleteConfirmationQuestion)}
          confirmActionName="delete"
          onConfirm={this.confirmDelete.bind(this)}
          onCancel={() => this.setState({ showDeleteModal: false })}
        />

    );
  }
}

DeleteStackButton.propTypes = {
  content: PropTypes.string.isRequired,
  deleteStack: PropTypes.func.isRequired,
  disabled: PropTypes.bool.isRequired,
  intl: PropTypes.object,
  loaded: PropTypes.bool.isRequired,
  loaderContent: PropTypes.string.isRequired,
  stack: ImmutablePropTypes.record.isRequired
};

export default injectIntl(DeleteStackButton);
<|endoftext|>


<commit_before>/*
 *  SettingScene.js
 *  2014/06/25
 *  @auther minimo
 *  This Program is MIT license.
 *
 */

tm.define("shotgun.SettingScene", {
    superClass: tm.app.Scene,

    bgm: null,
    se: null,

    //ラベル用フォントパラメータ
    labelParam: {fontFamily:"'azuki'", align: "center", baseline:"middle", outlineWidth:2 },

    init: function() {
        this.superInit();
        this.background = "rgba(0, 0, 0, 0.0)";
        var that = this;

        //バックグラウンド
        this.bg = tm.display.Shape(SC_W, SC_H)
            .addChildTo(this)
            .setPosition(SC_W*0.5, SC_H*0.5)
            .renderRectangle({fillStyle: appMain.bgColor, strokeStyle: appMain.bgColor});

        var lb = this.credit1 = tm.display.OutlineLabel("SETTING", 60).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.5, SC_H*0.2);

        //ＢＧＭ音量
        var lb = tm.display.OutlineLabel("BGM", 60).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.2, SC_H*0.4);
        this.bgm = [];
        for (var i = 0; i < 6; i++) {
            var lb = this.bgm[i] = tm.display.OutlineLabel(""+i, 30).addChildTo(this);
            lb.fontFamily = "'azuki'"; lb.align = "center"; lb.baseline = "middle"; lb.outlineWidth = 2;
            lb.setPosition(SC_W*0.4+i*60, SC_H*0.4);
            if (appMain.volumeBGM == i) lb.fontSize = 80;
        }

        //ＳＥ音量
        var lb = tm.display.OutlineLabel("SE", 60).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.2, SC_H*0.6);
        this.se = [];
        for (var i = 0; i < 6; i++) {
            var lb = this.se[i] = tm.display.OutlineLabel(""+i, 30).addChildTo(this);
            lb.setParam(this.labelParam);
            lb.setPosition(SC_W*0.4+i*60, SC_H*0.6);
            if (appMain.volumeSE == i) lb.fontSize = 80;
        }

        var that = this;
        var width = 250, height = 60;
        var param = {fillStyle:'rgba(0,80,0,1)', lineWidth:4};

        //戻るボタン
        shotgun.Button(width, height, "RETURN")
            .addChildTo(this)
            .setPosition(SC_W*0.5, SC_H*0.9)
            .addEventListener("pushed", function() {
                that.mask.tweener.clear().fadeOut(200);
                appMain.saveConfig();
                appMain.popScene();
            });

        //マスク
        this.mask = tm.display.Sprite("blackback", SC_W*2, SC_H*2).addChildTo(this);
        this.mask.tweener.clear().fadeOut(200);

        this.time = 0;
    },

    update: function() {
        this.time++;
    },

    //タッチorクリック開始処理
    ontouchstart: function(e) {
    },

    //タッチorクリック移動処理
    ontouchmove: function(e) {
        var sx = e.pointing.x;
        var sy = e.pointing.y;

        //ＢＧＭボリューム
        if ( SC_H*0.35 < sy && sy < SC_H*0.45 && sx > SC_W*0.4-30 && sx < SC_W*0.4-30+360) {
            var x = sx-(SC_W*0.4-30);
            x = ~~(x/60);
            if (appMain.volumeBGM != x) {
                this.bgm[appMain.volumeBGM].fontSize = 30;
                this.bgm[x].fontSize = 80;
                appMain.setVolumeBGM(x);
            }
        }
        //ＳＥボリューム
        if ( SC_H*0.55 < sy && sy < SC_H*0.65 && SC_W*0.4-30 < sx && sx < SC_W*0.4-30+360) {
            var x = sx-(SC_W*0.4-30);
            x = ~~(x/60);
            if (appMain.volumeSE != x) {
                this.se[appMain.volumeSE].fontSize = 30;
                this.se[x].fontSize = 80;
                appMain.volumeSE = x;
                appMain.playSE("hand");
            }
        }
    },

    //タッチorクリック終了処理
    ontouchend: function(e) {
    },
});

<commit_msg>設定画面修正<commit_after>/*
 *  SettingScene.js
 *  2014/06/25
 *  @auther minimo
 *  This Program is MIT license.
 *
 */

tm.define("shotgun.SettingScene", {
    superClass: tm.app.Scene,

    volBgm: 50,
    volSe: 50,

    bgm: null,
    se: null,

    //ラベル用フォントパラメータ
    labelParam: {fontFamily:"'azuki'", align: "center", baseline:"middle", outlineWidth:2 },

    init: function() {
        this.superInit();
        this.background = "rgba(0, 0, 0, 0.0)";
        var that = this;

        //バックグラウンド
        this.bg = tm.display.Shape(SC_W, SC_H)
            .addChildTo(this)
            .setPosition(SC_W*0.5, SC_H*0.5)
            .renderRectangle({fillStyle: appMain.bgColor, strokeStyle: appMain.bgColor});

        var lb = this.credit1 = tm.display.OutlineLabel("SETTING", 60).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.5, SC_H*0.2);

        var lb = this.credit1 = tm.display.OutlineLabel("SOUND VOLUME", 50).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.5, SC_H*0.3);

        //ＢＧＭ音量
        var lb = tm.display.OutlineLabel("BGM", 60).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.2, SC_H*0.4);
        this.bgm = [];
        for (var i = 0; i < 6; i++) {
            var lb = this.bgm[i] = tm.display.OutlineLabel(""+i, 30).addChildTo(this);
            lb.fontFamily = "'azuki'"; lb.align = "center"; lb.baseline = "middle"; lb.outlineWidth = 2;
            lb.setPosition(SC_W*0.4+i*60, SC_H*0.4);
            if (appMain.volumeBGM == i) lb.fontSize = 80;
        }

        //ＳＥ音量
        var lb = tm.display.OutlineLabel("SE", 60).addChildTo(this);
        lb.setParam(this.labelParam);
        lb.setPosition(SC_W*0.2, SC_H*0.5);
        this.se = [];
        for (var i = 0; i < 6; i++) {
            var lb = this.se[i] = tm.display.OutlineLabel(""+i, 30).addChildTo(this);
            lb.setParam(this.labelParam);
            lb.setPosition(SC_W*0.4+i*60, SC_H*0.5);
            if (appMain.volumeSE == i) lb.fontSize = 80;
        }

        var that = this;
        var width = 250, height = 60;
        var param = {fillStyle:'rgba(0,80,0,1)', lineWidth:4};

        //戻るボタン
        shotgun.Button(width, height, "RETURN")
            .addChildTo(this)
            .setPosition(SC_W*0.5, SC_H*0.9)
            .addEventListener("pushed", function() {
                that.mask.tweener.clear().fadeOut(200);
                appMain.saveConfig();
                appMain.popScene();
            });

        //ＢＧＭ音量ゲージ
        var scale = SC_W*0.5;
        var color = "hsla({0}, 50%, 50%, 1.0)".format(300);
        this.bgmMeter = tm.display.Shape(scale, 60)
//            .addChildTo(this)
            .setPosition(SC_W*0.4, SC_H*0.4)
            .setOrigin(0.0, 1.0);
        this.bgmMeter.update = function() {
            var limit = that.volBgm*(scale/100);
            var c = this.canvas;

            c.clear(0,0,30,500);

            // パラメータセット
            c.fillStyle = color;
            c.strokeStyle = color;
            c.lineWidth = 1;

            // 描画
            var lw      = Number(c.lineWidth);
            var lw_half = lw/2;
            c.fillRect(0, 0, scale, this.height);
            c.restore();
        }
        tm.display.Shape(scale, 60)
            .addChildTo(this)
//            .setPosition(SC_W*0.4, SC_H*0.4)
            .renderRectangle({fillStyle: "rgba(0,0,0,0)", strokeStyle: "Black", lineWidth: 3})
            .setOrigin(0.0, 1.0);

        //マスク
        this.mask = tm.display.Sprite("blackback", SC_W*2, SC_H*2).addChildTo(this);
        this.mask.tweener.clear().fadeOut(200);

        this.time = 0;
    },

    update: function() {
        this.time++;
    },

    //タッチorクリック開始処理
    ontouchstart: function(e) {
    },

    //タッチorクリック移動処理
    ontouchmove: function(e) {
        var sx = e.pointing.x;
        var sy = e.pointing.y;

        //ＢＧＭボリューム
        if ( SC_H*0.35 < sy && sy < SC_H*0.45 && sx > SC_W*0.4-30 && sx < SC_W*0.4-30+360) {
            var x = sx-(SC_W*0.4-30);
            x = ~~(x/60);
            if (appMain.volumeBGM != x) {
                this.bgm[appMain.volumeBGM].fontSize = 30;
                this.bgm[x].fontSize = 80;
                appMain.setVolumeBGM(x);
            }
        }
        //ＳＥボリューム
        if ( SC_H*0.45 < sy && sy < SC_H*0.55 && SC_W*0.4-30 < sx && sx < SC_W*0.4-30+360) {
            var x = sx-(SC_W*0.4-30);
            x = ~~(x/60);
            if (appMain.volumeSE != x) {
                this.se[appMain.volumeSE].fontSize = 30;
                this.se[x].fontSize = 80;
                appMain.volumeSE = x;
                appMain.playSE("hand");
            }
        }
    },

    //タッチorクリック終了処理
    ontouchend: function(e) {
    },
});

<|endoftext|>