define(['jquery', 'lodash', 'i18n', 'async', 'core/pluginifier', 'util/bytes', 'handlebars', 'lib/handlebars/helpers', 'ui/filesender', 'ui/progressbar'], function ($$1, _, __, async, Pluginifier, bytes, Handlebars, Helpers0, filesender, progressbar) { 'use strict';

    $$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
    _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
    __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
    async = async && Object.prototype.hasOwnProperty.call(async, 'default') ? async['default'] : async;
    Pluginifier = Pluginifier && Object.prototype.hasOwnProperty.call(Pluginifier, 'default') ? Pluginifier['default'] : Pluginifier;
    bytes = bytes && Object.prototype.hasOwnProperty.call(bytes, 'default') ? bytes['default'] : bytes;
    Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
    Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, options, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, functionType="function", self=this;

    function program1(depth0,data) {
      
      
      return "multiple";
      }

    function program3(depth0,data) {
      
      var buffer = "", helper, options;
      buffer += "\n            "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Drag files here", options) : helperMissing.call(depth0, "__", "Drag files here", options)))
        + "\n            ";
      return buffer;
      }

    function program5(depth0,data) {
      
      var buffer = "", helper, options;
      buffer += "\n            "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Drag file here", options) : helperMissing.call(depth0, "__", "Drag file here", options)))
        + "\n            ";
      return buffer;
      }

    function program7(depth0,data) {
      
      var buffer = "", helper, options;
      buffer += "\n    <button type=\"button\" class=\"btn-info btn-reset small\"><span class=\"icon-eraser\"></span>"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Reset", options) : helperMissing.call(depth0, "__", "Reset", options)))
        + "</button>\n    ";
      return buffer;
      }

    function program9(depth0,data) {
      
      var buffer = "", stack1, helper;
      buffer += "\n    <button class=\"btn-success btn-upload small\"><span class=\"icon-upload\"></span>";
      if (helper = helpers.uploadBtnText) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.uploadBtnText); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</button>\n    ";
      return buffer;
      }

      buffer += "<div class=\"file-upload grid-row\">\n    <span class=\"btn-info btn-browse small col-5\"><span class=\"icon-folder-open\"></span>"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Browse...", options) : helperMissing.call(depth0, "__", "Browse...", options)))
        + "</span>\n    <span class=\"file-name col-7 truncate placeholder\">"
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "No file selected", options) : helperMissing.call(depth0, "__", "No file selected", options)))
        + "</span>\n    <input type=\"file\" name=\"";
      if (helper = helpers.inputName) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.inputName); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\" ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.multiple), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += ">\n</div>\n<!--<div class=\"grid-row\" style=\"display:none;\">-->\n<!--<ul class=\"file-list\"></ul>-->\n<!--</div>-->\n<div class=\"grid-row\">\n    <div class=\"file-drop col-12\" data-drop-msg=\""
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Drop the files to upload", options) : helperMissing.call(depth0, "__", "Drop the files to upload", options)))
        + "\">\n        <div class=\"dragholder\">\n            ~ "
        + escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "or", options) : helperMissing.call(depth0, "__", "or", options)))
        + " ~\n            <br/>\n            ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.multiple), {hash:{},inverse:self.program(5, program5, data),fn:self.program(3, program3, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n        </div>\n    </div>\n</div>\n<div class=\"grid-row\">\n    <div class=\"progressbar col-12\"></div>\n    <br/>\n</div>\n<div class=\"grid-row\">\n    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.showResetButton), {hash:{},inverse:self.noop,fn:self.program(7, program7, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n    ";
      stack1 = helpers['if'].call(depth0, (depth0 && depth0.showUploadButton), {hash:{},inverse:self.noop,fn:self.program(9, program9, data),data:data});
      if(stack1 || stack1 === 0) { buffer += stack1; }
      buffer += "\n</div>";
      return buffer;
      });
    function uploaderTpl(data, options, asString) {
      var html = Template(data, options);
      return (asString || true) ? html : $(html);
    }

    if (!Helpers0.__initialized) {
      Helpers0(Handlebars);
      Helpers0.__initialized = true;
    }
    var Template$1 = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
      this.compilerInfo = [4,'>= 1.0.0'];
    helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
      var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression;


      buffer += "<li data-file-name=\"";
      if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\">\n    ";
      if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "\n    <span class=\"size\">";
      if (helper = helpers.size) { stack1 = helper.call(depth0, {hash:{},data:data}); }
      else { helper = (depth0 && depth0.size); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
      buffer += escapeExpression(stack1)
        + "</span>\n    <span class=\"status\"></span>\n    <a href=\"#\" data-role=\"delete\"><span class=\"icon-close\"></span></a>\n</li>\n";
      return buffer;
      });
    function fileEntryTpl(data, options, asString) {
      var html = Template$1(data, options);
      return (asString || true) ? html : $(html);
    }

    /**
     * @author Bertrand Chevrier <bertrand@taotesting.com>
     */
    const ns = 'uploader';
    const dataNs = 'ui.' + ns;

    //the plugin defaults
    const defaults = {
      upload: true,
      read: false,
      multiple: false,
      uploadQueueSize: 3,
      inputName: 'content',
      showResetButton: true,
      showUploadButton: true,
      browseBtnClass: 'btn-browse',
      uploadBtnClass: 'btn-upload',
      resetBtnClass: 'btn-reset',
      fileNameClass: 'file-name',
      dropZoneClass: 'file-drop',
      progressBarClass: 'progressbar',
      dragOverClass: 'drag-hover',
      formAttributes: {
        class: 'uploader uploaderContainer'
      },
      defaultErrMsg: __('Unable to upload file'),
      uploadBtnText: __('Upload'),
      /**
       * Make files available before file selection. It can be used to filter.
       * @callback fileSelect
       * @param {Array<File>} files - the selected files
       * @param {Function} [done] - callback with filtered files
       * @returns {undefined|Array<File>} the files to be selected
       */
      fileSelect: function (files, done) {
        if (_.isFunction(done)) {
          return done(files);
        }
        return files;
      }
    };

    //feature tests
    const tests = {
      filereader: typeof FileReader !== 'undefined',
      dnd: 'draggable' in document.createElement('span')
    };

    /**
     * Define a jQuery component to help you to manage file(s) upload/reading.
     * @exports ui/uploader
     */
    const uploader = {
      /**
       * Initialize the plugin.
       *
       * Called the jQuery way once registered by the Pluginifier.
       * @example $('selector').uploader();
       *
       * @constructor
       * @param {Object} [options] - the plugin options
       * @param {Boolean} [options.upload =  true] - if we upload the file once selected
       * @param {String} [options.uploadUrl] - the URL where the files will be posted
       * @param {jQueryElement} [options.$form] - a form to be used instead
       * @param {Boolean} [options.read =  false] - if we can read the file once selected
       * @param {Boolean} [options.multiple =  false] - enable to select more multiple files (may be not supported by old browsers)
       * @param {Number} [options.uploadQueueSize =  3] - max parallel uploads (applies only in multiple mode)
       * @param {String} [options.browseBtnClass = btn-browse] - the class to identify the browse button
       * @param {String} [options.uploadBtnClass = btn-upload] - the class to identify the upload button
       * @param {String} [options.resetBtnClass = btn-reset] - the class to identify the reset button
       * @param {String} [options.fileNameClass = file-name] - the class of the elt where the file name is set
       * @param {String} [options.dropZoneClass = file-drop] - the class of the drop file elt
       * @param {String} [options.progressBarClass = progressbar] - the class to identify the progress bar
       * @param {String} [options.dragOverClass = drag-hover] - the class to set to the drop zone when dragging over
       * @param {Function} [options.fileSelect] - called back before selection with files in params and returns the files to select; filter use case
       * @param {Object} [options.formAttributes] - object with all the attributes you want to be on the form element
       * @param {String} [options.defaultErrMsg] - localized error message when something goes wrong
       * @param {String} [options.uploadBtnText] - text on upload button
       * @returns {jQueryElement} for chainingV
       */
      init: function (options) {
        //get options using default
        options = _.defaults(options || {}, defaults);
        return this.each(function () {
          const $elt = $$1(this);
          let $builtInForm;
          if (!$elt.data(dataNs)) {
            $elt.html(uploaderTpl(options));

            // form could be inside $elt ...
            $builtInForm = options.$form && options.$form.length ? options.$form : $elt.find('form');

            // ... if not it could be a wrapper
            if (!$builtInForm.length) {
              $builtInForm = $elt.closest('form');
            }

            // ... if no form is present wrap $elt in one
            if (!$builtInForm.length) {
              $elt.wrap($$1('<form>', options.formAttributes));
              $builtInForm = $elt.parent();
            }

            //retrieve elements
            options.$input = $$1('input[type=file]', $elt);
            options.$browseBtn = $$1('.' + options.browseBtnClass, $elt);
            options.$fileName = $$1('.' + options.fileNameClass, $elt);
            options.$dropZone = $$1('.' + options.dropZoneClass, $elt);
            options.$progressBar = $$1('.' + options.progressBarClass, $elt);
            options.$form = $builtInForm;
            options.$uploadBtn = $$1('.' + options.uploadBtnClass, $elt);
            options.$resetBtn = $$1('.' + options.resetBtnClass, $elt);
            options.useDropZone = tests.dnd;
            options.dropZonePlaceholder = options.$dropZone.html();
            options.fileNamePlaceholder = options.$fileName.text();
            options.files = [];
            $elt.data(dataNs, options);
            uploader._reset($elt);
            const inputHandler = function (e) {
              // _.values also get the length property of the FileList object,
              // so we go for a plain old loop.
              const finalFiles = [];
              _.forEach(e.target.files, function (file) {
                finalFiles.push(file);
              });
              uploader._selectFiles($elt, finalFiles);
              options.$input.val('');
            };
            const dragOverHandler = function (e) {
              e.preventDefault();
              e.stopPropagation();
              options.$dropZone.addClass(options.dragOverClass);
            };
            const dragOutHandler = function (e) {
              e.preventDefault();
              e.stopPropagation();
              options.$dropZone.removeClass(options.dragOverClass);
            };

            //manage input selection
            if (!tests.filereader) {
              throw new Error('FileReader API not supported! Please use a compliant browser!');
            }
            options.$input.on('change', inputHandler);

            // IE Specific hack. It prevents the browseBtn to slightly
            // move on click. Special thanks to Dieter Raber, OAT S.A.
            options.$input.on('mousedown', function (e) {
              e.preventDefault();
              $$1(this).blur();
              return false;
            });

            //manage drag and drop selection
            if (options.useDropZone) {
              //prevent drag and drop outside the zone to loose the current context
              $$1(document).off('drop.' + ns).on('drop.' + ns, function (e) {
                e.stopImmediatePropagation();
                e.preventDefault();
                return false;
              });
              $$1(document).off('dragover.' + ns).on('dragover.' + ns, function (e) {
                e.stopImmediatePropagation();
                e.preventDefault();
                return false;
              });
              options.$dropZone.on('dragover', dragOverHandler).on('dragend', dragOutHandler).on('dragleave', dragOutHandler).on('drop', function (e) {
                let files = [];
                dragOutHandler(e);
                if (e.target.files) {
                  files = _.values(e.target.files);
                } else if (e.originalEvent.files) {
                  files = _.values(e.originalEvent.files);
                } else if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files) {
                  files = _.values(e.originalEvent.dataTransfer.files);
                }
                if (files && files.length) {
                  let append = options.$dropZone.children('ul').length > 0;
                  if (!options.multiple) {
                    files = [files[0]];
                    append = false;
                  }
                  uploader._selectFiles($elt, files, append);
                }
                return false;
              });
            } else {
              options.$dropZone.hide();
            }

            //getting files
            $elt.on('fileselect.' + ns, function () {
              if (options.files.length === 0) {
                uploader._reset($elt);
              }
              if (options.upload) {
                options.$uploadBtn.off('click').on('click', function (e) {
                  e.preventDefault();
                  uploader._upload($elt, options.files);
                }).removeProp('disabled');
              }
              if (options.read) {
                uploader._read($elt, options.files);
              }
              options.$resetBtn.off('click').on('click', function (e) {
                e.preventDefault();
                uploader._reset($elt);
              }).removeProp('disabled');
            });

            /**
             * The plugin has been created.
             * @event uploader#create.uploader
             */
            $elt.trigger('create.' + ns);
          }
        });
      },
      /**
       * Select files to upload/read.
       *
       * Called the jQuery way once registered by the Pluginifier:
       * @example $('selector').uploader('selectFiles', files);
       *
       * @param {jQueryElement} $elt - plugin's element
       * @param {Array<File>} files - the selected files
       * @param {Boolean} [append = false] - in append mode the files are added instead of replaced
       * @fires uploader#fileselect.uploader
       */
      _selectFiles: function _selectFiles($elt, files, append) {
        const self = this;
        let listContent;
        const options = $elt.data(dataNs);

        //update the file name field with the current number of files selected
        const updateFileName = function updateFileName() {
          const length = options.files.length;
          options.$fileName.text(length + ' ' + (length > 1 ? __('files selected') : __('file selected'))).removeClass('placeholder');
        };
        if (files.length <= 0 && !append) {
          //empty file list, so we reset the plugin
          self._reset($elt);
        }
        if (files.length > 0) {
          //execute the fileSelect function to filter files before selection
          options.fileSelect.call($elt, files, function (filteredFiles) {
            if (append) {
              options.files = options.files.concat(filteredFiles);
            } else {
              options.files = filteredFiles;
            }
            if (options.useDropZone) {
              updateFileName();
              listContent = _.reduce(filteredFiles, function (acc, file) {
                return acc + fileEntryTpl({
                  name: file.name,
                  size: bytes.hrSize(file.size)
                });
              }, '');
              if (append) {
                options.$dropZone.children('ul').append(listContent);
              } else {
                options.$dropZone.html('<ul>' + listContent + '</ul>');
              }
              options.$dropZone.off('click.' + ns).on('click.' + ns, '[data-role=delete]', function (e) {
                const $fileEntry = $$1(this).parent();
                const name = $fileEntry.data('file-name');
                e.preventDefault();
                e.stopPropagation();
                if (name) {
                  options.files = _.reject(options.files, {
                    name: name
                  });
                  if (options.files.length === 0) {
                    self._reset($elt);
                  } else {
                    updateFileName();
                  }
                  $fileEntry.remove();
                }
              });
            } else {
              //legacy mode, no dnd support
              options.files = options.files.slice(0, 1);
              options.$fileName.text(files[0].name).removeClass('placeholder');
            }

            /**
             * Files has been selected
             * @event uploader#fileselect.uploader
             */
            $elt.trigger('fileselect.' + ns);
          });
        }
      },
      /**
       * Get the selected files.
       *
       * Called the jQuery way once registered by the Pluginifier:
       * @example const files = $('selector').uploader('files');
       *
       * @param {jQueryElement} $elt - plugin's element
       * @returns {Array<File>} the selected files
       */
      _files: function ($elt) {
        let files = [];
        const options = $elt.data(dataNs);
        if (options) {
          files = options.files;
        }
        return files;
      },
      /**
       * Reset the component
       *
       * Called the jQuery way once registered by the Pluginifier:
       * @example $('selector').uploader('reset');
       *
       * @param {jQueryElement} $elt - plugin's element
       * @fires uploader#reset.uploader
       */
      _reset: function ($elt) {
        const options = $elt.data(dataNs);
        options.$fileName.text(options.fileNamePlaceholder).addClass('placeholder');
        options.$dropZone.empty().html(options.dropZonePlaceholder);
        options.$uploadBtn.prop('disabled', true);
        options.$resetBtn.prop('disabled', true);
        const importButton = options.$form[0].querySelector('button');
        if (importButton) {
          importButton.setAttribute('disabled', true);
        }
        if (options.$progressBar) {
          options.$progressBar.removeClass('success').progressbar('destroy').progressbar({
            value: 0
          });
        }
        /**
         * The plugin has been created.
         * @event uploader#reset.uploader
         */
        $elt.trigger('reset.' + ns);
      },
      /**
       * Upload the selected file
       *
       * Called the jQuery way once registered by the Pluginifier:
       * @example $('selector').uploader('upload');
       *
       * @param {jQueryElement} $elt - plugin's element
       * @fires uploader#upload.uploader
       * @fires uploader#fail.uploader
       * @fires uploader#end.uploader
       */
      _upload: function ($elt) {
        let length,
          $fileEntries,
          entryHeight,
          errors = [],
          q;
        const options = $elt.data(dataNs);
        if (options && options.files.length) {
          length = options.files.length;
          $fileEntries = $$1('ul', options.$dropZone);
          entryHeight = $$1('li:first', $fileEntries).outerHeight();

          //create an async queue to start uploads
          q = async.queue(function (file, done) {
            var $fileEntry = $$1('li[data-file-name="' + file.name + '"]', $fileEntries);
            var $status = $$1('.status', $fileEntry);
            var index = $fileEntries.children().index($fileEntry);

            //update the scroll into the element
            options.$dropZone.stop(true, true).animate({
              scrollTop: index * entryHeight
            }, 25);
            $status.removeClass('success').removeClass('error').addClass('sending');

            //send (upload) the file
            options.$form.sendfile({
              url: options.uploadUrl,
              file: file,
              loaded: function (result) {
                $status.removeClass('sending').removeClass('error').addClass('success');
                done(null, result);
              },
              failed: function (message) {
                message = message || options.defaultErrMsg;
                $status.removeClass('sending').removeClass('success').addClass('error').attr('title', message);
                done(new Error(message));
              }
            });
          }, options.uploadQueueSize || 1);

          //disable buttons
          options.$uploadBtn.prop('disabled', true);
          options.$resetBtn.prop('disabled', true);
          options.$progressBar.progressbar('value', 0);

          //start pushing uploads into the queue
          _.forEach(options.files, function (file, index) {
            _.delay(function () {
              q.push(file, function (err, result) {
                var complete = (index + 1) / length * 100;
                if (err) {
                  errors.push(err);

                  /**
                   * The file fails to upload
                   * @event uploader#fail.uploader
                   * @param {Object} file - the uploaded file
                   * @param {Object} err - the error
                   */
                  $elt.trigger('fail.' + ns, [file, err]);
                } else {
                  /**
                   * A file is uploaded
                   * @event uploader#upload.uploader
                   * @param {Object} file - the uploaded file
                   * @param {Object} result - the upload response
                   */
                  $elt.trigger('upload.' + ns, [file, result]);
                }

                //update progress bar regarding the number of files uploaded
                options.$progressBar.progressbar('value', complete);
                if (complete >= 100) {
                  if (errors.length === length) {
                    options.$progressBar.addClass('error');
                  } else if (errors.length > 0) {
                    options.$progressBar.addClass('warning');
                  } else {
                    options.$progressBar.addClass('success');
                  }

                  /**
                   * The upload sequence is complete
                   * @event uploader#end.uploader
                   */
                  $elt.trigger('end.' + ns);
                }
              });
            }, 50);
          });
        }
      },
      /**
       * Read the selected file.
       *
       * TODO update files status and progress bar by file
       *
       * Called the jQuery way once registered by the Pluginifier:
       * @example $('selector').uploader('read');
       *
       * @param {jQueryElement} $elt - plugin's element
       * @fires uploader#readstart.uploader
       * @fires uploader#readend.uploader
       */
      _read: function ($elt) {
        var options = $elt.data(dataNs);
        if (options && options.files.length) {
          _.forEach(options.files, function (file) {
            // Let's read the file to get its base64 encoded content.
            var reader = new FileReader();
            reader.onload = function (e) {
              options.$progressBar.progressbar('value', 100);

              /**
               * The read is fininshed
               * @event uploader#readend.uploader
               * @param {Object} file - the reading file
               * @param {Object} result - the content
               */
              $elt.trigger('readend.' + ns, [file, e.target.result]);
            };
            reader.onloadstart = function () {
              options.$progressBar.progressbar('value', 0);

              /**
               * The reading starts
               * @event uploader#readstart.uploader
               * @param {Object} file - the reading file
               */
              $elt.trigger('readstart.' + ns, [file]);
            };
            if (options.$progressBar.length) {
              reader.onprogress = function (e) {
                var percentProgress = Math.ceil(Math.round(e.loaded) / Math.round(e.total) * 100);
                options.$progressBar.progressbar('value', percentProgress);
              };
            }
            reader.readAsDataURL(file);
          });
        }
      },
      /**
       * Destroy completely the plugin.
       *
       * Called the jQuery way once registered by the Pluginifier:
       * @example $('selector').uploader('destroy');
       *
       * @fires uploader#destroy.uploader
       */
      destroy: function () {
        this.each(function () {
          var $elt = $$1(this);
          $$1(document).off('drop.' + ns).off('dragover.' + ns);
          $elt.empty();

          /**
           * The plugin has been destroyed.
           * @event uploader#destroy.uploader
           */
          $elt.trigger('destroy.' + ns);
        });
      }
    };

    //Register the incrementer to behave as a jQuery plugin.
    Pluginifier.register(ns, uploader, {
      expose: ['reset', 'selectFiles', 'upload', 'read']
    });

});
