From e74580a09dec272e4d7ca789d4b06fe89333a35d Mon Sep 17 00:00:00 2001 From: Davo Smith Date: Wed, 12 Oct 2011 23:32:10 +0100 Subject: [PATCH] MDL-29766 Filemanager - now able to drag and drop files from the desktop onto a filemanager form element to upload them (with supported web browsers) --- admin/settings/development.php | 2 + lang/en/admin.php | 2 + lang/en/moodle.php | 2 + lib/form/filemanager.js | 213 ++++++++++++++++++++++++++++++++- lib/form/filemanager.php | 15 ++- theme/base/style/core.css | 3 + 6 files changed, 234 insertions(+), 3 deletions(-) diff --git a/admin/settings/development.php b/admin/settings/development.php index 609fadb813ce0..2c12de8cd0ccc 100644 --- a/admin/settings/development.php +++ b/admin/settings/development.php @@ -17,6 +17,8 @@ $temp->add(new admin_setting_configcheckbox('enablesafebrowserintegration', get_string('enablesafebrowserintegration', 'admin'), get_string('configenablesafebrowserintegration', 'admin'), 0)); $temp->add(new admin_setting_configcheckbox('enablegroupmembersonly', get_string('enablegroupmembersonly', 'admin'), get_string('configenablegroupmembersonly', 'admin'), 0)); + $temp->add(new admin_setting_configcheckbox('enabledndupload', get_string('enabledndupload', 'admin'), get_string('configenabledndupload', 'admin'), 0)); + $ADMIN->add('experimental', $temp); // "debugging" settingpage diff --git a/lang/en/admin.php b/lang/en/admin.php index 03c0ed706c83e..7efa7fdb4c4c2 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -190,6 +190,7 @@ $string['configenablecalendarexport'] = 'Enable exporting or subscribing to calendars.'; $string['configenablecomments'] = 'Enable comments'; $string['configenablecourserequests'] = 'This will allow any user to request a course be created.'; +$string['configenabledndupload'] = 'Allows files to be dragged and dropped onto filemanager elements to upload them (requires a web browser that implements the relevant HTML 5 standards)'; $string['configenableglobalsearch'] = 'This setting enables global text searching in resources and activities, it is not compatible with PHP 4.'; $string['configenablegroupmembersonly'] = 'If enabled, access to activities can be restricted to group members only. This may result in an increased server load. In addition, gradebook categories must be set up in a certain way to ensure that activities are hidden from non-group members.'; $string['configenablehtmlpurifier'] = 'Use HTML Purifier instead of KSES for cleaning of untrusted text. HTML Purifier is actively developed and is believed to be more secure, but it is more resource intensive. Expect minor visual differences in the resulting html code. Please note that embed and object tags can not be enabled, MathML tags and old lang tags are not supported.'; @@ -467,6 +468,7 @@ $string['enablecourseajax_desc'] = 'Allow AJAX when editing main course pages. Note that the course format and the theme must support AJAX editing and the user has to enable AJAX in their profiles, too.'; $string['enablecourserequests'] = 'Enable course requests'; $string['enabledevicedetection'] = 'Enable device detection'; +$string['enabledndupload'] = 'Enable drag and drop upload'; $string['enableglobalsearch'] = 'Enable global search'; $string['enablegravatar'] = 'Enable Gravatar'; $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.'; diff --git a/lang/en/moodle.php b/lang/en/moodle.php index dc1bc8f3a737f..1bfaa69389e09 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -451,6 +451,8 @@ $string['displayingrecords'] = 'Displaying {$a} records'; $string['displayingusers'] = 'Displaying users {$a->start} to {$a->end}'; $string['displayonpage'] = 'Display on page'; +$string['dndenabled'] = 'Drag and drop upload enabled'; +$string['dndenabled_help'] = 'You can drag one or more files from your desktop and drop them onto the box below to upload them.
Note: this may not work with other web browsers'; $string['documentation'] = 'Moodle documentation'; $string['down'] = 'Down'; $string['download'] = 'Download'; diff --git a/lib/form/filemanager.js b/lib/form/filemanager.js index cf6837b0e1c07..1ec0c3007f9f4 100644 --- a/lib/form/filemanager.js +++ b/lib/form/filemanager.js @@ -763,5 +763,216 @@ M.form_filemanager.init = function(Y, options) { item.style.display = ''; } - new FileManagerHelper(options); + options.filemanager = new FileManagerHelper(options); + + var DndUploadHelper = function(options) { + DndUploadHelper.superclass.constructor.apply(this, arguments); + }; + DndUploadHelper.NAME = "DndUpload"; + DndUploadHelper.ATTRS = { + options: {}, + lang: {} + }; + + Y.extend(DndUploadHelper, Y.Base, { + api: M.cfg.wwwroot+'/repository/repository_ajax.php', + initializer: function(options) { + if (!options.enabledndupload) { + return; + } + + if (!this.browsersupport()) { + return; + } + + this.options = options; + this.client_id = options.client_id; + this.maxfiles = options.maxfiles; + this.maxbytes = options.maxbytes; + this.itemid = options.itemid; + + this.upload_repo = options.upload_repo; + + this.uploadingcount = 0; + + this.container = Y.one('#filemanager-'+this.client_id); + + // Needed to tell the filemanager to redraw when files uploaded + // and to check how many files are already uploaded + this.filemanager = options.filemanager; + + // Nasty hack to distinguish between dragenter(first entry), dragenter+dragleave(moving between child elements) and dragleave (leaving element) + this.entercount = 0; + + this.initevents(); + this.initinstructions(); + }, + + browsersupport: function() { + if (typeof FileReader=='undefined') { + return false; + } + if (typeof FormData=='undefined') { + return false; + } + return true; + }, + + initevents: function() { + Y.on('dragenter', this.dragenter, this.container, this); + Y.on('dragleave', this.dragleave, this.container, this); + Y.on('dragover', this.dragover, this.container, this); + Y.on('drop', this.drop, this.container, this); + }, + + initinstructions: function() { + Y.one('#dndenabled-'+this.client_id).setStyle('display', 'inline'); + }, + + dragenter: function(e) { + if (!this.hasfiles(e) || this.reachedmaxfiles()) { + return true; + } + + e.preventDefault(); + e.stopPropagation(); + + this.entercount++; + if (this.entercount >= 2) { + this.entercount = 2; // Just moved over a child element - nothing to do + return false; + } + + this.showuploadready(); + return false; + }, + + dragleave: function(e) { + if (!this.hasfiles(e) || this.reachedmaxfiles()) { + return true; + } + + e.preventDefault(); + e.stopPropagation(); + + this.entercount--; + if (this.entercount == 1) { + return false; // Just moved over a child element - nothing to do + } + + this.entercount = 0; + this.hideuploadready(); + return false; + }, + + dragover: function(e) { + if (!this.hasfiles(e) || this.reachedmaxfiles()) { + return true; + } + + e.preventDefault(); + e.stopPropagation(); + + return false; + }, + + drop: function(e) { + if (!this.hasfiles(e) || this.reachedmaxfiles()) { + return true; + } + + e.preventDefault(); + e.stopPropagation(); + this.entercount = 0; + + this.hideuploadready(); + + var files = e._event.dataTransfer.files; + var currentfilecount = this.filemanager.filecount; + + for (var i=0, f; f=files[i]; i++) { + if (currentfilecount >= this.maxfiles && this.maxfiles != -1) { + break; + } + if (this.uploadfile(f)) { + currentfilecount++; + } + } + + return false; + }, + + hasfiles: function(e) { + var types = e._event.dataTransfer.types; + for (var i=0; i= this.maxfiles && this.maxfiles != -1) { + return true; + } + return false; + }, + + showuploadready: function() { + this.container.addClass('dndupload-over'); + }, + + hideuploadready: function() { + this.container.removeClass('dndupload-over'); + }, + + redrawfilemanager: function() { + this.filemanager.refresh(this.filemanager.currentpath); + }, + + uploadfile: function(file) { + if (file.size > this.maxbytes && this.maxbytes > 0) { + alert(M.util.get_string('uploadformlimit', 'moodle')+"\n'"+file.name+"'"); + return false; + } + + this.uploadingcount++; + + // This would be an ideal place to use the Y.io function + // however, this does not support data encoded using the + // FormData object, which is needed to transfer data from + // the DataTransfer object into an XMLHTTPRequest + var xhr = new XMLHttpRequest(); + var self = this; + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200) { + var result = JSON.parse(xhr.responseText); + if (result) { + self.uploadingcount--; + if (self.uploadingcount <= 0) { + self.uploadingcount = 0; + self.redrawfilemanager(); + } + } + } + } + }; + + var url = this.api + '?action=upload'; + + var formdata = new FormData(); + formdata.append('repo_upload_file', file); + formdata.append('sesskey', M.cfg.sesskey); + formdata.append('repo_id', this.upload_repo); + formdata.append('itemid', this.itemid); + formdata.append('savepath', this.filemanager.currentpath); + + xhr.open("POST", url, true); + xhr.send(formdata); + } + }); + + new DndUploadHelper(options); }; diff --git a/lib/form/filemanager.php b/lib/form/filemanager.php index 3670fef7551f0..65d7427d33bb6 100644 --- a/lib/form/filemanager.php +++ b/lib/form/filemanager.php @@ -239,7 +239,7 @@ public function __construct(stdClass $options) { * @return string HTML fragment */ function form_filemanager_render($options) { - global $CFG, $OUTPUT, $PAGE; + global $CFG, $OUTPUT, $PAGE, $DB; $fm = new form_filemanaer_x($options); //TODO: this is unnecessary here, the nested options are getting too complex @@ -273,7 +273,16 @@ function form_filemanager_render($options) { $extra = ''; } + $options->enabledndupload = isset($CFG->enabledndupload) && $CFG->enabledndupload; + if ($options->enabledndupload) { + $options->upload_repo = $DB->get_field('repository', 'id', array('type'=>'upload')); + if (!$options->upload_repo) { + $options->enabledndupload = false; + } + } + $maxsize = get_string('maxfilesize', 'moodle', display_size(get_max_upload_file_size($CFG->maxbytes, $course_maxbytes, $options->maxbytes))); + $strdndenabled = get_string('dndenabled', 'moodle').$OUTPUT->help_icon('dndenabled'); $html .= << $icon_progress @@ -285,6 +294,7 @@ function form_filemanager_render($options) { $maxsize +
    @@ -313,7 +323,8 @@ function form_filemanager_render($options) { array('cannotdeletefile', 'error'), array('confirmdeletefile', 'repository'), array('nopathselected', 'repository'), array('popupblockeddownload', 'repository'), array('draftareanofiles', 'repository'), array('path', 'moodle'), array('setmainfile', 'repository'), - array('moving', 'repository'), array('files', 'moodle') + array('moving', 'repository'), array('files', 'moodle'), + array('uploadformlimit', 'moodle') ) ); $PAGE->requires->js_module($module); diff --git a/theme/base/style/core.css b/theme/base/style/core.css index 62a5bfe860d12..9b0a689e5b031 100644 --- a/theme/base/style/core.css +++ b/theme/base/style/core.css @@ -518,6 +518,7 @@ body.tag .managelink {padding: 5px;} .filemanager-toolbar {margin: 5px 0;} .filemanager-toolbar a {border: 1px solid #AACCEE;background: #F4FAFF;color: black;padding: 3px;} .filemanager-toolbar a:hover {background: #FFFFFF;} +.filemanager-toolbar .helplink a {border: 0px; background: transparent;} .fm-breadcrumb {margin:0;} .filemanager-container {padding: 5px;margin: 6px 0;background: #E9F4FF;border: #AACCEE 1px solid} .filemanager-container ul{margin:0;padding:0;} @@ -531,6 +532,8 @@ body.tag .managelink {padding: 5px;} .fm-file-entry{border: 1px solid red;} .fm-operation {font-weight: bold;} +.filemanager-container.dndupload-over {background: #8EF947;} + /* * Backup and Restore CSS */