Skip to content

Commit da65b41

Browse files
authored
Merge pull request scratchfoundation#4499 from LLK/multi-upload
Multiple file upload for costumes, sounds, backdrops and sprites
2 parents 65f9f7f + 65756fc commit da65b41

File tree

12 files changed

+123
-21
lines changed

12 files changed

+123
-21
lines changed

src/components/action-menu/action-menu.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ class ActionMenu extends React.Component {
142142
<div className={styles.moreButtonsOuter}>
143143
<div className={styles.moreButtons}>
144144
{(moreButtons || []).map(({img, title, onClick: handleClick,
145-
fileAccept, fileChange, fileInput}, keyId) => {
145+
fileAccept, fileChange, fileInput, fileMultiple}, keyId) => {
146146
const isComingSoon = !handleClick;
147147
const hasFileInput = fileInput;
148148
const tooltipId = `${this.mainTooltipId}-${title}`;
@@ -166,6 +166,7 @@ class ActionMenu extends React.Component {
166166
<input
167167
accept={fileAccept}
168168
className={styles.fileInput}
169+
multiple={fileMultiple}
169170
ref={fileInput}
170171
type="file"
171172
onChange={fileChange}
@@ -198,7 +199,8 @@ ActionMenu.propTypes = {
198199
onClick: PropTypes.func, // Optional, "coming soon" if no callback provided
199200
fileAccept: PropTypes.string, // Optional, only for file upload
200201
fileChange: PropTypes.func, // Optional, only for file upload
201-
fileInput: PropTypes.func // Optional, only for file upload
202+
fileInput: PropTypes.func, // Optional, only for file upload
203+
fileMultiple: PropTypes.bool // Optional, only for file upload
202204
})),
203205
onClick: PropTypes.func.isRequired,
204206
title: PropTypes.node.isRequired,

src/components/sprite-selector/sprite-selector.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ const SpriteSelectorComponent = function (props) {
122122
onClick: onFileUploadClick,
123123
fileAccept: '.svg, .png, .jpg, .jpeg, .sprite2, .sprite3',
124124
fileChange: onSpriteUpload,
125-
fileInput: spriteFileInput
125+
fileInput: spriteFileInput,
126+
fileMultiple: true
126127
}, {
127128
title: intl.formatMessage(messages.addSpriteFromSurprise),
128129
img: surpriseIcon,

src/components/stage-selector/stage-selector.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ const StageSelector = props => {
104104
onClick: onBackdropFileUploadClick,
105105
fileAccept: '.svg, .png, .jpg, .jpeg', // Bitmap coming soon
106106
fileChange: onBackdropFileUpload,
107-
fileInput: fileInputRef
107+
fileInput: fileInputRef,
108+
fileMultiple: true
108109
}, {
109110
title: intl.formatMessage(messages.addBackdropFromSurprise),
110111
img: surpriseIcon,

src/containers/costume-tab.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,8 @@ class CostumeTab extends React.Component {
293293
onClick: this.handleFileUploadClick,
294294
fileAccept: '.svg, .png, .jpg, .jpeg',
295295
fileChange: this.handleCostumeUpload,
296-
fileInput: this.setFileInput
296+
fileInput: this.setFileInput,
297+
fileMultiple: true
297298
},
298299
{
299300
title: intl.formatMessage(messages.addSurpriseCostumeMsg),

src/containers/sound-tab.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ class SoundTab extends React.Component {
223223
onClick: this.handleFileUploadClick,
224224
fileAccept: '.wav, .mp3',
225225
fileChange: this.handleSoundUpload,
226-
fileInput: this.setFileInput
226+
fileInput: this.setFileInput,
227+
fileMultiple: true
227228
}, {
228229
title: intl.formatMessage(messages.surpriseSound),
229230
img: surpriseIcon,

src/lib/file-uploader.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,25 @@ const extractFileName = function (nameExt) {
2121
* @param {Function} onload The function that handles loading the file
2222
*/
2323
const handleFileUpload = function (fileInput, onload) {
24-
let thisFile = null;
25-
const reader = new FileReader();
26-
reader.onload = () => {
27-
// Reset the file input value now that we have everything we need
28-
// so that the user can upload the same sound multiple times if
29-
// they choose
30-
fileInput.value = null;
31-
const fileType = thisFile.type;
32-
const fileName = extractFileName(thisFile.name);
33-
34-
onload(reader.result, fileType, fileName);
24+
const readFile = (i, files) => {
25+
if (i === files.length) {
26+
// Reset the file input value now that we have everything we need
27+
// so that the user can upload the same sound multiple times if
28+
// they choose
29+
fileInput.value = null;
30+
return;
31+
}
32+
const file = files[i];
33+
const reader = new FileReader();
34+
reader.onload = () => {
35+
const fileType = file.type;
36+
const fileName = extractFileName(file.name);
37+
onload(reader.result, fileType, fileName);
38+
readFile(i + 1, files);
39+
};
40+
reader.readAsArrayBuffer(file);
3541
};
36-
if (fileInput.files) {
37-
thisFile = fileInput.files[0];
38-
reader.readAsArrayBuffer(thisFile);
39-
}
42+
readFile(0, fileInput.files);
4043
};
4144

4245
/**

test/fixtures/movie.wav

74.6 KB
Binary file not shown.

test/fixtures/sneaker.wav

4.56 KB
Binary file not shown.

test/integration/backdrops.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import SeleniumHelper from '../helpers/selenium-helper';
44
const {
55
clickText,
66
clickXpath,
7+
findByText,
78
findByXpath,
89
getDriver,
910
getLogs,
@@ -49,4 +50,30 @@ describe('Working with backdrops', () => {
4950
const logs = await getLogs();
5051
await expect(logs).toEqual([]);
5152
});
53+
54+
test.only('Adding multiple backdrops at the same time', async () => {
55+
const files = [
56+
path.resolve(__dirname, '../fixtures/gh-3582-png.png'),
57+
path.resolve(__dirname, '../fixtures/100-100.svg')
58+
];
59+
await loadUri(uri);
60+
await clickXpath('//button[@title="Try It"]');
61+
62+
const buttonXpath = '//button[@aria-label="Choose a Backdrop"]';
63+
const fileXpath = `${buttonXpath}/following-sibling::div//input[@type="file"]`;
64+
65+
const el = await findByXpath(buttonXpath);
66+
await driver.actions().mouseMove(el)
67+
.perform();
68+
await driver.sleep(500); // Wait for thermometer menu to come up
69+
const input = await findByXpath(fileXpath);
70+
await input.sendKeys(files.join('\n'));
71+
72+
await clickXpath('//span[text()="Stage"]');
73+
await findByText('gh-3582-png', scope.costumesTab);
74+
await findByText('100-100', scope.costumesTab);
75+
76+
const logs = await getLogs();
77+
await expect(logs).toEqual([]);
78+
});
5279
});

test/integration/costumes.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import SeleniumHelper from '../helpers/selenium-helper';
44
const {
55
clickText,
66
clickXpath,
7+
findByText,
78
findByXpath,
89
getDriver,
910
getLogs,
@@ -181,4 +182,25 @@ describe('Working with costumes', () => {
181182
await expect(logs).toEqual([]);
182183
});
183184

185+
test.only('Adding multiple costumes at the same time', async () => {
186+
const files = [
187+
path.resolve(__dirname, '../fixtures/gh-3582-png.png'),
188+
path.resolve(__dirname, '../fixtures/100-100.svg')
189+
];
190+
await loadUri(uri);
191+
await clickXpath('//button[@title="Try It"]');
192+
await clickText('Costumes');
193+
const el = await findByXpath('//button[@aria-label="Choose a Costume"]');
194+
await driver.actions().mouseMove(el)
195+
.perform();
196+
await driver.sleep(500); // Wait for thermometer menu to come up
197+
const input = await findByXpath('//input[@type="file"]');
198+
await input.sendKeys(files.join('\n'));
199+
200+
await findByText('gh-3582-png', scope.costumesTab);
201+
await findByText('100-100', scope.costumesTab);
202+
203+
const logs = await getLogs();
204+
await expect(logs).toEqual([]);
205+
});
184206
});

0 commit comments

Comments
 (0)