Merge pull request #4499 from LLK/multi-upload
Multiple file upload for costumes, sounds, backdrops and sprites
This commit is contained in:
12 files changed
+123
-21
No files matched your search
@@ -142,7 +142,7 @@ class ActionMenu extends React.Component {
|
||||
<div className={styles.moreButtonsOuter}>
|
||||
<div className={styles.moreButtons}>
|
||||
{(moreButtons || []).map(({img, title, onClick: handleClick,
|
||||
fileAccept, fileChange, fileInput}, keyId) => {
|
||||
fileAccept, fileChange, fileInput, fileMultiple}, keyId) => {
|
||||
const isComingSoon = !handleClick;
|
||||
const hasFileInput = fileInput;
|
||||
const tooltipId = `${this.mainTooltipId}-${title}`;
|
||||
@@ -166,6 +166,7 @@ class ActionMenu extends React.Component {
|
||||
<input
|
||||
accept={fileAccept}
|
||||
className={styles.fileInput}
|
||||
multiple={fileMultiple}
|
||||
ref={fileInput}
|
||||
type="file"
|
||||
onChange={fileChange}
|
||||
@@ -198,7 +199,8 @@ ActionMenu.propTypes = {
|
||||
onClick: PropTypes.func, // Optional, "coming soon" if no callback provided
|
||||
fileAccept: PropTypes.string, // Optional, only for file upload
|
||||
fileChange: PropTypes.func, // Optional, only for file upload
|
||||
fileInput: PropTypes.func // Optional, only for file upload
|
||||
fileInput: PropTypes.func, // Optional, only for file upload
|
||||
fileMultiple: PropTypes.bool // Optional, only for file upload
|
||||
})),
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.node.isRequired,
|
||||
|
||||
@@ -122,7 +122,8 @@ const SpriteSelectorComponent = function (props) {
|
||||
onClick: onFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg, .sprite2, .sprite3',
|
||||
fileChange: onSpriteUpload,
|
||||
fileInput: spriteFileInput
|
||||
fileInput: spriteFileInput,
|
||||
fileMultiple: true
|
||||
}, {
|
||||
title: intl.formatMessage(messages.addSpriteFromSurprise),
|
||||
img: surpriseIcon,
|
||||
|
||||
@@ -104,7 +104,8 @@ const StageSelector = props => {
|
||||
onClick: onBackdropFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg', // Bitmap coming soon
|
||||
fileChange: onBackdropFileUpload,
|
||||
fileInput: fileInputRef
|
||||
fileInput: fileInputRef,
|
||||
fileMultiple: true
|
||||
}, {
|
||||
title: intl.formatMessage(messages.addBackdropFromSurprise),
|
||||
img: surpriseIcon,
|
||||
|
||||
@@ -293,7 +293,8 @@ class CostumeTab extends React.Component {
|
||||
onClick: this.handleFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg',
|
||||
fileChange: this.handleCostumeUpload,
|
||||
fileInput: this.setFileInput
|
||||
fileInput: this.setFileInput,
|
||||
fileMultiple: true
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.addSurpriseCostumeMsg),
|
||||
|
||||
@@ -223,7 +223,8 @@ class SoundTab extends React.Component {
|
||||
onClick: this.handleFileUploadClick,
|
||||
fileAccept: '.wav, .mp3',
|
||||
fileChange: this.handleSoundUpload,
|
||||
fileInput: this.setFileInput
|
||||
fileInput: this.setFileInput,
|
||||
fileMultiple: true
|
||||
}, {
|
||||
title: intl.formatMessage(messages.surpriseSound),
|
||||
img: surpriseIcon,
|
||||
|
||||
+18
-15
@@ -21,22 +21,25 @@ const extractFileName = function (nameExt) {
|
||||
* @param {Function} onload The function that handles loading the file
|
||||
*/
|
||||
const handleFileUpload = function (fileInput, onload) {
|
||||
let thisFile = null;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// Reset the file input value now that we have everything we need
|
||||
// so that the user can upload the same sound multiple times if
|
||||
// they choose
|
||||
fileInput.value = null;
|
||||
const fileType = thisFile.type;
|
||||
const fileName = extractFileName(thisFile.name);
|
||||
|
||||
onload(reader.result, fileType, fileName);
|
||||
const readFile = (i, files) => {
|
||||
if (i === files.length) {
|
||||
// Reset the file input value now that we have everything we need
|
||||
// so that the user can upload the same sound multiple times if
|
||||
// they choose
|
||||
fileInput.value = null;
|
||||
return;
|
||||
}
|
||||
const file = files[i];
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const fileType = file.type;
|
||||
const fileName = extractFileName(file.name);
|
||||
onload(reader.result, fileType, fileName);
|
||||
readFile(i + 1, files);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
};
|
||||
if (fileInput.files) {
|
||||
thisFile = fileInput.files[0];
|
||||
reader.readAsArrayBuffer(thisFile);
|
||||
}
|
||||
readFile(0, fileInput.files);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -4,6 +4,7 @@ import SeleniumHelper from '../helpers/selenium-helper';
|
||||
const {
|
||||
clickText,
|
||||
clickXpath,
|
||||
findByText,
|
||||
findByXpath,
|
||||
getDriver,
|
||||
getLogs,
|
||||
@@ -49,4 +50,30 @@ describe('Working with backdrops', () => {
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test.only('Adding multiple backdrops at the same time', async () => {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../fixtures/gh-3582-png.png'),
|
||||
path.resolve(__dirname, '../fixtures/100-100.svg')
|
||||
];
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="Try It"]');
|
||||
|
||||
const buttonXpath = '//button[@aria-label="Choose a Backdrop"]';
|
||||
const fileXpath = `${buttonXpath}/following-sibling::div//input[@type="file"]`;
|
||||
|
||||
const el = await findByXpath(buttonXpath);
|
||||
await driver.actions().mouseMove(el)
|
||||
.perform();
|
||||
await driver.sleep(500); // Wait for thermometer menu to come up
|
||||
const input = await findByXpath(fileXpath);
|
||||
await input.sendKeys(files.join('\n'));
|
||||
|
||||
await clickXpath('//span[text()="Stage"]');
|
||||
await findByText('gh-3582-png', scope.costumesTab);
|
||||
await findByText('100-100', scope.costumesTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import SeleniumHelper from '../helpers/selenium-helper';
|
||||
const {
|
||||
clickText,
|
||||
clickXpath,
|
||||
findByText,
|
||||
findByXpath,
|
||||
getDriver,
|
||||
getLogs,
|
||||
@@ -181,4 +182,25 @@ describe('Working with costumes', () => {
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test.only('Adding multiple costumes at the same time', async () => {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../fixtures/gh-3582-png.png'),
|
||||
path.resolve(__dirname, '../fixtures/100-100.svg')
|
||||
];
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="Try It"]');
|
||||
await clickText('Costumes');
|
||||
const el = await findByXpath('//button[@aria-label="Choose a Costume"]');
|
||||
await driver.actions().mouseMove(el)
|
||||
.perform();
|
||||
await driver.sleep(500); // Wait for thermometer menu to come up
|
||||
const input = await findByXpath('//input[@type="file"]');
|
||||
await input.sendKeys(files.join('\n'));
|
||||
|
||||
await findByText('gh-3582-png', scope.costumesTab);
|
||||
await findByText('100-100', scope.costumesTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import SeleniumHelper from '../helpers/selenium-helper';
|
||||
const {
|
||||
clickText,
|
||||
clickXpath,
|
||||
findByText,
|
||||
findByXpath,
|
||||
getDriver,
|
||||
getLogs,
|
||||
@@ -114,4 +115,26 @@ describe('Working with sounds', () => {
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test.only('Adding multiple sounds at the same time', async () => {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../fixtures/movie.wav'),
|
||||
path.resolve(__dirname, '../fixtures/sneaker.wav')
|
||||
];
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="Try It"]');
|
||||
await clickText('Sounds');
|
||||
const el = await findByXpath('//button[@aria-label="Choose a Sound"]');
|
||||
await driver.actions().mouseMove(el)
|
||||
.perform();
|
||||
await driver.sleep(500); // Wait for thermometer menu to come up
|
||||
const input = await findByXpath('//input[@type="file"]');
|
||||
await input.sendKeys(files.join('\n'));
|
||||
|
||||
await findByText('movie', scope.soundsTab);
|
||||
await findByText('sneaker', scope.soundsTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -159,4 +159,25 @@ describe('Working with sprites', () => {
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test.only('Adding multiple sprites at the same time', async () => {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../fixtures/gh-3582-png.png'),
|
||||
path.resolve(__dirname, '../fixtures/100-100.svg')
|
||||
];
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="Try It"]');
|
||||
const el = await findByXpath('//button[@aria-label="Choose a Sprite"]');
|
||||
await driver.actions().mouseMove(el)
|
||||
.perform();
|
||||
await driver.sleep(500); // Wait for thermometer menu to come up
|
||||
const input = await findByXpath('//input[@type="file"]');
|
||||
await input.sendKeys(files.join('\n'));
|
||||
|
||||
await findByText('gh-3582-png', scope.spriteTile);
|
||||
await findByText('100-100', scope.spriteTile);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in new issue
Block a user