Merge branch 'hotfix/21.56.9' into master
[gitter.git] / public / js / views / app / dropTargetView.js
blob9d53a493e1976dc71278b1f03beb93fa919be907
1 'use strict';
3 var _ = require('lodash');
5 var context = require('gitter-web-client-context');
6 var Marionette = require('backbone.marionette');
7 var appEvents = require('../../utils/appevents');
8 var apiClient = require('../../components/api-client');
10 require('transloadit');
12 var PROGRESS_THRESHOLD = 62.5;
14 function contains(domStringsList, item) {
15 if (!domStringsList) return false;
16 for (var i = 0; i < domStringsList.length; i++) {
17 if (domStringsList[i] === item) return true;
19 return false;
22 function ignoreEvent(e) {
23 e.stopPropagation();
24 e.preventDefault();
27 var DropTargetView = Marionette.ItemView.extend({
28 template: false,
29 el: 'body',
30 ui: {
31 progressBar: '#file-progress-bar',
32 dragOverlay: '.js-drag-overlay',
33 uploadForm: '#upload-form'
36 events: {
37 paste: 'handlePaste',
38 dragenter: 'onDragEnter',
39 dragleave: 'onDragLeave',
40 dragover: 'onDragOver',
41 drop: 'onDrop'
44 /**
45 * IMPORTANT: when dragging moving over child nodes will cause dragenter and dragleave, so we need to keep this count, if it's zero means that we should hide the overlay. WC.
47 dragCounter: 0,
49 updateProgressBar: function(spec) {
50 var bar = this.ui.progressBar;
51 var value = spec.value && spec.value.toFixed(0) + '%';
52 var timeout = spec.timeout || 200;
53 setTimeout(function() {
54 bar.css('width', value);
55 }, timeout);
58 resetProgressBar: function() {
59 this.ui.progressBar.hide();
60 this.updateProgressBar({
61 value: 0,
62 timeout: 0
63 });
66 handleUploadProgress: function(done, expected) {
67 this.updateProgressBar({
68 value: PROGRESS_THRESHOLD + (done / expected) * (100 - PROGRESS_THRESHOLD),
69 timeout: 0
70 });
73 handleUploadStart: function() {
74 this.ui.progressBar.show();
77 handleUploadSuccess: function(res) {
78 this.resetProgressBar();
79 var n = parseInt(res.fields.numberOfFiles, 10);
80 appEvents.triggerParent('user_notification', {
81 title: 'Upload complete',
82 text: (n > 1 ? n + ' files' : 'file') + ' uploaded successfully.'
83 });
86 handleUploadError: function(err) {
87 appEvents.triggerParent('user_notification', {
88 title: 'Error Uploading File',
89 text: err.message
90 });
91 this.resetProgressBar();
94 isTextDrag: function(e) {
95 var dt = e.dataTransfer;
96 if (contains(dt.types, 'Files')) return false;
97 if (!dt.files && dt.files.length > 0) {
98 return false;
101 var items = dt.items;
102 if (!items) return true;
103 for (var i = 0; i < items.length; i++) {
104 var kind = items[i].kind;
105 if (kind !== 'string') {
106 return false;
110 return true;
113 onDragEnter: function(e) {
114 if (e.originalEvent) e = e.originalEvent;
115 if (this.isTextDrag(e)) return;
116 this.dragCounter++;
117 this.ui.dragOverlay.toggleClass('hide', false);
118 ignoreEvent(e);
121 onDragLeave: function(e) {
122 if (e.originalEvent) e = e.originalEvent;
123 if (this.isTextDrag(e)) return;
125 this.dragCounter--;
126 this.ui.dragOverlay.toggleClass('hide', this.dragCounter === 0);
127 ignoreEvent(e);
130 onDragOver: function(e) {
131 if (e.originalEvent) e = e.originalEvent;
132 if (this.isTextDrag(e)) return;
133 ignoreEvent(e);
136 onDrop: function(e) {
137 if (e.originalEvent) e = e.originalEvent;
138 if (this.isTextDrag(e)) return;
140 this.dragCounter = 0; // reset the counter
141 this.ui.dragOverlay.toggleClass('hide', true);
142 ignoreEvent(e);
144 var files = e.dataTransfer.files;
145 this.upload(files);
148 isImage: function(file) {
149 var fileType = file.type;
150 // svg causes an INVALID_FILE_META_DATA error from transloadit.
151 // see https://github.com/gitterHQ/gitter/issues/721
152 return fileType !== 'image/svg+xml' && fileType.indexOf('image/') >= 0;
156 * handles pasting, image-only for now
158 handlePaste: function(evt) {
159 evt = evt.originalEvent || evt;
160 var clipboard = evt.clipboardData;
161 var blob = null;
163 if (!clipboard || !clipboard.items) {
164 return; // Safari + FF, don't support pasting images in. Ignore and perform default behaviour. WC.
167 if (clipboard.items.length === 1) {
168 blob = clipboard.items[0].getAsFile();
169 if (!blob || !this.isImage(blob)) {
170 return;
171 } else {
172 evt.preventDefault();
173 this.upload([blob]);
178 // Returns image iff all files are safe for opening in a browser
179 // otherwise returns ''
180 getFileType: function(files) {
181 if (!files.length) return '';
183 for (var i = 0; i < files.length; i++) {
184 var file = files[i];
185 if (!this.isImage(file)) {
186 return '';
189 return 'image';
192 upload: function(files) {
193 if (!files.length) return;
194 var self = this;
195 this.ui.progressBar.show();
197 this.updateProgressBar({
198 value: 0,
199 timeout: 0
202 this.updateProgressBar({
203 value: PROGRESS_THRESHOLD,
204 timeout: 200
207 var DEFAULT_OPTIONS = {
208 wait: true,
209 modal: false,
210 autoSubmit: false,
211 debug: false,
212 onStart: this.handleUploadStart.bind(this),
213 onProgress: this.handleUploadProgress.bind(this),
214 onSuccess: this.handleUploadSuccess.bind(this),
215 onError: this.handleUploadError.bind(this)
218 var formData = new FormData();
220 for (var i = 0; i < files.length; i++) {
221 var file = files[i];
222 formData.append('file', file);
225 formData.append('numberOfFiles', files.length);
226 apiClient.priv
227 .get('/generate-signature', {
228 room_id: context.getTroupeId(),
229 type: this.getFileType(files)
231 .then(function(res) {
232 formData.append('signature', res.sig);
234 var form = self.ui.uploadForm;
235 form.find('input[name="params"]').attr('value', res.params);
236 form.unbind('submit.transloadit');
237 form.transloadit(_.extend(DEFAULT_OPTIONS, { formData: formData }));
238 form.submit();
243 module.exports = DropTargetView;