Merge pull request #1490 from fsih/duplicateCostume

Duplicate costume
This commit is contained in:
Paul Kaplan authored and GitHub committed 2018-02-26 09:40:46 -05:00
commit 95ef3e2914
8 files changed
+86 -6

No files matched your search

+1 -1
View File
@@ -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",
+3
View File
@@ -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
};
+20 -2
View File
@@ -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
}))
})
}),
+18 -2
View File
@@ -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(
+8
View File
@@ -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] ? (
+1
View File
@@ -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']",
+19 -1
View File
@@ -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"]');
+16
View File
@@ -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);