8 files changed
+86
-6
No files matched your search
+1
-1
@@ -94,7 +94,7 @@
|
||||
"scratch-paint": "0.2.0-prerelease.20180222192821",
|
||||
"scratch-render": "0.1.0-prerelease.1516837442",
|
||||
"scratch-storage": "0.4.0",
|
||||
"scratch-vm": "0.1.0-prerelease.1518812805-prerelease.1518812820",
|
||||
"scratch-vm": "0.1.0-prerelease.1519653784-prerelease.1519653801",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.20.0",
|
||||
|
||||
@@ -13,6 +13,7 @@ const Selector = props => {
|
||||
items,
|
||||
selectedItemIndex,
|
||||
onDeleteClick,
|
||||
onDuplicateClick,
|
||||
onItemClick
|
||||
} = props;
|
||||
|
||||
@@ -30,6 +31,7 @@ const Selector = props => {
|
||||
selected={index === selectedItemIndex}
|
||||
onClick={onItemClick}
|
||||
onDeleteButtonClick={onDeleteClick}
|
||||
onDuplicateButtonClick={onDuplicateClick}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
@@ -58,6 +60,7 @@ Selector.propTypes = {
|
||||
name: PropTypes.string.isRequired
|
||||
})),
|
||||
onDeleteClick: PropTypes.func,
|
||||
onDuplicateClick: PropTypes.func,
|
||||
onItemClick: PropTypes.func.isRequired,
|
||||
selectedItemIndex: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
@@ -51,10 +51,21 @@ class CostumeTab extends React.Component {
|
||||
bindAll(this, [
|
||||
'handleSelectCostume',
|
||||
'handleDeleteCostume',
|
||||
'handleDuplicateCostume',
|
||||
'handleNewCostume',
|
||||
'handleNewBlankCostume'
|
||||
]);
|
||||
this.state = {selectedCostumeIndex: 0};
|
||||
const {
|
||||
editingTarget,
|
||||
sprites,
|
||||
stage
|
||||
} = props;
|
||||
const target = editingTarget && sprites[editingTarget] ? sprites[editingTarget] : stage;
|
||||
if (target && target.currentCostume) {
|
||||
this.state = {selectedCostumeIndex: target.currentCostume};
|
||||
} else {
|
||||
this.state = {selectedCostumeIndex: 0};
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const {
|
||||
@@ -75,6 +86,11 @@ class CostumeTab extends React.Component {
|
||||
handleDeleteCostume (costumeIndex) {
|
||||
this.props.vm.deleteCostume(costumeIndex);
|
||||
}
|
||||
handleDuplicateCostume (costumeIndex) {
|
||||
this.props.vm.duplicateCostume(costumeIndex).then(() => {
|
||||
this.setState({selectedCostumeIndex: costumeIndex + 1});
|
||||
});
|
||||
}
|
||||
handleNewCostume () {
|
||||
if (!this.props.vm.editingTarget) return;
|
||||
const costumes = this.props.vm.editingTarget.getCostumes() || [];
|
||||
@@ -145,6 +161,7 @@ class CostumeTab extends React.Component {
|
||||
items={target.costumes || []}
|
||||
selectedItemIndex={this.state.selectedCostumeIndex}
|
||||
onDeleteClick={target.costumes.length > 1 ? this.handleDeleteCostume : null}
|
||||
onDuplicateClick={this.handleDuplicateCostume}
|
||||
onItemClick={this.handleSelectCostume}
|
||||
>
|
||||
{target.costumes ?
|
||||
@@ -186,7 +203,8 @@ CostumeTab.propTypes = {
|
||||
id: PropTypes.shape({
|
||||
costumes: PropTypes.arrayOf(PropTypes.shape({
|
||||
url: PropTypes.string,
|
||||
name: PropTypes.string.isRequired
|
||||
name: PropTypes.string.isRequired,
|
||||
skinId: PropTypes.number
|
||||
}))
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import WavEncoder from 'wav-encoder';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
@@ -9,6 +10,7 @@ import {computeChunkedRMS} from '../lib/audio/audio-util.js';
|
||||
import AudioEffects from '../lib/audio/audio-effects.js';
|
||||
import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx';
|
||||
import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js';
|
||||
import log from '../lib/log.js';
|
||||
|
||||
const UNDO_STACK_SIZE = 99;
|
||||
|
||||
@@ -72,11 +74,25 @@ class SoundEditor extends React.Component {
|
||||
}
|
||||
this.undoStack.push(this.copyCurrentBuffer());
|
||||
}
|
||||
// Encode the new sound into a wav so that it can be stored
|
||||
let wavBuffer = null;
|
||||
try {
|
||||
wavBuffer = WavEncoder.encode.sync({
|
||||
sampleRate: sampleRate,
|
||||
channelData: [samples]
|
||||
});
|
||||
} catch (e) {
|
||||
// This error state is mostly for the mock sounds used during testing.
|
||||
// Any incorrect sound buffer trying to get interpretd as a Wav file
|
||||
// should yield this error.
|
||||
log.error(`Encountered error while trying to encode sound update: ${e}`);
|
||||
}
|
||||
|
||||
this.resetState(samples, sampleRate);
|
||||
this.props.onUpdateSoundBuffer(
|
||||
this.props.soundIndex,
|
||||
this.audioBufferPlayer.buffer
|
||||
);
|
||||
this.audioBufferPlayer.buffer,
|
||||
wavBuffer ? new Uint8Array(wavBuffer) : new Uint8Array());
|
||||
}
|
||||
handlePlay () {
|
||||
this.audioBufferPlayer.play(
|
||||
|
||||
@@ -26,6 +26,7 @@ class SoundTab extends React.Component {
|
||||
bindAll(this, [
|
||||
'handleSelectSound',
|
||||
'handleDeleteSound',
|
||||
'handleDuplicateSound',
|
||||
'handleNewSound'
|
||||
]);
|
||||
this.state = {selectedSoundIndex: 0};
|
||||
@@ -56,6 +57,12 @@ class SoundTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleDuplicateSound (soundIndex) {
|
||||
this.props.vm.duplicateSound(soundIndex).then(() => {
|
||||
this.setState({selectedSoundIndex: soundIndex + 1});
|
||||
});
|
||||
}
|
||||
|
||||
handleNewSound () {
|
||||
if (!this.props.vm.editingTarget) {
|
||||
return null;
|
||||
@@ -116,6 +123,7 @@ class SoundTab extends React.Component {
|
||||
}))}
|
||||
selectedItemIndex={this.state.selectedSoundIndex}
|
||||
onDeleteClick={this.handleDeleteSound}
|
||||
onDuplicateClick={this.handleDuplicateSound}
|
||||
onItemClick={this.handleSelectSound}
|
||||
>
|
||||
{sprite.sounds && sprite.sounds[this.state.selectedSoundIndex] ? (
|
||||
|
||||
@@ -25,6 +25,7 @@ class SeleniumHelper {
|
||||
// List of useful xpath scopes for finding elements
|
||||
return {
|
||||
blocksTab: "*[@id='react-tabs-1']",
|
||||
costumesTab: "*[@id='react-tabs-3']",
|
||||
modal: '*[@class="ReactModalPortal"]',
|
||||
reportedValue: '*[@class="blocklyDropDownContent"]',
|
||||
soundsTab: "*[@id='react-tabs-5']",
|
||||
|
||||
@@ -7,7 +7,9 @@ const {
|
||||
findByXpath,
|
||||
getDriver,
|
||||
getLogs,
|
||||
loadUri
|
||||
loadUri,
|
||||
rightClickText,
|
||||
scope
|
||||
} = new SeleniumHelper();
|
||||
|
||||
const uri = path.resolve(__dirname, '../../build/index.html');
|
||||
@@ -36,6 +38,22 @@ describe('Working with costumes', () => {
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('Duplicating a costume', async () => {
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="tryit"]');
|
||||
await clickText('Costumes');
|
||||
|
||||
await rightClickText('costume1', scope.costumesTab);
|
||||
await clickText('duplicate', scope.costumesTab);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for duplication to finish
|
||||
|
||||
// Make sure the duplicated costume is named correctly.
|
||||
await clickText('costume3', scope.costumesTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('Adding a backdrop', async () => {
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="tryit"]');
|
||||
|
||||
@@ -63,6 +63,22 @@ describe('Working with sounds', () => {
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('Duplicating a sound', async () => {
|
||||
await loadUri(uri);
|
||||
await clickXpath('//button[@title="tryit"]');
|
||||
await clickText('Sounds');
|
||||
|
||||
await rightClickText('Meow', scope.soundsTab);
|
||||
await clickText('duplicate', scope.soundsTab);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for error
|
||||
|
||||
// Make sure the duplicated sound is named correctly.
|
||||
await clickText('Meow2', scope.soundsTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
// Regression test for gui issue #1320
|
||||
test('Switching sprites with different numbers of sounds', async () => {
|
||||
await loadUri(uri);
|
||||
|
||||
Reference in new issue
Block a user