From d00ec4555f81210cd067f98d9bc7cef51f456462 Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Sat, 7 Jun 2008 00:28:49 +0200 Subject: experimental Flash based multi upload This patch adds experimental support for uploading multiple files in one go. This is achieved by using Flash for selecting multiple images and intitiating the HTTP upload. When Flash 8 or higher is detected, an additional icon is added to the usual upload form. Clicking it will swith the form to the Flash based upload queue. Things that need work: * Better Icon * Feedback if Flash detection works on all Flash 8 supported platforms * Progress feedback seems not to work on Linux (might be Adobe bug) * No final feedback how many images were uploaded correctly The flash sources are located in lib/_fla/ Any feedback and help would be appreciated. darcs-hash:20080606222849-7ad00-738083445af275752aaebc29bfa51430f3d94459.gz --- lib/_fla/.htaccess | 3 + lib/_fla/MultipleUpload.as | 274 +++++++++++++++++++++++++++++++++++++++++++ lib/_fla/README | 1 + lib/_fla/multipleUpload.fla | Bin 0 -> 1812992 bytes lib/exe/mediamanager.php | 16 +++ lib/exe/multipleUpload.swf | Bin 0 -> 64353 bytes lib/images/multiupload.png | Bin 0 -> 698 bytes lib/scripts/helpers.js | 278 +++++++++++++++++++++++--------------------- lib/scripts/media.js | 21 +++- lib/tpl/default/media.css | 3 +- 10 files changed, 462 insertions(+), 134 deletions(-) create mode 100644 lib/_fla/.htaccess create mode 100644 lib/_fla/MultipleUpload.as create mode 100644 lib/_fla/README create mode 100644 lib/_fla/multipleUpload.fla create mode 100644 lib/exe/multipleUpload.swf create mode 100644 lib/images/multiupload.png (limited to 'lib') diff --git a/lib/_fla/.htaccess b/lib/_fla/.htaccess new file mode 100644 index 000000000..9a7d38c12 --- /dev/null +++ b/lib/_fla/.htaccess @@ -0,0 +1,3 @@ +## no access to the fla directory +order allow,deny +deny from all diff --git a/lib/_fla/MultipleUpload.as b/lib/_fla/MultipleUpload.as new file mode 100644 index 000000000..8e7e0b008 --- /dev/null +++ b/lib/_fla/MultipleUpload.as @@ -0,0 +1,274 @@ +/** + * Flash Multi Upload + * + * Based on a example from Alastair Dawson + * + * @link http://blog.vixiom.com/2006/09/08/multiple-file-upload-with-flash-and-ruby-on-rails/ + * @author Alastair Dawson + * @author Andreas Gohr + */ + +// delegate +import mx.utils.Delegate; +// ui components +import mx.controls.DataGrid; +import mx.controls.gridclasses.DataGridColumn +import mx.controls.Button; +import mx.controls.TextInput; +import mx.controls.CheckBox; +import mx.controls.Label; +// file reference +import flash.net.FileReferenceList; +import flash.net.FileReference; + +class MultipleUpload { + + private var fileRef:FileReferenceList; + private var fileRefListener:Object; + private var list:Array; + private var dp:Array; + + private var files_dg:DataGrid; + private var browse_btn:Button; + private var upload_btn:Button; + private var ns_input:TextInput; + private var ns_label:Label; + private var overwrite_cb:CheckBox; + + /** + * Constructor. + * + * Initializes the needed objects and stage objects + */ + public function MultipleUpload(fdg:DataGrid, bb:Button, ub:Button, nsi:TextInput, nsl:Label, ob:CheckBox) { + // references for objects on the stage + files_dg = fdg; + browse_btn = bb; + upload_btn = ub; + ns_input = nsi; + ns_label = nsl; + overwrite_cb = ob; + + // file list references & listener + fileRef = new FileReferenceList(); + fileRefListener = new Object(); + fileRef.addListener(fileRefListener); + + // setup + iniUI(); + inifileRefListener(); + } + + /** + * Initializes the User Interface + * + * Uses flashvars to access possibly localized names + */ + private function iniUI() { + // register button handlers + browse_btn.onRelease = Delegate.create(this, this.browse); + upload_btn.onRelease = Delegate.create(this, this.upload); + + // columns for dataGrid + var col:DataGridColumn; + col = new DataGridColumn('name'); + col.headerText = ( _root.L_gridname ? _root.L_gridname : 'Filename' ); + col.sortable = false; + files_dg.addColumn(col); + col = new DataGridColumn('size'); + col.headerText = ( _root.L_gridsize ? _root.L_gridsize : 'Size' ); + col.sortable = false; + files_dg.addColumn(col); + col = new DataGridColumn('status'); + col.headerText = ( _root.L_gridstat ? _root.L_gridstat : 'Status' ); + col.sortable = false; + files_dg.addColumn(col); + + // label translations + if(_root.L_overwrite) overwrite_cb.label = _root.L_overwrite; + if(_root.L_browse) browse_btn.label = _root.L_browse; + if(_root.L_upload) upload_btn.label = _root.L_upload; + if(_root.L_namespace) ns_label.text = _root.L_namespace; + + // prefill input field + if(_root.O_ns) ns_input.text = _root.O_ns; + + // disable buttons + upload_btn.enabled = false; + if(!_root.O_overwrite) overwrite_cb.visible = false; + + // initalize the data provider list + dp = new Array(); + list = new Array(); + files_dg.spaceColumnsEqually(); + } + + /** + * Open files selection dialog + * + * Adds the allowed file types + */ + private function browse() { + if(_root.O_extensions){ + var exts:Array = _root.O_extensions.split('|'); + var filter:Object = new Object(); + filter.description = (_root.L_filetypes ? _root.L_filetypes : 'Allowed filetypes'); + filter.extension = ''; + for(var i:Number = 0; i _root.O_maxsize){ + stat = (_root.L_toobig ? _root.L_toobig : 'too big'); + }else{ + stat = (_root.L_ready ? _root.L_ready : 'ready for upload'); + } + // add to grid + dp.push({name:sel[i].name, size:Math.round(sel[i].size / 1000) + " kb", status:stat}); + // add to reference list + list.push(sel[i]); + } + // update dataGrid + files_dg.dataProvider = dp; + files_dg.spaceColumnsEqually(); + + if(list.length > 0) upload_btn.enabled = true; + } + + /** + * Does nothing + */ + private function onCancel() { + } + + /** + * Does nothing + */ + private function onOpen(file:FileReference) { + } + + /** + * Set the upload progress + */ + private function onProgress(file:FileReference, bytesLoaded:Number, bytesTotal:Number) { + var percentDone = Math.round((bytesLoaded / bytesTotal) * 100); + var msg:String = 'uploading @PCT@%'; + if(_root.L_progress) msg = _root.L_progress; + msg = msg.split('@PCT@').join(percentDone); + this.setStatus(file,msg); + } + + /** + * Handle upload completion + */ + private function onComplete(file:FileReference) { + this.setStatus(file,(_root.L_done ? _root.L_done : 'complete')); + } + + /** + * Handle upload errors + */ + private function onHTTPError(file:FileReference, httpError:Number) { + if(httpError == 400){ + this.setStatus(file,(_root.L_fail ? _root.L_fail : 'failed')); + }else if(httpError == 401){ + this.setStatus(file,(_root.L_authfail ? _root.L_authfail : 'auth failed')); + }else{ + this.setStatus(file,"HTTP Error " + httpError); + } + } + + /** + * Handle IO errors + */ + private function onIOError(file:FileReference) { + this.setStatus(file,"IO Error"); + } + + /** + * Handle Security errors + */ + private function onSecurityError(file:FileReference, errorString:String) { + this.setStatus(file,"SecurityError: " + errorString); + } + + +} diff --git a/lib/_fla/README b/lib/_fla/README new file mode 100644 index 000000000..beaa15d02 --- /dev/null +++ b/lib/_fla/README @@ -0,0 +1 @@ +Flash source files. Those will not be included in the tarball releases. diff --git a/lib/_fla/multipleUpload.fla b/lib/_fla/multipleUpload.fla new file mode 100644 index 000000000..a04237c1c Binary files /dev/null and b/lib/_fla/multipleUpload.fla differ diff --git a/lib/exe/mediamanager.php b/lib/exe/mediamanager.php index 32849be62..c3754d309 100644 --- a/lib/exe/mediamanager.php +++ b/lib/exe/mediamanager.php @@ -1,6 +1,8 @@ = AUTH_UPLOAD) { io_createNamespace("$NS:xxx", 'media'); } + // handle flash upload + if($_FILES['Filedata']['tmp_name']){ + $_FILES['upload'] =& $_FILES['Filedata']; + $JUMPTO = media_upload($NS,$AUTH); + if($JUMPTO == false){ + header("HTTP/1.0 400 Bad Request"); + echo 'Upload failed'; + } + echo 'ok'; + exit; + } + + // handle upload if($_FILES['upload']['tmp_name']){ $JUMPTO = media_upload($NS,$AUTH); diff --git a/lib/exe/multipleUpload.swf b/lib/exe/multipleUpload.swf new file mode 100644 index 000000000..6921cbb45 Binary files /dev/null and b/lib/exe/multipleUpload.swf differ diff --git a/lib/images/multiupload.png b/lib/images/multiupload.png new file mode 100644 index 000000000..1e8efa063 Binary files /dev/null and b/lib/images/multiupload.png differ diff --git a/lib/scripts/helpers.js b/lib/scripts/helpers.js index 7b500c226..8d4f3ea78 100644 --- a/lib/scripts/helpers.js +++ b/lib/scripts/helpers.js @@ -1,132 +1,146 @@ -/** - * $Id: helpers.js 156 2006-12-23 08:48:25Z wingedfox $ - * $HeadURL: https://svn.debugger.ru/repos/jslibs/BrowserExtensions/trunk/helpers.js $ - * - * File contains differrent helper functions - * - * @author Ilya Lebedev - * @license LGPL - * @version $Rev: 156 $ - */ -//----------------------------------------------------------------------------- -// Variable/property checks -//----------------------------------------------------------------------------- -/** - * Checks if property is undefined - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isUndefined (prop /* :Object */) /* :Boolean */ { - return (typeof prop == 'undefined'); -} -/** - * Checks if property is function - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isFunction (prop /* :Object */) /* :Boolean */ { - return (typeof prop == 'function'); -} -/** - * Checks if property is string - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isString (prop /* :Object */) /* :Boolean */ { - return (typeof prop == 'string'); -} -/** - * Checks if property is number - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isNumber (prop /* :Object */) /* :Boolean */ { - return (typeof prop == 'number'); -} -/** - * Checks if property is the calculable number - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isNumeric (prop /* :Object */) /* :Boolean */ { - return isNumber(prop)&&!isNaN(prop)&&isFinite(prop); -} -/** - * Checks if property is array - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isArray (prop /* :Object */) /* :Boolean */ { - return (prop instanceof Array); -} -/** - * Checks if property is regexp - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isRegExp (prop /* :Object */) /* :Boolean */ { - return (prop instanceof RegExp); -} -/** - * Checks if property is a boolean value - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isBoolean (prop /* :Object */) /* :Boolean */ { - return ('boolean' == typeof prop); -} -/** - * Checks if property is a scalar value (value that could be used as the hash key) - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isScalar (prop /* :Object */) /* :Boolean */ { - return isNumeric(prop)||isString(prop); -} -/** - * Checks if property is empty - * - * @param {Object} prop value to check - * @return {Boolean} true if matched - * @scope public - */ -function isEmpty (prop /* :Object */) /* :Boolean */ { - if (isBoolean(prop)) return false; - if (isRegExp(prop) && new RegExp("").toString() == prop.toString()) return true; - if (isString(prop) || isNumber(prop)) return !prop; - if (Boolean(prop)&&false != prop) { - for (var i in prop) if(prop.hasOwnProperty(i)) return false - } - return true; -} - -/* -* Checks if property is derived from prototype, applies method if it is not exists -* -* @param string property name -* @return bool true if prototyped -* @access public -*/ -if ('undefined' == typeof Object.hasOwnProperty) { - Object.prototype.hasOwnProperty = function (prop) { - return !('undefined' == typeof this[prop] || this.constructor && this.constructor.prototype[prop] && this[prop] === this.constructor.prototype[prop]); - } -} +/** + * Differrent helper functions + * + * @author Ilya Lebedev + * @license LGPL + */ +//----------------------------------------------------------------------------- +// Variable/property checks +//----------------------------------------------------------------------------- +/** + * Checks if property is undefined + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isUndefined (prop /* :Object */) /* :Boolean */ { + return (typeof prop == 'undefined'); +} +/** + * Checks if property is function + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isFunction (prop /* :Object */) /* :Boolean */ { + return (typeof prop == 'function'); +} +/** + * Checks if property is string + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isString (prop /* :Object */) /* :Boolean */ { + return (typeof prop == 'string'); +} +/** + * Checks if property is number + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isNumber (prop /* :Object */) /* :Boolean */ { + return (typeof prop == 'number'); +} +/** + * Checks if property is the calculable number + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isNumeric (prop /* :Object */) /* :Boolean */ { + return isNumber(prop)&&!isNaN(prop)&&isFinite(prop); +} +/** + * Checks if property is array + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isArray (prop /* :Object */) /* :Boolean */ { + return (prop instanceof Array); +} +/** + * Checks if property is regexp + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isRegExp (prop /* :Object */) /* :Boolean */ { + return (prop instanceof RegExp); +} +/** + * Checks if property is a boolean value + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isBoolean (prop /* :Object */) /* :Boolean */ { + return ('boolean' == typeof prop); +} +/** + * Checks if property is a scalar value (value that could be used as the hash key) + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isScalar (prop /* :Object */) /* :Boolean */ { + return isNumeric(prop)||isString(prop); +} +/** + * Checks if property is empty + * + * @param {Object} prop value to check + * @return {Boolean} true if matched + * @scope public + */ +function isEmpty (prop /* :Object */) /* :Boolean */ { + if (isBoolean(prop)) return false; + if (isRegExp(prop) && new RegExp("").toString() == prop.toString()) return true; + if (isString(prop) || isNumber(prop)) return !prop; + if (Boolean(prop)&&false != prop) { + for (var i in prop) if(prop.hasOwnProperty(i)) return false + } + return true; +} + +/** + * Checks if property is derived from prototype, applies method if it is not exists + * + * @param string property name + * @return bool true if prototyped + * @access public + */ +if ('undefined' == typeof Object.hasOwnProperty) { + Object.prototype.hasOwnProperty = function (prop) { + return !('undefined' == typeof this[prop] || this.constructor && this.constructor.prototype[prop] && this[prop] === this.constructor.prototype[prop]); + } +} + +/** + * Very simplistic Flash plugin check, probably works for Flash 8 and higher only + */ +function hasFlash(version){ + var ver = 0; + try{ + if(navigator.plugins != null && navigator.plugins.length > 0){ + ver = navigator.plugins["Shockwave Flash"].description.split(' ')[2].split('.')[0]; + }else{ + var axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"); + ver = axo.GetVariable("$version").split(' ')[1].split(',')[0]; + } + }catch(e){ } + + if(ver >= version) return true; + return false; +} diff --git a/lib/scripts/media.js b/lib/scripts/media.js index f616f8741..ebfd01322 100644 --- a/lib/scripts/media.js +++ b/lib/scripts/media.js @@ -265,12 +265,31 @@ media = { text = text.substr(text.lastIndexOf('/')+1); text = text.substr(text.lastIndexOf('\\')+1); name.value = text; - } + }, + + initFlashUpload: function(){ + if(!hasFlash(8)) return; + var oform = $('dw__upload'); + var oflash = $('dw__flashupload'); + if(!oform || !oflash) return; + + var clicky = document.createElement('img'); + clicky.src = DOKU_BASE+'lib/images/multiupload.png'; + clicky.title = LANG['mu_btn']; + clicky.alt = LANG['mu_btn']; + clicky.style.cursor = 'pointer'; + clicky.onclick = function(){ + oform.style.display = 'none'; + oflash.style.display = ''; + }; + oform.appendChild(clicky); + } }; addInitEvent(function(){ media.treeattach($('media__tree')); media.selectorattach($('media__content')); media.attachoptions($('media__opts')); + media.initFlashUpload(); }); diff --git a/lib/tpl/default/media.css b/lib/tpl/default/media.css index 143595ba1..81a439289 100644 --- a/lib/tpl/default/media.css +++ b/lib/tpl/default/media.css @@ -137,7 +137,8 @@ it's dirty, so any "real" fixes are welcome */ padding: 0 0.5em 0.5em 0.5em; } -#media__content form#dw__upload { +#media__content form#dw__upload, +#media__content div#dw__flashupload { display: block; border-bottom: solid 1px __border__; padding: 0 0.5em 1em 0.5em; -- cgit v1.2.3