<commit_before>ig.module(
        'plusplus.core.image'
    ).requires(
        'impact.image',
        'plusplus.helpers.signals'
    )
    .defines(function () {
        'use strict';

        /**
         * Fixes and enhancements for images.
         * @class ig.Image
         * @memberof ig
         * @author Collin Hover - collinhover.com
         **/
        ig.Image.inject(/**@lends ig.Image.prototype */{

            /**
             * Width of unscaled drawing.
             * @type Number
             * @default
             */
            width: 1,

            /**
             * Height of unscaled drawing.
             * @type Number
             * @default
             */
            height: 1,

            /**
             * Current image data after resizing.
             *
- created on load and recreated on each resize
             * @type Canvas
             */
            data: null,

            /**
             * Context of Current image data after resizing.
             *
- created on load and recreated on each resize
             * @type CanvasRenderingContext2D
             */
            dataContext: null,

            /**
             * Width of current image.
             *
- this value is set calculated based on {@link ig.ImageDrawing#width} and {@link ig.ImageDrawing#scale}
             * @type Number
             */
            dataWidth: 1,

            /**
             * Height of current image.
             *
- this value is set calculated based on {@link ig.ImageDrawing#height} and {@link ig.ImageDrawing#scale}
             * @type Number
             */
            dataHeight: 1,

            /**
             * Scale of current image.
             *
- will automatically copy system scale unless is ignoring system scale due to {@link ig.ImageDrawing#ignoreSystemScale}
             * @type Number
             * @default
             */
            scale: 1,

            /**
             * If image should ignore system scale.
             * Tip: because pixel perfect scaling is so performance intensive, try to ignore scale whenever possible.
             * @type Boolean
             * @default
             */
            ignoreSystemScale: false,

            /**
             * Whether image should retain copies of data when scale is not 1.
             * Tip: this is best used when an image is used by multiple entities requesting drawing at different scales.
             * @type Boolean
             * @default
             */
            retainScaledData: true,

            /**
             * Copies of data at various scales.
             *
- created on init.
             * Tip: data is mapped by scale, and 1 always refers to the original image data.
             * @type Object
             * @default
             */
            scaleCache: null,

            /**
             * Signal dispatched when image finishes loading.
             *
- created on init.
             * @type ig.Signal
             */
            onLoaded: null,

            // internal properties, do not modify

            _scale: 0,

            /**
             * Initializes image and begins loading path.
             * @param {Object} settings settings object.
             * @override
             */
            init: function (path, settings) {

                ig.merge(this, settings);

                this.onLoaded = new ig.Signal();
                this.scaleCache = {};

                this.parent(path);

            },

            /**
             * @override
             */
            onload: function( event ) {

                this.width = this.data.width;
                this.height = this.data.height;
                this.loaded = true;

                // store original image data at 1 scale
                // resize is skipped to save on performance
                // as resize will be done based on draw call

                var original = ig.$new('canvas');
                original.width = this.width;
                original.height = this.height;
                original.getContext('2d').drawImage( this.data, 0, 0 );
                this.data = original;
                this.dataContext = this.data.getContext('2d');
                this.dataWidth = this.data.width;
                this.dataHeight = this.data.height;

				if ( !this.scaleCache ) {

					this.scaleCache = {};

				}

                this.scaleCache.x1 = this.data;

                if( this.loadCallback ) {
                    this.loadCallback( this.path, true );
                }

				if ( this.onLoaded ) {

					this.onLoaded.dispatch(this);
					this.onLoaded.removeAll();
					this.onLoaded.forget();

				}

            },

            /**
             * @override
             */
            resize: function ( scale ) {

                if ( scale && this._scale !== scale ) {

                    // store scale so we know when system was resized

                    this.scale = this._scale = scale;

                    // do we already have scaled data?

					if ( !this.scaleCache ) {

						this.scaleCache = {};

					}

                    this.data = this.scaleCache[ "x" + scale ];

                    if ( this.data ) {

                        this.dataContext = this.data.getContext('2d');
                        this.dataWidth = this.data.width;
                        this.dataHeight = this.data.height;

                    }
                    else {

                        // the original image is copied into another canvas with the new size.
                        // the scaled canvas becomes the image (data) of this object.

                        var origData = this.scaleCache.x1;

                        // can't find usable data

                        if ( !origData ) {

                            return;

                        }

                        var origPixels = ig.getImagePixels(origData, 0, 0, this.width, this.height);

                        var widthScaled = ( this.width * scale ) | 0;
                        var heightScaled = ( this.height * scale) | 0;

                        var scaled = this.data = ig.$new('canvas');
                        this.dataWidth = scaled.width = widthScaled;
                        this.dataHeight = scaled.height = heightScaled;
                        var scaledCtx = this.dataContext = this.data.getContext('2d');
                        var scaledPixels = scaledCtx.getImageData(0, 0, widthScaled, heightScaled);

                        for (var y = 0; y < heightScaled; y++) {
                            for (var x = 0; x < widthScaled; x++) {
                                var index = (Math.floor(y / scale) * this.width + Math.floor(x / scale)) * 4;
                                var indexScaled = (y * widthScaled + x) * 4;
                                scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
                                scaledPixels.data[ indexScaled + 1 ] = origPixels.data[ index + 1 ];
                                scaledPixels.data[ indexScaled + 2 ] = origPixels.data[ index + 2 ];
                                scaledPixels.data[ indexScaled + 3 ] = origPixels.data[ index + 3 ];
                            }
                        }
                        scaledCtx.putImageData(scaledPixels, 0, 0);

                        // store for reuse

                        if ( this.retainScaledData ) {

                            this.scaleCache[ "x" + scale ] = this.data;

                        }

                    }

                }

            },

            /**
             * Draws image into system context, accounting for system scale changes.
             * @param {Number} targetX
             * @param {Number} targetY
             * @param {Number} sourceX
             * @param {Number} sourceY
             * @param {Number} width
             * @param {Number} height
             * @param {Number} scale
             * @override
             **/
            draw: function( targetX, targetY, sourceX, sourceY, width, height, scale ) {

                if( !this.loaded ) { return; }

                if ( !scale ) {

                    if (!this.ignoreSystemScale && this._scale !== ig.system.scale) {

                        this.resize(ig.system.scale);

                    }
                    else if ( this._scale !== this.scale ) {

                        this.resize(this.scale);

                    }

                    scale = this._scale;

                }
                else {

                    this.resize( scale );

                }

                width = ( ( typeof width !== 'undefined' ? width : this.width) * scale ) | 0;
                height = ( ( typeof height !== 'undefined' ? height : this.height) * scale ) | 0;

                // no sense in drawing when 0 width or height

                if (width === 0 || height === 0) {

                    return;

                }

                sourceX = sourceX ? ( sourceX * scale ) | 0 : 0;
                sourceY = sourceY ? ( sourceY * scale ) | 0 : 0;

                ig.system.context.drawImage(
                    this.data, sourceX, sourceY, width, height,
                    ig.system.getDrawPos(targetX),
                    ig.system.getDrawPos(targetY),
                    width, height
                );

                ig.Image.drawCount++;

            },

            /**
             * Draws image tile into system.
             * @param {Number} targetX
             * @param {Number} targetY
             * @param {Number} tile
             * @param {Number} tileWidth
             * @param {Number} tileHeight
             * @param {Boolean} flipX
             * @param {Boolean} flipY
             * @param {Number} scale
             * @override
             */
            drawTile: function( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY, scale ) {

                tileHeight = tileHeight ? tileHeight : tileWidth;

                if( !this.loaded || tileWidth > this.width || tileHeight > this.height ) { return; }

                if ( !scale ) {

                    if (!this.ignoreSystemScale && this._scale !== ig.system.scale) {

                        this.resize(ig.system.scale);

                    }
                    else if ( this._scale !== this.scale ) {

                        this.resize(this.scale);

                    }

                    scale = this._scale;

                }
                else {

                    this.resize( scale );

                }

                var tileWidthScaled = Math.floor(tileWidth * scale);
                var tileHeightScaled = Math.floor(tileHeight * scale);

                var scaleX = flipX ? -1 : 1;
                var scaleY = flipY ? -1 : 1;

                if( flipX || flipY ) {

                    ig.system.context.save();
                    ig.system.context.scale( scaleX, scaleY );

                }

                ig.system.context.drawImage(
                    this.data,
                    ( Math.floor(tile * tileWidth) % this.width ) * scale,
                    ( Math.floor(tile * tileWidth / this.width) * tileHeight ) * scale,
                    tileWidthScaled,
                    tileHeightScaled,
                    ig.system.getDrawPos(targetX) * scaleX - (flipX ? tileWidthScaled : 0),
                    ig.system.getDrawPos(targetY) * scaleY - (flipY ? tileHeightScaled : 0),
                    tileWidthScaled,
                    tileHeightScaled
                );

                if( flipX || flipY ) {

                    ig.system.context.restore();

                }

                ig.Image.drawCount++;

            }

        });

        /*
         * overrides and fixes for when in editor
         */
        if (ig.global.wm) {

            ig.Image.inject({

                resize: function ( scale ) {

                    if ( scale && this._scale !== scale ) {

                        var origData = this.scaleCache.x1;

                        if ( !origData ) {

                            this.scaleCache.x1 = this.data;

                        }

                        this.parent( scale );

                    }

                },

                draw: function( targetX, targetY, sourceX, sourceY, width, height, scale ) {

                    if ( !scale ) {

                        scale = ig.system.scale;

                    }

                    this.parent( targetX, targetY, sourceX, sourceY, width, height, scale );

                },

                drawTile: function( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY, scale ) {

                    if ( !scale ) {

                        scale = ig.system.scale;

                    }

                    this.parent( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY, scale );

                }

            });

        }

    });<commit_msg>minor perf improvement on image draw<commit_after>ig.module(
        'plusplus.core.image'
    ).requires(
        'impact.image',
        'plusplus.helpers.signals'
    )
    .defines(function () {
        'use strict';

        /**
         * Fixes and enhancements for images.
         * @class ig.Image
         * @memberof ig
         * @author Collin Hover - collinhover.com
         **/
        ig.Image.inject(/**@lends ig.Image.prototype */{

            /**
             * Width of unscaled drawing.
             * @type Number
             * @default
             */
            width: 1,

            /**
             * Height of unscaled drawing.
             * @type Number
             * @default
             */
            height: 1,

            /**
             * Current image data after resizing.
             *
- created on load and recreated on each resize
             * @type Canvas
             */
            data: null,

            /**
             * Context of Current image data after resizing.
             *
- created on load and recreated on each resize
             * @type CanvasRenderingContext2D
             */
            dataContext: null,

            /**
             * Width of current image.
             *
- this value is set calculated based on {@link ig.ImageDrawing#width} and {@link ig.ImageDrawing#scale}
             * @type Number
             */
            dataWidth: 1,

            /**
             * Height of current image.
             *
- this value is set calculated based on {@link ig.ImageDrawing#height} and {@link ig.ImageDrawing#scale}
             * @type Number
             */
            dataHeight: 1,

            /**
             * Scale of current image.
             *
- will automatically copy system scale unless is ignoring system scale due to {@link ig.ImageDrawing#ignoreSystemScale}
             * @type Number
             * @default
             */
            scale: 1,

            /**
             * If image should ignore system scale.
             * Tip: because pixel perfect scaling is so performance intensive, try to ignore scale whenever possible.
             * @type Boolean
             * @default
             */
            ignoreSystemScale: false,

            /**
             * Whether image should retain copies of data when scale is not 1.
             * Tip: this is best used when an image is used by multiple entities requesting drawing at different scales.
             * @type Boolean
             * @default
             */
            retainScaledData: true,

            /**
             * Copies of data at various scales.
             *
- created on init.
             * Tip: data is mapped by scale, and 1 always refers to the original image data.
             * @type Object
             * @default
             */
            scaleCache: null,

            /**
             * Signal dispatched when image finishes loading.
             *
- created on init.
             * @type ig.Signal
             */
            onLoaded: null,

            // internal properties, do not modify

            _scale: 0,

            /**
             * Initializes image and begins loading path.
             * @param {Object} settings settings object.
             * @override
             */
            init: function (path, settings) {

                ig.merge(this, settings);

                this.onLoaded = new ig.Signal();
                this.scaleCache = {};

                this.parent(path);

            },

            /**
             * @override
             */
            onload: function( event ) {

                this.width = this.data.width;
                this.height = this.data.height;
                this.loaded = true;

                // store original image data at 1 scale
                // resize is skipped to save on performance
                // as resize will be done based on draw call

                var original = ig.$new('canvas');
                original.width = this.width;
                original.height = this.height;
                original.getContext('2d').drawImage( this.data, 0, 0 );
                this.data = original;
                this.dataContext = this.data.getContext('2d');
                this.dataWidth = this.data.width;
                this.dataHeight = this.data.height;

				if ( !this.scaleCache ) {

					this.scaleCache = {};

				}

                this.scaleCache.x1 = this.data;

                if( this.loadCallback ) {
                    this.loadCallback( this.path, true );
                }

				if ( this.onLoaded ) {

					this.onLoaded.dispatch(this);
					this.onLoaded.removeAll();
					this.onLoaded.forget();

				}

            },

            /**
             * @override
             */
            resize: function ( scale ) {

                if ( scale && this._scale !== scale ) {

                    // store scale so we know when system was resized

                    this.scale = this._scale = scale;

                    // do we already have scaled data?

					if ( !this.scaleCache ) {

						this.scaleCache = {};

					}

                    this.data = this.scaleCache[ "x" + scale ];

                    if ( this.data ) {

                        this.dataContext = this.data.getContext('2d');
                        this.dataWidth = this.data.width;
                        this.dataHeight = this.data.height;

                    }
                    else {

                        // the original image is copied into another canvas with the new size.
                        // the scaled canvas becomes the image (data) of this object.

                        var origData = this.scaleCache.x1;

                        // can't find usable data

                        if ( !origData ) {

                            return;

                        }

                        var origPixels = ig.getImagePixels(origData, 0, 0, this.width, this.height);

                        var widthScaled = ( this.width * scale ) | 0;
                        var heightScaled = ( this.height * scale) | 0;

                        var scaled = this.data = ig.$new('canvas');
                        this.dataWidth = scaled.width = widthScaled;
                        this.dataHeight = scaled.height = heightScaled;
                        var scaledCtx = this.dataContext = this.data.getContext('2d');
                        var scaledPixels = scaledCtx.getImageData(0, 0, widthScaled, heightScaled);

                        for (var y = 0; y < heightScaled; y++) {
                            for (var x = 0; x < widthScaled; x++) {
                                var index = (Math.floor(y / scale) * this.width + Math.floor(x / scale)) * 4;
                                var indexScaled = (y * widthScaled + x) * 4;
                                scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
                                scaledPixels.data[ indexScaled + 1 ] = origPixels.data[ index + 1 ];
                                scaledPixels.data[ indexScaled + 2 ] = origPixels.data[ index + 2 ];
                                scaledPixels.data[ indexScaled + 3 ] = origPixels.data[ index + 3 ];
                            }
                        }
                        scaledCtx.putImageData(scaledPixels, 0, 0);

                        // store for reuse

                        if ( this.retainScaledData ) {

                            this.scaleCache[ "x" + scale ] = this.data;

                        }

                    }

                }

            },

            /**
             * Draws image into system context, accounting for system scale changes.
             * @param {Number} targetX
             * @param {Number} targetY
             * @param {Number} sourceX
             * @param {Number} sourceY
             * @param {Number} width
             * @param {Number} height
             * @param {Number} scale
             * @override
             **/
            draw: function( targetX, targetY, sourceX, sourceY, width, height, scale ) {

                if( !this.loaded ) { return; }

                if ( !scale ) {

                    if (!this.ignoreSystemScale && this._scale !== ig.system.scale) {

                        this.resize(ig.system.scale);

                    }
                    else if ( this._scale !== this.scale ) {

                        this.resize(this.scale);

                    }

                    scale = this._scale;

                }
                else {

                    this.resize( scale );

                }

                width = ( ( typeof width !== 'undefined' ? width : this.width) * scale ) | 0;
                height = ( ( typeof height !== 'undefined' ? height : this.height) * scale ) | 0;

                // no sense in drawing when 0 width or height

                if (width === 0 || height === 0) {

                    return;

                }

                sourceX = sourceX ? ( sourceX * scale ) | 0 : 0;
                sourceY = sourceY ? ( sourceY * scale ) | 0 : 0;

                ig.system.context.drawImage(
                    this.data, sourceX, sourceY, width, height,
                    ig.system.getDrawPos(targetX),
                    ig.system.getDrawPos(targetY),
                    width, height
                );

                ig.Image.drawCount++;

            },

            /**
             * Draws image tile into system.
             * @param {Number} targetX
             * @param {Number} targetY
             * @param {Number} tile
             * @param {Number} tileWidth
             * @param {Number} tileHeight
             * @param {Boolean} flipX
             * @param {Boolean} flipY
             * @param {Number} scale
             * @override
             */
            drawTile: function( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY, scale ) {

                tileHeight = tileHeight ? tileHeight : tileWidth;

                if( !this.loaded || tileWidth > this.width || tileHeight > this.height ) { return; }

                if ( !scale ) {

                    if (!this.ignoreSystemScale && this._scale !== ig.system.scale) {

                        this.resize(ig.system.scale);

                    }
                    else if ( this._scale !== this.scale ) {

                        this.resize(this.scale);

                    }

                    scale = this._scale;

                }
                else {

                    this.resize( scale );

                }

                var tileWidthScaled = Math.floor(tileWidth * scale);
                var tileHeightScaled = Math.floor(tileHeight * scale);
                var dirX;
                var dirY;
                var offsetX;
                var offsetY;

                if ( flipX ) {

                    dirX = -1;
                    offsetX = tileWidthScaled;

                }
                else {

                    dirX = 1;
                    offsetX = 0;

                }

                if ( flipY ) {

                    dirY = -1;
                    offsetY = tileHeightScaled;

                }
                else {

                    dirY = 1;
                    offsetY = 0;

                }

                if( flipX || flipY ) {

                    ig.system.context.save();
                    ig.system.context.scale( dirX, dirY );

                    ig.system.context.drawImage(
                        this.data,
                        ( Math.floor(tile * tileWidth) % this.width ) * scale,
                        ( Math.floor(tile * tileWidth / this.width) * tileHeight ) * scale,
                        tileWidthScaled,
                        tileHeightScaled,
                        ig.system.getDrawPos(targetX) * dirX - offsetX,
                        ig.system.getDrawPos(targetY) * dirY - offsetY,
                        tileWidthScaled,
                        tileHeightScaled
                    );

                    ig.system.context.restore();

                }
                else {

                    ig.system.context.drawImage(
                        this.data,
                        ( Math.floor(tile * tileWidth) % this.width ) * scale,
                        ( Math.floor(tile * tileWidth / this.width) * tileHeight ) * scale,
                        tileWidthScaled,
                        tileHeightScaled,
                        ig.system.getDrawPos(targetX) * dirX - offsetX,
                        ig.system.getDrawPos(targetY) * dirY - offsetY,
                        tileWidthScaled,
                        tileHeightScaled
                    );

                }

                ig.Image.drawCount++;

            }

        });

        /*
         * overrides and fixes for when in editor
         */
        if (ig.global.wm) {

            ig.Image.inject({

                resize: function ( scale ) {

                    if ( scale && this._scale !== scale ) {

                        var origData = this.scaleCache.x1;

                        if ( !origData ) {

                            this.scaleCache.x1 = this.data;

                        }

                        this.parent( scale );

                    }

                },

                draw: function( targetX, targetY, sourceX, sourceY, width, height, scale ) {

                    if ( !scale ) {

                        scale = ig.system.scale;

                    }

                    this.parent( targetX, targetY, sourceX, sourceY, width, height, scale );

                },

                drawTile: function( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY, scale ) {

                    if ( !scale ) {

                        scale = ig.system.scale;

                    }

                    this.parent( targetX, targetY, tile, tileWidth, tileHeight, flipX, flipY, scale );

                }

            });

        }

    });<|endoftext|>


<commit_before>// @cliDescription  Add a new thingy
// ----------------------------------------------------------------------------
const Shell = require('shelljs')
const Exists = require('npm-exists')
const Toml = require('toml')
// Yeah, why would toml include this? :(
const json2toml = require('json2toml')
const R = require('ramda')

// use yarn or use npm? hardcode for now
const useYarn = false

// Optional - could be used for changes warnings
const detectedChanges = (oldObject, newObject) => {
  let oldKeys = R.keys(oldObject)
  let newKeys = R.keys(newObject)
  const inter = R.intersection(oldKeys, newKeys)
  return R.reduce((acc, k) => {
    if (oldObject[k] !== newObject[k]) {
      return R.concat([`'${k}'`], acc)
    }
    return acc
  }, [], inter)
}

const noMegusta = (moduleName) => {
  console.warn('Rolling back...')

  if (useYarn) {
    Shell.exec(`yarn remove ${moduleName}`, {silent: true})
  } else {
    Shell.exec(`npm rm ${moduleName}`, {silent: true})
  }
  process.exit(1)
}

module.exports = async function (context) {
    // grab a fist-full of features...
  const { system, print, filesystem, strings, parameters, prompt } = context
  const { trim, kebabCase } = strings
  const { info, warning, success, debug, checkmark, error } = print

  // /////////////////////////////////////////////////////////////////////////
  // ...and be the CLI you wish to see in the world
  // debug(context)
  // debug(parameters, 'Captured parameters')

  // take the last parameter (because of https://github.com/infinitered/gluegun/issues/123)
  // prepend `ignite` as convention
  const moduleName = `ignite-${parameters.array.pop()}`
  info(`🔎    Finding Plugin`)
  const moduleExists = await Exists(moduleName)
  // it exists?  Let's install it else warn
  if (moduleExists) {
    success(`${checkmark}    Installing`)

    if (useYarn) {
      Shell.exec(`yarn add ${moduleName} --dev`, {silent: true})
    } else {
      Shell.exec(`npm i ${moduleName} --save-dev`, {silent: true})
    }

    // once installed, let's check on its toml
    const tomlFilePath = `${process.cwd()}/node_modules/${moduleName}/ignite.toml`
    if (!filesystem.exists(tomlFilePath)) {
      error('No `ignite.toml` file found in this node module, are you sure it is an Ignite plugin?')
      noMegusta(moduleName)
    }
    const newConfig = Toml.parse(filesystem.read(tomlFilePath))

    // debug(newConfig, 'Toml Config from Module')
    const proposedGenerators = R.reduce((acc, k) => {
      acc[k] = moduleName
      return acc
    }, {}, newConfig.ignite.generators)

    // we compare the toml changes against ours
    const changes = detectedChanges(context.config.ignite.generators, proposedGenerators)
    if (changes.length > 0) {
      // we warn the user on changes
      warning(`The following generators would be changed: ${R.join(', ', changes)}`)
      const ok = await context.prompt.confirm('You ok with that?')
      // if they refuse, then npm/yarn uninstall
      if (!ok) noMegusta(moduleName)
    }

    const combinedGenerators = Object.assign({}, context.config.ignite.generators, proposedGenerators)
    const updatedConfig = R.assocPath(['ignite', 'generators'], combinedGenerators, context.config)
    // local toml file

    // We write the toml changes
    const localToml = `${process.cwd()}/ignite.toml`
    filesystem.write(localToml, json2toml(updatedConfig))

    // and then call the add function
    const pluginModule = require(`${process.cwd()}/node_modules/${moduleName}`)
    await pluginModule.add(context)

    // get cooking message!
    success('Time to get cooking! 🍽 ')
  } else {
    error("We couldn't find that ignite plugin")
    warning(`Please make sure ${moduleName} exists on the NPM registry`)
    process.exit(1)
  }

}
<commit_msg>clean up requires<commit_after>// @cliDescription  Add a new thingy
// ----------------------------------------------------------------------------
const Shell = require('shelljs')
const Exists = require('npm-exists')
const Toml = require('toml')
// Yeah, why would toml include this? :(
const json2toml = require('json2toml')
const R = require('ramda')

// use yarn or use npm? hardcode for now
const useYarn = false

// Optional - could be used for changes warnings
const detectedChanges = (oldObject, newObject) => {
  let oldKeys = R.keys(oldObject)
  let newKeys = R.keys(newObject)
  const inter = R.intersection(oldKeys, newKeys)
  return R.reduce((acc, k) => {
    if (oldObject[k] !== newObject[k]) {
      return R.concat([`'${k}'`], acc)
    }
    return acc
  }, [], inter)
}

const noMegusta = (moduleName) => {
  console.warn('Rolling back...')

  if (useYarn) {
    Shell.exec(`yarn remove ${moduleName}`, {silent: true})
  } else {
    Shell.exec(`npm rm ${moduleName}`, {silent: true})
  }
  process.exit(1)
}

module.exports = async function (context) {
    // grab a fist-full of features...
  const { print, filesystem, parameters, prompt } = context
  const { info, warning, success, checkmark, error } = print

  // /////////////////////////////////////////////////////////////////////////
  // ...and be the CLI you wish to see in the world
  // debug(context)
  // debug(parameters, 'Captured parameters')

  // take the last parameter (because of https://github.com/infinitered/gluegun/issues/123)
  // prepend `ignite` as convention
  const moduleName = `ignite-${parameters.array.pop()}`
  info(`🔎    Finding Plugin`)
  const moduleExists = await Exists(moduleName)
  // it exists?  Let's install it else warn
  if (moduleExists) {
    success(`${checkmark}    Installing`)

    if (useYarn) {
      Shell.exec(`yarn add ${moduleName} --dev`, {silent: true})
    } else {
      Shell.exec(`npm i ${moduleName} --save-dev`, {silent: true})
    }

    // once installed, let's check on its toml
    const tomlFilePath = `${process.cwd()}/node_modules/${moduleName}/ignite.toml`
    if (!filesystem.exists(tomlFilePath)) {
      error('No `ignite.toml` file found in this node module, are you sure it is an Ignite plugin?')
      noMegusta(moduleName)
    }
    const newConfig = Toml.parse(filesystem.read(tomlFilePath))

    // debug(newConfig, 'Toml Config from Module')
    const proposedGenerators = R.reduce((acc, k) => {
      acc[k] = moduleName
      return acc
    }, {}, newConfig.ignite.generators)

    // we compare the toml changes against ours
    const changes = detectedChanges(context.config.ignite.generators, proposedGenerators)
    if (changes.length > 0) {
      // we warn the user on changes
      warning(`The following generators would be changed: ${R.join(', ', changes)}`)
      const ok = await prompt.confirm('You ok with that?')
      // if they refuse, then npm/yarn uninstall
      if (!ok) noMegusta(moduleName)
    }

    const combinedGenerators = Object.assign({}, context.config.ignite.generators, proposedGenerators)
    const updatedConfig = R.assocPath(['ignite', 'generators'], combinedGenerators, context.config)
    // local toml file

    // We write the toml changes
    const localToml = `${process.cwd()}/ignite.toml`
    filesystem.write(localToml, json2toml(updatedConfig))

    // and then call the add function
    const pluginModule = require(`${process.cwd()}/node_modules/${moduleName}`)
    await pluginModule.add(context)

    // get cooking message!
    success('Time to get cooking! 🍽 ')
  } else {
    error("We couldn't find that ignite plugin")
    warning(`Please make sure ${moduleName} exists on the NPM registry`)
    process.exit(1)
  }

}
<|endoftext|>


<commit_before>const request = require('request');
const qs = require('querystring');
const _ = require('underscore');
const crypto = require('crypto');
const Beautifier = require('./beautifier.js');
const assert = require('assert');

class BinanceRest {

    constructor({
        key,
        secret,
        recvWindow = false,
        timeout = 15000,
        disableBeautification = false,
        handleDrift = false
    }) {
        this.key = key;
        this.secret = secret;
        this.recvWindow = recvWindow;
        this.timeout = timeout;
        this.disableBeautification = disableBeautification;
        this.handleDrift = handleDrift;

        this._beautifier = new Beautifier();
        this._baseUrl = 'https://api.binance.com/';
        this._drift = 0;
        this._syncInterval = 0;
    }

    _makeRequest(query, callback, route, security, method, attempt = 0) {
        assert(_.isUndefined(callback) || _.isFunction(callback), 'callback must be a function or undefined');
        assert(_.isObject(query), 'query must be an object');

        let queryString;
        const type = _.last(route.split('/')),
            options = {
                url: `${this._baseUrl}${route}`,
                timeout: this.timeout
            };

        if (security === 'SIGNED') {
            if (this.recvWindow) {
                query.recvWindow = this.recvWindow;
            }
            queryString = qs.stringify(query);
            options.url += '?' + queryString;
            if (options.url.substr(options.url.length - 1) !== '?') {
                options.url += '&';
            }
            options.url += `signature=${this._sign(queryString)}`;
        } else {
            queryString = qs.stringify(query);
            if (queryString) {
                options.url += '?' + queryString;
            }
        }
        if (security === 'API-KEY' || security === 'SIGNED') {
            options.headers = { 'X-MBX-APIKEY': this.key };
        }
        if (method) {
            options.method = method;
        }

        const action = cb => {
            request(options, (err, response, body) => {
                let payload;
                try {
                    payload = JSON.parse(body);
                } catch (e) {
                    payload = body;
                }
                if (err) {
                    cb(err, payload);
                } else if (response.statusCode < 200 || response.statusCode > 299) {
                    /*
                     * If we get a response that the timestamp is ahead of the server,
                     * calculate the drift and then attempt the request again
                     */
                    if (response.statusCode === 400 && payload.code === -1021 &&
                        this.handleDrift && attempt === 0) {

                        this.calculateDrift()
                            .then(() => {
                                query.timestamp = this._getTime() + this._drift;
                                return this._makeRequest(query, cb, route, security, method,
                                    ++attempt);
                            });
                    } else {
                        cb(new Error(`Response code ${response.statusCode}`), payload);
                    }
                } else {
                    if (_.isArray(payload)) {
                        payload = _.map(payload, (item) => {
                            return this._doBeautifications(item, type);
                        });
                    } else {
                        payload = this._doBeautifications(payload);
                    }
                    cb(err, payload);
                }
            });
        };
        if (callback) {
            action(callback);
        } else {
            return new Promise((resolve, reject) => action((err, payload) => {
                if (err) {
                    if (payload === undefined) {
                        reject(err);
                    } else {
                        reject(payload);
                    }
                } else {
                    resolve(payload);
                }
            }));
        }
    }

    _doBeautifications(response, route) {
        if (this.disableBeautification) {
            return response;
        }
        return this._beautifier.beautify(response, route);
    }

    _sign(queryString) {
        return crypto.createHmac('sha256', this.secret)
            .update(queryString)
            .digest('hex');
    }

    _setTimestamp(query) {
        if (!query.timestamp) {
            query.timestamp = this._getTime() + this._drift;
        }
    }

    _getTime() {
        return new Date().getTime();
    }

    calculateDrift() {
        const systemTime = this._getTime();
        return this.time()
            .then((response) => {
                // Calculate the approximate trip time from here to binance
                const transitTime = parseInt((this._getTime() - systemTime) / 2);
                this._drift = response.serverTime - (systemTime + transitTime);
            });
    }

    startTimeSync(interval = 300000, onRecalculateDriftCb) {
        return new Promise((resolve, reject) => {
            // If there's already an interval running, clear it and reset values
            if (this._syncInterval !== 0) {
                this.endTimeSync();
                return resolve();
            }

            // Calculate initial drift value and setup interval to periodically update it
            this.calculateDrift()
                .then(resolve)
                .catch(reject);

            this._syncInterval = setInterval(() => {
                const promise = this.calculateDrift();

                if (_.isFunction(onRecalculateDriftCb)) {
                    onRecalculateDriftCb(promise);
                }
            }, interval);
        });
    }

    endTimeSync() {
        clearInterval(this._syncInterval);
        this._drift = 0;
        this._syncInterval = 0;
    }

    // Public APIs
    ping(callback) {
        return this._makeRequest({}, callback, 'api/v1/ping');
    }

    time(callback) {
        return this._makeRequest({}, callback, 'api/v1/time');
    }

    depth(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/depth');
    }

    trades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/trades');
    }

    historicalTrades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/historicalTrades', 'API-KEY');
    }

    aggTrades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/aggTrades');
    }

    exchangeInfo(callback) {
        return this._makeRequest({}, callback, 'api/v1/exchangeInfo');
    }

    klines(query = {}, callback) {
        return this._makeRequest(query, callback, 'api/v1/klines');
    }

    ticker24hr(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/ticker/24hr');
    }

    tickerPrice(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v3/ticker/price');
    }

    bookTicker(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v3/ticker/bookTicker');
    }

    allBookTickers(callback) {
        return this._makeRequest({}, callback, 'api/v1/ticker/allBookTickers');
    }

    allPrices(callback) {
        return this._makeRequest({}, callback, 'api/v1/ticker/allPrices');
    }

    // Private APIs
    newOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'POST');
    }

    testOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order/test', 'SIGNED', 'POST');
    }

    queryOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED');
    }

    cancelOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'DELETE');
    }

    openOrders(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/openOrders', 'SIGNED');
    }

    allOrders(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/allOrders', 'SIGNED');
    }

    account(query = {}, callback) {
        if (_.isFunction(query)) {
            callback = query;
            query = {};
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/account', 'SIGNED');
    }

    myTrades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/myTrades', 'SIGNED');
    }

    withdraw(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/withdraw.html', 'SIGNED', 'POST');
    }

    depositHistory(query = {}, callback) {
        if (_.isString(query)) {
            query = { asset: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/depositHistory.html', 'SIGNED');
    }

    withdrawHistory(query = {}, callback) {
        if (_.isString(query)) {
            query = { asset: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/withdrawHistory.html', 'SIGNED');
    }

    depositAddress(query = {}, callback) {
        if (_.isString(query)) {
            query = { asset: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/depositAddress.html', 'SIGNED');
    }

    accountStatus(callback) {
        const query = {};
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/accountStatus.html', 'SIGNED');
    }

    startUserDataStream(callback) {
        return this._makeRequest({}, callback, 'api/v1/userDataStream', 'API-KEY', 'POST');
    }

    keepAliveUserDataStream(query = {}, callback) {
        return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'PUT');
    }

    closeUserDataStream(query = {}, callback) {
        return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'DELETE');
    }
}

module.exports = BinanceRest;
<commit_msg>Make it possible to specify request options<commit_after>const request = require('request');
const qs = require('querystring');
const _ = require('underscore');
const crypto = require('crypto');
const Beautifier = require('./beautifier.js');
const assert = require('assert');

class BinanceRest {

    constructor({
        key,
        secret,
        recvWindow = false,
        timeout = 15000,
        disableBeautification = false,
        handleDrift = false,
        requestOptions = {}
    }) {
        this.key = key;
        this.secret = secret;
        this.recvWindow = recvWindow;
        this.timeout = timeout;
        this.disableBeautification = disableBeautification;
        this.handleDrift = handleDrift;
        this.requestOptions = requestOptions;

        this._beautifier = new Beautifier();
        this._baseUrl = 'https://api.binance.com/';
        this._drift = 0;
        this._syncInterval = 0;
    }

    _makeRequest(query, callback, route, security, method, attempt = 0) {
        assert(_.isUndefined(callback) || _.isFunction(callback), 'callback must be a function or undefined');
        assert(_.isObject(query), 'query must be an object');

        let queryString;
        const type = _.last(route.split('/')),
            options = Object.assign({
                url: `${this._baseUrl}${route}`,
                timeout: this.timeout
            }, this.requestOptions);

        if (security === 'SIGNED') {
            if (this.recvWindow) {
                query.recvWindow = this.recvWindow;
            }
            queryString = qs.stringify(query);
            options.url += '?' + queryString;
            if (options.url.substr(options.url.length - 1) !== '?') {
                options.url += '&';
            }
            options.url += `signature=${this._sign(queryString)}`;
        } else {
            queryString = qs.stringify(query);
            if (queryString) {
                options.url += '?' + queryString;
            }
        }
        if (security === 'API-KEY' || security === 'SIGNED') {
            options.headers = { 'X-MBX-APIKEY': this.key };
        }
        if (method) {
            options.method = method;
        }

        const action = cb => {
            request(options, (err, response, body) => {
                let payload;
                try {
                    payload = JSON.parse(body);
                } catch (e) {
                    payload = body;
                }
                if (err) {
                    cb(err, payload);
                } else if (response.statusCode < 200 || response.statusCode > 299) {
                    /*
                     * If we get a response that the timestamp is ahead of the server,
                     * calculate the drift and then attempt the request again
                     */
                    if (response.statusCode === 400 && payload.code === -1021 &&
                        this.handleDrift && attempt === 0) {

                        this.calculateDrift()
                            .then(() => {
                                query.timestamp = this._getTime() + this._drift;
                                return this._makeRequest(query, cb, route, security, method,
                                    ++attempt);
                            });
                    } else {
                        cb(new Error(`Response code ${response.statusCode}`), payload);
                    }
                } else {
                    if (_.isArray(payload)) {
                        payload = _.map(payload, (item) => {
                            return this._doBeautifications(item, type);
                        });
                    } else {
                        payload = this._doBeautifications(payload);
                    }
                    cb(err, payload);
                }
            });
        };
        if (callback) {
            action(callback);
        } else {
            return new Promise((resolve, reject) => action((err, payload) => {
                if (err) {
                    if (payload === undefined) {
                        reject(err);
                    } else {
                        reject(payload);
                    }
                } else {
                    resolve(payload);
                }
            }));
        }
    }

    _doBeautifications(response, route) {
        if (this.disableBeautification) {
            return response;
        }
        return this._beautifier.beautify(response, route);
    }

    _sign(queryString) {
        return crypto.createHmac('sha256', this.secret)
            .update(queryString)
            .digest('hex');
    }

    _setTimestamp(query) {
        if (!query.timestamp) {
            query.timestamp = this._getTime() + this._drift;
        }
    }

    _getTime() {
        return new Date().getTime();
    }

    calculateDrift() {
        const systemTime = this._getTime();
        return this.time()
            .then((response) => {
                // Calculate the approximate trip time from here to binance
                const transitTime = parseInt((this._getTime() - systemTime) / 2);
                this._drift = response.serverTime - (systemTime + transitTime);
            });
    }

    startTimeSync(interval = 300000, onRecalculateDriftCb) {
        return new Promise((resolve, reject) => {
            // If there's already an interval running, clear it and reset values
            if (this._syncInterval !== 0) {
                this.endTimeSync();
                return resolve();
            }

            // Calculate initial drift value and setup interval to periodically update it
            this.calculateDrift()
                .then(resolve)
                .catch(reject);

            this._syncInterval = setInterval(() => {
                const promise = this.calculateDrift();

                if (_.isFunction(onRecalculateDriftCb)) {
                    onRecalculateDriftCb(promise);
                }
            }, interval);
        });
    }

    endTimeSync() {
        clearInterval(this._syncInterval);
        this._drift = 0;
        this._syncInterval = 0;
    }

    // Public APIs
    ping(callback) {
        return this._makeRequest({}, callback, 'api/v1/ping');
    }

    time(callback) {
        return this._makeRequest({}, callback, 'api/v1/time');
    }

    depth(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/depth');
    }

    trades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/trades');
    }

    historicalTrades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/historicalTrades', 'API-KEY');
    }

    aggTrades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/aggTrades');
    }

    exchangeInfo(callback) {
        return this._makeRequest({}, callback, 'api/v1/exchangeInfo');
    }

    klines(query = {}, callback) {
        return this._makeRequest(query, callback, 'api/v1/klines');
    }

    ticker24hr(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v1/ticker/24hr');
    }

    tickerPrice(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v3/ticker/price');
    }

    bookTicker(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }

        return this._makeRequest(query, callback, 'api/v3/ticker/bookTicker');
    }

    allBookTickers(callback) {
        return this._makeRequest({}, callback, 'api/v1/ticker/allBookTickers');
    }

    allPrices(callback) {
        return this._makeRequest({}, callback, 'api/v1/ticker/allPrices');
    }

    // Private APIs
    newOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'POST');
    }

    testOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order/test', 'SIGNED', 'POST');
    }

    queryOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED');
    }

    cancelOrder(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/order', 'SIGNED', 'DELETE');
    }

    openOrders(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/openOrders', 'SIGNED');
    }

    allOrders(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/allOrders', 'SIGNED');
    }

    account(query = {}, callback) {
        if (_.isFunction(query)) {
            callback = query;
            query = {};
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/account', 'SIGNED');
    }

    myTrades(query = {}, callback) {
        if (_.isString(query)) {
            query = { symbol: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'api/v3/myTrades', 'SIGNED');
    }

    withdraw(query = {}, callback) {
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/withdraw.html', 'SIGNED', 'POST');
    }

    depositHistory(query = {}, callback) {
        if (_.isString(query)) {
            query = { asset: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/depositHistory.html', 'SIGNED');
    }

    withdrawHistory(query = {}, callback) {
        if (_.isString(query)) {
            query = { asset: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/withdrawHistory.html', 'SIGNED');
    }

    depositAddress(query = {}, callback) {
        if (_.isString(query)) {
            query = { asset: query };
        }
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/depositAddress.html', 'SIGNED');
    }

    accountStatus(callback) {
        const query = {};
        this._setTimestamp(query);
        return this._makeRequest(query, callback, 'wapi/v3/accountStatus.html', 'SIGNED');
    }

    startUserDataStream(callback) {
        return this._makeRequest({}, callback, 'api/v1/userDataStream', 'API-KEY', 'POST');
    }

    keepAliveUserDataStream(query = {}, callback) {
        return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'PUT');
    }

    closeUserDataStream(query = {}, callback) {
        return this._makeRequest(query, callback, 'api/v1/userDataStream', 'API-KEY', 'DELETE');
    }
}

module.exports = BinanceRest;
<|endoftext|>


<commit_before>/**
 * SurveyQuestionSingleSelectChoice component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Radio from '../Radio';
import { TextField, Input } from '../../material-components';

const MAXIMUM_CHARACTER_LIMIT = 100;

const SurveyQuestionSingleSelectChoice = ( {
	value,
	setValue,
	writeIn,
	setWriteIn,
	choice,
} ) => {
	const { answer_ordinal, text, write_in } = choice; // eslint-disable-line camelcase
	const isChecked = value === answer_ordinal; // eslint-disable-line camelcase
	return (

			<Radio
				id={ text }
				value={ answer_ordinal } // eslint-disable-line camelcase
				checked={ isChecked }
				name={ text }
				onClick={ () => setValue( answer_ordinal ) }
			>
				{ text }
			</Radio>
			{ write_in && ( // eslint-disable-line camelcase
				<TextField>
					<Input
						onChange={ ( event ) =>
							setWriteIn(
								event.target.value?.slice(
									0,
									MAXIMUM_CHARACTER_LIMIT
								)
							)
						}
						value={ writeIn }
						disabled={ ! isChecked }
						aria-label={ `Text input for option ${ text }` }
					/>
				</TextField>
			) }

	);
};

SurveyQuestionSingleSelectChoice.propTypes = {
	choice: PropTypes.shape( {
		answer_ordinal: PropTypes.oneOfType( [
			PropTypes.string,
			PropTypes.number,
		] ),
		text: PropTypes.string,
		write_in: PropTypes.bool,
	} ),
	value: PropTypes.string.isRequired,
	setValue: PropTypes.func.isRequired,
	writeIn: PropTypes.string.isRequired,
	setWriteIn: PropTypes.func.isRequired,
};

export default SurveyQuestionSingleSelectChoice;
<commit_msg>Remove optional chaining operator.<commit_after>/**
 * SurveyQuestionSingleSelectChoice component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Radio from '../Radio';
import { TextField, Input } from '../../material-components';

const MAXIMUM_CHARACTER_LIMIT = 100;

const SurveyQuestionSingleSelectChoice = ( {
	value,
	setValue,
	writeIn,
	setWriteIn,
	choice,
} ) => {
	const { answer_ordinal, text, write_in } = choice; // eslint-disable-line camelcase
	const isChecked = value === answer_ordinal; // eslint-disable-line camelcase
	return (

			<Radio
				id={ text }
				value={ answer_ordinal } // eslint-disable-line camelcase
				checked={ isChecked }
				name={ text }
				onClick={ () => setValue( answer_ordinal ) }
			>
				{ text }
			</Radio>
			{ write_in && ( // eslint-disable-line camelcase
				<TextField>
					<Input
						onChange={ ( event ) =>
							setWriteIn(
								event.target.value.slice(
									0,
									MAXIMUM_CHARACTER_LIMIT
								)
							)
						}
						value={ writeIn }
						disabled={ ! isChecked }
						aria-label={ `Text input for option ${ text }` }
					/>
				</TextField>
			) }

	);
};

SurveyQuestionSingleSelectChoice.propTypes = {
	choice: PropTypes.shape( {
		answer_ordinal: PropTypes.oneOfType( [
			PropTypes.string,
			PropTypes.number,
		] ),
		text: PropTypes.string,
		write_in: PropTypes.bool,
	} ),
	value: PropTypes.string.isRequired,
	setValue: PropTypes.func.isRequired,
	writeIn: PropTypes.string.isRequired,
	setWriteIn: PropTypes.func.isRequired,
};

export default SurveyQuestionSingleSelectChoice;
<|endoftext|>


<commit_before>define(function(require) {
    "use strict";
    var analytics = require("analytics");

    QUnit.module("Google Analytics");
    QUnit.test("test without analytics", function (assert) {
        var result=[];
        window.ga = function(cmd, id, auto) {
            result[0] = [cmd, id, auto];
        };
        window.apidata.analytics = undefined;
        var ga = analytics.init();
        assert.equal(result.length, 0);
    });

    QUnit.test("test with analytics", function (assert) {
        var result=[];
        window.ga = function(cmd, id, auto) {
            result[0] = [cmd, id, auto];
        };
        window.apidata.analytics = {"tracking_id": "X"};
        var ga = analytics.init();
        assert.equal(result[0][0], "create");
        assert.equal(result[0][1], "X");
        assert.equal(result[0][2], "auto");
    });
});
<commit_msg>Review<commit_after>define(function(require) {
    "use strict";
    var gamodule = require("gamodule");

    QUnit.module("Google Analytics");
    QUnit.test("test without analytics", function (assert) {
        var result=[];
        window.ga = function(cmd, id, auto) {
            result[0] = [cmd, id, auto];
        };
        window.apidata.analytics = undefined;
        var ga = gamodule.init();
        assert.notEqual(ga, undefined);
        assert.equal(result.length, 0);
    });

    QUnit.test("test with analytics", function (assert) {
        var result=[];
        window.ga = function(cmd, id, auto) {
            result[0] = [cmd, id, auto];
        };
        window.apidata.analytics = {"tracking_id": "X"};
        var ga = gamodule.init();
        assert.notEqual(ga, undefined);
        assert.equal(result[0][0], "create");
        assert.equal(result[0][1], "X");
        assert.equal(result[0][2], "auto");
    });
});
<|endoftext|>


<commit_before>'use strict';

var isGitRepo = require('../../../lib/utilities/git-repo');
var expect    = require('chai').expect;

describe('cleanBaseURL()', function() {
  it('recognizes git-style urls in various formats', function() {
    expect(isGitRepo('https://github.com/trek/app-blueprint-test.git'));
    expect(isGitRepo('git@github.com:trek/app-blueprint-test.git'));
    expect(isGitRepo('git+ssh://user@server/project.git'));
    expect(isGitRepo('git+https://user@server/project.git'));
  });
});
<commit_msg>as we are using an external lib, we should just point our tests at it. This helps usknow it at least supports our existing use-cases.<commit_after>'use strict';

var isGitRepo = require('is-git-url');
var expect    = require('chai').expect;

describe('cleanBaseURL()', function() {
  it('recognizes git-style urls in various formats', function() {
    expect(isGitRepo('https://github.com/trek/app-blueprint-test.git'));
    expect(isGitRepo('git@github.com:trek/app-blueprint-test.git'));
    expect(isGitRepo('git+ssh://user@server/project.git'));
    expect(isGitRepo('git+https://user@server/project.git'));
  });
});
<|endoftext|>


<commit_before>exports.name = 'WG-NOTE';
exports.config = {
    status: 'NOTE',
    longStatus: 'Working Group Note',
    previousVersion: true,
    styleSheet: 'W3C-WG-NOTE',
    stabilityWarning: true,
    noteStatus: true,
};

const base = require('../base');

exports.rules = base.extendWithInserts({
    'headers.h2-status': [
        require('../../rules/headers/github-repo'),
        require('../../rules/headers/copyright'),
    ],
    'sotd.supersedable': [
        require('../../rules/sotd/diff'),
        require('../../rules/sotd/deliverer-note'),
        require('../../rules/sotd/group-homepage'),
        require('../../rules/sotd/stability'),
        require('../../rules/sotd/status'),
        require('../../rules/sotd/pp'),
        require('../../rules/sotd/process-document'),
    ],
});
<commit_msg>fix-wg-note (#1321)<commit_after>exports.name = 'WG-NOTE';
exports.config = {
    status: 'NOTE',
    longStatus: 'Working Group Note',
    previousVersion: true,
    styleSheet: 'W3C-WG-NOTE',
    stabilityWarning: true,
    noteStatus: true,
};

const base = require('../base');

exports.rules = base.insertAfter(require('../TR').rules, 'sotd.pp', [
    require('../../rules/sotd/diff'),
    require('../../rules/sotd/deliverer-note'),
]);
<|endoftext|>


<commit_before>/* eslint-disable func-style */
import { assign, defaults, identity } from "lodash";
import React from "react";

function getDatumKey(datum, idx) {
  return (datum.key || idx).toString();
}

function getKeyedData(data) {
  return data.reduce((keyedData, datum, idx) => {
    const key = getDatumKey(datum, idx);
    keyedData[key] = datum;
    return keyedData;
  }, {});
}

function getKeyedDataDifference(a, b) {
  let hasDifference = false;
  const difference = Object.keys(a).reduce((_difference, key) => {
    if (!(key in b)) {
      hasDifference = true;
      _difference[key] = true;
    }
    return _difference;
  }, {});
  return hasDifference && difference;
}

/**
 * Calculate which data-points exist in oldData and not nextData -
 * these are the `exiting` data-points.  Also calculate which
 * data-points exist in nextData and not oldData - these are the
 * `entering` data-points.
 *
 * @param  {Array} oldData   this.props.data Array
 * @param  {Array} nextData  this.props.data Array
 *
 * @return {Object}          Object with `entering` and `exiting` properties.
 *                           entering[datum.key] will be true if the data is
 *                           entering, and similarly for `exiting`.
 */
function getNodeTransitions(oldData, nextData) {
  const oldDataKeyed = oldData && getKeyedData(oldData);
  const nextDataKeyed = nextData && getKeyedData(nextData);

  return {
    entering: oldDataKeyed && getKeyedDataDifference(nextDataKeyed, oldDataKeyed),
    exiting: nextDataKeyed && getKeyedDataDifference(oldDataKeyed, nextDataKeyed)
  };
}

function getChildData(child) {
  if (child.type && child.type.getData) {
    return child.type.getData(child.props);
  }
  return child.props && child.props.data || false;
}

/**
 * If a parent component has animation enabled, calculate the transitions
 * for any data of any child component that supports data transitions
 * Data transitions are defined as any two datasets where data nodes exist
 * in the first set and not the second, in the second and not the first,
 * or both.
 *
 * @param  {Children}  oldChildren   this.props.children from old props
 * @param  {Children}  nextChildren  this.props.children from next props
 *
 * @return {Object}                  Object with the following properties:
 *                                    - nodesWillExit
 *                                    - nodesWillEnter
 *                                    - childrenTransitions
 *                                    - nodesShouldEnter
 *                                    - nodesDoneClipPathEnter
 *                                    - nodesDoneClipPathExit
 */
export function getInitialTransitionState(oldChildren, nextChildren) {
  let nodesWillExit = false;
  let nodesWillEnter = false;

  const getTransition = (oldChild, newChild) => {
    if (!newChild || oldChild.type !== newChild.type) {
      return {};
    }

    const { entering, exiting } =
      getNodeTransitions(getChildData(oldChild), getChildData(newChild)) || {};

    nodesWillExit = nodesWillExit || !!exiting;
    nodesWillEnter = nodesWillEnter || !!entering;

    return { entering: entering || false, exiting: exiting || false };
  };

  const getTransitionsFromChildren = (old, next) => {
    return old.map((child, idx) => {
      if (child.props.children) {
        return getTransitionsFromChildren(
          React.Children.toArray(old[idx].props.children),
          React.Children.toArray(next[idx].props.children)
        );
      }
      // get Transition entering and exiting nodes
      return getTransition(child, next[idx]);
    });
  };

  const childrenTransitions = getTransitionsFromChildren(
    React.Children.toArray(oldChildren),
    React.Children.toArray(nextChildren)
  );
  return {
    nodesWillExit,
    nodesWillEnter,
    childrenTransitions,
    // TODO: This may need to be refactored for the following situation.
    //       The component receives new props, and the data provided
    //       is a perfect match for the previous data and domain except
    //       for new nodes. In this case, we wouldn't want a delay before
    //       the new nodes appear.
    nodesShouldEnter: false,
    nodesDoneClipPathEnter: false,
    nodesDoneClipPathExit: false
  };
}

function getInitialChildProps(animate, data) {
  const after = animate.onEnter && animate.onEnter.after ? animate.onEnter.after : identity;
  return {
    data: data.map((datum) => assign({}, datum, after(datum)))
  };
}

function getChildClipPathToExit(animate, child, data, exitingNodes, cb) { // eslint-disable-line max-params, max-len
  let clipWidth;

  if (exitingNodes) {
    animate = assign({}, animate, { onEnd: cb });
    const beforeClipPathWidth = animate.onExit && animate.onExit.beforeClipPathWidth ?
          animate.onExit.beforeClipPathWidth : identity;

    clipWidth = beforeClipPathWidth(data, child, exitingNodes);
  }

  return { animate, clipWidth };
}

function getChildPropsOnExit(animate, child, data, exitingNodes, cb) { // eslint-disable-line max-params, max-len
  // Whether or not _this_ child has exiting nodes, we want the exit-
  // transition for all children to have the same duration, delay, etc.
  const onExit = animate && animate.onExit;
  animate = assign({}, animate, onExit);

  if (exitingNodes) {
    // After the exit transition occurs, trigger the animations for
    // nodes that are neither exiting or entering.
    animate.onEnd = cb;
    const before = animate.onExit && animate.onExit.before ? animate.onExit.before : identity;
    // If nodes need to exit, transform them with the provided onExit.before function.
    data = data.map((datum, idx) => {
      const key = (datum.key || idx).toString();
      return exitingNodes[key] ? assign({}, datum, before(datum)) : datum;
    });
  }

  return { animate, data };
}

function getChildClipPathToEnter(animate, child, data, enteringNodes, cb) { // eslint-disable-line max-params, max-len
  let clipWidth;

  if (enteringNodes) {
    animate = assign({}, animate, { onEnd: cb });
    const afterClipPathWidth = animate.onEnter && animate.onEnter.afterClipPathWidth ?
          animate.onEnter.afterClipPathWidth : identity;

    clipWidth = afterClipPathWidth(data, child);
  }

  return { animate, clipWidth };
}

function getChildPropsBeforeEnter(animate, child, data, enteringNodes, cb) { // eslint-disable-line max-params,max-len
  let clipWidth;

  if (enteringNodes) {
    // Perform a normal animation here, except - when it finishes - trigger
    // the transition for entering nodes.
    animate = assign({}, animate, { onEnd: cb });
    const before = animate.onEnter && animate.onEnter.before ? animate.onEnter.before : identity;
    const beforeClipPathWidth = animate.onEnter && animate.onEnter.beforeClipPathWidth ?
      animate.onEnter.beforeClipPathWidth : identity;
    // We want the entering nodes to be included in the transition target
    // domain.  However, we may not want these nodes to be displayed initially,
    // so perform the `onEnter.before` transformation on each node.
    data = data.map((datum, idx) => {
      const key = (datum.key || idx).toString();
      return enteringNodes[key] ? assign({}, datum, before(datum)) : datum;
    });

    clipWidth = beforeClipPathWidth(data, child, enteringNodes);
  }

  return { animate, data, clipWidth };
}

function getChildPropsOnEnter(animate, child, data, enteringNodes) { // eslint-disable-line max-params, max-len
  // Whether or not _this_ child has entering nodes, we want the entering-
  // transition for all children to have the same duration, delay, etc.
  const onEnter = animate && animate.onEnter;
  animate = assign({}, animate, onEnter);

  if (enteringNodes) {
    // Old nodes have been transitioned to their new values, and the
    // domain should encompass the nodes that will now enter. So perform
    // the `onEnter.after` transformation on each node.
    const after = animate.onEnter && animate.onEnter.after ? animate.onEnter.after : identity;
    data = data.map((datum, idx) => {
      const key = getDatumKey(datum, idx);
      return enteringNodes[key] ? assign({}, datum, after(datum)) : datum;
    });
  }
  return { animate, data };
}

/**
 * getTransitionPropsFactory - putting the Java in JavaScript.  This will return a
 * function that returns prop transformations for a child, given that child's props
 * and its index in the parent's children array.
 *
 * In particular, this will include an `animate` object that is set appropriately
 * so that each child will be synchoronized for each stage of a transition
 * animation.  It will also include a transformed `data` object, where each datum
 * is transformed by `animate.onExit` and `animate.onEnter` `before` and `after`
 * functions.
 *
 * @param  {Object}  props       `this.props` for the parent component.
 * @param  {Object} state        `this.state` for the parent component.
 * @param  {Function} setState    Function that, when called, will `this.setState` on
 *                                 the parent component with the provided object.
 *
 * @return {Function}              Child-prop transformation function.
 */
export function getTransitionPropsFactory(props, state, setState) {
  const nodesWillExit = state && state.nodesWillExit;
  const nodesWillEnter = state && state.nodesWillEnter;
  const nodesShouldEnter = state && state.nodesShouldEnter;
  const nodesDoneClipPathEnter = state && state.nodesDoneClipPathEnter;
  const nodesDoneClipPathExit = state && state.nodesDoneClipPathExit;
  const childrenTransitions = state && state.childrenTransitions || [];
  const transitionDurations = {
    enter: props.animate && props.animate.onEnter && props.animate.onEnter.duration,
    exit: props.animate && props.animate.onExit && props.animate.onExit.duration,
    move: props.animate && props.animate.duration
  };

  const onExit = (nodes, child, data, animate) => { // eslint-disable-line max-params
    if (child.type.role === "line" && !nodesDoneClipPathExit) {
      return getChildClipPathToExit(animate, child, data, nodes, () => {
        setState({ nodesDoneClipPathExit: true });
      });
    }

    return getChildPropsOnExit(animate, child, data, nodes, () => {
      setState({ nodesWillExit: false });
    });
  };

  const onEnter = (nodes, child, data, animate) => { // eslint-disable-line max-params
    if (nodesShouldEnter) {
      if (child.type.role === "line" && !nodesDoneClipPathEnter) {
        return getChildClipPathToEnter(animate, child, data, nodes, () => {
          setState({ nodesDoneClipPathEnter: true });
        });
      }

      return getChildPropsOnEnter(animate, child, data, nodes);
    }

    return getChildPropsBeforeEnter(animate, child, data, nodes, () => {
      setState({ nodesShouldEnter: true });
    });
  };

  const getChildTransitionDuration = function (child, type) {
    const animate = child.props.animate;
    const defaultTransitions = child.type && child.type.defaultTransitions;
    return animate[type] && animate[type].duration ||
      defaultTransitions[type] && defaultTransitions[type].duration;
  };

  return function getTransitionProps(child, index) { // eslint-disable-line max-statements
    const data = getChildData(child) || [];
    const animate = defaults({}, props.animate, child.props.animate);

    animate.onExit = defaults(
      {}, animate.onExit, child.type.defaultTransitions && child.type.defaultTransitions.onExit
    );
    animate.onEnter = defaults(
      {}, animate.onEnter, child.type.defaultTransitions && child.type.defaultTransitions.onEnter
    );

    const childTransitions = childrenTransitions[index] || childrenTransitions[0];
    if (nodesWillExit) {
      const exitingNodes = childTransitions && childTransitions.exiting;
      const exit = transitionDurations.exit || getChildTransitionDuration(child, "onExit");
      // if nodesWillExit, but this child has no exiting nodes, set a delay instead of a duration
      // devide duration to apply half of the duration time to clipPath animation, half to shape animation
      const animation = exitingNodes ? {duration: exit / 2} : {delay: exit / 2};
      return onExit(exitingNodes, child, data, assign({}, animate, animation));
    } else if (nodesWillEnter) {
      const enteringNodes = childTransitions && childTransitions.entering;
      const enter = transitionDurations.enter || getChildTransitionDuration(child, "onEnter");
      const move = transitionDurations.move ||
        child.props.animate && child.props.animate.duration;
      // devide duration to apply half of the duration time to clipPath animation, half to shape animation
      const animation = { duration: nodesShouldEnter && enteringNodes ? enter / 2 : move / 2 };
      return onEnter(enteringNodes, child, data, assign({}, animate, animation));
    } else if (!state && animate && animate.onExit) {
      // This is the initial render, and nodes may enter when props change. Because
      // animation interpolation is determined by old- and next- props, data may need
      // to be augmented with certain properties.
      //
      // For example, it may be desired that exiting nodes go from `opacity: 1` to
      // `opacity: 0`. Without setting this on a per-datum basis, the interpolation
      // might go from `opacity: undefined` to `opacity: 0`, which would result in
      // interpolated `opacity: NaN` values.
      //
      return getInitialChildProps(animate, data);
    }
    return { animate, data };

  };
}
<commit_msg>fix clipPath exit and enter function<commit_after>/* eslint-disable func-style */
import { assign, defaults, identity } from "lodash";
import React from "react";

function getDatumKey(datum, idx) {
  return (datum.key || idx).toString();
}

function getKeyedData(data) {
  return data.reduce((keyedData, datum, idx) => {
    const key = getDatumKey(datum, idx);
    keyedData[key] = datum;
    return keyedData;
  }, {});
}

function getKeyedDataDifference(a, b) {
  let hasDifference = false;
  const difference = Object.keys(a).reduce((_difference, key) => {
    if (!(key in b)) {
      hasDifference = true;
      _difference[key] = true;
    }
    return _difference;
  }, {});
  return hasDifference && difference;
}

/**
 * Calculate which data-points exist in oldData and not nextData -
 * these are the `exiting` data-points.  Also calculate which
 * data-points exist in nextData and not oldData - these are the
 * `entering` data-points.
 *
 * @param  {Array} oldData   this.props.data Array
 * @param  {Array} nextData  this.props.data Array
 *
 * @return {Object}          Object with `entering` and `exiting` properties.
 *                           entering[datum.key] will be true if the data is
 *                           entering, and similarly for `exiting`.
 */
function getNodeTransitions(oldData, nextData) {
  const oldDataKeyed = oldData && getKeyedData(oldData);
  const nextDataKeyed = nextData && getKeyedData(nextData);

  return {
    entering: oldDataKeyed && getKeyedDataDifference(nextDataKeyed, oldDataKeyed),
    exiting: nextDataKeyed && getKeyedDataDifference(oldDataKeyed, nextDataKeyed)
  };
}

function getChildData(child) {
  if (child.type && child.type.getData) {
    return child.type.getData(child.props);
  }
  return child.props && child.props.data || false;
}

/**
 * If a parent component has animation enabled, calculate the transitions
 * for any data of any child component that supports data transitions
 * Data transitions are defined as any two datasets where data nodes exist
 * in the first set and not the second, in the second and not the first,
 * or both.
 *
 * @param  {Children}  oldChildren   this.props.children from old props
 * @param  {Children}  nextChildren  this.props.children from next props
 *
 * @return {Object}                  Object with the following properties:
 *                                    - nodesWillExit
 *                                    - nodesWillEnter
 *                                    - childrenTransitions
 *                                    - nodesShouldEnter
 *                                    - nodesDoneClipPathEnter
 *                                    - nodesDoneClipPathExit
 */
export function getInitialTransitionState(oldChildren, nextChildren) {
  let nodesWillExit = false;
  let nodesWillEnter = false;

  const getTransition = (oldChild, newChild) => {
    if (!newChild || oldChild.type !== newChild.type) {
      return {};
    }

    const { entering, exiting } =
      getNodeTransitions(getChildData(oldChild), getChildData(newChild)) || {};

    nodesWillExit = nodesWillExit || !!exiting;
    nodesWillEnter = nodesWillEnter || !!entering;

    return { entering: entering || false, exiting: exiting || false };
  };

  const getTransitionsFromChildren = (old, next) => {
    return old.map((child, idx) => {
      if (child.props.children) {
        return getTransitionsFromChildren(
          React.Children.toArray(old[idx].props.children),
          React.Children.toArray(next[idx].props.children)
        );
      }
      // get Transition entering and exiting nodes
      return getTransition(child, next[idx]);
    });
  };

  const childrenTransitions = getTransitionsFromChildren(
    React.Children.toArray(oldChildren),
    React.Children.toArray(nextChildren)
  );
  return {
    nodesWillExit,
    nodesWillEnter,
    childrenTransitions,
    // TODO: This may need to be refactored for the following situation.
    //       The component receives new props, and the data provided
    //       is a perfect match for the previous data and domain except
    //       for new nodes. In this case, we wouldn't want a delay before
    //       the new nodes appear.
    nodesShouldEnter: false,
    nodesDoneClipPathEnter: false,
    nodesDoneClipPathExit: false
  };
}

function getInitialChildProps(animate, data) {
  const after = animate.onEnter && animate.onEnter.after ? animate.onEnter.after : identity;
  return {
    data: data.map((datum) => assign({}, datum, after(datum)))
  };
}

function getChildClipPathToExit(animate, child, data, exitingNodes, cb) { // eslint-disable-line max-params, max-len
  let clipWidth;

  if (exitingNodes) {
    animate = assign({}, animate, { onEnd: cb });
    const beforeClipPathWidth = animate.onExit && animate.onExit.beforeClipPathWidth;

    if (beforeClipPathWidth) {
      clipWidth = beforeClipPathWidth(data, child, exitingNodes);
      return { animate, clipWidth };
    }
  }

  return { animate };
}

function getChildPropsOnExit(animate, child, data, exitingNodes, cb) { // eslint-disable-line max-params, max-len
  // Whether or not _this_ child has exiting nodes, we want the exit-
  // transition for all children to have the same duration, delay, etc.
  const onExit = animate && animate.onExit;
  animate = assign({}, animate, onExit);

  if (exitingNodes) {
    // After the exit transition occurs, trigger the animations for
    // nodes that are neither exiting or entering.
    animate.onEnd = cb;
    const before = animate.onExit && animate.onExit.before ? animate.onExit.before : identity;
    // If nodes need to exit, transform them with the provided onExit.before function.
    data = data.map((datum, idx) => {
      const key = (datum.key || idx).toString();
      return exitingNodes[key] ? assign({}, datum, before(datum)) : datum;
    });
  }

  return { animate, data };
}

function getChildClipPathToEnter(animate, child, data, enteringNodes, cb) { // eslint-disable-line max-params, max-len
  let clipWidth;

  if (enteringNodes) {
    animate = assign({}, animate, { onEnd: cb });
    const afterClipPathWidth = animate.onEnter && animate.onEnter.afterClipPathWidth;

    if (afterClipPathWidth) {
      clipWidth = afterClipPathWidth(data, child);
      return { animate, clipWidth};
    }
  }

  return { animate };
}

function getChildPropsBeforeEnter(animate, child, data, enteringNodes, cb) { // eslint-disable-line max-params,max-len
  let clipWidth;

  if (enteringNodes) {
    // Perform a normal animation here, except - when it finishes - trigger
    // the transition for entering nodes.
    animate = assign({}, animate, { onEnd: cb });
    const before = animate.onEnter && animate.onEnter.before ? animate.onEnter.before : identity;
    const beforeClipPathWidth = animate.onEnter && animate.onEnter.beforeClipPathWidth;
    // We want the entering nodes to be included in the transition target
    // domain.  However, we may not want these nodes to be displayed initially,
    // so perform the `onEnter.before` transformation on each node.
    data = data.map((datum, idx) => {
      const key = (datum.key || idx).toString();
      return enteringNodes[key] ? assign({}, datum, before(datum)) : datum;
    });

    if (beforeClipPathWidth) {
      clipWidth = beforeClipPathWidth(data, child, enteringNodes);
      return { animate, data, clipWidth};
    }
  }

  return { animate, data };
}

function getChildPropsOnEnter(animate, child, data, enteringNodes) { // eslint-disable-line max-params, max-len
  // Whether or not _this_ child has entering nodes, we want the entering-
  // transition for all children to have the same duration, delay, etc.
  const onEnter = animate && animate.onEnter;
  animate = assign({}, animate, onEnter);

  if (enteringNodes) {
    // Old nodes have been transitioned to their new values, and the
    // domain should encompass the nodes that will now enter. So perform
    // the `onEnter.after` transformation on each node.
    const after = animate.onEnter && animate.onEnter.after ? animate.onEnter.after : identity;
    data = data.map((datum, idx) => {
      const key = getDatumKey(datum, idx);
      return enteringNodes[key] ? assign({}, datum, after(datum)) : datum;
    });
  }
  return { animate, data };
}

/**
 * getTransitionPropsFactory - putting the Java in JavaScript.  This will return a
 * function that returns prop transformations for a child, given that child's props
 * and its index in the parent's children array.
 *
 * In particular, this will include an `animate` object that is set appropriately
 * so that each child will be synchoronized for each stage of a transition
 * animation.  It will also include a transformed `data` object, where each datum
 * is transformed by `animate.onExit` and `animate.onEnter` `before` and `after`
 * functions.
 *
 * @param  {Object}  props       `this.props` for the parent component.
 * @param  {Object} state        `this.state` for the parent component.
 * @param  {Function} setState    Function that, when called, will `this.setState` on
 *                                 the parent component with the provided object.
 *
 * @return {Function}              Child-prop transformation function.
 */
export function getTransitionPropsFactory(props, state, setState) {
  const nodesWillExit = state && state.nodesWillExit;
  const nodesWillEnter = state && state.nodesWillEnter;
  const nodesShouldEnter = state && state.nodesShouldEnter;
  const nodesDoneClipPathEnter = state && state.nodesDoneClipPathEnter;
  const nodesDoneClipPathExit = state && state.nodesDoneClipPathExit;
  const childrenTransitions = state && state.childrenTransitions || [];
  const transitionDurations = {
    enter: props.animate && props.animate.onEnter && props.animate.onEnter.duration,
    exit: props.animate && props.animate.onExit && props.animate.onExit.duration,
    move: props.animate && props.animate.duration
  };

  const onExit = (nodes, child, data, animate) => { // eslint-disable-line max-params
    if (!nodesDoneClipPathExit) {
      return getChildClipPathToExit(animate, child, data, nodes, () => {
        setState({ nodesDoneClipPathExit: true });
      });
    }

    return getChildPropsOnExit(animate, child, data, nodes, () => {
      setState({ nodesWillExit: false });
    });
  };

  const onEnter = (nodes, child, data, animate) => { // eslint-disable-line max-params
    if (nodesShouldEnter) {
      if (!nodesDoneClipPathEnter) {
        return getChildClipPathToEnter(animate, child, data, nodes, () => {
          setState({ nodesDoneClipPathEnter: true });
        });
      }

      return getChildPropsOnEnter(animate, child, data, nodes);
    }

    return getChildPropsBeforeEnter(animate, child, data, nodes, () => {
      setState({ nodesShouldEnter: true });
    });
  };

  const getChildTransitionDuration = function (child, type) {
    const animate = child.props.animate;
    const defaultTransitions = child.type && child.type.defaultTransitions;
    return animate[type] && animate[type].duration ||
      defaultTransitions[type] && defaultTransitions[type].duration;
  };

  return function getTransitionProps(child, index) { // eslint-disable-line max-statements
    const data = getChildData(child) || [];
    const animate = defaults({}, props.animate, child.props.animate);

    animate.onExit = defaults(
      {}, animate.onExit, child.type.defaultTransitions && child.type.defaultTransitions.onExit
    );
    animate.onEnter = defaults(
      {}, animate.onEnter, child.type.defaultTransitions && child.type.defaultTransitions.onEnter
    );

    const childTransitions = childrenTransitions[index] || childrenTransitions[0];
    if (nodesWillExit) {
      const exitingNodes = childTransitions && childTransitions.exiting;
      const exit = transitionDurations.exit || getChildTransitionDuration(child, "onExit");
      // if nodesWillExit, but this child has no exiting nodes, set a delay instead of a duration
      // devide duration to apply half of the duration time to clipPath animation
      // , half to shape animation
      const animation = exitingNodes ? {duration: exit / 2} : {delay: exit / 2};
      return onExit(exitingNodes, child, data, assign({}, animate, animation));
    } else if (nodesWillEnter) {
      const enteringNodes = childTransitions && childTransitions.entering;
      const enter = transitionDurations.enter || getChildTransitionDuration(child, "onEnter");
      const move = transitionDurations.move ||
        child.props.animate && child.props.animate.duration;
      // devide duration to apply half of the duration time to clipPath animation
      // , half to shape animation
      const animation = { duration: nodesShouldEnter && enteringNodes ? enter / 2 : move / 2 };
      return onEnter(enteringNodes, child, data, assign({}, animate, animation));
    } else if (!state && animate && animate.onExit) {
      // This is the initial render, and nodes may enter when props change. Because
      // animation interpolation is determined by old- and next- props, data may need
      // to be augmented with certain properties.
      //
      // For example, it may be desired that exiting nodes go from `opacity: 1` to
      // `opacity: 0`. Without setting this on a per-datum basis, the interpolation
      // might go from `opacity: undefined` to `opacity: 0`, which would result in
      // interpolated `opacity: NaN` values.
      //
      return getInitialChildProps(animate, data);
    }
    return { animate, data };

  };
}
<|endoftext|>