From 70bcfb5b861894ecfc5df386921586e3b2ff2555 Mon Sep 17 00:00:00 2001 From: plegall Date: Tue, 27 May 2014 14:11:23 +0000 Subject: feature 2616: HTML5 upload (with plupload 2.1.2). First basic implementation. Needs customization. Chunked upload + Drag & drop (no more Flash) use a new specific API method pwg.images.upload git-svn-id: http://piwigo.org/svn/trunk@28545 68402e56-0260-453c-a942-63ccdbb3a9ee --- .../jquery.ui.plupload/jquery.ui.plupload.js | 1323 ++++++++++++++++++++ 1 file changed, 1323 insertions(+) create mode 100644 themes/default/js/plugins/plupload/jquery.ui.plupload/jquery.ui.plupload.js (limited to 'themes/default/js/plugins/plupload/jquery.ui.plupload/jquery.ui.plupload.js') diff --git a/themes/default/js/plugins/plupload/jquery.ui.plupload/jquery.ui.plupload.js b/themes/default/js/plugins/plupload/jquery.ui.plupload/jquery.ui.plupload.js new file mode 100644 index 000000000..af9193761 --- /dev/null +++ b/themes/default/js/plugins/plupload/jquery.ui.plupload/jquery.ui.plupload.js @@ -0,0 +1,1323 @@ +/** + * jquery.ui.plupload.js + * + * Copyright 2013, Moxiecode Systems AB + * Released under GPL License. + * + * License: http://www.plupload.com/license + * Contributing: http://www.plupload.com/contributing + * + * Depends: + * jquery.ui.core.js + * jquery.ui.widget.js + * jquery.ui.button.js + * jquery.ui.progressbar.js + * + * Optionally: + * jquery.ui.sortable.js + */ + + /* global jQuery:true */ + +/** +jQuery UI based implementation of the Plupload API - multi-runtime file uploading API. + +To use the widget you must include _jQuery_ and _jQuery UI_ bundle (including `ui.core`, `ui.widget`, `ui.button`, +`ui.progressbar` and `ui.sortable`). + +In general the widget is designed the way that you do not usually need to do anything to it after you instantiate it. +But! You still can intervenue, to some extent, in case you need to. Although, due to the fact that widget is based on +_jQuery UI_ widget factory, there are some specifics. See examples below for more details. + +@example + +
+

Your browser doesn't have Flash, Silverlight or HTML5 support.

+
+ + + +@example + // Invoking methods: + $('#uploader').plupload(options); + + // Display welcome message in the notification area + $('#uploader').plupload('notify', 'info', "This might be obvious, but you need to click 'Add Files' to add some files."); + +@example + // Subscribing to the events... + // ... on initialization: + $('#uploader').plupload({ + ... + viewchanged: function(event, args) { + // stuff ... + } + }); + // ... or after initialization + $('#uploader').on("viewchanged", function(event, args) { + // stuff ... + }); + +@class UI.Plupload +@constructor +@param {Object} settings For detailed information about each option check documentation. + @param {String} settings.url URL of the server-side upload handler. + @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled. + @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message. + @param {Object} [settings.filters={}] Set of file type filters. + @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR` + @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`. + @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`. + @param {Number} [settings.filters.max_file_count=0] Limit the number of files that can reside in the queue at the same time (default is 0 - no limit). + @param {String} [settings.flash_swf_url] URL of the Flash swf. + @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs. + @param {Number|String} [settings.max_file_size] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`. + @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event. + @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message. + @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload. + @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog. + @param {Boolean} [settings.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`. + @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess. + @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}` + @param {Number} [settings.resize.width] If image is bigger, it will be resized. + @param {Number} [settings.resize.height] If image is bigger, it will be resized. + @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100). + @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally. + @param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails. + @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap. + @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files. + + @param {Boolean} [settings.autostart=false] Whether to auto start uploading right after file selection. + @param {Boolean} [settings.dragdrop=true] Enable ability to add file to the queue by drag'n'dropping them from the desktop. + @param {Boolean} [settings.rename=false] Enable ability to rename files in the queue. + @param {Boolean} [settings.sortable=false] Enable ability to sort files in the queue, changing their uploading priority. + @param {Object} [settings.buttons] Control the visibility of functional buttons. + @param {Boolean} [settings.buttons.browse=true] Display browse button. + @param {Boolean} [settings.buttons.start=true] Display start button. + @param {Boolean} [settings.buttons.stop=true] Display stop button. + @param {Object} [settings.views] Control various views of the file queue. + @param {Boolean} [settings.views.list=true] Enable list view. + @param {Boolean} [settings.views.thumbs=false] Enable thumbs view. + @param {String} [settings.views.default='list'] Default view. + @param {Boolean} [settings.views.remember=true] Whether to remember the current view (requires jQuery Cookie plugin). + @param {Boolean} [settings.multiple_queues=true] Re-activate the widget after each upload procedure. +*/ +;(function(window, document, plupload, o, $) { + +/** +Dispatched when the widget is initialized and ready. + +@event ready +@param {plupload.Uploader} uploader Uploader instance sending the event. +*/ + +/** +Dispatched when file dialog is closed. + +@event selected +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {Array} files Array of selected files represented by plupload.File objects +*/ + +/** +Dispatched when file dialog is closed. + +@event removed +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {Array} files Array of removed files represented by plupload.File objects +*/ + +/** +Dispatched when upload is started. + +@event start +@param {plupload.Uploader} uploader Uploader instance sending the event. +*/ + +/** +Dispatched when upload is stopped. + +@event stop +@param {plupload.Uploader} uploader Uploader instance sending the event. +*/ + +/** +Dispatched during the upload process. + +@event progress +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {plupload.File} file File that is being uploaded (includes loaded and percent properties among others). + @param {Number} size Total file size in bytes. + @param {Number} loaded Number of bytes uploaded of the files total size. + @param {Number} percent Number of percentage uploaded of the file. +*/ + +/** +Dispatched when file is uploaded. + +@event uploaded +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {plupload.File} file File that was uploaded. + @param {Enum} status Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE. +*/ + +/** +Dispatched when upload of the whole queue is complete. + +@event complete +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {Array} files Array of uploaded files represented by plupload.File objects +*/ + +/** +Dispatched when the view is changed, e.g. from `list` to `thumbs` or vice versa. + +@event viewchanged +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {String} type Current view type. +*/ + +/** +Dispatched when error of some kind is detected. + +@event error +@param {plupload.Uploader} uploader Uploader instance sending the event. +@param {String} error Error message. +@param {plupload.File} file File that was uploaded. + @param {Enum} status Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE. +*/ + +var uploaders = {}; + +function _(str) { + return plupload.translate(str) || str; +} + +function renderUI(obj) { + obj.id = obj.attr('id'); + + obj.html( + '
' + + '
' + + '
' + + '
' + + '' + + '
' + _("Select files") + '
' + + '
' + _("Add files to the upload queue and click the start button.") + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + _('Filename') + '' + _('Status') + '' + _('Size') + ' 
' + + + '
' + + '
' + _("Drag files here.") + '
' + + '
' + + '
 
' + + '
' + + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + + '
' + + '' + + '
' + ); +} + + +$.widget("ui.plupload", { + + widgetEventPrefix: '', + + contents_bak: '', + + options: { + browse_button_hover: 'ui-state-hover', + browse_button_active: 'ui-state-active', + + filters: {}, + + // widget specific + buttons: { + browse: true, + start: true, + stop: true + }, + + views: { + list: true, + thumbs: false, + active: 'list', + remember: true // requires: https://github.com/carhartl/jquery-cookie, otherwise disabled even if set to true + }, + + thumb_width: 100, + thumb_height: 60, + + multiple_queues: true, // re-use widget by default + dragdrop : true, + autostart: false, + sortable: false, + rename: false + }, + + FILE_COUNT_ERROR: -9001, + + _create: function() { + var id = this.element.attr('id'); + if (!id) { + id = plupload.guid(); + this.element.attr('id', id); + } + this.id = id; + + // backup the elements initial state + this.contents_bak = this.element.html(); + renderUI(this.element); + + // container, just in case + this.container = $('.plupload_container', this.element).attr('id', id + '_container'); + + this.content = $('.plupload_content', this.element); + + if ($.fn.resizable) { + this.container.resizable({ + handles: 's', + minHeight: 300 + }); + } + + // list of files, may become sortable + this.filelist = $('.plupload_filelist_content', this.container) + .attr({ + id: id + '_filelist', + unselectable: 'on' + }); + + + // buttons + this.browse_button = $('.plupload_add', this.container).attr('id', id + '_browse'); + this.start_button = $('.plupload_start', this.container).attr('id', id + '_start'); + this.stop_button = $('.plupload_stop', this.container).attr('id', id + '_stop'); + this.thumbs_switcher = $('#' + id + '_view_thumbs'); + this.list_switcher = $('#' + id + '_view_list'); + + if ($.ui.button) { + this.browse_button.button({ + icons: { primary: 'ui-icon-circle-plus' }, + disabled: true + }); + + this.start_button.button({ + icons: { primary: 'ui-icon-circle-arrow-e' }, + disabled: true + }); + + this.stop_button.button({ + icons: { primary: 'ui-icon-circle-close' } + }); + + this.list_switcher.button({ + text: false, + icons: { secondary: "ui-icon-grip-dotted-horizontal" } + }); + + this.thumbs_switcher.button({ + text: false, + icons: { secondary: "ui-icon-image" } + }); + } + + // progressbar + this.progressbar = $('.plupload_progress_container', this.container); + + if ($.ui.progressbar) { + this.progressbar.progressbar(); + } + + // counter + this.counter = $('.plupload_count', this.element) + .attr({ + id: id + '_count', + name: id + '_count' + }); + + // initialize uploader instance + this._initUploader(); + }, + + _initUploader: function() { + var self = this + , id = this.id + , uploader + , options = { + container: id + '_buttons', + browse_button: id + '_browse' + } + ; + + $('.plupload_buttons', this.element).attr('id', id + '_buttons'); + + if (self.options.dragdrop) { + this.filelist.parent().attr('id', this.id + '_dropbox'); + options.drop_element = this.id + '_dropbox'; + } + + this.filelist.on('click', function(e) { + if ($(e.target).hasClass('plupload_action_icon')) { + self.removeFile($(e.target).closest('.plupload_file').attr('id')); + e.preventDefault(); + } + }); + + uploader = this.uploader = uploaders[id] = new plupload.Uploader($.extend(this.options, options)); + + if (self.options.views.thumbs) { + uploader.settings.required_features.display_media = true; + } + + // for backward compatibility + if (self.options.max_file_count) { + plupload.extend(uploader.getOption('filters'), { + max_file_count: self.options.max_file_count + }); + } + + plupload.addFileFilter('max_file_count', function(maxCount, file, cb) { + if (maxCount <= this.files.length - (this.total.uploaded + this.total.failed)) { + self.browse_button.button('disable'); + this.disableBrowse(); + + this.trigger('Error', { + code : self.FILE_COUNT_ERROR, + message : _("File count error."), + file : file + }); + cb(false); + } else { + cb(true); + } + }); + + + uploader.bind('Error', function(up, err) { + var message, details = ""; + + message = '' + err.message + ''; + + switch (err.code) { + case plupload.FILE_EXTENSION_ERROR: + details = o.sprintf(_("File: %s"), err.file.name); + break; + + case plupload.FILE_SIZE_ERROR: + details = o.sprintf(_("File: %s, size: %d, max file size: %d"), err.file.name, plupload.formatSize(err.file.size), plupload.formatSize(plupload.parseSize(up.getOption('filters').max_file_size))); + break; + + case plupload.FILE_DUPLICATE_ERROR: + details = o.sprintf(_("%s already present in the queue."), err.file.name); + break; + + case self.FILE_COUNT_ERROR: + details = o.sprintf(_("Upload element accepts only %d file(s) at a time. Extra files were stripped."), up.getOption('filters').max_file_count || 0); + break; + + case plupload.IMAGE_FORMAT_ERROR : + details = _("Image format either wrong or not supported."); + break; + + case plupload.IMAGE_MEMORY_ERROR : + details = _("Runtime ran out of available memory."); + break; + + /* // This needs a review + case plupload.IMAGE_DIMENSIONS_ERROR : + details = o.sprintf(_('Resoultion out of boundaries! %s runtime supports images only up to %wx%hpx.'), up.runtime, up.features.maxWidth, up.features.maxHeight); + break; */ + + case plupload.HTTP_ERROR: + details = _("Upload URL might be wrong or doesn't exist."); + break; + } + + message += "
" + details + ""; + + self._trigger('error', null, { up: up, error: err } ); + + // do not show UI if no runtime can be initialized + if (err.code === plupload.INIT_ERROR) { + setTimeout(function() { + self.destroy(); + }, 1); + } else { + self.notify('error', message); + } + }); + + + uploader.bind('PostInit', function(up) { + // all buttons are optional, so they can be disabled and hidden + if (!self.options.buttons.browse) { + self.browse_button.button('disable').hide(); + up.disableBrowse(true); + } else { + self.browse_button.button('enable'); + } + + if (!self.options.buttons.start) { + self.start_button.button('disable').hide(); + } + + if (!self.options.buttons.stop) { + self.stop_button.button('disable').hide(); + } + + if (!self.options.unique_names && self.options.rename) { + self._enableRenaming(); + } + + if (self.options.dragdrop && up.features.dragdrop) { + self.filelist.parent().addClass('plupload_dropbox'); + } + + self._enableViewSwitcher(); + + self.start_button.click(function(e) { + if (!$(this).button('option', 'disabled')) { + self.start(); + } + e.preventDefault(); + }); + + self.stop_button.click(function(e) { + self.stop(); + e.preventDefault(); + }); + + self._trigger('ready', null, { up: up }); + }); + + // uploader internal events must run first + uploader.init(); + + uploader.bind('FileFiltered', function(up, file) { + self._addFiles(file); + }); + + uploader.bind('FilesAdded', function(up, files) { + self._trigger('selected', null, { up: up, files: files } ); + + // re-enable sortable + if (self.options.sortable && $.ui.sortable) { + self._enableSortingList(); + } + + self._trigger('updatelist', null, { filelist: self.filelist }); + + if (self.options.autostart) { + // set a little delay to make sure that QueueChanged triggered by the core has time to complete + setTimeout(function() { + self.start(); + }, 10); + } + }); + + uploader.bind('FilesRemoved', function(up, files) { + // destroy sortable if enabled + if ($.ui.sortable && self.options.sortable) { + $('tbody', self.filelist).sortable('destroy'); + } + + $.each(files, function(i, file) { + $('#' + file.id).toggle("highlight", function() { + $(this).remove(); + }); + }); + + if (up.files.length) { + // re-initialize sortable + if (self.options.sortable && $.ui.sortable) { + self._enableSortingList(); + } + } + + self._trigger('updatelist', null, { filelist: self.filelist }); + self._trigger('removed', null, { up: up, files: files } ); + }); + + uploader.bind('QueueChanged StateChanged', function() { + self._handleState(); + }); + + uploader.bind('UploadFile', function(up, file) { + self._handleFileStatus(file); + }); + + uploader.bind('FileUploaded', function(up, file) { + self._handleFileStatus(file); + self._trigger('uploaded', null, { up: up, file: file } ); + }); + + uploader.bind('UploadProgress', function(up, file) { + self._handleFileStatus(file); + self._updateTotalProgress(); + self._trigger('progress', null, { up: up, file: file } ); + }); + + uploader.bind('UploadComplete', function(up, files) { + self._addFormFields(); + self._trigger('complete', null, { up: up, files: files } ); + }); + }, + + + _setOption: function(key, value) { + var self = this; + + if (key == 'buttons' && typeof(value) == 'object') { + value = $.extend(self.options.buttons, value); + + if (!value.browse) { + self.browse_button.button('disable').hide(); + self.uploader.disableBrowse(true); + } else { + self.browse_button.button('enable').show(); + self.uploader.disableBrowse(false); + } + + if (!value.start) { + self.start_button.button('disable').hide(); + } else { + self.start_button.button('enable').show(); + } + + if (!value.stop) { + self.stop_button.button('disable').hide(); + } else { + self.start_button.button('enable').show(); + } + } + + self.uploader.settings[key] = value; + }, + + + /** + Start upload. Triggers `start` event. + + @method start + */ + start: function() { + this.uploader.start(); + this._trigger('start', null, { up: this.uploader }); + }, + + + /** + Stop upload. Triggers `stop` event. + + @method stop + */ + stop: function() { + this.uploader.stop(); + this._trigger('stop', null, { up: this.uploader }); + }, + + + /** + Enable browse button. + + @method enable + */ + enable: function() { + this.browse_button.button('enable'); + this.uploader.disableBrowse(false); + }, + + + /** + Disable browse button. + + @method disable + */ + disable: function() { + this.browse_button.button('disable'); + this.uploader.disableBrowse(true); + }, + + + /** + Retrieve file by it's unique id. + + @method getFile + @param {String} id Unique id of the file + @return {plupload.File} + */ + getFile: function(id) { + var file; + + if (typeof id === 'number') { + file = this.uploader.files[id]; + } else { + file = this.uploader.getFile(id); + } + return file; + }, + + /** + Return array of files currently in the queue. + + @method getFiles + @return {Array} Array of files in the queue represented by plupload.File objects + */ + getFiles: function() { + return this.uploader.files; + }, + + + /** + Remove the file from the queue. + + @method removeFile + @param {plupload.File|String} file File to remove, might be specified directly or by it's unique id + */ + removeFile: function(file) { + if (plupload.typeOf(file) === 'string') { + file = this.getFile(file); + } + this.uploader.removeFile(file); + }, + + + /** + Clear the file queue. + + @method clearQueue + */ + clearQueue: function() { + this.uploader.splice(); + }, + + + /** + Retrieve internal plupload.Uploader object (usually not required). + + @method getUploader + @return {plupload.Uploader} + */ + getUploader: function() { + return this.uploader; + }, + + + /** + Trigger refresh procedure, specifically browse_button re-measure and re-position operations. + Might get handy, when UI Widget is placed within the popup, that is constantly hidden and shown + again - without calling this method after each show operation, dialog trigger might get displaced + and disfunctional. + + @method refresh + */ + refresh: function() { + this.uploader.refresh(); + }, + + + /** + Display a message in notification area. + + @method notify + @param {Enum} type Type of the message, either `error` or `info` + @param {String} message The text message to display. + */ + notify: function(type, message) { + var popup = $( + '
' + + '' + + '

' + message + '

' + + '
' + ); + + popup + .addClass('ui-state-' + (type === 'error' ? 'error' : 'highlight')) + .find('p .ui-icon') + .addClass('ui-icon-' + (type === 'error' ? 'alert' : 'info')) + .end() + .find('.plupload_message_close') + .click(function() { + popup.remove(); + }) + .end(); + + $('.plupload_header', this.container).append(popup); + }, + + + /** + Destroy the widget, the uploader, free associated resources and bring back original html. + + @method destroy + */ + destroy: function() { + // destroy uploader instance + this.uploader.destroy(); + + // unbind all button events + $('.plupload_button', this.element).unbind(); + + // destroy buttons + if ($.ui.button) { + $('.plupload_add, .plupload_start, .plupload_stop', this.container) + .button('destroy'); + } + + // destroy progressbar + if ($.ui.progressbar) { + this.progressbar.progressbar('destroy'); + } + + // destroy sortable behavior + if ($.ui.sortable && this.options.sortable) { + $('tbody', this.filelist).sortable('destroy'); + } + + // restore the elements initial state + this.element + .empty() + .html(this.contents_bak); + this.contents_bak = ''; + + $.Widget.prototype.destroy.apply(this); + }, + + + _handleState: function() { + var up = this.uploader + , filesPending = up.files.length - (up.total.uploaded + up.total.failed) + , maxCount = up.getOption('filters').max_file_count || 0 + ; + + if (plupload.STARTED === up.state) { + $([]) + .add(this.stop_button) + .add('.plupload_started') + .removeClass('plupload_hidden'); + + this.start_button.button('disable'); + + if (!this.options.multiple_queues) { + this.browse_button.button('disable'); + up.disableBrowse(); + } + + $('.plupload_upload_status', this.element).html(o.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length)); + $('.plupload_header_content', this.element).addClass('plupload_header_content_bw'); + } + else if (plupload.STOPPED === up.state) { + $([]) + .add(this.stop_button) + .add('.plupload_started') + .addClass('plupload_hidden'); + + if (filesPending) { + this.start_button.button('enable'); + } else { + this.start_button.button('disable'); + } + + if (this.options.multiple_queues) { + $('.plupload_header_content', this.element).removeClass('plupload_header_content_bw'); + } + + // if max_file_count defined, only that many files can be queued at once + if (this.options.multiple_queues && maxCount && maxCount > filesPending) { + this.browse_button.button('enable'); + up.disableBrowse(false); + } + + this._updateTotalProgress(); + } + + if (up.total.queued === 0) { + $('.ui-button-text', this.browse_button).html(_('Add Files')); + } else { + $('.ui-button-text', this.browse_button).html(o.sprintf(_('%d files queued'), up.total.queued)); + } + + up.refresh(); + }, + + + _handleFileStatus: function(file) { + var $file = $('#' + file.id), actionClass, iconClass; + + // since this method might be called asynchronously, file row might not yet be rendered + if (!$file.length) { + return; + } + + switch (file.status) { + case plupload.DONE: + actionClass = 'plupload_done'; + iconClass = 'plupload_action_icon ui-icon ui-icon-circle-check'; + break; + + case plupload.FAILED: + actionClass = 'ui-state-error plupload_failed'; + iconClass = 'plupload_action_icon ui-icon ui-icon-alert'; + break; + + case plupload.QUEUED: + actionClass = 'plupload_delete'; + iconClass = 'plupload_action_icon ui-icon ui-icon-circle-minus'; + break; + + case plupload.UPLOADING: + actionClass = 'ui-state-highlight plupload_uploading'; + iconClass = 'plupload_action_icon ui-icon ui-icon-circle-arrow-w'; + + // scroll uploading file into the view if its bottom boundary is out of it + var scroller = $('.plupload_scroll', this.container) + , scrollTop = scroller.scrollTop() + , scrollerHeight = scroller.height() + , rowOffset = $file.position().top + $file.height() + ; + + if (scrollerHeight < rowOffset) { + scroller.scrollTop(scrollTop + rowOffset - scrollerHeight); + } + + // Set file specific progress + $file + .find('.plupload_file_percent') + .html(file.percent + '%') + .end() + .find('.plupload_file_progress') + .css('width', file.percent + '%') + .end() + .find('.plupload_file_size') + .html(plupload.formatSize(file.size)); + break; + } + actionClass += ' ui-state-default plupload_file'; + + $file + .attr('class', actionClass) + .find('.plupload_action_icon') + .attr('class', iconClass); + }, + + + _updateTotalProgress: function() { + var up = this.uploader; + + // Scroll to end of file list + this.filelist[0].scrollTop = this.filelist[0].scrollHeight; + + this.progressbar.progressbar('value', up.total.percent); + + this.element + .find('.plupload_total_status') + .html(up.total.percent + '%') + .end() + .find('.plupload_total_file_size') + .html(plupload.formatSize(up.total.size)) + .end() + .find('.plupload_upload_status') + .html(o.sprintf(_('Uploaded %d/%d files'), up.total.uploaded, up.files.length)); + }, + + + _displayThumbs: function() { + var self = this + , tw, th // thumb width/height + , cols + , num = 0 // number of simultaneously visible thumbs + , thumbs = [] // array of thumbs to preload at any given moment + , loading = false + ; + + if (!this.options.views.thumbs) { + return; + } + + + function onLast(el, eventName, cb) { + var timer; + + el.on(eventName, function() { + clearTimeout(timer); + timer = setTimeout(function() { + clearTimeout(timer); + cb(); + }, 300); + }); + } + + + // calculate number of simultaneously visible thumbs + function measure() { + if (!tw || !th) { + var wrapper = $('.plupload_file:eq(0)', self.filelist); + tw = wrapper.outerWidth(true); + th = wrapper.outerHeight(true); + } + + var aw = self.content.width(), ah = self.content.height(); + cols = Math.floor(aw / tw); + num = cols * (Math.ceil(ah / th) + 1); + } + + + function pickThumbsToLoad() { + // calculate index of virst visible thumb + var startIdx = Math.floor(self.content.scrollTop() / th) * cols; + // get potentially visible thumbs that are not yet visible + thumbs = $('.plupload_file', self.filelist) + .slice(startIdx, startIdx + num) + .filter('.plupload_file_loading') + .get(); + } + + + function init() { + function mpl() { // measure, pick, load + if (self.view_mode !== 'thumbs') { + return; + } + measure(); + pickThumbsToLoad(); + lazyLoad(); + } + + if ($.fn.resizable) { + onLast(self.container, 'resize', mpl); + } + + onLast(self.window, 'resize', mpl); + onLast(self.content, 'scroll', mpl); + + self.element.on('viewchanged selected', mpl); + + mpl(); + } + + + function preloadThumb(file, cb) { + var img = new o.Image(); + + img.onload = function() { + var thumb = $('#' + file.id + ' .plupload_file_thumb', self.filelist).html(''); + this.embed(thumb[0], { + width:  self.options.thumb_width, + height: self.options.thumb_height, + crop: true, + swf_url: o.resolveUrl(self.options.flash_swf_url), + xap_url: o.resolveUrl(self.options.silverlight_xap_url) + }); + }; + + img.bind("embedded error", function() { + $('#' + file.id, self.filelist).removeClass('plupload_file_loading'); + this.destroy(); + setTimeout(cb, 1); // detach, otherwise ui might hang (in SilverLight for example) + }); + + img.load(file.getSource()); + } + + + function lazyLoad() { + if (self.view_mode !== 'thumbs' || loading) { + return; + } + + pickThumbsToLoad(); + if (!thumbs.length) { + return; + } + + loading = true; + + preloadThumb(self.getFile($(thumbs.shift()).attr('id')), function() { + loading = false; + lazyLoad(); + }); + } + + // this has to run only once to measure structures and bind listeners + this.element.on('selected', function onselected() { + self.element.off('selected', onselected); + init(); + }); + }, + + + _addFiles: function(files) { + var self = this, file_html, html = ''; + + file_html = '
  • ' + + '
    ' + + '
    %ext%
    ' + + '
    ' + + '
    ' + + '
    ' + + '%percent% ' + + '
    ' + + '
    ' + + '%name% ' + + '
    ' + + '
    ' + + '
    ' + + '
    ' + + '
    %size%
    ' + + '
    ' + + '
  • '; + + if (plupload.typeOf(files) !== 'array') { + files = [files]; + } + + $.each(files, function(i, file) { + var ext = o.Mime.getFileExtension(file.name) || 'none'; + + html += file_html.replace(/%(\w+)%/g, function($0, $1) { + switch ($1) { + case 'thumb_width': + case 'thumb_height': + return self.options[$1]; + + case 'size': + return plupload.formatSize(file.size); + + case 'ext': + return ext; + + default: + return file[$1] || ''; + } + }); + }); + + self.filelist.append(html); + }, + + + _addFormFields: function() { + var self = this; + + // re-add from fresh + $('.plupload_file_fields', this.filelist).html(''); + + plupload.each(this.uploader.files, function(file, count) { + var fields = '' + , id = self.id + '_' + count + ; + + if (file.target_name) { + fields += ''; + } + fields += ''; + fields += ''; + + $('#' + file.id).find('.plupload_file_fields').html(fields); + }); + + this.counter.val(this.uploader.files.length); + }, + + + _viewChanged: function(view) { + // update or write a new cookie + if (this.options.views.remember && $.cookie) { + $.cookie('plupload_ui_view', view, { expires: 7, path: '/' }); + } + + // ugly fix for IE6 - make content area stretchable + if (o.Env.browser === 'IE' && o.Env.version < 7) { + this.content.attr('style', 'height:expression(document.getElementById("' + this.id + '_container' + '").clientHeight - ' + (view === 'list' ? 132 : 102) + ')'); + } + + this.container.removeClass('plupload_view_list plupload_view_thumbs').addClass('plupload_view_' + view); + this.view_mode = view; + this._trigger('viewchanged', null, { view: view }); + }, + + + _enableViewSwitcher: function() { + var self = this + , view + , switcher = $('.plupload_view_switch', this.container) + , buttons + , button + ; + + plupload.each(['list', 'thumbs'], function(view) { + if (!self.options.views[view]) { + switcher.find('[for="' + self.id + '_view_' + view + '"], #'+ self.id +'_view_' + view).remove(); + } + }); + + // check if any visible left + buttons = switcher.find('.plupload_button'); + + if (buttons.length === 1) { + switcher.hide(); + view = buttons.eq(0).data('view'); + this._viewChanged(view); + } else if ($.ui.button && buttons.length > 1) { + if (this.options.views.remember && $.cookie) { + view = $.cookie('plupload_ui_view'); + } + + // if wierd case, bail out to default + if (!~plupload.inArray(view, ['list', 'thumbs'])) { + view = this.options.views.active; + } + + switcher + .show() + .buttonset() + .find('.ui-button') + .click(function(e) { + view = $(this).data('view'); + self._viewChanged(view); + e.preventDefault(); // avoid auto scrolling to widget in IE and FF (see #850) + }); + + // if view not active - happens when switcher wasn't clicked manually + button = switcher.find('[for="' + self.id + '_view_'+view+'"]'); + if (button.length) { + button.trigger('click'); + } + } else { + switcher.show(); + this._viewChanged(this.options.views.active); + } + + // initialize thumb viewer if requested + if (this.options.views.thumbs) { + this._displayThumbs(); + } + }, + + + _enableRenaming: function() { + var self = this; + + this.filelist.dblclick(function(e) { + var nameSpan = $(e.target), nameInput, file, parts, name, ext = ""; + + if (!nameSpan.hasClass('plupload_file_name_wrapper')) { + return; + } + + // Get file name and split out name and extension + file = self.uploader.getFile(nameSpan.closest('.plupload_file')[0].id); + name = file.name; + parts = /^(.+)(\.[^.]+)$/.exec(name); + if (parts) { + name = parts[1]; + ext = parts[2]; + } + + // Display input element + nameInput = $('').width(nameSpan.width()).insertAfter(nameSpan.hide()); + nameInput.val(name).blur(function() { + nameSpan.show().parent().scrollLeft(0).end().next().remove(); + }).keydown(function(e) { + var nameInput = $(this); + + if ($.inArray(e.keyCode, [13, 27]) !== -1) { + e.preventDefault(); + + // Rename file and glue extension back on + if (e.keyCode === 13) { + file.name = nameInput.val() + ext; + nameSpan.html(file.name); + } + nameInput.blur(); + } + })[0].focus(); + }); + }, + + + _enableSortingList: function() { + var self = this; + + if ($('.plupload_file', this.filelist).length < 2) { + return; + } + + // destroy sortable if enabled + $('tbody', this.filelist).sortable('destroy'); + + // enable + this.filelist.sortable({ + items: '.plupload_delete', + + cancel: 'object, .plupload_clearer', + + stop: function() { + var files = []; + + $.each($(this).sortable('toArray'), function(i, id) { + files[files.length] = self.uploader.getFile(id); + }); + + files.unshift(files.length); + files.unshift(0); + + // re-populate files array + Array.prototype.splice.apply(self.uploader.files, files); + } + }); + } +}); + +} (window, document, plupload, mOxie, jQuery)); -- cgit v1.2.3