diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx index 36cf5d4449f7ddd1815cb5388d3784915a709a3e..f9e50f3ff667e02ff78cc5d345e761993e9b8709 100644 --- a/src/components/action-menu/action-menu.jsx +++ b/src/components/action-menu/action-menu.jsx @@ -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, diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index 115eaee955f25d6ccba82b86c7f8206cc0ee3719..d834f43b03bd44a23ce4fe6326543670439dbe27 100644 --- a/src/components/sprite-selector/sprite-selector.jsx +++ b/src/components/sprite-selector/sprite-selector.jsx @@ -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, diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx index 64a1948a82670324221c5743f0657e9215d89677..db89a67c5a961a5419d7bf5cb850bd75ae504813 100644 --- a/src/components/stage-selector/stage-selector.jsx +++ b/src/components/stage-selector/stage-selector.jsx @@ -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, diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 39e0419acdd720dec654f37bf9244dfe2014e573..3817c4f3c9fda0b4111194eaa95298bd5abe4285 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -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), diff --git a/src/containers/sound-tab.jsx b/src/containers/sound-tab.jsx index 3880de2e5349d3df811651a96846cb5c9b06e1a8..9db3289d96d9eeb61b30a6d5fbe60244b5ec61a1 100644 --- a/src/containers/sound-tab.jsx +++ b/src/containers/sound-tab.jsx @@ -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, diff --git a/src/lib/file-uploader.js b/src/lib/file-uploader.js index 74f3c64a8625c05610c4e0d505484e56fc4a8600..483f31edf61c95a08d6aa38522df9eda52d0aac7 100644 --- a/src/lib/file-uploader.js +++ b/src/lib/file-uploader.js @@ -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); }; /** diff --git a/test/fixtures/movie.wav b/test/fixtures/movie.wav new file mode 100644 index 0000000000000000000000000000000000000000..79c10d2146204fa108038bc4a67c853ec81a4707 Binary files /dev/null and b/test/fixtures/movie.wav differ diff --git a/test/fixtures/sneaker.wav b/test/fixtures/sneaker.wav new file mode 100644 index 0000000000000000000000000000000000000000..01c4a17597b5e747368094e61c2e8d0d3414cc2b Binary files /dev/null and b/test/fixtures/sneaker.wav differ diff --git a/test/integration/backdrops.test.js b/test/integration/backdrops.test.js index 9a527b2c9d4caa39287b9566af459648efb6ae7b..d0a3e84aa841f73309fb36c88019092e943b6a03 100644 --- a/test/integration/backdrops.test.js +++ b/test/integration/backdrops.test.js @@ -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([]); + }); }); diff --git a/test/integration/costumes.test.js b/test/integration/costumes.test.js index 5181c429a6b69cb0298f9eb677787c672c3ac8dd..803abbdafde5649a6de75157b500b6ce3fe0b649 100644 --- a/test/integration/costumes.test.js +++ b/test/integration/costumes.test.js @@ -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([]); + }); }); diff --git a/test/integration/sounds.test.js b/test/integration/sounds.test.js index 3877ada3f8fa90acb8e866418fcc5ff1425b516d..940e4750cbc17da4520646e4a29216cf47fd57a9 100644 --- a/test/integration/sounds.test.js +++ b/test/integration/sounds.test.js @@ -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([]); + }); }); diff --git a/test/integration/sprites.test.js b/test/integration/sprites.test.js index a3dd48cf4a5e319fd8660276ed22330910eeca53..12ab36a5fb7bfd172585e4c4670e929a5b1ff7e4 100644 --- a/test/integration/sprites.test.js +++ b/test/integration/sprites.test.js @@ -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([]); + }); + });