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