Skip to content

Commit 8e396b0

Browse files
committed
[Dropzone] Improve display with multiple files
1 parent 61819ae commit 8e396b0

File tree

9 files changed

+218
-121
lines changed

9 files changed

+218
-121
lines changed

src/Dropzone/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## 2.24
4+
5+
- Preview works with muliple files
6+
37
## 2.20
48

59
- Enable file replacement via "drag-and-drop"

src/Dropzone/assets/dist/controller.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ export default class extends Controller {
66
readonly previewClearButtonTarget: HTMLButtonElement;
77
readonly previewFilenameTarget: HTMLDivElement;
88
readonly previewImageTarget: HTMLDivElement;
9+
readonly containerTarget: HTMLDivElement;
910
static targets: string[];
1011
initialize(): void;
1112
connect(): void;
1213
disconnect(): void;
1314
clear(): void;
1415
onInputChange(event: any): void;
15-
_populateImagePreview(file: Blob): void;
16+
_populateImagePreview(file: Blob, imagePreviewElement: HTMLElement): void;
1617
onDragEnter(): void;
1718
onDragLeave(event: any): void;
1819
private dispatchEvent;

src/Dropzone/assets/dist/controller.js

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,55 +25,79 @@ class default_1 extends Controller {
2525
this.inputTarget.value = '';
2626
this.inputTarget.style.display = 'block';
2727
this.placeholderTarget.style.display = 'block';
28+
this.previewTarget.innerHTML = '';
2829
this.previewTarget.style.display = 'none';
29-
this.previewImageTarget.style.display = 'none';
30-
this.previewImageTarget.style.backgroundImage = 'none';
31-
this.previewFilenameTarget.textContent = '';
3230
this.dispatchEvent('clear');
3331
}
3432
onInputChange(event) {
35-
const file = event.target.files[0];
36-
if (typeof file === 'undefined') {
33+
const files = event.target.files;
34+
if (files.length === 0) {
35+
this.previewClearButtonTarget.style.display = 'none';
3736
return;
3837
}
3938
this.inputTarget.style.display = 'none';
4039
this.placeholderTarget.style.display = 'none';
41-
this.previewFilenameTarget.textContent = file.name;
42-
this.previewTarget.style.display = 'flex';
43-
this.previewImageTarget.style.display = 'none';
44-
if (file.type && file.type.indexOf('image') !== -1) {
45-
this._populateImagePreview(file);
40+
this.previewTarget.innerHTML = '';
41+
for (const file of files) {
42+
const filePreviewContainer = document.createElement('div');
43+
filePreviewContainer.classList.add('dropzone-preview-file');
44+
const fileNameElement = document.createElement('span');
45+
fileNameElement.textContent = file.name;
46+
filePreviewContainer.appendChild(fileNameElement);
47+
if (file.type) {
48+
const imagePreviewElement = document.createElement('div');
49+
if (file.type.indexOf('image') !== -1) {
50+
imagePreviewElement.classList.add('dropzone-preview-image');
51+
this._populateImagePreview(file, imagePreviewElement);
52+
}
53+
else {
54+
imagePreviewElement.classList.add('dropzone-preview-svg');
55+
}
56+
filePreviewContainer.appendChild(imagePreviewElement);
57+
}
58+
this.previewTarget.appendChild(filePreviewContainer);
59+
this.dispatchEvent('change', file);
4660
}
47-
this.dispatchEvent('change', file);
61+
this.previewTarget.style.display = 'grid';
4862
}
49-
_populateImagePreview(file) {
63+
_populateImagePreview(file, imagePreviewElement) {
5064
if (typeof FileReader === 'undefined') {
5165
return;
5266
}
5367
const reader = new FileReader();
5468
reader.addEventListener('load', (event) => {
55-
this.previewImageTarget.style.display = 'block';
56-
this.previewImageTarget.style.backgroundImage = `url("${event.target.result}")`;
69+
imagePreviewElement.style.backgroundImage = `url("${event.target.result}")`;
70+
imagePreviewElement.style.display = 'block';
5771
});
5872
reader.readAsDataURL(file);
5973
}
6074
onDragEnter() {
6175
this.inputTarget.style.display = 'block';
6276
this.placeholderTarget.style.display = 'block';
6377
this.previewTarget.style.display = 'none';
78+
this.containerTarget.classList.add('dropzone-on-drag-enter');
6479
}
6580
onDragLeave(event) {
6681
event.preventDefault();
6782
if (!this.element.contains(event.relatedTarget)) {
6883
this.inputTarget.style.display = 'none';
6984
this.placeholderTarget.style.display = 'none';
7085
this.previewTarget.style.display = 'block';
86+
this.containerTarget.classList.add('dropzone-on-drag-leave');
7187
}
7288
}
7389
dispatchEvent(name, payload = {}) {
7490
this.dispatch(name, { detail: payload, prefix: 'dropzone' });
7591
}
7692
}
77-
default_1.targets = ['input', 'placeholder', 'preview', 'previewClearButton', 'previewFilename', 'previewImage'];
93+
default_1.targets = [
94+
'container',
95+
'input',
96+
'placeholder',
97+
'preview',
98+
'previewClearButton',
99+
'previewFilename',
100+
'previewImage',
101+
];
78102

79103
export { default_1 as default };

src/Dropzone/assets/dist/style.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Dropzone/assets/src/controller.ts

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@ export default class extends Controller {
1616
declare readonly previewClearButtonTarget: HTMLButtonElement;
1717
declare readonly previewFilenameTarget: HTMLDivElement;
1818
declare readonly previewImageTarget: HTMLDivElement;
19-
20-
static targets = ['input', 'placeholder', 'preview', 'previewClearButton', 'previewFilename', 'previewImage'];
19+
declare readonly containerTarget: HTMLDivElement;
20+
21+
static targets = [
22+
'container',
23+
'input',
24+
'placeholder',
25+
'preview',
26+
'previewClearButton',
27+
'previewFilename',
28+
'previewImage',
29+
];
2130

2231
initialize() {
2332
this.clear = this.clear.bind(this);
@@ -56,38 +65,60 @@ export default class extends Controller {
5665
this.inputTarget.value = '';
5766
this.inputTarget.style.display = 'block';
5867
this.placeholderTarget.style.display = 'block';
68+
this.previewTarget.innerHTML = '';
5969
this.previewTarget.style.display = 'none';
60-
this.previewImageTarget.style.display = 'none';
61-
this.previewImageTarget.style.backgroundImage = 'none';
62-
this.previewFilenameTarget.textContent = '';
6370

6471
this.dispatchEvent('clear');
6572
}
6673

6774
onInputChange(event: any) {
68-
const file = event.target.files[0];
69-
if (typeof file === 'undefined') {
75+
const files = event.target.files;
76+
if (files.length === 0) {
77+
this.previewClearButtonTarget.style.display = 'none';
7078
return;
7179
}
7280

7381
// Hide the input and placeholder
7482
this.inputTarget.style.display = 'none';
7583
this.placeholderTarget.style.display = 'none';
7684

77-
// Show the filename in preview
78-
this.previewFilenameTarget.textContent = file.name;
79-
this.previewTarget.style.display = 'flex';
85+
// Clear previous previews
86+
this.previewTarget.innerHTML = '';
87+
88+
for (const file of files) {
89+
// Create a container for each file preview
90+
const filePreviewContainer = document.createElement('div');
91+
filePreviewContainer.classList.add('dropzone-preview-file');
92+
93+
// Create a filename preview element
94+
const fileNameElement = document.createElement('span');
95+
fileNameElement.textContent = file.name;
96+
filePreviewContainer.appendChild(fileNameElement);
97+
98+
// Create an image preview element if the file is an image, else a default svg file icon
99+
if (file.type) {
100+
const imagePreviewElement = document.createElement('div');
101+
if (file.type.indexOf('image') !== -1) {
102+
imagePreviewElement.classList.add('dropzone-preview-image');
103+
this._populateImagePreview(file, imagePreviewElement);
104+
} else {
105+
imagePreviewElement.classList.add('dropzone-preview-svg');
106+
}
107+
108+
filePreviewContainer.appendChild(imagePreviewElement);
109+
}
110+
111+
// Append the file preview container to the main preview target
112+
this.previewTarget.appendChild(filePreviewContainer);
80113

81-
// If the file is an image, load it and display it as preview
82-
this.previewImageTarget.style.display = 'none';
83-
if (file.type && file.type.indexOf('image') !== -1) {
84-
this._populateImagePreview(file);
114+
this.dispatchEvent('change', file);
85115
}
86116

87-
this.dispatchEvent('change', file);
117+
// Show the preview container
118+
this.previewTarget.style.display = 'grid';
88119
}
89120

90-
_populateImagePreview(file: Blob) {
121+
_populateImagePreview(file: Blob, imagePreviewElement: HTMLElement) {
91122
if (typeof FileReader === 'undefined') {
92123
// FileReader API not available, skip
93124
return;
@@ -96,8 +127,8 @@ export default class extends Controller {
96127
const reader = new FileReader();
97128

98129
reader.addEventListener('load', (event: any) => {
99-
this.previewImageTarget.style.display = 'block';
100-
this.previewImageTarget.style.backgroundImage = `url("${event.target.result}")`;
130+
imagePreviewElement.style.backgroundImage = `url("${event.target.result}")`;
131+
imagePreviewElement.style.display = 'block';
101132
});
102133

103134
reader.readAsDataURL(file);
@@ -107,6 +138,7 @@ export default class extends Controller {
107138
this.inputTarget.style.display = 'block';
108139
this.placeholderTarget.style.display = 'block';
109140
this.previewTarget.style.display = 'none';
141+
this.containerTarget.classList.add('dropzone-on-drag-enter');
110142
}
111143

112144
onDragLeave(event: any) {
@@ -117,6 +149,7 @@ export default class extends Controller {
117149
this.inputTarget.style.display = 'none';
118150
this.placeholderTarget.style.display = 'none';
119151
this.previewTarget.style.display = 'block';
152+
this.containerTarget.classList.add('dropzone-on-drag-leave');
120153
}
121154
}
122155

0 commit comments

Comments
 (0)