Merge branch 'develop' into ask-wait-answer-blocks
This commit is contained in:
29 files changed
+433
-199
No files matched your search
+7
-4
@@ -38,16 +38,18 @@
|
||||
"babel-plugin-transform-object-rest-spread": "^6.22.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babel-preset-react": "^6.22.0",
|
||||
"buffer-loader": "0.0.1",
|
||||
"chromedriver": "2.33.1",
|
||||
"classnames": "2.2.5",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.28.7",
|
||||
"enzyme": "^3.1.0",
|
||||
"enzyme-adapter-react-16": "1.0.2",
|
||||
"enzyme-adapter-react-16": "1.0.3",
|
||||
"eslint": "^4.7.1",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-react": "^7.2.1",
|
||||
"file-loader": "1.1.5",
|
||||
"get-float-time-domain-data": "0.1.0",
|
||||
"gh-pages": "github:rschamp/gh-pages#publish-branch-to-subfolder",
|
||||
"html-webpack-plugin": "^2.30.0",
|
||||
@@ -66,6 +68,7 @@
|
||||
"postcss-simple-vars": "^4.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"raf": "^3.4.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"react": "16.0.0",
|
||||
"react-contextmenu": "2.8.0",
|
||||
"react-dom": "16.0.0",
|
||||
@@ -83,17 +86,17 @@
|
||||
"redux-throttle": "0.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"scratch-audio": "latest",
|
||||
"scratch-paint": "latest",
|
||||
"scratch-blocks": "latest",
|
||||
"scratch-l10n": "^2.0.0",
|
||||
"scratch-paint": "latest",
|
||||
"scratch-render": "latest",
|
||||
"scratch-storage": "^0.2.0",
|
||||
"scratch-storage": "^0.3.0",
|
||||
"scratch-vm": "latest",
|
||||
"selenium-webdriver": "3.5.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.19.0",
|
||||
"svg-to-image": "1.1.3",
|
||||
"svg-url-loader": "^2.1.0",
|
||||
"text-encoding": "0.6.4",
|
||||
"wav-encoder": "1.3.0",
|
||||
"web-audio-test-api": "^0.5.2",
|
||||
"webpack": "^3.6.0",
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
}
|
||||
|
||||
.extension-button-container {
|
||||
width: 60px;
|
||||
width: 3.25rem;
|
||||
height: 3.25rem;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "../../css/units.css";
|
||||
@import "../../css/colors.css";
|
||||
|
||||
.stage {
|
||||
/*
|
||||
@@ -7,8 +8,6 @@
|
||||
*/
|
||||
display: block;
|
||||
|
||||
border-radius: $space;
|
||||
|
||||
/* @todo: This is for overriding the value being set somewhere. Where is it being set? */
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -31,6 +30,10 @@
|
||||
|
||||
.stage-wrapper {
|
||||
position: relative;
|
||||
border-radius: $space;
|
||||
border: 1px solid $ui-pane-border;
|
||||
/* Keep the canvas inside the border radius */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monitor-wrapper, .color-picker-wrapper {
|
||||
|
||||
@@ -7,7 +7,6 @@ import BackdropLibrary from '../../containers/backdrop-library.jsx';
|
||||
import CostumeLibrary from '../../containers/costume-library.jsx';
|
||||
import SoundLibrary from '../../containers/sound-library.jsx';
|
||||
import SpriteLibrary from '../../containers/sprite-library.jsx';
|
||||
import ExtensionLibrary from '../../containers/extension-library.jsx';
|
||||
|
||||
import SpriteSelectorComponent from '../sprite-selector/sprite-selector.jsx';
|
||||
import StageSelector from '../../containers/stage-selector.jsx';
|
||||
@@ -22,7 +21,6 @@ import styles from './target-pane.css';
|
||||
*/
|
||||
const TargetPane = ({
|
||||
editingTarget,
|
||||
extensionLibraryVisible,
|
||||
backdropLibraryVisible,
|
||||
costumeLibraryVisible,
|
||||
soundLibraryVisible,
|
||||
@@ -40,7 +38,6 @@ const TargetPane = ({
|
||||
onRequestCloseCostumeLibrary,
|
||||
onRequestCloseSoundLibrary,
|
||||
onRequestCloseSpriteLibrary,
|
||||
onRequestCloseExtensionLibrary,
|
||||
onSelectSprite,
|
||||
stage,
|
||||
sprites,
|
||||
@@ -78,12 +75,6 @@ const TargetPane = ({
|
||||
onSelect={onSelectSprite}
|
||||
/>}
|
||||
<div>
|
||||
{extensionLibraryVisible ? (
|
||||
<ExtensionLibrary
|
||||
vm={vm}
|
||||
onRequestClose={onRequestCloseExtensionLibrary}
|
||||
/>
|
||||
) : null}
|
||||
{spriteLibraryVisible ? (
|
||||
<SpriteLibrary
|
||||
vm={vm}
|
||||
|
||||
+31
-13
@@ -8,10 +8,12 @@ import VMScratchBlocks from '../lib/blocks';
|
||||
import VM from 'scratch-vm';
|
||||
import Prompt from './prompt.jsx';
|
||||
import BlocksComponent from '../components/blocks/blocks.jsx';
|
||||
import ExtensionLibrary from './extension-library.jsx';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {updateToolbox} from '../reducers/toolbox';
|
||||
import {activateColorPicker} from '../reducers/color-picker';
|
||||
import {closeExtensionLibrary} from '../reducers/modals';
|
||||
|
||||
const addFunctionListener = (object, property, callback) => {
|
||||
const oldFn = object[property];
|
||||
@@ -29,6 +31,7 @@ class Blocks extends React.Component {
|
||||
bindAll(this, [
|
||||
'attachVM',
|
||||
'detachVM',
|
||||
'handleCategorySelected',
|
||||
'handlePromptStart',
|
||||
'handlePromptCallback',
|
||||
'handlePromptClose',
|
||||
@@ -53,12 +56,13 @@ class Blocks extends React.Component {
|
||||
componentDidMount () {
|
||||
this.ScratchBlocks.FieldColourSlider.activateEyedropper_ = this.props.onActivateColorPicker;
|
||||
|
||||
const workspaceConfig = defaultsDeep({}, Blocks.defaultOptions, this.props.options);
|
||||
const workspaceConfig = defaultsDeep({},
|
||||
Blocks.defaultOptions,
|
||||
this.props.options,
|
||||
{toolbox: this.props.toolboxXML}
|
||||
);
|
||||
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
|
||||
|
||||
// Load the toolbox from the GUI (otherwise we get the scratch-blocks default toolbox)
|
||||
this.workspace.updateToolbox(this.props.toolboxXML);
|
||||
|
||||
// @todo change this when blockly supports UI events
|
||||
addFunctionListener(this.workspace, 'translate', this.onWorkspaceMetricsChange);
|
||||
addFunctionListener(this.workspace, 'zoom', this.onWorkspaceMetricsChange);
|
||||
@@ -69,7 +73,8 @@ class Blocks extends React.Component {
|
||||
return (
|
||||
this.state.prompt !== nextState.prompt ||
|
||||
this.props.isVisible !== nextProps.isVisible ||
|
||||
this.props.toolboxXML !== nextProps.toolboxXML
|
||||
this.props.toolboxXML !== nextProps.toolboxXML ||
|
||||
this.props.extensionLibraryVisible !== nextProps.extensionLibraryVisible
|
||||
);
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
@@ -87,7 +92,6 @@ class Blocks extends React.Component {
|
||||
this.workspace.setVisible(true);
|
||||
this.props.vm.refreshWorkspace();
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
this.workspace.toolbox_.refreshSelection();
|
||||
} else {
|
||||
this.workspace.setVisible(false);
|
||||
}
|
||||
@@ -172,13 +176,11 @@ class Blocks extends React.Component {
|
||||
this.onWorkspaceMetricsChange();
|
||||
}
|
||||
|
||||
this.ScratchBlocks.Events.disable();
|
||||
this.workspace.clear();
|
||||
|
||||
// Remove and reattach the workspace listener (but allow flyout events)
|
||||
this.workspace.removeChangeListener(this.props.vm.blockListener);
|
||||
const dom = this.ScratchBlocks.Xml.textToDom(data.xml);
|
||||
this.ScratchBlocks.Xml.domToWorkspace(dom, this.workspace);
|
||||
this.ScratchBlocks.Events.enable();
|
||||
this.workspace.toolbox_.refreshSelection();
|
||||
this.ScratchBlocks.Xml.clearWorkspaceAndLoadFromXml(dom, this.workspace);
|
||||
this.workspace.addChangeListener(this.props.vm.blockListener);
|
||||
|
||||
if (this.props.vm.editingTarget && this.state.workspaceMetrics[this.props.vm.editingTarget.id]) {
|
||||
const {scrollX, scrollY, scale} = this.state.workspaceMetrics[this.props.vm.editingTarget.id];
|
||||
@@ -193,7 +195,8 @@ class Blocks extends React.Component {
|
||||
const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML();
|
||||
const toolboxXML = makeToolboxXML(dynamicBlocksXML);
|
||||
this.props.onExtensionAdded(toolboxXML);
|
||||
const categoryName = blocksInfo[0].json.category;
|
||||
}
|
||||
handleCategorySelected (categoryName) {
|
||||
this.workspace.toolbox_.setSelectedCategoryByName(categoryName);
|
||||
}
|
||||
setBlocks (blocks) {
|
||||
@@ -212,11 +215,13 @@ class Blocks extends React.Component {
|
||||
render () {
|
||||
/* eslint-disable no-unused-vars */
|
||||
const {
|
||||
extensionLibraryVisible,
|
||||
options,
|
||||
vm,
|
||||
isVisible,
|
||||
onActivateColorPicker,
|
||||
onExtensionAdded,
|
||||
onRequestCloseExtensionLibrary,
|
||||
toolboxXML,
|
||||
...props
|
||||
} = this.props;
|
||||
@@ -236,15 +241,24 @@ class Blocks extends React.Component {
|
||||
onOk={this.handlePromptCallback}
|
||||
/>
|
||||
) : null}
|
||||
{extensionLibraryVisible ? (
|
||||
<ExtensionLibrary
|
||||
vm={vm}
|
||||
onCategorySelected={this.handleCategorySelected}
|
||||
onRequestClose={onRequestCloseExtensionLibrary}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Blocks.propTypes = {
|
||||
extensionLibraryVisible: PropTypes.bool,
|
||||
isVisible: PropTypes.bool,
|
||||
onActivateColorPicker: PropTypes.func,
|
||||
onExtensionAdded: PropTypes.func,
|
||||
onRequestCloseExtensionLibrary: PropTypes.func,
|
||||
options: PropTypes.shape({
|
||||
media: PropTypes.string,
|
||||
zoom: PropTypes.shape({
|
||||
@@ -302,6 +316,7 @@ Blocks.defaultProps = {
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
extensionLibraryVisible: state.modals.extensionLibrary,
|
||||
toolboxXML: state.toolbox.toolboxXML
|
||||
});
|
||||
|
||||
@@ -309,6 +324,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
onActivateColorPicker: callback => dispatch(activateColorPicker(callback)),
|
||||
onExtensionAdded: toolboxXML => {
|
||||
dispatch(updateToolbox(toolboxXML));
|
||||
},
|
||||
onRequestCloseExtensionLibrary: () => {
|
||||
dispatch(closeExtensionLibrary());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import extensionLibraryContent from '../lib/libraries/extensions.json';
|
||||
import extensionLibraryContent from '../lib/libraries/extensions/index';
|
||||
|
||||
import LibraryComponent from '../components/library/library.jsx';
|
||||
import extensionIcon from '../components/sprite-selector/icon--sprite.svg';
|
||||
@@ -19,7 +19,13 @@ class ExtensionLibrary extends React.PureComponent {
|
||||
// eslint-disable-next-line no-alert
|
||||
const url = item.extensionURL || prompt('Enter the URL of the extension');
|
||||
if (url) {
|
||||
this.props.vm.extensionManager.loadExtensionURL(url);
|
||||
if (this.props.vm.extensionManager.isExtensionLoaded(url)) {
|
||||
this.props.onCategorySelected(item.name);
|
||||
} else {
|
||||
this.props.vm.extensionManager.loadExtensionURL(url).then(() => {
|
||||
this.props.onCategorySelected(item.name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
render () {
|
||||
@@ -40,6 +46,7 @@ class ExtensionLibrary extends React.PureComponent {
|
||||
}
|
||||
|
||||
ExtensionLibrary.propTypes = {
|
||||
onCategorySelected: PropTypes.func,
|
||||
onRequestClose: PropTypes.func,
|
||||
visible: PropTypes.bool,
|
||||
vm: PropTypes.instanceOf(VM).isRequired // eslint-disable-line react/no-unused-prop-types
|
||||
|
||||
@@ -55,7 +55,7 @@ const mapStateToProps = (state, {selectedCostumeIndex}) => {
|
||||
name: costume && costume.name,
|
||||
rotationCenterX: costume && costume.rotationCenterX,
|
||||
rotationCenterY: costume && costume.rotationCenterY,
|
||||
svgId: editingTarget && `${editingTarget}${selectedCostumeIndex}`
|
||||
svgId: editingTarget && `${editingTarget}${costume.skinId}`
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
openSpriteLibrary,
|
||||
closeBackdropLibrary,
|
||||
closeCostumeLibrary,
|
||||
closeExtensionLibrary,
|
||||
closeSoundLibrary,
|
||||
closeSpriteLibrary
|
||||
} from '../reducers/modals';
|
||||
@@ -97,8 +96,7 @@ const mapStateToProps = state => ({
|
||||
soundLibraryVisible: state.modals.soundLibrary,
|
||||
spriteLibraryVisible: state.modals.spriteLibrary,
|
||||
costumeLibraryVisible: state.modals.costumeLibrary,
|
||||
backdropLibraryVisible: state.modals.backdropLibrary,
|
||||
extensionLibraryVisible: state.modals.extensionLibrary
|
||||
backdropLibraryVisible: state.modals.backdropLibrary
|
||||
});
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onNewSpriteClick: e => {
|
||||
@@ -111,9 +109,6 @@ const mapDispatchToProps = dispatch => ({
|
||||
onRequestCloseCostumeLibrary: () => {
|
||||
dispatch(closeCostumeLibrary());
|
||||
},
|
||||
onRequestCloseExtensionLibrary: () => {
|
||||
dispatch(closeExtensionLibrary());
|
||||
},
|
||||
onRequestCloseSoundLibrary: () => {
|
||||
dispatch(closeSoundLibrary());
|
||||
},
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import AppStateHOC from '../lib/app-state-hoc.jsx';
|
||||
import Controls from '../containers/controls.jsx';
|
||||
import Stage from '../containers/stage.jsx';
|
||||
import Box from '../components/box/box.jsx';
|
||||
import GUI from '../containers/gui.jsx';
|
||||
import ProjectLoaderHOC from '../lib/project-loader-hoc.jsx';
|
||||
|
||||
const mapStateToProps = state => ({vm: state.vm});
|
||||
|
||||
const VMStage = connect(mapStateToProps)(Stage);
|
||||
const VMControls = connect(mapStateToProps)(Controls);
|
||||
|
||||
const DEFAULT_PROJECT_ID = '10015059';
|
||||
|
||||
class Player extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.updateProject = this.updateProject.bind(this);
|
||||
|
||||
this.state = {
|
||||
projectId: window.location.hash.substring(1) || DEFAULT_PROJECT_ID
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
window.addEventListener('hashchange', this.updateProject);
|
||||
if (!window.location.hash.substring(1)) {
|
||||
window.location.hash = DEFAULT_PROJECT_ID;
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
window.addEventListener('hashchange', this.updateProject);
|
||||
}
|
||||
updateProject () {
|
||||
this.setState({projectId: window.location.hash.substring(1)});
|
||||
}
|
||||
render () {
|
||||
const width = 480;
|
||||
const height = 360;
|
||||
return (
|
||||
<div style={{display: 'flex'}}>
|
||||
<GUI
|
||||
{...this.props}
|
||||
width={width}
|
||||
>
|
||||
<Box height={40}>
|
||||
<VMControls
|
||||
style={{
|
||||
marginRight: 10,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<VMStage
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
</GUI>
|
||||
<iframe
|
||||
allowFullScreen
|
||||
allowTransparency
|
||||
frameBorder="0"
|
||||
height="402"
|
||||
src={`https://scratch.mit.edu/projects/embed/${this.state.projectId}/?autostart=true`}
|
||||
width="485"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const App = AppStateHOC(ProjectLoaderHOC(Player));
|
||||
|
||||
const appTarget = document.createElement('div');
|
||||
document.body.appendChild(appTarget);
|
||||
|
||||
ReactDOM.render(<App />, appTarget);
|
||||
@@ -0,0 +1,42 @@
|
||||
<svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<g id="Layer_3">
|
||||
<path fill="#FAA51D" stroke="#000000" d="M22.462,79.039c-2.415-0.451-5.304-1.309-7.742-3.503
		C9.268,70.629,7.526,62.535,3.672,64.622c-3.856,2.088-3.782,15.165,8.353,19.194c4.182,1.391,7.998,1.396,11.091,1.312
		c0.811-0.025,7.717-0.654,10.079-4.074c2.361-3.42,0.719-4.272-0.09-4.744C32.295,75.838,25.878,79.677,22.462,79.039z"/>
|
||||
<path fill="#FFFFFF" d="M4.236,64.877c-1.989,0.613-3.075,4.998-2.076,8.484c0.998,3.49,2.634,5.022,3.863,6.398
		c1.528,1.038-0.72-2.402,1.361-4.15c2.075-1.744,5.733-0.914,5.733-0.914s-2.909-3.987-4.57-6.396
		C6.975,65.988,6.359,64.375,4.236,64.877z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FAA51D" d="M38.217,86.756c0,0-8.832,6.2-17.071,8.412l0.086,0.215c1.247,1.824,5.87,7.497-0.334,9.496
		c-5.333,1.717-15.12-13.104-10.821-15.902c2.626-1.713,4.892-0.252,4.892-0.252s3.474-1.07,6.001-2.345
		c4.303-2.161,5.784-3.453,5.784-3.453s4.184-4.306,6.856-4.137C36.281,78.96,41.669,83.504,38.217,86.756z"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.232,95.383c1.247,1.824,5.87,7.497-0.334,9.496
		c-5.333,1.717-15.329-13.344-11.03-16.145c2.626-1.713,5.101-0.01,5.101-0.01s3.474-1.072,6.001-2.348
		c4.303-2.161,5.784-3.453,5.784-3.453"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M38.217,86.756c0,0-10.123,7.107-18.804,8.819"/>
|
||||
</g>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.169,74.885c0,0,1.235,0.165,4.744,3.676
	c3.509,3.508,6.026,2.16,8.911,0.724c2.877-1.443,10.537-6.126,6.49-9.817c-4.049-3.688-6.207,1.146-9.715,2.405
	c-3.512,1.26-5.061-2.487-6.858-4.287c-0.589-0.593-1.188-1.099-1.729-1.505c0,0-0.971-0.76-1.906,2.79
	C51.172,72.412,50.162,73.415,52.169,74.885z"/>
|
||||
<g id="Layer_2_1_">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M46.753,82.012c1.188-0.912,2.397-2.402,3.951-4.713
		c1.296-1.927,2.7-5.578,2.7-5.578c0.875-2.521,1.934-6.576-1.902-7.296c-1.553-0.291-4.079-0.098-7.67-0.776
		c-3.593-0.681-6.798-2.522-9.517,2.233c-2.718,4.757-9.59,8.271-1.056,16.563c0,0,4.901,3.842,10.764,9.639
		c4.831,4.775,12.045,10.602,12.045,10.602s18.972,2.188,19.535-0.693c1.922-9.79-14.777-6.911-14.777-6.911
		s-4.605-3.933-6.725-5.794c-3.478-3.059-11.125-10.771-11.125-10.771"/>
|
||||
<path fill="#FFFFFF" d="M51.253,75.434c0,0,2.47-2.66-2.469-5.317c-4.939-2.657-7.213-0.017-8.739,1.521
		c-2.644,2.655,3.443,6.611,3.443,6.611l3.176,3.204c0,0,1.738-1.647,2.499-2.979C50.036,77.26,51.253,75.434,51.253,75.434"/>
|
||||
</g>
|
||||
<g id="Layer_8"/>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M29.926,73.218c0.749-0.571,2.889-2.202,4.854-3.657
	c2.428-1.799,6.117-5.849,1.077-7.646c-5.04-1.801-7.507,1.604-11.519,4.946c-2.159,1.801-5.308,2.699-4.319,6.209
	c0.993,3.511,4.862,13.408,11.789,10.17c6.929-3.239-1.799-9.18-3.06-11.157"/>
|
||||
<g id="Layer_2">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.709,14.156c-1.54-0.143-4.75-0.316-6.518-0.231
		c-4.728,0.225-9.224,1.928-9.224,1.928L23.949,7.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224
		c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.729,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823
		s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L62.822,5.647l-7.452,7.204L52.709,14.156z"/>
|
||||
<path fill="#FFFFFF" d="M76.42,35.066l-2.482-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277
		L41.46,41.17c0,0-8.437,0.928-8.739,6.081C32.048,58.704,46.1,63.479,51.425,63.783c2.905,0.167,8.235-0.338,12.277-1.141
		c17.752-3.234,22.551-13.919,21.31-19.635c-1.242-5.714-7.978-7.196-7.978-7.196L76.42,35.066z"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M10.673,46.155c0,0,4.107,0.374,5.974,0.268
		c1.865-0.107,5.492-0.587,5.492-0.587"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.656,40.671c0,0,4.549-0.743,6.859-1.549
		c2.715-0.942,4.543-2.545,4.543-2.545"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M22.337,41.909c0,0-2.384-1.777-6.117-3.43
		c-4.134-1.831-6.405-2.303-6.405-2.303"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M82.117,46.622c0,0,2.726,1.104,5.533,1.385
		c2.77,0.276,4.646,0.11,4.646,0.11"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M52.35,14.212
		c2.84,0.7,3.887,1.469,3.887,1.469"/>
|
||||
<line fill="none" stroke="#000000" x1="33.898" y1="13.684" x2="39.956" y2="18.042"/>
|
||||
</g>
|
||||
<g id="Layer_5">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M71.84,25.366c2.924,4.479,3.033,9.591,0.242,11.415
		c-2.793,1.825-7.426-0.332-10.354-4.813c-2.933-4.48-3.037-9.589-0.244-11.415C64.275,18.73,68.913,20.884,71.84,25.366z"/>
|
||||
<path fill="#231F20" d="M71.089,32.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956
		c0-1.08,0.81-1.958,1.803-1.958C70.287,30.564,71.089,31.442,71.089,32.522"/>
|
||||
</g>
|
||||
<g id="Layer_7">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M47.867,28.619c2.926,4.48,2.619,9.862-0.681,12.015
		c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C39.891,22.253,44.938,24.137,47.867,28.619z"/>
|
||||
<path fill="#231F20" d="M46.079,34.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957
		c0-1.08,0.811-1.957,1.803-1.957C45.274,32.55,46.079,33.427,46.079,34.507"/>
|
||||
</g>
|
||||
<path fill="#5E4A42" stroke="#000000" d="M59.766,37.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345
	c-1.283,0.142-6.125-2.353-6.195-3.919C55.206,37.355,58.055,37.926,59.766,37.926z"/>
|
||||
<g id="Layer_4">
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M46.774,45.235c0,0,10.347,3.054,14.217,3.897
		c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C45.176,55.863,45.345,49.975,46.774,45.235z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,37 @@
|
||||
<svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<g id="Layer_8">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M23.754,82.506c-2.871-1.402-10.511-2.518-14.771-6.695
		c-9.431-9.246-3.824-21.732,0.704-21.797c3.611-0.061,3.363,5.482,4.622,11.145c0.875,3.939,1.673,6.988,6.407,9.674
		c4.735,2.688,6.918,2.916,6.918,2.916s3.258,0.791,1.596,2.562C27.568,82.076,23.754,82.506,23.754,82.506z"/>
|
||||
<path fill="#FFFFFF" d="M14.291,66.369c-0.115-0.459-0.222-0.928-0.33-1.413c-1.172-5.271-0.941-10.432-4.303-10.377
		c-3.433,0.05-7.53,7.771-4.19,15.285c0.193-0.143,0.399-0.336,0.569-0.588c0.451-0.664,0.821-2.969,2.654-3.688
		C9.989,65.082,12.778,65.869,14.291,66.369z"/>
|
||||
</g>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.543,61.473c0,0,1.455,2.342,1.519,4.969
	c0.065,2.723-0.351,6.984,1.804,8.158c3.381,1.842,9.959-0.463,10.174,5.191c0.211,5.652-10.152,3.623-13.299,2.547
	c-2.695-0.926-4.559-1.758-5.18-5.182c0,0-3.764-13.969-0.196-15.59C50.933,59.945,52.543,61.473,52.543,61.473z"/>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M14.61,89.584c0,0,1.504,10.391-5.922,8.387
	c-5.586-1.51-5.705-20.68-0.406-20.691c3.237-0.006,4.425,2.859,4.425,2.859s4.385,4.812,6.95,4.312
	c2.565-0.502,7.978-6.703,7.978-6.703s2.875-2.539,6.146,0.023c3.271,2.561,1.521,6.178,1.521,6.178s-6.07,7.072-11.014,8.072
	c-4.945,1.002-9.375-2.568-11.58-3.451"/>
|
||||
<g id="Layer_6">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M42.435,76.238c0,0,4.866,4.062,8.166,7.52
		c4.384,4.592-3.339,15.039-3.339,15.039s11.234,6.467,2.998,11c-2.657,1.467-15.661-13.225-15.661-13.225s7.979-7.475,4.972-10.172
		c-4.964-4.449-10.771-4.959-11.6-9.824c-0.502-2.957,0.613-4.502,2.658-7.053c1.354-1.689,2.084-3.182,2.084-3.182
		s1.135-7.238,4.289-8.254c3.153-1.014,8.842,0.057,10.758,0.395c1.914,0.338,6.645,1.877,4.617,9.922c0,0-1.451,3.771-2.789,5.762
		c-1.604,2.389-2.854,3.924-4.08,4.867"/>
|
||||
<path fill="#FFFFFF" d="M50.182,72.631c0,0,2.55-2.748-2.551-5.494c-5.102-2.746-7.449-0.016-9.024,1.572
		c-2.731,2.742,3.334,7.453,3.334,7.453l3.383,2.564c0,0,1.916-1.582,2.701-2.957C48.807,74.396,50.182,72.631,50.182,72.631"/>
|
||||
</g>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M24.428,65.395c0.939,0.057,3.625,0.213,6.064,0.391
	c3.014,0.217,8.451-0.451,5.801-5.098c-2.649-4.65-6.74-3.674-11.961-3.754c-2.812-0.043-5.785-1.412-7.322,1.895
	c-1.534,3.312-5.051,13.338,2.313,15.396c7.366,2.057,4.618-8.137,4.95-10.457"/>
|
||||
<g id="Layer_2">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M51.709,10.156c-1.54-0.143-4.75-0.316-6.518-0.231
		c-4.728,0.225-9.224,1.928-9.224,1.928L22.949,3.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224
		c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.728,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823
		s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L61.822,1.647L54.37,8.851L51.709,10.156z"/>
|
||||
<path fill="#FFFFFF" d="M75.42,31.066l-2.483-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277
		l-7.784,3.417c0,0-8.437,0.928-8.739,6.081C31.048,54.704,45.1,59.478,50.425,59.783c2.905,0.167,8.235-0.337,12.277-1.14
		c17.752-3.235,22.551-13.92,21.309-19.635c-1.242-5.714-7.977-7.196-7.977-7.196L75.42,31.066z"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M9.673,42.155c0,0,4.107,0.374,5.974,0.268
		c1.865-0.107,5.492-0.587,5.492-0.587"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M80.656,36.671c0,0,4.549-0.743,6.859-1.549
		c2.715-0.942,4.543-2.545,4.543-2.545"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.337,37.909c0,0-2.384-1.777-6.117-3.43
		c-4.134-1.831-6.405-2.303-6.405-2.303"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.117,42.622c0,0,2.726,1.104,5.533,1.385
		c2.77,0.276,4.647,0.11,4.647,0.11"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M51.349,10.212
		c2.84,0.7,3.888,1.469,3.888,1.469"/>
|
||||
<line fill="none" stroke="#000000" x1="32.898" y1="9.684" x2="38.956" y2="14.042"/>
|
||||
</g>
|
||||
<g id="Layer_5">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M70.84,21.366c2.924,4.479,3.033,9.591,0.242,11.415
		c-2.793,1.825-7.426-0.332-10.354-4.813c-2.932-4.48-3.037-9.589-0.244-11.415C63.275,14.73,67.913,16.884,70.84,21.366z"/>
|
||||
<path fill="#231F20" d="M70.089,28.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956
		c0-1.08,0.81-1.958,1.803-1.958C69.287,26.564,70.089,27.442,70.089,28.522"/>
|
||||
</g>
|
||||
<g id="Layer_7">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M46.867,24.619c2.926,4.48,2.619,9.862-0.681,12.015
		c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C38.891,18.253,43.938,20.137,46.867,24.619z"/>
|
||||
<path fill="#231F20" d="M45.079,30.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957
		c0-1.08,0.811-1.957,1.803-1.957C44.274,28.55,45.079,29.427,45.079,30.507"/>
|
||||
</g>
|
||||
<path fill="#5E4A42" stroke="#000000" d="M58.766,33.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345
	c-1.283,0.142-6.125-2.353-6.195-3.919C54.206,33.355,57.055,33.926,58.766,33.926z"/>
|
||||
<g id="Layer_4">
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M45.774,41.235c0,0,10.347,3.054,14.217,3.897
		c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C44.176,51.863,44.345,45.975,45.774,41.235z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
import {TextEncoder} from 'text-encoding';
|
||||
import projectJson from './project.json';
|
||||
|
||||
/* eslint-disable import/no-unresolved */
|
||||
import popWav from '!buffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav';
|
||||
import meowWav from '!buffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav';
|
||||
import backdrop from '!buffer-loader!./739b5e2a2435f6e1ec2993791b423146.png';
|
||||
import penLayer from '!buffer-loader!./5c81a336fab8be57adc039a8a2b33ca9.png';
|
||||
import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg';
|
||||
import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg';
|
||||
/* eslint-enable import/no-unresolved */
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
export default [{
|
||||
id: 0,
|
||||
assetType: 'Project',
|
||||
dataFormat: 'JSON',
|
||||
data: JSON.stringify(projectJson)
|
||||
}, {
|
||||
id: '83a9787d4cb6f3b7632b4ddfebf74367',
|
||||
assetType: 'Sound',
|
||||
dataFormat: 'WAV',
|
||||
data: popWav
|
||||
}, {
|
||||
id: '83c36d806dc92327b9e7049a565c6bff',
|
||||
assetType: 'Sound',
|
||||
dataFormat: 'WAV',
|
||||
data: meowWav
|
||||
}, {
|
||||
id: '739b5e2a2435f6e1ec2993791b423146',
|
||||
assetType: 'ImageBitmap',
|
||||
dataFormat: 'PNG',
|
||||
data: backdrop
|
||||
}, {
|
||||
id: '5c81a336fab8be57adc039a8a2b33ca9',
|
||||
assetType: 'ImageBitmap',
|
||||
dataFormat: 'PNG',
|
||||
data: penLayer
|
||||
}, {
|
||||
id: '09dc888b0b7df19f70d81588ae73420e',
|
||||
assetType: 'ImageVector',
|
||||
dataFormat: 'SVG',
|
||||
data: encoder.encode(costume1)
|
||||
}, {
|
||||
id: '3696356a03a8d938318876a593572843',
|
||||
assetType: 'ImageVector',
|
||||
dataFormat: 'SVG',
|
||||
data: encoder.encode(costume2)
|
||||
}];
|
||||
File renamed without changes.
@@ -1,9 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Pen",
|
||||
"extensionURL": "pen",
|
||||
"md5": "1a2ec605e73000897797f0c851134f5b.png",
|
||||
"description": "Draw with your sprites.",
|
||||
"featured": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
import penImage from './pen.png';
|
||||
import wedoImage from './wedo.png';
|
||||
|
||||
export default [
|
||||
{
|
||||
name: 'Pen',
|
||||
extensionURL: 'pen',
|
||||
iconURL: penImage,
|
||||
description: 'Draw with your sprites.',
|
||||
featured: true
|
||||
},
|
||||
{
|
||||
name: 'Lego WeDo 2.0',
|
||||
extensionURL: 'wedo2',
|
||||
iconURL: wedoImage,
|
||||
description: 'Build with motors and sensors.',
|
||||
featured: true
|
||||
}
|
||||
];
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
+74
-94
@@ -1,78 +1,6 @@
|
||||
const separator = '<sep gap="45"/>';
|
||||
const categorySeparator = '<sep gap="36"/>';
|
||||
|
||||
const top = `
|
||||
<category name="Top" colour="#FFFFFF" secondaryColour="#CCCCCC">
|
||||
<block type="event_whenflagclicked"/>
|
||||
<block type="event_whenkeypressed">
|
||||
</block>
|
||||
<block type="event_whenthisspriteclicked"/>
|
||||
<block type="motion_movesteps">
|
||||
<value name="STEPS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_turnright">
|
||||
<value name="DEGREES">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">15</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="motion_ifonedgebounce"/>
|
||||
<block type="sound_playuntildone">
|
||||
<value name="SOUND_MENU">
|
||||
<shadow type="sound_sounds_menu"/>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_sayforsecs">
|
||||
<value name="MESSAGE">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">Hello!</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="SECS">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">2</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_changeeffectby">
|
||||
<value name="CHANGE">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_repeat">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_whole_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_wait">
|
||||
<value name="DURATION">
|
||||
<shadow type="math_positive_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="operator_random">
|
||||
<value name="FROM">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">1</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="TO">
|
||||
<shadow type="math_number">
|
||||
<field name="NUM">10</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
</category>
|
||||
`;
|
||||
const blockSeparator = '<sep gap="36"/>'; // At default scale, about 28px
|
||||
|
||||
const motion = `
|
||||
<category name="Motion" colour="#4C97FF" secondaryColour="#3373CC">
|
||||
@@ -97,6 +25,7 @@ const motion = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="motion_pointindirection">
|
||||
<value name="DIRECTION">
|
||||
<shadow type="math_angle">
|
||||
@@ -110,6 +39,7 @@ const motion = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="motion_gotoxy">
|
||||
<value name="X">
|
||||
<shadow id="movex" type="math_number">
|
||||
@@ -156,6 +86,7 @@ const motion = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="motion_changexby">
|
||||
<value name="DX">
|
||||
<shadow type="math_number">
|
||||
@@ -184,11 +115,15 @@ const motion = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="motion_ifonedgebounce"/>
|
||||
${blockSeparator}
|
||||
<block type="motion_setrotationstyle"/>
|
||||
<block type="motion_xposition"/>
|
||||
<block type="motion_yposition"/>
|
||||
<block type="motion_direction"/>
|
||||
${blockSeparator}
|
||||
<block id="xposition" type="motion_xposition"/>
|
||||
<block id="yposition" type="motion_yposition"/>
|
||||
<block id="direction" type="motion_direction"/>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -232,8 +167,10 @@ const looks = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="looks_show"/>
|
||||
<block type="looks_hide"/>
|
||||
${blockSeparator}
|
||||
<block type="looks_switchcostumeto">
|
||||
<value name="COSTUME">
|
||||
<shadow type="looks_costume"/>
|
||||
@@ -251,6 +188,7 @@ const looks = `
|
||||
<shadow type="looks_backdrops"/>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="looks_changeeffectby">
|
||||
<value name="CHANGE">
|
||||
<shadow type="math_number">
|
||||
@@ -266,6 +204,7 @@ const looks = `
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_cleargraphiceffects"/>
|
||||
${blockSeparator}
|
||||
<block type="looks_changesizeby">
|
||||
<value name="CHANGE">
|
||||
<shadow type="math_number">
|
||||
@@ -280,6 +219,7 @@ const looks = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="looks_gotofront"/>
|
||||
<block type="looks_gobacklayers">
|
||||
<value name="NUM">
|
||||
@@ -288,10 +228,12 @@ const looks = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="looks_costumeorder"/>
|
||||
<block type="looks_backdroporder"/>
|
||||
<block type="looks_backdropname"/>
|
||||
<block type="looks_size"/>
|
||||
${blockSeparator}
|
||||
<block id="costumeorder" type="looks_costumeorder"/>
|
||||
<block id="backdroporder" type="looks_backdroporder"/>
|
||||
<block id="backdropname" type="looks_backdropname"/>
|
||||
<block id="size" type="looks_size"/>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -308,6 +250,7 @@ const sound = `
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_stopallsounds"/>
|
||||
${blockSeparator}
|
||||
<block type="sound_playdrumforbeats">
|
||||
<value name="DRUM">
|
||||
<shadow type="sound_drums_menu"/>
|
||||
@@ -325,6 +268,7 @@ const sound = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="sound_playnoteforbeats">
|
||||
<value name="NOTE">
|
||||
<shadow type="math_number">
|
||||
@@ -342,6 +286,7 @@ const sound = `
|
||||
<shadow type="sound_instruments_menu"/>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="sound_changeeffectby">
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
@@ -357,6 +302,7 @@ const sound = `
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_cleareffects"/>
|
||||
${blockSeparator}
|
||||
<block type="sound_changevolumeby">
|
||||
<value name="VOLUME">
|
||||
<shadow type="math_number">
|
||||
@@ -371,7 +317,8 @@ const sound = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_volume"/>
|
||||
<block id="volume" type="sound_volume"/>
|
||||
${blockSeparator}
|
||||
<block type="sound_changetempoby">
|
||||
<value name="TEMPO">
|
||||
<shadow type="math_number">
|
||||
@@ -386,7 +333,8 @@ const sound = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sound_tempo"/>
|
||||
<block id="tempo" type="sound_tempo"/>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -398,6 +346,7 @@ const events = `
|
||||
<block type="event_whenthisspriteclicked"/>
|
||||
<block type="event_whenbackdropswitchesto">
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="event_whengreaterthan">
|
||||
<value name="VALUE">
|
||||
<shadow type="math_number">
|
||||
@@ -405,6 +354,7 @@ const events = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="event_whenbroadcastreceived">
|
||||
</block>
|
||||
<block type="event_broadcast">
|
||||
@@ -417,6 +367,7 @@ const events = `
|
||||
<shadow type="event_broadcast_menu"/>
|
||||
</value>
|
||||
</block>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -429,6 +380,7 @@ const control = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="control_repeat">
|
||||
<value name="TIMES">
|
||||
<shadow type="math_whole_number">
|
||||
@@ -437,11 +389,14 @@ const control = `
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_forever"/>
|
||||
${blockSeparator}
|
||||
<block type="control_if"/>
|
||||
<block type="control_if_else"/>
|
||||
<block type="control_wait_until"/>
|
||||
<block type="control_repeat_until"/>
|
||||
${blockSeparator}
|
||||
<block type="control_stop"/>
|
||||
${blockSeparator}
|
||||
<block type="control_start_as_clone"/>
|
||||
<block type="control_create_clone_of">
|
||||
<value name="CLONE_OPTION">
|
||||
@@ -449,6 +404,7 @@ const control = `
|
||||
</value>
|
||||
</block>
|
||||
<block type="control_delete_this_clone"/>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -477,6 +433,7 @@ const sensing = `
|
||||
<shadow type="sensing_distancetomenu"/>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="sensing_askandwait">
|
||||
<value name="QUESTION">
|
||||
<shadow type="text">
|
||||
@@ -484,7 +441,8 @@ const sensing = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_answer"/>
|
||||
<block id="answer" type="sensing_answer"/>
|
||||
${blockSeparator}
|
||||
<block type="sensing_keypressed">
|
||||
<value name="KEY_OPTION">
|
||||
<shadow type="sensing_keyoptions"/>
|
||||
@@ -493,23 +451,28 @@ const sensing = `
|
||||
<block type="sensing_mousedown"/>
|
||||
<block type="sensing_mousex"/>
|
||||
<block type="sensing_mousey"/>
|
||||
<block type="sensing_loudness"/>
|
||||
<block type="sensing_timer"/>
|
||||
${blockSeparator}
|
||||
<block id="loudness" type="sensing_loudness"/>
|
||||
${blockSeparator}
|
||||
<block id="timer" type="sensing_timer"/>
|
||||
<block type="sensing_resettimer"/>
|
||||
<block type="sensing_of">
|
||||
${blockSeparator}
|
||||
<block id="of" type="sensing_of">
|
||||
<value name="PROPERTY">
|
||||
<shadow type="sensing_of_property_menu"/>
|
||||
<shadow id="sensing_of_property_menu" type="sensing_of_property_menu"/>
|
||||
</value>
|
||||
<value name="OBJECT">
|
||||
<shadow type="sensing_of_object_menu"/>
|
||||
<shadow id="sensing_of_object_menu" type="sensing_of_object_menu"/>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_current">
|
||||
${blockSeparator}
|
||||
<block id="current" type="sensing_current">
|
||||
<value name="CURRENTMENU">
|
||||
<shadow type="sensing_currentmenu"/>
|
||||
<shadow id="sensing_currentmenu" type="sensing_currentmenu"/>
|
||||
</value>
|
||||
</block>
|
||||
<block type="sensing_dayssince2000"/>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -563,6 +526,7 @@ const operators = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="operator_random">
|
||||
<value name="FROM">
|
||||
<shadow type="math_number">
|
||||
@@ -575,6 +539,7 @@ const operators = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="operator_lt">
|
||||
<value name="OPERAND1">
|
||||
<shadow type="text">
|
||||
@@ -611,9 +576,11 @@ const operators = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="operator_and"/>
|
||||
<block type="operator_or"/>
|
||||
<block type="operator_not"/>
|
||||
${blockSeparator}
|
||||
<block type="operator_join">
|
||||
<value name="STRING1">
|
||||
<shadow type="text">
|
||||
@@ -645,6 +612,19 @@ const operators = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
<block type="operator_contains" id="operator_contains">
|
||||
<value name="STRING1">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">hello</field>
|
||||
</shadow>
|
||||
</value>
|
||||
<value name="STRING2">
|
||||
<shadow type="text">
|
||||
<field name="TEXT">world</field>
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="operator_mod">
|
||||
<value name="NUM1">
|
||||
<shadow type="math_number">
|
||||
@@ -664,6 +644,7 @@ const operators = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${blockSeparator}
|
||||
<block type="operator_mathop">
|
||||
<value name="NUM">
|
||||
<shadow type="math_number">
|
||||
@@ -671,6 +652,7 @@ const operators = `
|
||||
</shadow>
|
||||
</value>
|
||||
</block>
|
||||
${categorySeparator}
|
||||
</category>
|
||||
`;
|
||||
|
||||
@@ -687,11 +669,10 @@ const xmlClose = '</xml>';
|
||||
* @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
|
||||
*/
|
||||
const makeToolboxXML = function (categoriesXML) {
|
||||
const gap = [separator];
|
||||
const gap = [categorySeparator];
|
||||
|
||||
const everything = [
|
||||
xmlOpen,
|
||||
top, gap,
|
||||
motion, gap,
|
||||
looks, gap,
|
||||
sound, gap,
|
||||
@@ -707,7 +688,6 @@ const makeToolboxXML = function (categoriesXML) {
|
||||
}
|
||||
|
||||
everything.push(xmlClose);
|
||||
|
||||
return everything.join('\n');
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
import React from 'react';
|
||||
import xhr from 'xhr';
|
||||
|
||||
import log from './log';
|
||||
import emptyProject from './empty-project.json';
|
||||
|
||||
class ProjectLoaderConstructor {
|
||||
get DEFAULT_PROJECT_DATA () {
|
||||
return emptyProject;
|
||||
}
|
||||
|
||||
load (id, callback) {
|
||||
callback = callback || (err => log.error(err));
|
||||
xhr({
|
||||
uri: `https://projects.scratch.mit.edu/internalapi/project/${id}/get/`
|
||||
}, (err, res, body) => {
|
||||
if (err) return callback(err);
|
||||
callback(null, body);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const ProjectLoader = new ProjectLoaderConstructor();
|
||||
import storage from './storage';
|
||||
|
||||
/* Higher Order Component to provide behavior for loading projects by id from
|
||||
* the window's hash (#this part in the url)
|
||||
@@ -35,13 +16,23 @@ const ProjectLoaderHOC = function (WrappedComponent) {
|
||||
this.updateProject = this.updateProject.bind(this);
|
||||
this.state = {
|
||||
projectId: null,
|
||||
projectData: this.fetchProjectId().length ? null : JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA)
|
||||
projectData: null
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
window.addEventListener('hashchange', this.updateProject);
|
||||
this.updateProject();
|
||||
}
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (this.state.projectId !== prevState.projectId) {
|
||||
storage
|
||||
.load(storage.AssetType.Project, this.state.projectId, storage.DataFormat.JSON)
|
||||
.then(projectAsset => projectAsset && this.setState({
|
||||
projectData: projectAsset.data.toString()
|
||||
}))
|
||||
.catch(err => log.error(err));
|
||||
}
|
||||
}
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('hashchange', this.updateProject);
|
||||
}
|
||||
@@ -49,18 +40,9 @@ const ProjectLoaderHOC = function (WrappedComponent) {
|
||||
return window.location.hash.substring(1);
|
||||
}
|
||||
updateProject () {
|
||||
const projectId = this.fetchProjectId();
|
||||
let projectId = this.fetchProjectId();
|
||||
if (projectId !== this.state.projectId) {
|
||||
if (projectId.length < 1) {
|
||||
return this.setState({
|
||||
projectId: projectId,
|
||||
projectData: JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA)
|
||||
});
|
||||
}
|
||||
ProjectLoader.load(projectId, (err, body) => {
|
||||
if (err) return log.error(err);
|
||||
this.setState({projectData: body});
|
||||
});
|
||||
if (projectId.length < 1) projectId = 0;
|
||||
this.setState({projectId: projectId});
|
||||
}
|
||||
}
|
||||
@@ -80,6 +62,5 @@ const ProjectLoaderHOC = function (WrappedComponent) {
|
||||
|
||||
|
||||
export {
|
||||
ProjectLoaderHOC as default,
|
||||
ProjectLoader
|
||||
ProjectLoaderHOC as default
|
||||
};
|
||||
+11
-1
@@ -1,5 +1,7 @@
|
||||
import ScratchStorage from 'scratch-storage';
|
||||
|
||||
import defaultProjectAssets from './default-project';
|
||||
|
||||
const PROJECT_SERVER = 'https://cdn.projects.scratch.mit.edu';
|
||||
const ASSET_SERVER = 'https://cdn.assets.scratch.mit.edu';
|
||||
|
||||
@@ -23,7 +25,15 @@ class Storage extends ScratchStorage {
|
||||
[this.AssetType.ImageVector, this.AssetType.ImageBitmap, this.AssetType.Sound],
|
||||
asset => `${ASSET_SERVER}/internalapi/asset/${asset.assetId}.${asset.dataFormat}/get/`
|
||||
);
|
||||
defaultProjectAssets.forEach(asset => this.cache(
|
||||
this.AssetType[asset.assetType],
|
||||
this.DataFormat[asset.dataFormat],
|
||||
asset.data,
|
||||
asset.id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
export default Storage;
|
||||
const storage = new Storage();
|
||||
|
||||
export default storage;
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import VM from 'scratch-vm';
|
||||
import Storage from '../lib/storage';
|
||||
import storage from '../lib/storage';
|
||||
|
||||
const SET_VM = 'scratch-gui/vm/SET_VM';
|
||||
const defaultVM = new VM();
|
||||
defaultVM.attachStorage(new Storage());
|
||||
defaultVM.attachStorage(storage);
|
||||
const initialState = defaultVM;
|
||||
|
||||
const reducer = function (state, action) {
|
||||
|
||||
@@ -72,6 +72,7 @@ describe('blocks example', () => {
|
||||
await clickText('Sensing');
|
||||
await clickText('Operators');
|
||||
await clickText('Data');
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
|
||||
await clickText('Create variable...');
|
||||
let el = await findByXpath("//input[@placeholder='']");
|
||||
await el.sendKeys('score');
|
||||
|
||||
@@ -23,6 +23,7 @@ const blocksTabScope = "*[@id='react-tabs-1']";
|
||||
const costumesTabScope = "*[@id='react-tabs-3']";
|
||||
const soundsTabScope = "*[@id='react-tabs-5']";
|
||||
const reportedValueScope = '*[@class="blocklyDropDownContent"]';
|
||||
const modalScope = '*[@class="ReactModalPortal"]';
|
||||
|
||||
describe('costumes, sounds and variables', () => {
|
||||
beforeAll(() => {
|
||||
@@ -135,9 +136,18 @@ describe('costumes, sounds and variables', () => {
|
||||
await driver.get(`file://${uri}`);
|
||||
await clickText('Blocks');
|
||||
await clickText('Extensions');
|
||||
await clickText('Pen'); // Modal closes
|
||||
await clickText('Pen', blocksTabScope); // Click the new category
|
||||
await clickText('Pen', modalScope); // Modal closes
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
|
||||
await clickText('stamp', blocksTabScope); // Click the "stamp" block
|
||||
|
||||
// Make sure trying to load the extension again scrolls back down
|
||||
await clickText('Motion', blocksTabScope); // To scroll the list back to the top
|
||||
await clickText('Extensions');
|
||||
await clickText('Pen', modalScope); // Modal closes
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for scroll animation
|
||||
await clickText('stamp', blocksTabScope); // Would fail if didn't scroll back
|
||||
|
||||
|
||||
const logs = await getLogs(errorWhitelist);
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import ProjectLoaderHOC, {ProjectLoader} from '../../../src/lib/project-loader-hoc.jsx';
|
||||
import ProjectLoaderHOC from '../../../src/lib/project-loader-hoc.jsx';
|
||||
import storage from '../../../src/lib/storage';
|
||||
import {mount} from 'enzyme';
|
||||
|
||||
describe('ProjectLoaderHOC', () => {
|
||||
@@ -7,34 +8,41 @@ describe('ProjectLoaderHOC', () => {
|
||||
const Component = ({projectData}) => <div>{projectData}</div>;
|
||||
const WrappedComponent = ProjectLoaderHOC(Component);
|
||||
window.location.hash = '#winning';
|
||||
ProjectLoader.load = jest.fn((id, cb) => cb(null, null));
|
||||
const originalLoad = storage.load;
|
||||
storage.load = jest.fn(() => Promise.resolve(null));
|
||||
const mounted = mount(<WrappedComponent />);
|
||||
ProjectLoader.load.mockRestore();
|
||||
storage.load = originalLoad;
|
||||
window.location.hash = '';
|
||||
expect(mounted.find('div').exists()).toEqual(false);
|
||||
const mountedDiv = mounted.find('div');
|
||||
expect(mountedDiv.exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('when there is no hash, it loads the default project', () => {
|
||||
const Component = ({projectData}) => <div>{projectData}</div>;
|
||||
const WrappedComponent = ProjectLoaderHOC(Component);
|
||||
window.location.hash = '';
|
||||
const originalLoad = storage.load;
|
||||
storage.load = jest.fn((type, id) => Promise.resolve(id));
|
||||
const mounted = mount(<WrappedComponent />);
|
||||
expect(mounted.find('div').text()).toEqual(JSON.stringify(ProjectLoader.DEFAULT_PROJECT_DATA));
|
||||
expect(mounted.state().projectId).toEqual(0);
|
||||
expect(storage.load).toHaveBeenCalledWith(
|
||||
storage.AssetType.Project, 0, storage.DataFormat.JSON
|
||||
);
|
||||
storage.load = originalLoad;
|
||||
});
|
||||
|
||||
test('when there is a hash, it tries to load that project', () => {
|
||||
const Component = ({projectData}) => <div>{projectData}</div>;
|
||||
const WrappedComponent = ProjectLoaderHOC(Component);
|
||||
window.location.hash = '#winning';
|
||||
ProjectLoader.load = jest.fn((id, cb) => cb(null, id));
|
||||
const originalLoad = storage.load;
|
||||
storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
|
||||
const mounted = mount(<WrappedComponent />);
|
||||
mounted.update();
|
||||
ProjectLoader.load.mockRestore();
|
||||
window.location.hash = '';
|
||||
expect(mounted
|
||||
.find('div')
|
||||
.text()
|
||||
).toEqual('winning');
|
||||
expect(mounted.state().projectId).toEqual('winning');
|
||||
expect(storage.load).toHaveBeenLastCalledWith(
|
||||
storage.AssetType.Project, 'winning', storage.DataFormat.JSON
|
||||
);
|
||||
storage.load = originalLoad;
|
||||
});
|
||||
|
||||
test('when hash change happens, the project data state is changed', () => {
|
||||
@@ -42,10 +50,12 @@ describe('ProjectLoaderHOC', () => {
|
||||
const WrappedComponent = ProjectLoaderHOC(Component);
|
||||
window.location.hash = '';
|
||||
const mounted = mount(<WrappedComponent />);
|
||||
const before = mounted.find('div').text();
|
||||
ProjectLoader.load = jest.fn((id, cb) => cb(null, id));
|
||||
window.location.hash = `#winning`;
|
||||
expect(mounted.state().projectId).toEqual(0);
|
||||
const originalLoad = storage.load;
|
||||
storage.load = jest.fn((type, id) => Promise.resolve({data: id}));
|
||||
window.location.hash = '#winning';
|
||||
mounted.instance().updateProject();
|
||||
expect(mounted.find('div').text()).not.toEqual(before);
|
||||
expect(mounted.state().projectId).toEqual('winning');
|
||||
storage.load = originalLoad;
|
||||
});
|
||||
});
|
||||
+9
-2
@@ -21,6 +21,7 @@ module.exports = {
|
||||
lib: ['react', 'react-dom'],
|
||||
gui: './src/index.jsx',
|
||||
blocksonly: './src/examples/blocks-only.jsx',
|
||||
compatibilitytesting: './src/examples/compatibility-testing.jsx',
|
||||
player: './src/examples/player.jsx'
|
||||
},
|
||||
output: {
|
||||
@@ -66,8 +67,8 @@ module.exports = {
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-url-loader?noquotes'
|
||||
test: /\.(svg|png|wav)$/,
|
||||
loader: 'file-loader'
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
@@ -90,6 +91,12 @@ module.exports = {
|
||||
filename: 'blocks-only.html',
|
||||
title: 'Scratch 3.0 GUI: Blocks Only Example'
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
chunks: ['lib', 'compatibilitytesting'],
|
||||
template: 'src/index.ejs',
|
||||
filename: 'compatibility-testing.html',
|
||||
title: 'Scratch 3.0 GUI: Compatibility Testing'
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
chunks: ['lib', 'player'],
|
||||
template: 'src/index.ejs',
|
||||
|
||||
Reference in new issue
Block a user