By adding the drag and drop support for media files, the default event handlers were overwritten. Thus drag and drop did not support text. Now, the drag and drop listeners revert to the default behaviour when the user does not drag a file. Resolves: #478
262 lines
9.2 KiB
JavaScript
262 lines
9.2 KiB
JavaScript
/*
|
|
* vim: ts=4:sw=4:expandtab
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
window.Whisper = window.Whisper || {};
|
|
|
|
Whisper.FileSizeToast = Whisper.ToastView.extend({
|
|
template: $('#file-size-modal').html()
|
|
});
|
|
|
|
Whisper.FileInputView = Backbone.View.extend({
|
|
tagName: 'span',
|
|
className: 'file-input',
|
|
initialize: function(options) {
|
|
this.$input = this.$('input[type=file]');
|
|
this.thumb = new Whisper.AttachmentPreviewView();
|
|
this.$el.addClass('file-input');
|
|
this.window = options.window;
|
|
},
|
|
|
|
events: {
|
|
'change': 'previewImages',
|
|
'click .close': 'deleteFiles',
|
|
'click .choose-file': 'open',
|
|
'drop': 'openDropped',
|
|
'dragover': 'showArea',
|
|
'dragleave': 'hideArea'
|
|
},
|
|
|
|
open: function() {
|
|
// hack
|
|
if (this.window && this.window.chrome && this.window.chrome.fileSystem) {
|
|
this.window.chrome.fileSystem.chooseEntry({type: 'openFile'}, function(entry) {
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
entry.file(function(file) {
|
|
this.file = file;
|
|
this.previewImages();
|
|
}.bind(this));
|
|
}.bind(this));
|
|
} else {
|
|
this.$input.click();
|
|
}
|
|
},
|
|
|
|
addThumb: function(src) {
|
|
this.$('.avatar').hide();
|
|
this.thumb.src = src;
|
|
this.$('.attachment-previews').append(this.thumb.render().el);
|
|
this.thumb.$('img')[0].onload = function() {
|
|
this.$el.trigger('force-resize');
|
|
}.bind(this);
|
|
},
|
|
|
|
autoScale: function(file) {
|
|
if (file.type.split('/')[0] !== 'image' || file.type === 'image/gif') {
|
|
// nothing to do
|
|
return Promise.resolve(file);
|
|
}
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
var url = URL.createObjectURL(file);
|
|
var img = document.createElement('img');
|
|
img.onerror = reject;
|
|
img.onload = function () {
|
|
URL.revokeObjectURL(url);
|
|
|
|
var maxSize = 420 * 1024;
|
|
var maxHeight = 1280;
|
|
var maxWidth = 1280;
|
|
if (img.width <= maxWidth && img.height <= maxHeight &&
|
|
file.size <= maxSize) {
|
|
resolve(file);
|
|
return;
|
|
}
|
|
|
|
// loadImage.scale -> components/blueimp-load-image
|
|
var canvas = loadImage.scale(img, {
|
|
canvas: true, maxWidth: maxWidth, maxHeight: maxHeight
|
|
});
|
|
|
|
var quality = 0.95;
|
|
var i = 4;
|
|
var blob;
|
|
do {
|
|
i = i - 1;
|
|
// dataURLtoBlob -> components/blueimp-canvas-to-blob
|
|
blob = dataURLtoBlob(
|
|
canvas.toDataURL('image/jpeg', quality)
|
|
);
|
|
quality = quality * maxSize / blob.size;
|
|
if (quality < 50) {
|
|
quality = 50;
|
|
}
|
|
} while (i > 0 && blob.size > maxSize);
|
|
|
|
resolve(blob);
|
|
};
|
|
img.src = url;
|
|
});
|
|
},
|
|
|
|
previewImages: function() {
|
|
this.clearForm();
|
|
var file = this.file || this.$input.prop('files')[0];
|
|
if (!file) { return; }
|
|
|
|
var type = file.type.split('/')[0];
|
|
switch (type) {
|
|
case 'audio': this.addThumb('/images/audio.png'); break;
|
|
case 'video': this.addThumb('/images/video.png'); break;
|
|
case 'image':
|
|
this.oUrl = URL.createObjectURL(file);
|
|
this.addThumb(this.oUrl);
|
|
break;
|
|
}
|
|
|
|
this.autoScale(file).then(function(blob) {
|
|
var limitKb = 1000000;
|
|
var blobType = file.type === 'image/gif' ? 'gif' : type;
|
|
switch (blobType) {
|
|
case 'image':
|
|
limitKb = 420; break;
|
|
case 'gif':
|
|
limitKb = 5000; break;
|
|
case 'audio':
|
|
limitKb = 100000; break;
|
|
case 'video':
|
|
limitKb = 100000; break;
|
|
}
|
|
if ((blob.size/1024).toFixed(4) >= limitKb) {
|
|
var units = ['kB','MB','GB'];
|
|
var u = -1;
|
|
var limit = limitKb * 1000;
|
|
do {
|
|
limit /= 1000;
|
|
++u;
|
|
} while (limit >= 1000 && u < units.length - 1);
|
|
var toast = new Whisper.FileSizeToast({
|
|
model: {limit: limit, units: units[u]}
|
|
});
|
|
toast.$el.insertAfter(this.$el);
|
|
toast.render();
|
|
this.deleteFiles();
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
hasFiles: function() {
|
|
var files = this.file ? [this.file] : this.$input.prop('files');
|
|
return files && files.length && files.length > 0;
|
|
},
|
|
|
|
getFiles: function() {
|
|
var promises = [];
|
|
var files = this.file ? [this.file] : this.$input.prop('files');
|
|
for (var i = 0; i < files.length; i++) {
|
|
promises.push(this.getFile(files[i]));
|
|
}
|
|
this.clearForm();
|
|
return Promise.all(promises);
|
|
},
|
|
|
|
getFile: function(file) {
|
|
file = file || this.file || this.$input.prop('files')[0];
|
|
if (file === undefined) { return Promise.resolve(); }
|
|
return this.autoScale(file).then(this.readFile);
|
|
},
|
|
|
|
getThumbnail: function() {
|
|
// Scale and crop an image to 256px square
|
|
var size = 256;
|
|
var file = this.file || this.$input.prop('files')[0];
|
|
if (file === undefined || file.type.split('/')[0] !== 'image' || file.type === 'image/gif') {
|
|
// nothing to do
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
var url = URL.createObjectURL(file);
|
|
var img = document.createElement('img');
|
|
img.onerror = reject;
|
|
img.onload = function () {
|
|
URL.revokeObjectURL(url);
|
|
// loadImage.scale -> components/blueimp-load-image
|
|
// scale, then crop.
|
|
var canvas = loadImage.scale(img, {
|
|
canvas: true, maxWidth: size, maxHeight: size,
|
|
cover: true, minWidth: size, minHeight: size
|
|
});
|
|
canvas = loadImage.scale(canvas, {
|
|
canvas: true, maxWidth: size, maxHeight: size,
|
|
crop: true, minWidth: size, minHeight: size
|
|
});
|
|
|
|
// dataURLtoBlob -> components/blueimp-canvas-to-blob
|
|
var blob = dataURLtoBlob(canvas.toDataURL('image/png'));
|
|
|
|
resolve(blob);
|
|
};
|
|
img.src = url;
|
|
}).then(this.readFile);
|
|
},
|
|
|
|
readFile: function(file) {
|
|
var contentType = file.type;
|
|
return new Promise(function(resolve, reject) {
|
|
var FR = new FileReader();
|
|
FR.onload = function(e) {
|
|
resolve({data: e.target.result, contentType: contentType});
|
|
};
|
|
FR.readAsArrayBuffer(file);
|
|
});
|
|
},
|
|
|
|
clearForm: function() {
|
|
if (this.oUrl) {
|
|
URL.revokeObjectURL(this.oUrl);
|
|
this.oUrl = null;
|
|
}
|
|
this.thumb.remove();
|
|
this.$('.avatar').show();
|
|
this.$el.trigger('force-resize');
|
|
},
|
|
|
|
deleteFiles: function(e) {
|
|
if (e) { e.stopPropagation(); }
|
|
this.clearForm();
|
|
this.$input.wrap('<form>').parent('form').trigger('reset');
|
|
this.$input.unwrap();
|
|
this.file = null;
|
|
},
|
|
|
|
openDropped: function(e) {
|
|
if (e.originalEvent.dataTransfer.types[0] != "Files") return;
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
this.file = e.originalEvent.dataTransfer.files[0];
|
|
this.previewImages();
|
|
this.$el.removeClass("dropoff");
|
|
},
|
|
|
|
showArea: function(e) {
|
|
if (e.originalEvent.dataTransfer.types[0] != "Files") return;
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
this.$el.addClass("dropoff");
|
|
},
|
|
|
|
hideArea: function(e) {
|
|
if (e.originalEvent.dataTransfer.types[0] != "Files") return;
|
|
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
this.$el.removeClass("dropoff");
|
|
}
|
|
});
|
|
})();
|