diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 4c63a8cceb7bec2c86632886b6e195ff1c4535ae..d4563a02c6fbdf50075f0787253cf5495e9d7d47 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -11,6 +11,8 @@ import {connect} from 'react-redux'; import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js'; import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; import DragConstants from '../lib/drag-constants'; +import {emptyCostume} from '../lib/empty-assets'; +import sharedMessages from '../lib/shared-messages'; import { closeCameraCapture, @@ -37,7 +39,7 @@ import searchIcon from '../components/action-menu/icon--search.svg'; import costumeLibraryContent from '../lib/libraries/costumes.json'; import backdropLibraryContent from '../lib/libraries/backdrops.json'; -const messages = defineMessages({ +let messages = defineMessages({ addLibraryBackdropMsg: { defaultMessage: 'Choose a Backdrop', description: 'Button to add a backdrop in the editor tab', @@ -75,6 +77,8 @@ const messages = defineMessages({ } }); +messages = {...messages, ...sharedMessages}; + class CostumeTab extends React.Component { constructor (props) { super(props); @@ -150,20 +154,10 @@ class CostumeTab extends React.Component { this.props.vm.addCostume(costume.md5, costume); } handleNewBlankCostume () { - const emptyItem = costumeLibraryContent.find(item => ( - item.name === 'Empty' - )); - const name = this.props.vm.editingTarget.isStage ? `backdrop1` : `costume1`; - const vmCostume = { - name: name, - md5: emptyItem.md5, - rotationCenterX: emptyItem.info[0], - rotationCenterY: emptyItem.info[1], - bitmapResolution: emptyItem.info.length > 2 ? emptyItem.info[2] : 1, - skinId: null - }; - - this.handleNewCostume(vmCostume); + const name = this.props.vm.editingTarget.isStage ? + this.props.intl.formatMessage(messages.backdrop) : + this.props.intl.formatMessage(messages.costume); + this.handleNewCostume(emptyCostume(name)); } handleSurpriseCostume () { const item = costumeLibraryContent[Math.floor(Math.random() * costumeLibraryContent.length)]; diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx index e5a86fcc067417198d55724646f85f1ebecebfa9..7520ae6d4889b8103754c8f3951b09f40ca10318 100644 --- a/src/containers/stage-selector.jsx +++ b/src/containers/stage-selector.jsx @@ -2,6 +2,7 @@ import bindAll from 'lodash.bindall'; import omit from 'lodash.omit'; import PropTypes from 'prop-types'; import React from 'react'; +import {intlShape, injectIntl} from 'react-intl'; import {connect} from 'react-redux'; import {openBackdropLibrary} from '../reducers/modals'; @@ -9,11 +10,12 @@ import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab'; import {setHoveredSprite} from '../reducers/hovered-target'; import DragConstants from '../lib/drag-constants'; import DropAreaHOC from '../lib/drop-area-hoc.jsx'; +import {emptyCostume} from '../lib/empty-assets'; +import sharedMessages from '../lib/shared-messages'; import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx'; import backdropLibraryContent from '../lib/libraries/backdrops.json'; -import costumeLibraryContent from '../lib/libraries/costumes.json'; import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js'; const dragTypes = [ @@ -66,11 +68,7 @@ class StageSelector extends React.Component { this.addBackdropFromLibraryItem(item); } handleEmptyBackdrop () { - // @todo this is brittle, will need to be refactored for localized libraries - const emptyItem = costumeLibraryContent.find(item => item.name === 'Empty'); - if (emptyItem) { - this.addBackdropFromLibraryItem(emptyItem); - } + this.handleNewBackdrop(emptyCostume(this.props.intl.formatMessage(sharedMessages.backdrop))); } handleBackdropUpload (e) { const storage = this.props.vm.runtime.storage; @@ -108,7 +106,7 @@ class StageSelector extends React.Component { } render () { const componentProps = omit(this.props, [ - 'assetId', 'dispatchSetHoveredSprite', 'id', 'onActivateTab', 'onSelect']); + 'assetId', 'dispatchSetHoveredSprite', 'id', 'intl', 'onActivateTab', 'onSelect']); return ( <DroppableStage fileInputRef={this.setFileInput} @@ -128,6 +126,7 @@ class StageSelector extends React.Component { StageSelector.propTypes = { ...StageSelectorComponent.propTypes, id: PropTypes.string, + intl: intlShape.isRequired, onSelect: PropTypes.func }; @@ -152,7 +151,7 @@ const mapDispatchToProps = dispatch => ({ } }); -export default connect( +export default injectIntl(connect( mapStateToProps, mapDispatchToProps -)(StageSelector); +)(StageSelector)); diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx index 4444c31432c50174ff1eba9a799fd7a8d710c9c7..5bccb7c333d68fb1d6c6ba33585e7efd0d5d9f8d 100644 --- a/src/containers/target-pane.jsx +++ b/src/containers/target-pane.jsx @@ -2,6 +2,7 @@ import bindAll from 'lodash.bindall'; import React from 'react'; import {connect} from 'react-redux'; +import {intlShape, injectIntl} from 'react-intl'; import { openSpriteLibrary, @@ -15,6 +16,8 @@ import DragConstants from '../lib/drag-constants'; import TargetPaneComponent from '../components/target-pane/target-pane.jsx'; import spriteLibraryContent from '../lib/libraries/sprites.json'; import {handleFileUpload, spriteUpload} from '../lib/file-uploader.js'; +import sharedMessages from '../lib/shared-messages'; +import {emptySprite} from '../lib/empty-assets'; class TargetPane extends React.Component { constructor (props) { @@ -109,15 +112,17 @@ class TargetPane extends React.Component { this.props.vm.addSprite(JSON.stringify(item.json)); } handlePaintSpriteClick () { - // @todo this is brittle, will need to be refactored for localized libraries - const emptyItem = spriteLibraryContent.find(item => item.name === 'Empty'); - if (emptyItem) { - this.props.vm.addSprite(JSON.stringify(emptyItem.json)).then(() => { - setTimeout(() => { // Wait for targets update to propagate before tab switching - this.props.onActivateTab(COSTUMES_TAB_INDEX); - }); + const formatMessage = this.props.intl.formatMessage; + const emptyItem = emptySprite( + formatMessage(sharedMessages.sprite), + formatMessage(sharedMessages.sound), + formatMessage(sharedMessages.costume) + ); + this.props.vm.addSprite(JSON.stringify(emptyItem)).then(() => { + setTimeout(() => { // Wait for targets update to propagate before tab switching + this.props.onActivateTab(COSTUMES_TAB_INDEX); }); - } + }); } handleNewSprite (spriteJSONString) { this.props.vm.addSprite(spriteJSONString); @@ -215,6 +220,7 @@ const { } = TargetPaneComponent.propTypes; TargetPane.propTypes = { + intl: intlShape.isRequired, ...targetPaneProps }; @@ -253,7 +259,7 @@ const mapDispatchToProps = dispatch => ({ } }); -export default connect( +export default injectIntl(connect( mapStateToProps, mapDispatchToProps -)(TargetPane); +)(TargetPane)); diff --git a/src/lib/empty-assets.js b/src/lib/empty-assets.js new file mode 100644 index 0000000000000000000000000000000000000000..65f1f678c5ad695eb51fdcdef323b90f8d0be7df --- /dev/null +++ b/src/lib/empty-assets.js @@ -0,0 +1,64 @@ +/** + * @fileoverview + * Utility functions to return json corresponding to default empty assets. + */ + +/** + * Generate a blank costume object for vm.addCostume with the provided name. + * @param {string} name the name to use for the costume, caller should localize + * @return {object} vm costume object + */ +const emptyCostume = name => ({ + name: name, + md5: 'cd21514d0531fdffb22204e0ec5ed84a.svg', + rotationCenterX: 0, + rotationCenterY: 0, + bitmapResolution: 1, + skinId: null +}); + +/** + * Generate a new empty sprite. The caller should provide localized versions of the + * default names. + * @param {string} name the name to use for the sprite + * @param {string} soundName the name to use for the default sound + * @param {string} costumeName the name to use for the default costume + * @return {object} object expected by vm.addSprite + */ +const emptySprite = (name, soundName, costumeName) => ({ + objName: name, + sounds: [ + { + soundName: soundName, + soundID: -1, + md5: '83a9787d4cb6f3b7632b4ddfebf74367.wav', + sampleCount: 258, + rate: 11025, + format: '' + } + ], + costumes: [ + { + costumeName: costumeName, + baseLayerID: -1, + baseLayerMD5: 'cd21514d0531fdffb22204e0ec5ed84a.svg', + bitmapResolution: 1, + rotationCenterX: 0, + rotationCenterY: 0 + } + ], + currentCostumeIndex: 0, + scratchX: 36, + scratchY: 28, + scale: 1, + direction: 90, + rotationStyle: 'normal', + isDraggable: false, + visible: true, + spriteInfo: {} +}); + +export { + emptyCostume, + emptySprite +}; diff --git a/src/lib/shared-messages.js b/src/lib/shared-messages.js new file mode 100644 index 0000000000000000000000000000000000000000..85ebe1c4041bb940c676f5b0a194af6d6c43ff6b --- /dev/null +++ b/src/lib/shared-messages.js @@ -0,0 +1,24 @@ +import {defineMessages} from 'react-intl'; + +export default defineMessages({ + backdrop: { + defaultMessage: 'backdrop1', + description: 'Default name for a new backdrop, scratch will automatically adjust the number if necessary', + id: 'gui.sharedMessages.backdrop' + }, + costume: { + defaultMessage: 'costume1', + description: 'Default name for a new costume, scratch will automatically adjust the number if necessary', + id: 'gui.sharedMessages.costume' + }, + sprite: { + defaultMessage: 'sprite1', + description: 'Default name for a new sprite, scratch will automatically adjust the number if necessary', + id: 'gui.sharedMessages.sprite' + }, + sound: { + defaultMessage: 'pop', + description: 'Name of the pop sound, the default sound added to a sprite', + id: 'gui.sharedMessages.pop' + } +});