diff --git a/packages/scratch-gui/src/containers/blocks.jsx b/packages/scratch-gui/src/containers/blocks.jsx index 295dcf1d9fd..38500878daf 100644 --- a/packages/scratch-gui/src/containers/blocks.jsx +++ b/packages/scratch-gui/src/containers/blocks.jsx @@ -393,6 +393,47 @@ class Blocks extends React.Component { const dom = this.ScratchBlocks.Xml.textToDom(data.xml); try { this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace); + + // When we converted blocks from Ruby, update top block positions. + if (this.props.vm.editingTarget) { + const blocks = this.props.vm.editingTarget.blocks; + const scripts = blocks.getScripts(); + let fromRuby = false; + for (let i = 0; i < scripts.length; i++) { + const topBlockId = scripts[i]; + const topBlock = blocks.getBlock(topBlockId); + if (typeof topBlock.x === 'undefined' || typeof topBlock.y === 'undefined') { + fromRuby = true; + break; + } + } + if (fromRuby) { + this.workspace.cleanUp(); + + // Re-calculate the position of the comments. + this.workspace.getTopComments(false).forEach(comment => { + if (comment.blockId) { + const block = this.workspace.getBlockById(comment.blockId); + if (block) { + const blockXY = block.getRelativeToSurfaceXY(); + const blockHW = block.getHeightWidth(); + const rtl = this.workspace.RTL; + if (rtl) { + comment.setAnchorLocation( + blockXY.x - (blockHW.width / 2), + blockXY.y + (blockHW.height / 2) + ); + } else { + comment.setAnchorLocation( + blockXY.x + (blockHW.width / 2), + blockXY.y + (blockHW.height / 2) + ); + } + } + } + }); + } + } } catch (error) { // The workspace is likely incomplete. What did update should be // functional. diff --git a/packages/scratch-gui/src/containers/extension-library.jsx b/packages/scratch-gui/src/containers/extension-library.jsx index ab9a2bbe45e..6e6b990bb08 100644 --- a/packages/scratch-gui/src/containers/extension-library.jsx +++ b/packages/scratch-gui/src/containers/extension-library.jsx @@ -2,7 +2,8 @@ import bindAll from 'lodash.bindall'; import PropTypes from 'prop-types'; import React from 'react'; import VM from '@scratch/scratch-vm'; -import {defineMessages, injectIntl} from 'react-intl'; +import {connect} from 'react-redux'; +import {defineMessages, injectIntl, FormattedMessage} from 'react-intl'; import intlShape from '../lib/intlShape.js'; import extensionLibraryContent from '../lib/libraries/extensions/index.jsx'; @@ -10,6 +11,10 @@ import extensionLibraryContent from '../lib/libraries/extensions/index.jsx'; import LibraryComponent from '../components/library/library.jsx'; import extensionIcon from '../components/action-menu/icon--sprite.svg'; +import {toggleShowAllExtensions} from '../reducers/extension-filter'; + +import styles from './extension-library.css'; + const messages = defineMessages({ extensionTitle: { defaultMessage: 'Choose an Extension', @@ -26,10 +31,25 @@ const messages = defineMessages({ class ExtensionLibrary extends React.PureComponent { constructor (props) { super(props); + extensionLibraryContent.forEach(extension => { + if (extension.setFormatMessage) { + extension.setFormatMessage(this.props.intl.formatMessage); + } + if (extension.translationMap) { + Object.assign( + this.props.intl.messages, + extension.translationMap[this.props.intl.locale] + ); + } + }); bindAll(this, [ - 'handleItemSelect' + 'handleItemSelect', + 'handleToggleShowAllExtensions' ]); } + handleToggleShowAllExtensions (event) { + this.props.onToggleShowAllExtensions(event.target.checked); + } handleItemSelect (item) { const id = item.extensionId; let url = item.extensionURL ? item.extensionURL : id; @@ -48,14 +68,58 @@ class ExtensionLibrary extends React.PureComponent { } } render () { - const extensionLibraryThumbnailData = extensionLibraryContent.map(extension => ({ - rawURL: extension.iconURL || extensionIcon, - ...extension - })); + const query = new URLSearchParams(window.location.search); + const extensionsParam = query.get('extensions') || ''; + const showMeshV2 = extensionsParam.split(',').includes('meshV2'); + + const showAllExtensionsParam = query.get('showAllExtensions'); + const showAllExtensions = showAllExtensionsParam === 'true' ? true : + showAllExtensionsParam === 'false' ? false : + this.props.showAllExtensions; + + const extensionLibraryThumbnailData = extensionLibraryContent + .filter(extension => { + if (extension.extensionId === 'meshV2' && !showMeshV2) { + return false; + } + if (!showAllExtensions && extension.defaultHidden) { + return false; + } + return true; + }) + .map(extension => ({ + rawURL: extension.iconURL || extensionIcon, + ...extension + })); + + const checkboxLabel = this.props.intl.formatMessage({ + defaultMessage: 'Show all extensions', + description: 'Checkbox label to show all extensions including hidden ones', + id: 'gui.extensionLibrary.showAllExtensions' + }); + + const headerActions = ( + + ); + return ( ({ + showAllExtensions: state.scratchGui.extensionFilter.showAllExtensions +}); + +const mapDispatchToProps = dispatch => ({ + onToggleShowAllExtensions: showAll => dispatch(toggleShowAllExtensions(showAll)) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(injectIntl(ExtensionLibrary)); diff --git a/packages/scratch-gui/src/containers/gui.jsx b/packages/scratch-gui/src/containers/gui.jsx index d84fab6f7f2..ffbda7ba753 100644 --- a/packages/scratch-gui/src/containers/gui.jsx +++ b/packages/scratch-gui/src/containers/gui.jsx @@ -16,7 +16,8 @@ import { activateTab, BLOCKS_TAB_INDEX, COSTUMES_TAB_INDEX, - SOUNDS_TAB_INDEX + SOUNDS_TAB_INDEX, + RUBY_TAB_INDEX } from '../reducers/editor-tab'; import { @@ -178,6 +179,7 @@ const mapStateToProps = (state, ownProps) => { platform: ownProps.platform, projectId: state.scratchGui.projectState.projectId, soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, + rubyTabVisible: state.scratchGui.editorTab.activeTabIndex === RUBY_TAB_INDEX, targetIsStage: ( state.scratchGui.targets.stage && state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget diff --git a/packages/scratch-gui/src/locales/ja-Hira.js b/packages/scratch-gui/src/locales/ja-Hira.js index 0628ae61881..ef53ffe1c53 100644 --- a/packages/scratch-gui/src/locales/ja-Hira.js +++ b/packages/scratch-gui/src/locales/ja-Hira.js @@ -1,6 +1,11 @@ export default { 'gui.modal.reload': 'さいよみこみ', 'gui.menuBar.meshV2': 'メッシュ', + 'gui.sharedMessages.backdrop': 'はいけい{index}', + 'gui.sharedMessages.costume': 'コスチューム{index}', + 'gui.sharedMessages.pop': 'ポップ', + 'gui.sharedMessages.sprite': 'スプライト{index}', + 'gui.sharedMessages.loadFromComputerTitle': 'コンピューターからよみこむ', 'gui.menuBar.loadFromUrl': 'Scratchからよみこむ', 'gui.urlLoader.loadError': 'プロジェクトURLのよみこみにしっぱいしました。', 'gui.urlLoader.invalidUrl': 'ゆうこうなScratchプロジェクトURLをにゅうりょくしてください。', diff --git a/packages/scratch-gui/src/locales/ja.js b/packages/scratch-gui/src/locales/ja.js index c84548fff25..3ef57d44c89 100644 --- a/packages/scratch-gui/src/locales/ja.js +++ b/packages/scratch-gui/src/locales/ja.js @@ -3,6 +3,11 @@ export default { 'gui.modal.stop': '中止', 'gui.menuBar.loadFromUrl': 'Scratchから読み込む', 'gui.menuBar.meshV2': 'メッシュ', + 'gui.sharedMessages.backdrop': '背景{index}', + 'gui.sharedMessages.costume': 'コスチューム{index}', + 'gui.sharedMessages.pop': 'ポップ', + 'gui.sharedMessages.sprite': 'スプライト{index}', + 'gui.sharedMessages.loadFromComputerTitle': 'コンピューターから読み込む', 'gui.menuBar.loadFromGoogleDrive': 'Google ドライブから読み込む', 'gui.menuBar.saveToGoogleDrive': 'Googleドライブにコピーを保存...', 'gui.menuBar.saveDirectlyToGoogleDrive': 'Googleドライブに直ちに保存', diff --git a/packages/scratch-gui/webpack.config.js b/packages/scratch-gui/webpack.config.js index b00966cbc7f..30d9bf4c4c4 100644 --- a/packages/scratch-gui/webpack.config.js +++ b/packages/scratch-gui/webpack.config.js @@ -274,7 +274,7 @@ const buildWithPwaConfig = buildConfig.clone() exclude: [ /\.DS_Store/ ], - maximumFileSizeToCacheInBytes: 32 * 1024 * 1024 + maximumFileSizeToCacheInBytes: 64 * 1024 * 1024 }) ) .addPlugin(