active_storage_drag_and_drop 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'active_storage_drag_and_drop/version'
@@ -8,9 +10,9 @@ Gem::Specification.new do |spec|
8
10
  spec.authors = ["Dave O'Keeffe", 'Ian Grant']
9
11
  spec.email = ['ian.grant@marinosoftware.com']
10
12
 
11
- spec.summary = 'Provides js drag and drop file upload functionality for active storage.'
13
+ spec.summary = 'Provides JS drag and drop file upload functionality for active storage.'
12
14
  spec.description = 'Provides a form helper to make it easy to make drag and drop file upload'\
13
- "fields that work with Rails' Active Storage."
15
+ " fields that work with Rails' Active Storage."
14
16
  spec.homepage = 'https://github.com/marinosoftware/active_storage_drag_and_drop'
15
17
  spec.license = 'MIT'
16
18
 
@@ -29,9 +31,24 @@ Gem::Specification.new do |spec|
29
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
32
  spec.require_paths = ['lib']
31
33
 
34
+ spec.add_development_dependency 'bootsnap'
32
35
  spec.add_development_dependency 'bundler', '~> 1.16'
36
+ spec.add_development_dependency 'capybara'
37
+ spec.add_development_dependency 'geckodriver-helper'
38
+ spec.add_development_dependency 'github-markup'
39
+ spec.add_development_dependency 'haml'
40
+ spec.add_development_dependency 'listen'
33
41
  spec.add_development_dependency 'minitest', '~> 5.0'
42
+ spec.add_development_dependency 'nokogiri'
43
+ spec.add_development_dependency 'pry'
44
+ spec.add_development_dependency 'pry-byebug'
45
+ spec.add_development_dependency 'puma'
34
46
  spec.add_development_dependency 'rake', '~> 10.0'
47
+ spec.add_development_dependency 'redcarpet'
48
+ spec.add_development_dependency 'rubocop'
49
+ spec.add_development_dependency 'selenium-webdriver'
50
+ spec.add_development_dependency 'simplecov'
51
+ spec.add_development_dependency 'sqlite3'
35
52
  spec.add_dependency 'rack', '~> 2.0.6'
36
53
  spec.add_dependency 'rails', '~> 5.2'
37
54
  end
@@ -104,7 +104,7 @@ return /******/ (function(modules) { // webpackBootstrap
104
104
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
105
105
 
106
106
  "use strict";
107
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"DragAndDropUploadController\", function() { return DragAndDropUploadController; });\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers */ \"./app/javascript/active_storage_drag_and_drop/helpers.js\");\n/* harmony import */ var activestorage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! activestorage */ \"./node_modules/activestorage/app/assets/javascripts/activestorage.js\");\n/* harmony import */ var activestorage__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(activestorage__WEBPACK_IMPORTED_MODULE_1__);\n\n\nconst eventFamily = 'dnd-upload';\n\nclass DragAndDropUploadController {\n constructor(input, file) {\n this.input = input;\n this.form = input.closest('form');\n this.url = this.input.dataset.directUploadUrl;\n this.iconContainer = document.getElementById(this.input.dataset.iconContainerId);\n this.file = file;\n this.upload = new activestorage__WEBPACK_IMPORTED_MODULE_1__[\"DirectUpload\"](this.file, this.url, this);\n this.dispatch(\"initialize\");\n }\n\n start(callback) {\n this.upload.create((error, blob) => {\n if (error) {\n // Handle the error\n this.dispatchError(error);\n callback(error);\n } else {\n // // Add an appropriately-named hidden input to the form with a\n // // value of blob.signed_id so that the blob ids will be\n // // transmitted in the normal upload flow\n const hiddenField = document.createElement('input');\n hiddenField.setAttribute(\"type\", \"hidden\");\n hiddenField.setAttribute(\"value\", blob.signed_id);\n hiddenField.name = this.input.name;\n hiddenField.setAttribute('data-direct-upload-id', this.upload.id);\n this.form.appendChild(hiddenField);\n this.dispatch(\"end\");\n callback(error);\n }\n });\n }\n\n dispatch(name, detail = {}) {\n detail.file = this.file;\n detail.id = this.upload.id;\n detail.iconContainer = this.iconContainer;\n return Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"dispatchEvent\"])(this.input, `${eventFamily}:${name}`, { detail });\n }\n\n dispatchError(error) {\n const event = this.dispatch(\"error\", { error });\n if (!event.defaultPrevented) {\n alert(error);\n }\n }\n\n directUploadWillCreateBlobWithXHR(xhr) {\n this.dispatch(\"before-blob-request\", { xhr });\n }\n // directUploadWillStoreFileWithXHR\n directUploadWillStoreFileWithXHR(xhr) {\n this.dispatch(\"before-storage-request\", { xhr });\n xhr.upload.addEventListener(\"progress\", event => this.uploadRequestDidProgress(event));\n }\n\n uploadRequestDidProgress(event) {\n const progress = event.loaded / event.total * 100;\n if (progress) {\n this.dispatch(\"progress\", { progress });\n }\n }\n\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/direct_upload_controller.js?");
107
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"DragAndDropUploadController\", function() { return DragAndDropUploadController; });\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers */ \"./app/javascript/active_storage_drag_and_drop/helpers.js\");\n/* harmony import */ var activestorage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! activestorage */ \"./node_modules/activestorage/app/assets/javascripts/activestorage.js\");\n/* harmony import */ var activestorage__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(activestorage__WEBPACK_IMPORTED_MODULE_1__);\n\n\nconst eventFamily = 'dnd-upload';\n\nclass DragAndDropUploadController {\n constructor(input, file) {\n this.input = input;\n this.form = input.closest('form');\n this.url = this.input.dataset.directUploadUrl;\n this.iconContainer = document.getElementById(this.input.dataset.iconContainerId);\n this.file = file;\n this.upload = new activestorage__WEBPACK_IMPORTED_MODULE_1__[\"DirectUpload\"](this.file, this.url, this);\n let event = this.dispatch('initialize');\n if (!event.defaultPrevented) {\n const { detail } = event;\n const { id, file, iconContainer } = detail;\n Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"fileUploadUIPainter\"])(iconContainer, id, file.name, false);\n }\n }\n\n start(callback) {\n this.upload.create((error, blob) => {\n if (error) {\n // Handle the error\n this.dispatchError(error);\n callback(error);\n } else {\n // Add an appropriately-named hidden input to the form with a\n // value of blob.signed_id so that the blob ids will be\n // transmitted in the normal upload flow\n const hiddenField = document.createElement('input');\n hiddenField.setAttribute('type', 'hidden');\n hiddenField.setAttribute('value', blob.signed_id);\n hiddenField.name = this.input.name;\n hiddenField.setAttribute('data-direct-upload-id', this.upload.id);\n this.form.appendChild(hiddenField);\n let event = this.dispatch('end');\n Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"defaultEndEventUI\"])(event);\n callback(error);\n }\n });\n }\n\n dispatch(name, detail = {}) {\n detail.file = this.file;\n detail.id = this.upload.id;\n detail.iconContainer = this.iconContainer;\n return Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"dispatchEvent\"])(this.input, `${eventFamily}:${name}`, { detail });\n }\n\n dispatchError(error) {\n const event = this.dispatch('error', { error });\n Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"defaultErrorEventUI\"])(event);\n }\n\n directUploadWillCreateBlobWithXHR(xhr) {\n this.dispatch('before-blob-request', { xhr });\n }\n // directUploadWillStoreFileWithXHR\n directUploadWillStoreFileWithXHR(xhr) {\n this.dispatch('before-storage-request', { xhr });\n xhr.upload.addEventListener('progress', event => this.uploadRequestDidProgress(event));\n }\n\n uploadRequestDidProgress(event) {\n const progress = event.loaded / event.total * 100;\n if (progress) {\n let event = this.dispatch('progress', { progress });\n if (!event.defaultPrevented) {\n const { id, progress } = event.detail;\n const progressElement = document.getElementById(`direct-upload-progress-${id}`);\n progressElement.style.width = `${progress}%`;\n }\n }\n }\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/direct_upload_controller.js?");
108
108
 
109
109
  /***/ }),
110
110
 
@@ -112,11 +112,11 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
112
112
  /*!****************************************************************!*\
113
113
  !*** ./app/javascript/active_storage_drag_and_drop/helpers.js ***!
114
114
  \****************************************************************/
115
- /*! exports provided: dispatchEvent, hasClassnameInHeirarchy, getClassnameFromHeirarchy */
115
+ /*! exports provided: dispatchEvent, defaultErrorEventUI, defaultEndEventUI, hasClassnameInHeirarchy, getClassnameFromHeirarchy, fileUploadUIPainter */
116
116
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
117
117
 
118
118
  "use strict";
119
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"dispatchEvent\", function() { return dispatchEvent; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"hasClassnameInHeirarchy\", function() { return hasClassnameInHeirarchy; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getClassnameFromHeirarchy\", function() { return getClassnameFromHeirarchy; });\n\nfunction dispatchEvent(element, type, eventInit = {}) {\n const { bubbles, cancelable, detail } = eventInit;\n const event = document.createEvent(\"Event\");\n event.initEvent(type, bubbles || true, cancelable || true);\n event.detail = detail || {};\n element.dispatchEvent(event);\n return event;\n}\n\nfunction hasClassnameInHeirarchy(element, classname) {\n if (element && element.classList) {\n if (element.classList.contains(classname)) {\n return true;\n } else {\n return hasClassnameInHeirarchy(element.parentNode, classname);\n }\n } else {\n return false;\n }\n}\n\nfunction getClassnameFromHeirarchy(element, classname) {\n if (element && element.classList) {\n if (element.classList.contains(classname)) {\n return element;\n } else {\n return getClassnameFromHeirarchy(element.parentNode, classname);\n }\n } else {\n return null;\n }\n}\n\n//export function disable(input) {\n// input.disabled = true\n//}\n\n//export function enable(input) {\n// input.disabled = false\n//}\n\n//export function toArray(value) {\n// if (Array.isArray(value)) {\n// return value\n// } else if (Array.from) {\n// return Array.from(value)\n// } else {\n// return [].slice.call(value)\n// }\n//}\n\n//export function findElements(root, selector) {\n// if (typeof root == \"string\") {\n// selector = root\n// root = document\n// }\n// const elements = root.querySelectorAll(selector)\n// return toArray(elements)\n//}\n\n//export function findElement(root, selector) {\n// if (typeof root == \"string\") {\n// selector = root\n// root = document\n// }\n// return root.querySelector(selector)\n//}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/helpers.js?");
119
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"dispatchEvent\", function() { return dispatchEvent; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultErrorEventUI\", function() { return defaultErrorEventUI; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"defaultEndEventUI\", function() { return defaultEndEventUI; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"hasClassnameInHeirarchy\", function() { return hasClassnameInHeirarchy; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"getClassnameFromHeirarchy\", function() { return getClassnameFromHeirarchy; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"fileUploadUIPainter\", function() { return fileUploadUIPainter; });\nfunction dispatchEvent(element, type, eventInit = {}) {\n const { bubbles, cancelable, detail } = eventInit;\n const event = document.createEvent('Event');\n event.initEvent(type, bubbles || true, cancelable || true);\n event.detail = detail || {};\n element.dispatchEvent(event);\n return event;\n}\n\nfunction defaultErrorEventUI(event) {\n if (!event.defaultPrevented) {\n const { id, error } = event.detail;\n const element = document.getElementById(`direct-upload-${id}`);\n element.classList.add('direct-upload--error');\n element.setAttribute('title', error);\n }\n}\n\nfunction defaultEndEventUI(event) {\n if (!event.defaultPrevented) {\n const { id } = event.detail;\n const element = document.getElementById(`direct-upload-${id}`);\n element.classList.remove('direct-upload--pending');\n element.classList.add('direct-upload--complete');\n }\n}\n\nfunction hasClassnameInHeirarchy(element, classname) {\n if (element && element.classList) {\n if (element.classList.contains(classname)) {\n return true;\n } else {\n return hasClassnameInHeirarchy(element.parentNode, classname);\n }\n }\n}\n\nfunction getClassnameFromHeirarchy(element, classname) {\n if (element && element.classList) {\n if (element.classList.contains(classname)) {\n return element;\n } else {\n return getClassnameFromHeirarchy(element.parentNode, classname);\n }\n }\n}\n\nfunction fileUploadUIPainter(iconContainer, id, filename, complete) {\n // the only rule here is that all root level elements must have the data: { direct_upload_id: [id] } attribute ala: 'data-direct-upload-id=\"${id}\"'\n var cname = complete ? 'complete' : 'pending';\n var progress = complete ? 100 : 0;\n iconContainer.insertAdjacentHTML('beforeend', `\n <div id=\"direct-upload-${id}\" class=\"direct-upload direct-upload--${cname}\" data-direct-upload-id=\"${id}\">\n <div id=\"direct-upload-progress-${id}\" class=\"direct-upload__progress\" style=\"width: ${progress}%\"></div>\n <span class=\"direct-upload__filename\">${filename}</span>\n </div>\n <a href='remove' class='direct-upload__remove' data-dnd-delete='true' data-direct-upload-id=\"${id}\">x</a>\n `);\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/helpers.js?");
120
120
 
121
121
  /***/ }),
122
122
 
@@ -128,7 +128,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
128
128
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
129
129
 
130
130
  "use strict";
131
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ujs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ujs */ \"./app/javascript/active_storage_drag_and_drop/ujs.js\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"start\", function() { return _ujs__WEBPACK_IMPORTED_MODULE_0__[\"start\"]; });\n\n\n\n\n\nfunction autostart() {\n Object(_ujs__WEBPACK_IMPORTED_MODULE_0__[\"start\"])();\n}\n\nsetTimeout(autostart, 1);\n\n//----------------------------------------------------------------------------------------------------\n// UI Events - this code is completely outside the draganddrop lib - it's just reacting to events\n//----------------------------------------------------------------------------------------------------\nvar fileUploadUIPainter = function (iconContainer, id, filename, complete) {\n // the only rule here is that all root level elements must have the data: { direct_upload_id: [id] } attribute ala: 'data-direct-upload-id=\"${id}\"'\n var cname = complete ? 'complete' : 'pending';\n var progress = complete ? 100 : 0;\n iconContainer.insertAdjacentHTML(\"beforeend\", `\n <div id=\"direct-upload-${id}\" class=\"direct-upload direct-upload--${cname}\" data-direct-upload-id=\"${id}\">\n <div id=\"direct-upload-progress-${id}\" class=\"direct-upload__progress\" style=\"width: ${progress}%\"></div>\n <span class=\"direct-upload__filename\">${filename}</span>\n </div>\n <a href='remove' class='direct-upload__remove' data-dnd-delete='true' data-direct-upload-id=\"${id}\">x</a>\n `);\n};\n\n// addEventListener(\"dnd-uploads:start\", event => {\n// })\n// addEventListener(\"dnd-uploads:end\", event => {\n// })\n\naddEventListener(\"dnd-upload:initialize\", event => {\n if (!event.defaultPrevented) {\n const { target, detail } = event;\n const { id, file, iconContainer } = detail;\n fileUploadUIPainter(iconContainer, id, file.name, false);\n }\n});\n\naddEventListener(\"dnd-upload:placeholder\", event => {\n if (!event.defaultPrevented) {\n const { target, detail } = event;\n const { id, fileName, iconContainer } = detail;\n fileUploadUIPainter(iconContainer, id, fileName, true);\n }\n});\n\naddEventListener(\"dnd-upload:start\", event => {\n if (!event.defaultPrevented) {\n const { id } = event.detail;\n const element = document.getElementById(`direct-upload-${id}`);\n element.classList.remove(\"direct-upload--pending\");\n }\n});\n\naddEventListener(\"dnd-upload:progress\", event => {\n if (!event.defaultPrevented) {\n const { id, progress } = event.detail;\n const progressElement = document.getElementById(`direct-upload-progress-${id}`);\n progressElement.style.width = `${progress}%`;\n }\n});\n\naddEventListener(\"dnd-upload:error\", event => {\n if (!event.defaultPrevented) {\n event.preventDefault();\n const { id, error } = event.detail;\n const element = document.getElementById(`direct-upload-${id}`);\n element.classList.add(\"direct-upload--error\");\n element.setAttribute(\"title\", error);\n }\n});\n\naddEventListener(\"dnd-upload:end\", event => {\n if (!event.defaultPrevented) {\n const { id } = event.detail;\n const element = document.getElementById(`direct-upload-${id}`);\n element.classList.remove(\"direct-upload--pending\");\n element.classList.add(\"direct-upload--complete\");\n }\n});\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/index.js?");
131
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ujs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ujs */ \"./app/javascript/active_storage_drag_and_drop/ujs.js\");\n/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, \"start\", function() { return _ujs__WEBPACK_IMPORTED_MODULE_0__[\"start\"]; });\n\n\n\n\n\nfunction autostart() {\n Object(_ujs__WEBPACK_IMPORTED_MODULE_0__[\"start\"])();\n}\n\nsetTimeout(autostart, 1);\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/index.js?");
132
132
 
133
133
  /***/ }),
134
134
 
@@ -140,7 +140,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ujs
140
140
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
141
141
 
142
142
  "use strict";
143
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"start\", function() { return start; });\n/* harmony import */ var _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./upload_queue_processor */ \"./app/javascript/active_storage_drag_and_drop/upload_queue_processor.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers */ \"./app/javascript/active_storage_drag_and_drop/helpers.js\");\n\n\n\nlet started = false;\nlet formSubmitted = false;\n\nfunction didSubmitForm(event) {\n handleFormSubmissionEvent(event);\n}\n\nfunction didSubmitRemoteElement(event) {\n if (event.target.tagName == \"FORM\") {\n handleFormSubmissionEvent(event);\n }\n}\n\nfunction handleFormSubmissionEvent(event) {\n if (formSubmitted) {\n return;\n }\n formSubmitted = true;\n const form = event.target;\n const next_upload = new _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"UploadQueueProcessor\"](form);\n // if the upload processor has no dnd file inputs, then we let the event happen naturally\n // if it DOES have dnd file inputs, then we have to process our queue first and then submit the form\n if (next_upload.current_uploaders.length > 0) {\n event.preventDefault();\n // inputs.forEach(disable)\n next_upload.start(error => {\n if (error) {\n // inputs.forEach(enable)\n } else {\n form.submit();\n // The original ActiveStorage DirectUpload system did this action using\n // input.click(), but doing that either makes the form submission event\n // happen multiple times, or the browser seems to block the input.click()\n // event completely, because it's not a trusted 'as a result of a mouse\n // click' event.\n // HOWEVER\n // form.submit() doesn't trigger to any UJS submission events. This\n // results in remote forms being submitted locally whenever there's a\n // dnd file to upload. Instead we use Rails.fire(element, 'submit')\n // Rails.fire(form, 'submit')\n }\n });\n }\n}\n\nfunction addAttachedFileIcons() {\n document.querySelectorAll(\"input[type='hidden'][data-direct-upload-id][data-uploaded-file-name]\").forEach(uploadedFile => {\n const dataset = uploadedFile.dataset;\n let iconContainer = document.getElementById(dataset.iconContainerId);\n let detail = {\n id: dataset.directUploadId,\n fileName: dataset.uploadedFileName,\n iconContainer: iconContainer\n };\n _helpers__WEBPACK_IMPORTED_MODULE_1__[\"dispatchEvent\"](uploadedFile, `dnd-upload:placeholder`, { detail });\n });\n}\n\nfunction start() {\n if (started) {\n return;\n }\n started = true;\n document.addEventListener(\"submit\", didSubmitForm);\n document.addEventListener(\"ajax:before\", didSubmitRemoteElement);\n\n // input[type=file][data-dnd=true]\n document.addEventListener(\"change\", event => {\n if (event.target.type == 'file' && event.target.dataset.dnd == 'true') {\n const input = event.target;\n Array.from(input.files).forEach(file => Object(_upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"createUploader\"])(input, file));\n input.value = null;\n }\n });\n document.addEventListener(\"dragover\", event => {\n if (_helpers__WEBPACK_IMPORTED_MODULE_1__[\"hasClassnameInHeirarchy\"](event.target, 'asdndzone')) {\n event.preventDefault();\n }\n });\n document.addEventListener(\"drop\", event => {\n let asdndz = _helpers__WEBPACK_IMPORTED_MODULE_1__[\"getClassnameFromHeirarchy\"](event.target, 'asdndzone');\n if (asdndz) {\n event.preventDefault();\n // get the input associated with this dndz\n const input = document.getElementById(asdndz.dataset.dndInputId);\n Array.from(event.dataTransfer.files).forEach(file => Object(_upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"createUploader\"])(input, file));\n }\n });\n document.addEventListener(\"click\", event => {\n if (event.target.dataset.dndDelete == 'true' && event.target.hasAttribute('data-direct-upload-id')) {\n event.preventDefault();\n document.querySelectorAll('[data-direct-upload-id=\"' + event.target.dataset.directUploadId + '\"]').forEach(element => {\n element.remove();\n });\n for (var i = 0; i < _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"uploaders\"].length; i++) {\n if (_upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"uploaders\"][i].upload.id == event.target.dataset.directUploadId) {\n _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"uploaders\"].splice(i, 1);\n break;\n }\n }\n }\n });\n addEventListener(\"turbolinks:load\", addAttachedFileIcons);\n addEventListener(\"load\", addAttachedFileIcons);\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/ujs.js?");
143
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"start\", function() { return start; });\n/* harmony import */ var _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./upload_queue_processor */ \"./app/javascript/active_storage_drag_and_drop/upload_queue_processor.js\");\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers */ \"./app/javascript/active_storage_drag_and_drop/helpers.js\");\n\n\n\nlet started = false;\nlet formSubmitted = false;\n\nfunction didSubmitForm(event) {\n handleFormSubmissionEvent(event);\n}\n\nfunction didSubmitRemoteElement(event) {\n if (event.target.tagName === 'FORM') {\n handleFormSubmissionEvent(event);\n }\n}\n\nfunction processUploadQueue(event) {\n const form = event.target;\n const { callback } = event.detail;\n const nextUpload = new _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"UploadQueueProcessor\"](form);\n if (nextUpload.current_uploaders.length > 0) {\n nextUpload.start(error => {\n if (error) {\n callback(error);\n } else {\n callback();\n }\n });\n } else {\n callback();\n }\n}\n\nfunction handleFormSubmissionEvent(event) {\n if (formSubmitted) {\n return;\n }\n formSubmitted = true;\n const form = event.target;\n const nextUpload = new _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"UploadQueueProcessor\"](form);\n // if the upload processor has no dnd file inputs, then we let the event happen naturally\n // if it DOES have dnd file inputs, then we have to process our queue first and then submit the form\n if (nextUpload.current_uploaders.length > 0) {\n // inputs.forEach(disable)\n event.preventDefault();\n nextUpload.start(error => {\n if (error) {\n // inputs.forEach(enable)\n } else {\n form.submit();\n // The original ActiveStorage DirectUpload system did this action using\n // input.click(), but doing that either makes the form submission event\n // happen multiple times, or the browser seems to block the input.click()\n // event completely, because it's not a trusted 'as a result of a mouse\n // click' event.\n // HOWEVER\n // form.submit() doesn't trigger to any UJS submission events. This\n // results in remote forms being submitted locally whenever there's a\n // dnd file to upload. Instead we use Rails.fire(element, 'submit')\n // Rails.fire(form, 'submit')\n }\n });\n }\n}\n\nfunction addAttachedFileIcons() {\n document.querySelectorAll(\"input[type='hidden'][data-direct-upload-id][data-uploaded-file-name]\").forEach(uploadedFile => {\n const dataset = uploadedFile.dataset;\n let iconContainer = document.getElementById(dataset.iconContainerId);\n let detail = {\n id: dataset.directUploadId,\n fileName: dataset.uploadedFileName,\n iconContainer: iconContainer\n };\n let event = _helpers__WEBPACK_IMPORTED_MODULE_1__[\"dispatchEvent\"](uploadedFile, 'dnd-upload:placeholder', { detail });\n if (!event.defaultPrevented) {\n const { detail } = event;\n const { id, fileName, iconContainer } = detail;\n _helpers__WEBPACK_IMPORTED_MODULE_1__[\"fileUploadUIPainter\"](iconContainer, id, fileName, true);\n }\n });\n}\n\nfunction createUploadersForFileInput(event) {\n if (event.target.type === 'file' && event.target.dataset.dnd === 'true') {\n const input = event.target;\n Array.from(input.files).forEach(file => Object(_upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"createUploader\"])(input, file));\n input.value = null;\n }\n}\n\nfunction preventDragover(event) {\n if (_helpers__WEBPACK_IMPORTED_MODULE_1__[\"hasClassnameInHeirarchy\"](event.target, 'asdndzone')) {\n event.preventDefault();\n }\n}\n\nfunction createUploadersForDroppedFiles(event) {\n let asdndz = _helpers__WEBPACK_IMPORTED_MODULE_1__[\"getClassnameFromHeirarchy\"](event.target, 'asdndzone');\n if (asdndz) {\n event.preventDefault();\n // get the input associated with this dndz\n const input = document.getElementById(asdndz.dataset.dndInputId);\n Array.from(event.dataTransfer.files).forEach(file => Object(_upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"createUploader\"])(input, file));\n }\n}\n\nfunction removeFileFromQueue(event) {\n if (event.target.dataset.dndDelete === 'true' && event.target.hasAttribute('data-direct-upload-id')) {\n event.preventDefault();\n document.querySelectorAll('[data-direct-upload-id=\"' + event.target.dataset.directUploadId + '\"]').forEach(element => {\n element.remove();\n });\n for (var i = 0; i < _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"uploaders\"].length; i++) {\n if (_upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"uploaders\"][i].upload.id === event.target.dataset.directUploadId) {\n _upload_queue_processor__WEBPACK_IMPORTED_MODULE_0__[\"uploaders\"].splice(i, 1);\n break;\n }\n }\n }\n}\n\nfunction start() {\n if (started) {\n return;\n }\n started = true;\n document.addEventListener('submit', didSubmitForm);\n document.addEventListener('ajax:before', didSubmitRemoteElement);\n document.addEventListener('dnd-uploads:process-upload-queue', processUploadQueue);\n\n // input[type=file][data-dnd=true]\n document.addEventListener('change', createUploadersForFileInput);\n document.addEventListener('dragover', preventDragover);\n document.addEventListener('drop', createUploadersForDroppedFiles);\n document.addEventListener('click', removeFileFromQueue);\n addAttachedFileIcons();\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/ujs.js?");
144
144
 
145
145
  /***/ }),
146
146
 
@@ -152,7 +152,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) *
152
152
  /***/ (function(module, __webpack_exports__, __webpack_require__) {
153
153
 
154
154
  "use strict";
155
- eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uploaders\", function() { return uploaders; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"UploadQueueProcessor\", function() { return UploadQueueProcessor; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"createUploader\", function() { return createUploader; });\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers */ \"./app/javascript/active_storage_drag_and_drop/helpers.js\");\n/* harmony import */ var _direct_upload_controller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./direct_upload_controller */ \"./app/javascript/active_storage_drag_and_drop/direct_upload_controller.js\");\n\n\nconst uploaders = [];\n\nconst eventFamily = 'dnd-upload';\n\nclass ValidationError extends Error {\n constructor(...args) {\n super(...args);\n Error.captureStackTrace(this, ValidationError);\n }\n}\n\nclass UploadQueueProcessor {\n constructor(form) {\n this.form = form;\n this.current_uploaders = [];\n uploaders.forEach(uploader => {\n if (form == uploader.form) {\n this.current_uploaders.push(uploader);\n }\n });\n }\n\n start(callback) {\n const startNextUploader = () => {\n const nextUploader = this.current_uploaders.shift();\n if (nextUploader) {\n nextUploader.start(error => {\n if (error) {\n callback(error);\n this.dispatch(\"end\");\n } else {\n startNextUploader();\n }\n });\n } else {\n callback();\n this.dispatch(\"end\");\n }\n };\n\n this.dispatch(\"start\");\n startNextUploader();\n }\n\n dispatch(name, detail = {}) {\n return Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"dispatchEvent\"])(this.form, `${eventFamily}:${name}`, { detail });\n }\n}\n\nfunction createUploader(input, file) {\n // your form needs the file_field direct_upload: true, which\n // provides data-direct-upload-url\n const error = validateUploader(input, file);\n if (error) {\n const event = Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"dispatchEvent\"])(input, `${eventFamily}:error`, { error });\n if (!event.defaultPrevented) {\n alert(error);\n }\n return event;\n }\n uploaders.push(new _direct_upload_controller__WEBPACK_IMPORTED_MODULE_1__[\"DragAndDropUploadController\"](input, file));\n}\n\nfunction validateUploader(input, file) {\n const sizeLimit = input.getAttribute('size_limit');\n if (input.accept !== '' && !input.accept.split(', ').includes(file.type)) {\n return new ValidationError('Invalid filetype');\n } else if (sizeLimit && file.size > sizeLimit) {\n return new ValidationError(`File too large. Can be no larger than ${humanFileSize(sizeLimit)}`);\n }\n}\n\nfunction humanFileSize(bytes) {\n var thresh = 1000;\n if (Math.abs(bytes) < thresh) {\n return bytes + ' B';\n }\n var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];\n var u = -1;\n do {\n bytes /= thresh;\n ++u;\n } while (Math.abs(bytes) >= thresh && u < units.length - 1);\n return bytes.toFixed(1) + ' ' + units[u];\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/upload_queue_processor.js?");
155
+ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"uploaders\", function() { return uploaders; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"UploadQueueProcessor\", function() { return UploadQueueProcessor; });\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"createUploader\", function() { return createUploader; });\n/* harmony import */ var _helpers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers */ \"./app/javascript/active_storage_drag_and_drop/helpers.js\");\n/* harmony import */ var _direct_upload_controller__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./direct_upload_controller */ \"./app/javascript/active_storage_drag_and_drop/direct_upload_controller.js\");\n\n\nconst uploaders = [];\n\nclass ValidationError extends Error {\n constructor(...args) {\n super(...args);\n Error.captureStackTrace(this, ValidationError);\n }\n}\n\nclass UploadQueueProcessor {\n constructor(form) {\n this.form = form;\n this.current_uploaders = [];\n uploaders.forEach(uploader => {\n if (form === uploader.form) {\n this.current_uploaders.push(uploader);\n }\n });\n }\n\n start(callback) {\n const startNextUploader = () => {\n const nextUploader = this.current_uploaders.shift();\n if (nextUploader) {\n nextUploader.start(error => {\n if (error) {\n this.dispatchError(error);\n callback(error);\n } else {\n startNextUploader();\n }\n });\n } else {\n callback();\n let event = this.dispatch('end');\n Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"defaultEndEventUI\"])(event);\n }\n };\n\n this.dispatch('start');\n startNextUploader();\n }\n\n dispatch(name, detail = {}) {\n return Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"dispatchEvent\"])(this.form, `dnd-uploads:${name}`, { detail });\n }\n\n dispatchError(error) {\n const event = this.dispatch('error', { error });\n Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"defaultErrorEventUI\"])(event);\n }\n}\n\nfunction createUploader(input, file) {\n // your form needs the file_field direct_upload: true, which\n // provides data-direct-upload-url\n const error = validateUploader(input, file);\n if (error) {\n let detail = {\n id: null,\n file: file,\n iconContainer: input.dataset.iconContainerId,\n error: error\n };\n return dispatchErrorWithoutAttachment(input, detail);\n }\n if (!input.multiple) {\n removeAttachedFiles(input);\n }\n uploaders.push(new _direct_upload_controller__WEBPACK_IMPORTED_MODULE_1__[\"DragAndDropUploadController\"](input, file));\n}\n\nfunction removeAttachedFiles(input) {\n input.closest('label.asdndzone').querySelectorAll('[data-direct-upload-id]').forEach(element => {\n element.remove();\n });\n uploaders.splice(0, uploaders.length);\n}\n\nfunction dispatchErrorWithoutAttachment(input, detail) {\n let event = Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"dispatchEvent\"])(input, 'dnd-upload:error', { detail });\n if (!event.defaultPrevented) {\n const { error, iconContainer, file } = event.detail;\n Object(_helpers__WEBPACK_IMPORTED_MODULE_0__[\"fileUploadUIPainter\"])(iconContainer, 'error', file.name, true);\n const element = document.getElementById(`direct-upload-error`);\n element.classList.add('direct-upload--error');\n element.setAttribute('title', error);\n }\n return event;\n}\n\nfunction validateUploader(input, file) {\n const sizeLimit = input.getAttribute('size_limit');\n if (input.accept !== '' && !input.accept.split(', ').includes(file.type)) {\n return new ValidationError('Invalid filetype');\n } else if (sizeLimit && file.size > sizeLimit) {\n return new ValidationError(`File too large. Can be no larger than ${humanFileSize(sizeLimit)}`);\n }\n}\n\nfunction humanFileSize(bytes) {\n var thresh = 1000;\n if (Math.abs(bytes) < thresh) {\n return bytes + ' B';\n }\n var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];\n var u = -1;\n do {\n bytes /= thresh;\n ++u;\n } while (Math.abs(bytes) >= thresh && u < units.length - 1);\n return bytes.toFixed(1) + ' ' + units[u];\n}\n\n//# sourceURL=webpack://ActiveStorage/./app/javascript/active_storage_drag_and_drop/upload_queue_processor.js?");
156
156
 
157
157
  /***/ }),
158
158
 
@@ -1,68 +1,76 @@
1
- import { dispatchEvent } from './helpers'
1
+ import { dispatchEvent, defaultErrorEventUI, defaultEndEventUI, fileUploadUIPainter } from './helpers'
2
2
  import { DirectUpload } from 'activestorage'
3
3
  const eventFamily = 'dnd-upload'
4
4
 
5
5
  export class DragAndDropUploadController {
6
- constructor(input, file) {
6
+ constructor (input, file) {
7
7
  this.input = input
8
8
  this.form = input.closest('form')
9
9
  this.url = this.input.dataset.directUploadUrl
10
10
  this.iconContainer = document.getElementById(this.input.dataset.iconContainerId)
11
11
  this.file = file
12
12
  this.upload = new DirectUpload(this.file, this.url, this)
13
- this.dispatch("initialize")
13
+ let event = this.dispatch('initialize')
14
+ if (!event.defaultPrevented) {
15
+ const { detail } = event
16
+ const { id, file, iconContainer } = detail
17
+ fileUploadUIPainter(iconContainer, id, file.name, false)
18
+ }
14
19
  }
15
20
 
16
- start(callback) {
21
+ start (callback) {
17
22
  this.upload.create((error, blob) => {
18
23
  if (error) {
19
24
  // Handle the error
20
25
  this.dispatchError(error)
21
26
  callback(error)
22
27
  } else {
23
- // // Add an appropriately-named hidden input to the form with a
24
- // // value of blob.signed_id so that the blob ids will be
25
- // // transmitted in the normal upload flow
28
+ // Add an appropriately-named hidden input to the form with a
29
+ // value of blob.signed_id so that the blob ids will be
30
+ // transmitted in the normal upload flow
26
31
  const hiddenField = document.createElement('input')
27
- hiddenField.setAttribute("type", "hidden");
28
- hiddenField.setAttribute("value", blob.signed_id);
32
+ hiddenField.setAttribute('type', 'hidden')
33
+ hiddenField.setAttribute('value', blob.signed_id)
29
34
  hiddenField.name = this.input.name
30
35
  hiddenField.setAttribute('data-direct-upload-id', this.upload.id)
31
36
  this.form.appendChild(hiddenField)
32
- this.dispatch("end")
37
+ let event = this.dispatch('end')
38
+ defaultEndEventUI(event)
33
39
  callback(error)
34
40
  }
35
41
  })
36
42
  }
37
43
 
38
- dispatch(name, detail = {}) {
44
+ dispatch (name, detail = {}) {
39
45
  detail.file = this.file
40
46
  detail.id = this.upload.id
41
47
  detail.iconContainer = this.iconContainer
42
48
  return dispatchEvent(this.input, `${eventFamily}:${name}`, { detail })
43
49
  }
44
50
 
45
- dispatchError(error) {
46
- const event = this.dispatch("error", { error })
47
- if (!event.defaultPrevented) {
48
- alert(error)
49
- }
51
+ dispatchError (error) {
52
+ const event = this.dispatch('error', { error })
53
+ defaultErrorEventUI(event)
50
54
  }
51
55
 
52
- directUploadWillCreateBlobWithXHR(xhr) {
53
- this.dispatch("before-blob-request", { xhr })
56
+ directUploadWillCreateBlobWithXHR (xhr) {
57
+ this.dispatch('before-blob-request', { xhr })
54
58
  }
55
59
  // directUploadWillStoreFileWithXHR
56
- directUploadWillStoreFileWithXHR(xhr) {
57
- this.dispatch("before-storage-request", { xhr })
58
- xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event))
60
+ directUploadWillStoreFileWithXHR (xhr) {
61
+ this.dispatch('before-storage-request', { xhr })
62
+ xhr.upload.addEventListener('progress', event => this.uploadRequestDidProgress(event))
59
63
  }
60
64
 
61
- uploadRequestDidProgress(event) {
65
+ uploadRequestDidProgress (event) {
62
66
  const progress = event.loaded / event.total * 100
63
67
  if (progress) {
64
- this.dispatch("progress", { progress })
68
+ let event = this.dispatch('progress', { progress })
69
+ if (!event.defaultPrevented) {
70
+ const { id, progress } = event.detail
71
+ const progressElement = document.getElementById(`direct-upload-progress-${id}`)
72
+ progressElement.style.width = `${progress}%`
73
+ }
65
74
  }
66
75
  }
67
-
68
76
  }
@@ -1,69 +1,59 @@
1
-
2
- export function dispatchEvent(element, type, eventInit = {}) {
1
+ export function dispatchEvent (element, type, eventInit = {}) {
3
2
  const { bubbles, cancelable, detail } = eventInit
4
- const event = document.createEvent("Event")
3
+ const event = document.createEvent('Event')
5
4
  event.initEvent(type, bubbles || true, cancelable || true)
6
5
  event.detail = detail || {}
7
6
  element.dispatchEvent(event)
8
7
  return event
9
8
  }
10
9
 
11
- export function hasClassnameInHeirarchy(element, classname) {
12
- if(element && element.classList) {
10
+ export function defaultErrorEventUI (event) {
11
+ if (!event.defaultPrevented) {
12
+ const { id, error } = event.detail
13
+ const element = document.getElementById(`direct-upload-${id}`)
14
+ element.classList.add('direct-upload--error')
15
+ element.setAttribute('title', error)
16
+ }
17
+ }
18
+
19
+ export function defaultEndEventUI (event) {
20
+ if (!event.defaultPrevented) {
21
+ const { id } = event.detail
22
+ const element = document.getElementById(`direct-upload-${id}`)
23
+ element.classList.remove('direct-upload--pending')
24
+ element.classList.add('direct-upload--complete')
25
+ }
26
+ }
27
+
28
+ export function hasClassnameInHeirarchy (element, classname) {
29
+ if (element && element.classList) {
13
30
  if (element.classList.contains(classname)) {
14
31
  return true
15
32
  } else {
16
33
  return hasClassnameInHeirarchy(element.parentNode, classname)
17
34
  }
18
- } else {
19
- return false
20
35
  }
21
36
  }
22
37
 
23
- export function getClassnameFromHeirarchy(element, classname) {
24
- if(element && element.classList) {
38
+ export function getClassnameFromHeirarchy (element, classname) {
39
+ if (element && element.classList) {
25
40
  if (element.classList.contains(classname)) {
26
41
  return element
27
42
  } else {
28
43
  return getClassnameFromHeirarchy(element.parentNode, classname)
29
44
  }
30
- } else {
31
- return null
32
45
  }
33
46
  }
34
47
 
35
- //export function disable(input) {
36
- // input.disabled = true
37
- //}
38
-
39
- //export function enable(input) {
40
- // input.disabled = false
41
- //}
42
-
43
- //export function toArray(value) {
44
- // if (Array.isArray(value)) {
45
- // return value
46
- // } else if (Array.from) {
47
- // return Array.from(value)
48
- // } else {
49
- // return [].slice.call(value)
50
- // }
51
- //}
52
-
53
- //export function findElements(root, selector) {
54
- // if (typeof root == "string") {
55
- // selector = root
56
- // root = document
57
- // }
58
- // const elements = root.querySelectorAll(selector)
59
- // return toArray(elements)
60
- //}
61
-
62
- //export function findElement(root, selector) {
63
- // if (typeof root == "string") {
64
- // selector = root
65
- // root = document
66
- // }
67
- // return root.querySelector(selector)
68
- //}
69
-
48
+ export function fileUploadUIPainter (iconContainer, id, filename, complete) {
49
+ // the only rule here is that all root level elements must have the data: { direct_upload_id: [id] } attribute ala: 'data-direct-upload-id="${id}"'
50
+ var cname = (complete ? 'complete' : 'pending')
51
+ var progress = (complete ? 100 : 0)
52
+ iconContainer.insertAdjacentHTML('beforeend', `
53
+ <div id="direct-upload-${id}" class="direct-upload direct-upload--${cname}" data-direct-upload-id="${id}">
54
+ <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: ${progress}%"></div>
55
+ <span class="direct-upload__filename">${filename}</span>
56
+ </div>
57
+ <a href='remove' class='direct-upload__remove' data-dnd-delete='true' data-direct-upload-id="${id}">x</a>
58
+ `)
59
+ }
@@ -1,81 +1,9 @@
1
- import { start } from "./ujs"
1
+ import { start } from './ujs'
2
2
 
3
3
  export { start }
4
4
 
5
- function autostart() {
5
+ function autostart () {
6
6
  start()
7
7
  }
8
8
 
9
9
  setTimeout(autostart, 1)
10
-
11
- //----------------------------------------------------------------------------------------------------
12
- // UI Events - this code is completely outside the draganddrop lib - it's just reacting to events
13
- //----------------------------------------------------------------------------------------------------
14
- var fileUploadUIPainter = function(iconContainer, id, filename, complete) {
15
- // the only rule here is that all root level elements must have the data: { direct_upload_id: [id] } attribute ala: 'data-direct-upload-id="${id}"'
16
- var cname = ( complete ? 'complete' : 'pending' )
17
- var progress = ( complete ? 100 : 0 )
18
- iconContainer.insertAdjacentHTML("beforeend", `
19
- <div id="direct-upload-${id}" class="direct-upload direct-upload--${cname}" data-direct-upload-id="${id}">
20
- <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: ${progress}%"></div>
21
- <span class="direct-upload__filename">${filename}</span>
22
- </div>
23
- <a href='remove' class='direct-upload__remove' data-dnd-delete='true' data-direct-upload-id="${id}">x</a>
24
- `)
25
- }
26
-
27
- // addEventListener("dnd-uploads:start", event => {
28
- // })
29
- // addEventListener("dnd-uploads:end", event => {
30
- // })
31
-
32
- addEventListener("dnd-upload:initialize", event => {
33
- if (!event.defaultPrevented) {
34
- const { target, detail } = event
35
- const { id, file, iconContainer } = detail
36
- fileUploadUIPainter(iconContainer, id, file.name, false)
37
- }
38
- })
39
-
40
- addEventListener("dnd-upload:placeholder", event => {
41
- if (!event.defaultPrevented) {
42
- const { target, detail } = event
43
- const { id, fileName, iconContainer } = detail
44
- fileUploadUIPainter(iconContainer, id, fileName, true)
45
- }
46
- })
47
-
48
- addEventListener("dnd-upload:start", event => {
49
- if (!event.defaultPrevented) {
50
- const { id } = event.detail
51
- const element = document.getElementById(`direct-upload-${id}`)
52
- element.classList.remove("direct-upload--pending")
53
- }
54
- })
55
-
56
- addEventListener("dnd-upload:progress", event => {
57
- if (!event.defaultPrevented) {
58
- const { id, progress } = event.detail
59
- const progressElement = document.getElementById(`direct-upload-progress-${id}`)
60
- progressElement.style.width = `${progress}%`
61
- }
62
- })
63
-
64
- addEventListener("dnd-upload:error", event => {
65
- if (!event.defaultPrevented) {
66
- event.preventDefault()
67
- const { id, error } = event.detail
68
- const element = document.getElementById(`direct-upload-${id}`)
69
- element.classList.add("direct-upload--error")
70
- element.setAttribute("title", error)
71
- }
72
- })
73
-
74
- addEventListener("dnd-upload:end", event => {
75
- if (!event.defaultPrevented) {
76
- const { id } = event.detail
77
- const element = document.getElementById(`direct-upload-${id}`)
78
- element.classList.remove("direct-upload--pending")
79
- element.classList.add("direct-upload--complete")
80
- }
81
- })
@@ -1,104 +1,134 @@
1
- import { UploadQueueProcessor, uploaders, createUploader } from "./upload_queue_processor"
1
+ import { UploadQueueProcessor, uploaders, createUploader } from './upload_queue_processor'
2
2
  import * as helpers from './helpers'
3
3
 
4
4
  let started = false
5
5
  let formSubmitted = false
6
6
 
7
- function didSubmitForm(event) {
7
+ function didSubmitForm (event) {
8
8
  handleFormSubmissionEvent(event)
9
9
  }
10
10
 
11
- function didSubmitRemoteElement(event) {
12
- if (event.target.tagName == "FORM") {
11
+ function didSubmitRemoteElement (event) {
12
+ if (event.target.tagName === 'FORM') {
13
13
  handleFormSubmissionEvent(event)
14
14
  }
15
15
  }
16
16
 
17
- function handleFormSubmissionEvent(event) {
18
- if(formSubmitted) { return }
17
+ function processUploadQueue (event) {
18
+ const form = event.target
19
+ const { callback } = event.detail
20
+ const nextUpload = new UploadQueueProcessor(form)
21
+ if (nextUpload.current_uploaders.length > 0) {
22
+ nextUpload.start(error => {
23
+ if (error) {
24
+ callback(error)
25
+ } else {
26
+ callback()
27
+ }
28
+ })
29
+ } else {
30
+ callback()
31
+ }
32
+ }
33
+
34
+ function handleFormSubmissionEvent (event) {
35
+ if (formSubmitted) { return }
19
36
  formSubmitted = true
20
37
  const form = event.target
21
- const next_upload = new UploadQueueProcessor(form)
38
+ const nextUpload = new UploadQueueProcessor(form)
22
39
  // if the upload processor has no dnd file inputs, then we let the event happen naturally
23
40
  // if it DOES have dnd file inputs, then we have to process our queue first and then submit the form
24
- if( next_upload.current_uploaders.length > 0) {
25
- event.preventDefault()
41
+ if (nextUpload.current_uploaders.length > 0) {
26
42
  // inputs.forEach(disable)
27
- next_upload.start(error => {
43
+ event.preventDefault()
44
+ nextUpload.start(error => {
28
45
  if (error) {
29
46
  // inputs.forEach(enable)
30
47
  } else {
31
- form.submit()
32
- // The original ActiveStorage DirectUpload system did this action using
33
- // input.click(), but doing that either makes the form submission event
34
- // happen multiple times, or the browser seems to block the input.click()
35
- // event completely, because it's not a trusted 'as a result of a mouse
36
- // click' event.
37
- // HOWEVER
38
- // form.submit() doesn't trigger to any UJS submission events. This
39
- // results in remote forms being submitted locally whenever there's a
40
- // dnd file to upload. Instead we use Rails.fire(element, 'submit')
41
- // Rails.fire(form, 'submit')
48
+ form.submit()
49
+ // The original ActiveStorage DirectUpload system did this action using
50
+ // input.click(), but doing that either makes the form submission event
51
+ // happen multiple times, or the browser seems to block the input.click()
52
+ // event completely, because it's not a trusted 'as a result of a mouse
53
+ // click' event.
54
+ // HOWEVER
55
+ // form.submit() doesn't trigger to any UJS submission events. This
56
+ // results in remote forms being submitted locally whenever there's a
57
+ // dnd file to upload. Instead we use Rails.fire(element, 'submit')
58
+ // Rails.fire(form, 'submit')
42
59
  }
43
60
  })
44
61
  }
45
62
  }
46
63
 
47
- function addAttachedFileIcons() {
48
- document.querySelectorAll("input[type='hidden'][data-direct-upload-id][data-uploaded-file-name]").forEach( uploadedFile => {
64
+ function addAttachedFileIcons () {
65
+ document.querySelectorAll("input[type='hidden'][data-direct-upload-id][data-uploaded-file-name]").forEach(uploadedFile => {
49
66
  const dataset = uploadedFile.dataset
50
- let iconContainer = document.getElementById(dataset.iconContainerId);
67
+ let iconContainer = document.getElementById(dataset.iconContainerId)
51
68
  let detail = {
52
69
  id: dataset.directUploadId,
53
70
  fileName: dataset.uploadedFileName,
54
71
  iconContainer: iconContainer
55
72
  }
56
- helpers.dispatchEvent(uploadedFile, `dnd-upload:placeholder`, { detail })
73
+ let event = helpers.dispatchEvent(uploadedFile, 'dnd-upload:placeholder', { detail })
74
+ if (!event.defaultPrevented) {
75
+ const { detail } = event
76
+ const { id, fileName, iconContainer } = detail
77
+ helpers.fileUploadUIPainter(iconContainer, id, fileName, true)
78
+ }
57
79
  })
58
80
  }
59
81
 
60
- export function start() {
82
+ function createUploadersForFileInput (event) {
83
+ if (event.target.type === 'file' && event.target.dataset.dnd === 'true') {
84
+ const input = event.target
85
+ Array.from(input.files).forEach(file => createUploader(input, file))
86
+ input.value = null
87
+ }
88
+ }
89
+
90
+ function preventDragover (event) {
91
+ if (helpers.hasClassnameInHeirarchy(event.target, 'asdndzone')) {
92
+ event.preventDefault()
93
+ }
94
+ }
95
+
96
+ function createUploadersForDroppedFiles (event) {
97
+ let asdndz = helpers.getClassnameFromHeirarchy(event.target, 'asdndzone')
98
+ if (asdndz) {
99
+ event.preventDefault()
100
+ // get the input associated with this dndz
101
+ const input = document.getElementById(asdndz.dataset.dndInputId)
102
+ Array.from(event.dataTransfer.files).forEach(file => createUploader(input, file))
103
+ }
104
+ }
105
+
106
+ function removeFileFromQueue (event) {
107
+ if (event.target.dataset.dndDelete === 'true' && event.target.hasAttribute('data-direct-upload-id')) {
108
+ event.preventDefault()
109
+ document.querySelectorAll('[data-direct-upload-id="' + event.target.dataset.directUploadId + '"]').forEach(element => {
110
+ element.remove()
111
+ })
112
+ for (var i = 0; i < uploaders.length; i++) {
113
+ if (uploaders[i].upload.id === event.target.dataset.directUploadId) {
114
+ uploaders.splice(i, 1)
115
+ break
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ export function start () {
61
122
  if (started) { return }
62
123
  started = true
63
- document.addEventListener("submit", didSubmitForm)
64
- document.addEventListener("ajax:before", didSubmitRemoteElement)
124
+ document.addEventListener('submit', didSubmitForm)
125
+ document.addEventListener('ajax:before', didSubmitRemoteElement)
126
+ document.addEventListener('dnd-uploads:process-upload-queue', processUploadQueue)
65
127
 
66
128
  // input[type=file][data-dnd=true]
67
- document.addEventListener("change", event => {
68
- if(event.target.type == 'file' && event.target.dataset.dnd == 'true') {
69
- const input = event.target;
70
- Array.from(input.files).forEach(file => createUploader(input, file))
71
- input.value = null
72
- }
73
- })
74
- document.addEventListener("dragover", event => {
75
- if(helpers.hasClassnameInHeirarchy(event.target, 'asdndzone')) {
76
- event.preventDefault();
77
- }
78
- })
79
- document.addEventListener("drop", event => {
80
- let asdndz = helpers.getClassnameFromHeirarchy(event.target, 'asdndzone')
81
- if(asdndz) {
82
- event.preventDefault()
83
- // get the input associated with this dndz
84
- const input = document.getElementById(asdndz.dataset.dndInputId)
85
- Array.from(event.dataTransfer.files).forEach(file => createUploader(input, file))
86
- }
87
- })
88
- document.addEventListener("click", event => {
89
- if( event.target.dataset.dndDelete == 'true' && event.target.hasAttribute('data-direct-upload-id') ) {
90
- event.preventDefault();
91
- document.querySelectorAll('[data-direct-upload-id="'+event.target.dataset.directUploadId+'"]').forEach(element => {
92
- element.remove()
93
- })
94
- for(var i=0;i<uploaders.length;i++) {
95
- if(uploaders[i].upload.id == event.target.dataset.directUploadId) {
96
- uploaders.splice( i, 1 )
97
- break
98
- }
99
- }
100
- }
101
- })
102
- addEventListener("turbolinks:load", addAttachedFileIcons);
103
- addEventListener("load", addAttachedFileIcons);
129
+ document.addEventListener('change', createUploadersForFileInput)
130
+ document.addEventListener('dragover', preventDragover)
131
+ document.addEventListener('drop', createUploadersForDroppedFiles)
132
+ document.addEventListener('click', removeFileFromQueue)
133
+ addAttachedFileIcons()
104
134
  }