From e31774600cb8025f75da342989cd40a38f340758 Mon Sep 17 00:00:00 2001 From: Robson Wenzel Date: Wed, 17 Apr 2024 16:25:34 -0300 Subject: [PATCH 1/7] saving --- index.html | 20 +++ src/css/dropfilesuploader.css | 112 ++++++++++++ src/js/dropfilesuploader.js | 325 ++++++++++++++++++++++++++++++++++ 3 files changed, 457 insertions(+) create mode 100644 index.html create mode 100644 src/css/dropfilesuploader.css create mode 100644 src/js/dropfilesuploader.js diff --git a/index.html b/index.html new file mode 100644 index 0000000..d5aef4a --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + Dropfiles Uploader Example + + + +
+ + + + + + \ No newline at end of file diff --git a/src/css/dropfilesuploader.css b/src/css/dropfilesuploader.css new file mode 100644 index 0000000..1148c46 --- /dev/null +++ b/src/css/dropfilesuploader.css @@ -0,0 +1,112 @@ +.df-container, .df-container *, .df-container ::after, .df-container ::before { + box-sizing: border-box; +} + +.df-container { + width: 100%; + height: 100%; + background-color: #fafafa; + border: 2px solid #c5c5c5; + border-radius: 8px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 18px; +} + +.df-container .df-drop-area { + width: 100%; + height: 100%; + margin: auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.df-files { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + gap: 8px; +} + +.df-file { + padding: 8px; + border-bottom: 1px solid #e3e3e3; +} + +.df-file-wrapper { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; +} + +.df-file-thumbnail > img { + width: 100%; + min-width: 100%; + height: 100%; +} + +.df-file-thumbnail > svg { + color: #343434; +} + +.df-file-data { + width: 100%; + padding: 12px 8px; + min-width: 0; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + gap: 4px; +} + +.df-file-progress-container { + width: 100%; + background-color: #f1f1f1; + border-radius: 18px; +} + +.df-file-progress-bar { + height: 4px; + width: 0%; +} + +.df-file-info { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + gap: 8px; +} + +.df-file-name { + display: flex; + flex-direction: column; + color: #343434; + gap: 4px; + min-width: 0; +} + +.df-file-name span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 16px; +} + +.df-file-name small { + font-size: 12px; + font-weight: 600; +} + +.df-file-status { + display: flex; + align-items: center; + justify-content: center; + padding: 4px; + width: 32px; + height: 32px; +} \ No newline at end of file diff --git a/src/js/dropfilesuploader.js b/src/js/dropfilesuploader.js new file mode 100644 index 0000000..fb380b9 --- /dev/null +++ b/src/js/dropfilesuploader.js @@ -0,0 +1,325 @@ +(function($) { + var plugin = function(element, options) { + var $element = $(element); + var filesQueue = []; + var settings = $.extend({ + url: null, + autoProcessQueue: true, + request: { + method: 'POST', + timeout: null, + headers: null, + multipleUpload: false, + parallelUploads: 2, + }, + droppable: true, + paramName: 'file', + maxFiles: null, + maxFilesize: 5, + filesizeBase: 1000, + acceptedFiles: null, + debug: false, + imageThumbnails: true, + thumbnails: { + width: 60, + height: 60, + object_fit: 'cover', + }, + styles: {}, + lang: { + message: 'Drag or click to add files here', + errors: { + maxFilesize: 'The file size is {filesize}, the maximum allowed is {maxFilesize}.', + acceptedFiles: 'Unsupported file extension. The file must have the extension .{fileExtension}, supported extensions are [{acceptedFiles}].', + } + }, + events: {} + }, options); + + var privateFunctions = { + debugLog: function() { + + }, + appendFilesToList: async function() { + const $fileListDiv = $element.find('.df-files'); + for (let i = 0; i < filesQueue.length; i++) { + const fileObject = filesQueue[i]; + if (fileObject.displayed) continue; + let thumbnail = ''; + let newStatus = (fileObject.status === 'accepted' ? 'added' : 'error'); + let statusIcon = privateFunctions.getStatusIcon(newStatus); + let fileCategory = privateFunctions.getFileCategory(fileObject.file.type); + + if (settings.imageThumbnails) { + let thumbnailContent = ''; + if (fileCategory.category === 'image') { + try { + content = await privateFunctions.createFileBase64(fileObject.file); + thumbnailContent = `${fileObject.file.name}`; + } catch (error) { + thumbnailContent = fileCategory.icon; + } + } else { + thumbnailContent = fileCategory.icon; + } + thumbnail = `
${thumbnailContent}
`; + } + + const template = $('\ +
\ +
\ + ' + thumbnail + '\ +
\ +
\ +
\ + \ + ' + fileObject.file.name + '\ + \ + \ + ' + privateFunctions.formatFileSize(fileObject.file.size) + '\ + \ +
\ +
\ + ' + statusIcon + '\ +
\ +
\ +
\ +
\ +
\ + '); + const $fileListItem = $fileListDiv.append(template); + + filesQueue[i].status = newStatus; + filesQueue[i].displayed = true; + filesQueue[i].$element = $fileListItem; + } + + privateFunctions.enqueueFiles(); + }, + createFileBase64: function(file) { + return new Promise(function(resolve, reject) { + var fileReader = new FileReader(); + + fileReader.onload = function(e) { + var base64 = e.target.result; + + resolve(base64); + }; + + fileReader.readAsDataURL(file); + }); + }, + getStatusIcon: function(status) { + var statusIcons = { + added: '', + error: '', + success: '', + }; + + return statusIcons[status] || ''; + }, + getFileCategory: function(fileType) { + const fileCategories = { + 'image/': { category: 'image', icon: '' }, + 'video/': { category: 'video', icon: '' }, + 'audio/': { category: 'audio', icon: '' }, + 'application/pdf': { category: 'pdf', icon: '' }, + 'application/msword': { category: 'doc', icon: '' }, + 'application/vnd.ms-excel': { category: 'xls', icon: '' }, + 'application/vnd.ms-powerpoint': { category: 'ppt', icon: '' }, + 'text/': { category: 'text', icon: '' }, + 'application/xml': { category: 'xml', icon: '' }, + 'application/zip': { category: 'archive', icon: '' }, + 'application/x-rar-compressed': { category: 'archive', icon: '' }, + 'application/x-7z-compressed': { category: 'archive', icon: '' }, + 'default': { category: 'unknown', icon: '' } + }; + + const categoryKeys = Object.keys(fileCategories); + const matchedKey = categoryKeys.find(key => fileType.startsWith(key)) || 'default'; + return fileCategories[matchedKey]; + }, + createFileHash: function() { + var timestamp = new Date().getTime(); + var random = Math.floor(Math.random() * 1000000); + var fileHash = timestamp + random; + + return fileHash; + }, + formatFileSize: function(sizeInBytes) { + var sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + var i = 0; + while (sizeInBytes >= settings.filesizeBase && i < sizes.length - 1) { + sizeInBytes /= settings.filesizeBase; + i++; + } + return sizeInBytes.toFixed(2) + ' ' + sizes[i]; + }, + translateMessages: function(errorCode, variables) { + let message = 'Undefined error.'; + if (settings.lang.errors[errorCode]) { + message = settings.lang.errors[errorCode]; + for (var key in variables) { + message = message.replace('{' + key + '}', variables[key]); + } + } + return message; + }, + enqueueFiles: function() { + for (let i = 0; i < filesQueue.length; i++) { + const fileObject = filesQueue[i]; + if (fileObject.status === 'added') { + console.log(fileObject); + const $progressBar = $(` +
+
+
+ `); + $(fileObject.$element).find('.df-file-data').append($progressBar); + $(fileObject.$element).find('.df-file-status').html('0%'); + + filesQueue[i].$progressBar = $progressBar; + filesQueue[i].status = 'enqueued'; + } + } + console.log(filesQueue); + } + }; + + var methods = { + init: function() { + if ($element.prop('tagName').toString().toLowerCase() !== 'div') { + return $.error('The element is not a div tag'); + } + const $dfContainerDiv = $(` +
+
+
${settings.lang.message}
+
+
+
+ `); + $element.html($dfContainerDiv); + var $dropArea = $element.find('.df-drop-area'); + $dropArea.on('click', function(e) { + var $fileInput = $(''); + $fileInput.on('change', function(e) { + uploadedFiles = e.target.files; + // settings.onDrop(uploadedFiles); + methods.addFiles(uploadedFiles); + }); + $fileInput.click(); + }); + if (settings.droppable) { + $dropArea.on('dragover dragenter', function(e) { + e.preventDefault(); + e.stopPropagation(); + }).on('drop', function(e) { + e.preventDefault(); + uploadedFiles = e.originalEvent.dataTransfer.files; + // settings.onDrop(uploadedFiles); + methods.addFiles(uploadedFiles); + }); + } else { + $dropArea.on('drop dragover dragenter', function(e) { + e.preventDefault(); + e.stopPropagation(); + }); + } + console.log('dropfilesUploader init'); + }, + destroy: function() { + console.log('dropfilesUploader destroy'); + }, + restart: function() { + console.log('dropfilesUploader restart'); + }, + enqueueFiles: function() { + + }, + proccessQueue: function() { + + }, + addFile: function(file) { + const maxFilesize = settings.maxFilesize; + const filesizeBase = settings.filesizeBase; + const maxSizeBytes = maxFilesize * filesizeBase * filesizeBase; + var fileStatus = 'accepted'; + var errors = []; + const fileHash = privateFunctions.createFileHash(); + + if (file.size > maxSizeBytes) { + fileStatus = 'error'; + errors.push(privateFunctions.translateMessages('errors.maxFilesize', {filesize: privateFunctions.formatFileSize(file.size), maxFilesize: privateFunctions.formatFileSize(maxSizeBytes)})) + } + + const acceptedFiles = settings.acceptedFiles; + const fileExtension = file.name.split('.').pop().toLowerCase(); + if (acceptedFiles && acceptedFiles.split(',').map(ext => ext.trim()).indexOf(fileExtension) === -1) { + fileStatus = 'error'; + errors.push(privateFunctions.translateMessages('errors.acceptedFiles', {acceptedFiles: acceptedFiles, fileExtension: fileExtension})) + } + + filesQueue.push({ + file: file, + status: fileStatus, + hash: fileHash, + errors, errors + }); + }, + addFiles: function(files) { + for (let i = 0; i < files.length; i++) { + const file = files[i]; + methods.addFile(file); + } + + privateFunctions.appendFilesToList(); + }, + getFiles: function() { + return filesQueue; + }, + getFile: function(hash) { + + }, + removeFiles: function() { + + }, + removeFile: function(hash) { + console.log('removeFile'); + }, + abortUpload: function() { + + }, + }; + + return { + init: methods.init, + destroy: methods.destroy, + restart: methods.restart, + enqueueFiles: methods.enqueueFiles, + proccessQueue: methods.proccessQueue, + addFiles: methods.addFiles, + getFiles: methods.getFiles, + getFile: methods.getFile, + removeFiles: methods.removeFiles, + removeFile: methods.removeFile, + abortUpload: methods.abortUpload, + }; + }; + + $.fn.dropfilesUploader = function(methodOrOptions, args) { + var pluginInstance = this.data('dropfilesUploader'); + if (typeof methodOrOptions === 'object' || !methodOrOptions) { + if (!pluginInstance) { + pluginInstance = new plugin(this, methodOrOptions); + this.data('dropfilesUploader', pluginInstance); + pluginInstance.init(); + } + return pluginInstance; + } else if (typeof pluginInstance[methodOrOptions] === 'function') { + return pluginInstance[methodOrOptions].apply(pluginInstance, args); + } else { + $.error('Method ['+methodOrOptions+'] does not exist or is not a function. Please read the documentation!'); + } + }; +})(jQuery); \ No newline at end of file From 00117f8f6728d0c54d2342c55c2d2a818f73b672 Mon Sep 17 00:00:00 2001 From: Robson Wenzel Date: Thu, 18 Apr 2024 02:13:01 -0300 Subject: [PATCH 2/7] saving --- index.html | 3 +- src/css/dropfilesuploader.css | 1 + src/js/dropfilesuploader.js | 112 +++++++++++++++++++++++++++------- 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index d5aef4a..0f1252c 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,8 @@ diff --git a/src/css/dropfilesuploader.css b/src/css/dropfilesuploader.css index 1148c46..2ce0571 100644 --- a/src/css/dropfilesuploader.css +++ b/src/css/dropfilesuploader.css @@ -79,6 +79,7 @@ flex-direction: row; flex-wrap: nowrap; justify-content: space-between; + align-items: center; gap: 8px; } diff --git a/src/js/dropfilesuploader.js b/src/js/dropfilesuploader.js index fb380b9..241cd32 100644 --- a/src/js/dropfilesuploader.js +++ b/src/js/dropfilesuploader.js @@ -33,12 +33,25 @@ acceptedFiles: 'Unsupported file extension. The file must have the extension .{fileExtension}, supported extensions are [{acceptedFiles}].', } }, - events: {} + events: { + onInit: () => {}, + onDestroy: () => {}, + onRestart: () => {}, + onFileAccepted: () => {}, + onFileError: () => {}, + onFileAdded: () => {}, + onFileEnqueued: () => {}, + onUploadSuccess: () => {}, + onUploadError: () => {}, + onUploadProgress: () => {}, + } }, options); var privateFunctions = { - debugLog: function() { - + debugLog: function(log) { + if (settings.debug) { + console.log(log); + } }, appendFilesToList: async function() { const $fileListDiv = $element.find('.df-files'); @@ -87,11 +100,11 @@ \ \ '); - const $fileListItem = $fileListDiv.append(template); + $fileListDiv.append(template); filesQueue[i].status = newStatus; filesQueue[i].displayed = true; - filesQueue[i].$element = $fileListItem; + filesQueue[i].$element = template; } privateFunctions.enqueueFiles(); @@ -166,24 +179,76 @@ return message; }, enqueueFiles: function() { - for (let i = 0; i < filesQueue.length; i++) { - const fileObject = filesQueue[i]; - if (fileObject.status === 'added') { - console.log(fileObject); - const $progressBar = $(` -
-
-
- `); - $(fileObject.$element).find('.df-file-data').append($progressBar); - $(fileObject.$element).find('.df-file-status').html('0%'); + if (settings.url) { + for (let i = 0; i < filesQueue.length; i++) { + const fileObject = filesQueue[i]; + if (fileObject.status === 'added') { + const $progressBar = $(` +
+
+
+ `); + $(fileObject.$element).find('.df-file-data').append($progressBar); + $(fileObject.$element).find('.df-file-status').html('0%'); + + filesQueue[i].$progressBar = $progressBar; + filesQueue[i].status = 'enqueued'; + } + } + + privateFunctions.proccessQueue(); + } else { + for (let i = 0; i < filesQueue.length; i++) { + const fileObject = filesQueue[i]; + if (fileObject.status === 'added') { + let newStatus = (fileObject.status === 'added' ? 'success' : 'error'); + let statusIcon = privateFunctions.getStatusIcon(newStatus); + const fileList = new DataTransfer(); + fileList.items.add(fileObject.file); + const $inputFile = $(``); - filesQueue[i].$progressBar = $progressBar; - filesQueue[i].status = 'enqueued'; + $inputFile[0].files = fileList.files; + $(fileObject.$element).find('.df-file-data').append($inputFile); + $(fileObject.$element).find('.df-file-status').html(statusIcon); + + filesQueue[i].status = newStatus; + } } } - console.log(filesQueue); - } + }, + proccessQueue: function() { + const multipleUpload = settings.request.multipleUpload; + const parallelUploads = settings.request.parallelUploads; + const enqueuedFiles = filesQueue.filter(file => file.status === 'enqueued'); + enqueuedFiles.forEach(enqueuedFile => { + const fileToUpdate = filesQueue.find(file => file.hash === enqueuedFile.hash); + if (fileToUpdate) { + fileToUpdate.status = 'processing'; + } + }); + const filesToSend = []; + + if (!multipleUpload) { + for (let i = 0; i < enqueuedFiles.length; i++) { + filesToSend.push(enqueuedFiles[i]); + } + privateFunctions.sendRequest(filesToSend); + } else if (parallelUploads !== null && parallelUploads > 0) { + for (let i = 0; i < enqueuedFiles.length; i += parallelUploads) { + const chunk = enqueuedFiles.slice(i, i + parallelUploads); + const chunkFiles = chunk.map(item => item); + privateFunctions.sendRequest(chunkFiles); + } + } else { + for (let i = 0; i < enqueuedFiles.length; i++) { + const file = enqueuedFiles[i]; + privateFunctions.sendRequest([file]); + } + } + }, + sendRequest: function(files) { + console.log('Sending request with files:', files); + }, }; var methods = { @@ -226,13 +291,14 @@ e.stopPropagation(); }); } - console.log('dropfilesUploader init'); + + privateFunctions.debugLog('dropfilesUploader init'); }, destroy: function() { - console.log('dropfilesUploader destroy'); + privateFunctions.debugLog('dropfilesUploader destroy'); }, restart: function() { - console.log('dropfilesUploader restart'); + privateFunctions.debugLog('dropfilesUploader restart'); }, enqueueFiles: function() { From 9d4b24448ab74b3e8b56933ac99de48c47968065 Mon Sep 17 00:00:00 2001 From: Robson Wenzel Date: Mon, 22 Apr 2024 23:41:21 -0300 Subject: [PATCH 3/7] saving --- index.html | 2 +- src/css/dropfilesuploader.css | 9 +++ src/js/dropfilesuploader.js | 140 +++++++++++++++++++++++++++++++--- 3 files changed, 140 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index 0f1252c..66d911d 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@ diff --git a/src/css/dropfilesuploader.css b/src/css/dropfilesuploader.css index 2ce0571..a9fd515 100644 --- a/src/css/dropfilesuploader.css +++ b/src/css/dropfilesuploader.css @@ -67,11 +67,14 @@ width: 100%; background-color: #f1f1f1; border-radius: 18px; + overflow: hidden; } .df-file-progress-bar { height: 4px; width: 0%; + background-color: #ccc; + transition: background-color 0.5s ease; } .df-file-info { @@ -89,6 +92,7 @@ color: #343434; gap: 4px; min-width: 0; + width: 100%; } .df-file-name span { @@ -109,5 +113,10 @@ justify-content: center; padding: 4px; width: 32px; + min-width: 32px; height: 32px; +} + +.df-file-status svg { + width: 100%; } \ No newline at end of file diff --git a/src/js/dropfilesuploader.js b/src/js/dropfilesuploader.js index 241cd32..cd8b359 100644 --- a/src/js/dropfilesuploader.js +++ b/src/js/dropfilesuploader.js @@ -9,13 +9,14 @@ method: 'POST', timeout: null, headers: null, + withCredentials: false, multipleUpload: false, parallelUploads: 2, }, droppable: true, paramName: 'file', maxFiles: null, - maxFilesize: 5, + maxFilesize: null, filesizeBase: 1000, acceptedFiles: null, debug: false, @@ -25,7 +26,9 @@ height: 60, object_fit: 'cover', }, - styles: {}, + styles: { + progressBarColor: 'rgba(245,158,11,1)', + }, lang: { message: 'Drag or click to add files here', errors: { @@ -169,13 +172,22 @@ return sizeInBytes.toFixed(2) + ' ' + sizes[i]; }, translateMessages: function(errorCode, variables) { + let prefix = errorCode; let message = 'Undefined error.'; - if (settings.lang.errors[errorCode]) { - message = settings.lang.errors[errorCode]; + if (errorCode.includes('.')) { + prefix = errorCode.split('.')[0]; + target = errorCode.split('.')[1]; + } + if (settings.lang[prefix] !== undefined) { + message = settings.lang[prefix]; + if (typeof settings.lang[prefix] === 'object') { + message = settings.lang[prefix][target]; + } for (var key in variables) { message = message.replace('{' + key + '}', variables[key]); } } + return message; }, enqueueFiles: function() { @@ -196,7 +208,9 @@ } } - privateFunctions.proccessQueue(); + if (settings.autoProcessQueue) { + privateFunctions.proccessQueue(); + } } else { for (let i = 0; i < filesQueue.length; i++) { const fileObject = filesQueue[i]; @@ -233,7 +247,7 @@ filesToSend.push(enqueuedFiles[i]); } privateFunctions.sendRequest(filesToSend); - } else if (parallelUploads !== null && parallelUploads > 0) { + } else if (parallelUploads && parallelUploads > 0) { for (let i = 0; i < enqueuedFiles.length; i += parallelUploads) { const chunk = enqueuedFiles.slice(i, i + parallelUploads); const chunkFiles = chunk.map(item => item); @@ -248,6 +262,109 @@ }, sendRequest: function(files) { console.log('Sending request with files:', files); + + var formData = new FormData(); + + if (!settings.maxFiles || settings.maxFiles > 1) { + for (var i = 0; i < files.length; i++) { + formData.append(settings.paramName+'[]', files[i].file); + } + } else { + formData.append(settings.paramName, files.file); + } + + var xhr = new XMLHttpRequest(); + + xhr.timeout = settings.request.timeout??0; + + xhr.upload.addEventListener('progress', function(event) { + console.log(event); + if (event.lengthComputable) { + files.forEach(function(file) { + if (file.status === 'processing') { + console.log(file, event.loaded, file.file.size); + var percentComplete = Math.floor((event.loaded / event.total) * 100); + privateFunctions.updateFileProgress(file, percentComplete); + } + }); + } + }); + + xhr.addEventListener('load', function() { + if (xhr.status === 200) { + files.forEach(function(file) { + if (file.status === 'processing') { + file.status = 'success'; + privateFunctions.updateFileProgress(file, 100); + } + }); + } else { + files.forEach(function(file) { + if (file.status === 'processing') { + file.status = 'error'; + privateFunctions.updateFileProgress(file, 100); + } + }); + } + + // console.log('Request concluída:', xhr.responseText); + }); + + xhr.addEventListener('timeout', function() { + console.log('Timeout da request'); + }); + + xhr.addEventListener('error', function() { + console.log('Erro na request'); + // Aqui você pode adicionar o código para lidar com a falha da request + // Por exemplo, atualizar o status dos arquivos para error + files.forEach(function(file) { + if (file.status === 'processing') { + file.status = 'error'; + privateFunctions.updateFileProgress(file, 100); + } + }); + }); + + xhr.open(settings.request.method, settings.url, true); + + console.log('settings.request.headers', settings.request.headers); + if ( + (settings.request.headers) && + (typeof settings.request.headers === 'object') + ) { + for (const key in settings.request.headers) { + const header = settings.request.headers[key]; + xhr.setRequestHeader(key, header); + } + } + + xhr.setRequestHeader('Cache-Control', 'no-cache'); + + xhr.send(formData); + }, + updateFileProgress: function(file, percentage) { + if (file.status === 'processing') { + file.$element.find('.df-file-status').html(percentage+'%'); + } else { + file.$element.find('.df-file-status').html(privateFunctions.getStatusIcon(file.status)); + } + privateFunctions.updateRequestProgressBar(file, percentage); + }, + updateRequestProgressBar: function(file, percentage) { + console.log(file, percentage); + let bgColor = ''; + if (file.status === 'processing') { + bgColor = settings.styles.progressBarColor; + } else { + percentage = 100; + bgColor = (file.status === 'success' ? 'rgba(61,178,148,1)' : 'rgba(239,68,68,1)') + } + + file.$progressBar.children().css({ + 'width': percentage + '%', + 'background-color': bgColor + }); }, }; @@ -301,10 +418,10 @@ privateFunctions.debugLog('dropfilesUploader restart'); }, enqueueFiles: function() { - + privateFunctions.enqueueFiles(); }, proccessQueue: function() { - + privateFunctions.proccessQueue(); }, addFile: function(file) { const maxFilesize = settings.maxFilesize; @@ -314,7 +431,10 @@ var errors = []; const fileHash = privateFunctions.createFileHash(); - if (file.size > maxSizeBytes) { + if ( + settings.maxFilesize && + file.size > maxSizeBytes + ) { fileStatus = 'error'; errors.push(privateFunctions.translateMessages('errors.maxFilesize', {filesize: privateFunctions.formatFileSize(file.size), maxFilesize: privateFunctions.formatFileSize(maxSizeBytes)})) } @@ -345,7 +465,7 @@ return filesQueue; }, getFile: function(hash) { - + return filesQueue.filter(file => file.hash === hash); }, removeFiles: function() { From 64609aa1dd73dd25958b55749a538b11ac3b2e51 Mon Sep 17 00:00:00 2001 From: Robson Wenzel Date: Thu, 25 Apr 2024 02:58:30 -0300 Subject: [PATCH 4/7] saving --- index.html | 5 +- src/css/dropfilesuploader.css | 1 + src/js/dropfilesuploader.js | 242 +++++++++++++++++++++++----------- 3 files changed, 169 insertions(+), 79 deletions(-) diff --git a/index.html b/index.html index 66d911d..806ed50 100644 --- a/index.html +++ b/index.html @@ -6,15 +6,14 @@ Dropfiles Uploader Example - +
diff --git a/src/css/dropfilesuploader.css b/src/css/dropfilesuploader.css index a9fd515..4a6a4cd 100644 --- a/src/css/dropfilesuploader.css +++ b/src/css/dropfilesuploader.css @@ -56,6 +56,7 @@ .df-file-data { width: 100%; padding: 12px 8px; + padding-right: 0; min-width: 0; display: flex; flex-direction: column; diff --git a/src/js/dropfilesuploader.js b/src/js/dropfilesuploader.js index cd8b359..18fc02f 100644 --- a/src/js/dropfilesuploader.js +++ b/src/js/dropfilesuploader.js @@ -9,6 +9,7 @@ method: 'POST', timeout: null, headers: null, + customParams: null, withCredentials: false, multipleUpload: false, parallelUploads: 2, @@ -28,6 +29,11 @@ }, styles: { progressBarColor: 'rgba(245,158,11,1)', + icons: { + added: null, + error: null, + success: null + } }, lang: { message: 'Drag or click to add files here', @@ -40,13 +46,13 @@ onInit: () => {}, onDestroy: () => {}, onRestart: () => {}, - onFileAccepted: () => {}, - onFileError: () => {}, - onFileAdded: () => {}, - onFileEnqueued: () => {}, - onUploadSuccess: () => {}, - onUploadError: () => {}, - onUploadProgress: () => {}, + onFileAccepted: (fileObject) => {}, + onFileError: (fileObject) => {}, + onFileAdded: (fileObject) => {}, + onFileEnqueued: (fileObject) => {}, + onUploadSuccess: (fileObject, response, status) => {}, + onUploadError: (fileObject, response, status) => {}, + onUploadProgress: (fileObject, percentage) => {}, } }, options); @@ -56,6 +62,49 @@ console.log(log); } }, + addFile: function (file) { + const maxFilesize = settings.maxFilesize; + const filesizeBase = settings.filesizeBase; + const maxSizeBytes = maxFilesize * filesizeBase * filesizeBase; + if ( + settings.maxFiles && + filesQueue.length >= settings.maxFiles + ) { + return false; + } + var fileStatus = 'accepted'; + var errors = []; + const fileHash = privateFunctions.createFileHash(); + + if ( + settings.maxFilesize && + file.size > maxSizeBytes + ) { + fileStatus = 'error'; + errors.push(privateFunctions.translateMessages('errors.maxFilesize', {filesize: privateFunctions.formatFileSize(file.size), maxFilesize: privateFunctions.formatFileSize(maxSizeBytes)})) + } + + const acceptedFiles = settings.acceptedFiles; + const fileExtension = file.name.split('.').pop().toLowerCase(); + if (acceptedFiles && acceptedFiles.split(',').map(ext => ext.trim()).indexOf(fileExtension) === -1) { + fileStatus = 'error'; + errors.push(privateFunctions.translateMessages('errors.acceptedFiles', {acceptedFiles: acceptedFiles, fileExtension: fileExtension})) + } + + const fileObject = { + file: file, + status: fileStatus, + hash: fileHash, + errors, errors + }; + if (fileStatus === 'accepted') { + settings.events.onFileAccepted(fileObject); + } else { + settings.events.onFileError(fileObject); + } + privateFunctions.debugLog({'addFile': fileObject}); + filesQueue.push(fileObject); + }, appendFilesToList: async function() { const $fileListDiv = $element.find('.df-files'); for (let i = 0; i < filesQueue.length; i++) { @@ -108,6 +157,8 @@ filesQueue[i].status = newStatus; filesQueue[i].displayed = true; filesQueue[i].$element = template; + settings.events.onFileAdded(filesQueue[i]); + privateFunctions.debugLog({'appendFileToList': filesQueue[i]}); } privateFunctions.enqueueFiles(); @@ -127,9 +178,9 @@ }, getStatusIcon: function(status) { var statusIcons = { - added: '', - error: '', - success: '', + added: settings.styles.icons.added ?? '', + error: settings.styles.icons.error ?? '', + success: settings.styles.icons.success ?? '', }; return statusIcons[status] || ''; @@ -205,6 +256,8 @@ filesQueue[i].$progressBar = $progressBar; filesQueue[i].status = 'enqueued'; + settings.events.onFileEnqueued(filesQueue[i]); + privateFunctions.debugLog({'enqueueFile': filesQueue[i]}); } } @@ -226,6 +279,8 @@ $(fileObject.$element).find('.df-file-status').html(statusIcon); filesQueue[i].status = newStatus; + settings.events.onFileEnqueued(filesQueue[i]); + privateFunctions.debugLog({'enqueueFile': filesQueue[i]}); } } } @@ -234,12 +289,16 @@ const multipleUpload = settings.request.multipleUpload; const parallelUploads = settings.request.parallelUploads; const enqueuedFiles = filesQueue.filter(file => file.status === 'enqueued'); + if (enqueuedFiles.length === 0) { + return false; + } enqueuedFiles.forEach(enqueuedFile => { const fileToUpdate = filesQueue.find(file => file.hash === enqueuedFile.hash); if (fileToUpdate) { fileToUpdate.status = 'processing'; } }); + privateFunctions.debugLog({'proccessQueue': enqueuedFiles}); const filesToSend = []; if (!multipleUpload) { @@ -261,8 +320,7 @@ } }, sendRequest: function(files) { - console.log('Sending request with files:', files); - + privateFunctions.debugLog({'sendRequest': files}); var formData = new FormData(); if (!settings.maxFiles || settings.maxFiles > 1) { @@ -275,27 +333,33 @@ var xhr = new XMLHttpRequest(); - xhr.timeout = settings.request.timeout??0; + files.forEach(function(file) { + file.xhr = xhr; + }); + + xhr.timeout = settings.request.timeout??0; + xhr.withCredentials = settings.request.withCredentials; xhr.upload.addEventListener('progress', function(event) { - console.log(event); if (event.lengthComputable) { files.forEach(function(file) { if (file.status === 'processing') { - console.log(file, event.loaded, file.file.size); var percentComplete = Math.floor((event.loaded / event.total) * 100); privateFunctions.updateFileProgress(file, percentComplete); + settings.events.onUploadProgress(file, percentComplete); + privateFunctions.debugLog({'xhr_progress': [file, percentComplete]}); } }); } }); xhr.addEventListener('load', function() { - if (xhr.status === 200) { + if ([200, 201, 202, 203, 204, 205, 206, 207, 208, 226].includes(xhr.status)) { files.forEach(function(file) { if (file.status === 'processing') { file.status = 'success'; privateFunctions.updateFileProgress(file, 100); + settings.events.onUploadSuccess(file, xhr.response, xhr.status); } }); } else { @@ -303,32 +367,48 @@ if (file.status === 'processing') { file.status = 'error'; privateFunctions.updateFileProgress(file, 100); + settings.events.onUploadError(file, xhr.response, xhr.status); } }); } - - // console.log('Request concluída:', xhr.responseText); + privateFunctions.debugLog({'xhr_load': [files, xhr]}); }); xhr.addEventListener('timeout', function() { - console.log('Timeout da request'); + files.forEach(function(file) { + if (file.status === 'processing') { + file.status = 'error'; + privateFunctions.updateFileProgress(file, 100); + settings.events.onUploadError(file, xhr.response, xhr.status); + } + }); + privateFunctions.debugLog({'xhr_timeout': [files, xhr]}); }); xhr.addEventListener('error', function() { - console.log('Erro na request'); - // Aqui você pode adicionar o código para lidar com a falha da request - // Por exemplo, atualizar o status dos arquivos para error files.forEach(function(file) { if (file.status === 'processing') { file.status = 'error'; privateFunctions.updateFileProgress(file, 100); + settings.events.onUploadError(file, xhr.response, xhr.status); + } + }); + privateFunctions.debugLog({'xhr_error': [files, xhr]}); + }); + + xhr.addEventListener('abort', function() { + files.forEach(function(file) { + if (file.status === 'processing') { + file.status = 'error'; + privateFunctions.updateFileProgress(file, 100); + settings.events.onUploadError(file, xhr.response, xhr.status); } }); + privateFunctions.debugLog({'xhr_abort': [files, xhr]}); }); xhr.open(settings.request.method, settings.url, true); - console.log('settings.request.headers', settings.request.headers); if ( (settings.request.headers) && (typeof settings.request.headers === 'object') @@ -341,6 +421,16 @@ xhr.setRequestHeader('Cache-Control', 'no-cache'); + if ( + (settings.request.customParams) && + (typeof settings.request.customParams === 'object') + ) { + for (const key in settings.request.customParams) { + const customParamValue = settings.request.customParams[key]; + formData.append(key, customParamValue); + } + } + xhr.send(formData); }, updateFileProgress: function(file, percentage) { @@ -352,7 +442,6 @@ privateFunctions.updateRequestProgressBar(file, percentage); }, updateRequestProgressBar: function(file, percentage) { - console.log(file, percentage); let bgColor = ''; if (file.status === 'processing') { bgColor = settings.styles.progressBarColor; @@ -384,11 +473,15 @@ $element.html($dfContainerDiv); var $dropArea = $element.find('.df-drop-area'); $dropArea.on('click', function(e) { - var $fileInput = $(''); + var $fileInput = $(''); $fileInput.on('change', function(e) { uploadedFiles = e.target.files; - // settings.onDrop(uploadedFiles); - methods.addFiles(uploadedFiles); + for (let i = 0; i < uploadedFiles.length; i++) { + const file = uploadedFiles[i]; + privateFunctions.addFile(file); + } + + privateFunctions.appendFilesToList(); }); $fileInput.click(); }); @@ -399,8 +492,12 @@ }).on('drop', function(e) { e.preventDefault(); uploadedFiles = e.originalEvent.dataTransfer.files; - // settings.onDrop(uploadedFiles); - methods.addFiles(uploadedFiles); + for (let i = 0; i < uploadedFiles.length; i++) { + const file = uploadedFiles[i]; + privateFunctions.addFile(file); + } + + privateFunctions.appendFilesToList(); }); } else { $dropArea.on('drop dragover dragenter', function(e) { @@ -409,72 +506,66 @@ }); } + settings.events.onInit(); privateFunctions.debugLog('dropfilesUploader init'); }, destroy: function() { + methods.abortUploads(); + methods.removeFiles(); + $element.children().remove(); + settings.events.onDestroy(); privateFunctions.debugLog('dropfilesUploader destroy'); }, restart: function() { + methods.destroy(); + methods.init(); + settings.events.onRestart(); privateFunctions.debugLog('dropfilesUploader restart'); }, enqueueFiles: function() { privateFunctions.enqueueFiles(); + return true; }, proccessQueue: function() { privateFunctions.proccessQueue(); - }, - addFile: function(file) { - const maxFilesize = settings.maxFilesize; - const filesizeBase = settings.filesizeBase; - const maxSizeBytes = maxFilesize * filesizeBase * filesizeBase; - var fileStatus = 'accepted'; - var errors = []; - const fileHash = privateFunctions.createFileHash(); - - if ( - settings.maxFilesize && - file.size > maxSizeBytes - ) { - fileStatus = 'error'; - errors.push(privateFunctions.translateMessages('errors.maxFilesize', {filesize: privateFunctions.formatFileSize(file.size), maxFilesize: privateFunctions.formatFileSize(maxSizeBytes)})) - } - - const acceptedFiles = settings.acceptedFiles; - const fileExtension = file.name.split('.').pop().toLowerCase(); - if (acceptedFiles && acceptedFiles.split(',').map(ext => ext.trim()).indexOf(fileExtension) === -1) { - fileStatus = 'error'; - errors.push(privateFunctions.translateMessages('errors.acceptedFiles', {acceptedFiles: acceptedFiles, fileExtension: fileExtension})) - } - - filesQueue.push({ - file: file, - status: fileStatus, - hash: fileHash, - errors, errors - }); - }, - addFiles: function(files) { - for (let i = 0; i < files.length; i++) { - const file = files[i]; - methods.addFile(file); - } - - privateFunctions.appendFilesToList(); + return true; }, getFiles: function() { - return filesQueue; + return filesQueue.map(function (file) { + return { ...file }; + }); }, getFile: function(hash) { - return filesQueue.filter(file => file.hash === hash); + var file = filesQueue.find(function (f) { + return f.hash === hash; + }); + if (file) { + return { ...file }; + } + return null; }, removeFiles: function() { - + methods.abortUploads(); + filesQueue.length = 0; + $element.find('.df-files').children().remove(); + return true; }, removeFile: function(hash) { - console.log('removeFile'); + for (let i = 0; i < filesQueue.length; i++) { + if (filesQueue[i].hash === hash) { + filesQueue[i].xhr.abort(); + filesQueue[i].$element.remove(); + filesQueue.splice(i, 1); + break; + } + } + return true; }, - abortUpload: function() { - + abortUploads: function() { + for (let i = 0; i < filesQueue.length; i++) { + filesQueue[i].xhr.abort(); + } + return true; }, }; @@ -484,12 +575,11 @@ restart: methods.restart, enqueueFiles: methods.enqueueFiles, proccessQueue: methods.proccessQueue, - addFiles: methods.addFiles, getFiles: methods.getFiles, getFile: methods.getFile, removeFiles: methods.removeFiles, removeFile: methods.removeFile, - abortUpload: methods.abortUpload, + abortUploads: methods.abortUploads, }; }; @@ -503,7 +593,7 @@ } return pluginInstance; } else if (typeof pluginInstance[methodOrOptions] === 'function') { - return pluginInstance[methodOrOptions].apply(pluginInstance, args); + return pluginInstance[methodOrOptions].call(pluginInstance, args); } else { $.error('Method ['+methodOrOptions+'] does not exist or is not a function. Please read the documentation!'); } From 595159a91fe2d7197c72bab549beb332845fc477 Mon Sep 17 00:00:00 2001 From: Robson Wenzel <93221078+rwenzel22@users.noreply.github.com> Date: Fri, 26 Apr 2024 00:50:12 -0300 Subject: [PATCH 5/7] Create FUNDING.yml --- .github/FUNDING.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..479ccbb --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: rwenzel22 +buy_me_a_coffee: rwenzel From d18e9bc850da365eb111497be11ee63cb819c74c Mon Sep 17 00:00:00 2001 From: Robson Wenzel Date: Fri, 26 Apr 2024 01:17:42 -0300 Subject: [PATCH 6/7] saving --- README.md | 21 ++++++++++++++++++++- index.html | 4 +--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 508baf9..be57fa1 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ -# drop-files \ No newline at end of file +# Dropfiles Uploader + +Dropfiles Uploader é um plugin jQuery simples e altamente customizável que ajuda você a realizar o envio de arquivos em seus formulários da web. + +- [📚 Documentação](https://dropfiles-uploader.gitbook.io) + +## Diferenciais do Dropfiles Uploader ✅ + +- Envio de arquivos via submit ou Api. +- Validação de extensão e tamanho de arquivos. +- Suporte a traduções. +- Suporte a arrastar e soltar arquivos. +- Suporte a eventos. +- Debug mode. +- Suporte a thumbnails de imagens. +- Suporte a envio de parâmetros personalizados na envio da request. + +# Licença MIT + +- [Licença](https://github.com/rwenzel22/drop-files-uploader/blob/main/LICENSE) \ No newline at end of file diff --git a/index.html b/index.html index 806ed50..232c7ac 100644 --- a/index.html +++ b/index.html @@ -12,9 +12,7 @@ \ No newline at end of file From b2c00598689360908ef7308591ad5671ff48d854 Mon Sep 17 00:00:00 2001 From: Robson Wenzel <93221078+rwenzel22@users.noreply.github.com> Date: Fri, 26 Apr 2024 02:15:44 -0300 Subject: [PATCH 7/7] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f3d5c41 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here.