<commit_before>export { ArrayInterleaver };

/**
 *
 */
class ArrayInterleaver {

//!!! ...unfinished... (2022/07/08)
// Needs another kinds channel shuffling: along input channel (not along output channel)

  /**
   * Rearrange array elements along width by interleaving.
   *   - Only ( groupCount == 2 ) is supported.
   *
   * @param {Array} arrayIn
   *   The array to be re-arranged. It is an 1D array but represents a 2D array.
   *
   * @param {number} height
   *   The virtual 2D array's height.
   *
   * @param {number} width
   *   The virtual 2D array's width.
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements (from arrayIn) temporarily. Providing this array could reduce memory
   * re-allocation and improve performance.
   *
   * @return {Array}
   *   Retrun the (modified) arrayIn itself.
   */
  static interleave_asGrouptTwo_alongWidth( arrayIn, height, width, arrayTemp = [] ) {

    let elementCount = height * width;

    if ( elementCount != arrayIn.length )
      throw Error( `ArrayInterleaver.interleave_asGrouptTwo_alongWidth(): `
        + `( height * width ) = ( ${height} * ${width} ) = ${elementCount} `
        + ` should be the same as input array length ( ${arrayIn.length} ).`
      );

    if ( ( width % 2 ) != 0 )
      throw Error( `ArrayInterleaver.interleave_asGrouptTwo_alongWidth(): `
        + `width ( ${width} ) must be even (i.e. divisible by 2).`
      );

    arrayTemp.length = width;
    let widthHalf = Math.floor( width / 2 );

    for ( let indexBegin = 0; indexBegin < elementCount; indexBegin += width ) {

      // Copy the elements to be re-arrange.
      for ( let i = 0; i < width; ++i ) {
        arrayTemp[ i ] = arrayIn[ indexBegin + i ];
      }

      // Interleave the elements order.
      for ( let i = 0, toIndex = indexBegin; i < widthHalf; ++i, toIndex += 2 ) {
        arrayIn[ toIndex     ] = arrayTemp[ i ];
        arrayIn[ toIndex + 1 ] = arrayTemp[ i + widthHalf ];
      }
    }

    return arrayIn;
  }

  /**
   * Rearrange array elements by interleaving.
   *   - Only ( groupCount == 2 ) is supported.
   *
   * @param {Array} arrayIn
   *   The array to be re-arranged.
   *
   * @param {number} indexBegin
   *   Re-arrange the arrayIn from which element.
   *
   * @param {number} elementCount
   *   How many elements (begin at indexBegin) will be interleaved. It must be even (i.e. divisible by 2).
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements (from arrayIn) temporarily. Providing this array could reduce memory
   * re-allocation and improve performance.
   *
   * @return {Array}
   *   Retrun the (modified) arrayIn itself.
   */
  static interleave_asGrouptTwo( arrayIn, indexBegin, elementCount, arrayTemp = [] ) {

    if ( ( elementCount % 2 ) != 0 )
      throw Error( `ArrayInterleaver.interleave_byGrouptTwo(): `
        + `elementCount ( ${elementCount} ) must be even (i.e. divisible by 2).`
      );

    // Copy the elements to be re-arrange.
    arrayTemp.length = elementCount;
    for ( let i = 0; i < elementCount; ++i ) {
      arrayTemp[ i ] = arrayIn[ indexBegin + i ];
    }

    // Interleave the elements order.
    let elementCountHalf = Math.floor( elementCount / 2 );
    for ( let i = 0, toIndex = indexBegin; i < elementCountHalf; ++i, toIndex += 2 ) {
      arrayIn[ toIndex     ] = arrayTemp[ i ];
      arrayIn[ toIndex + 1 ] = arrayTemp[ i + elementCountHalf ];
    }

    return arrayIn;
  }

}
<commit_msg>Update ArrayInterleaver.js<commit_after>export { ArrayInterleaver };

/**
 *
 */
class ArrayInterleaver {

  /**
   * Rearrange array elements along width by interleaving.
   *   - Only ( groupCount == 2 ) is supported.
   *
   * @param {Array} arrayIn
   *   The array to be re-arranged. It is an 1D array but represents a 2D array.
   *
   * @param {number} height
   *   The virtual 2D array's height.
   *
   * @param {number} width
   *   The virtual 2D array's width.
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements (from arrayIn) temporarily. Providing this array could reduce memory
   * re-allocation and improve performance.
   *
   * @return {Array}
   *   Retrun the (modified) arrayIn itself.
   */
  static interleave_asGrouptTwo_alongWidth( arrayIn, height, width, arrayTemp = [] ) {

    let elementCount = height * width;

    if ( elementCount != arrayIn.length )
      throw Error( `ArrayInterleaver.interleave_asGrouptTwo_alongWidth(): `
        + `( height * width ) = ( ${height} * ${width} ) = ${elementCount} `
        + ` should be the same as input array length ( ${arrayIn.length} ).`
      );

    if ( ( width % 2 ) != 0 )
      throw Error( `ArrayInterleaver.interleave_asGrouptTwo_alongWidth(): `
        + `width ( ${width} ) must be even (i.e. divisible by 2).`
      );

    arrayTemp.length = width;
    let widthHalf = Math.floor( width / 2 );

    for ( let indexBegin = 0; indexBegin < elementCount; indexBegin += width ) {

      // Copy the elements to be re-arrange.
      for ( let i = 0; i < width; ++i ) {
        arrayTemp[ i ] = arrayIn[ indexBegin + i ];
      }

      // Interleave the elements order.
      for ( let i = 0, toIndex = indexBegin; i < widthHalf; ++i, toIndex += 2 ) {
        arrayIn[ toIndex     ] = arrayTemp[ i ];
        arrayIn[ toIndex + 1 ] = arrayTemp[ i + widthHalf ];
      }
    }

    return arrayIn;
  }

//!!! ...unfinished... (2022/07/08)
// Needs another kinds channel shuffling: along input channel (not along output channel)

  /**
   * Rearrange array elements along height by interleaving.
   *   - Only ( groupCount == 2 ) is supported.
   *
   * @param {Array} arrayIn
   *   The array to be re-arranged. It is an 1D array but represents a 2D array.
   *
   * @param {number} height
   *   The virtual 2D array's height.
   *
   * @param {number} width
   *   The virtual 2D array's width.
   *
   * @param {Array} arrayTemp
   *   A temporary array for placing the original elements (from arrayIn) temporarily. Providing this array could reduce memory
   * re-allocation and improve performance.
   *
   * @return {Array}
   *   Retrun the (modified) arrayIn itself.
   */
  static interleave_asGrouptTwo_alongHeight( arrayIn, height, width, arrayTemp = [] ) {

    let elementCount = height * width;

    if ( elementCount != arrayIn.length )
      throw Error( `ArrayInterleaver.interleave_asGrouptTwo_alongHeight(): `
        + `( height * width ) = ( ${height} * ${width} ) = ${elementCount} `
        + ` should be the same as input array length ( ${arrayIn.length} ).`
      );

    if ( ( height % 2 ) != 0 )
      throw Error( `ArrayInterleaver.interleave_asGrouptTwo_alongHeight(): `
        + `height ( ${height} ) must be even (i.e. divisible by 2).`
      );

//!!! ...unfinished... (2022/07/08)
    arrayTemp.length = width???;

    let heightHalf = Math.floor( height / 2 );

//!!! ...unfinished... (2022/07/08)

    for ( let indexBegin = 0; indexBegin < elementCount; indexBegin += width ) {

      // Copy the elements to be re-arrange.
      for ( let i = 0; i < width; ++i ) {
        arrayTemp[ i ] = arrayIn[ indexBegin + i ];
      }

      // Interleave the elements order.
      for ( let i = 0, toIndex = indexBegin; i < widthHalf; ++i, toIndex += 2 ) {
        arrayIn[ toIndex     ] = arrayTemp[ i ];
        arrayIn[ toIndex + 1 ] = arrayTemp[ i + widthHalf ];
      }
    }

    return arrayIn;
  }

//!!! (2022/07/08 Remarked) Replaced by interleave_asGrouptTwo_alongWidth()
//   /**
//    * Rearrange array elements by interleaving.
//    *   - Only ( groupCount == 2 ) is supported.
//    *
//    * @param {Array} arrayIn
//    *   The array to be re-arranged.
//    *
//    * @param {number} indexBegin
//    *   Re-arrange the arrayIn from which element.
//    *
//    * @param {number} elementCount
//    *   How many elements (begin at indexBegin) will be interleaved. It must be even (i.e. divisible by 2).
//    *
//    * @param {Array} arrayTemp
//    *   A temporary array for placing the original elements (from arrayIn) temporarily. Providing this array could reduce memory
//    * re-allocation and improve performance.
//    *
//    * @return {Array}
//    *   Retrun the (modified) arrayIn itself.
//    */
//   static interleave_asGrouptTwo( arrayIn, indexBegin, elementCount, arrayTemp = [] ) {
//
//     if ( ( elementCount % 2 ) != 0 )
//       throw Error( `ArrayInterleaver.interleave_byGrouptTwo(): `
//         + `elementCount ( ${elementCount} ) must be even (i.e. divisible by 2).`
//       );
//
//     // Copy the elements to be re-arrange.
//     arrayTemp.length = elementCount;
//     for ( let i = 0; i < elementCount; ++i ) {
//       arrayTemp[ i ] = arrayIn[ indexBegin + i ];
//     }
//
//     // Interleave the elements order.
//     let elementCountHalf = Math.floor( elementCount / 2 );
//     for ( let i = 0, toIndex = indexBegin; i < elementCountHalf; ++i, toIndex += 2 ) {
//       arrayIn[ toIndex     ] = arrayTemp[ i ];
//       arrayIn[ toIndex + 1 ] = arrayTemp[ i + elementCountHalf ];
//     }
//
//     return arrayIn;
//   }

}
<|endoftext|>


<commit_before>import { isObject } from 'lodash';

const mewApiError = 'Websocket connection failed';

export const knownErrors = {
  'Can\'t assign to property "request"': mewApiError,
  "Cannot create property 'request'": mewApiError
};

const handleError = err => {
  const errorValues = Object.keys(knownErrors);
  const foundError = errorValues.find(item => {
    const message =
      err && err.message
        ? isObject(err.message)
          ? err.message.message
          : err.message
        : err;
    if (!message) return false;
    return message.includes(item);
  });
  return foundError ? true : false;
};

export default handleError;
<commit_msg>chore: 👷 add possible global catch<commit_after>import { isObject } from 'lodash';

const mewApiError = 'Websocket connection failed';

export const knownErrors = {
  'Can\'t assign to property "request"': mewApiError,
  "Cannot create property 'request'": mewApiError,
  'Load failed':
    'eth_subscribe is not supported. Please make sure your provider supports eth_subscribe'
};

const handleError = err => {
  const errorValues = Object.keys(knownErrors);
  const foundError = errorValues.find(item => {
    const message =
      err && err.message
        ? isObject(err.message)
          ? err.message.message
          : err.message
        : err;
    if (!message) return false;
    return message.includes(item);
  });
  return foundError ? true : false;
};

export default handleError;
<|endoftext|>


<commit_before>if (!ko.getBindingHandler('mdc-instance')) {
  ko.bindingHandlers['mdc-instance'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      var root = bindingContext.$component.root;
      var instance = bindingContext.$component.attachTo(root);
      bindingContext.$component.instance(instance);
      bindingContext.$component.initialize(bindingContext['mdc-parent'])
    }
  };
  ko.virtualElements.allowedBindings['mdc-instance'] = true;
}

if (!ko.getBindingHandler('mdc-child')) {
  ko.bindingHandlers['mdc-child'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {

      var newBindingContext = bindingContext.$parentContext.extend(function () {
        return { 'mdc-parent': bindingContext.$component };
      });

      var children = bindingContext.$componentTemplateNodes;
      ko.utils.arrayForEach(children, function (child) {
        ko.virtualElements.prepend(element, child)
      });

      ko.applyBindingsToDescendants(newBindingContext, element);

      return { controlsDescendantBindings: true }
    }
  }
  ko.virtualElements.allowedBindings['mdc-child'] = true;
}

if (!ko.getBindingHandler('mdc-bindings')) {
  ko.bindingHandlers['mdc-bindings'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      ko.applyBindingsToNode(element, valueAccessor(), bindingContext);
    }
  };
}

if (!ko.getBindingHandler('mdc-css')) {
  ko.bindingHandlers['mdc-css'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      function toggleClass(className, value) {
        ko.utils.toggleDomNodeCssClass(element, className, value);
      }

      var classList = ko.unwrap(valueAccessor());

      ko.utils.objectForEach(classList, function (key, value) {
        var className = bindingContext.$component.foundation.cssClasses[key];
        if (ko.unwrap(value)) {
          toggleClass(className, true);
        }
        if (ko.isSubscribable(value)) {
          value.subscribe( function (value) {
            toggleClass(className, value)
          });
        }
      });
    }
  };
}

if (!ko.getBindingHandler('mdc-attr')) {
  ko.bindingHandlers['mdc-attr'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      var attrList = ko.unwrap(valueAccessor());

      ko.utils.objectForEach(attrList, function (key, value) {
        var attrName = bindingContext.$component.foundation.strings[key];
        if (ko.unwrap(value)) {
          element.setAttribute(attrName, true);
        }
      });
    }
  };
}

window.register = window.register || function (name, viewModelConstructor, template) {
  ko.components.register(name, {
      viewModel: {
        createViewModel: function(params, componentInfo) {
          var element = componentInfo.element;
          var root = element.children[0];
          var attributes = element.attributes;
          var attrs = {}
          var names = [];
          ko.utils.arrayForEach(attributes, function (attr) {
            var attrName = attr.name.toLowerCase();
            if (attrName != 'params'
             && attrName != 'class'
             && attrName != 'data-bind') {
              attrs[attr.name] = attr.value;
              names.push(attr.name);
            }
          });
          ko.utils.arrayForEach(names, function (name) {
            element.removeAttribute(name);
          });
          return new viewModelConstructor(root, params, attrs);
        }
      },
      template: template
  });
};
<commit_msg>fix: Render child components in the right order<commit_after>if (!ko.getBindingHandler('mdc-instance')) {
  ko.bindingHandlers['mdc-instance'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      var root = bindingContext.$component.root;
      var instance = bindingContext.$component.attachTo(root);
      bindingContext.$component.instance(instance);
      bindingContext.$component.initialize(bindingContext['mdc-parent'])
    }
  };
  ko.virtualElements.allowedBindings['mdc-instance'] = true;
}

if (!ko.getBindingHandler('mdc-child')) {
  ko.bindingHandlers['mdc-child'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {

      var newBindingContext = bindingContext.$parentContext.extend(function () {
        return { 'mdc-parent': bindingContext.$component };
      });

      var children = bindingContext.$componentTemplateNodes;
      ko.utils.arrayForEach(children.reverse(), function (child) {
        ko.virtualElements.prepend(element, child)
      });

      ko.applyBindingsToDescendants(newBindingContext, element);

      return { controlsDescendantBindings: true }
    }
  }
  ko.virtualElements.allowedBindings['mdc-child'] = true;
}

if (!ko.getBindingHandler('mdc-bindings')) {
  ko.bindingHandlers['mdc-bindings'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      ko.applyBindingsToNode(element, valueAccessor(), bindingContext);
    }
  };
}

if (!ko.getBindingHandler('mdc-css')) {
  ko.bindingHandlers['mdc-css'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      function toggleClass(className, value) {
        ko.utils.toggleDomNodeCssClass(element, className, value);
      }

      var classList = ko.unwrap(valueAccessor());

      ko.utils.objectForEach(classList, function (key, value) {
        var className = bindingContext.$component.foundation.cssClasses[key];
        if (ko.unwrap(value)) {
          toggleClass(className, true);
        }
        if (ko.isSubscribable(value)) {
          value.subscribe( function (value) {
            toggleClass(className, value)
          });
        }
      });
    }
  };
}

if (!ko.getBindingHandler('mdc-attr')) {
  ko.bindingHandlers['mdc-attr'] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
      var attrList = ko.unwrap(valueAccessor());

      ko.utils.objectForEach(attrList, function (key, value) {
        var attrName = bindingContext.$component.foundation.strings[key];
        if (ko.unwrap(value)) {
          element.setAttribute(attrName, true);
        }
      });
    }
  };
}

window.register = window.register || function (name, viewModelConstructor, template) {
  ko.components.register(name, {
      viewModel: {
        createViewModel: function(params, componentInfo) {
          var element = componentInfo.element;
          var root = element.children[0];
          var attributes = element.attributes;
          var attrs = {}
          var names = [];
          ko.utils.arrayForEach(attributes, function (attr) {
            var attrName = attr.name.toLowerCase();
            if (attrName != 'params'
             && attrName != 'class'
             && attrName != 'data-bind') {
              attrs[attr.name] = attr.value;
              names.push(attr.name);
            }
          });
          ko.utils.arrayForEach(names, function (name) {
            element.removeAttribute(name);
          });
          return new viewModelConstructor(root, params, attrs);
        }
      },
      template: template
  });
};
<|endoftext|>


<commit_before>/**
 * CanvasVideo
 *
 * @class
 * @author Jean-Vincent Roger
 */

'use strict';

var EventDispatcher     = require( '../event/EventDispatcher' ),
    Event               = require( '../event/Event' ),
    Utils               = require( '../core/Utils' ),
    AudioPlayer         = require( './AudioPlayer' ),
    CanvasVideoEvent    = require( '../event/CanvasVideoEvent' );

function CanvasVideo ( src, options )
{
    EventDispatcher.call ( this );

    var that = this;

    var canvas, video, sound;
    var videoReady  = false,
        audioReady  = false,
        readyToPlay = false,
        isPlaying   = false;

    var lastTime, time, elapsed;
    var currentTime = 0,
        needTouchDevice;

    var built = false;
    var seeking = false;

    this.options = {
        fps: 24,
        loop: true,
        hideVideoElement: true,
        xhr: false,
        autoplay: false,
        volume: 1,
        playbackRate: 1
    };

    this.src = src;

    function _constructor ( src, options )
    {
        needTouchDevice = Utils.isIOSdevice;

        // copy options
        for (var i in options) {
            that.options[i] = options[i];
        }

        // if audio driving, increase FPS for smouth
        if( that.options.audio && !options.fps ) that.options.fps = 33;

        that.element = document.createElement ('canvas');
        that.ctx     = that.element.getContext('2d');

        if ( that.options.width ) that.element.width = that.options.width;
        if ( that.options.height ) that.element.height = that.options.height;

        if ( that.options.preload === true || that.options.autoplay === true ) that.load ();
    }

    this.load = function ()
    {
        if ( !built )
        {
            if ( that.options.xhr )
            {
                xhrPreload (src);
            }
            else
            {
                build (src);
            }
        }
    }

    // gerer le cas de n'est pas encore loadé.
    this.play = function ()
    {
        if ( built && readyToPlay )
        {
            isPlaying = true;
            lastTime = Date.now();
            if ( sound ) {
                lastTime = sound.currentTime;
                sound.play ();
            }
            draw();
            calculate ();
            that.dispatchEvent (new Event('play'));
        }
        else {
            that.options.autoplay = true;
            if (!built) this.load ();
        }
    }

    this.pause = function ()
    {
        isPlaying = false;
        that.dispatchEvent (new Event('pause'));
        if ( sound )
        {
            sound.pause ();
        }
    }

    this.destroy = function ()
    {
        isPlaying = false;
        if ( sound )
        {
            sound.destroy ();
            sound = null;
        }
        Utils.removeVideoElement (video);
        unbind ();
        video = null;
    }

    this.canPlayType = function ( type )
    {
        var v;
        if (video) v = video;
        else v = document.createElement('video');
        return v.canPlayType ( type );
    }

    /********************************************************************************
    // GETTER / SETTER
    /********************************************************************************/

    Object.defineProperty( that, 'width', {
        get: function() {
            return that.element.width;
        },
        set: function(value) {
            that.options.width = value;
            that.element.width = value;
            video.width = value;
        }
    });

    Object.defineProperty( that, 'height', {
        get: function() {
            return that.element.height;
        },
        set: function(value) {
            that.options.height = value;
            that.element.height = value;
            video.height = value;
        }
    });

    Object.defineProperty( that, 'videoWidth', {
        get: function() {
            return video.videoWidth;
        }
    });

    Object.defineProperty( that, 'videoHeight', {
        get: function() {
            return video.videoHeight;
        }
    });

    Object.defineProperty( that, 'fps', {
        get: function() {
            return that.options.fps;
        },
        set: function(value) {
            if (that.options.audio ) that.options.fps = value;
        }
    });

    Object.defineProperty( that, 'loop', {
        get: function() {
            return that.options.loop;
        },
        set: function(value) {
            that.options.loop = value;
        }
    });

    Object.defineProperty( that, 'volume', {
        get: function() {
            return that.options.volume;
        },
        set: function(value) {
            that.options.volume = value;
            if (sound) sound.volume = value;
        }
    });

    Object.defineProperty( that, 'muted', {
        get: function() {
            if ( !sound || sound.volume != 0 ) return false;
            else return true;
        },
        set: function(value) {
            if (value)
            {
                if (sound) sound.volume = 0;
            }
            else
            {
                if (sound) sound.volume = that.options.volume;
            }
        }
    });

    Object.defineProperty( that, 'needTouchDevice', {
        get: function() {
            return needTouchDevice;
        }
    });

    // A affiner.
    Object.defineProperty( that, 'currentTime', {
        get: function() {
            return currentTime;
        },
        set: function(value) {
            seeking = true;
            if (sound)
            {
                isPlaying = true;
                sound.currentTime = value;
                video.currentTime = value;
                lastTime = sound.currentTime;
            } else
            {
                video.currentTime = value;
            }
        }
    });

    Object.defineProperty( that, 'duration', {
        get: function() {
            if (video) return video.duration;
            else return NaN;
        }
    });

    Object.defineProperty( that, 'seeking', {
        get: function() {
            return seeking;
        }
    });

    Object.defineProperty( that, 'playbackRate', {
        get: function() {
            return that.options.playbackRate;
        },
        set: function(value) {
            //if ( video ) video.playbackRate = value;
            that.options.playbackRate = value;
            if (that.options.audio)
            {
                if (sound) sound.playbackRate = value;
            }
        }
    });

    Object.defineProperty( that, 'readyState', {
        get: function() {
            return readyToPlay?4:0;
        }
    });

    Object.defineProperty( that, 'controls', {
        get: function() {
            console.warn ('controls attribute is not currently supported by CanvasVideo.');
        },
        set: function(value) {
            console.warn ('controls attribute is not currently supported by CanvasVideo.');
        }
    });

    Object.defineProperty( that, 'autoplay', {
        get: function() {
            return that.options.autoplay;
        },
        set: function(value) {
            that.options.autoplay = true;
        }
    });

    /********************************************************************************
    // PRIVATES
    /********************************************************************************/

    function calculate ()
    {
        if (isPlaying)
        {
            if ( that.options.audio )
            {
                var time = sound.currentTime;
                var elapsed = (time - lastTime);
            }
            else
            {
                var time = Date.now();
                var elapsed = (time - lastTime) / 1000;
            }

            if(elapsed >= ((1000/that.options.fps)/1000)) {
                if ( !that.options.audio )
                {
                    video.currentTime = (video.currentTime + (elapsed*that.options.playbackRate));
                    lastTime = time;
                }
                else
                {
                    video.currentTime = (video.currentTime + elapsed);
                    lastTime = video.currentTime;
                }
            }
            // if we are at the end of the video stop
            currentTime = (Math.round(parseFloat(video.currentTime)*10000)/10000);
            var duration = (Math.round(parseFloat(video.duration)*10000)/10000);
            if(currentTime >= duration) {
                //console.log('currentTime: ' + currentTime + ' duration: ' + video.duration);
                if ( !that.options.audio ) that.dispatchEvent ( new Event ( CanvasVideoEvent.ENDED ));
                if ( that.options.loop )
                {
                    if ( !that.options.audio ) {
                        video.currentTime = 0;
                    }
                }
                else
                {
                    isPlaying = false;
                    that.currentTime = 0;
                    return;
                }

            }
            requestAnimationFrame ( calculate );
        }
    }

    function draw ()
    {
        that.ctx.drawImage( video, 0, 0, video.width, video.height );
        that.dispatchEvent ( new Event( 'timeupdate' ) );

        if (seeking)
        {
            that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY_THROUGH ) );
            that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY ) );
            seeking = false;
        }

    }

    function bothReady ()
    {
        readyToPlay = true;

        if ( !that.options.width ) that.element.width = video.videoWidth;
        if ( !that.options.height ) that.element.height = video.videoHeight;

        video.width = that.element.width;
        video.height = that.element.height;

        that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY_THROUGH ) );
        that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY ) );
        if ( that.options.autoplay ) that.play ();
    }

    function build (src)
    {
        built = true;
        // create video element
        video = createVideoElement ( src );
        bind ();
        if ( !that.options.id ) that.id = video.id ? video.id : Utils.uid();
        else that.id = that.options.id;
        video.id = that.id;
        //video.playbackRate = that.options.playbackRate;
        //document.body.appendChild ( video );
        //if(that.options.hideVideoElement) video.style.display = "none";
        setTimeout ( function(){
            video.load();
        }, 50);

        // gestion de l'audio.
        if (that.options.audio) {
            var buffer = null;
            if(src.arraybuffer) buffer = src.arraybuffer;
            sound = new AudioPlayer ( getAudioSrc (), { loop:that.options.loop, volume:that.options.volume, rate:that.options.playbackRate, arraybuffer:buffer } );
            sound.addEventListener ( CanvasVideoEvent.CAN_PLAY, audioCanPlay );
            sound.addEventListener ( CanvasVideoEvent.ENDED, audioEnded );
        }
    }

    // traitement url
    function xhrPreload (src)
    {
        var videoInfos = getVideoInfos(src);
        var url = videoInfos.src;
        var mime = videoInfos.mime;
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, true);
        //xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
        xhr.responseType = "arraybuffer";

        xhr.onload = function(oEvent) {
            var blob = URL.createObjectURL( new Blob([oEvent.target.response], {type: mime}) );
            build ( {src:blob, mime:mime, arraybuffer:xhr.response} );
        };

        xhr.onprogress = function(oEvent) {
            if(oEvent.lengthComputable) {
                var perc = oEvent.loaded/oEvent.total;
                that.dispatchEvent (new Event(CanvasVideoEvent.PROGRESS, {perc:perc}));
                // do something with this
            }
        }

        xhr.send();
    }

    function bind ()
    {
        video.addEventListener ( 'timeupdate', draw );
        video.addEventListener ( 'canplay', videoCanPlay );
        video.addEventListener ( 'canplaythrough', videoCanPlay );
        //video.addEventListener ( 'volumechange', function(e){} );
        //video.addEventListener ( 'loadstart', function(e){} );
    }

    function unbind ()
    {
        video.removeEventListener ( 'timeupdate', draw );
        video.removeEventListener ( 'canplay', videoCanPlay );
        video.removeEventListener ( 'canplaythrough', videoCanPlay );
    }

    function createVideoElement ( src )
    {
        var v;
        if (Array.isArray(src))
        {
            v = document.createElement('video');
            for (var i = 0; i < src.length; ++i)
            {
                if ( typeof src[i] === 'string' )
                {
                    v.appendChild( createSourceElement( src[i] ) );
                }
                else
                {
                    var mime = src[i].mime?src[i].mime:src[i].type;
                    v.appendChild( createSourceElement(src[i].src, mime) );
                }
            }
        }
        else if (typeof src === 'string')
        {
            v = document.createElement('video');
            v.appendChild( createSourceElement( src ) );
        }
        else if (typeof src === 'object')
        {
            v = document.createElement('video');
            var mime = src.mime?src.mime:src.type;
            v.appendChild( createSourceElement( src.src, mime ) );
        }
        else {
            v = src;
        }
        return v;
    }

    // a fixer le setting du type mime (notamment pour les blobs qui n'ont pas d'extension)
    function createSourceElement ( src, mime )
    {
        var s = document.createElement('source');
        s.src = src;
        var ext = Utils.getExtension ( src );
        if (ext||mime) s.type = mime?mime:'video/' + ext;
        return s;
    }

    function getAudioSrc ()
    {
        var src;
        if ( typeof options.audio === "boolean" )
        {
            var sources = video.querySelectorAll('source');
            for (var i = 0; i <= sources.length-1; i++) {
                if ( Utils.getAudioSupport (sources[i].type) || Utils.getAudioSupport ('video/'+Utils.getExtension (sources[i].src)) || !sources[i].type ) {
                    src = sources[i].src;
                    break;
                }
            }
        }
        else if ( typeof options.audio === "string" )
        {
            src = options.audio;
        }
        return src;
    }

    function getVideoInfos (src)
    {
        var o = {};
        if (Array.isArray(src))
        {
            if ( typeof src[0] === 'string' )
            {
                o.src = src[0];
            }
            else
            {
                var mime = src[0].mime?src[0].mime:src[0].type;
                o.src = src[0].src;
                o.mime = mime;
            }
        }
        else if (typeof src === 'string')
        {
            o.src = src;
        }
        else if (typeof src === 'object')
        {
            var mime = src.mime?src.mime:src.type;
            o.src = src.src;
            o.mime = mime;
        }
        else {
            o = src;
        }

        o.mime = o.mime?o.mime:"video/"+Utils.getExtension(o.src);
        return o;
    }

    /********************************************************************************
    // HANDLERS
    /********************************************************************************/

    function videoCanPlay (e)
    {
        if ( !videoReady )
        {
            videoReady = true;
            if ( !that.options.audio ) {
                bothReady ();
            }
            else if ( that.options.audio && that.needTouchDevice  )
            {
                audioReady = true;
                bothReady ();
            }
            else if ( audioReady )
            {
                bothReady ();
            }
        }
    }

    function audioCanPlay (e)
    {
        if ( !audioReady )
        {
            audioReady = true;
            if ( videoReady ) bothReady ();
        }
    }

    // TO FIX : NO LOOP END.
    function audioEnded (e)
    {
        that.dispatchEvent ( new Event( CanvasVideoEvent.ENDED ));
        if (that.options.loop)
        {
            isPlaying = true;
            sound.currentTime = 0;
            video.currentTime = 0;
            lastTime = sound.currentTime;
        } else {
            isPlaying = false;
            sound.currentTime = 0;
            video.currentTime = 0;
            lastTime = sound.currentTime;
            sound.pause();
        }
    }

    _constructor( src, options );
}

CanvasVideo.prototype = Object.create ( EventDispatcher.prototype );
CanvasVideo.prototype.constructor = CanvasVideo;
module.exports = CanvasVideo;
<commit_msg>fix mp3 file<commit_after>/**
 * CanvasVideo
 *
 * @class
 * @author Jean-Vincent Roger
 */

'use strict';

var EventDispatcher     = require( '../event/EventDispatcher' ),
    Event               = require( '../event/Event' ),
    Utils               = require( '../core/Utils' ),
    AudioPlayer         = require( './AudioPlayer' ),
    CanvasVideoEvent    = require( '../event/CanvasVideoEvent' );

function CanvasVideo ( src, options )
{
    EventDispatcher.call ( this );

    var that = this;

    var canvas, video, sound;
    var videoReady  = false,
        audioReady  = false,
        readyToPlay = false,
        isPlaying   = false;

    var lastTime, time, elapsed;
    var currentTime = 0,
        needTouchDevice;

    var built = false;
    var seeking = false;

    this.options = {
        fps: 24,
        loop: true,
        hideVideoElement: true,
        xhr: false,
        autoplay: false,
        volume: 1,
        playbackRate: 1
    };

    this.src = src;

    function _constructor ( src, options )
    {
        needTouchDevice = Utils.isIOSdevice;

        // copy options
        for (var i in options) {
            that.options[i] = options[i];
        }

        // if audio driving, increase FPS for smouth
        if( that.options.audio && !options.fps ) that.options.fps = 33;

        that.element = document.createElement ('canvas');
        that.ctx     = that.element.getContext('2d');

        if ( that.options.width ) that.element.width = that.options.width;
        if ( that.options.height ) that.element.height = that.options.height;

        if ( that.options.preload === true || that.options.autoplay === true ) that.load ();
    }

    this.load = function ()
    {
        if ( !built )
        {
            if ( that.options.xhr )
            {
                xhrPreload (src);
            }
            else
            {
                build (src);
            }
        }
    }

    this.play = function ()
    {
        if ( built && readyToPlay )
        {
            isPlaying = true;
            lastTime = Date.now();
            if ( sound ) {
                lastTime = sound.currentTime;
                sound.play ();
            }
            draw();
            calculate ();
            that.dispatchEvent (new Event('play'));
        }
        else {
            that.options.autoplay = true;
            if (!built) this.load ();
        }
    }

    this.pause = function ()
    {
        isPlaying = false;
        that.dispatchEvent (new Event('pause'));
        if ( sound )
        {
            sound.pause ();
        }
    }

    this.destroy = function ()
    {
        isPlaying = false;
        if ( sound )
        {
            sound.destroy ();
            sound = null;
        }
        Utils.removeVideoElement (video);
        unbind ();
        video = null;
    }

    this.canPlayType = function ( type )
    {
        var v;
        if (video) v = video;
        else v = document.createElement('video');
        return v.canPlayType ( type );
    }

    /********************************************************************************
    // GETTER / SETTER
    /********************************************************************************/

    Object.defineProperty( that, 'width', {
        get: function() {
            return that.element.width;
        },
        set: function(value) {
            that.options.width = value;
            that.element.width = value;
            video.width = value;
        }
    });

    Object.defineProperty( that, 'height', {
        get: function() {
            return that.element.height;
        },
        set: function(value) {
            that.options.height = value;
            that.element.height = value;
            video.height = value;
        }
    });

    Object.defineProperty( that, 'videoWidth', {
        get: function() {
            return video.videoWidth;
        }
    });

    Object.defineProperty( that, 'videoHeight', {
        get: function() {
            return video.videoHeight;
        }
    });

    Object.defineProperty( that, 'fps', {
        get: function() {
            return that.options.fps;
        },
        set: function(value) {
            if (that.options.audio ) that.options.fps = value;
        }
    });

    Object.defineProperty( that, 'loop', {
        get: function() {
            return that.options.loop;
        },
        set: function(value) {
            that.options.loop = value;
        }
    });

    Object.defineProperty( that, 'volume', {
        get: function() {
            return that.options.volume;
        },
        set: function(value) {
            that.options.volume = value;
            if (sound) sound.volume = value;
        }
    });

    Object.defineProperty( that, 'muted', {
        get: function() {
            if ( !sound || sound.volume != 0 ) return false;
            else return true;
        },
        set: function(value) {
            if (value)
            {
                if (sound) sound.volume = 0;
            }
            else
            {
                if (sound) sound.volume = that.options.volume;
            }
        }
    });

    Object.defineProperty( that, 'needTouchDevice', {
        get: function() {
            return needTouchDevice;
        }
    });

    // A affiner.
    Object.defineProperty( that, 'currentTime', {
        get: function() {
            return currentTime;
        },
        set: function(value) {
            seeking = true;
            if (sound)
            {
                isPlaying = true;
                sound.currentTime = value;
                video.currentTime = value;
                lastTime = sound.currentTime;
            } else
            {
                video.currentTime = value;
            }
        }
    });

    Object.defineProperty( that, 'duration', {
        get: function() {
            if (video) return video.duration;
            else return NaN;
        }
    });

    Object.defineProperty( that, 'seeking', {
        get: function() {
            return seeking;
        }
    });

    Object.defineProperty( that, 'playbackRate', {
        get: function() {
            return that.options.playbackRate;
        },
        set: function(value) {
            //if ( video ) video.playbackRate = value;
            that.options.playbackRate = value;
            if (that.options.audio)
            {
                if (sound) sound.playbackRate = value;
            }
        }
    });

    Object.defineProperty( that, 'readyState', {
        get: function() {
            return readyToPlay?4:0;
        }
    });

    Object.defineProperty( that, 'controls', {
        get: function() {
            console.warn ('controls attribute is not currently supported by CanvasVideo.');
        },
        set: function(value) {
            console.warn ('controls attribute is not currently supported by CanvasVideo.');
        }
    });

    Object.defineProperty( that, 'autoplay', {
        get: function() {
            return that.options.autoplay;
        },
        set: function(value) {
            that.options.autoplay = true;
        }
    });

    /********************************************************************************
    // PRIVATES
    /********************************************************************************/

    function calculate ()
    {
        if (isPlaying)
        {
            if ( that.options.audio )
            {
                var time = sound.currentTime;
                var elapsed = (time - lastTime);
            }
            else
            {
                var time = Date.now();
                var elapsed = (time - lastTime) / 1000;
            }

            if(elapsed >= ((1000/that.options.fps)/1000)) {
                if ( !that.options.audio )
                {
                    video.currentTime = (video.currentTime + (elapsed*that.options.playbackRate));
                    lastTime = time;
                }
                else
                {
                    video.currentTime = (video.currentTime + elapsed);
                    lastTime = video.currentTime;
                }
            }
            // if we are at the end of the video stop
            currentTime = (Math.round(parseFloat(video.currentTime)*10000)/10000);
            var duration = (Math.round(parseFloat(video.duration)*10000)/10000);
            if(currentTime >= duration) {
                //console.log('currentTime: ' + currentTime + ' duration: ' + video.duration);
                if ( !that.options.audio ) that.dispatchEvent ( new Event ( CanvasVideoEvent.ENDED ));
                if ( that.options.loop )
                {
                    if ( !that.options.audio ) {
                        video.currentTime = 0;
                    }
                }
                else
                {
                    isPlaying = false;
                    that.currentTime = 0;
                    return;
                }

            }
            requestAnimationFrame ( calculate );
        }
    }

    function draw ()
    {
        that.ctx.drawImage( video, 0, 0, video.width, video.height );
        that.dispatchEvent ( new Event( 'timeupdate' ) );

        if (seeking)
        {
            that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY_THROUGH ) );
            that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY ) );
            seeking = false;
        }

    }

    function bothReady ()
    {
        readyToPlay = true;

        if ( !that.options.width ) that.element.width = video.videoWidth;
        if ( !that.options.height ) that.element.height = video.videoHeight;

        video.width = that.element.width;
        video.height = that.element.height;

        that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY_THROUGH ) );
        that.dispatchEvent ( new Event ( CanvasVideoEvent.CAN_PLAY ) );
        if ( that.options.autoplay ) that.play ();
    }

    function build (src)
    {
        built = true;
        // create video element
        video = createVideoElement ( src );
        bind ();
        if ( !that.options.id ) that.id = video.id ? video.id : Utils.uid();
        else that.id = that.options.id;
        video.id = that.id;
        //video.playbackRate = that.options.playbackRate;
        //document.body.appendChild ( video );
        //if(that.options.hideVideoElement) video.style.display = "none";
        setTimeout ( function(){
            video.load();
        }, 50);

        // gestion de l'audio.
        if (that.options.audio) {
            var buffer = null;
            if(src.arraybuffer && typeof that.options.audio != 'string') buffer = src.arraybuffer;
            sound = new AudioPlayer ( getAudioSrc (), { loop:that.options.loop, volume:that.options.volume, rate:that.options.playbackRate, arraybuffer:buffer } );
            sound.addEventListener ( CanvasVideoEvent.CAN_PLAY, audioCanPlay );
            sound.addEventListener ( CanvasVideoEvent.ENDED, audioEnded );
        }
    }

    // traitement url
    function xhrPreload (src)
    {
        var videoInfos = getVideoInfos(src);
        var url = videoInfos.src;
        var mime = videoInfos.mime;
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, true);
        //xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
        xhr.responseType = "arraybuffer";

        xhr.onload = function(oEvent) {
            var blob = URL.createObjectURL( new Blob([oEvent.target.response], {type: mime}) );
            build ( {src:blob, mime:mime, arraybuffer:xhr.response} );
        };

        xhr.onprogress = function(oEvent) {
            if(oEvent.lengthComputable) {
                var perc = oEvent.loaded/oEvent.total;
                that.dispatchEvent (new Event(CanvasVideoEvent.PROGRESS, {perc:perc}));
                // do something with this
            }
        }

        xhr.send();
    }

    function bind ()
    {
        video.addEventListener ( 'timeupdate', draw );
        video.addEventListener ( 'canplay', videoCanPlay );
        video.addEventListener ( 'canplaythrough', videoCanPlay );
        //video.addEventListener ( 'volumechange', function(e){} );
        //video.addEventListener ( 'loadstart', function(e){} );
    }

    function unbind ()
    {
        video.removeEventListener ( 'timeupdate', draw );
        video.removeEventListener ( 'canplay', videoCanPlay );
        video.removeEventListener ( 'canplaythrough', videoCanPlay );
    }

    function createVideoElement ( src )
    {
        var v;
        if (Array.isArray(src))
        {
            v = document.createElement('video');
            for (var i = 0; i < src.length; ++i)
            {
                if ( typeof src[i] === 'string' )
                {
                    v.appendChild( createSourceElement( src[i] ) );
                }
                else
                {
                    var mime = src[i].mime?src[i].mime:src[i].type;
                    v.appendChild( createSourceElement(src[i].src, mime) );
                }
            }
        }
        else if (typeof src === 'string')
        {
            v = document.createElement('video');
            v.appendChild( createSourceElement( src ) );
        }
        else if (typeof src === 'object')
        {
            v = document.createElement('video');
            var mime = src.mime?src.mime:src.type;
            v.appendChild( createSourceElement( src.src, mime ) );
        }
        else {
            v = src;
        }
        return v;
    }

    // a fixer le setting du type mime (notamment pour les blobs qui n'ont pas d'extension)
    function createSourceElement ( src, mime )
    {
        var s = document.createElement('source');
        s.src = src;
        var ext = Utils.getExtension ( src );
        if (ext||mime) s.type = mime?mime:'video/' + ext;
        return s;
    }

    function getAudioSrc ()
    {
        var src;
        if ( typeof options.audio === "boolean" )
        {
            var sources = video.querySelectorAll('source');
            for (var i = 0; i <= sources.length-1; i++) {
                if ( Utils.getAudioSupport (sources[i].type) || Utils.getAudioSupport ('video/'+Utils.getExtension (sources[i].src)) || !sources[i].type ) {
                    src = sources[i].src;
                    break;
                }
            }
        }
        else if ( typeof options.audio === "string" )
        {
            src = options.audio;
        }
        return src;
    }

    function getVideoInfos (src)
    {
        var o = {};
        if (Array.isArray(src))
        {
            if ( typeof src[0] === 'string' )
            {
                o.src = src[0];
            }
            else
            {
                var mime = src[0].mime?src[0].mime:src[0].type;
                o.src = src[0].src;
                o.mime = mime;
            }
        }
        else if (typeof src === 'string')
        {
            o.src = src;
        }
        else if (typeof src === 'object')
        {
            var mime = src.mime?src.mime:src.type;
            o.src = src.src;
            o.mime = mime;
        }
        else {
            o = src;
        }

        o.mime = o.mime?o.mime:"video/"+Utils.getExtension(o.src);
        return o;
    }

    /********************************************************************************
    // HANDLERS
    /********************************************************************************/

    function videoCanPlay (e)
    {
        if ( !videoReady )
        {
            videoReady = true;
            if ( !that.options.audio ) {
                bothReady ();
            }
            else if ( that.options.audio && that.needTouchDevice  )
            {
                audioReady = true;
                bothReady ();
            }
            else if ( audioReady )
            {
                bothReady ();
            }
        }
    }

    function audioCanPlay (e)
    {
        if ( !audioReady )
        {
            audioReady = true;
            if ( videoReady ) bothReady ();
        }
    }

    // TO FIX : NO LOOP END.
    function audioEnded (e)
    {
        that.dispatchEvent ( new Event( CanvasVideoEvent.ENDED ));
        if (that.options.loop)
        {
            isPlaying = true;
            sound.currentTime = 0;
            video.currentTime = 0;
            lastTime = sound.currentTime;
        } else {
            isPlaying = false;
            sound.currentTime = 0;
            video.currentTime = 0;
            lastTime = sound.currentTime;
            sound.pause();
        }
    }

    _constructor( src, options );
}

CanvasVideo.prototype = Object.create ( EventDispatcher.prototype );
CanvasVideo.prototype.constructor = CanvasVideo;
module.exports = CanvasVideo;
<|endoftext|>


<commit_before>(function () {
  var providers = {};

  L.TileLayer.Provider = L.TileLayer.extend({
    initialize: function (arg, options) {
      var parts = arg.split('.');

      var providerName = parts[0];
      var variantName = parts[1];

      if (!providers[providerName]) {
        throw "No such provider (" + providerName + ")";
      }

      var provider = {
        url: providers[providerName].url,
        options: providers[providerName].options
      };

      // overwrite values in provider from variant.
      if (variantName && 'variants' in providers[providerName]) {
        if (!(variantName in providers[providerName].variants)) {
          throw "No such name in provider (" + variantName + ")";
        }
        var variant = providers[providerName].variants[variantName];
        provider = {
          url: variant.url || provider.url,
          options: L.Util.extend({}, provider.options, variant.options)
        };
      } else if (typeof provider.url === 'function') {
        provider.url = provider.url(parts.splice(1).join('.'));
      }

      // replace attribution placeholders with their values from toplevel provider attribution.
      var attribution = provider.options.attribution;
      if (attribution.indexOf('{attribution.') != -1) {
        provider.options.attribution = attribution.replace(/\{attribution.(\w*)\}/,
          function(match, attributionName){
            return providers[attributionName].options.attribution;
          });
      }
      // Compute final options combining provider options with any user overrides
      var layer_opts = L.Util.extend({}, provider.options, options);
      L.TileLayer.prototype.initialize.call(this, provider.url, layer_opts);
    }
  });

  /**
   * Definition of providers.
   * see http://leafletjs.com/reference.html#tilelayer for options in the options map.
   */
  providers = {
    OpenStreetMap: {
      url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      options: {
        attribution: '© OpenStreetMap contributors, CC-BY-SA'
      },
      variants: {
        Mapnik: {},
        BlackAndWhite: {
          url: 'http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png'
        },
        DE: {
          url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png'
        }
      }
    },
    OpenCycleMap: {
      url: 'http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png',
      options: {
        attribution: '© OpenCycleMap, OpenStreetMap contributors, CC-BY-SA'
      }
    },
    Thunderforest: {
      url: 'http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png',
      options: {
        attribution: '{attribution.OpenCycleMap}'
      },
      variants: {
        OpenCycleMap: {},
        Transport: {
          url: 'http://{s}.tile2.opencyclemap.org/transport/{z}/{x}/{y}.png'
        },
        Landscape: {
          url: 'http://{s}.tile3.opencyclemap.org/landscape/{z}/{x}/{y}.png'
        }
      }
    },
    MapQuestOpen: {
      url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
      options: {
        attribution: 'Tiles Courtesy of MapQuest — Map data {attribution.OpenStreetMap}',
        subdomains: '1234'
      },
      variants: {
        OSM: {},
        Aerial:{
          url: 'http://oatile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg',
          options: {
            attribution:
              'Tiles Courtesy of MapQuest — ' +
              'Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency'
          }
        }
      }
    },
    MapBox: {
      url: function(id) {
        return 'http://{s}.tiles.mapbox.com/v3/' + id + '/{z}/{x}/{y}.png';
      },
      options: {
        attribution: 'Imagery from MapBox — Map data {attribution.OpenStreetMap}',
        subdomains: 'abcd'
      }
    },
    Stamen: {
      url: 'http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png',
      options: {
        attribution:
          'Map tiles by Stamen Design, CC BY 3.0 — ' +
          'Map data {attribution.OpenStreetMap}',
        subdomains: 'abcd',
        minZoom: 0,
        maxZoom: 20
      },
      variants: {
        Toner: {},
        TonerBackground: {
          url: 'http://{s}.tile.stamen.com/toner-background/{z}/{x}/{y}.png'
        },
        TonerHybrid: {
          url: 'http://{s}.tile.stamen.com/toner-hybrid/{z}/{x}/{y}.png'
        },
        TonerLines: {
          url: 'http://{s}.tile.stamen.com/toner-lines/{z}/{x}/{y}.png'
        },
        TonerLabels: {
          url: 'http://{s}.tile.stamen.com/toner-labels/{z}/{x}/{y}.png'
        },
        TonerLite: {
          url: 'http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png'
        },
        Terrain: {
          url: 'http://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.png',
          options: {
            minZoom: 4,
            maxZoom: 18
          }
        },
        Watercolor: {
          url: 'http://{s}.tile.stamen.com/watercolor/{z}/{x}/{y}.png',
          options: {
            minZoom: 3,
            maxZoom: 16
          }
        }
      }
    },
    Esri: {
      url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
      options: {
        attribution: 'Tiles © Esri'
      },
      variants: {
        WorldStreetMap: {
          options: {
            attribution: '{attribution.Esri} — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
          }
        },
        DeLorme: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            maxZoom: 11,
            attribution: '{attribution.Esri} — Copyright: ©2012 DeLorme'
          }
        },
        WorldTopoMap: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
          }
        },
        WorldImagery: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
          }
        },
        WorldTerrain: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: USGS, Esri, TANA, DeLorme, and NPS'
          }
        },
        WorldShadedRelief: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: Esri'
          }
        },
        WorldPhysical: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: US National Park Service'
          }
        },
        OceanBasemap: {
          url: 'http://services.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{z}/{y}/{x}',
          options: {
            maxZoom: 11,
            attribution: '{attribution.Esri} — Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
          }
        },
        NatGeoWorldMap: {
          url: 'http://services.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
          }
        },
        WorldGrayCanvas: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Esri, DeLorme, NAVTEQ'
          }
        }
      }
    },
    OpenWeatherMap: {
      options: {
        attribution: 'Map data © OpenWeatherMap'
      },
      variants: {
        Clouds: {
          url: 'http://{s}.tile.openweathermap.org/map/clouds/{z}/{x}/{y}.png'
        },
        CloudsClassic: {
          url: 'http://{s}.tile.openweathermap.org/map/clouds_cls/{z}/{x}/{y}.png'
        },
        Precipitation: {
          url: 'http://{s}.tile.openweathermap.org/map/precipitation/{z}/{x}/{y}.png'
        },
        PrecipitationClassic: {
          url: 'http://{s}.tile.openweathermap.org/map/precipitation_cls/{z}/{x}/{y}.png'
        },
        Rain: {
          url: 'http://{s}.tile.openweathermap.org/map/rain/{z}/{x}/{y}.png'
        },
        RainClassic: {
          url: 'http://{s}.tile.openweathermap.org/map/rain_cls/{z}/{x}/{y}.png'
        },
        Pressure: {
          url: 'http://{s}.tile.openweathermap.org/map/pressure/{z}/{x}/{y}.png'
        },
        PressureContour: {
          url: 'http://{s}.tile.openweathermap.org/map/pressure_cntr/{z}/{x}/{y}.png'
        },
        Wind: {
          url: 'http://{s}.tile.openweathermap.org/map/wind/{z}/{x}/{y}.png'
        },
        Temperature: {
          url: 'http://{s}.tile.openweathermap.org/map/temp/{z}/{x}/{y}.png'
        },
        Snow: {
          url: 'http://{s}.tile.openweathermap.org/map/snow/{z}/{x}/{y}.png'
        }
      }
    }

  };
}());

L.TileLayer.provider = function(provider, options){
  return new L.TileLayer.Provider(provider, options);
};

<commit_msg>autofill control<commit_after>(function () {
  var providers = {};

  L.TileLayer.Provider = L.TileLayer.extend({
    initialize: function (arg, options) {
      var parts = arg.split('.');

      var providerName = parts[0];
      var variantName = parts[1];

      if (!providers[providerName]) {
        throw "No such provider (" + providerName + ")";
      }

      var provider = {
        url: providers[providerName].url,
        options: providers[providerName].options
      };

      // overwrite values in provider from variant.
      if (variantName && 'variants' in providers[providerName]) {
        if (!(variantName in providers[providerName].variants)) {
          throw "No such name in provider (" + variantName + ")";
        }
        var variant = providers[providerName].variants[variantName];
        provider = {
          url: variant.url || provider.url,
          options: L.Util.extend({}, provider.options, variant.options)
        };
      } else if (typeof provider.url === 'function') {
        provider.url = provider.url(parts.splice(1).join('.'));
      }

      // replace attribution placeholders with their values from toplevel provider attribution.
      var attribution = provider.options.attribution;
      if (attribution.indexOf('{attribution.') != -1) {
        provider.options.attribution = attribution.replace(/\{attribution.(\w*)\}/,
          function(match, attributionName){
            return providers[attributionName].options.attribution;
          });
      }
      // Compute final options combining provider options with any user overrides
      var layer_opts = L.Util.extend({}, provider.options, options);
      L.TileLayer.prototype.initialize.call(this, provider.url, layer_opts);
    }
  });

  /**
   * Definition of providers.
   * see http://leafletjs.com/reference.html#tilelayer for options in the options map.
   */
  providers = {
    OpenStreetMap: {
      url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      options: {
        attribution: '© OpenStreetMap contributors, CC-BY-SA'
      },
      variants: {
        Mapnik: {},
        BlackAndWhite: {
          url: 'http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png'
        },
        DE: {
          url: 'http://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png'
        }
      }
    },
    OpenCycleMap: {
      url: 'http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png',
      options: {
        attribution: '© OpenCycleMap, OpenStreetMap contributors, CC-BY-SA'
      }
    },
    Thunderforest: {
      url: 'http://{s}.tile.opencyclemap.org/cycle/{z}/{x}/{y}.png',
      options: {
        attribution: '{attribution.OpenCycleMap}'
      },
      variants: {
        OpenCycleMap: {},
        Transport: {
          url: 'http://{s}.tile2.opencyclemap.org/transport/{z}/{x}/{y}.png'
        },
        Landscape: {
          url: 'http://{s}.tile3.opencyclemap.org/landscape/{z}/{x}/{y}.png'
        }
      }
    },
    MapQuestOpen: {
      url: 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
      options: {
        attribution: 'Tiles Courtesy of MapQuest — Map data {attribution.OpenStreetMap}',
        subdomains: '1234'
      },
      variants: {
        OSM: {},
        Aerial:{
          url: 'http://oatile{s}.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg',
          options: {
            attribution:
              'Tiles Courtesy of MapQuest — ' +
              'Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency'
          }
        }
      }
    },
    MapBox: {
      url: function(id) {
        return 'http://{s}.tiles.mapbox.com/v3/' + id + '/{z}/{x}/{y}.png';
      },
      options: {
        attribution: 'Imagery from MapBox — Map data {attribution.OpenStreetMap}',
        subdomains: 'abcd'
      }
    },
    Stamen: {
      url: 'http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png',
      options: {
        attribution:
          'Map tiles by Stamen Design, CC BY 3.0 — ' +
          'Map data {attribution.OpenStreetMap}',
        subdomains: 'abcd',
        minZoom: 0,
        maxZoom: 20
      },
      variants: {
        Toner: {},
        TonerBackground: {
          url: 'http://{s}.tile.stamen.com/toner-background/{z}/{x}/{y}.png'
        },
        TonerHybrid: {
          url: 'http://{s}.tile.stamen.com/toner-hybrid/{z}/{x}/{y}.png'
        },
        TonerLines: {
          url: 'http://{s}.tile.stamen.com/toner-lines/{z}/{x}/{y}.png'
        },
        TonerLabels: {
          url: 'http://{s}.tile.stamen.com/toner-labels/{z}/{x}/{y}.png'
        },
        TonerLite: {
          url: 'http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png'
        },
        Terrain: {
          url: 'http://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.png',
          options: {
            minZoom: 4,
            maxZoom: 18
          }
        },
        Watercolor: {
          url: 'http://{s}.tile.stamen.com/watercolor/{z}/{x}/{y}.png',
          options: {
            minZoom: 3,
            maxZoom: 16
          }
        }
      }
    },
    Esri: {
      url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
      options: {
        attribution: 'Tiles © Esri'
      },
      variants: {
        WorldStreetMap: {
          options: {
            attribution: '{attribution.Esri} — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
          }
        },
        DeLorme: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            maxZoom: 11,
            attribution: '{attribution.Esri} — Copyright: ©2012 DeLorme'
          }
        },
        WorldTopoMap: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
          }
        },
        WorldImagery: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
          }
        },
        WorldTerrain: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: USGS, Esri, TANA, DeLorme, and NPS'
          }
        },
        WorldShadedRelief: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: Esri'
          }
        },
        WorldPhysical: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Physical_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Source: US National Park Service'
          }
        },
        OceanBasemap: {
          url: 'http://services.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{z}/{y}/{x}',
          options: {
            maxZoom: 11,
            attribution: '{attribution.Esri} — Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
          }
        },
        NatGeoWorldMap: {
          url: 'http://services.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
          }
        },
        WorldGrayCanvas: {
          url: 'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
          options: {
            attribution: '{attribution.Esri} — Esri, DeLorme, NAVTEQ'
          }
        }
      }
    },
    OpenWeatherMap: {
      options: {
        attribution: 'Map data © OpenWeatherMap'
      },
      variants: {
        Clouds: {
          url: 'http://{s}.tile.openweathermap.org/map/clouds/{z}/{x}/{y}.png'
        },
        CloudsClassic: {
          url: 'http://{s}.tile.openweathermap.org/map/clouds_cls/{z}/{x}/{y}.png'
        },
        Precipitation: {
          url: 'http://{s}.tile.openweathermap.org/map/precipitation/{z}/{x}/{y}.png'
        },
        PrecipitationClassic: {
          url: 'http://{s}.tile.openweathermap.org/map/precipitation_cls/{z}/{x}/{y}.png'
        },
        Rain: {
          url: 'http://{s}.tile.openweathermap.org/map/rain/{z}/{x}/{y}.png'
        },
        RainClassic: {
          url: 'http://{s}.tile.openweathermap.org/map/rain_cls/{z}/{x}/{y}.png'
        },
        Pressure: {
          url: 'http://{s}.tile.openweathermap.org/map/pressure/{z}/{x}/{y}.png'
        },
        PressureContour: {
          url: 'http://{s}.tile.openweathermap.org/map/pressure_cntr/{z}/{x}/{y}.png'
        },
        Wind: {
          url: 'http://{s}.tile.openweathermap.org/map/wind/{z}/{x}/{y}.png'
        },
        Temperature: {
          url: 'http://{s}.tile.openweathermap.org/map/temp/{z}/{x}/{y}.png'
        },
        Snow: {
          url: 'http://{s}.tile.openweathermap.org/map/snow/{z}/{x}/{y}.png'
        }
      }
    }

  };
}());

L.TileLayer.provider = function(provider, options){
  return new L.TileLayer.Provider(provider, options);
};
L.TileLayer.provider = function(provider){
  return new L.TileLayer.Provider(provider);
};

L.Control.Layers.Provided = L.Control.Layers.extend({
    initialize: function(base, overlay, options){
        var first;
        if(base.length){
            (function(){
                var out = {},
                len = base.length,
                i=0;
                while(i<len){
                    if (i === 0) {
                        first = L.TileLayer.provider(base[0]);
                        out[base[i].replace(/\./g,": ").replace(/([a-z])([A-Z])/g,"$1 $2")] = first;
                    } else {
                        out[base[i].replace(/\./g,": ").replace(/([a-z])([A-Z])/g,"$1 $2")] = L.TileLayer.provider(base[i]);
                    }
                    i++;
                }
                base = out;
            }());
        this._first = first;
        }
        if(overlay && overlay.length){
            (function(){
                var out = {},
                len = overlay.length,
                i=0;
                while(i<len){
                    out[overlay[i].replace(/\./g,": ").replace(/([a-z])([A-Z])/g,"$1 $2")] = L.TileLayer.provider(overlay[i]);
                    i++;
                }
                overlay = out;
            }());
        }
        L.Control.Layers.prototype.initialize.call(this, base, overlay, options);
    },
    onAdd: function(map){
        this._first.addTo(map);
        return L.Control.Layers.prototype.onAdd.call(this, map);
    }
});
L.control.layers.provided = function (baseLayers, overlays, options) {
    return new L.Control.Layers.Provided(baseLayers, overlays, options);
};
<|endoftext|>


<commit_before>var child_process = require('child_process'),
    path = require('path'),
    fs = require('fs'),
    constants = process.ENOENT ? process : require('constants'),
    URL = require('url'),
    glob = require('glob'),
    request = require('request'),
    _ = require('underscore'),
    seq = require('seq'),
    fileUtils = require('./fileUtils'),
    error = require('./error'),
    assets = require('./assets');

require('bufferjs');

function AssetConfigResolver(config) {
    _.extend(this, config || {});
    if (!/^[^:]+:/.test(this.root)) { // No protocol?
        this.root = fileUtils.fsDirToFileUrl(this.root || process.cwd());
    }
    this.protocols = this.protocols || {};
};

AssetConfigResolver.prototype.resolve = function (assetConfig, fromUrl, cb) {
    var that = this;
    if (_.isArray(assetConfig)) {
        // Call ourselves recursively for each item, flatten the results and report back
        return seq()
            .extend(assetConfig)
            .parMap(function (_assetConfig) {
                that.resolve(_assetConfig, fromUrl, this);
            }).
            seq(function () {
                cb(null, _.flatten(this.stack));
            })
            ['catch'](cb);
    }
    if (typeof assetConfig === 'string') {
        if (/^\//.test(assetConfig) && /^file:/.test(fromUrl) && /^file:/.test(that.root)) {
            assetConfig = {url: that.root + assetConfig.substr(1)};
        } else {
            assetConfig = {url: URL.resolve(fromUrl, assetConfig)};
        }
    }
    if (assetConfig.url) {
        var protocol = assetConfig.url.substr(0, assetConfig.url.indexOf(':')),
            pathname = assetConfig.url.replace(/^\w+:(?:\/\/)?/, ""); // Strip protocol and two leading slashes if present
        if (protocol === 'file') {
            if (/[\*\?]/.test(pathname)) {
                // Expand wildcard, then expand each resulting url
                return glob.glob(pathname, error.passToFunction(cb, function (fsPaths) {
                    if (!fsPaths.length) {
                        cb(new Error("AssetGraph.resolveAssetConfig: Wildcard " + pathname + " expanded to nothing"));
                    } else {
                        that.resolve(fsPaths.map(function (fsPath) {
                            return 'file://' + fsPath;
                        }), fromUrl, cb);
                    }
                }));
            }
            assetConfig.rawSrcProxy = function (cb) {
                fs.readFile(pathname, null, cb);
            };
        } else if (protocol === 'http' || protocol === 'https') {
            assetConfig.rawSrcProxy = function (cb) {
                request({
                    url: assetConfig.url,
                    onResponse: true
                }, error.passToFunction(cb, function (response) {
                    if (response.statusCode >= 400) {
                        cb(new Error("Got " + response.statusCode + " from remote server: " + assetConfig.url));
                    } else {
                        var metadata = {},
                            buffers = [];
                        if ('content-type' in response.headers) {
                            var matchContentType = response.headers['content-type'].match(/^\s*([\w\/\-]+)\s*(?:;\s*charset=([\'\"]|)\s*([\w\-]+)\s*\2\s*)?$/i);
                            if (matchContentType) {
                                metadata.contentType = matchContentType[1].toLowerCase();
                                if (matchContentType[3]) {
                                    metadata.originalEncoding = matchContentType[3].toLowerCase();
                                }
                            } else {
                                console.log("AssetConfigResolver: Couldn't parse Content-Type " + response.headers['content-type'] + " while fetching " + assetConfig.url);
                            }
                        }
                        response.on('data', function (buffer) {
                            buffers.push(buffer);
                        }).on('end', function () {
                            cb(null, Buffer.concat(buffers), metadata);
                        }).on('error', cb);
                    }
               }));
            };
        } else if (protocol === 'data') {
            var dataUrlMatch = pathname.match(/^([\w\/\-]+)(?:;charset=([\w\/\-]+))?(;base64)?,(.*)$/);
            if (dataUrlMatch) {
                var contentType = dataUrlMatch[1];
                if (dataUrlMatch[2]) {
                    assetConfig.originalEncoding = dataUrlMatch[2];
                }
                if (dataUrlMatch[3]) {
                    assetConfig.rawSrc = new Buffer(dataUrlMatch[4], 'base64');
                } else {
                    assetConfig.rawSrc = new Buffer(dataUrlMatch[4], 'binary'); // FIXME: support percent-encoded bytes?
                }
                assetConfig.contentType = assetConfig.contentType || contentType;
            } else {
                cb(new Error("Cannot parse data url: " + assetConfig.url));
            }
        } else if (protocol in this.protocols) {
            return that.protocols[protocol].resolve(pathname, error.passToFunction(cb, function (resolvedAssetConfigs) {
                // Reresolve
                that.resolve(resolvedAssetConfigs, fromUrl, cb);
            }));
        } else if (/^file:/.test(fromUrl)) {
            return fileUtils.findParentDirCached(fileUtils.fileUrlToFsPath(fromUrl), protocol, error.passToFunction(cb, function (parentPath) {
                assetConfig.url = fileUtils.fsFilePathToFileUrl(parentPath + '/' + pathname);
                // Reresolve
                that.resolve(assetConfig, fromUrl, cb);
            }));
        } else {
            return cb(new Error("Cannot resolve assetConfig url: " + assetConfig.url));
        }
        if (!assetConfig.type) {
            if (assetConfig.contentType) {
                assetConfig.type = assets.lookupContentType(assetConfig.contentType);
            } else {
                var extension = path.extname(assetConfig.url.replace(/\?.*$/, ""));
                if (extension in assets.typeByExtension) {
                    assetConfig.type = assets.typeByExtension[extension];
                } else {
                    // Looks like there's no way around loading the asset and looking at the src or HTTP headers
                    return assetConfig.rawSrcProxy(function (err, src, metadata) {
                        if (err) {
                            if (err.errno === constants.EISDIR || err.errno === constants.EINVAL) {
                                assetConfig.url += /\/$/.test(assetConfig.url) ? 'index.html' : '/index.html';
                                delete assetConfig.rawSrcProxy;
                                return that.resolve(assetConfig, fromUrl, cb);
                            } else {
                                return cb(err);
                            }
                        }
                        function foundType(type) {
                            assetConfig.rawSrc = src;
                            assetConfig.type = type;
                            cb(null, assetConfig);
                        }
                        if (metadata && metadata.contentType) {
                            // If the asset was served using HTTP, we shouldn't try to second guess by sniffing.
                            foundType(assets.lookupContentType(metadata.contentType));
                        } else {
                            // Work the magic
                            var fileProcess = child_process.spawn('file', ['-b', '--mime-type', '-']),
                                fileOutput = '';
                            fileProcess.stdout.on('data', function (chunk) {
                                fileOutput += chunk;
                            }).on('end', function () {
                                foundType(assets.lookupContentType(fileOutput.match(/^([^\n]*)/)[1]));
                            });
                            fileProcess.stdin.write(src);
                            fileProcess.stdin.end();
                        }
                    });
                }
            }
        }
    }
    cb(null, assetConfig);
};

module.exports = AssetConfigResolver;
<commit_msg>AssetConfigResolver: Minor cosmetics.<commit_after>var child_process = require('child_process'),
    path = require('path'),
    fs = require('fs'),
    constants = process.ENOENT ? process : require('constants'),
    URL = require('url'),
    glob = require('glob'),
    request = require('request'),
    _ = require('underscore'),
    seq = require('seq'),
    fileUtils = require('./fileUtils'),
    error = require('./error'),
    assets = require('./assets');

require('bufferjs');

function AssetConfigResolver(config) {
    _.extend(this, config || {});
    if (!/^[^:]+:/.test(this.root)) { // No protocol?
        this.root = fileUtils.fsDirToFileUrl(this.root || process.cwd());
    }
    this.protocols = this.protocols || {};
};

AssetConfigResolver.prototype.resolve = function (assetConfig, fromUrl, cb) {
    var that = this;
    if (_.isArray(assetConfig)) {
        // Call ourselves recursively for each item, flatten the results and report back
        return seq()
            .extend(assetConfig)
            .parMap(function (_assetConfig) {
                that.resolve(_assetConfig, fromUrl, this);
            })
            .seq(function () {
                cb(null, _.flatten(this.stack));
            })
            ['catch'](cb);
    }
    if (typeof assetConfig === 'string') {
        if (/^\//.test(assetConfig) && /^file:/.test(fromUrl) && /^file:/.test(that.root)) {
            assetConfig = {url: that.root + assetConfig.substr(1)};
        } else {
            assetConfig = {url: URL.resolve(fromUrl, assetConfig)};
        }
    }
    if (assetConfig.url) {
        var protocol = assetConfig.url.substr(0, assetConfig.url.indexOf(':')),
            pathname = assetConfig.url.replace(/^\w+:(?:\/\/)?/, ""); // Strip protocol and two leading slashes if present
        if (protocol === 'file') {
            if (/[\*\?]/.test(pathname)) {
                // Expand wildcard, then expand each resulting url
                return glob.glob(pathname, error.passToFunction(cb, function (fsPaths) {
                    if (!fsPaths.length) {
                        cb(new Error("AssetGraph.resolveAssetConfig: Wildcard " + pathname + " expanded to nothing"));
                    } else {
                        that.resolve(fsPaths.map(function (fsPath) {
                            return 'file://' + fsPath;
                        }), fromUrl, cb);
                    }
                }));
            }
            assetConfig.rawSrcProxy = function (cb) {
                fs.readFile(pathname, null, cb);
            };
        } else if (protocol === 'http' || protocol === 'https') {
            assetConfig.rawSrcProxy = function (cb) {
                request({
                    url: assetConfig.url,
                    onResponse: true
                }, error.passToFunction(cb, function (response) {
                    if (response.statusCode >= 400) {
                        cb(new Error("Got " + response.statusCode + " from remote server: " + assetConfig.url));
                    } else {
                        var metadata = {},
                            buffers = [];
                        if ('content-type' in response.headers) {
                            var matchContentType = response.headers['content-type'].match(/^\s*([\w\/\-]+)\s*(?:;\s*charset=([\'\"]|)\s*([\w\-]+)\s*\2\s*)?$/i);
                            if (matchContentType) {
                                metadata.contentType = matchContentType[1].toLowerCase();
                                if (matchContentType[3]) {
                                    metadata.originalEncoding = matchContentType[3].toLowerCase();
                                }
                            } else {
                                console.log("AssetConfigResolver: Couldn't parse Content-Type " + response.headers['content-type'] + " while fetching " + assetConfig.url);
                            }
                        }
                        response.on('data', function (buffer) {
                            buffers.push(buffer);
                        }).on('end', function () {
                            cb(null, Buffer.concat(buffers), metadata);
                        }).on('error', cb);
                    }
               }));
            };
        } else if (protocol === 'data') {
            var dataUrlMatch = pathname.match(/^([\w\/\-]+)(?:;charset=([\w\/\-]+))?(;base64)?,(.*)$/);
            if (dataUrlMatch) {
                var contentType = dataUrlMatch[1];
                if (dataUrlMatch[2]) {
                    assetConfig.originalEncoding = dataUrlMatch[2];
                }
                if (dataUrlMatch[3]) {
                    assetConfig.rawSrc = new Buffer(dataUrlMatch[4], 'base64');
                } else {
                    assetConfig.rawSrc = new Buffer(dataUrlMatch[4], 'binary'); // FIXME: support percent-encoded bytes?
                }
                assetConfig.contentType = assetConfig.contentType || contentType;
            } else {
                cb(new Error("Cannot parse data url: " + assetConfig.url));
            }
        } else if (protocol in this.protocols) {
            return that.protocols[protocol].resolve(pathname, error.passToFunction(cb, function (resolvedAssetConfigs) {
                // Reresolve
                that.resolve(resolvedAssetConfigs, fromUrl, cb);
            }));
        } else if (/^file:/.test(fromUrl)) {
            return fileUtils.findParentDirCached(fileUtils.fileUrlToFsPath(fromUrl), protocol, error.passToFunction(cb, function (parentPath) {
                assetConfig.url = fileUtils.fsFilePathToFileUrl(parentPath + '/' + pathname);
                // Reresolve
                that.resolve(assetConfig, fromUrl, cb);
            }));
        } else {
            return cb(new Error("Cannot resolve assetConfig url: " + assetConfig.url));
        }
        if (!assetConfig.type) {
            if (assetConfig.contentType) {
                assetConfig.type = assets.lookupContentType(assetConfig.contentType);
            } else {
                var extension = path.extname(assetConfig.url.replace(/\?.*$/, ""));
                if (extension in assets.typeByExtension) {
                    assetConfig.type = assets.typeByExtension[extension];
                } else {
                    // Looks like there's no way around loading the asset and looking at the src or HTTP headers
                    return assetConfig.rawSrcProxy(function (err, src, metadata) {
                        if (err) {
                            if (err.errno === constants.EISDIR || err.errno === constants.EINVAL) {
                                assetConfig.url += /\/$/.test(assetConfig.url) ? 'index.html' : '/index.html';
                                delete assetConfig.rawSrcProxy;
                                return that.resolve(assetConfig, fromUrl, cb);
                            } else {
                                return cb(err);
                            }
                        }
                        function foundType(type) {
                            assetConfig.rawSrc = src;
                            assetConfig.type = type;
                            cb(null, assetConfig);
                        }
                        if (metadata && metadata.contentType) {
                            // If the asset was served using HTTP, we shouldn't try to second guess by sniffing.
                            foundType(assets.lookupContentType(metadata.contentType));
                        } else {
                            // Work the magic
                            var fileProcess = child_process.spawn('file', ['-b', '--mime-type', '-']),
                                fileOutput = '';
                            fileProcess.stdout.on('data', function (chunk) {
                                fileOutput += chunk;
                            }).on('end', function () {
                                foundType(assets.lookupContentType(fileOutput.match(/^([^\n]*)/)[1]));
                            });
                            fileProcess.stdin.write(src);
                            fileProcess.stdin.end();
                        }
                    });
                }
            }
        }
    }
    cb(null, assetConfig);
};

module.exports = AssetConfigResolver;
<|endoftext|>