Adds drag'n'drop upload.

This commit is contained in:
Lars Jung 2012-08-09 11:34:20 +02:00
parent da438f2f0b
commit d76fad3a51
15 changed files with 766 additions and 27 deletions

View file

@ -60,7 +60,7 @@ var H5AI_CONFIG = {
*/
"autoupdate": {
"enabled": true,
"interval": 5000
"interval": 1000
},
/*
@ -213,7 +213,7 @@ var H5AI_CONFIG = {
"java": "java",
"makefile": "xml",
"markdown": "plain",
"php": "php",
/*"php": "php",*/
"python": "python",
"readme": "plain",
"rb": "ruby",
@ -331,7 +331,7 @@ var H5AI_CONFIG = {
"hpp": [".hpp"],
"html": [".htm", ".html", ".shtml"],
"ico": [".ico"],
"image": [".xpm"],
"image": [".svg", ".xpm"],
"install": ["install"],
"java": [".java"],
"jpg": [".jpg", ".jpeg"],

View file

@ -0,0 +1,100 @@
#dropbox {
margin: 48px 24px;
padding: 24px 12px;
overflow: hidden;
background-color: #f8f8f8;
border: 1px dashed #ccc;
border-radius: 10px;
.transition(all 0.2s ease-in-out);
.label {
text-align: center;
color: #eee;
font-size: 5em;
font-weight: bold;
}
&.hint {
// color: #ccc;
// border-color: #aaa;
}
&.match {
border-color: #555;
.label {
color: #999;
}
}
}
#uploads {
width: 450px;
margin: 12px auto;
padding: 0;
list-style: none;
.upload {
color: #555;
font-size: 0.9em;
.name {
display: inline-block;
white-space: nowrap;
width: 320px;
overflow: hidden;
&:before {
display: inline-block;
content: '•';
color: #55c;
width: 1em;
text-align: center;
padding-right: 1em;
}
}
.size {
// display: inline-block;
display: none;
float: right;
white-space: nowrap;
&:after {
content: ' bytes'
}
}
.progress {
display: inline-block;
margin: 4px 8px;
width: 84px;
height: 8px;
background-color: #ddd;
overflow: hidden;
float: right;
.bar {
width: 0%;
height: 100%;
background-color: #999;
}
}
&.finished .name:before {
// content: '✓✔';
content: '✔';
color: #008200;
}
&.error .name:before {
content: '✖';
color: #c55;
}
}
}

View file

@ -18,11 +18,22 @@
}
// @check-white: rgba(255,255,255,0.5);
// @check-black: rgba(0,0,0,0.2);
@check-white: #f8f8f8;
@check-black: #e8e8e8;
#pv-img-image {
max-width: 100%;
max-height: 100%;
border: 2px solid #fff;
.border-radius(4px);
background-color: @check-white;
background-image:
-webkit-linear-gradient(45deg, @check-black 25%, transparent 25%, transparent 75%, @check-black 75%, @check-black),
-webkit-linear-gradient(45deg, @check-black 25%, transparent 25%, transparent 75%, @check-black 75%, @check-black);
background-size: 60px 60px;
background-position: 0 0, 30px 30px
}
#pv-img-overlay.fullscreen {

View file

@ -23,6 +23,7 @@ body {
@import "inc/content";
@import "inc/extended";
@import "inc/dropbox";
@import "inc/bottombar";
@import "inc/l10n";

BIN
src/_h5ai/images/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

View file

@ -0,0 +1,139 @@
modulejs.define('ext/dropbox', ['_', '$', 'core/settings', 'core/entry'], function (_, $, allsettings, entry) {
var defaults = {
enabled: true
},
settings = _.extend({}, defaults, allsettings.dropbox),
template = '<div id="dropbox"><div class="label">dropbox</div><ul id="uploads" /></div>',
uploadTemplate = '<li class="upload clearfix">' +
'<span class="name"></span>' +
'<div class="progress"><div class="bar"></div></div>' +
'<span class="size"></span>' +
'</li>',
init = function () {
if (!settings.enabled) {
return;
}
var $dropbox = $(template).appendTo('#content');
var uploads = {};
$dropbox.filedrop({
// The name of the $_FILES entry:
paramname: 'userfile',
maxfiles: 24,
maxfilesize: 1024,
url: allsettings.h5aiAbsHref + 'php/api.php',
data: {
action: 'upload',
href: entry.absHref
},
dragOver: function () {
$dropbox.addClass('match');
},
dragLeave: function () {
$dropbox.removeClass('match');
},
docOver: function () {
$dropbox.addClass('hint');
},
docLeave: function () {
$dropbox.removeClass('hint');
},
drop: function () {
$dropbox.removeClass('match hint');
},
beforeEach: function (file) {
uploads[file.name] = $(uploadTemplate).appendTo('#uploads')
.find('.name').text(file.name).end()
.find('.size').text(file.size).end()
.find('.progress .bar').css('width', 0).end();
console.log('beforeEach', file);
},
uploadStarted: function (i, file, len) {
console.log('uploadStarted', i, file, len);
},
progressUpdated: function (i, file, progress) {
uploads[file.name]
.find('.progress .bar').css('width', '' + progress + '%');
console.log('progressUpdated', i, file, progress);
},
uploadFinished: function (i, file, response) {
uploads[file.name].addClass(response.code ? 'error' : 'finished');
setTimeout(function () {
uploads[file.name].slideUp(400, function () {
uploads[file.name].remove();
});
}, 5000);
console.log('uploadFinished', i, file, response);
},
afterAll: function () {
// $('#uploads .upload').remove();
},
error: function (err, file) {
uploads[file.name].addClass('error');
setTimeout(function () {
uploads[file.name].slideUp(400, function () {
uploads[file.name].remove();
});
}, 5000);
switch (err) {
case 'BrowserNotSupported':
console.log('ERROR', 'Your browser does not support HTML5 file uploads!');
break;
case 'TooManyFiles':
console.log('ERROR', 'Too many files! Please select 5 at most! (configurable)');
break;
case 'FileTooLarge':
console.log('ERROR', file.name + ' is too large! Please upload files up to 2mb (configurable).');
break;
default:
break;
}
console.log('error', err, file);
}
});
};
init();
});

View file

@ -1,5 +1,5 @@
modulejs.define('ext/preview-img', ['_', '$', 'core/settings', 'core/resource', 'core/store', 'core/entry'], function (_, $, allsettings, resource, store, entry) {
modulejs.define('ext/preview-img', ['_', '$', 'core/settings', 'core/resource', 'core/store', 'core/event', 'core/entry'], function (_, $, allsettings, resource, store, event, entry) {
var defaults = {
enabled: false,
@ -149,7 +149,6 @@ modulejs.define('ext/preview-img', ['_', '$', 'core/settings', 'core/resource',
$('#pv-img-bar-original').find('a').attr('href', currentEntries[currentIdx].absHref);
}, 1);
});
});
},
@ -201,13 +200,20 @@ modulejs.define('ext/preview-img', ['_', '$', 'core/settings', 'core/resource',
}
},
initEntry = function (entries, entry, idx) {
initEntry = function (entry) {
if (entry.$extended) {
if (entry.$extended && _.indexOf(settings.types, entry.type) >= 0) {
entry.$extended.find('a').on('click', function (event) {
event.preventDefault();
onEnter(entries, idx);
var matchedEntries = _.compact(_.map($('#extended .entry'), function (entry) {
entry = $(entry).data('entry');
return _.indexOf(settings.types, entry.type) >= 0 ? entry : null;
}));
onEnter(matchedEntries, _.indexOf(matchedEntries, entry));
});
}
},
@ -218,13 +224,9 @@ modulejs.define('ext/preview-img', ['_', '$', 'core/settings', 'core/resource',
return;
}
var imageEntries = _.filter(entry.content, function (entry) {
_.each(entry.content, function (e) {
return _.indexOf(settings.types, entry.type) >= 0;
});
_.each(imageEntries, function (e, idx) {
initEntry(imageEntries, e, idx);
initEntry(e);
});
$(template).appendTo('body');
@ -277,6 +279,11 @@ modulejs.define('ext/preview-img', ['_', '$', 'core/settings', 'core/resource',
}
});
event.sub('entry.created', function (entry) {
initEntry(entry);
});
$(window).on('resize load', adjustSize);
};

View file

@ -1,5 +1,5 @@
modulejs.define('ext/preview-txt', ['_', '$', 'core/settings', 'core/resource', 'core/store', 'core/entry'], function (_, $, allsettings, resource, store, entry) {
modulejs.define('ext/preview-txt', ['_', '$', 'core/settings', 'core/resource', 'core/store', 'core/event', 'core/entry'], function (_, $, allsettings, resource, store, event, entry) {
var defaults = {
enabled: false,
@ -16,7 +16,7 @@ modulejs.define('ext/preview-txt', ['_', '$', 'core/settings', 'core/resource',
java: 'java',
makefile: 'xml',
markdown: 'plain',
php: 'php',
// php: 'php',
python: 'python',
readme: 'plain',
rb: 'ruby',
@ -207,13 +207,20 @@ modulejs.define('ext/preview-txt', ['_', '$', 'core/settings', 'core/resource',
}
},
initEntry = function (entries, entry, idx) {
initEntry = function (entry) {
if (entry.$extended) {
if (entry.$extended && _.indexOf(_.keys(settings.types), entry.type) >= 0) {
entry.$extended.find('a').on('click', function (event) {
event.preventDefault();
onEnter(entries, idx);
var matchedEntries = _.compact(_.map($('#extended .entry'), function (entry) {
entry = $(entry).data('entry');
return _.indexOf(_.keys(settings.types), entry.type) >= 0 ? entry : null;
}));
onEnter(matchedEntries, _.indexOf(matchedEntries, entry));
});
}
},
@ -224,13 +231,9 @@ modulejs.define('ext/preview-txt', ['_', '$', 'core/settings', 'core/resource',
return;
}
var imageEntries = _.filter(entry.content, function (entry) {
_.each(entry.content, function (e) {
return _.indexOf(_.keys(settings.types), entry.type) >= 0;
});
_.each(imageEntries, function (e, idx) {
initEntry(imageEntries, e, idx);
initEntry(e);
});
$(template).appendTo('body');
@ -254,6 +257,11 @@ modulejs.define('ext/preview-txt', ['_', '$', 'core/settings', 'core/resource',
event.stopPropagation();
});
event.sub('entry.created', function (entry) {
initEntry(entry);
});
$(window).on('resize load', adjustSize);
};

View file

@ -1,5 +1,5 @@
modulejs.define('ext/thumbnails', ['_', 'core/settings', 'core/entry', 'core/ajax'], function (_, allsettings, entry, ajax) {
modulejs.define('ext/thumbnails', ['_', 'core/settings', 'core/entry', 'core/event', 'core/ajax'], function (_, allsettings, entry, event, ajax) {
var defaults = {
enabled: false,
@ -52,6 +52,11 @@ modulejs.define('ext/thumbnails', ['_', 'core/settings', 'core/entry', 'core/aja
_.each(entry.content, checkEntry);
}, settings.delay);
event.sub('entry.created', function (entry) {
checkEntry(entry);
});
};
init(entry);

View file

@ -27,6 +27,7 @@
// @include "ext/crumb.js"
// @include "ext/custom.js"
// @include "ext/download.js"
// @include "ext/dropbox.js"
// @include "ext/filter.js"
// @include "ext/folderstatus.js"
// @include "ext/google-analytics.js"

View file

@ -18,7 +18,7 @@ modulejs.define('view/extended', ['_', '$', 'core/settings', 'core/resource', 'c
'</a>' +
'</li>',
hintTemplate = '<span class="hint"></span>',
listTemplate = '<ul>' +
listTemplate = '<ul class="clearfix">' +
'<li class="header">' +
'<a class="icon"></a>' +
'<a class="label" href="#"><span class="l10n-name"></span></a>' +

View file

@ -0,0 +1,424 @@
/*
* Default text - jQuery plugin for html5 dragging files from desktop to browser
*
* Author: Weixi Yen
*
* Email: [Firstname][Lastname]@gmail.com
*
* Copyright (c) 2010 Resopollution
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* Project home:
* http://www.github.com/weixiyen/jquery-filedrop
*
* Version: 0.1.0
*
* Features:
* Allows sending of extra parameters with file.
* Works with Firefox 3.6+
* Future-compliant with HTML5 spec (will work with Webkit browsers and IE9)
* Usage:
* See README at project homepage
*
*/
;(function($) {
jQuery.event.props.push("dataTransfer");
var default_opts = {
fallback_id: '',
url: '',
refresh: 1000,
paramname: 'userfile',
allowedfiletypes:[],
maxfiles: 25, // Ignored if queuefiles is set > 0
maxfilesize: 1, // MB file size limit
queuefiles: 0, // Max files before queueing (for large volume uploads)
queuewait: 200, // Queue wait time if full
data: {},
headers: {},
drop: empty,
dragEnter: empty,
dragOver: empty,
dragLeave: empty,
docEnter: empty,
docOver: empty,
docLeave: empty,
beforeEach: empty,
afterAll: empty,
rename: empty,
error: function(err, file, i) {
alert(err);
},
uploadStarted: empty,
uploadFinished: empty,
progressUpdated: empty,
speedUpdated: empty
},
errors = ["BrowserNotSupported", "TooManyFiles", "FileTooLarge", "FileTypeNotAllowed"],
doc_leave_timer, stop_loop = false,
files_count = 0,
files;
$.fn.filedrop = function(options) {
var opts = $.extend({}, default_opts, options);
this.on('drop', drop).on('dragenter', dragEnter).on('dragover', dragOver).on('dragleave', dragLeave);
$(document).on('drop', docDrop).on('dragenter', docEnter).on('dragover', docOver).on('dragleave', docLeave);
$('#' + opts.fallback_id).change(function(e) {
opts.drop(e);
files = e.target.files;
files_count = files.length;
upload();
});
function drop(e) {
opts.drop(e);
files = e.dataTransfer.files;
if (files === null || files === undefined) {
opts.error(errors[0]);
return false;
}
files_count = files.length;
upload();
e.preventDefault();
return false;
}
function getBuilder(filename, filedata, mime, boundary) {
var dashdash = '--',
crlf = '\r\n',
builder = '';
if (opts.data) {
var params = $.param(opts.data).split(/&/);
$.each(params, function() {
var pair = this.split(/=/, 2);
var name = decodeURIComponent(pair[0]);
var val = decodeURIComponent(pair[1]);
builder += dashdash;
builder += boundary;
builder += crlf;
builder += 'Content-Disposition: form-data; name="' + name + '"';
builder += crlf;
builder += crlf;
builder += val;
builder += crlf;
});
}
builder += dashdash;
builder += boundary;
builder += crlf;
builder += 'Content-Disposition: form-data; name="' + opts.paramname + '"';
builder += '; filename="' + filename + '"';
builder += crlf;
builder += 'Content-Type: ' + mime;
builder += crlf;
builder += crlf;
builder += filedata;
builder += crlf;
builder += dashdash;
builder += boundary;
builder += dashdash;
builder += crlf;
return builder;
}
function progress(e) {
if (e.lengthComputable) {
var percentage = Math.round((e.loaded * 100) / e.total);
if (this.currentProgress != percentage) {
this.currentProgress = percentage;
opts.progressUpdated(this.index, this.file, this.currentProgress);
var elapsed = new Date().getTime();
var diffTime = elapsed - this.currentStart;
if (diffTime >= opts.refresh) {
var diffData = e.loaded - this.startData;
var speed = diffData / diffTime; // KB per second
opts.speedUpdated(this.index, this.file, speed);
this.startData = e.loaded;
this.currentStart = elapsed;
}
}
}
}
// Respond to an upload
function upload() {
stop_loop = false;
if (!files) {
opts.error(errors[0]);
return false;
}
if(opts.allowedfiletypes.push && opts.allowedfiletypes.length){
for(var fileIndex = files.length;fileIndex--;){
if(!files[fileIndex].type || $.inArray(files[fileIndex].type, opts.allowedfiletypes) < 0){
opts.error(errors[3]);
return false;
}
}
}
var filesDone = 0,
filesRejected = 0;
if (files_count > opts.maxfiles && opts.queuefiles === 0) {
opts.error(errors[1]);
return false;
}
// Define queues to manage upload process
var workQueue = [];
var processingQueue = [];
var doneQueue = [];
// Add everything to the workQueue
for (var i = 0; i < files_count; i++) {
workQueue.push(i);
}
// Helper function to enable pause of processing to wait
// for in process queue to complete
var pause = function(timeout) {
setTimeout(process, timeout);
return;
}
// Process an upload, recursive
var process = function() {
var fileIndex;
if (stop_loop) return false;
// Check to see if are in queue mode
if (opts.queuefiles > 0 && processingQueue.length >= opts.queuefiles) {
return pause(opts.queuewait);
} else {
// Take first thing off work queue
fileIndex = workQueue[0];
workQueue.splice(0, 1);
// Add to processing queue
processingQueue.push(fileIndex);
}
try {
if (beforeEach(files[fileIndex]) != false) {
if (fileIndex === files_count) return;
var reader = new FileReader(),
max_file_size = 1048576 * opts.maxfilesize;
reader.index = fileIndex;
if (files[fileIndex].size > max_file_size) {
opts.error(errors[2], files[fileIndex], fileIndex);
// Remove from queue
processingQueue.forEach(function(value, key) {
if (value === fileIndex) processingQueue.splice(key, 1);
});
filesRejected++;
return true;
}
reader.onloadend = send;
reader.readAsBinaryString(files[fileIndex]);
} else {
filesRejected++;
}
} catch (err) {
// Remove from queue
processingQueue.forEach(function(value, key) {
if (value === fileIndex) processingQueue.splice(key, 1);
});
opts.error(errors[0]);
return false;
}
// If we still have work to do,
if (workQueue.length > 0) {
process();
}
};
var send = function(e) {
var fileIndex = ((typeof(e.srcElement) === "undefined") ? e.target : e.srcElement).index
// Sometimes the index is not attached to the
// event object. Find it by size. Hack for sure.
if (e.target.index == undefined) {
e.target.index = getIndexBySize(e.total);
}
var xhr = new XMLHttpRequest(),
upload = xhr.upload,
file = files[e.target.index],
index = e.target.index,
start_time = new Date().getTime(),
boundary = '------multipartformboundary' + (new Date).getTime(),
builder;
newName = rename(file.name);
mime = file.type
if (typeof newName === "string") {
builder = getBuilder(newName, e.target.result, mime, boundary);
} else {
builder = getBuilder(file.name, e.target.result, mime, boundary);
}
upload.index = index;
upload.file = file;
upload.downloadStartTime = start_time;
upload.currentStart = start_time;
upload.currentProgress = 0;
upload.startData = 0;
upload.addEventListener("progress", progress, false);
xhr.open("POST", opts.url, true);
xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary);
// Add headers
$.each(opts.headers, function(k, v) {
xhr.setRequestHeader(k, v);
});
xhr.sendAsBinary(builder);
opts.uploadStarted(index, file, files_count);
xhr.onload = function() {
if (xhr.responseText) {
var now = new Date().getTime(),
timeDiff = now - start_time,
result = opts.uploadFinished(index, file, jQuery.parseJSON(xhr.responseText), timeDiff, xhr);
filesDone++;
// Remove from processing queue
processingQueue.forEach(function(value, key) {
if (value === fileIndex) processingQueue.splice(key, 1);
});
// Add to donequeue
doneQueue.push(fileIndex);
if (filesDone == files_count - filesRejected) {
afterAll();
}
if (result === false) stop_loop = true;
}
//Pass any errors to the error option
if(xhr.status != 200){
opts.error(xhr.statusText);
}
};
}
// Initiate the processing loop
process();
}
function getIndexBySize(size) {
for (var i = 0; i < files_count; i++) {
if (files[i].size == size) {
return i;
}
}
return undefined;
}
function rename(name) {
return opts.rename(name);
}
function beforeEach(file) {
return opts.beforeEach(file);
}
function afterAll() {
return opts.afterAll();
}
function dragEnter(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.dragEnter(e);
}
function dragOver(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.docOver(e);
opts.dragOver(e);
}
function dragLeave(e) {
clearTimeout(doc_leave_timer);
opts.dragLeave(e);
e.stopPropagation();
}
function docDrop(e) {
e.preventDefault();
opts.docLeave(e);
return false;
}
function docEnter(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.docEnter(e);
return false;
}
function docOver(e) {
clearTimeout(doc_leave_timer);
e.preventDefault();
opts.docOver(e);
return false;
}
function docLeave(e) {
doc_leave_timer = setTimeout(function() {
opts.docLeave(e);
}, 200);
}
return this;
};
function empty() {}
try {
if (XMLHttpRequest.prototype.sendAsBinary) return;
XMLHttpRequest.prototype.sendAsBinary = function(datastr) {
function byteValue(x) {
return x.charCodeAt(0) & 0xff;
}
var ords = Array.prototype.map.call(datastr, byteValue);
var ui8a = new Uint8Array(ords);
this.send(ui8a.buffer);
}
} catch (e) {}
})(jQuery);

View file

@ -2,6 +2,7 @@
// jQuery libs
// -----------
// @include "lib/jquery-1.7.2.min.js"
// @include "lib/jquery.filedrop-0.1.0.js"
// @include "lib/jquery.fracs-0.11.min.js"
// @include "lib/jquery.mousewheel-3.0.6.js"
// @include "lib/jquery.qrcode-0.2.min.js"

View file

@ -138,6 +138,41 @@ else if ($action === "getentries") {
}
else if ($action === "upload") {
list($href) = check_keys(array("href"));
json_fail(1, "wrong HTTP method", strtolower($_SERVER["REQUEST_METHOD"]) !== "post");
json_fail(2, "something went wrong", !array_key_exists("userfile", $_FILES));
$userfile = $_FILES["userfile"];
json_fail(3, "something went wrong: " . $userfile["error"], $userfile["error"] !== 0);
$upload_dir = $h5ai->getAbsPath($href);
$dest = $upload_dir . "/" . $userfile["name"];
json_fail(4, "already exists: " . $userfile["name"], file_exists($dest));
json_fail(5, "can't move uploaded file", !move_uploaded_file($userfile["tmp_name"], $dest));
json_exit();
}
else if ($action === "delete") {
list($href) = check_keys(array("href"));
$absPath = $h5ai->getAbsPath($href);
if (unlink($absPath)) {
json_exit();
} else {
json_fail(1, "deletion failed");
}
}
else {
json_fail(100, "unsupported action");
}

View file

@ -0,0 +1,7 @@
# Cache
This directory is used for server side caching. To use caching make this
directory writable for your webserver.
There is no critical data in here. You can savely remove any content. This
will clear the cache.