active_storage_drag_and_drop 0.4.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.babelrc +3 -0
- data/.eslintrc.yml +16 -1
- data/.flowconfig +11 -0
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Gemfile.lock +47 -47
- data/README.md +80 -30
- data/active_storage_drag_and_drop.gemspec +1 -1
- data/app/assets/javascripts/active_storage_drag_and_drop.js +1 -171
- data/app/assets/stylesheets/active_storage_drag_and_drop.css +88 -6
- data/app/javascript/active_storage_drag_and_drop/default_ui.js +77 -0
- data/app/javascript/active_storage_drag_and_drop/drag_and_drop_form_controller.js +87 -0
- data/app/javascript/active_storage_drag_and_drop/drag_and_drop_upload_controller.js +102 -0
- data/app/javascript/active_storage_drag_and_drop/helpers.js +10 -54
- data/app/javascript/active_storage_drag_and_drop/index.js +5 -2
- data/app/javascript/active_storage_drag_and_drop/ujs.js +88 -108
- data/demo.webp +0 -0
- data/lib/active_storage_drag_and_drop/form_builder.rb +17 -6
- data/lib/active_storage_drag_and_drop/version.rb +1 -1
- data/package.json +23 -7
- data/webpack.config.js +3 -1
- data/yarn.lock +3396 -1098
- metadata +10 -7
- data/app/assets/stylesheets/direct_uploads.css +0 -43
- data/app/javascript/active_storage_drag_and_drop/direct_upload_controller.js +0 -76
- data/app/javascript/active_storage_drag_and_drop/upload_queue_processor.js +0 -113
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_storage_drag_and_drop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave O'Keeffe
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-04-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bootsnap
|
@@ -283,14 +283,14 @@ dependencies:
|
|
283
283
|
requirements:
|
284
284
|
- - "~>"
|
285
285
|
- !ruby/object:Gem::Version
|
286
|
-
version:
|
286
|
+
version: 5.2.2.1
|
287
287
|
type: :runtime
|
288
288
|
prerelease: false
|
289
289
|
version_requirements: !ruby/object:Gem::Requirement
|
290
290
|
requirements:
|
291
291
|
- - "~>"
|
292
292
|
- !ruby/object:Gem::Version
|
293
|
-
version:
|
293
|
+
version: 5.2.2.1
|
294
294
|
description: Provides a form helper to make it easy to make drag and drop file upload
|
295
295
|
fields that work with Rails' Active Storage.
|
296
296
|
email:
|
@@ -299,9 +299,11 @@ executables: []
|
|
299
299
|
extensions: []
|
300
300
|
extra_rdoc_files: []
|
301
301
|
files:
|
302
|
+
- ".babelrc"
|
302
303
|
- ".codeclimate.yml"
|
303
304
|
- ".eslintignore"
|
304
305
|
- ".eslintrc.yml"
|
306
|
+
- ".flowconfig"
|
305
307
|
- ".gitignore"
|
306
308
|
- ".rubocop.yml"
|
307
309
|
- ".travis.yml"
|
@@ -315,14 +317,15 @@ files:
|
|
315
317
|
- active_storage_drag_and_drop.gemspec
|
316
318
|
- app/assets/javascripts/active_storage_drag_and_drop.js
|
317
319
|
- app/assets/stylesheets/active_storage_drag_and_drop.css
|
318
|
-
- app/
|
319
|
-
- app/javascript/active_storage_drag_and_drop/
|
320
|
+
- app/javascript/active_storage_drag_and_drop/default_ui.js
|
321
|
+
- app/javascript/active_storage_drag_and_drop/drag_and_drop_form_controller.js
|
322
|
+
- app/javascript/active_storage_drag_and_drop/drag_and_drop_upload_controller.js
|
320
323
|
- app/javascript/active_storage_drag_and_drop/helpers.js
|
321
324
|
- app/javascript/active_storage_drag_and_drop/index.js
|
322
325
|
- app/javascript/active_storage_drag_and_drop/ujs.js
|
323
|
-
- app/javascript/active_storage_drag_and_drop/upload_queue_processor.js
|
324
326
|
- bin/console
|
325
327
|
- bin/setup
|
328
|
+
- demo.webp
|
326
329
|
- lib/active_storage_drag_and_drop.rb
|
327
330
|
- lib/active_storage_drag_and_drop/form_builder.rb
|
328
331
|
- lib/active_storage_drag_and_drop/version.rb
|
@@ -1,43 +0,0 @@
|
|
1
|
-
/* direct_uploads.css */
|
2
|
-
|
3
|
-
.direct-upload {
|
4
|
-
display: inline-block;
|
5
|
-
position: relative;
|
6
|
-
padding: 2px 4px;
|
7
|
-
margin: 0 3px 3px 0;
|
8
|
-
border: 1px solid rgba(0, 0, 0, 0.3);
|
9
|
-
border-radius: 3px;
|
10
|
-
font-size: 11px;
|
11
|
-
line-height: 13px;
|
12
|
-
}
|
13
|
-
|
14
|
-
.direct-upload--pending {
|
15
|
-
opacity: 0.6;
|
16
|
-
}
|
17
|
-
|
18
|
-
.direct-upload__progress {
|
19
|
-
position: absolute;
|
20
|
-
top: 0;
|
21
|
-
left: 0;
|
22
|
-
bottom: 0;
|
23
|
-
opacity: 0.2;
|
24
|
-
background: #0076ff;
|
25
|
-
transition: width 120ms ease-out, opacity 60ms 60ms ease-in;
|
26
|
-
transform: translate3d(0, 0, 0);
|
27
|
-
}
|
28
|
-
|
29
|
-
.direct-upload--complete .direct-upload__progress {
|
30
|
-
opacity: 0.4;
|
31
|
-
}
|
32
|
-
|
33
|
-
.direct-upload--error {
|
34
|
-
border-color: red;
|
35
|
-
}
|
36
|
-
|
37
|
-
.direct-upload__remove {
|
38
|
-
font-size: 0.5em;
|
39
|
-
}
|
40
|
-
|
41
|
-
input[type=file][data-direct-upload-url][disabled] {
|
42
|
-
display: none;
|
43
|
-
}
|
@@ -1,76 +0,0 @@
|
|
1
|
-
import { dispatchEvent, defaultErrorEventUI, defaultEndEventUI, fileUploadUIPainter } from './helpers'
|
2
|
-
import { DirectUpload } from 'activestorage'
|
3
|
-
const eventFamily = 'dnd-upload'
|
4
|
-
|
5
|
-
export class DragAndDropUploadController {
|
6
|
-
constructor (input, file) {
|
7
|
-
this.input = input
|
8
|
-
this.form = input.closest('form')
|
9
|
-
this.url = this.input.dataset.directUploadUrl
|
10
|
-
this.iconContainer = document.getElementById(this.input.dataset.iconContainerId)
|
11
|
-
this.file = file
|
12
|
-
this.upload = new DirectUpload(this.file, this.url, this)
|
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
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
start (callback) {
|
22
|
-
this.upload.create((error, blob) => {
|
23
|
-
if (error) {
|
24
|
-
// Handle the error
|
25
|
-
this.dispatchError(error)
|
26
|
-
callback(error)
|
27
|
-
} else {
|
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
|
31
|
-
const hiddenField = document.createElement('input')
|
32
|
-
hiddenField.setAttribute('type', 'hidden')
|
33
|
-
hiddenField.setAttribute('value', blob.signed_id)
|
34
|
-
hiddenField.name = this.input.name
|
35
|
-
hiddenField.setAttribute('data-direct-upload-id', this.upload.id)
|
36
|
-
this.form.appendChild(hiddenField)
|
37
|
-
let event = this.dispatch('end')
|
38
|
-
defaultEndEventUI(event)
|
39
|
-
callback(error)
|
40
|
-
}
|
41
|
-
})
|
42
|
-
}
|
43
|
-
|
44
|
-
dispatch (name, detail = {}) {
|
45
|
-
detail.file = this.file
|
46
|
-
detail.id = this.upload.id
|
47
|
-
detail.iconContainer = this.iconContainer
|
48
|
-
return dispatchEvent(this.input, `${eventFamily}:${name}`, { detail })
|
49
|
-
}
|
50
|
-
|
51
|
-
dispatchError (error) {
|
52
|
-
const event = this.dispatch('error', { error })
|
53
|
-
defaultErrorEventUI(event)
|
54
|
-
}
|
55
|
-
|
56
|
-
directUploadWillCreateBlobWithXHR (xhr) {
|
57
|
-
this.dispatch('before-blob-request', { xhr })
|
58
|
-
}
|
59
|
-
// directUploadWillStoreFileWithXHR
|
60
|
-
directUploadWillStoreFileWithXHR (xhr) {
|
61
|
-
this.dispatch('before-storage-request', { xhr })
|
62
|
-
xhr.upload.addEventListener('progress', event => this.uploadRequestDidProgress(event))
|
63
|
-
}
|
64
|
-
|
65
|
-
uploadRequestDidProgress (event) {
|
66
|
-
const progress = event.loaded / event.total * 100
|
67
|
-
if (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
|
-
}
|
74
|
-
}
|
75
|
-
}
|
76
|
-
}
|
@@ -1,113 +0,0 @@
|
|
1
|
-
import { dispatchEvent, defaultErrorEventUI, defaultEndEventUI, fileUploadUIPainter } from './helpers'
|
2
|
-
import { DragAndDropUploadController } from './direct_upload_controller'
|
3
|
-
export const uploaders = []
|
4
|
-
|
5
|
-
class ValidationError extends Error {
|
6
|
-
constructor (...args) {
|
7
|
-
super(...args)
|
8
|
-
Error.captureStackTrace(this, ValidationError)
|
9
|
-
}
|
10
|
-
}
|
11
|
-
|
12
|
-
export class UploadQueueProcessor {
|
13
|
-
constructor (form) {
|
14
|
-
this.form = form
|
15
|
-
this.current_uploaders = []
|
16
|
-
uploaders.forEach(uploader => {
|
17
|
-
if (form === uploader.form) {
|
18
|
-
this.current_uploaders.push(uploader)
|
19
|
-
}
|
20
|
-
})
|
21
|
-
}
|
22
|
-
|
23
|
-
start (callback) {
|
24
|
-
const startNextUploader = () => {
|
25
|
-
const nextUploader = this.current_uploaders.shift()
|
26
|
-
if (nextUploader) {
|
27
|
-
nextUploader.start(error => {
|
28
|
-
if (error) {
|
29
|
-
this.dispatchError(error)
|
30
|
-
callback(error)
|
31
|
-
} else {
|
32
|
-
startNextUploader()
|
33
|
-
}
|
34
|
-
})
|
35
|
-
} else {
|
36
|
-
callback()
|
37
|
-
let event = this.dispatch('end')
|
38
|
-
defaultEndEventUI(event)
|
39
|
-
}
|
40
|
-
}
|
41
|
-
|
42
|
-
this.dispatch('start')
|
43
|
-
startNextUploader()
|
44
|
-
}
|
45
|
-
|
46
|
-
dispatch (name, detail = {}) {
|
47
|
-
return dispatchEvent(this.form, `dnd-uploads:${name}`, { detail })
|
48
|
-
}
|
49
|
-
|
50
|
-
dispatchError (error) {
|
51
|
-
const event = this.dispatch('error', { error })
|
52
|
-
defaultErrorEventUI(event)
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
export function createUploader (input, file) {
|
57
|
-
// your form needs the file_field direct_upload: true, which
|
58
|
-
// provides data-direct-upload-url
|
59
|
-
const error = validateUploader(input, file)
|
60
|
-
if (error) {
|
61
|
-
let detail = {
|
62
|
-
id: null,
|
63
|
-
file: file,
|
64
|
-
iconContainer: input.dataset.iconContainerId,
|
65
|
-
error: error
|
66
|
-
}
|
67
|
-
return dispatchErrorWithoutAttachment(input, detail)
|
68
|
-
}
|
69
|
-
if (!input.multiple) { removeAttachedFiles(input) }
|
70
|
-
uploaders.push(new DragAndDropUploadController(input, file))
|
71
|
-
}
|
72
|
-
|
73
|
-
function removeAttachedFiles (input) {
|
74
|
-
input.closest('label.asdndzone').querySelectorAll('[data-direct-upload-id]').forEach(element => {
|
75
|
-
element.remove()
|
76
|
-
})
|
77
|
-
uploaders.splice(0, uploaders.length)
|
78
|
-
}
|
79
|
-
|
80
|
-
function dispatchErrorWithoutAttachment (input, detail) {
|
81
|
-
let event = dispatchEvent(input, 'dnd-upload:error', { detail })
|
82
|
-
if (!event.defaultPrevented) {
|
83
|
-
const { error, iconContainer, file } = event.detail
|
84
|
-
fileUploadUIPainter(iconContainer, 'error', file.name, true)
|
85
|
-
const element = document.getElementById(`direct-upload-error`)
|
86
|
-
element.classList.add('direct-upload--error')
|
87
|
-
element.setAttribute('title', error)
|
88
|
-
}
|
89
|
-
return event
|
90
|
-
}
|
91
|
-
|
92
|
-
function validateUploader (input, file) {
|
93
|
-
const sizeLimit = input.getAttribute('size_limit')
|
94
|
-
if (input.accept !== '' && !input.accept.split(', ').includes(file.type)) {
|
95
|
-
return new ValidationError('Invalid filetype')
|
96
|
-
} else if (sizeLimit && file.size > sizeLimit) {
|
97
|
-
return new ValidationError(`File too large. Can be no larger than ${humanFileSize(sizeLimit)}`)
|
98
|
-
}
|
99
|
-
}
|
100
|
-
|
101
|
-
function humanFileSize (bytes) {
|
102
|
-
var thresh = 1000
|
103
|
-
if (Math.abs(bytes) < thresh) {
|
104
|
-
return bytes + ' B'
|
105
|
-
}
|
106
|
-
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
107
|
-
var u = -1
|
108
|
-
do {
|
109
|
-
bytes /= thresh
|
110
|
-
++u
|
111
|
-
} while (Math.abs(bytes) >= thresh && u < units.length - 1)
|
112
|
-
return bytes.toFixed(1) + ' ' + units[u]
|
113
|
-
}
|