Skip to content

Commit 83aa023

Browse files
author
Douwe Maan
committed
Merge branch 'ability_to_cancel_attaching_file' into 'master'
Add an ability to cancel attaching file and redesign attaching files UI Closes #15611, #24270, and #28905 See merge request !9431
2 parents a14d75f + 5b0e086 commit 83aa023

File tree

7 files changed

+330
-117
lines changed

7 files changed

+330
-117
lines changed

app/assets/javascripts/dropzone_input.js

+158-94
Original file line numberDiff line numberDiff line change
@@ -5,130 +5,182 @@ require('./preview_markdown');
55

66
window.DropzoneInput = (function() {
77
function DropzoneInput(form) {
8-
var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, uploads_path, showError, showSpinner, uploadFile, uploadProgress;
8+
var updateAttachingMessage, $attachingFileMessage, $mdArea, $attachButton, $cancelButton, $retryLink, $uploadingErrorContainer, $uploadingErrorMessage, $uploadProgress, $uploadingProgressContainer, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divHover, divSpinner, dropzone, $formDropzone, formTextarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, maxFileSize, pasteText, uploadsPath, showError, showSpinner, uploadFile;
99
Dropzone.autoDiscover = false;
10-
alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
11-
alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
12-
divHover = "<div class=\"div-dropzone-hover\"></div>";
13-
divSpinner = "<div class=\"div-dropzone-spinner\"></div>";
14-
divAlert = "<div class=\"" + alertClass + "\"></div>";
15-
iconPaperclip = "<i class=\"fa fa-paperclip div-dropzone-icon\"></i>";
16-
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
17-
uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
18-
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
19-
uploads_path = window.uploads_path || null;
20-
max_file_size = gon.max_file_size || 10;
21-
form_textarea = $(form).find(".js-gfm-input");
22-
form_textarea.wrap("<div class=\"div-dropzone\"></div>");
23-
form_textarea.on('paste', (function(_this) {
10+
divHover = '<div class="div-dropzone-hover"></div>';
11+
iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
12+
$attachButton = form.find('.button-attach-file');
13+
$attachingFileMessage = form.find('.attaching-file-message');
14+
$cancelButton = form.find('.button-cancel-uploading-files');
15+
$retryLink = form.find('.retry-uploading-link');
16+
$uploadProgress = form.find('.uploading-progress');
17+
$uploadingErrorContainer = form.find('.uploading-error-container');
18+
$uploadingErrorMessage = form.find('.uploading-error-message');
19+
$uploadingProgressContainer = form.find('.uploading-progress-container');
20+
uploadsPath = window.uploads_path || null;
21+
maxFileSize = gon.max_file_size || 10;
22+
formTextarea = form.find('.js-gfm-input');
23+
formTextarea.wrap('<div class="div-dropzone"></div>');
24+
formTextarea.on('paste', (function(_this) {
2425
return function(event) {
2526
return handlePaste(event);
2627
};
2728
})(this));
28-
$mdArea = $(form_textarea).closest('.md-area');
29-
$(form).setupMarkdownPreview();
30-
form_dropzone = $(form).find('.div-dropzone');
31-
form_dropzone.parent().addClass("div-dropzone-wrapper");
32-
form_dropzone.append(divHover);
33-
form_dropzone.find(".div-dropzone-hover").append(iconPaperclip);
34-
form_dropzone.append(divSpinner);
35-
form_dropzone.find(".div-dropzone-spinner").append(iconSpinner);
36-
form_dropzone.find(".div-dropzone-spinner").append(uploadProgress);
37-
form_dropzone.find(".div-dropzone-spinner").css({
38-
"opacity": 0,
39-
"display": "none"
40-
});
4129

42-
if (!uploads_path) return;
30+
// Add dropzone area to the form.
31+
$mdArea = formTextarea.closest('.md-area');
32+
form.setupMarkdownPreview();
33+
$formDropzone = form.find('.div-dropzone');
34+
$formDropzone.parent().addClass('div-dropzone-wrapper');
35+
$formDropzone.append(divHover);
36+
$formDropzone.find('.div-dropzone-hover').append(iconPaperclip);
37+
38+
if (!uploadsPath) return;
4339

44-
dropzone = form_dropzone.dropzone({
45-
url: uploads_path,
46-
dictDefaultMessage: "",
40+
dropzone = $formDropzone.dropzone({
41+
url: uploadsPath,
42+
dictDefaultMessage: '',
4743
clickable: true,
48-
paramName: "file",
49-
maxFilesize: max_file_size,
44+
paramName: 'file',
45+
maxFilesize: maxFileSize,
5046
uploadMultiple: false,
5147
headers: {
52-
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
48+
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
5349
},
5450
previewContainer: false,
5551
processing: function() {
56-
return $(".div-dropzone-alert").alert("close");
52+
return $('.div-dropzone-alert').alert('close');
5753
},
5854
dragover: function() {
5955
$mdArea.addClass('is-dropzone-hover');
60-
form.find(".div-dropzone-hover").css("opacity", 0.7);
56+
form.find('.div-dropzone-hover').css('opacity', 0.7);
6157
},
6258
dragleave: function() {
6359
$mdArea.removeClass('is-dropzone-hover');
64-
form.find(".div-dropzone-hover").css("opacity", 0);
60+
form.find('.div-dropzone-hover').css('opacity', 0);
6561
},
6662
drop: function() {
6763
$mdArea.removeClass('is-dropzone-hover');
68-
form.find(".div-dropzone-hover").css("opacity", 0);
69-
form_textarea.focus();
64+
form.find('.div-dropzone-hover').css('opacity', 0);
65+
formTextarea.focus();
7066
},
7167
success: function(header, response) {
7268
const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
7369
const shouldPad = processingFileCount >= 1;
7470

7571
pasteText(response.link.markdown, shouldPad);
72+
// Show 'Attach a file' link only when all files have been uploaded.
73+
if (!processingFileCount) $attachButton.removeClass('hide');
7674
},
77-
error: function(temp) {
78-
var checkIfMsgExists, errorAlert;
79-
errorAlert = $(form).find('.error-alert');
80-
checkIfMsgExists = errorAlert.children().length;
81-
if (checkIfMsgExists === 0) {
82-
errorAlert.append(divAlert);
83-
$(".div-dropzone-alert").append(btnAlert + "Attaching the file failed.");
84-
}
75+
error: function(file, errorMessage = 'Attaching the file failed.', xhr) {
76+
// If 'error' event is fired by dropzone, the second parameter is error message.
77+
// If the 'errorMessage' parameter is empty, the default error message is set.
78+
// If the 'error' event is fired by backend (xhr) error response, the third parameter is
79+
// xhr object (xhr.responseText is error message).
80+
// On error we hide the 'Attach' and 'Cancel' buttons
81+
// and show an error.
82+
83+
// If there's xhr error message, let's show it instead of dropzone's one.
84+
const message = xhr ? xhr.responseText : errorMessage;
85+
86+
$uploadingErrorContainer.removeClass('hide');
87+
$uploadingErrorMessage.html(message);
88+
$attachButton.addClass('hide');
89+
$cancelButton.addClass('hide');
8590
},
8691
totaluploadprogress: function(totalUploadProgress) {
87-
uploadProgress.text(Math.round(totalUploadProgress) + "%");
92+
updateAttachingMessage(this.files, $attachingFileMessage);
93+
$uploadProgress.text(Math.round(totalUploadProgress) + '%');
94+
},
95+
sending: function(file) {
96+
// DOM elements already exist.
97+
// Instead of dynamically generating them,
98+
// we just either hide or show them.
99+
$attachButton.addClass('hide');
100+
$uploadingErrorContainer.addClass('hide');
101+
$uploadingProgressContainer.removeClass('hide');
102+
$cancelButton.removeClass('hide');
88103
},
89-
sending: function() {
90-
form_dropzone.find(".div-dropzone-spinner").css({
91-
"opacity": 0.7,
92-
"display": "inherit"
93-
});
104+
removedfile: function() {
105+
$attachButton.removeClass('hide');
106+
$cancelButton.addClass('hide');
107+
$uploadingProgressContainer.addClass('hide');
108+
$uploadingErrorContainer.addClass('hide');
94109
},
95110
queuecomplete: function() {
96-
uploadProgress.text("");
97-
$(".dz-preview").remove();
98-
$(".markdown-area").trigger("input");
99-
$(".div-dropzone-spinner").css({
100-
"opacity": 0,
101-
"display": "none"
102-
});
111+
$('.dz-preview').remove();
112+
$('.markdown-area').trigger('input');
113+
114+
$uploadingProgressContainer.addClass('hide');
115+
$cancelButton.addClass('hide');
103116
}
104117
});
105-
child = $(dropzone[0]).children("textarea");
118+
119+
child = $(dropzone[0]).children('textarea');
120+
121+
// removeAllFiles(true) stops uploading files (if any)
122+
// and remove them from dropzone files queue.
123+
$cancelButton.on('click', (e) => {
124+
const target = e.target.closest('form').querySelector('.div-dropzone');
125+
126+
e.preventDefault();
127+
e.stopPropagation();
128+
Dropzone.forElement(target).removeAllFiles(true);
129+
});
130+
131+
// If 'error' event is fired, we store a failed files,
132+
// clear dropzone files queue, change status of failed files to undefined,
133+
// and add that files to the dropzone files queue again.
134+
// addFile() adds file to dropzone files queue and upload it.
135+
$retryLink.on('click', (e) => {
136+
const dropzoneInstance = Dropzone.forElement(e.target.closest('form').querySelector('.div-dropzone'));
137+
const failedFiles = dropzoneInstance.files;
138+
139+
e.preventDefault();
140+
141+
// 'true' parameter of removeAllFiles() cancels uploading of files that are being uploaded at the moment.
142+
dropzoneInstance.removeAllFiles(true);
143+
144+
failedFiles.map((failedFile, i) => {
145+
const file = failedFile;
146+
147+
if (file.status === Dropzone.ERROR) {
148+
file.status = undefined;
149+
file.accepted = undefined;
150+
}
151+
152+
return dropzoneInstance.addFile(file);
153+
});
154+
});
155+
106156
handlePaste = function(event) {
107157
var filename, image, pasteEvent, text;
108158
pasteEvent = event.originalEvent;
109159
if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
110160
image = isImage(pasteEvent);
111161
if (image) {
112162
event.preventDefault();
113-
filename = getFilename(pasteEvent) || "image.png";
114-
text = "{{" + filename + "}}";
163+
filename = getFilename(pasteEvent) || 'image.png';
164+
text = `{{${filename}}}`;
115165
pasteText(text);
116166
return uploadFile(image.getAsFile(), filename);
117167
}
118168
}
119169
};
170+
120171
isImage = function(data) {
121172
var i, item;
122173
i = 0;
123174
while (i < data.clipboardData.items.length) {
124175
item = data.clipboardData.items[i];
125-
if (item.type.indexOf("image") !== -1) {
176+
if (item.type.indexOf('image') !== -1) {
126177
return item;
127178
}
128179
i += 1;
129180
}
130181
return false;
131182
};
183+
132184
pasteText = function(text, shouldPad) {
133185
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
134186
var formattedText = text;
@@ -142,31 +194,33 @@ window.DropzoneInput = (function() {
142194
$(child).val(beforeSelection + formattedText + afterSelection);
143195
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
144196
textarea.style.height = `${textarea.scrollHeight}px`;
145-
return form_textarea.trigger("input");
197+
return formTextarea.trigger('input');
146198
};
199+
147200
getFilename = function(e) {
148201
var value;
149202
if (window.clipboardData && window.clipboardData.getData) {
150-
value = window.clipboardData.getData("Text");
203+
value = window.clipboardData.getData('Text');
151204
} else if (e.clipboardData && e.clipboardData.getData) {
152-
value = e.clipboardData.getData("text/plain");
205+
value = e.clipboardData.getData('text/plain');
153206
}
154207
value = value.split("\r");
155208
return value.first();
156209
};
210+
157211
uploadFile = function(item, filename) {
158212
var formData;
159213
formData = new FormData();
160-
formData.append("file", item, filename);
214+
formData.append('file', item, filename);
161215
return $.ajax({
162-
url: uploads_path,
163-
type: "POST",
216+
url: uploadsPath,
217+
type: 'POST',
164218
data: formData,
165-
dataType: "json",
219+
dataType: 'json',
166220
processData: false,
167221
contentType: false,
168222
headers: {
169-
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
223+
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
170224
},
171225
beforeSend: function() {
172226
showSpinner();
@@ -183,44 +237,54 @@ window.DropzoneInput = (function() {
183237
}
184238
});
185239
};
240+
241+
updateAttachingMessage = (files, messageContainer) => {
242+
let attachingMessage;
243+
const filesCount = files.filter(function(file) {
244+
return file.status === 'uploading' ||
245+
file.status === 'queued';
246+
}).length;
247+
248+
// Dinamycally change uploading files text depending on files number in
249+
// dropzone files queue.
250+
if (filesCount > 1) {
251+
attachingMessage = 'Attaching ' + filesCount + ' files -';
252+
} else {
253+
attachingMessage = 'Attaching a file -';
254+
}
255+
256+
messageContainer.text(attachingMessage);
257+
};
258+
186259
insertToTextArea = function(filename, url) {
187260
return $(child).val(function(index, val) {
188-
return val.replace("{{" + filename + "}}", url);
261+
return val.replace(`{{${filename}}}`, url);
189262
});
190263
};
264+
191265
appendToTextArea = function(url) {
192266
return $(child).val(function(index, val) {
193267
return val + url + "\n";
194268
});
195269
};
270+
196271
showSpinner = function(e) {
197-
return form.find(".div-dropzone-spinner").css({
198-
"opacity": 0.7,
199-
"display": "inherit"
200-
});
272+
return $uploadingProgressContainer.removeClass('hide');
201273
};
274+
202275
closeSpinner = function() {
203-
return form.find(".div-dropzone-spinner").css({
204-
"opacity": 0,
205-
"display": "none"
206-
});
276+
return $uploadingProgressContainer.addClass('hide');
207277
};
278+
208279
showError = function(message) {
209-
var checkIfMsgExists, errorAlert;
210-
errorAlert = $(form).find('.error-alert');
211-
checkIfMsgExists = errorAlert.children().length;
212-
if (checkIfMsgExists === 0) {
213-
errorAlert.append(divAlert);
214-
return $(".div-dropzone-alert").append(btnAlert + message);
215-
}
280+
$uploadingErrorContainer.removeClass('hide');
281+
$uploadingErrorMessage.html(message);
216282
};
217-
closeAlertMessage = function() {
218-
return form.find(".div-dropzone-alert").alert("close");
219-
};
220-
form.find(".markdown-selector").click(function(e) {
283+
284+
form.find('.markdown-selector').click(function(e) {
221285
e.preventDefault();
222286
$(this).closest('.gfm-form').find('.div-dropzone').click();
223-
form_textarea.focus();
287+
formTextarea.focus();
224288
});
225289
}
226290

0 commit comments

Comments
 (0)