<commit_before>/*
 * dmuploader.js - Jquery File Uploader - 0.1
 * https://github.com/danielm/uploader
 *
 * Copyright (c) 2013-2017 Daniel Morales
 * Dual licensed under the MIT and GPL licenses.
 */

(function($) {
  var pluginName = 'dmUploader';

  var FileStatus = {
    PENDING: 0,
    UPLOADING: 1,
    COMPLETED: 2,
    FAILED: 3,
    CANCELLED: 4 //(by the user)
  };

  // These are the plugin defaults values
  var defaults = {
    auto: true,
    queue: true,
    dnd: true,
    url: document.URL,
    method: 'POST',
    extraData: {},
    headers: {},
    dataType: null,
    fieldName: 'file',
    maxFileSize: null,
    allowedTypes: '*',
    extFilter: null,
    onInit: function(){},
    onFallbackMode: function() {},
    onNewFile: function(id, file){},
    onBeforeUpload: function(id){},
    onComplete: function(){},
    onUploadProgress: function(id, percent){},
    onUploadSuccess: function(id, data){},
    onUploadError: function(id, message){},
    onFileTypeError: function(file){},
    onFileSizeError: function(file){},
    onFileExtError: function(file){},
    onDragLeave: function(){},
    onDragEnter: function(){},
    onDrop: function(){}
  };

  var DmUploaderFile = function(file, widget)
  {
    this.data = file;

    this.widget = widget;

    this.jqXHR = null;

    this.status = FileStatus.PENDING;

    // The file id doesnt have to bo that special.... or not?
    this.id = Date.now().toString(36).substr(0, 8);
  };

  DmUploaderFile.prototype.upload = function()
  {
    var file = this;

    if (!file.canUpload()){

      if (file.widget.queueRunning) {
        file.widget.processQueue();
      }

      return false;
    }

    // Form Data
    var fd = new FormData();
    fd.append(file.widget.settings.fieldName, file.data);

    // If the callback returns false file will not be processed. This may allow some customization
    var can_continue = file.widget.settings.onBeforeUpload.call(file.widget.element, file.id);
    if (can_continue ===  false) {
      return false;
    }

    // Append extra Form Data
    var customData = file.widget.settings.extraData;
    if (typeof(file.widget.settings.extraData) === "function"){
      customData = file.widget.settings.extraData.call(file.widget.element, file.id);
    }

    $.each(customData, function(exKey, exVal){
      fd.append(exKey, exVal);
    });

    file.status = FileStatus.UPLOADING;

    // Ajax Submit
    file.jqXHR = $.ajax({
      url: file.widget.settings.url,
      type: file.widget.settings.method,
      dataType: file.widget.settings.dataType,
      data: fd,
      headers: file.widget.settings.headers,
      cache: false,
      contentType: false,
      processData: false,
      forceSync: false,
      xhr: function() { return file.getXhr(); },
      success: function(data) { file.onSuccess(data); },
      error: function(xhr, status, errMsg) { file.onError(xhr, status, errMsg); },
      complete: function(data) { file.onComplete(); },
    });

    return true;
  };

  DmUploaderFile.prototype.onSuccess = function(data)
  {
    this.status = FileStatus.COMPLETED;
    this.widget.settings.onUploadSuccess.call(this.widget.element, this.id, data);
  };

  DmUploaderFile.prototype.onError = function(xhr, status, errMsg)
  {
    // If the status is: cancelled (by the user) don't invoke the error callback
    if (this.status != FileStatus.CANCELLED){
      this.status = FileStatus.FAILED;
      this.widget.settings.onUploadError.call(this.widget.element, this.id, errMsg);
    }
  };

  DmUploaderFile.prototype.onComplete = function()
  {
    if (this.widget.queueRunning){
      this.widget.processQueue();
    }
  };

  DmUploaderFile.prototype.getXhr = function()
  {
    var file = this;
    var xhrobj = $.ajaxSettings.xhr();

    if(xhrobj.upload){
      xhrobj.upload.addEventListener('progress', function(event) {
        var percent = 0;
        var position = event.loaded || event.position;
        var total = event.total || event.totalSize;
        if(event.lengthComputable){
          percent = Math.ceil(position / total * 100);
        }

        file.widget.settings.onUploadProgress.call(file.widget.element, file.id, percent);
      }, false);
    }

    return xhrobj;
  };

  DmUploaderFile.prototype.cancel = function()
  {
    switch (this.status){
      case FileStatus.PENDING:
        this.status = FileStatus.CANCELLED;
        break;
      case FileStatus.UPLOADING:
        this.status = FileStatus.CANCELLED;
        this.jqXHR.abort();
        break;
      default:
        return false;
    }

    return true;
  };

  DmUploaderFile.prototype.canUpload = function()
  {
    return (this.status == FileStatus.PENDING ||
      this.status == FileStatus.CANCELLED ||
      this.status == FileStatus.FAILED);
  };

  var DmUploader = function(element, options)
  {
    this.element = $(element);
    this.settings = $.extend({}, defaults, options);

    if (!this.checkSupport()) {
      $.error('Browser not supported by jQuery.dmUploader');

      this.settings.onFallbackMode.call(this.element);

      return false;
    }

    this.init();

    return this;
  };

  DmUploader.prototype.checkSupport = function()
  {
    // This one is mandatory for all modes
    if(typeof window.FormData === 'undefined'){
      return false;
    }

    // Test based on: Modernizr/feature-detects/forms/fileinput.js
    var exp = new RegExp(
      '/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|'+
      '(Windows Phone (OS 7|8.0))|(XBLWP)|'+
      '(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|'+
      '(Kindle\/(1.0|2.0|2.5|3.0))/');

    if (exp.test(window.navigator.userAgent)){
      return false;
    }

    return !$('<input type="file">').prop('disabled');
  };

  DmUploader.prototype.init = function()
  {
    var widget = this;

    // Queue vars
    this.queue = [];
    this.queuePos = -1;
    this.queueRunning = false;

    var input = widget.element.is("input[type=file]") ?
      widget.element : widget.element.find('input[type=file]');

    //-- Is the input our main element itself??
    if (input.length > 0){
      // Or does it has the input as a child
      input.on('change.' + pluginName, function(evt){
        var files = evt.target.files;

        widget.addFiles(files);

        $(this).val('');
      });
    }

    if (this.settings.dnd) {
      this.initDnD();
    }

    if (input.length == 0 && !this.settings.dnd){
      // Trigger an error because if this happens the plugin wont do anything.
      $.error('Markup error found by jQuery.dmUploader');

      return null;
    }

    // We good to go, tell them!
    this.settings.onInit.call(this.element);

    return this;
  };

  DmUploader.prototype.initDnD = function()
  {
    var widget = this;

    // -- Now our own Drop
    widget.element.on('drop.' + pluginName, function (evt){
      evt.stopPropagation();
      evt.preventDefault();

      var files = evt.originalEvent.dataTransfer.files;

      widget.addFiles(files);

      widget.settings.onDrop.call(this.element);
    });

    //-- These two events/callbacks are onlt to maybe do some fancy visual stuff
    widget.element.on('dragleave.' + pluginName, function(evt){
      widget.settings.onDragLeave.call(this.element);
    });

    widget.element.on('dragenter.' + pluginName, function(evt){
      widget.settings.onDragEnter.call(this.element);
    });

    // Disable some document events to prevent redirection
    $(document).off('dragover.' + pluginName).on('dragover.' + pluginName, function (e) {
      e.stopPropagation();
      e.preventDefault();
    });
    $(document).off('drop.' + pluginName).on('drop.' + pluginName, function (e) {
      e.stopPropagation();
      e.preventDefault();
    });
  };

  DmUploader.prototype.validateFile = function(file)
  {
    // Check file size
    if((this.settings.maxFileSize > 0) &&
        (file.size > this.settings.maxFileSize)){

      this.settings.onFileSizeError.call(this.element, file);

      return false;
    }

    // Check file type
    if((this.settings.allowedTypes != '*') &&
        !file.type.match(this.settings.allowedTypes)){

      this.settings.onFileTypeError.call(this.element, file);

      return false;
    }

    // Check file extension
    if(this.settings.extFilter !== null){
      var extList = this.settings.extFilter.toLowerCase().split(';');

      var ext = file.name.toLowerCase().split('.').pop();

      if($.inArray(ext, extList) < 0){
        this.settings.onFileExtError.call(this.element, file);

        return false;
      }
    }

    return new DmUploaderFile(file, this);
  };

  DmUploader.prototype.addFiles = function(files)
  {
    var nFiles = 0;

    for (var i= 0; i < files.length; i++)
    {
      var file = this.validateFile(files[i]);

      if (!file){
        continue;
      }

      var can_continue = this.settings.onNewFile.call(this.element, file.id, file.data);

      // If the callback returns false file will not be processed. This may allow some customization
      if (can_continue === false) {
        return;
      }

      // If we are using automatic uploading, and not a file queue: go for the upload
      if(this.settings.auto && !this.settings.queue){
        file.upload();
      }

      this.queue.push(file);

      nFiles++;
    }

    // No files were added
    if (nFiles === 0){
      return this;
    }

    // Are we auto-uploading files?
    if (this.settings.auto && this.settings.queue && !this.queueRunning) {
      this.processQueue();
    }

    return this;
  };

  DmUploader.prototype.processQueue = function()
  {
    this.queuePos++;

    if (this.queuePos >= this.queue.length){
      this.settings.onComplete.call(this.element);

      // Wait until new files are droped
      this.queuePos = (this.queue.length - 1);

      this.queueRunning = false;

      return false;
    }

    this.queueRunning = true;

    // Start next file
    return this.queue[this.queuePos].upload();
  };

  DmUploader.prototype.restartQueue = function()
  {
    this.queuePos = -1;
    this.queueRunning = false;

    this.processQueue();
  };

  DmUploader.prototype.findById = function(id)
  {
    var r = false;

    for (var i = 0; i < this.queue.length; i++){
      if (this.queue[i].id === id){
        r = this.queue[i];
        break;
      }
    }

    return r;
  };

  // Public API methods
  DmUploader.prototype.methods = {
    start: function(id) {
      if (this.queueRunning){
        // Do not allow to manually upload Files when a queue is running
        return false;
      }

      var file = false;

      if (typeof id !== 'undefined') {
        file = this.findById(id);

        if (!file){
          // File not found in stack
          return false;
        }
      }

      // Trying to Start an upload by ID
      if (file) {
        return file.upload();
      }

      // No id provided...
      if (this.settings.queue) {
        // Resume queue
        this.restartQueue();
      } else {
        // or upload them all
        for (var i = 0; i < this.queue.length; i++){
          this.queue[i].upload();
        }
      }

      return true;
    },
    cancel: function(id) {
      // todo: check auto/queue options

      // todo: check id is present

      return true;
    },
    reset: function() {
      return true;
    }
  };

  $.fn.dmUploader = function(options){
    var args = arguments;

    if (typeof options === 'string'){
      this.each(function(){
        var plugin = $.data(this, pluginName);

        if (plugin instanceof DmUploader){
          if (options === 'destroy'){
            if(plugin.methods.reset()){
              $.removeData(this, pluginName, null);
            }
          } else if (typeof plugin.methods[options] === 'function'){
            plugin.methods[options].apply(plugin, Array.prototype.slice.call(args, 1));
          } else {
            $.error('Method ' +  options + ' does not exist on jQuery.dmUploader');
          }
        } else {
          $.error('Unknown plugin data found by jQuery.dmUploader');
        }
      });
    } else {
      return this.each(function (){
        if(!$.data(this, pluginName)){
          $.data(this, pluginName, new DmUploader(this, options));
        }
      });
    }
  };
})(jQuery);<commit_msg>Adding data paramters to some callbacks<commit_after>/*
 * dmuploader.js - Jquery File Uploader - 0.1
 * https://github.com/danielm/uploader
 *
 * Copyright (c) 2013-2017 Daniel Morales
 * Dual licensed under the MIT and GPL licenses.
 */

(function($) {
  var pluginName = 'dmUploader';

  var FileStatus = {
    PENDING: 0,
    UPLOADING: 1,
    COMPLETED: 2,
    FAILED: 3,
    CANCELLED: 4 //(by the user)
  };

  // These are the plugin defaults values
  var defaults = {
    auto: true,
    queue: true,
    dnd: true,
    url: document.URL,
    method: 'POST',
    extraData: {},
    headers: {},
    dataType: null,
    fieldName: 'file',
    maxFileSize: null,
    allowedTypes: '*',
    extFilter: null,
    onInit: function(){},
    onComplete: function(){},
    onFallbackMode: function() {},
    onNewFile: function(id, file){},
    onBeforeUpload: function(id, file){},
    onUploadProgress: function(id, file, percent){},
    onUploadSuccess: function(id, file, data){},
    onUploadError: function(id, file, message){},
    onFileTypeError: function(file){},
    onFileSizeError: function(file){},
    onFileExtError: function(file){},
    onDragLeave: function(){},
    onDragEnter: function(){},
    onDrop: function(){}
  };

  var DmUploaderFile = function(file, widget)
  {
    this.data = file;

    this.widget = widget;

    this.jqXHR = null;

    this.status = FileStatus.PENDING;

    // The file id doesnt have to bo that special.... or not?
    this.id = Date.now().toString(36).substr(0, 8);
  };

  DmUploaderFile.prototype.upload = function()
  {
    var file = this;

    if (!file.canUpload()){

      if (file.widget.queueRunning) {
        file.widget.processQueue();
      }

      return false;
    }

    // Form Data
    var fd = new FormData();
    fd.append(file.widget.settings.fieldName, file.data);

    // If the callback returns false file will not be processed. This may allow some customization
    var can_continue = file.widget.settings.onBeforeUpload.call(file.widget.element, file.id, file.data);
    if (can_continue ===  false) {
      return false;
    }

    // Append extra Form Data
    var customData = file.widget.settings.extraData;
    if (typeof(file.widget.settings.extraData) === "function"){
      customData = file.widget.settings.extraData.call(file.widget.element, file.id);
    }

    $.each(customData, function(exKey, exVal){
      fd.append(exKey, exVal);
    });

    file.status = FileStatus.UPLOADING;

    // Ajax Submit
    file.jqXHR = $.ajax({
      url: file.widget.settings.url,
      type: file.widget.settings.method,
      dataType: file.widget.settings.dataType,
      data: fd,
      headers: file.widget.settings.headers,
      cache: false,
      contentType: false,
      processData: false,
      forceSync: false,
      xhr: function() { return file.getXhr(); },
      success: function(data) { file.onSuccess(data); },
      error: function(xhr, status, errMsg) { file.onError(xhr, status, errMsg); },
      complete: function(data) { file.onComplete(); },
    });

    return true;
  };

  DmUploaderFile.prototype.onSuccess = function(data)
  {
    this.status = FileStatus.COMPLETED;
    this.widget.settings.onUploadSuccess.call(this.widget.element, this.id, this.data, data);
  };

  DmUploaderFile.prototype.onError = function(xhr, status, errMsg)
  {
    // If the status is: cancelled (by the user) don't invoke the error callback
    if (this.status != FileStatus.CANCELLED){
      this.status = FileStatus.FAILED;
      this.widget.settings.onUploadError.call(this.widget.element, this.id, this.data, errMsg);
    }
  };

  DmUploaderFile.prototype.onComplete = function()
  {
    if (this.widget.queueRunning){
      this.widget.processQueue();
    }
  };

  DmUploaderFile.prototype.getXhr = function()
  {
    var file = this;
    var xhrobj = $.ajaxSettings.xhr();

    if(xhrobj.upload){
      xhrobj.upload.addEventListener('progress', function(event) {
        var percent = 0;
        var position = event.loaded || event.position;
        var total = event.total || event.totalSize;
        if(event.lengthComputable){
          percent = Math.ceil(position / total * 100);
        }

        file.widget.settings.onUploadProgress.call(file.widget.element, file.id, file.data, percent);
      }, false);
    }

    return xhrobj;
  };

  DmUploaderFile.prototype.cancel = function()
  {
    switch (this.status){
      case FileStatus.PENDING:
        this.status = FileStatus.CANCELLED;
        break;
      case FileStatus.UPLOADING:
        this.status = FileStatus.CANCELLED;
        this.jqXHR.abort();
        break;
      default:
        return false;
    }

    return true;
  };

  DmUploaderFile.prototype.canUpload = function()
  {
    return (this.status == FileStatus.PENDING ||
      this.status == FileStatus.CANCELLED ||
      this.status == FileStatus.FAILED);
  };

  var DmUploader = function(element, options)
  {
    this.element = $(element);
    this.settings = $.extend({}, defaults, options);

    if (!this.checkSupport()) {
      $.error('Browser not supported by jQuery.dmUploader');

      this.settings.onFallbackMode.call(this.element);

      return false;
    }

    this.init();

    return this;
  };

  DmUploader.prototype.checkSupport = function()
  {
    // This one is mandatory for all modes
    if(typeof window.FormData === 'undefined'){
      return false;
    }

    // Test based on: Modernizr/feature-detects/forms/fileinput.js
    var exp = new RegExp(
      '/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|'+
      '(Windows Phone (OS 7|8.0))|(XBLWP)|'+
      '(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|'+
      '(Kindle\/(1.0|2.0|2.5|3.0))/');

    if (exp.test(window.navigator.userAgent)){
      return false;
    }

    return !$('<input type="file">').prop('disabled');
  };

  DmUploader.prototype.init = function()
  {
    var widget = this;

    // Queue vars
    this.queue = [];
    this.queuePos = -1;
    this.queueRunning = false;

    var input = widget.element.is("input[type=file]") ?
      widget.element : widget.element.find('input[type=file]');

    //-- Is the input our main element itself??
    if (input.length > 0){
      // Or does it has the input as a child
      input.on('change.' + pluginName, function(evt){
        var files = evt.target.files;

        widget.addFiles(files);

        $(this).val('');
      });
    }

    if (this.settings.dnd) {
      this.initDnD();
    }

    if (input.length == 0 && !this.settings.dnd){
      // Trigger an error because if this happens the plugin wont do anything.
      $.error('Markup error found by jQuery.dmUploader');

      return null;
    }

    // We good to go, tell them!
    this.settings.onInit.call(this.element);

    return this;
  };

  DmUploader.prototype.initDnD = function()
  {
    var widget = this;

    // -- Now our own Drop
    widget.element.on('drop.' + pluginName, function (evt){
      evt.stopPropagation();
      evt.preventDefault();

      var files = evt.originalEvent.dataTransfer.files;

      widget.addFiles(files);

      widget.settings.onDrop.call(this.element);
    });

    //-- These two events/callbacks are onlt to maybe do some fancy visual stuff
    widget.element.on('dragleave.' + pluginName, function(evt){
      widget.settings.onDragLeave.call(this.element);
    });

    widget.element.on('dragenter.' + pluginName, function(evt){
      widget.settings.onDragEnter.call(this.element);
    });

    // Disable some document events to prevent redirection
    $(document).off('dragover.' + pluginName).on('dragover.' + pluginName, function (e) {
      e.stopPropagation();
      e.preventDefault();
    });
    $(document).off('drop.' + pluginName).on('drop.' + pluginName, function (e) {
      e.stopPropagation();
      e.preventDefault();
    });
  };

  DmUploader.prototype.validateFile = function(file)
  {
    // Check file size
    if((this.settings.maxFileSize > 0) &&
        (file.size > this.settings.maxFileSize)){

      this.settings.onFileSizeError.call(this.element, file);

      return false;
    }

    // Check file type
    if((this.settings.allowedTypes != '*') &&
        !file.type.match(this.settings.allowedTypes)){

      this.settings.onFileTypeError.call(this.element, file);

      return false;
    }

    // Check file extension
    if(this.settings.extFilter !== null){
      var extList = this.settings.extFilter.toLowerCase().split(';');

      var ext = file.name.toLowerCase().split('.').pop();

      if($.inArray(ext, extList) < 0){
        this.settings.onFileExtError.call(this.element, file);

        return false;
      }
    }

    return new DmUploaderFile(file, this);
  };

  DmUploader.prototype.addFiles = function(files)
  {
    var nFiles = 0;

    for (var i= 0; i < files.length; i++)
    {
      var file = this.validateFile(files[i]);

      if (!file){
        continue;
      }

      // If the callback returns false file will not be processed. This may allow some customization
      var can_continue = this.settings.onNewFile.call(this.element, file.id, file.data);
      if (can_continue === false) {
        return;
      }

      // If we are using automatic uploading, and not a file queue: go for the upload
      if(this.settings.auto && !this.settings.queue){
        file.upload();
      }

      this.queue.push(file);

      nFiles++;
    }

    // No files were added
    if (nFiles === 0){
      return this;
    }

    // Are we auto-uploading files?
    if (this.settings.auto && this.settings.queue && !this.queueRunning) {
      this.processQueue();
    }

    return this;
  };

  DmUploader.prototype.processQueue = function()
  {
    this.queuePos++;

    if (this.queuePos >= this.queue.length){
      this.settings.onComplete.call(this.element);

      // Wait until new files are droped
      this.queuePos = (this.queue.length - 1);

      this.queueRunning = false;

      return false;
    }

    this.queueRunning = true;

    // Start next file
    return this.queue[this.queuePos].upload();
  };

  DmUploader.prototype.restartQueue = function()
  {
    this.queuePos = -1;
    this.queueRunning = false;

    this.processQueue();
  };

  DmUploader.prototype.findById = function(id)
  {
    var r = false;

    for (var i = 0; i < this.queue.length; i++){
      if (this.queue[i].id === id){
        r = this.queue[i];
        break;
      }
    }

    return r;
  };

  // Public API methods
  DmUploader.prototype.methods = {
    start: function(id) {
      if (this.queueRunning){
        // Do not allow to manually upload Files when a queue is running
        return false;
      }

      var file = false;

      if (typeof id !== 'undefined') {
        file = this.findById(id);

        if (!file){
          // File not found in stack
          return false;
        }
      }

      // Trying to Start an upload by ID
      if (file) {
        return file.upload();
      }

      // No id provided...
      if (this.settings.queue) {
        // Resume queue
        this.restartQueue();
      } else {
        // or upload them all
        for (var i = 0; i < this.queue.length; i++){
          this.queue[i].upload();
        }
      }

      return true;
    },
    cancel: function(id) {
      // todo: check auto/queue options

      // todo: check id is present

      return true;
    },
    reset: function() {
      return true;
    }
  };

  $.fn.dmUploader = function(options){
    var args = arguments;

    if (typeof options === 'string'){
      this.each(function(){
        var plugin = $.data(this, pluginName);

        if (plugin instanceof DmUploader){
          if (options === 'destroy'){
            if(plugin.methods.reset()){
              $.removeData(this, pluginName, null);
            }
          } else if (typeof plugin.methods[options] === 'function'){
            plugin.methods[options].apply(plugin, Array.prototype.slice.call(args, 1));
          } else {
            $.error('Method ' +  options + ' does not exist on jQuery.dmUploader');
          }
        } else {
          $.error('Unknown plugin data found by jQuery.dmUploader');
        }
      });
    } else {
      return this.each(function (){
        if(!$.data(this, pluginName)){
          $.data(this, pluginName, new DmUploader(this, options));
        }
      });
    }
  };
})(jQuery);<|endoftext|>


<commit_before>/*
 * Module.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
/** @namespace modules **/
define([
  '../sys',
  'EaselJS',
  './Component',
  '../data/Loader'
], function(
  sys,
  createjs,
  Component,
  Loader
) {
  var Parent = createjs.EventDispatcher;

  // Event constants

  var PRELOADED = 'preloaded';
  var INIT = 'init';
  var SHOW = 'show';
  var SHOWED = 'showed';
  var HIDE = 'hide';
  var HIDDEN = 'hidden';

  return sys.Class({
    __extends: Parent
  },
  /**
   * A module is a set of components

   * with a separate controller and view instance
   *
   * automatically calls init and dispose in child class

   * .hide and .show gets called automatically by TemplateEngine
   *
   * acts as a controller. automatically sets controller property in component view
   *
   * public functions that need to be overridden by child classes:
   *
   * * _this.init: binds events and initializes module construction
   * * _this.dispose: unbinds and destroys module
   * * _this.progress: called when a single file got preloaded (optional)
   * * _this.hide: hides module by animating elements out (optional)
   * * _this.show: shows module by animating elements in (optional)
   *
   * @class Module
   * @extends createjs.EventDispatcher
   * @memberof dom
   * @param {Class} ContainerClass class reference of a container or component that the module uses
   * @param {object} options to override in component
   **/
  function Module(ContainerClass, options){
    var _this = this;

    /**
     * container holder. keeps needed component class.

     * gets set automatically by using passed ContainerClass from constructor
     * @memberof dom.Module
     * @instance
     * @var {object} container
     */
    _this.container = _this.container || null;

    /**
     * preload element manifest. Has to be an array of objects containing id and src [{id: 'mydata', src: 'http:/file.png'}]
     * @see data.Loader
     * @memberof dom.Module
     * @instance
     * @var {object} preload
     */
    _this.preload = [];

    /**
     * data.Loader instance
     * @see data.Loader
     * @memberof dom.Module
     * @instance
     * @var {object} data
     */
    _this.data = null;

    /**
     * if Module was preloaded
     * @memberof dom.Module
     * @instance
     * @private
     * @var {Boolean} _preloaded
     */
    var _preloaded = false;

    // empty function that get used if child class has no functions
    var _noop = function(){};

    // set default show and hide behaviour to simply dispatch appropriate events
    // and call callbacks for use in template class
    // NOTE: if overriding hide or show module MUST call _this.hidden() and _this.show() at some point
    // otherwise template rendering functionality will be broken
    var _childFunctions = {};
    _childFunctions.init = _this.init || _noop;
    _childFunctions.dispose = _this.dispose || _noop;
    _childFunctions.progress = _this.progress || _noop;
    _childFunctions.hide = _this.hide;
    _childFunctions.show = _this.show;

    var Init = function(){
      Parent.call(_this);

      if (ContainerClass) {
        if (typeof ContainerClass === 'string') {
          _this.container = new Component(ContainerClass, null, options);
        } else {
          _this.container = new ContainerClass(options);
        }
      } else {
        _this.container = new Component('

', null, options);
      }

      _this.container.controller = _this;
      _this.container.addEventListener('addedToStage', _init);
    };

    /**
     * initializes module.
     * gets called from Module.js automatically.
     * override in child class to initialize module
     *
     * @method init
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.init = function(){
      _childFunctions.init();
      _this.dispatchEvent(INIT);
      _this.show();
    };

    /**
     * dispatches hidden event that gets used in TemplateEngine
     * if ovverriding hide() in child class: needs to call hidden() at some point
     *
     * @method hidden
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.hidden = function(){
      _this.destroy();
      _this.dispatchEvent(HIDDEN);
    };

    /**
     * dispatches showed event that gets used in TemplateEngine
     * if ovverriding show() in child class: needs to call showed() at some point
     *
     * @method showed
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.showed = function(){
      _this.dispatchEvent(SHOWED);
    };

    /**
     * dispatches hidden by default.
     * if overridden needs to do some sort of animation
     * and then call hidden manually
     *
     * @method hide
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.hide = function(){
      // disallow multiple hide calls by setting hide to noop when first called
      _this.hide = _noop;
      _childFunctions.hide();
      _this.dispatchEvent(HIDE);
    };

    /**
     * dispatches showed by default.
     * if overridden needs to do some sort of animation
     * and then call showed manually
     *
     * @method show
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.show = function(){
      // disallow multiple show calls by setting show to noop when first called
      _this.show = _noop;

      // hides component for a short duration before really showing element
      _this.container.el.style.visibility = 'hidden';

      // add a little timeout so components can resize and align properly
      _this.__doShowTimeout = setTimeout(_doShow, 50);
    };

    /**
     * cleans up some data
     *
     * @method destroy
     * @memberof dom.Module
     * @instance
     * @protected
     **/
    _this.destroy = function(){
      if (_this.data) {
        try {
          _this.data.reset();
        } catch(e){}

        _this.data.removeAll();
        _this.data.destroy();

        _this.data = null;

        if (_this.__doShowTimeout) clearTimeout(_this.__doShowTimeout);
      }
    };

    /**
     * calls show on child class
     * or simply dispatches showed event by default
     * if show is not overriden in child class
     *
     * @method _doShow
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _doShow = function(){
      // re-shows component
      _this.__doShowTimeout = null;
      _this.container.el.style.visibility = '';
      _childFunctions.show();
      _this.dispatchEvent(SHOW);
    };

    /**
     * dispatches preloaded event and initializes module
     *
     * @method _complete
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _complete = function(){
      if (_this.data) {
        _this.data.removeEventListener('cached', _progress);
        _this.data.removeEventListener('progress', _progress);
        _this.data.removeEventListener('fileload', _fileLoad);
        _this.data.removeEventListener('complete', _complete);
      }

      _preloaded = true;

      _this.dispatchEvent(PRELOADED);
      _this.init();
    };

    /**
     * dispatches progress event used in preload components
     *
     * @method _progress
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _progress = function(event){
      var tmp = new createjs.Event(event.type);
      tmp.progress = event.progress;
      _this.dispatchEvent(tmp);
    };

    /**
     * dispatches filelload event used in preload components
     * also calls .progress event on child module class to display element immediately if needed
     *
     * @method _fileLoad
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _fileLoad = function(event){
      _this.dispatchEvent(event);
      _childFunctions.progress(event.result);
    };

    /**
     * preloads data, shows and initializes module
     *
     * @method _init
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _init = function(){
      _this.container.removeEventListener('addedToStage', _init);
      _this.container.addEventListener('removedFromStage', _dispose);

      if (_this.preload.length > 0) {
        _this.data = new Loader();
        _this.data.addEventListener('cached', _progress);
        _this.data.addEventListener('progress', _progress);
        _this.data.addEventListener('fileload', _fileLoad);
        _this.data.addEventListener('complete', _complete);
        _this.data.load(_this.preload);
      } else {
        _complete();
      }
    };

    /**
     * calls dispose to unbind events on child class module
     *
     * @method _dispose
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _dispose = function(){
      _this.container.removeEventListener('removedFromStage', _dispose);
      if (_this.dispose && _preloaded) _this.dispose();
    };

    if (! _childFunctions.hide) _childFunctions.hide = _this.hidden;
    if (! _childFunctions.show) _childFunctions.show = _this.showed;

    Init();
  });
});
<commit_msg>optimize page transitions<commit_after>/*
 * Module.js
 * Fabian Irsara
 * Copyright 2015, Licensed GPL & MIT
 */
/** @namespace modules **/
define([
  '../sys',
  'EaselJS',
  './Component',
  '../data/Loader'
], function(
  sys,
  createjs,
  Component,
  Loader
) {
  var Parent = createjs.EventDispatcher;

  // Event constants

  var PRELOADED = 'preloaded';
  var INIT = 'init';
  var SHOW = 'show';
  var SHOWED = 'showed';
  var HIDE = 'hide';
  var HIDDEN = 'hidden';

  return sys.Class({
    __extends: Parent
  },
  /**
   * A module is a set of components

   * with a separate controller and view instance
   *
   * automatically calls init and dispose in child class

   * .hide and .show gets called automatically by TemplateEngine
   *
   * acts as a controller. automatically sets controller property in component view
   *
   * public functions that need to be overridden by child classes:
   *
   * * _this.init: binds events and initializes module construction
   * * _this.dispose: unbinds and destroys module
   * * _this.progress: called when a single file got preloaded (optional)
   * * _this.hide: hides module by animating elements out (optional)
   * * _this.show: shows module by animating elements in (optional)
   *
   * @class Module
   * @extends createjs.EventDispatcher
   * @memberof dom
   * @param {Class} ContainerClass class reference of a container or component that the module uses
   * @param {object} options to override in component
   **/
  function Module(ContainerClass, options){
    var _this = this;

    /**
     * container holder. keeps needed component class.

     * gets set automatically by using passed ContainerClass from constructor
     * @memberof dom.Module
     * @instance
     * @var {object} container
     */
    _this.container = _this.container || null;

    /**
     * preload element manifest. Has to be an array of objects containing id and src [{id: 'mydata', src: 'http:/file.png'}]
     * @see data.Loader
     * @memberof dom.Module
     * @instance
     * @var {object} preload
     */
    _this.preload = [];

    /**
     * data.Loader instance
     * @see data.Loader
     * @memberof dom.Module
     * @instance
     * @var {object} data
     */
    _this.data = null;

    /**
     * if Module was preloaded
     * @memberof dom.Module
     * @instance
     * @private
     * @var {Boolean} _preloaded
     */
    var _preloaded = false;

    // empty function that get used if child class has no functions
    var _noop = function(){};

    // set default show and hide behaviour to simply dispatch appropriate events
    // and call callbacks for use in template class
    // NOTE: if overriding hide or show module MUST call _this.hidden() and _this.show() at some point
    // otherwise template rendering functionality will be broken
    var _childFunctions = {};
    _childFunctions.init = _this.init || _noop;
    _childFunctions.dispose = _this.dispose || _noop;
    _childFunctions.progress = _this.progress || _noop;
    _childFunctions.hide = _this.hide;
    _childFunctions.show = _this.show;

    var Init = function(){
      Parent.call(_this);

      if (ContainerClass) {
        if (typeof ContainerClass === 'string') {
          _this.container = new Component(ContainerClass, null, options);
        } else {
          _this.container = new ContainerClass(options);
        }
      } else {
        _this.container = new Component('

', null, options);
      }

      _this.container.controller = _this;
      _this.container.addEventListener('addedToStage', _init);
    };

    /**
     * initializes module.
     * gets called from Module.js automatically.
     * override in child class to initialize module
     *
     * @method init
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.init = function(){
      _childFunctions.init();
      _this.dispatchEvent(INIT);
      _this.show();
    };

    /**
     * dispatches hidden event that gets used in TemplateEngine
     * if ovverriding hide() in child class: needs to call hidden() at some point
     *
     * @method hidden
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.hidden = function(){
      _this.destroy();
      _this.dispatchEvent(HIDDEN);
    };

    /**
     * dispatches showed event that gets used in TemplateEngine
     * if ovverriding show() in child class: needs to call showed() at some point
     *
     * @method showed
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.showed = function(){
      _this.dispatchEvent(SHOWED);
    };

    /**
     * dispatches hidden by default.
     * if overridden needs to do some sort of animation
     * and then call hidden manually
     *
     * @method hide
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.hide = function(){
      // disallow multiple hide calls by setting hide to noop when first called
      _this.hide = _noop;
      _childFunctions.hide();
      _this.dispatchEvent(HIDE);
    };

    /**
     * dispatches showed by default.
     * if overridden needs to do some sort of animation
     * and then call showed manually
     *
     * @method show
     * @memberof dom.Module
     * @instance
     * @public
     **/
    _this.show = function(){
      // disallow multiple show calls by setting show to noop when first called
      _this.show = _noop;

      if (_this.view) {
        // hides component for a short duration before really showing element
        _this.view.el.style.visibility = 'hidden';

        // add a little timeout so components can resize and align properly
        _this.__doShowTimeout = setTimeout(_doShow, 50);
      }

      _childFunctions.show();
      _this.dispatchEvent(SHOW);
    };

    /**
     * cleans up some data
     *
     * @method destroy
     * @memberof dom.Module
     * @instance
     * @protected
     **/
    _this.destroy = function(){
      if (_this.data) {
        try {
          _this.data.reset();
        } catch(e){}

        _this.data.removeAll();
        _this.data.destroy();

        _this.data = null;

        if (_this.__doShowTimeout) clearTimeout(_this.__doShowTimeout);
      }
    };

    /**
     * calls show on child class
     * or simply dispatches showed event by default
     * if show is not overriden in child class
     *
     * @method _doShow
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _doShow = function(){
      // re-shows component
      _this.__doShowTimeout = null;
      if (_this.view) _this.view.el.style.visibility = '';
    };

    /**
     * dispatches preloaded event and initializes module
     *
     * @method _complete
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _complete = function(){
      if (_this.data) {
        _this.data.removeEventListener('cached', _progress);
        _this.data.removeEventListener('progress', _progress);
        _this.data.removeEventListener('fileload', _fileLoad);
        _this.data.removeEventListener('complete', _complete);
      }

      _preloaded = true;

      _this.dispatchEvent(PRELOADED);
      _this.init();
    };

    /**
     * dispatches progress event used in preload components
     *
     * @method _progress
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _progress = function(event){
      var tmp = new createjs.Event(event.type);
      tmp.progress = event.progress;
      _this.dispatchEvent(tmp);
    };

    /**
     * dispatches filelload event used in preload components
     * also calls .progress event on child module class to display element immediately if needed
     *
     * @method _fileLoad
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _fileLoad = function(event){
      _this.dispatchEvent(event);
      _childFunctions.progress(event.result);
    };

    /**
     * preloads data, shows and initializes module
     *
     * @method _init
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _init = function(){
      _this.container.removeEventListener('addedToStage', _init);
      _this.container.addEventListener('removedFromStage', _dispose);

      if (_this.preload.length > 0) {
        _this.data = new Loader();
        _this.data.addEventListener('cached', _progress);
        _this.data.addEventListener('progress', _progress);
        _this.data.addEventListener('fileload', _fileLoad);
        _this.data.addEventListener('complete', _complete);
        _this.data.load(_this.preload);
      } else {
        _complete();
      }
    };

    /**
     * calls dispose to unbind events on child class module
     *
     * @method _dispose
     * @memberof dom.Module
     * @instance
     * @private
     **/
    var _dispose = function(){
      _this.container.removeEventListener('removedFromStage', _dispose);
      if (_this.dispose && _preloaded) _this.dispose();
    };

    if (! _childFunctions.hide) _childFunctions.hide = _this.hidden;
    if (! _childFunctions.show) _childFunctions.show = _this.showed;

    Init();
  });
});
<|endoftext|>


<commit_before>import { getIsGltf, getIsGlb } from './utils.js';

class gltfDragInput
{
    constructor()
    {
        this.onDropFiles = () => { };
    }

    setupGlobalInputBindings() { }

    setupCanvasInputBindings(canvas)
    {
        canvas.ondrop = this.dropHandler.bind(this);
        canvas.ondragover = this.dragOverHandler.bind(this);
    }

    dragOverHandler(event)
    {
        event.preventDefault();
    }

    dropHandler(event)
    {
        event.preventDefault();

        let additionalFiles = [];
        let mainFile;
        for (const file of event.dataTransfer.files)
        {
            if (getIsGltf(file.name) || getIsGlb(file.name))
            {
                mainFile = file;
            }
            else
            {
                additionalFiles.push(file);
            }
        }

        if (mainFile === undefined)
        {
            console.warn("No gltf/glb file found. Provided files: " + additionalFiles.map(f => f.name).join(", "));
            return;
        }

        this.onDropFiles(mainFile, additionalFiles);
    }
}

export { gltfDragInput };
<commit_msg>move to drag input<commit_after>import { getIsGltf, getIsGlb } from './utils.js';

class gltfDragInput
{
    constructor()
    {
        this.onDropFiles = () => { };
    }

    setupGlobalInputBindings() { }

    setupCanvasInputBindings(canvas)
    {
        canvas.ondrop = this.dropHandler.bind(this);
        canvas.ondragover = this.dragOverHandler.bind(this);
    }

    dragOverHandler(event)
    {
        event.preventDefault();
    }

    dropHandler(event)
    {
        event.preventDefault();

        let files = [];
        let folders = [];
        let droppedFiles = event.dataTransfer.files;
        let droppedItems = event.dataTransfer.items;

        for (let i = 0; i < droppedItems.length; i++)
        {
            let entry;
            if (droppedItems[i].getAsEntry)
            {
                entry = droppedItems[i].getAsEntry();
            }
            else if (droppedItems[i].webkitGetAsEntry)
            {
                entry = droppedItems[i].webkitGetAsEntry();
            }
            if (!entry)
            {
                files.push(droppedFiles[i]);
            }
            else
            {
                if (entry.isDirectory)
                {
                    folders.push(entry)
                }
                else
                {
                    files.push(droppedFiles[i]);
                }
            }
        }

        if (folders.length === 0)
        {
            this._processFiles(files)
        }
        else
        {
            let remaining = folders.length;
            for (let i = 0; i < folders.length; i++)
            {
                this._traverseFolder(folders[i], files, remaining, function(object)
                {
                    object._processFiles(files)
                })
            }
        }
    }

    _traverseFolder(folder, files, remaining, callback)
    {
        let self = this;
        let relativePath = folder.fullPath.replace(/^\//, "").replace(/(.+?)\/?$/, "$1/");
        let reader = folder.createReader();
        reader.readEntries(function(entries)
        {
            remaining += entries.length;
            for (let entry of entries)
            {
                if (entry.isFile)
                {
                    entry.file(function(file)
                    {
                        file.fullPath = relativePath + file.name
                        files.push(file);
                        if (--remaining === 0)
                        {
                            callback(self);
                        }
                    });
                }
                else if (entry.isDirectory)
                {
                    self._traverseFolder(entry, files, remaining, callback);
                }
            }
            if (--remaining === 0)
            {
                callback(self);
            }
        })
    }

    _processFiles(files)
    {
        let mainFile;
        let additionalFiles = [];
        for (let file of files)
        {
            if (getIsGltf(file.name) || getIsGlb(file.name))
            {
                mainFile = file;
            }
            else
            {
                additionalFiles.push(file);
            }
        }
        if (mainFile === undefined)
        {
            console.warn("No gltf/glb file found. Provided files: " + additionalFiles.map(f => f.name).join(", "));
            return;
        }
        this.onDropFiles(mainFile, additionalFiles);
    }
}

export { gltfDragInput };
<|endoftext|>


<commit_before>import {Plugin} from "prosemirror-state"
import {Decoration, DecorationSet} from "prosemirror-view"

const gecko = typeof navigator != "undefined" && /gecko\/\d/i.test(navigator.userAgent)
const linux = typeof navigator != "undefined" && /linux/i.test(navigator.platform)

export function dropCursor(options) {
  function dispatch(view, data) {
    view.dispatch(view.state.tr.setMeta(plugin, data))
  }

  let timeout = null
  function scheduleRemoval(view) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
    }, 1000)
  }

  let plugin = new Plugin({
    state: {
      init() { return null },
      apply(tr, prev, state) {
        // Firefox on Linux gets really confused an breaks dragging when we
        // mess with the nodes around the target node during a drag. So
        // disable this plugin there. See https://bugzilla.mozilla.org/show_bug.cgi?id=1323170
        if (gecko && linux) return null
        let command = tr.getMeta(plugin)
        if (!command) return prev
        if (command.type == "set") return pluginStateFor(state, command.pos, options)
        return null
      }
    },
    props: {
      handleDOMEvents: {
        dragover(view, event) {
          let active = plugin.getState(view.state)
          let pos = view.posAtCoords({left: event.clientX, top: event.clientY})
          if (pos) {
            let target = pos.pos
            if (view.dragging)
              target = dropPos(view.dragging.slice, view.state.doc.resolve(target))
            if (!active || active.pos != target)
              dispatch(view, {type: "set", pos: target})
          }
          scheduleRemoval(view)
          return false
        },

        dragend(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        drop(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        dragleave(view, event) {
          if (event.target == view.dom) dispatch(view, {type: "remove"})
          return false
        }
      },
      decorations(state) {
        let active = plugin.getState(state)
        return active && active.deco
      }
    }
  })
  return plugin
}

function style(options, side) {
  let width = (options && options.width) || 1
  let color = (options && options.color) || "black"
  return `border-${side}: ${width}px solid ${color}; margin-${side}: -${width}px`
}

function pluginStateFor(state, pos, options) {
  let $pos = state.doc.resolve(pos), deco
  if (!$pos.parent.inlineContent) {
    let before, after
    if (before = $pos.nodeBefore)
      deco = Decoration.node(pos - before.nodeSize, pos, {nodeName: "div", style: style(options, "right")})
    else if (after = $pos.nodeAfter)
      deco = Decoration.node(pos, pos + after.nodeSize, {nodeName: "div", style: style(options, "left")})
  }
  if (!deco) {
    let node = document.createElement("span")
    node.textContent = "\u200b"
    node.style.cssText = style(options, "left") + "; display: inline-block; pointer-events: none"
    deco = Decoration.widget(pos, node)
  }
  return {pos, deco: DecorationSet.create(state.doc, [deco])}
}

function dropPos(slice, $pos) {
  if (!slice || !slice.content.size) return $pos.pos
  let content = slice.content
  for (let i = 0; i < slice.openStart; i++) content = content.firstChild.content
  for (let d = $pos.depth; d >= 0; d--) {
    let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1
    let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0)
    if ($pos.node(d).canReplace(insertPos, insertPos, content))
      return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1)
  }
  return $pos.pos
}
<commit_msg>In dragleave handler, check if relatedTarget is outside of editor<commit_after>import {Plugin} from "prosemirror-state"
import {Decoration, DecorationSet} from "prosemirror-view"

const gecko = typeof navigator != "undefined" && /gecko\/\d/i.test(navigator.userAgent)
const linux = typeof navigator != "undefined" && /linux/i.test(navigator.platform)

export function dropCursor(options) {
  function dispatch(view, data) {
    view.dispatch(view.state.tr.setMeta(plugin, data))
  }

  let timeout = null
  function scheduleRemoval(view) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
    }, 1000)
  }

  let plugin = new Plugin({
    state: {
      init() { return null },
      apply(tr, prev, state) {
        // Firefox on Linux gets really confused an breaks dragging when we
        // mess with the nodes around the target node during a drag. So
        // disable this plugin there. See https://bugzilla.mozilla.org/show_bug.cgi?id=1323170
        if (gecko && linux) return null
        let command = tr.getMeta(plugin)
        if (!command) return prev
        if (command.type == "set") return pluginStateFor(state, command.pos, options)
        return null
      }
    },
    props: {
      handleDOMEvents: {
        dragover(view, event) {
          let active = plugin.getState(view.state)
          let pos = view.posAtCoords({left: event.clientX, top: event.clientY})
          if (pos) {
            let target = pos.pos
            if (view.dragging)
              target = dropPos(view.dragging.slice, view.state.doc.resolve(target))
            if (!active || active.pos != target)
              dispatch(view, {type: "set", pos: target})
          }
          scheduleRemoval(view)
          return false
        },

        dragend(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        drop(view) {
          if (plugin.getState(view.state)) dispatch(view, {type: "remove"})
          return false
        },

        dragleave(view, event) {
          if (event.target == view.dom || !view.dom.contains(event.relatedTarget)) dispatch(view, {type: "remove"})
          return false
        }
      },
      decorations(state) {
        let active = plugin.getState(state)
        return active && active.deco
      }
    }
  })
  return plugin
}

function style(options, side) {
  let width = (options && options.width) || 1
  let color = (options && options.color) || "black"
  return `border-${side}: ${width}px solid ${color}; margin-${side}: -${width}px`
}

function pluginStateFor(state, pos, options) {
  let $pos = state.doc.resolve(pos), deco
  if (!$pos.parent.inlineContent) {
    let before, after
    if (before = $pos.nodeBefore)
      deco = Decoration.node(pos - before.nodeSize, pos, {nodeName: "div", style: style(options, "right")})
    else if (after = $pos.nodeAfter)
      deco = Decoration.node(pos, pos + after.nodeSize, {nodeName: "div", style: style(options, "left")})
  }
  if (!deco) {
    let node = document.createElement("span")
    node.textContent = "\u200b"
    node.style.cssText = style(options, "left") + "; display: inline-block; pointer-events: none"
    deco = Decoration.widget(pos, node)
  }
  return {pos, deco: DecorationSet.create(state.doc, [deco])}
}

function dropPos(slice, $pos) {
  if (!slice || !slice.content.size) return $pos.pos
  let content = slice.content
  for (let i = 0; i < slice.openStart; i++) content = content.firstChild.content
  for (let d = $pos.depth; d >= 0; d--) {
    let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1
    let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0)
    if ($pos.node(d).canReplace(insertPos, insertPos, content))
      return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1)
  }
  return $pos.pos
}
<|endoftext|>


<commit_before>
// Stream object for reading off bytes from a byte array

function ByteStream(data){
	this.data = data;
	this.pos = 0;
}

// read the next byte off the stream
ByteStream.prototype.readByte = function(){
	return this.data[this.pos++];
};

// look at the next byte in the stream without updating the stream position
ByteStream.prototype.peekByte = function(){
	return this.data[this.pos];
};

// read an array of bytes
ByteStream.prototype.readBytes = function(n){
	var bytes = new Array(n);
	for(var i=0; i<n; i++){
		bytes[i] = this.readByte();
	}
	return bytes;
};

// peek at an array of bytes without updating the stream position
ByteStream.prototype.peekBytes = function(n){
	var bytes = new Array(n);
	for(var i=0; i<n; i++){
		bytes[i] = this.data[this.pos + i];
	}
	return bytes;
};

// read a string from a byte set
ByteStream.prototype.readString = function(len){
	var str = '';
	for(var i=0; i<len; i++){
		str += String.fromCharCode(this.readByte());
	}
	return str;
};

// read a single byte and return an array of bit booleans
ByteStream.prototype.readBitArray = function(){
	var arr = [];
	var bite = this.readByte();
	for (var i = 7; i >= 0; i--) {
		arr.push(!!(bite & (1 << i)));
	}
	return arr;
};

// read an unsigned int with endian option
ByteStream.prototype.readUnsigned = function(littleEndian){
	var a = this.readBytes(2);
	if(littleEndian){
		return (a[1] << 8) + a[0];
	}else{
		return (a[0] << 8) + a[1];
	}
};

module.exports = ByteStream;<commit_msg>Return TypedArrays sharing the original buffer when possible.<commit_after>
// Stream object for reading off bytes from a byte array

function ByteStream(data){
	this.data = data;
	this.pos = 0;
}

// read the next byte off the stream
ByteStream.prototype.readByte = function(){
	return this.data[this.pos++];
};

// look at the next byte in the stream without updating the stream position
ByteStream.prototype.peekByte = function(){
	return this.data[this.pos];
};

// read an array of bytes
ByteStream.prototype.readBytes = function(n){
	return this.data.subarray(this.pos, this.pos += n);
};

// peek at an array of bytes without updating the stream position
ByteStream.prototype.peekBytes = function(n){
	return this.data.subarray(this.pos, this.pos + n);
};

// read a string from a byte set
ByteStream.prototype.readString = function(len){
	var str = '';
	for(var i=0; i<len; i++){
		str += String.fromCharCode(this.readByte());
	}
	return str;
};

// read a single byte and return an array of bit booleans
ByteStream.prototype.readBitArray = function(){
	var arr = [];
	var bite = this.readByte();
	for (var i = 7; i >= 0; i--) {
		arr.push(!!(bite & (1 << i)));
	}
	return arr;
};

// read an unsigned int with endian option
ByteStream.prototype.readUnsigned = function(littleEndian){
	var a = this.readBytes(2);
	if(littleEndian){
		return (a[1] << 8) + a[0];
	}else{
		return (a[0] << 8) + a[1];
	}
};

module.exports = ByteStream;<|endoftext|>


<commit_before>import {contains, has} from 'ramda';

export function propTypeErrorHandler(e, props, type) {
    /*
     * propType error messages are constructed in
     * https://github.com/facebook/prop-types/blob/v15.7.2/factoryWithTypeCheckers.js
     * (Version 15.7.2)
     *
     * Parse these exception objects to remove JS source code and improve
     * the clarity.
     *
     * If wrong prop type was passed in, e.message looks like:
     *
     * Error: "Failed component prop type: Invalid component prop `animate` of type `number` supplied to `function GraphWithDefaults(props) {
     *   var id = props.id ? props.id : generateId();
     *   return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(PlotlyGraph, _extends({}, props, {
     *     id: id
     *   }));
     * }`, expected `boolean`."
     *
     *
     * If a required prop type was omitted, e.message looks like:
     *
     * "Failed component prop type: The component prop `options[0].value` is marked as required in `function Checklist(props) {
     *    var _this;
     *
     *    _classCallCheck(this, Checklist);
     *
     *     _this = _possibleConstructorReturn(this, _getPrototypeOf(Checklist).call(this, props));
     *     _this.state = {
     *       values: props.values
     *     };
     *     return _this;
     *   }`, but its value is `undefined`."
     *
     */

    const messageParts = e.message.split('`');
    let errorMessage;
    if (contains('is marked as required', e.message)) {

        const invalidPropPath = messageParts[1];
        errorMessage = `${invalidPropPath} in ${type}`;
        if (props.id) {
            errorMessage += ` with ID "${props.id}"`;
        }
        errorMessage += ` is required but it was not provided.`;

    } else if(contains(', expected ', e.message)) {

        const invalidPropPath = messageParts[1];
        const invalidPropTypeProvided = messageParts[3];
        const expectedPropType = e.message.split(', expected ')[1];

        errorMessage = `Invalid argument \`${invalidPropPath}\` passed into ${type}`;
        if (props.id) {
            errorMessage += ` with ID "${props.id}"`;
        }
        errorMessage += '.';

        errorMessage += (
            `\nExpected ${expectedPropType}` +
            `\nWas supplied type \`${invalidPropTypeProvided}\`.`
        );

        if (has(invalidPropPath, props)) {
            /*
             * invalidPropPath may be nested like `options[0].value`.
             * For now, we won't try to unpack these nested options
             * but we could in the future.
             */
            const jsonSuppliedValue = JSON.stringify(props[invalidPropPath], null, 2);
            if (jsonSuppliedValue) {
                if (contains('\n', jsonSuppliedValue)) {
                    errorMessage += `\nValue provided: \n${jsonSuppliedValue}`;
                } else {
                    errorMessage += `\nValue provided: ${jsonSuppliedValue}`;
                }
            }
        }

    } else {
        /*
         * Not aware of other prop type warning messages.
         * But, if they exist, then at least throw the default
         * react prop types error
         */
        throw e;
    }

    throw new Error(errorMessage);
}
<commit_msg>handle a few more error message types<commit_after>import {contains, has} from 'ramda';

export function propTypeErrorHandler(e, props, type) {
    /*
     * propType error messages are constructed in
     * https://github.com/facebook/prop-types/blob/v15.7.2/factoryWithTypeCheckers.js
     * (Version 15.7.2)
     *
     * Parse these exception objects to remove JS source code and improve
     * the clarity.
     *
     * If wrong prop type was passed in, e.message looks like:
     *
     * Error: "Failed component prop type: Invalid component prop `animate` of type `number` supplied to `function GraphWithDefaults(props) {
     *   var id = props.id ? props.id : generateId();
     *   return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(PlotlyGraph, _extends({}, props, {
     *     id: id
     *   }));
     * }`, expected `boolean`."
     *
     *
     * If a required prop type was omitted, e.message looks like:
     *
     * "Failed component prop type: The component prop `options[0].value` is marked as required in `function Checklist(props) {
     *    var _this;
     *
     *    _classCallCheck(this, Checklist);
     *
     *     _this = _possibleConstructorReturn(this, _getPrototypeOf(Checklist).call(this, props));
     *     _this.state = {
     *       values: props.values
     *     };
     *     return _this;
     *   }`, but its value is `undefined`."
     *
     */

    const messageParts = e.message.split('`');
    let errorMessage;
    if (contains('is marked as required', e.message)) {

        const invalidPropPath = messageParts[1];
        errorMessage = `${invalidPropPath} in ${type}`;
        if (props.id) {
            errorMessage += ` with ID "${props.id}"`;
        }
        errorMessage += ` is required but it was not provided.`;

    } else if(contains('Bad object', e.message)) {
        /*
         * Handle .exact errors
         * https://github.com/facebook/prop-types/blob/v15.7.2/factoryWithTypeCheckers.js#L438-L442
         */
        errorMessage = (
            e.message.split('supplied to ')[0] +
            `supplied to ${type}` +
            '.\nBad' +
            e.message.split('.\nBad')[1]
        );

    } else if(contains('Invalid ', e.message) && contains(' supplied to ', e.message)) {

        const invalidPropPath = messageParts[1];

        errorMessage = `Invalid argument \`${invalidPropPath}\` passed into ${type}`;
        if (props.id) {
            errorMessage += ` with ID "${props.id}"`;
        }
        errorMessage += '.';

        /*
         * Not all error messages include the expected value.
         * In particular, oneOfType.
         * https://github.com/facebook/prop-types/blob/v15.7.2/factoryWithTypeCheckers.js#L388
         */
        if (contains(', expected ', e.message)) {
            const expectedPropType = e.message.split(', expected ')[1];
            errorMessage += `\nExpected ${expectedPropType}`;
        }

        /*
         * Not all error messages include the type
         * In particular, oneOfType.
         * https://github.com/facebook/prop-types/blob/v15.7.2/factoryWithTypeCheckers.js#L388
         */
        if (contains(' of type `', e.message)) {
            const invalidPropTypeProvided = e.message.split(' of type `')[1].split('`')[0];
            errorMessage += (
                `\nWas supplied type \`${invalidPropTypeProvided}\`.`
            );
        }

        if (has(invalidPropPath, props)) {
            /*
             * invalidPropPath may be nested like `options[0].value`.
             * For now, we won't try to unpack these nested options
             * but we could in the future.
             */
            const jsonSuppliedValue = JSON.stringify(props[invalidPropPath], null, 2);
            if (jsonSuppliedValue) {
                if (contains('\n', jsonSuppliedValue)) {
                    errorMessage += `\nValue provided: \n${jsonSuppliedValue}`;
                } else {
                    errorMessage += `\nValue provided: ${jsonSuppliedValue}`;
                }
            }
        }

    } else {
        /*
         * Not aware of other prop type warning messages.
         * But, if they exist, then at least throw the default
         * react prop types error
         */
        throw e;
    }

    throw new Error(errorMessage);
}
<|endoftext|>


<commit_before>// =================================================================================================
// Fermat.js | Expressions
// ***EXPERIMENTAL ***
// (c) 2014 Mathigon / Philipp Legner
// =================================================================================================

(function() {

    M.expression = {};

    function handleBracket(brkt, x) {
        switch(x) {
            case '(': ++brkt['(']; break;
            case ')': --brkt['(']; break;
            case '[': ++brkt['[']; break;
            case ']': --brkt['[']; break;
            case '{': ++brkt['{']; break;
            case '}': --brkt['{']; break;
            case '|': brkt['|'] = 1-brkt['|']; break;
            case '"': brkt['"'] = 1-brkt['"']; break;
        }

        // TODO Error on negative brkt
        return brkt['('] + brkt['['] + brkt['{'] + brkt['|'] + brkt['"'];
    }

    function splitAt(array, del) {
        var result = [];
        var last = -1;
        for (var i=0; i<array.length; ++i) {
            if (array[i] === del) {
                result.push(array.slice(last+1, i));
                last = i;
            }
        }
        result.push(array.slice(last+1, i));
        return result;
    }

    // ---------------------------------------------------------------------------------------------
    // Expression Parsing

    // TODO Error Handling

    function parse(str, multiple) {

        var current = '';
        var result = [];
        var n = str.length;
        var isOpen = false;
        var openFn;
        var openBrk;

        function push(x) {
            if (!x) return;
            var num = +x;
            result.push(num === num ? num : x);
        }

        var brkt = { '(': 0, '[': 0, '{': 0, '|': 0, '"': 0 };

        for (var i=0; i<n; ++i) {

            var x = str[i];
            var wasOpen = isOpen;
            isOpen = handleBracket(brkt, x);

            // TODO fail on {}@&\?<>=~`±§

            if (('([{|"').contains(x) && !wasOpen) {
                if (x === '(' && +current !== +current && !('+-*/!^%,').contains(current)) {
                    openFn = current;
                } else {
                    push(current);
                }
                openBrk = x;
                current = '';
            } else if (isOpen) {
                current += x;
            } else if ((')]}|"').contains(x)) {
                if (current) {
                    if (openFn) {
                        result.push(Array.prototype.concat([openFn], parse(current, true)));
                    } else if (openBrk === '[') {
                        result.push(Array.prototype.concat(['[]'], parse(current, true)));
                    } else if (openBrk === '|') {
                        result.push(Array.prototype.concat(['abs'], parse(current, true)));
                    } else if (openBrk === '"') {
                        result.push(Array.prototype.concat(['"'], parse(current, true)));
                    } else {
                        result.push(parse(current));
                    }
                }
                openFn = openBrk = null;
                current = '';
            } else if (('+-*/!^%,').contains(x)) {
                if (x === ',' && !multiple) return ['Error: unexpected ",".'];
                push(current);
                if (x !== ',') result.push(x);
                current = '';
            } else if (x.match(/\s\n\t/)) {
                push(current);
                current = '';
            } else {
                current += x.trim();
            }
        }

        if (brkt['('] + brkt['['] + brkt['{'] + brkt['|'] + brkt['"'])
            return ['Error: non-matching brackets'];

        push(current);

        // Handle Factorials and Percentages
        for (i=0; i<result.length; ++i) {
            if (result[i] === '!') {
                result.splice(i-1, 2, ['!', result[i-1]]);
                i -= 1;
            } else if (result[i] === '%') {
                result.splice(i-1, 2, ['/', result[i-1], 100]);
                i -= 1;
            }
        }

        // Handle Powers
        for (i=0; i<result.length; ++i) {
            if (result[i] === '^') {
                result.splice(i-1, 3, ['^', result[i-1], result[i+1]]);
                i -= 2;
            }
        }

        // Handle Leading -
        if (result[0] === '-') result.splice(0, 2, ['-', result[1]]);

        // Handle Multiplication and Division
        for (i=0; i<result.length; ++i) {
            if (result[i] === '/') {
                result.splice(i-1, 3, ['/', result[i-1], result[i+1]]);
                i -= 2;
            } else if (result[i] === '*') {
                result.splice(i-1, 3, ['*', result[i-1], result[i+1]]);
                i -= 2;
            }
        }

        // Handle Addition and Subtraction
        for (i=0; i<result.length; ++i) {
            if (result[i] === '-') {
                result.splice(i-1, 3, ['-', result[i-1], result[i+1]]);
                i -= 2;
            } else if (result[i] === '+') {
                result.splice(i-1, 3, ['+', result[i-1], result[i+1]]);
                i -= 2;
            }
        }

        return result[0];
    }

    // ---------------------------------------------------------------------------------------------
    // Expression Simplify

    function simplify (expr, vars) {
        // TODO
    }

    // ---------------------------------------------------------------------------------------------
    // Expression to String

    function toString(expr) {
        // TODO
    }

    // ---------------------------------------------------------------------------------------------
    // Expression Evaluate

    // TODO Return expressions when undefined variables

    var fn = {
        '+': function(a, b) { return a + b; },
        '-': function(a, b) { return (b === undefined) ? -a : a - b; },
        '*': function(a, b) { return a * b; },
        '/': function(a, b) { return a / b; },
        '!': function(n) { return M.factorial(n); },
        '^': function(a, b) { return Math.pow(a, b); },
        '[]': function() { return arguments; },
        '"': function(str) { return '' + str; },
        'mod': function(a, b) { return M.mod(a, b); }
    };

    function evaluate(expr, vars) {

        // Individual Values
        if (M.isNumber(expr)) return expr;
        if (M.isString(expr)) return M.has(vars, expr) ? vars[expr] : expr;

        // Functions
        var args = [];
        for (var i=1; i<expr.length; ++i) args.push(evaluate(expr[i], vars));
        var f = fn[expr[0]] || Math[expr[0]] || M[expr[0]];
        return f.apply(null, args);
    }

    // ---------------------------------------------------------------------------------------------
    // Expression Evaluate

    M.Expression = function(str) {
        this._expr = parse(str);
    };

    M.Expression.prototype.toString = function() {
        return toString(this._expr);
    };

    M.Expression.prototype.simplify = function() {
        return toString(this._expr);
    };

    M.Expression.prototype.toString = function() {
        return toString(this._expr);
    };

})();
<commit_msg>Improved expression parsing and handling<commit_after>// =================================================================================================
// Fermat.js | Expressions
// ***EXPERIMENTAL ***
// (c) 2014 Mathigon / Philipp Legner
// =================================================================================================

(function() {

    // TODO fix parser errors + test
    // TODO + and * with multiple arguments
    // TODO Simplify expressions
    // TODO More error messages: 1(1), "str"(10), %(x)

    // ---------------------------------------------------------------------------------------------
    // Expression Parser

    var brackets = { '(': ')', '[': ']', '{': '}', '|': '|' };

    function bracketsMatch(a, b) {
        return brackets[a] === b || brackets[b] === a;
    }

    function ExpressionParser() {
        this.current = '';
        this.result = [];

        this.currentParser = null;
        this.currentBracket = null;
        this.currentFn = null;
    }

    // Pushes a new letter to the expression parser
    ExpressionParser.prototype.send = function(x) {

        // Handle Strings
        if (this.currentBracket === '"' && x !== '"') {
            this.current += x;

        // Closing Strings
        } else if (!this.currentBracket && x == '"') {
            this.pushCurrent();
            this.currentBracket = '"'

        // Opening Strings
        } else if (this.currentBracket === '"' && x == '"') {
            this.result.push(new Expression('"', [this.current]));

        // Handle Invalid Characters
        } else if (('@&\?<>=~`±§').contains(x)) {
            throw new Error('Unexpected "' + x + '".');

        // Handle Content for CHild parsers
        } else if (this.currentParser) {

            if ((')]}|').contains(x) && this.currentParser.isReady()) {

                if (!bracketsMatch(x,this.currentBracket))
                    throw new Error('Unexpected "' + x + '".');

                var completed = this.currentParser.complete();
                if (this.currentFn) {
                    this.result.push(new Expression(this.currentFn, completed));
                } else if (x === ']') {
                    this.result.push(new Expression('[]', completed));
                } else if (x === '|') {
                    this.result.push(new Expression('abs', completed));
                } else {
                    if (completed.length !== 1) throw new Error('Unexpected ",".');
                    this.result.push(new Expression(completed[i]));
                }
                this.current = '';
                this.currentBracket = this.currentParser = this.currentFn = null;

            } else {
                this.currentParser.send(x);
            }

        // Handle Open Brackets
        } else if (('([{|').contains(x)) {
            if (x === '(' && isNaN(+this.current) && !('+-*/!^%,').contains(this.current)) {
                this.currentFn = this.current;
                this.current = '';
            } else {
                this.pushCurrent();
            }
            this.currentParser = new ExpressionParser();
            this.currentBracket = x;

        } else if (('+-*/!^%,').contains(x)) {
            this.pushCurrent();
            if (x !== ',') this.result.push(x);

        } else if (x.match(/\s\n\t/)) {
            this.pushCurrent();

        } else {
            this.current += x.trim();
        }
    };

    ExpressionParser.prototype.isReady = function() {
        return this.currentParser == null;
    };

    // Adds a new letter, number or expression to the results object
    ExpressionParser.prototype.pushCurrent = function() {
        if (!this.current) return;
        var num = +this.current;
        this.result.push(new Expression(num === num ? num : this.current));
        this.current = '';
    };

    // Completes the expression and returns a new expression
    ExpressionParser.prototype.complete = function(x) {

        this.pushCurrent();

        // Handle Factorials and Percentages
        for (i=0; i<this.result.length; ++i) {
            if (this.result[i] === '!') {
                this.result.splice(i-1, 2, new Expression('!', [this.result[i-1]]));
                i -= 1;
            } else if (this.result[i] === '%') {
                this.result.splice(i-1, 2, new Expression('/', [this.result[i-1], 100]));
                i -= 1;
            }
        }

        // Handle Powers
        for (i=0; i<this.result.length; ++i) {
            if (this.result[i] === '^') {
                this.result.splice(i-1, 3, new Expression('^', [this.result[i-1], this.result[i+1]]));
                i -= 2;
            }
        }

        // Handle Leading -
        if (this.result[0] === '-') this.result.splice(0, 2, new Expression('-', [this.result[1]]));

        // Handle Multiplication and Division
        for (i=0; i<this.result.length; ++i) {
            if (this.result[i] === '/') {
                this.result.splice(i-1, 3, new Expression('/', [this.result[i-1], this.result[i+1]]));
                i -= 2;
            } else if (this.result[i] === '*') {
                this.result.splice(i-1, 3, new Expression('*', [this.result[i-1], this.result[i+1]]));
                i -= 2;
            }
        }

        // Handle Addition and Subtraction
        for (i=0; i<this.result.length; ++i) {
            if (this.result[i] === '-') {
                this.result.splice(i-1, 3, new Expression('-', [this.result[i-1], this.result[i+1]]));
                i -= 2;
            } else if (this.result[i] === '+') {
                this.result.splice(i-1, 3, new Expression('+', [this.result[i-1], this.result[i+1]]));
                i -= 2;
            }
        }

        return this.result;
    };

    // ---------------------------------------------------------------------------------------------
    // Expressions

    function Expression(fn, args) {
        if (arguments.length === 1) {
            this.isVal = true;
            this.val = fn;
        } else {
            this.fn = fn;
            this.args = args;
        }
    }

    Expression.prototype.simplify = function() {
        // TODO !!!
        return this;
    };

    Expression.prototype.toString = function() {
        if (this.isVal) return this.val.toString();

        var newArgs = [];
        for (var i=0; i<this.args.length; ++i) newArgs.push(this.args[i].toString());

        var fn = strings[this.fn];
        return fn ? fn.apply(null, args) : this.fn + '(' + this.args.join(', ') + ')';
    };

    Expression.prototype.evaluate = function(vars) {
        if (vars == null) vars = {};
        if (this.isVal) {
            console.log(this.val)
            return (vars[this.val] === undefined) ? this.val : vars[this.val];
        }

        var newArgs = [];
        for (var i=0; i<this.args.length; ++i) {
            var newArg = this.args[i].evaluate();
            if (newArg instanceof Expression) return this;
            newArgs.push(newArg);
        }

        var fn = vars[this.fn] || functions[this.fn] || Math[this.fn] || M[this.fn];
        console.log.apply(null, newArgs)
        return (fn instanceof Function) ? fn.apply(null, newArgs) : this;
    };

    // ---------------------------------------------------------------------------------------------
    // Expression Functions

    var functions = {
        '+': function(a, b) { return a + b; },
        '-': function(a, b) { return (b === undefined) ? -a : a - b; },
        '*': function(a, b) { return a * b; },
        '/': function(a, b) { return a / b; },
        '!': function(n) { return M.factorial(n); },
        '^': function(a, b) { return Math.pow(a, b); },
        '[]': function() { return arguments; },
        '"': function(str) { return '' + str; },
        'mod': function(a, b) { return M.mod(a, b); }
    };

    var strings = {
        '+': function() { return arguments.join(' + '); },
        '-': function(a, b) { return (b === undefined) ? '-' + a : a + ' - ' + b },
        '*': function() { return arguments.join(' * '); },
        '/': function(a, b) { return a + ' / ' + b; },
        '!': function(n) { return n + '!'; },
        '^': function(a, b) { return a + ' ^ ' + b; },
        '[]': function() { return '[' + arguments.join(', ') + ']'; },
        '"': function(str) { return '"' + str + '"'; },
        'mod': function(a, b) { return a + ' mod ' + b; }
    };

    // ---------------------------------------------------------------------------------------------
    // Public Interface

    M.expression = {};

    M.expression.parse = function(str) {

        var parser = new ExpressionParser();

        var n = str.length;
        for (var i=0; i<n; ++i) parser.send(str[i]);

        return parser.complete()[0].simplify();
    };

})();
<|endoftext|>


<commit_before>function formatter(format, parse) {return {'format':format, 'parse':parse || format};}
function toString(obj) {
  return (isDefined(obj) && obj !== _null) ? "" + obj : obj;
}

var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;

angularFormatter.noop = formatter(identity, identity);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.json
 *
 * @description
 *   Formats the user input as JSON text.
 *
 * @returns {?string} A JSON string representation of the model.
 *
 * @example
 *

 *   <input type="text" size='50' name="data" ng:format="json"/>
 *
data={{data}}

 *

 *
 * @scenario
 * it('should format json', function(){
 *   expect(binding('data')).toEqual('data={\n  \"name\":\"misko\",\n  \"project\":\"angular\"}');
 *   input('data').enter('{}');
 *   expect(binding('data')).toEqual('data={\n  }');
 * });
 */
angularFormatter.json = formatter(toJson, function(value){
  return fromJson(value || 'null');
});

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.boolean
 *
 * @description
 *   Use boolean formatter if you wish to store the data as boolean.
 *
 * @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
 *
 * @example
 * Enter truthy text:
 * <input type="text" name="value" ng:format="boolean" value="no"/>
 * <input type="checkbox" name="value"/>
 *
value={{value}}

 *
 * @scenario
 * it('should format boolean', function(){
 *   expect(binding('value')).toEqual('value=false');
 *   input('value').enter('truthy');
 *   expect(binding('value')).toEqual('value=true');
 * });
 */
angularFormatter['boolean'] = formatter(toString, toBoolean);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.number
 *
 * @description
 * Use number formatter if you wish to convert the user entered string to a number.
 *
 * @returns {number} Number from the parsed string.
 *
 * @example
 * Enter valid number:
 * <input type="text" name="value" ng:format="number" value="1234"/>
 *
value={{value}}

 *
 * @scenario
 * it('should format numbers', function(){
 *   expect(binding('value')).toEqual('value=1234');
 *   input('value').enter('5678');
 *   expect(binding('value')).toEqual('value=5678');
 * });
 */
angularFormatter.number = formatter(toString, function(obj){
  if (obj == _null || NUMBER.exec(obj)) {
    return obj===_null || obj === '' ? _null : 1*obj;
  } else {
    throw "Not a number";
  }
});

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.list
 *
 * @description
 * Use list formatter if you wish to convert the user entered string to an array.
 *
 * @returns {Array} Array parsed from the entered string.
 *
 * @example
 * Enter a list of items:
 * <input type="text" name="value" ng:format="list" value=" chair ,, table"/>
 * <input type="text" name="value" ng:format="list"/>
 *
value={{value}}

 *
 * @scenario
 * it('should format lists', function(){
 *   expect(binding('value')).toEqual('value=["chair","table"]');
 *   this.addFutureAction('change to XYZ', function($window, $document, done){
 *     $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change');
 *     done();
 *   });
 *   expect(binding('value')).toEqual('value=["a","b"]');
 * });
 */
angularFormatter.list = formatter(
  function(obj) { return obj ? obj.join(", ") : obj; },
  function(value) {
    var list = [];
    forEach((value || '').split(','), function(item){
      item = trim(item);
      if (item) list.push(item);
    });
    return list;
  }
);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.trim
 *
 * @description
 * Use trim formatter if you wish to trim extra spaces in user text.
 *
 * @returns {String} Trim excess leading and trailing space.
 *
 * @example
 * Enter text with leading/trailing spaces:
 * <input type="text" name="value" ng:format="trim" value="  book  "/>
 * <input type="text" name="value" ng:format="trim"/>
 *
value={{value|json}}

 *
 * @scenario
 * it('should format trim', function(){
 *   expect(binding('value')).toEqual('value="book"');
 *   this.addFutureAction('change to XYZ', function($window, $document, done){
 *     $document.elements('.doc-example :input:last').val('  text  ').trigger('change');
 *     done();
 *   });
 *   expect(binding('value')).toEqual('value="text"');
 * });
 */
angularFormatter.trim = formatter(
  function(obj) { return obj ? trim("" + obj) : ""; }
);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.index
 * @description
 * Index formatter is meant to be used with `select` input widget. It is useful when one needs
 * to select from a set of objects. To create pull-down one can iterate over the array of object
 * to build the UI. However  the value of the pull-down must be a string. This means that when on
 * object is selected form the pull-down, the pull-down value is a string which needs to be
 * converted back to an object. This conversion from string to on object is not possible, at best
 * the converted object is a copy of the original object. To solve this issue we create a pull-down
 * where the value strings are an index of the object in the array. When pull-down is selected the
 * index can be used to look up the original user object.
 *
 * @inputType select
 * @param {array} array to be used for selecting an object.
 * @returns {object} object which is located at the selected position.
 *
 * @example
 *
 *

 *   User:
 *   <select name="currentUser" ng:format="index:users">
 *     <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
 *   </select>
 *   <select name="currentUser" ng:format="index:users">
 *     <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
 *   </select>
 *   user={{currentUser.name}}

 *   password={{currentUser.password}}

 *

 *
 * @scenario
 * it('should format trim', function(){
 *   expect(binding('currentUser.password')).toEqual('guest');
 *   select('currentUser').option('2');
 *   expect(binding('currentUser.password')).toEqual('abc');
 * });
 */
angularFormatter.index = formatter(
  function(object, array){
    return '' + indexOf(array || [], object);
  },
  function(index, array){
    return (array||[])[index];
  }
);
<commit_msg>fix spec title for angular.formatter.index<commit_after>function formatter(format, parse) {return {'format':format, 'parse':parse || format};}
function toString(obj) {
  return (isDefined(obj) && obj !== _null) ? "" + obj : obj;
}

var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/;

angularFormatter.noop = formatter(identity, identity);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.json
 *
 * @description
 *   Formats the user input as JSON text.
 *
 * @returns {?string} A JSON string representation of the model.
 *
 * @example
 *

 *   <input type="text" size='50' name="data" ng:format="json"/>
 *
data={{data}}

 *

 *
 * @scenario
 * it('should format json', function(){
 *   expect(binding('data')).toEqual('data={\n  \"name\":\"misko\",\n  \"project\":\"angular\"}');
 *   input('data').enter('{}');
 *   expect(binding('data')).toEqual('data={\n  }');
 * });
 */
angularFormatter.json = formatter(toJson, function(value){
  return fromJson(value || 'null');
});

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.boolean
 *
 * @description
 *   Use boolean formatter if you wish to store the data as boolean.
 *
 * @returns {boolean} Converts to `true` unless user enters (blank), `f`, `false`, `0`, `no`, `[]`.
 *
 * @example
 * Enter truthy text:
 * <input type="text" name="value" ng:format="boolean" value="no"/>
 * <input type="checkbox" name="value"/>
 *
value={{value}}

 *
 * @scenario
 * it('should format boolean', function(){
 *   expect(binding('value')).toEqual('value=false');
 *   input('value').enter('truthy');
 *   expect(binding('value')).toEqual('value=true');
 * });
 */
angularFormatter['boolean'] = formatter(toString, toBoolean);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.number
 *
 * @description
 * Use number formatter if you wish to convert the user entered string to a number.
 *
 * @returns {number} Number from the parsed string.
 *
 * @example
 * Enter valid number:
 * <input type="text" name="value" ng:format="number" value="1234"/>
 *
value={{value}}

 *
 * @scenario
 * it('should format numbers', function(){
 *   expect(binding('value')).toEqual('value=1234');
 *   input('value').enter('5678');
 *   expect(binding('value')).toEqual('value=5678');
 * });
 */
angularFormatter.number = formatter(toString, function(obj){
  if (obj == _null || NUMBER.exec(obj)) {
    return obj===_null || obj === '' ? _null : 1*obj;
  } else {
    throw "Not a number";
  }
});

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.list
 *
 * @description
 * Use list formatter if you wish to convert the user entered string to an array.
 *
 * @returns {Array} Array parsed from the entered string.
 *
 * @example
 * Enter a list of items:
 * <input type="text" name="value" ng:format="list" value=" chair ,, table"/>
 * <input type="text" name="value" ng:format="list"/>
 *
value={{value}}

 *
 * @scenario
 * it('should format lists', function(){
 *   expect(binding('value')).toEqual('value=["chair","table"]');
 *   this.addFutureAction('change to XYZ', function($window, $document, done){
 *     $document.elements('.doc-example :input:last').val(',,a,b,').trigger('change');
 *     done();
 *   });
 *   expect(binding('value')).toEqual('value=["a","b"]');
 * });
 */
angularFormatter.list = formatter(
  function(obj) { return obj ? obj.join(", ") : obj; },
  function(value) {
    var list = [];
    forEach((value || '').split(','), function(item){
      item = trim(item);
      if (item) list.push(item);
    });
    return list;
  }
);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.trim
 *
 * @description
 * Use trim formatter if you wish to trim extra spaces in user text.
 *
 * @returns {String} Trim excess leading and trailing space.
 *
 * @example
 * Enter text with leading/trailing spaces:
 * <input type="text" name="value" ng:format="trim" value="  book  "/>
 * <input type="text" name="value" ng:format="trim"/>
 *
value={{value|json}}

 *
 * @scenario
 * it('should format trim', function(){
 *   expect(binding('value')).toEqual('value="book"');
 *   this.addFutureAction('change to XYZ', function($window, $document, done){
 *     $document.elements('.doc-example :input:last').val('  text  ').trigger('change');
 *     done();
 *   });
 *   expect(binding('value')).toEqual('value="text"');
 * });
 */
angularFormatter.trim = formatter(
  function(obj) { return obj ? trim("" + obj) : ""; }
);

/**
 * @workInProgress
 * @ngdoc formatter
 * @name angular.formatter.index
 * @description
 * Index formatter is meant to be used with `select` input widget. It is useful when one needs
 * to select from a set of objects. To create pull-down one can iterate over the array of object
 * to build the UI. However  the value of the pull-down must be a string. This means that when on
 * object is selected form the pull-down, the pull-down value is a string which needs to be
 * converted back to an object. This conversion from string to on object is not possible, at best
 * the converted object is a copy of the original object. To solve this issue we create a pull-down
 * where the value strings are an index of the object in the array. When pull-down is selected the
 * index can be used to look up the original user object.
 *
 * @inputType select
 * @param {array} array to be used for selecting an object.
 * @returns {object} object which is located at the selected position.
 *
 * @example
 *
 *

 *   User:
 *   <select name="currentUser" ng:format="index:users">
 *     <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
 *   </select>
 *   <select name="currentUser" ng:format="index:users">
 *     <option ng:repeat="user in users" value="{{$index}}">{{user.name}}</option>
 *   </select>
 *   user={{currentUser.name}}

 *   password={{currentUser.password}}

 *

 *
 * @scenario
 * it('should retrieve object by index', function(){
 *   expect(binding('currentUser.password')).toEqual('guest');
 *   select('currentUser').option('2');
 *   expect(binding('currentUser.password')).toEqual('abc');
 * });
 */
angularFormatter.index = formatter(
  function(object, array){
    return '' + indexOf(array || [], object);
  },
  function(index, array){
    return (array||[])[index];
  }
);
<|endoftext|>