active_storage_drag_and_drop 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }