This commit is contained in:
Evan
2026-09-07 09:55:36 +08:00
parent 9b6892f2ed
commit badbc11154
9098 changed files with 985321 additions and 0 deletions

View File

@@ -0,0 +1,286 @@
import React from 'react';
import {FormattedMessage} from 'react-intl';
import keyMirror from 'keymirror';
import successImage from '../assets/icon--success.svg';
const AlertTypes = keyMirror({
STANDARD: null,
EXTENSION: null,
INLINE: null
});
const AlertLevels = {
SUCCESS: 'success',
INFO: 'info',
WARN: 'warn'
};
const alerts = [
{
alertId: 'createSuccess',
alertType: AlertTypes.STANDARD,
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
content: (
<FormattedMessage
defaultMessage="New project created."
description="Message indicating that project was successfully created"
id="gui.alerts.createsuccess"
/>
),
iconURL: successImage,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 5
},
{
alertId: 'createCopySuccess',
alertType: AlertTypes.STANDARD,
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
content: (
<FormattedMessage
defaultMessage="Project saved as a copy."
description="Message indicating that project was successfully created"
id="gui.alerts.createcopysuccess"
/>
),
iconURL: successImage,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 5
},
{
alertId: 'createRemixSuccess',
alertType: AlertTypes.STANDARD,
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
content: (
<FormattedMessage
defaultMessage="Project saved as a remix."
description="Message indicating that project was successfully created"
id="gui.alerts.createremixsuccess"
/>
),
iconURL: successImage,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 5
},
{
alertId: 'creating',
alertType: AlertTypes.STANDARD,
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
content: (
<FormattedMessage
defaultMessage="Creating new…"
description="Message indicating that project is in process of creating"
id="gui.alerts.creating"
/>
),
iconSpinner: true,
level: AlertLevels.SUCCESS
},
{
alertId: 'creatingCopy',
alertType: AlertTypes.STANDARD,
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
content: (
<FormattedMessage
defaultMessage="Copying project…"
description="Message indicating that project is in process of copying"
id="gui.alerts.creatingCopy"
/>
),
iconSpinner: true,
level: AlertLevels.SUCCESS
},
{
alertId: 'creatingRemix',
alertType: AlertTypes.STANDARD,
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
content: (
<FormattedMessage
defaultMessage="Remixing project…"
description="Message indicating that project is in process of remixing"
id="gui.alerts.creatingRemix"
/>
),
iconSpinner: true,
level: AlertLevels.SUCCESS
},
{
alertId: 'creatingError',
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
closeButton: true,
content: (
<FormattedMessage
defaultMessage="Could not create the project. Please try again!"
description="Message indicating that project could not be created"
id="gui.alerts.creatingError"
/>
),
level: AlertLevels.WARN
},
{
alertId: 'savingError',
clearList: ['createSuccess', 'creating', 'createCopySuccess', 'creatingCopy',
'createRemixSuccess', 'creatingRemix', 'saveSuccess', 'saving'],
showDownload: true,
// showSaveNow: true,
closeButton: true,
content: (
<FormattedMessage
defaultMessage="Project could not save."
description="Message indicating that project could not be saved"
id="gui.alerts.savingError"
/>
),
level: AlertLevels.WARN
},
{
alertId: 'saveSuccess',
alertType: AlertTypes.INLINE,
clearList: ['saveSuccess', 'saving', 'savingError', 'twSaveToDiskSuccess',
'twCreatingRestorePoint', 'twRestorePointSuccess', 'twRestorePointError'],
content: (
<FormattedMessage
defaultMessage="Project saved."
description="Message indicating that project was successfully saved"
id="gui.alerts.savesuccess"
/>
),
iconURL: successImage,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 3
},
{
alertId: 'twSaveToDiskSuccess',
alertType: AlertTypes.INLINE,
clearList: ['saveSuccess', 'saving', 'savingError', 'twCreatingRestorePoint',
'twRestorePointSuccess', 'twRestorePointError'],
content: (
<FormattedMessage
defaultMessage="Saved to your computer."
description="Message indicating that project was successfully saved to the user's disk"
id="tw.alerts.savedToDisk"
/>
),
iconURL: successImage,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 3
},
{
alertId: 'saving',
alertType: AlertTypes.INLINE,
clearList: ['saveSuccess', 'saving', 'savingError', 'twSaveToDiskSuccess',
'twCreatingRestorePoint', 'twRestorePointSuccess', 'twRestorePointError'],
content: (
<FormattedMessage
defaultMessage="Saving project…"
description="Message indicating that project is in process of saving"
id="gui.alerts.saving"
/>
),
iconSpinner: true,
level: AlertLevels.INFO
},
{
alertId: 'twCreatingRestorePoint',
alertType: AlertTypes.INLINE,
clearList: ['twRestorePointSuccess', 'twRestorePointError'],
content: (
<FormattedMessage
defaultMessage="Creating restore point…"
description="Menu bar message indicating that a restore point is being automatically created"
id="tw.alerts.creatingRestorePoint"
/>
),
iconSpinner: true,
level: AlertLevels.INFO
},
{
alertId: 'twRestorePointSuccess',
alertType: AlertTypes.INLINE,
clearList: ['twCreatingRestorePoint', 'twRestorePointError'],
content: (
<FormattedMessage
defaultMessage="Access restore points in &quot;File&quot;"
// eslint-disable-next-line max-len
description="Menu bar message indicating that a restore point was successfully created. File refers to the file dropdown menu."
id="tw.alerts.restorePointSuccess"
/>
),
iconURL: successImage,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 3
},
{
alertId: 'twRestorePointError',
alertType: AlertTypes.INLINE,
clearList: ['twCreatingRestorePoint', 'twRestorePointSuccess'],
content: (
<FormattedMessage
defaultMessage="Could not create restore point"
// eslint-disable-next-line max-len
description="Menu bar message indicating that a restore point could not be created."
id="tw.alerts.restorePointError"
/>
),
iconURL: successImage,
level: AlertLevels.WARN,
maxDisplaySecs: 5
},
{
alertId: 'cloudInfo',
alertType: AlertTypes.STANDARD,
clearList: ['cloudInfo'],
content: (
<FormattedMessage
defaultMessage="Please note, cloud variables only support numbers, not letters or symbols. {learnMoreLink}" // eslint-disable-line max-len
description="Info about cloud variable limitations"
id="gui.alerts.cloudInfo"
values={{
learnMoreLink: (
<a
href="https://scratch.mit.edu/info/faq/#clouddata"
rel="noopener noreferrer"
target="_blank"
>
<FormattedMessage
defaultMessage="Learn more."
description="Link text to cloud var faq"
id="gui.alerts.cloudInfoLearnMore"
/>
</a>
)
}}
/>
),
closeButton: true,
level: AlertLevels.SUCCESS,
maxDisplaySecs: 15
},
{
alertId: 'importingAsset',
alertType: AlertTypes.STANDARD,
clearList: [],
content: (
<FormattedMessage
defaultMessage="Importing…"
description="Message indicating that project is in process of importing"
id="gui.alerts.importing"
/>
),
iconSpinner: true,
level: AlertLevels.SUCCESS
}
];
export {
alerts as default,
AlertLevels,
AlertTypes
};

View File

@@ -0,0 +1,6 @@
const GoogleAnalytics = {
// Disable Scratch's analytics
event () {}
};
export default GoogleAnalytics;

View File

@@ -0,0 +1,136 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Provider} from 'react-redux';
import {createStore, combineReducers, compose} from 'redux';
import ConnectedIntlProvider from './connected-intl-provider.jsx';
import AddonHooks from '../addons/hooks';
import localesReducer, {initLocale, localesInitialState} from '../reducers/locales';
import {setPlayer, setFullScreen} from '../reducers/mode.js';
import locales from '@turbowarp/scratch-l10n';
import {detectLocale} from './detect-locale';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
/*
* Higher Order Component to provide redux state. If an `intl` prop is provided
* it will override the internal `intl` redux state
* @param {React.Component} WrappedComponent - component to provide state for
* @param {boolean} localesOnly - only provide the locale state, not everything
* required by the GUI. Used to exclude excess state when
only rendering modals, not the GUI.
* @returns {React.Component} component with redux and intl state provided
*/
const AppStateHOC = function (WrappedComponent, localesOnly) {
class AppStateWrapper extends React.Component {
constructor (props) {
super(props);
let initialState = {};
let reducers = {};
let enhancer;
let initializedLocales = localesInitialState;
const locale = detectLocale(Object.keys(locales));
if (locale !== 'en') {
initializedLocales = initLocale(initializedLocales, locale);
}
if (localesOnly) {
// Used for instantiating minimal state for the unsupported
// browser modal
reducers = {locales: localesReducer};
initialState = {locales: initializedLocales};
enhancer = composeEnhancers();
} else {
// You are right, this is gross. But it's necessary to avoid
// importing unneeded code that will crash unsupported browsers.
const guiRedux = require('../reducers/gui');
const guiReducer = guiRedux.default;
const {
guiInitialState,
guiMiddleware,
initFullScreen,
initPlayer,
initEmbedded,
initTelemetryModal
} = guiRedux;
const {ScratchPaintReducer} = require('./tw-scratch-paint');
let initializedGui = guiInitialState;
if (props.isFullScreen || props.isPlayerOnly) {
if (props.isFullScreen) {
initializedGui = initFullScreen(initializedGui);
}
if (props.isPlayerOnly) {
initializedGui = initPlayer(initializedGui);
}
} else if (props.showTelemetryModal) {
initializedGui = initTelemetryModal(initializedGui);
}
if (props.isEmbedded) {
initializedGui = initEmbedded(initializedGui);
}
reducers = {
locales: localesReducer,
scratchGui: guiReducer,
scratchPaint: ScratchPaintReducer
};
initialState = {
locales: initializedLocales,
scratchGui: initializedGui
};
enhancer = composeEnhancers(guiMiddleware);
}
const reducer = combineReducers(reducers);
const reducer2 = (previousState, action) => {
const nextState = reducer(previousState, action);
AddonHooks.appStateReducer(action, previousState, nextState);
return nextState;
};
this.store = createStore(
reducer2,
initialState,
enhancer
);
window.ReduxStore = this.store;
AddonHooks.appStateStore = this.store;
}
componentDidUpdate (prevProps) {
if (localesOnly) return;
if (prevProps.isPlayerOnly !== this.props.isPlayerOnly) {
this.store.dispatch(setPlayer(this.props.isPlayerOnly));
}
if (prevProps.isFullScreen !== this.props.isFullScreen) {
this.store.dispatch(setFullScreen(this.props.isFullScreen));
}
}
render () {
const {
isFullScreen, // eslint-disable-line no-unused-vars
isPlayerOnly, // eslint-disable-line no-unused-vars
showTelemetryModal, // eslint-disable-line no-unused-vars
...componentProps
} = this.props;
return (
<Provider store={this.store}>
<ConnectedIntlProvider>
<WrappedComponent
{...componentProps}
/>
</ConnectedIntlProvider>
</Provider>
);
}
}
AppStateWrapper.propTypes = {
isFullScreen: PropTypes.bool,
isPlayerOnly: PropTypes.bool,
isTelemetryEnabled: PropTypes.bool,
showTelemetryModal: PropTypes.bool,
isEmbedded: PropTypes.bool
};
return AppStateWrapper;
};
export default AppStateHOC;

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>back-icon</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="back-icon" fill="#FFFFFF">
<path d="M4.10804912,8.57765055 L9.33203638,3.35366329 C9.63820413,3.04558199 10.1165912,3.04558199 10.422759,3.35366329 L15.6467462,8.57765055 C16.1442688,9.07517314 15.7806946,9.89608543 15.0918172,9.89608543 L12.4893913,9.89608543 L10.9394171,16.0385759 C10.7863332,16.6336895 10.1739977,16.9953502 9.58079768,16.8422663 C9.15981702,16.7485024 8.85364927,16.4231992 8.75797184,16.0385759 L7.2079976,9.89608543 L4.66297817,9.89608543 C3.97410073,9.89608543 3.61052653,9.07517314 4.10804912,8.57765055" id="Fill-1" transform="translate(9.877398, 10.000000) rotate(-90.000000) translate(-9.877398, -10.000000) "></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>help</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="help" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M9.99905449,18 C5.58160974,18 2,14.4173461 2,10.0009453 C2,5.58265391 5.58160974,2 9.99905449,2 C14.4183903,2 18,5.58265391 18,10.0009453 C18,14.4173461 14.4183903,18 9.99905449,18 Z M9.85325612,12.3615266 C9.26892802,12.3615266 8.80562581,12.8360629 8.80562581,13.4221434 C8.80562581,13.9930994 9.26892802,14.4676356 9.85325612,14.4676356 C10.4394752,14.4676356 10.9141236,13.9930994 10.9141236,13.4221434 C10.9141236,12.8360629 10.4394752,12.3615266 9.85325612,12.3615266 Z M9.46181302,11.6431053 L10.1596029,11.6431053 C10.3487058,11.6431053 10.5245716,11.5239986 10.5699563,11.3406121 C10.6588347,10.9700579 10.9141236,10.7167198 11.2545089,10.4614912 L11.7159201,10.1211863 C12.4458575,9.57291741 12.8978135,8.90554177 12.8978135,7.93000118 C12.8978135,6.71435661 11.9106961,5.53274253 9.93835244,5.53274253 C8.16078478,5.53274253 7.1018083,6.7370436 7.1018083,8.16065225 C7.1018083,8.1984639 7.10369933,8.23627555 7.10369933,8.27597779 C7.11315447,8.49717594 7.28902021,8.67867187 7.51027065,8.6862342 L8.38392625,8.71648352 C8.62408699,8.72593643 8.81886302,8.53309701 8.81886302,8.29299303 L8.81886302,8.28354012 C8.81886302,7.71258419 9.23299846,7.13973768 9.93835244,7.13973768 C10.6701808,7.13973768 11.0219123,7.60103982 11.0219123,8.07557604 C11.0219123,8.3931939 10.9141236,8.6862342 10.5850845,8.93011934 L9.96482685,9.39331206 C9.26892802,9.91511284 9.0363314,10.5352239 9.0363314,11.1080704 C9.0363314,11.1515538 9.03822243,11.1950372 9.03822243,11.2347395 C9.04578655,11.4634999 9.23299846,11.6431053 9.46181302,11.6431053 Z" id="help-icon" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
<title>Alerts/Check</title>
<desc>Created with Sketch.</desc>
<g id="Alerts/Check" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M15.224782,4.51424732 C15.8244301,3.86519582 16.8367012,3.82514645 17.4857527,4.42479455 C18.1348042,5.02444265 18.1748535,6.03671368 17.5752055,6.68576518 L9.4450369,15.4857566 C8.81157357,16.1714093 7.72807682,16.1714093 7.09461349,15.4857566 L2.42479455,10.4312037 C1.82514645,9.78215224 1.86519582,8.76988121 2.51424732,8.17023311 C3.16329883,7.57058501 4.17556986,7.61063439 4.77521796,8.25968589 L8.26982519,12.0422045 L15.224782,4.51424732 Z" id="Check" fill="#0EBD8C" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 962 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51 (57462) - http://www.bohemiancoding.com/sketch -->
<title>Tutorials</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Tutorials" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M19.0000002,9.48986925 C19.0000002,9.86977638 18.6906473,10.1592294 18.3125492,10.1592294 L17.2813727,10.1592294 C16.9014655,10.1592294 16.5939217,9.86977638 16.5939217,9.48986925 C16.5939217,9.10996211 16.9014655,8.80241823 17.2813727,8.80241823 L18.3125492,8.80241823 C18.6906473,8.80241823 19.0000002,9.10996211 19.0000002,9.48986925 Z M5.32243869,14.1574807 C5.59380093,14.428843 5.59380093,14.8449317 5.32243869,15.116294 L4.60061513,15.8580174 C4.4558886,15.9846531 4.29307126,16.0570164 4.11035401,16.0570164 C3.92944585,16.0570164 3.76662851,15.9846531 3.62371106,15.8580174 C3.36863055,15.5866552 3.36863055,15.1524756 3.62371106,14.8992042 L4.36362544,14.1574807 C4.63498768,13.8861185 5.06916727,13.8861185 5.32243869,14.1574807 Z M16.358922,3.12262561 C16.6121934,3.39217877 16.6121934,3.82816744 16.358922,4.07962978 L15.6171985,4.82135324 C15.4905628,4.94979804 15.3096546,5.0221613 15.1468373,5.0221613 C14.96412,5.0221613 14.8031118,4.94979804 14.6583852,4.82135324 C14.4051138,4.55180008 14.4051138,4.1176205 14.6583852,3.86253999 L15.4001087,3.12262561 C15.6515711,2.85126337 16.0875597,2.85126337 16.358922,3.12262561 Z M12.4138577,17.3409216 C12.5965749,18.5005429 11.9254057,19.477447 9.98968834,19.477447 C8.0557801,19.477447 7.38461082,18.5005429 7.56551898,17.3409216 C7.65597306,16.8343788 8.14623418,17.5236389 9.98968834,17.5580114 C11.8530424,17.5580114 12.3414944,16.8343788 12.4138577,17.3409216 Z M3.62280652,4.08017251 C3.36772601,3.82690108 3.36772601,3.39272149 3.62280652,3.12316833 C3.89235968,2.84999701 4.32834835,2.84999701 4.59971059,3.12316833 L5.32153415,3.86308271 C5.59470547,4.11816322 5.59470547,4.55234281 5.32153415,4.82189597 C5.19670752,4.94853168 5.01579936,5.02270403 4.85298201,5.02270403 C4.67026477,5.02270403 4.48935661,4.94853168 4.3627209,4.82189597 L3.62280652,4.08017251 Z M3.38798773,9.48986925 C3.38798773,9.86977638 3.08044385,10.1592294 2.71862753,10.1592294 L1.6693602,10.1592294 C1.30754387,10.1592294 1,9.86977638 1,9.48986925 C1,9.10996211 1.30754387,8.80241823 1.6693602,8.80241823 L2.71862753,8.80241823 C3.08044385,8.80241823 3.38798773,9.10996211 3.38798773,9.48986925 Z M9.32050905,2.19872763 L9.32050905,1.1693602 C9.32050905,0.789453058 9.60996211,0.5 9.98986925,0.5 C10.3697764,0.5 10.6773203,0.789453058 10.6773203,1.1693602 L10.6773203,2.19872763 C10.6773203,2.57863477 10.3697764,2.88617864 9.98986925,2.88617864 C9.60996211,2.88617864 9.32050905,2.57863477 9.32050905,2.19872763 Z M15.0021108,8.80259914 C15.1269374,10.1594103 14.7307485,11.4257675 13.9709342,12.3827716 C13.2635833,13.3072124 12.8113129,13.5062113 12.7932221,14.6278419 C12.7932221,15.206748 12.5037691,15.6771093 12.0334078,15.8941991 L12.0171261,15.8941991 C10.7326782,16.5273776 9.24742215,16.5092868 7.96297421,15.8941991 C7.51070381,15.6771093 7.22125075,15.206748 7.22125075,14.7002052 C7.22125075,13.5243021 6.75088953,13.343394 6.02725689,12.4026715 C5.35970577,11.5524032 4.95989874,10.4669542 4.95989874,9.30914199 C4.95989874,6.32415734 7.54869452,3.97235124 10.5879516,4.31607675 C12.9035761,4.58743899 14.785021,6.48697468 15.0021108,8.80259914 Z M16.358922,14.8993851 C16.6121934,15.1526565 16.6121934,15.5868361 16.358922,15.8581983 C16.2322863,15.984834 16.0513781,16.0571973 15.8686608,16.0571973 C15.7076526,16.0571973 15.5249353,15.984834 15.4001087,15.8581983 L14.6583852,15.1164749 C14.4051138,14.8451126 14.4051138,14.4290239 14.6583852,14.1576616 C14.9297475,13.8844903 15.362118,13.8844903 15.6171985,14.1576616 L16.358922,14.8993851 Z" id="Combined-Shape" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49.3 (51167) - http://www.bohemiancoding.com/sketch -->
<title>Artboard</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="icon" stroke="#FFFFFF" stroke-width="2" x="1" y="1" width="18" height="18" rx="4"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 559 B

View File

@@ -0,0 +1,59 @@
import SharedAudioContext from './shared-audio-context.js';
class AudioBufferPlayer {
constructor (samples, sampleRate) {
this.audioContext = new SharedAudioContext();
this.buffer = this.audioContext.createBuffer(1, samples.length, sampleRate);
this.buffer.getChannelData(0).set(samples);
this.source = null;
this.startTime = null;
this.updateCallback = null;
this.trimStart = null;
this.trimEnd = null;
}
play (trimStart, trimEnd, onUpdate, onEnded) {
this.updateCallback = onUpdate;
this.trimStart = trimStart;
this.trimEnd = trimEnd;
this.startTime = Date.now();
const trimStartTime = this.buffer.duration * trimStart;
const trimmedDuration = (this.buffer.duration * trimEnd) - trimStartTime;
this.source = this.audioContext.createBufferSource();
this.source.onended = onEnded;
this.source.buffer = this.buffer;
this.source.connect(this.audioContext.destination);
this.source.start(0, trimStartTime, trimmedDuration);
this.update();
}
update () {
const timeSinceStart = (Date.now() - this.startTime) / 1000;
const percentage = timeSinceStart / this.buffer.duration;
if (percentage + this.trimStart < this.trimEnd && this.source.onended) {
requestAnimationFrame(this.update.bind(this));
this.updateCallback(percentage + this.trimStart);
} else {
this.updateCallback = null;
}
}
stop () {
if (this.source) {
this.source.onended = null; // Do not call onEnded callback if manually stopped
try {
this.source.stop();
} catch (e) {
// This is probably Safari, which dies when you call stop more than once
// which the spec says is allowed: https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode
console.log('Caught error while stopping buffer source node.'); // eslint-disable-line no-console
}
}
}
}
export default AudioBufferPlayer;

View File

@@ -0,0 +1,161 @@
import EchoEffect from './effects/echo-effect.js';
import RobotEffect from './effects/robot-effect.js';
import VolumeEffect from './effects/volume-effect.js';
import FadeEffect from './effects/fade-effect.js';
import MuteEffect from './effects/mute-effect.js';
const effectTypes = {
ROBOT: 'robot',
REVERSE: 'reverse',
LOUDER: 'higher',
SOFTER: 'lower',
FASTER: 'faster',
SLOWER: 'slower',
ECHO: 'echo',
FADEIN: 'fade in',
FADEOUT: 'fade out',
MUTE: 'mute'
};
class AudioEffects {
static get effectTypes () {
return effectTypes;
}
constructor (buffer, name, trimStart, trimEnd) {
this.trimStartSeconds = (trimStart * buffer.length) / buffer.sampleRate;
this.trimEndSeconds = (trimEnd * buffer.length) / buffer.sampleRate;
this.adjustedTrimStartSeconds = this.trimStartSeconds;
this.adjustedTrimEndSeconds = this.trimEndSeconds;
// Some effects will modify the playback rate and/or number of samples.
// Need to precompute those values to create the offline audio context.
const pitchRatio = Math.pow(2, 4 / 12); // A major third
let sampleCount = buffer.length;
const affectedSampleCount = Math.floor((this.trimEndSeconds - this.trimStartSeconds) *
buffer.sampleRate);
let adjustedAffectedSampleCount = affectedSampleCount;
const unaffectedSampleCount = sampleCount - affectedSampleCount;
this.playbackRate = 1;
switch (name) {
case effectTypes.ECHO:
sampleCount = Math.max(sampleCount,
Math.floor((this.trimEndSeconds + EchoEffect.TAIL_SECONDS) * buffer.sampleRate));
break;
case effectTypes.FASTER:
this.playbackRate = pitchRatio;
adjustedAffectedSampleCount = Math.floor(affectedSampleCount / this.playbackRate);
sampleCount = unaffectedSampleCount + adjustedAffectedSampleCount;
break;
case effectTypes.SLOWER:
this.playbackRate = 1 / pitchRatio;
adjustedAffectedSampleCount = Math.floor(affectedSampleCount / this.playbackRate);
sampleCount = unaffectedSampleCount + adjustedAffectedSampleCount;
break;
}
const durationSeconds = sampleCount / buffer.sampleRate;
this.adjustedTrimEndSeconds = this.trimStartSeconds +
(adjustedAffectedSampleCount / buffer.sampleRate);
this.adjustedTrimStart = this.adjustedTrimStartSeconds / durationSeconds;
this.adjustedTrimEnd = this.adjustedTrimEndSeconds / durationSeconds;
if (window.OfflineAudioContext) {
this.audioContext = new window.OfflineAudioContext(1, sampleCount, buffer.sampleRate);
} else {
// Need to use webkitOfflineAudioContext, which doesn't support all sample rates.
// Resample by adjusting sample count to make room and set offline context to desired sample rate.
const sampleScale = 44100 / buffer.sampleRate;
this.audioContext = new window.webkitOfflineAudioContext(1, sampleScale * sampleCount, 44100);
}
// For the reverse effect we need to manually reverse the data into a new audio buffer
// to prevent overwriting the original, so that the undo stack works correctly.
// Doing buffer.reverse() would mutate the original data.
if (name === effectTypes.REVERSE) {
const originalBufferData = buffer.getChannelData(0);
const newBuffer = this.audioContext.createBuffer(1, buffer.length, buffer.sampleRate);
const newBufferData = newBuffer.getChannelData(0);
const bufferLength = buffer.length;
const startSamples = Math.floor(this.trimStartSeconds * buffer.sampleRate);
const endSamples = Math.floor(this.trimEndSeconds * buffer.sampleRate);
let counter = 0;
for (let i = 0; i < bufferLength; i++) {
if (i >= startSamples && i < endSamples) {
newBufferData[i] = originalBufferData[endSamples - counter - 1];
counter++;
} else {
newBufferData[i] = originalBufferData[i];
}
}
this.buffer = newBuffer;
} else {
// All other effects use the original buffer because it is not modified.
this.buffer = buffer;
}
this.source = this.audioContext.createBufferSource();
this.source.buffer = this.buffer;
this.name = name;
}
process (done) {
// Some effects need to use more nodes and must expose an input and output
let input;
let output;
switch (this.name) {
case effectTypes.FASTER:
case effectTypes.SLOWER:
this.source.playbackRate.setValueAtTime(this.playbackRate, this.adjustedTrimStartSeconds);
this.source.playbackRate.setValueAtTime(1.0, this.adjustedTrimEndSeconds);
break;
case effectTypes.LOUDER:
({input, output} = new VolumeEffect(this.audioContext, 1.25,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.SOFTER:
({input, output} = new VolumeEffect(this.audioContext, 0.75,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.ECHO:
({input, output} = new EchoEffect(this.audioContext,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.ROBOT:
({input, output} = new RobotEffect(this.audioContext,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.FADEIN:
({input, output} = new FadeEffect(this.audioContext, true,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.FADEOUT:
({input, output} = new FadeEffect(this.audioContext, false,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.MUTE:
({input, output} = new MuteEffect(this.audioContext,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
}
if (input && output) {
this.source.connect(input);
output.connect(this.audioContext.destination);
} else {
// No effects nodes are needed, wire directly to the output
this.source.connect(this.audioContext.destination);
}
this.source.start();
this.audioContext.startRendering();
this.audioContext.oncomplete = ({renderedBuffer}) => {
done(renderedBuffer, this.adjustedTrimStart, this.adjustedTrimEnd);
};
}
}
export default AudioEffects;

View File

@@ -0,0 +1,139 @@
import 'get-float-time-domain-data';
import getUserMedia from 'get-user-media-promise';
import SharedAudioContext from './shared-audio-context.js';
import {computeRMS, computeChunkedRMS} from './audio-util.js';
class AudioRecorder {
constructor () {
this.audioContext = new SharedAudioContext();
this.bufferLength = 8192;
this.userMediaStream = null;
this.mediaStreamSource = null;
this.sourceNode = null;
this.scriptProcessorNode = null;
this.recordedSamples = 0;
this.recording = false;
this.started = false;
this.buffers = [];
this.disposed = false;
}
startListening (onStarted, onUpdate, onError) {
try {
getUserMedia({audio: true})
.then(userMediaStream => {
if (!this.disposed) {
this.started = true;
onStarted();
this.attachUserMediaStream(userMediaStream, onUpdate);
}
})
.catch(e => {
if (!this.disposed) {
onError(e);
}
});
} catch (e) {
if (!this.disposed) {
onError(e);
}
}
}
startRecording () {
this.recording = true;
}
attachUserMediaStream (userMediaStream, onUpdate) {
this.userMediaStream = userMediaStream;
this.mediaStreamSource = this.audioContext.createMediaStreamSource(userMediaStream);
this.sourceNode = this.audioContext.createGain();
this.scriptProcessorNode = this.audioContext.createScriptProcessor(this.bufferLength, 1, 1);
this.scriptProcessorNode.onaudioprocess = processEvent => {
if (this.recording && !this.disposed) {
this.buffers.push(new Float32Array(processEvent.inputBuffer.getChannelData(0)));
}
};
this.analyserNode = this.audioContext.createAnalyser();
this.analyserNode.fftSize = 2048;
const bufferLength = this.analyserNode.frequencyBinCount;
const dataArray = new Float32Array(bufferLength);
const update = () => {
if (this.disposed) return;
this.analyserNode.getFloatTimeDomainData(dataArray);
onUpdate(computeRMS(dataArray));
requestAnimationFrame(update);
};
requestAnimationFrame(update);
// Wire everything together, ending in the destination
this.mediaStreamSource.connect(this.sourceNode);
this.sourceNode.connect(this.analyserNode);
this.analyserNode.connect(this.scriptProcessorNode);
this.scriptProcessorNode.connect(this.audioContext.destination);
}
stop () {
const buffer = new Float32Array(this.buffers.length * this.bufferLength);
let offset = 0;
for (let i = 0; i < this.buffers.length; i++) {
const bufferChunk = this.buffers[i];
buffer.set(bufferChunk, offset);
offset += bufferChunk.length;
}
const chunkLevels = computeChunkedRMS(buffer);
const maxRMS = Math.max.apply(null, chunkLevels);
const threshold = maxRMS / 8;
let firstChunkAboveThreshold = null;
let lastChunkAboveThreshold = null;
for (let i = 0; i < chunkLevels.length; i++) {
if (chunkLevels[i] > threshold) {
if (firstChunkAboveThreshold === null) firstChunkAboveThreshold = i + 1;
lastChunkAboveThreshold = i + 1;
}
}
let trimStart = Math.max(2, firstChunkAboveThreshold - 2) / this.buffers.length;
let trimEnd = Math.min(this.buffers.length - 2, lastChunkAboveThreshold + 2) / this.buffers.length;
// With very few samples, the automatic trimming can produce invalid values
if (trimStart >= trimEnd) {
trimStart = 0;
trimEnd = 1;
}
return {
levels: chunkLevels,
samples: buffer,
sampleRate: this.audioContext.sampleRate,
trimStart: trimStart,
trimEnd: trimEnd
};
}
dispose () {
if (this.started) {
this.scriptProcessorNode.onaudioprocess = null;
this.scriptProcessorNode.disconnect();
this.analyserNode.disconnect();
this.sourceNode.disconnect();
this.mediaStreamSource.disconnect();
this.userMediaStream.getAudioTracks()[0].stop();
}
this.disposed = true;
}
}
export default AudioRecorder;

View File

@@ -0,0 +1,113 @@
import WavEncoder from 'wav-encoder';
export const SOUND_BYTE_LIMIT = 10 * 1000 * 1000; // 10mb
const _computeRMS = function (samples, start, end, scaling = 0.55) {
const length = end - start;
if (length === 0) return 0;
// Calculate RMS, adapted from https://github.com/Tonejs/Tone.js/blob/master/Tone/component/Meter.js#L88
let sum = 0;
for (let i = start; i < end; i++) {
const sample = samples[i];
sum += sample ** 2;
}
const rms = Math.sqrt(sum / length);
const val = rms / scaling;
return Math.sqrt(val);
};
const computeRMS = (samples, scaling) => _computeRMS(samples, 0, samples.length, scaling);
const computeChunkedRMS = function (samples, chunkSize = 1024) {
const sampleCount = samples.length;
const chunkLevels = [];
for (let i = 0; i < sampleCount; i += chunkSize) {
const maxIndex = Math.min(sampleCount, i + chunkSize);
chunkLevels.push(_computeRMS(samples, i, maxIndex));
}
return chunkLevels;
};
const encodeAndAddSoundToVM = function (vm, samples, sampleRate, name, callback) {
WavEncoder.encode({
sampleRate: sampleRate,
channelData: [samples]
}).then(wavBuffer => {
const vmSound = {
format: '',
dataFormat: 'wav',
rate: sampleRate,
sampleCount: samples.length
};
// Create an asset from the encoded .wav and get resulting md5
const storage = vm.runtime.storage;
vmSound.asset = storage.createAsset(
storage.AssetType.Sound,
storage.DataFormat.WAV,
new Uint8Array(wavBuffer),
null,
true // generate md5
);
vmSound.assetId = vmSound.asset.assetId;
// update vmSound object with md5 property
vmSound.md5 = `${vmSound.assetId}.${vmSound.dataFormat}`;
// The VM will update the sound name to a fresh name
vmSound.name = name;
vm.addSound(vmSound).then(() => {
if (callback) callback();
});
});
};
/**
@typedef SoundBuffer
@type {Object}
@property {Float32Array} samples Array of audio samples
@property {number} sampleRate Audio sample rate
*/
/**
* Downsample the given buffer to try to reduce file size below SOUND_BYTE_LIMIT
* @param {SoundBuffer} buffer - Buffer to resample
* @param {function(SoundBuffer):Promise<SoundBuffer>} resampler - resampler function
* @returns {SoundBuffer} Downsampled buffer with half the sample rate
*/
const downsampleIfNeeded = (buffer, resampler) => {
const {samples, sampleRate} = buffer;
const encodedByteLength = samples.length * 2; /* bitDepth 16 bit */
// Resolve immediately if already within byte limit
if (encodedByteLength < SOUND_BYTE_LIMIT) {
return Promise.resolve({samples, sampleRate});
}
// TW: Don't check if the sound will still fit at this reduced sample rate.
// Instead the GUI will show a warning if it's too large.
return resampler({samples, sampleRate}, 22050);
};
/**
* Drop every other sample of an audio buffer as a last-resort way of downsampling.
* @param {SoundBuffer} buffer - Buffer to resample
* @returns {SoundBuffer} Downsampled buffer with half the sample rate
*/
const dropEveryOtherSample = buffer => {
const newLength = Math.floor(buffer.samples.length / 2);
const newSamples = new Float32Array(newLength);
for (let i = 0; i < newLength; i++) {
newSamples[i] = buffer.samples[i * 2];
}
return {
samples: newSamples,
sampleRate: buffer.sampleRate / 2
};
};
export {
computeRMS,
computeChunkedRMS,
encodeAndAddSoundToVM,
downsampleIfNeeded,
dropEveryOtherSample
};

View File

@@ -0,0 +1,41 @@
class EchoEffect {
static get DELAY_TIME () {
return 0.25;
}
static get TAIL_SECONDS () {
return 0.75;
}
constructor (audioContext, startTime, endTime) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.effectInput = this.audioContext.createGain();
this.effectInput.gain.value = 0;
this.effectInput.gain.setValueAtTime(0.75, startTime);
this.effectInput.gain.setValueAtTime(0, endTime);
this.delay = this.audioContext.createDelay(1);
this.delay.delayTime.value = EchoEffect.DELAY_TIME;
this.decay = this.audioContext.createGain();
this.decay.gain.value = 0.3;
this.compressor = this.audioContext.createDynamicsCompressor();
this.compressor.threshold.value = -5;
this.compressor.knee.value = 15;
this.compressor.ratio.value = 12;
this.compressor.attack.value = 0;
this.compressor.release.value = 0.25;
this.input.connect(this.effectInput);
this.effectInput.connect(this.delay);
this.delay.connect(this.compressor);
this.input.connect(this.compressor);
this.delay.connect(this.decay);
this.decay.connect(this.delay);
this.compressor.connect(this.output);
}
}
export default EchoEffect;

View File

@@ -0,0 +1,27 @@
class FadeEffect {
constructor (audioContext, fadeIn, startSeconds, endSeconds) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.gain = this.audioContext.createGain();
this.gain.gain.setValueAtTime(1, 0);
if (fadeIn) {
this.gain.gain.setValueAtTime(0, startSeconds);
this.gain.gain.linearRampToValueAtTime(1, endSeconds);
} else {
this.gain.gain.setValueAtTime(1, startSeconds);
this.gain.gain.linearRampToValueAtTime(0, endSeconds);
}
this.gain.gain.setValueAtTime(1, endSeconds);
this.input.connect(this.gain);
this.gain.connect(this.output);
}
}
export default FadeEffect;

View File

@@ -0,0 +1,22 @@
class MuteEffect {
constructor (audioContext, startSeconds, endSeconds) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.gain = this.audioContext.createGain();
// Smoothly ramp the gain down before the start time, and up after the end time.
this.rampLength = 0.001;
this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength));
this.gain.gain.linearRampToValueAtTime(0, startSeconds);
this.gain.gain.setValueAtTime(0, endSeconds);
this.gain.gain.linearRampToValueAtTime(1.0, endSeconds + this.rampLength);
this.input.connect(this.gain);
this.gain.connect(this.output);
}
}
export default MuteEffect;

View File

@@ -0,0 +1,117 @@
class RobotEffect {
constructor (audioContext, startTime, endTime) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.passthrough = this.audioContext.createGain();
this.effectInput = this.audioContext.createGain();
this.passthrough.gain.value = 1;
this.effectInput.gain.value = 0;
this.passthrough.gain.setValueAtTime(0, startTime);
this.passthrough.gain.setValueAtTime(1, endTime);
this.effectInput.gain.setValueAtTime(1, startTime);
this.effectInput.gain.setValueAtTime(0, endTime);
// Ring modulator inspired by BBC Dalek voice
// http://recherche.ircam.fr/pub/dafx11/Papers/66_e.pdf
// https://github.com/bbc/webaudio.prototyping.bbc.co.uk
// > There are four parallel signal paths, two which process the
// > combination Vc + Vin / 2 and two which process Vc - Vin/2.
// > Each branch consists of a non-linearity [diode]...
const createDiodeNode = () => {
const node = this.audioContext.createWaveShaper();
// Piecewise function given by (2) in Parker paper
const transform = (v, vb = 0.2, vl = 0.4, h = 0.65) => {
if (v <= vb) return 0;
if (v <= vl) return h * (Math.pow(v - vb, 2) / ((2 * vl) - (2 * vb)));
return (h * v) - (h * vl) + (h * (Math.pow(v - vb, 2) / ((2 * vl) - (2 * vb))));
};
// Create the waveshaper curve with the voltage transform above
const bufferLength = 1024;
const curve = new Float32Array(bufferLength);
for (let i = 0; i < bufferLength; i++) {
const voltage = (2 * (i / bufferLength)) - 1;
curve[i] = transform(voltage);
}
node.curve = curve;
return node;
};
const oscillator = this.audioContext.createOscillator();
oscillator.frequency.value = 50;
oscillator.start(0);
const vInGain = this.audioContext.createGain();
vInGain.gain.value = 0.5;
const vInInverter1 = this.audioContext.createGain();
vInInverter1.gain.value = -1;
const vInInverter2 = this.audioContext.createGain();
vInInverter2.gain.value = -1;
const vInDiode1 = createDiodeNode(this.audioContext);
const vInDiode2 = createDiodeNode(this.audioContext);
const vInInverter3 = this.audioContext.createGain();
vInInverter3.gain.value = -1;
const vcInverter1 = this.audioContext.createGain();
vcInverter1.gain.value = -1;
const vcDiode3 = createDiodeNode(this.audioContext);
const vcDiode4 = createDiodeNode(this.audioContext);
const compressor = this.audioContext.createDynamicsCompressor();
compressor.threshold.value = -5;
compressor.knee.value = 15;
compressor.ratio.value = 12;
compressor.attack.value = 0;
compressor.release.value = 0.25;
const biquadFilter = this.audioContext.createBiquadFilter();
biquadFilter.type = 'highpass';
biquadFilter.frequency.value = 1000;
biquadFilter.gain.value = 1.25;
this.input.connect(this.effectInput);
this.input.connect(this.passthrough);
this.passthrough.connect(this.output);
this.effectInput.connect(vcInverter1);
this.effectInput.connect(vcDiode4);
vcInverter1.connect(vcDiode3);
oscillator.connect(vInGain);
vInGain.connect(vInInverter1);
vInGain.connect(vcInverter1);
vInGain.connect(vcDiode4);
vInInverter1.connect(vInInverter2);
vInInverter1.connect(vInDiode2);
vInInverter2.connect(vInDiode1);
vInDiode1.connect(vInInverter3);
vInDiode2.connect(vInInverter3);
vInInverter3.connect(compressor);
vcDiode3.connect(compressor);
vcDiode4.connect(compressor);
this.effectInput.connect(biquadFilter);
biquadFilter.connect(compressor);
compressor.connect(this.output);
}
}
export default RobotEffect;

View File

@@ -0,0 +1,22 @@
class VolumeEffect {
constructor (audioContext, volume, startSeconds, endSeconds) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.gain = this.audioContext.createGain();
// Smoothly ramp the gain up before the start time, and down after the end time.
this.rampLength = 0.01;
this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength));
this.gain.gain.exponentialRampToValueAtTime(volume, startSeconds);
this.gain.gain.setValueAtTime(volume, endSeconds);
this.gain.gain.exponentialRampToValueAtTime(1.0, endSeconds + this.rampLength);
this.input.connect(this.gain);
this.gain.connect(this.output);
}
}
export default VolumeEffect;

View File

@@ -0,0 +1,34 @@
import StartAudioContext from '@turbowarp/startaudiocontext';
import bowser from 'bowser';
import log from '../log';
let AUDIO_CONTEXT;
if (!bowser.msie) {
/**
* AudioContext can be initialized only when user interaction event happens
*/
const event =
typeof document.ontouchstart === 'undefined' ?
'mousedown' :
'touchstart';
const initAudioContext = () => {
document.removeEventListener(event, initAudioContext);
try {
AUDIO_CONTEXT = new (window.AudioContext || window.webkitAudioContext)();
StartAudioContext(AUDIO_CONTEXT);
} catch (e) {
log.error('could not create shared audio context; sound-related features will not be available', e);
}
};
document.addEventListener(event, initAudioContext);
}
/**
* Wrap browser AudioContext because we shouldn't create more than one
* @return {AudioContext} The singleton AudioContext
*/
export default function () {
return AUDIO_CONTEXT;
}

View File

@@ -0,0 +1,140 @@
import xhr from 'xhr';
import costumePayload from './backpack/costume-payload';
import soundPayload from './backpack/sound-payload';
import spritePayload from './backpack/sprite-payload';
import codePayload from './backpack/code-payload';
import localBackpackAPI from './tw-local-backpack-api';
export const LOCAL_API = '_local_';
// Add a new property for the full thumbnail url, which includes the host.
// Also include a full body url for loading sprite zips
// TODO retreiving the images through storage would allow us to remove this.
const includeFullUrls = (item, host) => Object.assign({}, item, {
thumbnailUrl: `${host}/${item.thumbnail}`,
bodyUrl: `${host}/${item.body}`
});
const getBackpackContents = ({
host,
username,
token,
limit,
offset
}) => new Promise((resolve, reject) => {
if (host === LOCAL_API) {
return resolve(localBackpackAPI.getBackpackContents({
limit,
offset
}));
}
xhr({
method: 'GET',
uri: `${host}/${username}?limit=${limit}&offset=${offset}`,
headers: {'x-token': token},
json: true
}, (error, response) => {
if (error || response.statusCode !== 200) {
return reject(new Error(response.status));
}
return resolve(response.body.map(item => includeFullUrls(item, host)));
});
});
const saveBackpackObject = ({
host,
username,
token,
type, // Type of object being saved to the backpack
mime, // Mime-type of the object being saved
name, // User-facing name of the object being saved
body, // Base64-encoded body of the object being saved
thumbnail // Base64-encoded JPEG thumbnail of the object being saved
}) => new Promise((resolve, reject) => {
if (host === LOCAL_API) {
return resolve(localBackpackAPI.saveBackpackObject({
type,
mime,
name,
body,
thumbnail
}));
}
xhr({
method: 'POST',
uri: `${host}/${username}`,
headers: {'x-token': token},
json: {type, mime, name, body, thumbnail}
}, (error, response) => {
if (error || response.statusCode !== 200) {
return reject(new Error(response.status));
}
return resolve(includeFullUrls(response.body, host));
});
});
const deleteBackpackObject = ({
host,
username,
token,
id
}) => new Promise((resolve, reject) => {
if (host === LOCAL_API) {
return resolve(localBackpackAPI.deleteBackpackObject({
id
}));
}
xhr({
method: 'DELETE',
uri: `${host}/${username}/${id}`,
headers: {'x-token': token}
}, (error, response) => {
if (error || response.statusCode !== 200) {
return reject(new Error(response.status));
}
return resolve(response.body);
});
});
const updateBackpackObject = ({
host,
id,
name
}) => new Promise((resolve, reject) => {
if (host === LOCAL_API) {
return resolve(localBackpackAPI.updateBackpackObject({
id,
name
}));
}
reject(new Error('updateBackpackObject not supported'));
});
// Two types of backpack items are not retreivable through storage
// code, as json and sprite3 as arraybuffer zips.
const fetchAs = (responseType, uri) => new Promise((resolve, reject) => {
xhr({uri, responseType}, (error, response) => {
if (error || response.statusCode !== 200) {
return reject(new Error(response.status));
}
return resolve(response.body);
});
});
// These two helpers allow easy fetching of backpack code and sprite zips
// Use the curried fetchAs here so the consumer does not worry about XHR responseTypes
const fetchCode = fetchAs.bind(null, 'json');
const fetchSprite = fetchAs.bind(null, 'arraybuffer');
export {
getBackpackContents,
saveBackpackObject,
deleteBackpackObject,
updateBackpackObject,
costumePayload,
soundPayload,
spritePayload,
codePayload,
fetchCode,
fetchSprite
};

View File

@@ -0,0 +1,62 @@
import computedStyleToInlineStyle from 'computed-style-to-inline-style';
import LazyScratchBlocks from '../tw-lazy-scratch-blocks';
/**
* Given a blockId, return a data-uri image that can be used to create a thumbnail.
* @param {string} blockId the ID of the block to imagify
* @return {Promise} resolves to a data-url of a picture of the blocks
*/
export default function (blockId) {
// Not sure any better way to access the scratch-blocks workspace than this...
const ScratchBlocks = LazyScratchBlocks.get();
const block = ScratchBlocks.getMainWorkspace().getBlockById(blockId);
const blockSvg = block.getSvgRoot().cloneNode(true /* deep */);
// Once we have the cloned SVG, do the rest in a setTimeout to prevent
// blocking the drag end from finishing promptly.
return new Promise(resolve => {
setTimeout(() => {
// TW: Fixes issues caused by scratch-blocks block hiding
blockSvg.style.display = '';
// Strip &nbsp; entities that cannot be inlined
blockSvg.innerHTML = blockSvg.innerHTML.replace(/&nbsp;/g, ' ');
// Create an <svg> element to put the cloned blockSvg inside
const NS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(NS, 'svg');
svg.appendChild(blockSvg);
// Needs to be on the DOM to get CSS properties and correct sizing
document.body.appendChild(svg);
const padding = 10;
const extraHatPadding = 16;
const topPadding = padding + (blockSvg.getAttribute('data-shapes') === 'hat' ? extraHatPadding : 0);
const leftPadding = padding;
blockSvg.setAttribute('transform', `translate(${leftPadding} ${topPadding})`);
const bounds = blockSvg.getBoundingClientRect();
svg.setAttribute('width', bounds.width + (2 * padding));
svg.setAttribute('height', bounds.height + (2 * padding));
// We need to inline the styles set by CSS rules because
// not all the styles are set directly on the SVG. This makes the
// image styled the same way the block actually appears.
// TODO this doesn't handle images that are xlink:href in the SVG
computedStyleToInlineStyle(svg, {
recursive: true,
// Enumerate the specific properties we need to inline.
// Specifically properties that are set from CSS in scratch-blocks
properties: ['fill', 'font-family', 'font-size', 'font-weight']
});
const svgString = (new XMLSerializer()).serializeToString(svg);
// Once we have the svg as a string, remove it from the DOM
svg.parentNode.removeChild(svg);
resolve(`data:image/svg+xml;utf-8,${encodeURIComponent(svgString)}`);
}, 10);
});
}

View File

@@ -0,0 +1,57 @@
import blockToImage from './block-to-image';
import createThumbnail from './thumbnail';
import {BLOCKS_DEFAULT_SCALE} from '../layout-constants';
import {Base64} from 'js-base64';
const codePayload = ({blockObjects, topBlockId}) => {
const payload = {
type: 'script', // Needs to match backpack-server type name
name: 'code', // All code currently gets the same name
mime: 'application/json',
// Backpack expects a base64 encoded string to store. Cannot use btoa because
// the code can contain characters outside the 0-255 code-point range supported by btoa
body: Base64.encode(JSON.stringify(blockObjects)) // Base64 encode the json
};
return blockToImage(topBlockId)
.then(createThumbnail)
.then(thumbnail => {
payload.thumbnail = thumbnail;
return payload;
});
};
const findTopBlock = payload => {
const blocks = payload.extensionURLs ? payload.blocks : payload;
return blocks.find(i => i.topLevel);
};
const placeInViewport = (payload, workspaceMetrics, isRtl) => {
const topBlock = findTopBlock(payload);
if (topBlock) {
const {scrollX, scrollY, scale} = workspaceMetrics || {
scrollX: 0,
scrollY: 0,
scale: BLOCKS_DEFAULT_SCALE
};
const posY = -scrollY + 30;
let posX;
if (isRtl) {
posX = scrollX + 30;
} else {
posX = -scrollX + 30;
}
topBlock.x = posX / scale;
topBlock.y = posY / scale;
}
return payload;
};
export {
codePayload as default,
findTopBlock,
placeInViewport
};

View File

@@ -0,0 +1,43 @@
import createThumbnail from './thumbnail';
import getCostumeUrl from '../get-costume-url';
const costumePayload = (costume, vm) => {
// TODO is it ok to base64 encode SVGs? What about unicode text inside them?
const assetDataUrl = vm.getExportedCostumeBase64(costume);
const assetDataFormat = costume.dataFormat;
const payload = {
type: 'costume',
name: costume.name,
// Params to be filled in below
mime: '',
body: '',
thumbnail: ''
};
switch (assetDataFormat) {
case 'svg':
payload.mime = 'image/svg+xml';
payload.body = assetDataUrl;
break;
case 'png':
payload.mime = 'image/png';
payload.body = assetDataUrl;
break;
case 'jpg':
payload.mime = 'image/jpeg';
payload.body = assetDataUrl;
break;
default:
alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line
}
// Do not generate the thumbnail from the raw asset. Instead use the getCostumeUrl
// utility which inlines the fonts to make the thumbnail show the right fonts.
const inlinedFontDataUrl = getCostumeUrl(costume.asset);
return createThumbnail(inlinedFontDataUrl).then(thumbnail => {
payload.thumbnail = thumbnail;
return payload;
});
};
export default costumePayload;

View File

@@ -0,0 +1,39 @@
// eslint-disable-next-line import/no-unresolved
import soundThumbnail from '!base64-loader!./sound-thumbnail.png';
const soundPayload = sound => {
const assetDataUrl = sound.asset.encodeDataURI();
const assetDataFormat = sound.dataFormat;
const payload = {
type: 'sound',
name: sound.name,
thumbnail: soundThumbnail,
// Params to be filled in below
mime: '',
body: ''
};
switch (assetDataFormat) {
case 'wav':
payload.mime = 'audio/x-wav';
payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', '');
break;
case 'mp3':
payload.mime = 'audio/mp3';
// TODO scratch-storage should be fixed so that encodeDataURI does not
// always prepend the wave format header; Once that is fixed, the following
// line will have to change.
payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', '');
break;
default:
alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line
}
// Return a promise to make it consistent with other payload constructors like costume-payload
return new Promise(resolve => resolve(payload));
};
export {
soundPayload as default,
soundThumbnail
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,29 @@
import createThumbnail from './thumbnail';
const spritePayload = (id, vm) => {
const target = vm.runtime.getTargetById(id);
if (!target) return null;
return vm.exportSprite(
id,
'base64'
).then(zippedSprite => {
const payload = {
type: 'sprite',
name: target.sprite.name,
mime: 'application/zip',
body: zippedSprite,
// Filled in below
thumbnail: ''
};
const costumeDataUrl = target.sprite.costumes[target.currentCostume].asset.encodeDataURI();
return createThumbnail(costumeDataUrl).then(thumbnail => {
payload.thumbnail = thumbnail;
return payload;
});
});
};
export default spritePayload;

View File

@@ -0,0 +1,37 @@
const jpegThumbnail = dataUrl => new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const maxDimension = 96; // 3x the maximum displayed size of 32px
// TW: After setting canvas width/height, the canvas is automatically cleared.
if (image.height < 1 || image.width < 1) {
canvas.width = canvas.height = maxDimension;
// drawImage can fail if image height/width is less than 1
// Use blank image; the costume is too small to render anyway
} else {
if (image.height > image.width) {
canvas.height = maxDimension;
canvas.width = (maxDimension / image.height) * image.width;
} else {
canvas.width = maxDimension;
canvas.height = (maxDimension / image.width) * image.height;
}
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
}
// TW: PNG allows using transparency while JPEG does not.
// A white background looks quite ugly in dark mode.
const dataURL = canvas.toDataURL('image/png').replace('data:image/png;base64,', '');
resolve(dataURL);
};
image.onerror = err => {
reject(err);
};
image.src = dataUrl;
});
export default jpegThumbnail;

View File

@@ -0,0 +1,362 @@
import LazyScratchBlocks from './tw-lazy-scratch-blocks';
/**
* Connect scratch blocks with the vm
* @param {VirtualMachine} vm - The scratch vm
* @return {ScratchBlocks} ScratchBlocks connected with the vm
*/
export default function (vm) {
const ScratchBlocks = LazyScratchBlocks.get();
const jsonForMenuBlock = function (name, menuOptionsFn, colors, start) {
return {
message0: '%1',
args0: [
{
type: 'field_dropdown',
name: name,
options: function () {
return start.concat(menuOptionsFn());
}
}
],
inputsInline: true,
output: 'String',
colour: colors.secondary,
colourSecondary: colors.secondary,
colourTertiary: colors.tertiary,
colourQuaternary: colors.quaternary,
outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND
};
};
const jsonForHatBlockMenu = function (hatName, name, menuOptionsFn, colors, start) {
return {
message0: hatName,
args0: [
{
type: 'field_dropdown',
name: name,
options: function () {
return start.concat(menuOptionsFn());
}
}
],
colour: colors.primary,
colourSecondary: colors.secondary,
colourTertiary: colors.tertiary,
colourQuaternary: colors.quaternary,
extensions: ['shape_hat']
};
};
const jsonForSensingMenus = function (menuOptionsFn) {
return {
message0: ScratchBlocks.Msg.SENSING_OF,
args0: [
{
type: 'field_dropdown',
name: 'PROPERTY',
options: function () {
return menuOptionsFn();
}
},
{
type: 'input_value',
name: 'OBJECT'
}
],
output: true,
colour: ScratchBlocks.Colours.sensing.primary,
colourSecondary: ScratchBlocks.Colours.sensing.secondary,
colourTertiary: ScratchBlocks.Colours.sensing.tertiary,
colourQuaternary: ScratchBlocks.Colours.sensing.quaternary,
outputShape: ScratchBlocks.OUTPUT_SHAPE_ROUND
};
};
const soundsMenu = function () {
let menu = [['', '']];
if (vm.editingTarget && vm.editingTarget.sprite.sounds.length > 0) {
menu = vm.editingTarget.sprite.sounds.map(sound => [sound.name, sound.name]);
}
menu.push([
ScratchBlocks.ScratchMsgs.translate('SOUND_RECORD', 'record...'),
ScratchBlocks.recordSoundCallback
]);
return menu;
};
const costumesMenu = function () {
if (vm.editingTarget && vm.editingTarget.getCostumes().length > 0) {
return vm.editingTarget.getCostumes().map(costume => [costume.name, costume.name]);
}
return [['', '']];
};
const backdropsMenu = function () {
const next = ScratchBlocks.ScratchMsgs.translate('LOOKS_NEXTBACKDROP', 'next backdrop');
const previous = ScratchBlocks.ScratchMsgs.translate('LOOKS_PREVIOUSBACKDROP', 'previous backdrop');
const random = ScratchBlocks.ScratchMsgs.translate('LOOKS_RANDOMBACKDROP', 'random backdrop');
if (vm.runtime.targets[0] && vm.runtime.targets[0].getCostumes().length > 0) {
return vm.runtime.targets[0].getCostumes().map(costume => [costume.name, costume.name])
.concat([[next, 'next backdrop'],
[previous, 'previous backdrop'],
[random, 'random backdrop']]);
}
return [['', '']];
};
const backdropNamesMenu = function () {
const stage = vm.runtime.getTargetForStage();
if (stage && stage.getCostumes().length > 0) {
return stage.getCostumes().map(costume => [costume.name, costume.name]);
}
return [['', '']];
};
const spriteMenu = function () {
const sprites = [];
for (const targetId in vm.runtime.targets) {
if (!Object.prototype.hasOwnProperty.call(vm.runtime.targets, targetId)) continue;
if (vm.runtime.targets[targetId].isOriginal) {
if (!vm.runtime.targets[targetId].isStage) {
if (vm.runtime.targets[targetId] === vm.editingTarget) {
continue;
}
sprites.push([vm.runtime.targets[targetId].sprite.name, vm.runtime.targets[targetId].sprite.name]);
}
}
}
return sprites;
};
const cloneMenu = function () {
if (vm.editingTarget && vm.editingTarget.isStage) {
const menu = spriteMenu();
if (menu.length === 0) {
return [['', '']]; // Empty menu matches Scratch 2 behavior
}
return menu;
}
const myself = ScratchBlocks.ScratchMsgs.translate('CONTROL_CREATECLONEOF_MYSELF', 'myself');
return [[myself, '_myself_']].concat(spriteMenu());
};
const soundColors = ScratchBlocks.Colours.sounds;
const looksColors = ScratchBlocks.Colours.looks;
const motionColors = ScratchBlocks.Colours.motion;
const sensingColors = ScratchBlocks.Colours.sensing;
const controlColors = ScratchBlocks.Colours.control;
const eventColors = ScratchBlocks.Colours.event;
ScratchBlocks.Blocks.sound_sounds_menu.init = function () {
const json = jsonForMenuBlock('SOUND_MENU', soundsMenu, soundColors, []);
this.jsonInit(json);
};
ScratchBlocks.Blocks.looks_costume.init = function () {
const json = jsonForMenuBlock('COSTUME', costumesMenu, looksColors, []);
this.jsonInit(json);
};
ScratchBlocks.Blocks.looks_backdrops.init = function () {
const json = jsonForMenuBlock('BACKDROP', backdropsMenu, looksColors, []);
this.jsonInit(json);
};
ScratchBlocks.Blocks.event_whenbackdropswitchesto.init = function () {
const json = jsonForHatBlockMenu(
ScratchBlocks.Msg.EVENT_WHENBACKDROPSWITCHESTO,
'BACKDROP', backdropNamesMenu, eventColors, []);
this.jsonInit(json);
};
ScratchBlocks.Blocks.motion_pointtowards_menu.init = function () {
const random = ScratchBlocks.ScratchMsgs.translate('MOTION_POINTTOWARDS_RANDOM', 'random direction');
const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_POINTTOWARDS_POINTER', 'mouse-pointer');
const json = jsonForMenuBlock('TOWARDS', spriteMenu, motionColors, [
[mouse, '_mouse_'],
[random, '_random_']
]);
this.jsonInit(json);
};
ScratchBlocks.Blocks.motion_goto_menu.init = function () {
const random = ScratchBlocks.ScratchMsgs.translate('MOTION_GOTO_RANDOM', 'random position');
const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_GOTO_POINTER', 'mouse-pointer');
const json = jsonForMenuBlock('TO', spriteMenu, motionColors, [
[random, '_random_'],
[mouse, '_mouse_']
]);
this.jsonInit(json);
};
ScratchBlocks.Blocks.motion_glideto_menu.init = function () {
const random = ScratchBlocks.ScratchMsgs.translate('MOTION_GLIDETO_RANDOM', 'random position');
const mouse = ScratchBlocks.ScratchMsgs.translate('MOTION_GLIDETO_POINTER', 'mouse-pointer');
const json = jsonForMenuBlock('TO', spriteMenu, motionColors, [
[random, '_random_'],
[mouse, '_mouse_']
]);
this.jsonInit(json);
};
ScratchBlocks.Blocks.sensing_of_object_menu.init = function () {
const stage = ScratchBlocks.ScratchMsgs.translate('SENSING_OF_STAGE', 'Stage');
const json = jsonForMenuBlock('OBJECT', spriteMenu, sensingColors, [
[stage, '_stage_']
]);
this.jsonInit(json);
};
ScratchBlocks.Blocks.sensing_of.init = function () {
const blockId = this.id;
const blockType = this.type;
// Get the sensing_of block from vm.
let defaultSensingOfBlock;
const blocks = vm.runtime.flyoutBlocks._blocks;
Object.keys(blocks).forEach(id => {
const block = blocks[id];
if (id === blockType || (block && block.opcode === blockType)) {
defaultSensingOfBlock = block;
}
});
// Function that fills in menu for the first input in the sensing block.
// Called every time it opens since it depends on the values in the other block input.
const menuFn = function () {
const stageOptions = [
[ScratchBlocks.Msg.SENSING_OF_BACKDROPNUMBER, 'backdrop #'],
[ScratchBlocks.Msg.SENSING_OF_BACKDROPNAME, 'backdrop name'],
[ScratchBlocks.Msg.SENSING_OF_VOLUME, 'volume']
];
const spriteOptions = [
[ScratchBlocks.Msg.SENSING_OF_XPOSITION, 'x position'],
[ScratchBlocks.Msg.SENSING_OF_YPOSITION, 'y position'],
[ScratchBlocks.Msg.SENSING_OF_DIRECTION, 'direction'],
[ScratchBlocks.Msg.SENSING_OF_COSTUMENUMBER, 'costume #'],
[ScratchBlocks.Msg.SENSING_OF_COSTUMENAME, 'costume name'],
[ScratchBlocks.Msg.SENSING_OF_SIZE, 'size'],
[ScratchBlocks.Msg.SENSING_OF_VOLUME, 'volume']
];
if (vm.editingTarget) {
let lookupBlocks = vm.editingTarget.blocks;
let sensingOfBlock = lookupBlocks.getBlock(blockId);
// The block doesn't exist, but should be in the flyout. Look there.
if (!sensingOfBlock) {
sensingOfBlock = vm.runtime.flyoutBlocks.getBlock(blockId) || defaultSensingOfBlock;
// If we still don't have a block, just return an empty list . This happens during
// scratch blocks construction.
if (!sensingOfBlock) {
return [['', '']];
}
// The block was in the flyout so look up future block info there.
lookupBlocks = vm.runtime.flyoutBlocks;
}
const sort = function (options) {
options.sort(ScratchBlocks.scratchBlocksUtils.compareStrings);
};
// Get all the stage variables (no lists) so we can add them to menu when the stage is selected.
const stageVariableOptions = vm.runtime.getTargetForStage().getAllVariableNamesInScopeByType('');
sort(stageVariableOptions);
const stageVariableMenuItems = stageVariableOptions.map(variable => [variable, variable]);
if (sensingOfBlock.inputs.OBJECT.shadow !== sensingOfBlock.inputs.OBJECT.block) {
// There's a block dropped on top of the menu. It'd be nice to evaluate it and
// return the correct list, but that is tricky. Scratch2 just returns stage options
// so just do that here too.
return stageOptions.concat(stageVariableMenuItems);
}
const menuBlock = lookupBlocks.getBlock(sensingOfBlock.inputs.OBJECT.shadow);
const selectedItem = menuBlock.fields.OBJECT.value;
if (selectedItem === '_stage_') {
return stageOptions.concat(stageVariableMenuItems);
}
// Get all the local variables (no lists) and add them to the menu.
const target = vm.runtime.getSpriteTargetByName(selectedItem);
let spriteVariableOptions = [];
// The target should exist, but there are ways for it not to (e.g. #4203).
if (target) {
spriteVariableOptions = target.getAllVariableNamesInScopeByType('', true);
sort(spriteVariableOptions);
}
const spriteVariableMenuItems = spriteVariableOptions.map(variable => [variable, variable]);
return spriteOptions.concat(spriteVariableMenuItems);
}
return [['', '']];
};
const json = jsonForSensingMenus(menuFn);
this.jsonInit(json);
};
ScratchBlocks.Blocks.sensing_distancetomenu.init = function () {
const mouse = ScratchBlocks.ScratchMsgs.translate('SENSING_DISTANCETO_POINTER', 'mouse-pointer');
const json = jsonForMenuBlock('DISTANCETOMENU', spriteMenu, sensingColors, [
[mouse, '_mouse_']
]);
this.jsonInit(json);
};
ScratchBlocks.Blocks.sensing_touchingobjectmenu.init = function () {
const mouse = ScratchBlocks.ScratchMsgs.translate('SENSING_TOUCHINGOBJECT_POINTER', 'mouse-pointer');
const edge = ScratchBlocks.ScratchMsgs.translate('SENSING_TOUCHINGOBJECT_EDGE', 'edge');
const json = jsonForMenuBlock('TOUCHINGOBJECTMENU', spriteMenu, sensingColors, [
[mouse, '_mouse_'],
[edge, '_edge_']
]);
this.jsonInit(json);
};
ScratchBlocks.Blocks.control_create_clone_of_menu.init = function () {
const json = jsonForMenuBlock('CLONE_OPTION', cloneMenu, controlColors, []);
this.jsonInit(json);
};
ScratchBlocks.VerticalFlyout.getCheckboxState = function (blockId) {
const monitoredBlock = vm.runtime.monitorBlocks._blocks[blockId];
return monitoredBlock ? monitoredBlock.isMonitored : false;
};
ScratchBlocks.FlyoutExtensionCategoryHeader.getExtensionState = function (extensionId) {
if (vm.getPeripheralIsConnected(extensionId)) {
return ScratchBlocks.StatusButtonState.READY;
}
return ScratchBlocks.StatusButtonState.NOT_READY;
};
ScratchBlocks.FieldNote.playNote_ = function (noteNum, extensionId) {
vm.runtime.emit('PLAY_NOTE', noteNum, extensionId);
};
// Use a collator's compare instead of localeCompare which internally
// creates a collator. Using this is a lot faster in browsers that create a
// collator for every localeCompare call.
const collator = new Intl.Collator([], {
sensitivity: 'base',
numeric: true
});
ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) {
return collator.compare(str1, str2);
};
// Blocks wants to know if 3D CSS transforms are supported. The cross
// section of browsers Scratch supports and browsers that support 3D CSS
// transforms will make the return always true.
//
// Shortcutting to true lets us skip an expensive style recalculation when
// first loading the Scratch editor.
ScratchBlocks.utils.is3dSupported = function () {
return true;
};
return ScratchBlocks;
}

View File

@@ -0,0 +1,27 @@
export default (bmpImage, type = 'image/bmp') => new Promise(resolve => {
// If the input is an ArrayBuffer, we need to convert it to a `Blob` and give it a URL so we can use it as an <img>
// `src`. If it's a data URI, we can use it as-is.
const imageUrl = bmpImage instanceof String ?
bmpImage :
window.URL.createObjectURL(new Blob([bmpImage], {type}));
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const image = document.createElement('img');
image.addEventListener('load', () => {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
ctx.drawImage(image, 0, 0);
const dataUrl = canvas.toDataURL('image/png');
// Revoke URL. If a blob URL was generated earlier, this allows the blob to be GC'd and prevents a memory leak.
window.URL.revokeObjectURL(imageUrl);
resolve(dataUrl);
});
image.setAttribute('src', imageUrl);
});

View File

@@ -0,0 +1,5 @@
// Legacy export format because this is used by some build-time scripts stuck in the past.
// eslint-disable-next-line import/no-commonjs
module.exports = {
APP_NAME: '001CODE'
};

View File

@@ -0,0 +1,225 @@
import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import bindAll from 'lodash.bindall';
import VM from 'scratch-vm';
import CloudProvider from '../lib/cloud-provider';
import {
getIsShowingWithId
} from '../reducers/project-state';
import {
showAlertWithTimeout
} from '../reducers/alerts';
import {openUsernameModal} from '../reducers/modals';
import {setUsernameInvalid, setCloudHost} from '../reducers/tw';
/**
* TW: Our scratch-vm has an alternative fix to the cloud variable and video sensing privacy concerns.
*/
const DISABLE_WITH_VIDEO_SENSING = false;
/*
* Higher Order Component to manage the connection to the cloud server.
* @param {React.Component} WrappedComponent component to manage VM events for
* @returns {React.Component} connected component with vm events bound to redux
*/
const cloudManagerHOC = function (WrappedComponent) {
class CloudManager extends React.Component {
constructor (props) {
super(props);
this.cloudProvider = null;
bindAll(this, [
'handleCloudDataUpdate',
'handleExtensionAdded'
]);
this.props.vm.on('HAS_CLOUD_DATA_UPDATE', this.handleCloudDataUpdate);
this.props.vm.on('EXTENSION_ADDED', this.handleExtensionAdded);
}
componentDidMount () {
if (this.shouldConnect(this.props)) {
this.connectToCloud();
}
}
componentWillReceiveProps (nextProps) {
if (this.props.reduxCloudHost !== nextProps.cloudHost) {
this.props.onSetReduxCloudHost(nextProps.cloudHost);
}
}
componentDidUpdate (prevProps) {
// TODO need to add cloud provider disconnection logic and cloud data clearing logic
// when loading a new project e.g. via file upload
// (and eventually move it out of the vm.clear function)
if (this.shouldReconnect(this.props, prevProps)) {
this.disconnectFromCloud();
if (this.shouldConnect(this.props)) {
this.connectToCloud();
}
return;
}
if (this.shouldConnect(this.props) && !this.shouldConnect(prevProps)) {
this.connectToCloud();
}
if (this.shouldDisconnect(this.props, prevProps)) {
this.disconnectFromCloud();
}
}
componentWillUnmount () {
this.props.vm.off('HAS_CLOUD_DATA_UPDATE', this.handleCloudDataUpdate);
this.props.vm.off('EXTENSION_ADDED', this.handleExtensionAdded);
this.disconnectFromCloud();
}
canUseCloud (props) {
return !!(
props.reduxCloudHost &&
props.username &&
props.vm &&
props.projectId &&
props.hasCloudPermission
);
}
shouldConnect (props) {
return !this.isConnected() && this.canUseCloud(props) &&
props.isShowingWithId && props.vm.runtime.hasCloudData() &&
props.canModifyCloudData;
}
shouldDisconnect (props, prevProps) {
return this.isConnected() &&
( // Can no longer use cloud or cloud provider info is now stale
!this.canUseCloud(props) ||
!props.vm.runtime.hasCloudData() ||
(props.projectId !== prevProps.projectId) ||
// tw: username changes are handled in "reconnect"
// (props.username !== prevProps.username) ||
// Editing someone else's project
!props.canModifyCloudData
);
}
shouldReconnect (props, prevProps) {
return this.isConnected() && (
props.username !== prevProps.username ||
props.reduxCloudHost !== prevProps.reduxCloudHost
);
}
isConnected () {
return this.cloudProvider && !!this.cloudProvider.connection;
}
connectToCloud () {
this.cloudProvider = new CloudProvider(
this.props.reduxCloudHost,
this.props.vm,
this.props.username,
this.props.projectId);
this.cloudProvider.onInvalidUsername = this.props.onInvalidUsername;
this.props.vm.setCloudProvider(this.cloudProvider);
}
disconnectFromCloud () {
if (this.cloudProvider) {
this.cloudProvider.requestCloseConnection();
this.cloudProvider = null;
this.props.vm.setCloudProvider(null);
}
}
handleCloudDataUpdate (projectHasCloudData) {
if (this.isConnected() && !projectHasCloudData) {
this.disconnectFromCloud();
} else if (this.shouldConnect(this.props)) {
this.props.onShowCloudInfo();
this.connectToCloud();
}
}
handleExtensionAdded (categoryInfo) {
// Note that props.vm.extensionManager.isExtensionLoaded('videoSensing') is still false
// at the point of this callback, so it is difficult to reuse the canModifyCloudData logic.
if (DISABLE_WITH_VIDEO_SENSING && categoryInfo.id === 'videoSensing' && this.isConnected()) {
this.disconnectFromCloud();
}
}
render () {
const {
/* eslint-disable no-unused-vars */
canModifyCloudData,
cloudHost,
reduxCloudHost,
onSetReduxCloudHost,
projectId,
username,
hasCloudPermission,
isShowingWithId,
onShowCloudInfo,
onInvalidUsername,
/* eslint-enable no-unused-vars */
vm,
...componentProps
} = this.props;
return (
<WrappedComponent
canUseCloud={this.canUseCloud(this.props)}
vm={vm}
{...componentProps}
/>
);
}
}
CloudManager.propTypes = {
canModifyCloudData: PropTypes.bool.isRequired,
cloudHost: PropTypes.string,
reduxCloudHost: PropTypes.string,
onSetReduxCloudHost: PropTypes.func,
hasCloudPermission: PropTypes.bool,
isShowingWithId: PropTypes.bool.isRequired,
onInvalidUsername: PropTypes.func,
onShowCloudInfo: PropTypes.func,
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
username: PropTypes.string,
vm: PropTypes.instanceOf(VM).isRequired
};
CloudManager.defaultProps = {
cloudHost: null,
onShowCloudInfo: () => {},
username: null
};
const mapStateToProps = (state, ownProps) => {
const loadingState = state.scratchGui.projectState.loadingState;
return {
reduxCloudHost: state.scratchGui.tw.cloudHost,
isShowingWithId: getIsShowingWithId(loadingState),
projectId: state.scratchGui.projectState.projectId,
// if you're editing someone else's project, you can't modify cloud data
canModifyCloudData: (!state.scratchGui.mode.hasEverEnteredEditor || ownProps.canSave) &&
// possible security concern if the program attempts to encode webcam data over cloud variables
!(DISABLE_WITH_VIDEO_SENSING && ownProps.vm.extensionManager.isExtensionLoaded('videoSensing'))
};
};
const mapDispatchToProps = dispatch => ({
onSetReduxCloudHost: cloudHost => dispatch(setCloudHost(cloudHost)),
onShowCloudInfo: () => showAlertWithTimeout(dispatch, 'cloudInfo'),
onInvalidUsername: () => {
dispatch(setUsernameInvalid(true));
dispatch(openUsernameModal());
}
});
// Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
{}, stateProps, dispatchProps, ownProps
);
return connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(CloudManager);
};
export default cloudManagerHOC;

View File

@@ -0,0 +1,259 @@
import log from './log.js';
import throttle from 'lodash.throttle';
const anonymizeUsername = username => {
if (/^player\d{2,7}$/i.test(username)) {
return 'player';
}
return username;
};
class CloudProvider {
/**
* A cloud data provider which creates and manages a web socket connection
* to the Scratch cloud data server. This provider is responsible for
* interfacing with the VM's cloud io device.
* @param {string} cloudHost The url for the cloud data server
* @param {VirtualMachine} vm The Scratch virtual machine to interface with
* @param {string} username The username to associate cloud data updates with
* @param {string} projectId The id associated with the project containing
* cloud data.
*/
constructor (cloudHost, vm, username, projectId) {
this.vm = vm;
this.username = anonymizeUsername(username);
this.projectId = projectId;
this.cloudHost = cloudHost;
this.connectionAttempts = 0;
// A queue of messages to send which were received before the
// connection was ready
this.queuedData = [];
this.openConnection();
// Send a message to the cloud server at a rate of no more
// than 10 messages/sec.
// tw: we let cloud variables change at a greater rate
this.sendCloudData = throttle(this._sendCloudData, 50);
}
/**
* Open a new websocket connection to the clouddata server.
* @param {string} cloudHost The cloud data server to connect to.
*/
openConnection () {
this.connectionAttempts += 1;
try {
// tw: only add ws:// or wss:// if it not already present in the cloudHost
if (!this.cloudHost || (!this.cloudHost.includes('ws://') && !this.cloudHost.includes('wss://'))) {
this.cloudHost = (location.protocol === 'http:' ? 'ws://' : 'wss://') + this.cloudHost;
}
this.connection = new WebSocket(this.cloudHost);
} catch (e) {
log.warn('Websocket support is not available in this browser', e);
this.connection = null;
return;
}
this.connection.onerror = this.onError.bind(this);
this.connection.onmessage = this.onMessage.bind(this);
this.connection.onopen = this.onOpen.bind(this);
this.connection.onclose = this.onClose.bind(this);
}
onError (event) {
log.error(`Websocket connection error: ${JSON.stringify(event)}`);
// Error is always followed by close, which handles reconnect logic.
}
onMessage (event) {
const messageString = event.data;
// Multiple commands can be received, newline separated
messageString.split('\n').forEach(message => {
if (message) { // .split can also contain '' in the array it returns
const parsedData = this.parseMessage(JSON.parse(message));
this.vm.postIOData('cloud', parsedData);
}
});
}
onOpen () {
// Reset connection attempts to 1 to make sure any subsequent reconnects
// use connectionAttempts=1 to calculate timeout
this.connectionAttempts = 1;
this.writeToServer('handshake');
log.info(`Successfully connected to clouddata server.`);
// Go through the queued data and send off messages that we weren't
// ready to send before
this.queuedData.forEach(data => {
this.sendCloudData(data);
});
// Reset the queue
this.queuedData = [];
}
onClose (e) {
// tw: code 4002 is "Username Error" -- do not try to reconnect
if (e && e.code === 4002) {
log.info('Cloud username is invalid. Not reconnecting.');
this.onInvalidUsername();
return;
}
// tw: code 4004 is "Project Unavailable" -- do not try to reconnect
if (e && e.code === 4004) {
log.info('Cloud variables are disabled for this project. Not reconnecting.');
return;
}
log.info(`Closed connection to websocket`);
const randomizedTimeout = this.randomizeDuration(this.exponentialTimeout());
this.setTimeout(this.openConnection.bind(this), randomizedTimeout);
}
// tw: method called when username is invalid
onInvalidUsername () { /* no-op */ }
exponentialTimeout () {
return (Math.pow(2, Math.min(this.connectionAttempts, 5)) - 1) * 1000;
}
randomizeDuration (t) {
return Math.random() * t;
}
setTimeout (fn, time) {
log.info(`Reconnecting in ${(time / 1000).toFixed(1)}s, attempt ${this.connectionAttempts}`);
this._connectionTimeout = window.setTimeout(fn, time);
}
parseMessage (message) {
const varData = {};
switch (message.method) {
case 'set': {
varData.varUpdate = {
name: message.name,
value: message.value
};
break;
}
}
return varData;
}
/**
* Format and send a message to the cloud data server.
* @param {string} methodName The message method, indicating the action to perform.
* @param {string} dataName The name of the cloud variable this message pertains to
* @param {string | number} dataValue The value to set the cloud variable to
* @param {string} dataNewName The new name for the cloud variable (if renaming)
*/
writeToServer (methodName, dataName, dataValue, dataNewName) {
const msg = {};
msg.method = methodName;
msg.user = this.username;
msg.project_id = this.projectId;
// Optional string params can use simple falsey undefined check
if (dataName) msg.name = dataName;
if (dataNewName) msg.new_name = dataNewName;
// Optional number params need different undefined check
if (typeof dataValue !== 'undefined' && dataValue !== null) msg.value = dataValue;
const dataToWrite = JSON.stringify(msg);
if (this.connection && this.connection.readyState === WebSocket.OPEN) {
this.sendCloudData(dataToWrite);
} else if (msg.method === 'create' || msg.method === 'delete' || msg.method === 'rename') {
// Save data for sending when connection is open, iff the data
// is a create, rename, or delete
this.queuedData.push(dataToWrite);
}
}
/**
* Send a formatted message to the cloud data server.
* @param {string} data The formatted message to send.
*/
_sendCloudData (data) {
this.connection.send(`${data}\n`);
}
/**
* Provides an API for the VM's cloud IO device to create
* a new cloud variable on the server.
* @param {string} name The name of the variable to create
* @param {string | number} value The value of the new cloud variable.
*/
createVariable (name, value) {
this.writeToServer('create', name, value);
}
/**
* Provides an API for the VM's cloud IO device to update
* a cloud variable on the server.
* @param {string} name The name of the variable to update
* @param {string | number} value The new value for the variable
*/
updateVariable (name, value) {
this.writeToServer('set', name, value);
}
/**
* Provides an API for the VM's cloud IO device to rename
* a cloud variable on the server.
* @param {string} oldName The old name of the variable to rename
* @param {string} newName The new name for the cloud variable.
*/
renameVariable (oldName, newName) {
this.writeToServer('rename', oldName, null, newName);
}
/**
* Provides an API for the VM's cloud IO device to delete
* a cloud variable on the server.
* @param {string} name The name of the variable to delete
*/
deleteVariable (name) {
this.writeToServer('delete', name);
}
/**
* Closes the connection to the web socket and clears the cloud
* provider of references related to the cloud data project.
*/
requestCloseConnection () {
if (this.connection &&
this.connection.readyState !== WebSocket.CLOSING &&
this.connection.readyState !== WebSocket.CLOSED) {
log.info('Request close cloud connection without reconnecting');
// Remove listeners, after this point we do not want to react to connection updates
this.connection.onclose = () => {};
this.connection.onerror = () => {};
this.connection.close();
}
this.clear();
}
/**
* Clear this provider of references related to the project
* and current state.
*/
clear () {
this.connection = null;
this.vm = null;
this.username = null;
this.projectId = null;
if (this._connectionTimeout) {
clearTimeout(this._connectionTimeout);
this._connectionTimeout = null;
}
this.connectionAttempts = 0;
}
}
export default CloudProvider;

View File

@@ -0,0 +1,39 @@
/**
* Report a telemetry event.
* @param {string} event - one of `projectWasCreated`, `projectDidLoad`, `projectDidSave`, `projectWasUploaded`
*/
// TODO make a telemetry HOC and move this stuff there
const collectMetadata = function (vm, projectName = '', locale = '') {
// TODO move most or all of this into a collectMetadata() method on the VM/Runtime
const metadata = {
projectName: projectName,
language: locale,
spriteCount: 0,
blocksCount: 0,
costumesCount: 0,
listsCount: 0,
scriptCount: 0,
soundsCount: 0,
variablesCount: 0
};
for (const target of vm.runtime.targets) {
++metadata.spriteCount;
metadata.blocksCount += Object.keys(target.sprite.blocks._blocks).length;
metadata.costumesCount += target.sprite.costumes_.length;
metadata.scriptCount += target.sprite.blocks._scripts.length;
metadata.soundsCount += target.sprite.sounds.length;
for (const variableName in target.variables) {
const variable = target.variables[variableName];
if (variable.type === 'list') {
++metadata.listsCount;
} else {
++metadata.variablesCount;
}
}
}
return metadata;
};
export default collectMetadata;

View File

@@ -0,0 +1,10 @@
import {IntlProvider as ReactIntlProvider} from 'react-intl';
import {connect} from 'react-redux';
const mapStateToProps = state => ({
key: state.locales.locale,
locale: state.locales.locale,
messages: state.locales.messages
});
export default connect(mapStateToProps)(ReactIntlProvider);

View File

@@ -0,0 +1,17 @@
/**
* Utility to convert data URIs to blobs
* Adapted from https://stackoverflow.com/questions/12168909/blob-from-dataurl
* @param {string} dataURI the data uri to blobify
* @return {Blob} a blob representing the data uri
*/
export default function dataURItoBlob (dataURI) {
const byteString = atob(dataURI.split(',')[1]);
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
const arrayBuffer = new ArrayBuffer(byteString.length);
const uintArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
uintArray[i] = byteString.charCodeAt(i);
}
const blob = new Blob([arrayBuffer], {type: mimeString});
return blob;
}

View File

@@ -0,0 +1 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="61.49876" height="117.72954" viewBox="0,0,61.49876,117.72954"><g transform="translate(-209.25062,-121.13523)"><g data-paper-data="{&quot;isPaintingLayer&quot;:true}" fill-rule="nonzero" stroke="#000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" style="mix-blend-mode: normal"><path d="M212.45298,121.45117c4.27378,-1.68745 3.99111,1.73524 23.39137,52.09733c11.02954,29.4711 19.76067,53.61865 16.55011,54.62697c-4.68511,1.80854 -12.36182,-22.62622 -23.39136,-52.09733c-16.89172,-48.16374 -21.64214,-52.78237 -16.55012,-54.62697z" data-paper-data="{&quot;index&quot;:null}" fill="#d99e82" stroke-width="0"/><path d="M231.66366,188.60508c-9.14277,0 -16.55443,-7.24756 -16.55443,-16.18792c0,-0.78587 -6.31352,-9.26285 -5.83245,-12.90786c0.40452,-2.76293 9.55433,-0.3003 11.53093,-0.54877c4.86102,-1.00187 5.4542,-2.73129 10.85596,-2.73129c1.57876,0 3.53694,-7.03389 6.07355,-5.48884c4.69159,2.85764 10.48089,15.87384 10.48089,21.67674c0,8.94036 -7.41168,16.18792 -16.55443,16.18792z" data-paper-data="{&quot;index&quot;:null}" fill="#fcb1e3" stroke-width="0"/><path d="M254.49773,207.54343c-3.95019,2.74614 -8.14073,4.33933 -12.55592,4.76162c-4.55084,0.43525 -8.46053,0.76116 -12.24363,-2.3874c-7.45344,-6.20327 -8.34307,-19.21037 -2.19171,-24.55014c1.44594,-1.25547 3.26814,-2.45373 4.53075,-3.07615c1.76698,-0.87105 1.91986,-1.00055 5.71606,-1.94996c6.04389,-0.9383 9.92917,-0.90806 14.55274,2.94c7.45344,6.20327 8.43469,16.90662 2.19171,24.26203z" data-paper-data="{&quot;index&quot;:null}" fill="#ffd983" stroke-width="0"/><path d="M232.32721,232.90573c-2.56114,-5.68425 -1.68892,-8.14742 -0.86406,-12.66379c1.34684,-6.26958 3.91259,-8.71043 9.84623,-10.86565c2.22542,-0.80831 7.74255,-2.38249 10.8511,-2.73285c3.62404,-0.35301 6.7831,0.58103 9.40006,2.45432c2.34915,1.68157 3.03827,2.18205 6.332,6.90816c2.62231,4.47889 4.4898,11.43587 0.7492,16.90554c-3.6011,5.26566 -11.02377,5.95331 -18.58348,5.95331c-8.33056,0 -15.04386,-0.62903 -17.73105,-5.95905z" data-paper-data="{&quot;index&quot;:null}" fill="#a6d388" stroke-width="0"/><path d="M231.87007,170.77208c-1.41148,0 -2.55572,-1.14423 -2.55572,-2.55573c0,-1.41149 1.14423,-2.55572 2.55572,-2.55572c1.4115,0 2.55572,1.14422 2.55572,2.55572c0,1.4115 -1.14422,2.55573 -2.55572,2.55573z" data-paper-data="{&quot;index&quot;:null}" fill="#000000" stroke-width="1.5"/><path d="M243.83381,164.03088z" data-paper-data="{&quot;index&quot;:null}" fill="#000000" stroke-width="0"/><path d="M245.61169,164.9569c0,1.37058 -0.22211,1.74084 -1.59269,1.74084c-0.68528,0 -0.93531,-0.35186 -1.3844,-0.80094c-0.44908,-0.44908 -0.80093,-0.99542 -0.80093,-1.68071c0,-1.37058 -0.07421,-1.74085 1.29638,-1.74085c1.37058,0 2.48163,1.11107 2.48163,2.48165z" data-paper-data="{&quot;index&quot;:null}" fill="#000000" stroke-width="1.5"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,3 @@
`dango-cat.svg` was created by https://scratch.mit.edu/users/littlebunny06/ 🍡🐱
If `default-project.sb3` is replaced with a non-empty file, it will be used instead of the costumes.

View File

@@ -0,0 +1,3 @@
<svg version="1.1" width="2" height="2" viewBox="-1 -1 2 2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Exported by Scratch - http://scratch.mit.edu/ -->
</svg>

After

Width:  |  Height:  |  Size: 202 B

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"> <image id="image0" width="64" height="64" x="0" y="0"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo
AAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxIAAAsSAdLd
fvwAAAAHdElNRQfoChwNJx5Ort+cAAARXklEQVR42s2beXDdV3XHP+f+fm/Xk/S0W9632I6XxAuQ
EOMkxLGzQmfSkhmXDBRoCzSFAtNlOnTaKcOwBEihDTNMSssQKKWlEEMoWUoWCCEkISvEiS3LsqRI
1i69Re+933JP/3iSIjmyrS0OZ+b98d697/7O+d7vOffc+zsXzpO4VXUk11yMuFGqd+wXcaNvx5hH
MOZBcaOXL7vpkyJulPSO/bjphvOl1usvkbrlAJhoHACJxNaJcW5DpA9QQBHpFeN8ViKxtZW+CQCi
9SveaPUXLsl1uxA3holXVQCIJevEuLci8tKU4ad/RF4Ux/2QiSVrQTDxKkwkTnLtrjfanLlL8w0f
x0mkcavqEMfFqcpExYm8AzEPA+GksWJcrdr8Nq3adJmKcaYDESDmQXEi17vp+og4Lm66ASeRfqNN
O7e41Q18SBVxowCIG92NMXeB5KcZqPHlm3XFe76k2+44odv+pV1X3PIFjbdecBojJIcx3xA3unNi
rMozljg+yFIMEmteR7mvHRNNYL0iJhJfpaH/J6r2fagumwKopom6vX9I/RXvIda4BlVbUUIM5f4T
DD307wz/4jsE2YFpGkqPiPlXcSJ3Wr/UXXlGiVjTGsr9Jxatu7OYP6cv3Ecw1o/6JTT0ETdaDXqL
hv6XUfv7QBoqQa32Te9k+aHPUvfWm3FSGZgwHgBVnFSG9IWXU3XBpdhSHm+gAw0DgDSql6uGV4nj
lsWNHNcw8LABYhyqtl6B19d+/gFwkjWE41lsKY+TqHHVL+/XwLsdG/45UJl1MaQuuITWm/+Rpmtu
Jda4GnSS5adL5bdo/UqqdxwgvmIL/kgv/kjPZIcW1F6nYbhbjNPnpGpP2lJObXkcMQb1ywuyY94u
4NY0EYz1I5EY6pcRN7pdbfARrL4LtHqyX6x5PQ1XvZ/Mpe/CTTeg1p7B8NnVEmPwswOMPPZdhh78
+ml0lzEx5j8xzj9r4P12UpdIbQv+6Kn5TeRcO8aa12FLObAhGvoYN7IM1Y+qDb+I6pVADCoJT/0V
76X10KepvuhgZU2fTve5iiomliS14c2kt70dRCj3taN+CSCO6h7UXi/GiYvjHNMwKEw+J75sI0Fu
aGkAqNl9I/5wN+qXsEEZ48ZSqL1ZQ/8rqH03UAMgkRg1O69l+aHPUL/vlkq0Xojhr0UCt7qB6q1X
ktrwJsLCKN5gJ9gQoBbV/Wrt5eI4eeNG2zXwfA08xHHJvOUmil2/WTgATqqWcHyMsDBGpK7V2PHc
PhuUv4ANPwGsnOyXXLeL1j/4e5qu/xjxlvW8upotkaiCCLHGNVRffJBYy0b8oe5pdNflqL1Bw2C7
OM4rseZ13cFYv9ryOKhOsmZWmTUGTPqSicSxfglxY5uwwa2q9hCqdZP9og2rqL/ivdTtPUSktmWe
fr5AEUHE4I/0MvzzbzP0yDfwhrqntw+JmG/huHeoXz42aUMkswx/pPe1kzz9S2LVNsL8CDrh5+JG
GlD9oNrgn1B7DZCAygpQd9khlh/6DDW7b6ykuwuguxFFBHS+sVgVk0iT2nQp6QsvBxvg9bejgQeQ
RPUS1F4jxnHEcY9pGBQnWZRYvoUg2/9aBrjpRlKbLyf76x9g4lVxWy7coNZ+DLWXTvYTJ0J66xU0
HvwwqU2XIW4E7ML8PFRhb/1JQhV+ObwKRxbGHDEONvDIH/k5A/fdQf7IzybzBwCLmMfEmC+ZWNX/
2lKu/LdhwOfqVhKMdJ8OQBNBrh9xo7s1DP4K1RtBE9PZ0XD1B6nd/Q6cZDVaCUILlqgJuW3bTwjU
8Ne/uYZAzaLGE+MQjo8y+uRhBh74GqXuF6e3jiPyQ3Hcz2vgPeNmVkwB8KoLOFGcZK1jS7nbUfsu
IAIQySyj8eCHab35U6Q3763M+iKje6jCztoePrD617Qmcjw1upyeUjVmMYm5KhKJk1yzk+odV2Ni
Kbz+dmwpT8UW3YZqOtqy+W7rF1XL+ZkASCQO4KhfPAS6CSC9/QAr/+grZC65CSdRvUTLGrhG+cCa
J7motoeYE+Jbl8eGVrMkWxNVnGQtVVveRtXmvXjDPXj9E6myyFGJVX2P0J8CYDbeTTijEFn+Vqzb
gi17LFV0typsSA2xr6EDVEDhioZ21qWGsbokezNQi/UDqN1IdOP+swJ7ZscTsJ5H/mQ3I0faKPUP
oqEFWZySIso1zUdpiBUmFBOa4nkONh9b7NATtiulbIFszwCFoRFsEJy1/zkij4BAkC+QbTvJ2LF2
vLHcVNN8xaqwIpHl6qa217QdaDrGsnh2USzwi2VyfUPkB0YIPH9OSs4t9IqgqpSHRhl76Ti5E12E
xfKC2HBVYxsrE2MV+k+KCmuSo7y94fiCHC30AwoDI2R7B/HyxYkd59xkfmuPCDYIGO/pZ+RIG+O9
fWgQzgkIRWiIFbiu+SjMtuaLcl3Ly9RHx+eWGAloaCmO5sj2DFAcy1cy0XnOycIWX4FwvEjuRBej
Lx+nPDw6la+fSawKb6vv4IKqwZmzP4WQsCU9yFvrOs/tBqp4+SLZU4MUBkcJ/bP7+dIDAEzksHij
WcaOtpM93klQGJ9dX6DaLXFDy0sYc+YEyjEBNy47QpVbPqMrBGWfXP8Iub4h/OLCDkGWBoBpQGho
KfYNMHqkjUL3Kazvz2BDYB32ZF5hR80pOFvGp4adNb3squ0hsDM3qjYMGR/Oku0doJwroHZplmV3
SUaZACIse+Q7X6E8PEqytZlIJkM0omxJ9/Lulc8Sc4LZ6T9N4q7PLSufYchLcjTfgBcagkKB0miO
oOwtmbpLD8CEKFDKjmNKbWxdU+bmXcNcvaafTKx4TuMrAwiX1HXx1arDPHyqgf860sqTg02U/QjG
CLLE2+0lA0ARrEJNpMyeTA8HW9q4uKaX2lGL7anBX5YhEo/MIaEUNCyR9ju4PnWKy7a5PN6ykR90
7ubxgQ2M+cnKNnqJgFg0AJMRuzFe4LL6Lg40H2dz9QBxE2JV8DwY6h4hN1wg05oh3ViNcZ1Z1mpB
NSAsDeIXe7BBJaCmIwEHW59nX/NLPDe8iru7dvPwqS30lyrnr2aB2+hFA2BVMKKsSo1xZeMJrmo6
wZrUKK5YQhXC0+juFTz6j/eTH8qTWZ4hWZMEIxNuYQm9UfzxHkJvFLBMLuhKZayoCbiksY3d9Sc4
ml3GPd0Xc1/PDk4W6lE1GFnYRm1BALhi2Vg9xP6mdvY2dLIsnkOkAkp4Jj8XUFXyw3mKuSLVDdXU
tmZwoyFBsYegNIjqZPo6+xihGowoF9Z2s6XmFW5e8zgP9G7nx90Xc2SslXABZwrzBkCBuBNweeNJ
DjQfpy5axCJzzuFFBBtYRnpHKeZy1LdmERk/q+Gni50AYm16gN9zn6IcunTkG8gF8XlvUeYNgAD5
IMqd7bt4sH8t+5va2dfYQWs8j4jOAwgIyh42KONE5q62Ixarho58A/f17OCe7os5lm0hVLOg04QF
x4BQDS9lGziaq+dwzyauaOzgquZ21k2LA3NCc47djFh86/Li2HLu6drJ/b3b6SzUoyoL9v9FAQCv
RuCu8RruOnkR957ayKUNXRxsbmNLepC4E2B14QuWoBhRxoMoz46s5u7O3TzSt4WBUjVMtMkbtQrM
BsSgl+TwK5t4qH8NuzO9XNPSxs7aXtKuNy8gJg0f9ZL8cmAjd3dVcoCsn8CILmrGXxcApivuSCVG
PNS/hseHVrC1up+DLcfZ13CSKtc7JwiCMuYnufeVHRzu2s3zIysphlGMKM4SGv66APCqEeCI4lmH
p0ZaeXZ0GUPrEtyy6jnm4vTfOP427njpakI1OGJfF8MnZfG7wXOII0qowv196+kvV501hTWidI/X
cU/XTqzK62r4eQNg0rDOQi2PDq4669m/oNzfs52ThYZFp7i/UwBA5WXIA33rGfVnT1YEZaBUzY+6
dy4oo/udB8CI8nKunieHW2edXSPKz/o28dJY63mh/nkHAKBsHe49tYHxMDLjdxEl6ye4u2sPnn1d
4vLvBgBGlOfGWnh+rGnG22CD8sTgOp4eXr2ka/ySACCydBgJUAgi3HdqA551pmJBKYxwd+cexoPY
0hQuTtf9HEf2Z7ZOlVL7w/gDL1dUX4r3VlSOAJ4YXsHRXN1UcvPC6EoeG9i4dJFfDCB4PS9QfPE+
znYMNdPhKjW7UyePpY5f4A+8TGLzdSQ3XYtJNZ6lzm+OuqGMeHEe6F/Ptvo+AutwuGs3I15qCYKf
gDGEYz0Unvk+4899nzA3ML2DJ5GYTq8Zmjp7NrEUYX5AxbgngDrQdYCr/jhe73N4Pc8hTgS3ehni
xhcFAiIMlZPsbeqkEI3z5ZcPVui/GJIZBy3nKL7wY8bu/xzFI/ej3tR7iiJivi/GuS3M9fc4iVps
KXsaANEksZVvJhjp6JFI4keofRFYMfERWxzG63oCf/g4TqIWp6oJjLMgICpnCjHqEkWOByt5eGDL
wukvBmxAueNXjP3fF8g/+R/Y/NSsKyKPYdy/NLHkbRp6XevvtIzdf/sUADMwd+vWEo52gXHRoIS4
sUYNg/eg9kMTjJgAq4r4+itJbn0nbmb1xKPmZ4Cq0JQaRzIZBv2a+Z/yTtDFH2ij8OR3KL54L3ai
6GGivQ0xXxUncpf6pUGJJiAMcBs34J86MmMyXiNOqoGwMIg4UTT0ECeyWW14K6qHQDNT/dItJLfc
QGLjAUyyfqKCZO6GqOOoU5tBnPkUx1T83Ob6KTx3mMIz/004Nr38TYYR+ZY47h0aeEfFjaFBGSfd
RJjrn220M4uJpZFIEjs+hEk1GlsY2KdqP4HaA0B0sl+kaTOpbTcRW31pJT7MtZTGcdTJZMDMEQDj
oOUCpaMPkX/iW3i9v53e6iHmJ2KcLzmZlY+GI13WqW7BFsem6D6rCmedodAjtnwPYa4Xtb5q6HeI
G/8h2GPAaiaqwm1hkHLnrwhGOnCS9ThVjSBziA/GYBKJyknpWSfdgFq8rqfJ/vR28r/6JmF2WlG0
yFMY928kGv+0Bl4b1leA2Nq34PecvVR2ztRza1YQZHsqbhGUECfaqjZ8P2r/GHSqbNbEa0hs3E9y
y404NSsqIJwpPpyLAVI5NAyGOij8+ruM/+YebHFsensnYr4mxv03DcqnJJJAQx83s4JgqGNOds25
WtyWs4BiYukKAMbksMHPxIn8lEql+AYgpkEZv/8IXvdTlYqt6lYkkmRWNpyRAQLGwY4PU3j6e4w9
cBul44+iweTrcMki5pti3I9ig/8R4+SxIU6yFi3nscXRuZq18MxToinEiWLLWUys2rXl3H40/PhE
6bw7OYPR5u2ktt9EdOUexInNjA+zMcA4qF+k1PYo+Sfuwut+djqDAsT8VIz5oklkHgrHRwInlUH9
8ln9fEkY8BoJfSINGypoq7WEXptEYj8E7QDWAk0AYb6PcufjhGPdOFWNOMn6iVRVZzJg4jfvlRfI
PvRl8r/8OuHIjCLo5zHO30kk/g8aekcQsSIQadpEMLTwu0NLkuA76RbC3CkmlxxxoqvUBn+K6vtA
W6YmN1lH8oKDJDZfj5NuQYxRk8mA60o40kXh6e9ReP4wtjD9soP0YMzXxbh3alDukkgc9Uu4dasI
hjuXQv2lE5OoJXPgU4hT2e+Lcfcg5ttAgWlX4tzMGq3Z+zFtft+P7LK/eMTWXvtJdRvWnX5tLo+Y
b4oT2QVMjWlSdQvQ7DxK6qKbkWgSE68GcTCxdAzjvhORR5h2cRLjaHT5Lo2t2qPMvDgZIuYhMe6N
JlETxTiYZAaJpd5o0+YnkcZNiBNBIpWCc3HjdRjnI4i8zIyZnnF19gjG+TOJJDIAEk0iTpRo6/Y3
2pyFi1PVVAHAmbhN6kTWI+aLMO3yNHIKMZ8XN7quAlas8t/q5jda/aUTE0vj1q1FjEu0eauIca9C
zM8R87A4kSsTW68VcSJEl+/AJGrOm17/D+V6zZQnRNw7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI0
LTEwLTI4VDEzOjM5OjMwKzAwOjAwWcgGrAAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNC0xMC0yOFQx
MzozOTozMCswMDowMCiVvhAAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjQtMTAtMjhUMTM6Mzk6
MzArMDA6MDB/gJ/PAAAAAElFTkSuQmCC" />
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,47 @@
import projectData from './project-data';
/* eslint-disable import/no-unresolved */
import overrideDefaultProject from '!arraybuffer-loader!./override-default-project.sb3';
import backdrop from '!raw-loader!./cd21514d0531fdffb22204e0ec5ed84a.svg';
import costume1 from '!raw-loader!./dango-cat.svg';
/* eslint-enable import/no-unresolved */
import {TextEncoder} from '../tw-text-encoder';
const defaultProject = translator => {
if (overrideDefaultProject.byteLength > 0) {
return [{
id: 0,
assetType: 'Project',
dataFormat: 'JSON',
data: overrideDefaultProject
}];
}
let _TextEncoder;
if (typeof TextEncoder === 'undefined') {
_TextEncoder = require('text-encoding').TextEncoder;
} else {
_TextEncoder = TextEncoder;
}
const encoder = new _TextEncoder();
const projectJson = projectData(translator);
return [{
id: 0,
assetType: 'Project',
dataFormat: 'JSON',
data: JSON.stringify(projectJson)
}, {
id: 'cd21514d0531fdffb22204e0ec5ed84a',
assetType: 'ImageVector',
dataFormat: 'SVG',
data: encoder.encode(backdrop)
}, {
id: '927d672925e7b99f7813735c484c6922',
assetType: 'ImageVector',
dataFormat: 'SVG',
data: encoder.encode(costume1)
}];
};
export default defaultProject;

View File

@@ -0,0 +1,92 @@
import {defineMessages} from 'react-intl';
import sharedMessages from '../shared-messages';
let messages = defineMessages({
variable: {
defaultMessage: 'my variable',
description: 'Name for the default variable',
id: 'gui.defaultProject.variable'
}
});
messages = {...messages, ...sharedMessages};
// use the default message if a translation function is not passed
const defaultTranslator = msgObj => msgObj.defaultMessage;
/**
* Generate a localized version of the default project
* @param {function} translateFunction a function to use for translating the default names
* @return {object} the project data json for the default project
*/
const projectData = translateFunction => {
const translator = translateFunction || defaultTranslator;
return ({
targets: [
{
isStage: true,
name: 'Stage',
variables: {
'`jEk@4|i[#Fk?(8x)AV.-my variable': [
translator(messages.variable),
0
]
},
lists: {},
broadcasts: {},
blocks: {},
currentCostume: 0,
costumes: [
{
assetId: 'cd21514d0531fdffb22204e0ec5ed84a',
name: translator(messages.backdrop, {index: 1}),
md5ext: 'cd21514d0531fdffb22204e0ec5ed84a.svg',
dataFormat: 'svg',
rotationCenterX: 240,
rotationCenterY: 180
}
],
sounds: [],
volume: 100
},
{
isStage: false,
name: translator(messages.sprite, {index: 1}),
variables: {},
lists: {},
broadcasts: {},
blocks: {},
comments: {},
currentCostume: 0,
costumes: [
{
assetId: '927d672925e7b99f7813735c484c6922',
name: translator(messages.costume, {index: 1}),
bitmapResolution: 1,
md5ext: '927d672925e7b99f7813735c484c6922.svg',
dataFormat: 'svg',
rotationCenterX: 30.74937882782359,
rotationCenterY: 58.864768144346826
}
],
sounds: [],
volume: 100,
visible: true,
x: 0,
y: 0,
size: 100,
direction: 90,
draggable: false,
rotationStyle: 'all around'
}
],
meta: {
semver: '3.0.0',
vm: '0.1.0',
agent: ''
}
});
};
export default projectData;

View File

@@ -0,0 +1,112 @@
// TODO: access `BlockType` and `ArgumentType` without reaching into VM
// Should we move these into a new extension support module or something?
import ArgumentType from 'scratch-vm/src/extension-support/argument-type';
import BlockType from 'scratch-vm/src/extension-support/block-type';
import {injectExtensionBlockTheme} from './themes/blockHelpers';
/**
* Define a block using extension info which has the ability to dynamically determine (and update) its layout.
* This functionality is used for extension blocks which can change its properties based on different state
* information. For example, the `control_stop` block changes its shape based on which menu item is selected
* and a variable block changes its text to reflect the variable name without using an editable field.
* @param {object} ScratchBlocks - The ScratchBlocks name space.
* @param {object} categoryInfo - Information about this block's extension category, including any menus and icons.
* @param {object} staticBlockInfo - The base block information before any dynamic changes.
* @param {string} extendedOpcode - The opcode for the block (including the extension ID).
* @param {Theme} theme - the current theme
*/
// TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime
const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode, theme) => ({
init: function () {
const colors = injectExtensionBlockTheme(staticBlockInfo.json, theme);
const blockJson = {
type: extendedOpcode,
inputsInline: true,
category: categoryInfo.name,
colour: colors.colour,
colourSecondary: colors.colourSecondary,
colourTertiary: colors.colourTertiary,
colourQuaternary: colors.colourQuaternary
};
// There is a scratch-blocks / Blockly extension called "scratch_extension" which adjusts the styling of
// blocks to allow for an icon, a feature of Scratch extension blocks. However, Scratch "core" extension
// blocks don't have icons and so they should not use 'scratch_extension'. Adding a scratch-blocks / Blockly
// extension after `jsonInit` isn't fully supported (?), so we decide now whether there will be an icon.
if (staticBlockInfo.blockIconURI || categoryInfo.blockIconURI) {
blockJson.extensions = ['scratch_extension'];
}
// initialize the basics of the block, to be overridden & extended later by `domToMutation`
this.jsonInit(blockJson);
// initialize the cached block info used to carry block info from `domToMutation` to `mutationToDom`
this.blockInfoText = '{}';
// we need a block info update (through `domToMutation`) before we have a completely initialized block
this.needsBlockInfoUpdate = true;
},
mutationToDom: function () {
const container = document.createElement('mutation');
container.setAttribute('blockInfo', this.blockInfoText);
return container;
},
domToMutation: function (xmlElement) {
const blockInfoText = xmlElement.getAttribute('blockInfo');
if (!blockInfoText) return;
if (!this.needsBlockInfoUpdate) {
throw new Error('Attempted to update block info twice');
}
delete this.needsBlockInfoUpdate;
this.blockInfoText = blockInfoText;
const blockInfo = JSON.parse(blockInfoText);
switch (blockInfo.blockType) {
case BlockType.COMMAND:
case BlockType.CONDITIONAL:
case BlockType.LOOP:
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
this.setPreviousStatement(true);
this.setNextStatement(!blockInfo.isTerminal);
break;
case BlockType.REPORTER:
this.setOutput(true);
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND);
if (!blockInfo.disableMonitor) {
this.setCheckboxInFlyout(true);
}
break;
case BlockType.BOOLEAN:
this.setOutput(true);
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL);
break;
case BlockType.HAT:
case BlockType.EVENT:
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
this.setNextStatement(true);
break;
}
if (blockInfo.color1 || blockInfo.color2 || blockInfo.color3) {
// `setColour` handles undefined parameters by adjusting defined colors
this.setColour(blockInfo.color1, blockInfo.color2, blockInfo.color3);
}
// Layout block arguments
// TODO handle E/C Blocks
const blockText = blockInfo.text;
const args = [];
let argCount = 0;
const scratchBlocksStyleText = blockText.replace(/\[(.+?)]/g, (match, argName) => {
const arg = blockInfo.arguments[argName];
switch (arg.type) {
case ArgumentType.STRING:
args.push({type: 'input_value', name: argName});
break;
case ArgumentType.BOOLEAN:
args.push({type: 'input_value', name: argName, check: 'Boolean'});
break;
}
return `%${++argCount}`;
});
this.interpolate_(scratchBlocksStyleText, args);
}
});
export default defineDynamicBlock;

View File

@@ -0,0 +1,59 @@
/**
* @fileoverview
* Utility function to detect locale from the browser setting or paramenter on the URL.
*/
import queryString from 'query-string';
// tw: read language from localStorage
export const LANGUAGE_KEY = 'tw:language';
/**
* look for language setting in the browser. Check against supported locales.
* If there's a parameter in the URL, override the browser setting
* @param {Array.string} supportedLocales An array of supported locale codes.
* @return {string} the preferred locale
*/
const detectLocale = supportedLocales => {
// tw: read language from localStorage
try {
const storedLanguage = localStorage.getItem(LANGUAGE_KEY);
if (storedLanguage && supportedLocales.includes(storedLanguage)) {
return storedLanguage;
}
} catch (e) { /* ignore */ }
// 默认设置为中文简体,不再根据系统语言自动检测
let locale = 'zh-cn'; // default changed to Chinese Simplified
// 注释掉根据系统语言自动检测的逻辑
// let browserLocale = window.navigator.userLanguage || window.navigator.language;
// browserLocale = browserLocale.toLowerCase();
// // try to set locale from browserLocale
// if (supportedLocales.includes(browserLocale)) {
// locale = browserLocale;
// } else {
// browserLocale = browserLocale.split('-')[0];
// if (supportedLocales.includes(browserLocale)) {
// locale = browserLocale;
// }
// }
const queryParams = queryString.parse(location.search);
// Flatten potential arrays and remove falsy values
const potentialLocales = [].concat(queryParams.locale, queryParams.lang).filter(l => l);
if (!potentialLocales.length) {
return locale;
}
const urlLocale = potentialLocales[0].toLowerCase();
if (supportedLocales.includes(urlLocale)) {
return urlLocale;
}
return locale;
};
export {
detectLocale
};

View File

@@ -0,0 +1,33 @@
export default (filename, blob) => {
const downloadLink = document.createElement('a');
document.body.appendChild(downloadLink);
// Use special ms version if available to get it working on Edge.
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveOrOpenBlob(blob, filename);
return;
}
if ('download' in HTMLAnchorElement.prototype) {
const url = window.URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = filename;
downloadLink.type = blob.type;
downloadLink.click();
// remove the link after a timeout to prevent a crash on iOS 13 Safari
window.setTimeout(() => {
document.body.removeChild(downloadLink);
window.URL.revokeObjectURL(url);
}, 1000);
} else {
// iOS 12 Safari, open a new page and set href to data-uri
let popup = window.open('', '_blank');
const reader = new FileReader();
reader.onloadend = function () {
popup.location.href = reader.result;
popup = null;
};
reader.readAsDataURL(blob);
}
};

View File

@@ -0,0 +1,11 @@
export default {
SOUND: 'SOUND',
COSTUME: 'COSTUME',
SPRITE: 'SPRITE',
CODE: 'CODE',
BACKPACK_SOUND: 'BACKPACK_SOUND',
BACKPACK_COSTUME: 'BACKPACK_COSTUME',
BACKPACK_SPRITE: 'BACKPACK_SPRITE',
BACKPACK_CODE: 'BACKPACK_CODE'
};

View File

@@ -0,0 +1,130 @@
import bindAll from 'lodash.bindall';
import {getEventXY} from '../lib/touch-utils';
class DragRecognizer {
/* Gesture states */
static get STATE_UNIDENTIFIED () {
return 'unidentified';
}
static get STATE_SCROLL () {
return 'scroll';
}
static get STATE_DRAG () {
return 'drag';
}
constructor ({
onDrag = (() => {}),
onDragEnd = (() => {}),
touchDragAngle = 70, // Angle and distance thresholds are the same as scratch-blocks
distanceThreshold = 3
}) {
this._onDrag = onDrag;
this._onDragEnd = onDragEnd;
this._touchDragAngle = touchDragAngle;
this._distanceThreshold = distanceThreshold;
this._initialOffset = null;
this._gestureState = DragRecognizer.STATE_UNIDENTIFIED;
bindAll(this, [
'start',
'gestureInProgress',
'reset',
'_handleMove',
'_handleEnd'
]);
}
start (event) {
if (typeof event.button === 'number' && event.button !== 0) {
return;
}
this._initialOffset = getEventXY(event);
this._bindListeners();
}
gestureInProgress () {
return this._gestureState !== DragRecognizer.STATE_UNIDENTIFIED;
}
reset () {
this._unbindListeners();
this._initialOffset = null;
this._gestureState = DragRecognizer.STATE_UNIDENTIFIED;
}
//
// Internal functions
//
_bindListeners () {
window.addEventListener('mouseup', this._handleEnd);
window.addEventListener('mousemove', this._handleMove);
window.addEventListener('touchend', this._handleEnd);
// touchmove must be marked as non-passive, or else it cannot prevent scrolling
window.addEventListener('touchmove', this._handleMove, {passive: false});
}
_unbindListeners () {
window.removeEventListener('mouseup', this._handleEnd);
window.removeEventListener('mousemove', this._handleMove);
window.removeEventListener('touchend', this._handleEnd);
window.removeEventListener('touchmove', this._handleMove, {passive: false});
}
_handleMove (event) {
// For gestures identified as vertical scrolls, do not process movement events
if (this._isScroll()) return;
const currentOffset = getEventXY(event);
// Try to identify this gesture if it hasn't been identified already
if (!this.gestureInProgress()) {
const dx = currentOffset.x - this._initialOffset.x;
const dy = currentOffset.y - this._initialOffset.y;
const dragDistance = Math.sqrt((dx * dx) + (dy * dy));
if (dragDistance < this._distanceThreshold) return;
// For touch moves, additionally check if the angle suggests drag vs. scroll
if (event.type === 'touchmove') {
// Direction goes from -180 to 180, with 0 toward the right.
let angle = Math.atan2(dy, dx) / Math.PI * 180;
// Fold over horizontal axis, range now 0 to 180
angle = Math.abs(angle);
// Fold over vertical axis, range now 0 to 90
if (angle > 90) angle = 180 - angle;
if (angle > this._touchDragAngle) {
this._gestureState = DragRecognizer.STATE_SCROLL;
} else {
this._gestureState = DragRecognizer.STATE_DRAG;
}
} else {
// Mouse moves are always considered drags
this._gestureState = DragRecognizer.STATE_DRAG;
}
}
if (this._isDrag()) {
this._onDrag(currentOffset, this._initialOffset);
event.preventDefault();
}
}
_handleEnd () {
this.reset();
// Call the callback after reset to make sure if gestureInProgress()
// is used in response, it get the correct value (i.e. no gesture in progress)
this._onDragEnd();
}
_isDrag () {
return this._gestureState === DragRecognizer.STATE_DRAG;
}
_isScroll () {
return this._gestureState === DragRecognizer.STATE_SCROLL;
}
}
export default DragRecognizer;

View File

@@ -0,0 +1,55 @@
/**
* @fileoverview
* Utility functions for drag interactions, e.g. sorting items in a grid/list.
*/
/**
* From an xy position and a list of boxes {top, left, bottom, right}, return there
* corresponding box index the position is over. The boxes are in a (possibly wrapped)
* list, the only requirement being all boxes are flush against the edges, that is,
* if they are along an outer edge, the position of that edge is identical.
* This functionality works for a single column of items, a wrapped list with
* many rows, or a single row of items.
* @param {{x: number, y: number}} position The xy coordinates to retreive the corresponding index of.
* @param {Array.<DOMRect>} boxes The rects of the items, returned from `getBoundingClientRect`
* @param {bool} isRtl are the boxes in RTL order.
* @return {?number} index of the corresponding box, or null if one could not be found.
*/
const indexForPositionOnList = ({x, y}, boxes, isRtl) => {
if (boxes.length === 0) return null;
let index = null;
const leftEdge = Math.min.apply(null, boxes.map(b => b.left));
const rightEdge = Math.max.apply(null, boxes.map(b => b.right));
const topEdge = Math.min.apply(null, boxes.map(b => b.top));
const bottomEdge = Math.max.apply(null, boxes.map(b => b.bottom));
for (let n = 0; n < boxes.length; n++) {
const box = boxes[n];
// Construct an "extended" box for each, extending out to infinity if
// the box is along a boundary.
let minX = box.left === leftEdge ? -Infinity : box.left;
let maxX = box.right === rightEdge ? Infinity : box.right;
const minY = box.top === topEdge ? -Infinity : box.top;
const maxY = box.bottom === bottomEdge ? Infinity : box.bottom;
// The last item in the wrapped list gets a right edge at infinity, even
// if it isn't the farthest right, in RTL mode. In LTR mode, it gets a
// left edge at infinity.
if (n === boxes.length - 1) {
if (isRtl) {
minX = -Infinity;
} else {
maxX = Infinity;
}
}
// Check if the point is in the bounds.
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
index = n;
break; // No need to keep looking.
}
}
return index;
};
export {
indexForPositionOnList
};

View File

@@ -0,0 +1,119 @@
import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import omit from 'lodash.omit';
import {connect} from 'react-redux';
/**
* Higher Order Component to give components the ability to react to drag overs
* and drops of objects stored in the assetDrag redux state.
*
* Example: You want to enable MyComponent to receive drops from a drag type
* Wrapped = DropAreaHOC([...dragTypes])(
* <MyComponent />
* )
*
* MyComponent now receives 2 new props
* containerRef: a ref that must be set on the container element
* dragOver: boolean if an asset is being dragged above the component
*
* Use the wrapped component:
* <Wrapped onDrop={yourDropHandler} />
*
* NB: This HOC _only_ works with objects that drag using the assetDrag reducer.
* This _does not_ handle drags for blocks coming from the workspace.
*
* @param {Array.<string>} dragTypes Types to respond to, from DragConstants
* @returns {function} The HOC, specialized for those drag types
*/
const DropAreaHOC = function (dragTypes) {
/**
* Return the HOC, specialized for the dragTypes
* @param {React.Component} WrappedComponent component to receive drop behaviors
* @returns {React.Component} component with drag over/drop behavior
*/
return function (WrappedComponent) {
class DropAreaWrapper extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'setRef'
]);
this.state = {
dragOver: false
};
this.ref = null;
this.containerBox = null;
}
componentWillReceiveProps (newProps) {
// If `dragging` becomes true, record the drop area rectangle
if (newProps.dragInfo.dragging && !this.props.dragInfo.dragging) {
this.dropAreaRect = this.ref && this.ref.getBoundingClientRect();
// If `dragging` becomes false, call the drop handler
} else if (!newProps.dragInfo.dragging && this.props.dragInfo.dragging && this.state.dragOver) {
this.props.onDrop(this.props.dragInfo);
this.setState({dragOver: false});
}
// If a drag is in progress (currentOffset) and it matches the relevant drag types,
// test if the drag is within the drop area rect and set the state accordingly.
if (this.dropAreaRect && newProps.dragInfo.currentOffset &&
dragTypes.includes(newProps.dragInfo.dragType)) {
const {x, y} = newProps.dragInfo.currentOffset;
const {top, right, bottom, left} = this.dropAreaRect;
if (x > left && x < right && y > top && y < bottom) {
this.setState({dragOver: true});
} else {
this.setState({dragOver: false});
}
}
}
setRef (el) {
this.ref = el;
if (this.props.componentRef) {
this.props.componentRef(this.ref);
}
}
render () {
const componentProps = omit(this.props, ['onDrop', 'dragInfo', 'componentRef']);
return (
<WrappedComponent
containerRef={this.setRef}
dragOver={this.state.dragOver}
{...componentProps}
/>
);
}
}
DropAreaWrapper.propTypes = {
componentRef: PropTypes.func,
dragInfo: PropTypes.shape({
currentOffset: PropTypes.shape({
x: PropTypes.number,
y: PropTypes.number
}),
dragType: PropTypes.string,
dragging: PropTypes.bool,
index: PropTypes.number
}),
onDrop: PropTypes.func
};
const mapStateToProps = state => ({
dragInfo: state.scratchGui.assetDrag
});
const mapDispatchToProps = () => ({});
return connect(
mapStateToProps,
mapDispatchToProps
)(DropAreaWrapper);
};
};
export default DropAreaHOC;

View File

@@ -0,0 +1,55 @@
/**
* @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: [],
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
};

View File

@@ -0,0 +1,26 @@
import React from 'react';
import ErrorBoundary from '../containers/error-boundary.jsx';
/*
* Higher Order Component to provide error boundary for wrapped component.
* A curried function, call like errorHOC(<tracking label>)(<Component>).
* @param {string} action - Label for GA tracking of errors.
* @returns {function} a function that accepts a component to wrap.
*/
const ErrorBoundaryHOC = function (action){
/**
* The function to be called with a React component to wrap it.
* @param {React.ComponentType} WrappedComponent - Component to wrap with an error boundary.
* @returns {React.ComponentType} the component wrapped with an error boundary.
*/
return function (WrappedComponent) {
const ErrorBoundaryWrapper = props => (
<ErrorBoundary action={action}>
<WrappedComponent {...props} />
</ErrorBoundary>
);
return ErrorBoundaryWrapper;
};
};
export default ErrorBoundaryHOC;

View File

@@ -0,0 +1,290 @@
import {BitmapAdapter, sanitizeSvg, fixForVanilla} from '@turbowarp/scratch-svg-renderer';
import randomizeSpritePosition from './randomize-sprite-position.js';
import bmpConverter from './bmp-converter';
import gifDecoder from './gif-decoder';
import convertAudioToWav from './tw-convert-audio-wav.js';
import log from './log.js';
/**
* Extract the file name given a string of the form fileName + ext
* @param {string} nameExt File name + extension (e.g. 'my_image.png')
* @return {string} The name without the extension, or the full name if
* there was no '.' in the string (e.g. 'my_image')
*/
const extractFileName = function (nameExt) {
// There could be multiple dots, but get the stuff before the first .
const nameParts = nameExt.split('.', 1); // we only care about the first .
return nameParts[0];
};
/**
* Handle a file upload given the input element that contains the file,
* and a function to handle loading the file.
* @param {Input} fileInput The <input/> element that contains the file being loaded
* @param {Function} onload The function that handles loading the file
* @param {Function} onerror The function that handles any error loading the file
*/
const handleFileUpload = function (fileInput, onload, onerror) {
const readFile = (i, files) => {
if (i === files.length) {
// Reset the file input value now that we have everything we need
// so that the user can upload the same sound multiple times if
// they choose
fileInput.value = null;
return;
}
const file = files[i];
const reader = new FileReader();
reader.onload = () => {
const fileType = file.type;
const fileName = extractFileName(file.name);
onload(reader.result, fileType, fileName, i, files.length);
readFile(i + 1, files);
};
reader.onerror = onerror;
reader.readAsArrayBuffer(file);
};
readFile(0, fileInput.files);
};
/**
* @typedef VMAsset
* @property {string} name The user-readable name of this asset - This will
* automatically get translated to a fresh name if this one already exists in the
* scope of this vm asset (e.g. if a sound already exists with the same name for
* the same target)
* @property {string} dataFormat The data format of this asset, typically
* the extension to be used for that particular asset, e.g. 'svg' for vector images
* @property {string} md5 The md5 hash of the asset data, followed by '.'' and dataFormat
* @property {string} The md5 hash of the asset data // TODO remove duplication....
*/
/**
* Create an asset (costume, sound) with storage and return an object representation
* of the asset to track in the VM.
* @param {ScratchStorage} storage The storage to cache the asset in
* @param {AssetType} assetType A ScratchStorage AssetType indicating what kind of
* asset this is.
* @param {string} dataFormat The format of this data (typically the file extension)
* @param {UInt8Array} data The asset data buffer
* @return {VMAsset} An object representing this asset and relevant information
* which can be used to look up the data in storage
*/
const createVMAsset = function (storage, assetType, dataFormat, data) {
const asset = storage.createAsset(
assetType,
dataFormat,
data,
null,
true // generate md5
);
return {
name: null, // Needs to be set by caller
dataFormat: dataFormat,
asset: asset,
md5: `${asset.assetId}.${dataFormat}`,
assetId: asset.assetId
};
};
/**
* Handles loading a costume or a backdrop using the provided, context-relevant information.
* @param {ArrayBuffer | string} fileData The costume data to load (this can be a base64 string
* iff the image is a bitmap)
* @param {string} fileType The MIME type of this file
* @param {VM} vm The ScratchStorage instance to cache the costume data
* @param {Function} handleCostume The function to execute on the costume object returned after
* caching this costume in storage - This function should be responsible for
* adding the costume to the VM and handling other UI flow that should come after adding the costume
* @param {Function} handleError The function to execute if there is an error parsing the costume
*/
const costumeUpload = function (fileData, fileType, vm, handleCostume, handleError = () => {}) {
const storage = vm.runtime.storage;
let costumeFormat = null;
let assetType = null;
switch (fileType) {
case 'image/svg+xml': {
// fix any vanilla compatibility issues in the SVG first
try {
fileData = fixForVanilla(fileData);
} catch (e) {
log.error('fixForVanilla error', e);
}
// run svg bytes through scratch-svg-renderer's sanitization code
fileData = sanitizeSvg.sanitizeByteStream(fileData);
costumeFormat = storage.DataFormat.SVG;
assetType = storage.AssetType.ImageVector;
break;
}
case 'image/jpeg': {
costumeFormat = storage.DataFormat.JPG;
assetType = storage.AssetType.ImageBitmap;
break;
}
case 'image/bmp': {
// Convert .bmp files to .png to compress them. .bmps are completely uncompressed,
// and would otherwise take up a lot of storage space and take much longer to upload and download.
bmpConverter(fileData).then(dataUrl => {
costumeUpload(dataUrl, 'image/png', vm, handleCostume);
});
return; // Return early because we're triggering another proper costumeUpload
}
case 'image/png': {
costumeFormat = storage.DataFormat.PNG;
assetType = storage.AssetType.ImageBitmap;
break;
}
case 'image/webp': {
// Scratch does not natively support webp, so convert to png
// see image/bmp logic above
bmpConverter(fileData, 'image/webp').then(dataUrl => {
costumeUpload(dataUrl, 'image/png', vm, handleCostume);
});
return;
}
case 'image/gif': {
let costumes = [];
gifDecoder(fileData, (frameNumber, dataUrl, numFrames) => {
costumeUpload(dataUrl, 'image/png', vm, costumes_ => {
costumes = costumes.concat(costumes_);
if (frameNumber === numFrames - 1) {
handleCostume(costumes);
}
}, handleError);
});
return; // Abandon this load, do not try to load gif itself
}
default:
handleError(`Encountered unexpected file type: ${fileType}`);
return;
}
const bitmapAdapter = new BitmapAdapter();
if (bitmapAdapter.setStageSize) {
const width = vm.runtime.stageWidth;
const height = vm.runtime.stageHeight;
bitmapAdapter.setStageSize(width, height);
}
const addCostumeFromBuffer = function (dataBuffer) {
const vmCostume = createVMAsset(
storage,
assetType,
costumeFormat,
dataBuffer
);
handleCostume([vmCostume]);
};
if (costumeFormat === storage.DataFormat.SVG) {
// Must pass in file data as a Uint8Array,
// passing in an array buffer causes the sprite/costume
// thumbnails to not display because the data URI for the costume
// is invalid
addCostumeFromBuffer(new Uint8Array(fileData));
} else {
// otherwise it's a bitmap
bitmapAdapter.importBitmap(fileData, fileType).then(addCostumeFromBuffer)
.catch(handleError);
}
};
/**
* Handles loading a sound using the provided, context-relevant information.
* @param {ArrayBuffer} fileData The sound data to load
* @param {string} fileType The MIME type of this file; This function will exit
* early if the fileType is unexpected.
* @param {ScratchStorage} storage The ScratchStorage instance to cache the sound data
* @param {Function} handleSound The function to execute on the sound object of type VMAsset
* This function should be responsible for adding the sound to the VM
* as well as handling other UI flow that should come after adding the sound
* @param {Function} handleError The function to execute if there is an error parsing the sound
*/
const soundUpload = function (fileData, fileType, storage, handleSound, handleError) {
let soundFormat;
switch (fileType) {
case 'audio/mp3':
case 'audio/mpeg': {
soundFormat = storage.DataFormat.MP3;
break;
}
case 'audio/wav':
case 'audio/wave':
case 'audio/x-wav':
case 'audio/x-pn-wav': {
soundFormat = storage.DataFormat.WAV;
break;
}
default:
convertAudioToWav(fileData)
.then(fixed => {
soundUpload(fixed, 'audio/wav', storage, handleSound, handleError);
})
.catch(handleError);
return;
}
const vmSound = createVMAsset(
storage,
storage.AssetType.Sound,
soundFormat,
new Uint8Array(fileData));
handleSound(vmSound);
};
const spriteUpload = function (fileData, fileType, spriteName, vm, handleSprite, handleError = () => {}) {
switch (fileType) {
case '':
case 'application/zip': { // We think this is a .sprite2 or .sprite3 file
handleSprite(new Uint8Array(fileData));
return;
}
case 'image/svg+xml':
case 'image/png':
case 'image/bmp':
case 'image/jpeg':
case 'image/webp':
case 'image/gif': {
// Make a sprite from an image by making it a costume first
costumeUpload(fileData, fileType, vm, vmCostumes => {
vmCostumes.forEach((costume, i) => {
costume.name = `${spriteName}${i ? i + 1 : ''}`;
});
const newSprite = {
name: spriteName,
isStage: false,
x: 0, // x/y will be randomized below
y: 0,
visible: true,
size: 100,
rotationStyle: 'all around',
direction: 90,
draggable: false,
currentCostume: 0,
blocks: {},
variables: {},
costumes: vmCostumes,
sounds: [] // TODO are all of these necessary?
};
randomizeSpritePosition(newSprite);
// TODO probably just want sprite upload to handle this object directly
handleSprite(JSON.stringify(newSprite));
}, handleError);
return;
}
default: {
handleError(`Encountered unexpected file type: ${fileType}`);
return;
}
}
};
export {
handleFileUpload,
costumeUpload,
soundUpload,
spriteUpload
};

View File

@@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';
import omit from 'lodash.omit';
import {connect} from 'react-redux';
import {setFontsLoaded} from '../reducers/fonts-loaded';
import {loadFonts} from 'scratch-render-fonts';
/* Higher Order Component to provide behavior for loading fonts.
* @param {React.Component} WrappedComponent component to receive fontsLoaded prop
* @returns {React.Component} component with font loading behavior
*/
const FontLoaderHOC = function (WrappedComponent) {
class FontLoaderComponent extends React.Component {
componentDidMount () {
if (this.props.fontsLoaded) return;
loadFonts().then(() => this.props.onSetFontsLoaded());
}
render () {
const componentProps = omit(this.props, ['onSetFontsLoaded']);
return (
<WrappedComponent
{...componentProps}
/>
);
}
}
FontLoaderComponent.propTypes = {
fontsLoaded: PropTypes.bool.isRequired,
onSetFontsLoaded: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
fontsLoaded: state.scratchGui.fontsLoaded
});
const mapDispatchToProps = dispatch => ({
onSetFontsLoaded: () => dispatch(setFontsLoaded())
});
return connect(
mapStateToProps,
mapDispatchToProps
)(FontLoaderComponent);
};
export {
FontLoaderHOC as default
};

View File

@@ -0,0 +1,40 @@
import storage from './storage';
import {inlineSvgFonts} from '@turbowarp/scratch-svg-renderer';
// Contains 'font-family', but doesn't only contain 'font-family="none"'
const HAS_FONT_REGEXP = 'font-family(?!="none")';
const getCostumeUrl = (function () {
let cachedAssetId;
let cachedUrl;
return function (asset) {
if (cachedAssetId === asset.assetId) {
return cachedUrl;
}
cachedAssetId = asset.assetId;
// If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
// Avoid parsing the SVG when possible, since it's expensive.
if (asset.assetType === storage.AssetType.ImageVector) {
const svgString = asset.decodeText();
if (svgString.match(HAS_FONT_REGEXP)) {
const svgText = inlineSvgFonts(svgString);
cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
} else {
cachedUrl = asset.encodeDataURI();
}
} else {
cachedUrl = asset.encodeDataURI();
}
return cachedUrl;
};
}());
export {
getCostumeUrl as default,
HAS_FONT_REGEXP
};

View File

@@ -0,0 +1,60 @@
import {GifReader} from 'omggif';
export default (arrayBuffer, onFrame) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const gifReader = new GifReader(new Uint8Array(arrayBuffer));
const numFrames = gifReader.numFrames();
canvas.width = gifReader.width;
canvas.height = gifReader.height;
let imageData = ctx.createImageData(canvas.width, canvas.height);
let previousData = ctx.createImageData(canvas.width, canvas.height);
const loadFrame = i => {
const framePixels = [];
gifReader.decodeAndBlitFrameRGBA(i, framePixels);
const {x, y, width, height, disposal} = gifReader.frameInfo(i);
for (let row = 0; row < height; row++) {
for (let column = 0; column < width; column++) {
const indexOffset = 4 * (x + (y * canvas.width));
const j = indexOffset + (4 * (column + (row * canvas.width)));
if (framePixels[j + 3]) {
imageData.data[j + 0] = framePixels[j + 0];
imageData.data[j + 1] = framePixels[j + 1];
imageData.data[j + 2] = framePixels[j + 2];
imageData.data[j + 3] = framePixels[j + 3];
}
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(imageData, 0, 0);
const dataUrl = canvas.toDataURL();
switch (disposal) {
case 2: // "Return to background", blank out the current frame
ctx.clearRect(x, y, width, height);
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
break;
case 3: // "Restore to previous", copy previous data to current
imageData = ctx.createImageData(canvas.width, canvas.height);
imageData.data.set(previousData.data);
break;
default: // 0 and 1, as well as 4+ modes = do-not-dispose, so cache frame
previousData = ctx.getImageData(0, 0, canvas.width, canvas.height);
break;
}
onFrame(i, dataUrl, numFrames);
if (i < numFrames - 1) {
setTimeout(() => {
loadFrame(i + 1);
});
}
};
loadFrame(0);
};

View File

@@ -0,0 +1,90 @@
import bindAll from 'lodash.bindall';
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {
defaultProjectId,
getIsFetchingWithoutId,
setProjectId
} from '../reducers/project-state';
/* Higher Order Component to get the project id from location.hash
* @param {React.Component} WrappedComponent: component to render
* @returns {React.Component} component with hash parsing behavior
*/
const HashParserHOC = function (WrappedComponent) {
class HashParserComponent extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleHashChange'
]);
}
componentDidMount () {
window.addEventListener('hashchange', this.handleHashChange);
this.handleHashChange();
}
componentDidUpdate (prevProps) {
// if we are newly fetching a non-hash project...
if (this.props.isFetchingWithoutId && !prevProps.isFetchingWithoutId) {
// ...clear the hash from the url
history.pushState('new-project', 'new-project',
window.location.pathname + window.location.search);
}
}
componentWillUnmount () {
window.removeEventListener('hashchange', this.handleHashChange);
}
handleHashChange () {
const hashMatch = window.location.hash.match(/#(\d+)/);
const hashProjectId = hashMatch === null ? defaultProjectId : hashMatch[1];
this.props.setProjectId(hashProjectId.toString());
}
render () {
const {
/* eslint-disable no-unused-vars */
isFetchingWithoutId: isFetchingWithoutIdProp,
reduxProjectId,
setProjectId: setProjectIdProp,
/* eslint-enable no-unused-vars */
...componentProps
} = this.props;
return (
<WrappedComponent
{...componentProps}
/>
);
}
}
HashParserComponent.propTypes = {
isFetchingWithoutId: PropTypes.bool,
reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
setProjectId: PropTypes.func
};
const mapStateToProps = state => {
const loadingState = state.scratchGui.projectState.loadingState;
return {
isFetchingWithoutId: getIsFetchingWithoutId(loadingState),
reduxProjectId: state.scratchGui.projectState.projectId
};
};
const mapDispatchToProps = dispatch => ({
setProjectId: projectId => {
dispatch(setProjectId(projectId));
}
});
// Allow incoming props to override redux-provided props. Used to mock in tests.
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
{}, stateProps, dispatchProps, ownProps
);
return connect(
mapStateToProps,
mapDispatchToProps,
mergeProps
)(HashParserComponent);
};
export {
HashParserHOC as default
};

View File

@@ -0,0 +1,34 @@
import Papa from 'papaparse';
export default () => new Promise((resolve, reject) => {
const fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('accept', '.csv, .tsv, .txt'); // parser auto-detects delimiter
fileInput.onchange = e => {
const file = e.target.files[0];
const fr = new FileReader();
fr.onload = () => {
document.body.removeChild(fileInput);
const text = fr.result;
Papa.parse(text, {
header: false,
complete: results => {
resolve({
rows: results.data,
text
});
},
error: err => {
reject(err);
}
});
};
fr.onerror = () => {
document.body.removeChild(fileInput);
reject(new Error('Cannot read file'));
};
fr.readAsText(file);
};
document.body.appendChild(fileInput);
fileInput.click();
});

View File

@@ -0,0 +1,35 @@
/**
* Internal stored state. Not valid until after at least one call to `setIsScratchDesktop()`.
* @type {boolean}
*/
let _isScratchDesktop; // undefined = not ready yet
/**
* Tell the `isScratchDesktop()` whether or not the GUI is running under Scratch Desktop.
* @param {boolean} value - the new value which `isScratchDesktop()` should return in the future.
*/
const setIsScratchDesktop = function (value) {
_isScratchDesktop = value;
};
/**
* @returns {boolean} - true if it seems like the GUI is running under Scratch Desktop; false otherwise.
* If `setIsScratchDesktop()` has not yet been called, this can return `undefined`.
*/
const isScratchDesktop = function () {
return _isScratchDesktop;
};
/**
* @returns {boolean} - false if it seems like the GUI is running under Scratch Desktop; true otherwise.
*/
const notScratchDesktop = function () {
return !isScratchDesktop();
};
export default isScratchDesktop;
export {
isScratchDesktop,
notScratchDesktop,
setIsScratchDesktop
};

View File

@@ -0,0 +1,70 @@
import keyMirror from 'keymirror';
/**
* Names for each state of the stage size toggle
* @enum {string}
*/
const STAGE_SIZE_MODES = keyMirror({
/**
* Display the stage at a large (but fixed) width.
*/
large: null,
/**
* Display the stage at a small (but fixed) width.
*/
small: null,
/**
* Display the stage at its full size.
*/
full: null
});
/**
* Names for each stage render size
* @enum {string}
*/
const STAGE_DISPLAY_SIZES = keyMirror({
large: null,
small: null,
constrained: null,
full: null
});
// zoom level to start with
const BLOCKS_DEFAULT_SCALE = 0.675;
const FIXED_WIDTH = 480;
/**
* Minimum amount of screen width (excluding the width used by the stage itself) to display constrained.
*/
const UNCONSTRAINED_NON_STAGE_WIDTH = 1096 - FIXED_WIDTH;
const STAGE_DISPLAY_SCALE_METADATA = {
[STAGE_DISPLAY_SIZES.large]: {
width: FIXED_WIDTH
},
[STAGE_DISPLAY_SIZES.small]: {
width: FIXED_WIDTH * 0.5
},
[STAGE_DISPLAY_SIZES.constrained]: {
scale: 0.85
},
[STAGE_DISPLAY_SIZES.full]: {
scale: 1
}
};
export {
BLOCKS_DEFAULT_SCALE,
STAGE_DISPLAY_SCALE_METADATA,
STAGE_DISPLAY_SIZES,
STAGE_SIZE_MODES,
FIXED_WIDTH,
UNCONSTRAINED_NON_STAGE_WIDTH
};

View File

@@ -0,0 +1,7 @@
module.exports = {
rules: {
// These manifest files use duplicate imports to make things easier to follow
// by providing clear parallel structure. Turn the error off for this folder.
'no-duplicate-imports': 0
}
};

View File

@@ -0,0 +1,11 @@
import messages from './tag-messages.js';
export default [
{tag: 'fantasy', intlLabel: messages.fantasy},
{tag: 'music', intlLabel: messages.music},
{tag: 'sports', intlLabel: messages.sports},
{tag: 'outdoors', intlLabel: messages.outdoors},
{tag: 'indoors', intlLabel: messages.indoors},
{tag: 'space', intlLabel: messages.space},
{tag: 'underwater', intlLabel: messages.underwater},
{tag: 'patterns', intlLabel: messages.patterns}
];

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.am.gif';
import introSay from './steps/intro-2-say.am.gif';
import introGreenFlag from './steps/intro-3-green-flag.am.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.am.gif';
import speechSaySomething from './steps/speech-say-something.am.png';
import speechSetVoice from './steps/speech-set-voice.am.png';
import speechMoveAround from './steps/speech-move-around.am.png';
import speechAddBackdrop from './steps/pick-backdrop.LTR.gif';
import speechAddSprite from './steps/speech-add-sprite.LTR.gif';
import speechSong from './steps/speech-song.am.png';
import speechChangeColor from './steps/speech-change-color.am.png';
import speechSpin from './steps/speech-spin.am.png';
import speechGrowShrink from './steps/speech-grow-shrink.am.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.am.png';
import cnGlide from './steps/cn-glide.am.png';
import cnPickSprite from './steps/cn-pick-sprite.LTR.gif';
import cnCollect from './steps/cn-collect.am.png';
import cnVariable from './steps/add-variable.am.gif';
import cnScore from './steps/cn-score.am.png';
import cnBackdrop from './steps/cn-backdrop.am.png';
// Add sprite
import addSprite from './steps/add-sprite.LTR.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.LTR.gif';
import namePlaySound from './steps/name-play-sound.am.png';
import namePickLetter2 from './steps/name-pick-letter2.LTR.gif';
import nameChangeColor from './steps/name-change-color.am.png';
import nameSpin from './steps/name-spin.am.png';
import nameGrow from './steps/name-grow.am.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.LTR.gif';
import musicPlaySound from './steps/music-play-sound.am.png';
import musicMakeSong from './steps/music-make-song.am.png';
import musicMakeBeat from './steps/music-make-beat.am.png';
import musicMakeBeatbox from './steps/music-make-beatbox.am.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.LTR.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.LTR.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.am.png';
import chaseGameUpDown from './steps/chase-game-up-down.am.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.LTR.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.am.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.am.png';
import chaseGameAddVariable from './steps/add-variable.am.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.am.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.LTR.gif';
import popGamePlaySound from './steps/pop-game-play-sound.am.png';
import popGameAddScore from './steps/add-variable.am.gif';
import popGameChangeScore from './steps/pop-game-change-score.am.png';
import popGameRandomPosition from './steps/pop-game-random-position.am.png';
import popGameChangeColor from './steps/pop-game-change-color.am.png';
import popGameResetScore from './steps/pop-game-reset-score.am.png';
// Animate A Character
import animateCharPickBackdrop from './steps/pick-backdrop.LTR.gif';
import animateCharPickSprite from './steps/animate-char-pick-sprite.LTR.gif';
import animateCharSaySomething from './steps/animate-char-say-something.am.png';
import animateCharAddSound from './steps/animate-char-add-sound.am.png';
import animateCharTalk from './steps/animate-char-talk.am.png';
import animateCharMove from './steps/animate-char-move.am.png';
import animateCharJump from './steps/animate-char-jump.am.png';
import animateCharChangeColor from './steps/animate-char-change-color.am.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.LTR.gif';
import storyPickSprite from './steps/story-pick-sprite.LTR.gif';
import storySaySomething from './steps/story-say-something.am.png';
import storyPickSprite2 from './steps/story-pick-sprite2.LTR.gif';
import storyFlip from './steps/story-flip.am.gif';
import storyConversation from './steps/story-conversation.am.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.LTR.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.am.png';
import storyHideCharacter from './steps/story-hide-character.am.png';
import storyShowCharacter from './steps/story-show-character.am.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.am.gif';
import videoPet from './steps/video-pet.am.png';
import videoAnimate from './steps/video-animate.am.png';
import videoPop from './steps/video-pop.am.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.LTR.gif';
import flyChooseCharacter from './steps/fly-choose-character.LTR.png';
import flySaySomething from './steps/fly-say-something.am.png';
import flyMoveArrows from './steps/fly-make-interactive.am.png';
import flyChooseObject from './steps/fly-object-to-collect.LTR.png';
import flyFlyingObject from './steps/fly-flying-heart.am.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.LTR.png';
import flyAddScore from './steps/add-variable.am.gif';
import flyKeepScore from './steps/fly-keep-score.am.png';
import flyAddScenery from './steps/fly-choose-scenery.LTR.gif';
import flyMoveScenery from './steps/fly-move-scenery.am.png';
import flySwitchLooks from './steps/fly-switch-costume.am.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.LTR.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.LTR.png';
import pongBounceAround from './steps/pong-bounce-around.am.png';
import pongAddPaddle from './steps/pong-add-a-paddle.LTR.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.am.png';
import pongSelectBallSprite from './steps/pong-select-ball.LTR.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.am.png';
import pongAddAScore from './steps/add-variable.am.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.am.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.am.png';
import pongResetScore from './steps/pong-reset-score.am.png';
import pongAddLineSprite from './steps/pong-add-line.LTR.gif';
import pongGameOver from './steps/pong-game-over.am.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.am.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.am.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.LTR.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.LTR.png';
import imagineFlyAround from './steps/imagine-fly-around.am.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.LTR.png';
import imagineLeftRight from './steps/imagine-left-right.am.png';
import imagineUpDown from './steps/imagine-up-down.am.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.am.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.am.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.am.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.LTR.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.am.png';
import imagineRecordASound from './steps/imagine-record-a-sound.am.gif';
import imagineChooseSound from './steps/imagine-choose-sound.am.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.LTR.png';
// Add Effects
import addEffects from './steps/add-effects.am.png';
// Hide and Show
import hideAndShow from './steps/hide-show.am.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.am.png';
// Change Size
import changeSize from './steps/change-size.am.png';
// Spin
import spinTurn from './steps/spin-turn.am.png';
import spinPointInDirection from './steps/spin-point-in-direction.am.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.am.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.am.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.am.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.am.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.am.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.am.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.am.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.am.png';
import glideAroundPoint from './steps/glide-around-point.am.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.am.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.am.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.LTR.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.am.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.am.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.LTR.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.am.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.am.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.am.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.am.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.LTR.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.am.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.LTR.png';
import talesSaySomething from './steps/talking-3-say-something.am.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.LTR.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.am.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.LTR.png';
import talesMoveAround from './steps/talking-7-move-around.am.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.LTR.png';
import talesAnimateTalking from './steps/talking-9-animate.am.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.LTR.png';
import talesChooseSound from './steps/talking-11-choose-sound.am.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.am.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.am.png';
const amImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {amImages};

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.ar.gif';
import introSay from './steps/intro-2-say.ar.gif';
import introGreenFlag from './steps/intro-3-green-flag.ar.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.ar.gif';
import speechSaySomething from './steps/speech-say-something.ar.png';
import speechSetVoice from './steps/speech-set-voice.ar.png';
import speechMoveAround from './steps/speech-move-around.ar.png';
import speechAddBackdrop from './steps/add-backdrop.RTL.png';
import speechAddSprite from './steps/speech-add-sprite.RTL.gif';
import speechSong from './steps/speech-song.ar.png';
import speechChangeColor from './steps/speech-change-color.ar.png';
import speechSpin from './steps/speech-spin.ar.png';
import speechGrowShrink from './steps/speech-grow-shrink.ar.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.ar.png';
import cnGlide from './steps/cn-glide.ar.png';
import cnPickSprite from './steps/cn-pick-sprite.RTL.gif';
import cnCollect from './steps/cn-collect.ar.png';
import cnVariable from './steps/add-variable.ar.gif';
import cnScore from './steps/cn-score.ar.png';
import cnBackdrop from './steps/cn-backdrop.ar.png';
// Add sprite
import addSprite from './steps/add-sprite.RTL.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.RTL.gif';
import namePlaySound from './steps/name-play-sound.ar.png';
import namePickLetter2 from './steps/name-pick-letter2.RTL.gif';
import nameChangeColor from './steps/name-change-color.ar.png';
import nameSpin from './steps/name-spin.ar.png';
import nameGrow from './steps/name-grow.ar.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.RTL.gif';
import musicPlaySound from './steps/music-play-sound.ar.png';
import musicMakeSong from './steps/music-make-song.ar.png';
import musicMakeBeat from './steps/music-make-beat.ar.png';
import musicMakeBeatbox from './steps/music-make-beatbox.ar.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.RTL.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.RTL.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.ar.png';
import chaseGameUpDown from './steps/chase-game-up-down.ar.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.RTL.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.ar.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.ar.png';
import chaseGameAddVariable from './steps/add-variable.ar.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.ar.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.RTL.gif';
import popGamePlaySound from './steps/pop-game-play-sound.ar.png';
import popGameAddScore from './steps/add-variable.ar.gif';
import popGameChangeScore from './steps/pop-game-change-score.ar.png';
import popGameRandomPosition from './steps/pop-game-random-position.ar.png';
import popGameChangeColor from './steps/pop-game-change-color.ar.png';
import popGameResetScore from './steps/pop-game-reset-score.ar.png';
// Animate A Character
import animateCharPickBackdrop from './steps/animate-char-pick-backdrop.RTL.png';
import animateCharPickSprite from './steps/animate-char-pick-sprite.RTL.gif';
import animateCharSaySomething from './steps/animate-char-say-something.ar.png';
import animateCharAddSound from './steps/animate-char-add-sound.ar.png';
import animateCharTalk from './steps/animate-char-talk.ar.png';
import animateCharMove from './steps/animate-char-move.ar.png';
import animateCharJump from './steps/animate-char-jump.ar.png';
import animateCharChangeColor from './steps/animate-char-change-color.ar.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.RTL.gif';
import storyPickSprite from './steps/story-pick-sprite.RTL.gif';
import storySaySomething from './steps/story-say-something.ar.png';
import storyPickSprite2 from './steps/story-pick-sprite2.RTL.gif';
import storyFlip from './steps/story-flip.ar.gif';
import storyConversation from './steps/story-conversation.ar.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.RTL.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.ar.png';
import storyHideCharacter from './steps/story-hide-character.ar.png';
import storyShowCharacter from './steps/story-show-character.ar.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.ar.gif';
import videoPet from './steps/video-pet.ar.png';
import videoAnimate from './steps/video-animate.ar.png';
import videoPop from './steps/video-pop.ar.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.RTL.gif';
import flyChooseCharacter from './steps/fly-choose-character.RTL.png';
import flySaySomething from './steps/fly-say-something.ar.png';
import flyMoveArrows from './steps/fly-make-interactive.ar.png';
import flyChooseObject from './steps/fly-object-to-collect.RTL.png';
import flyFlyingObject from './steps/fly-flying-heart.ar.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.RTL.png';
import flyAddScore from './steps/add-variable.ar.gif';
import flyKeepScore from './steps/fly-keep-score.ar.png';
import flyAddScenery from './steps/fly-choose-scenery.RTL.gif';
import flyMoveScenery from './steps/fly-move-scenery.ar.png';
import flySwitchLooks from './steps/fly-switch-costume.ar.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.RTL.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.RTL.png';
import pongBounceAround from './steps/pong-bounce-around.ar.png';
import pongAddPaddle from './steps/pong-add-a-paddle.RTL.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.ar.png';
import pongSelectBallSprite from './steps/pong-select-ball.RTL.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.ar.png';
import pongAddAScore from './steps/add-variable.ar.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.ar.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.ar.png';
import pongResetScore from './steps/pong-reset-score.ar.png';
import pongAddLineSprite from './steps/pong-add-line.RTL.gif';
import pongGameOver from './steps/pong-game-over.ar.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.ar.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.ar.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.RTL.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.RTL.png';
import imagineFlyAround from './steps/imagine-fly-around.ar.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.RTL.png';
import imagineLeftRight from './steps/imagine-left-right.ar.png';
import imagineUpDown from './steps/imagine-up-down.ar.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.ar.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.ar.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.ar.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.RTL.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.ar.png';
import imagineRecordASound from './steps/imagine-record-a-sound.ar.gif';
import imagineChooseSound from './steps/imagine-choose-sound.ar.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.RTL.png';
// Add Effects
import addEffects from './steps/add-effects.ar.png';
// Hide and Show
import hideAndShow from './steps/hide-show.ar.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.ar.png';
// Change Size
import changeSize from './steps/change-size.ar.png';
// Spin
import spinTurn from './steps/spin-turn.ar.png';
import spinPointInDirection from './steps/spin-point-in-direction.ar.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.ar.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.ar.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.ar.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.ar.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.ar.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.ar.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.ar.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.ar.png';
import glideAroundPoint from './steps/glide-around-point.ar.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.ar.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.ar.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.RTL.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.ar.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.ar.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.RTL.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.ar.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.ar.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.ar.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.ar.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.RTL.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.ar.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.RTL.png';
import talesSaySomething from './steps/talking-3-say-something.ar.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.RTL.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.ar.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.RTL.png';
import talesMoveAround from './steps/talking-7-move-around.ar.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.RTL.png';
import talesAnimateTalking from './steps/talking-9-animate.ar.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.RTL.png';
import talesChooseSound from './steps/talking-11-choose-sound.ar.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.ar.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.ar.png';
const arImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {arImages};

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.en.gif';
import introSay from './steps/intro-2-say.en.gif';
import introGreenFlag from './steps/intro-3-green-flag.en.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.en.gif';
import speechSaySomething from './steps/speech-say-something.en.png';
import speechSetVoice from './steps/speech-set-voice.en.png';
import speechMoveAround from './steps/speech-move-around.en.png';
import speechAddBackdrop from './steps/pick-backdrop.LTR.gif';
import speechAddSprite from './steps/speech-add-sprite.LTR.gif';
import speechSong from './steps/speech-song.en.png';
import speechChangeColor from './steps/speech-change-color.en.png';
import speechSpin from './steps/speech-spin.en.png';
import speechGrowShrink from './steps/speech-grow-shrink.en.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.en.png';
import cnGlide from './steps/cn-glide.en.png';
import cnPickSprite from './steps/cn-pick-sprite.LTR.gif';
import cnCollect from './steps/cn-collect.en.png';
import cnVariable from './steps/add-variable.en.gif';
import cnScore from './steps/cn-score.en.png';
import cnBackdrop from './steps/cn-backdrop.en.png';
// Add sprite
import addSprite from './steps/add-sprite.LTR.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.LTR.gif';
import namePlaySound from './steps/name-play-sound.en.png';
import namePickLetter2 from './steps/name-pick-letter2.LTR.gif';
import nameChangeColor from './steps/name-change-color.en.png';
import nameSpin from './steps/name-spin.en.png';
import nameGrow from './steps/name-grow.en.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.LTR.gif';
import musicPlaySound from './steps/music-play-sound.en.png';
import musicMakeSong from './steps/music-make-song.en.png';
import musicMakeBeat from './steps/music-make-beat.en.png';
import musicMakeBeatbox from './steps/music-make-beatbox.en.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.LTR.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.LTR.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.en.png';
import chaseGameUpDown from './steps/chase-game-up-down.en.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.LTR.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.en.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.en.png';
import chaseGameAddVariable from './steps/add-variable.en.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.en.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.LTR.gif';
import popGamePlaySound from './steps/pop-game-play-sound.en.png';
import popGameAddScore from './steps/add-variable.en.gif';
import popGameChangeScore from './steps/pop-game-change-score.en.png';
import popGameRandomPosition from './steps/pop-game-random-position.en.png';
import popGameChangeColor from './steps/pop-game-change-color.en.png';
import popGameResetScore from './steps/pop-game-reset-score.en.png';
// Animate A Character
import animateCharPickBackdrop from './steps/pick-backdrop.LTR.gif';
import animateCharPickSprite from './steps/animate-char-pick-sprite.LTR.gif';
import animateCharSaySomething from './steps/animate-char-say-something.en.png';
import animateCharAddSound from './steps/animate-char-add-sound.en.png';
import animateCharTalk from './steps/animate-char-talk.en.png';
import animateCharMove from './steps/animate-char-move.en.png';
import animateCharJump from './steps/animate-char-jump.en.png';
import animateCharChangeColor from './steps/animate-char-change-color.en.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.LTR.gif';
import storyPickSprite from './steps/story-pick-sprite.LTR.gif';
import storySaySomething from './steps/story-say-something.en.png';
import storyPickSprite2 from './steps/story-pick-sprite2.LTR.gif';
import storyFlip from './steps/story-flip.en.gif';
import storyConversation from './steps/story-conversation.en.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.LTR.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.en.png';
import storyHideCharacter from './steps/story-hide-character.en.png';
import storyShowCharacter from './steps/story-show-character.en.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.en.gif';
import videoPet from './steps/video-pet.en.png';
import videoAnimate from './steps/video-animate.en.png';
import videoPop from './steps/video-pop.en.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.LTR.gif';
import flyChooseCharacter from './steps/fly-choose-character.LTR.png';
import flySaySomething from './steps/fly-say-something.en.png';
import flyMoveArrows from './steps/fly-make-interactive.en.png';
import flyChooseObject from './steps/fly-object-to-collect.LTR.png';
import flyFlyingObject from './steps/fly-flying-heart.en.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.LTR.png';
import flyAddScore from './steps/add-variable.en.gif';
import flyKeepScore from './steps/fly-keep-score.en.png';
import flyAddScenery from './steps/fly-choose-scenery.LTR.gif';
import flyMoveScenery from './steps/fly-move-scenery.en.png';
import flySwitchLooks from './steps/fly-switch-costume.en.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.LTR.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.LTR.png';
import pongBounceAround from './steps/pong-bounce-around.en.png';
import pongAddPaddle from './steps/pong-add-a-paddle.LTR.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.en.png';
import pongSelectBallSprite from './steps/pong-select-ball.LTR.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.en.png';
import pongAddAScore from './steps/add-variable.en.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.en.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.en.png';
import pongResetScore from './steps/pong-reset-score.en.png';
import pongAddLineSprite from './steps/pong-add-line.LTR.gif';
import pongGameOver from './steps/pong-game-over.en.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.en.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.en.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.LTR.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.LTR.png';
import imagineFlyAround from './steps/imagine-fly-around.en.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.LTR.png';
import imagineLeftRight from './steps/imagine-left-right.en.png';
import imagineUpDown from './steps/imagine-up-down.en.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.en.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.en.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.en.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.LTR.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.en.png';
import imagineRecordASound from './steps/imagine-record-a-sound.en.gif';
import imagineChooseSound from './steps/imagine-choose-sound.en.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.LTR.png';
// Add Effects
import addEffects from './steps/add-effects.en.png';
// Hide and Show
import hideAndShow from './steps/hide-show.en.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.en.png';
// Change Size
import changeSize from './steps/change-size.en.png';
// Spin
import spinTurn from './steps/spin-turn.en.png';
import spinPointInDirection from './steps/spin-point-in-direction.en.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.en.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.en.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.en.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.en.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.en.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.en.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.en.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.en.png';
import glideAroundPoint from './steps/glide-around-point.en.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.en.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.en.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.LTR.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.en.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.en.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.LTR.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.en.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.en.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.en.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.en.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.LTR.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.en.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.LTR.png';
import talesSaySomething from './steps/talking-3-say-something.en.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.LTR.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.en.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.LTR.png';
import talesMoveAround from './steps/talking-7-move-around.en.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.LTR.png';
import talesAnimateTalking from './steps/talking-9-animate.en.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.LTR.png';
import talesChooseSound from './steps/talking-11-choose-sound.en.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.en.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.en.png';
const enImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {enImages};

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.es.gif';
import introSay from './steps/intro-2-say.es.gif';
import introGreenFlag from './steps/intro-3-green-flag.es.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.es.gif';
import speechSaySomething from './steps/speech-say-something.es.png';
import speechSetVoice from './steps/speech-set-voice.es.png';
import speechMoveAround from './steps/speech-move-around.es.png';
import speechAddBackdrop from './steps/pick-backdrop.LTR.gif';
import speechAddSprite from './steps/speech-add-sprite.LTR.gif';
import speechSong from './steps/speech-song.es.png';
import speechChangeColor from './steps/speech-change-color.es.png';
import speechSpin from './steps/speech-spin.es.png';
import speechGrowShrink from './steps/speech-grow-shrink.es.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.es.png';
import cnGlide from './steps/cn-glide.es.png';
import cnPickSprite from './steps/cn-pick-sprite.LTR.gif';
import cnCollect from './steps/cn-collect.es.png';
import cnVariable from './steps/add-variable.es.gif';
import cnScore from './steps/cn-score.es.png';
import cnBackdrop from './steps/cn-backdrop.es.png';
// Add sprite
import addSprite from './steps/add-sprite.LTR.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.LTR.gif';
import namePlaySound from './steps/name-play-sound.es.png';
import namePickLetter2 from './steps/name-pick-letter2.LTR.gif';
import nameChangeColor from './steps/name-change-color.es.png';
import nameSpin from './steps/name-spin.es.png';
import nameGrow from './steps/name-grow.es.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.LTR.gif';
import musicPlaySound from './steps/music-play-sound.es.png';
import musicMakeSong from './steps/music-make-song.es.png';
import musicMakeBeat from './steps/music-make-beat.es.png';
import musicMakeBeatbox from './steps/music-make-beatbox.es.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.LTR.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.LTR.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.es.png';
import chaseGameUpDown from './steps/chase-game-up-down.es.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.LTR.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.es.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.es.png';
import chaseGameAddVariable from './steps/add-variable.es.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.es.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.LTR.gif';
import popGamePlaySound from './steps/pop-game-play-sound.es.png';
import popGameAddScore from './steps/add-variable.es.gif';
import popGameChangeScore from './steps/pop-game-change-score.es.png';
import popGameRandomPosition from './steps/pop-game-random-position.es.png';
import popGameChangeColor from './steps/pop-game-change-color.es.png';
import popGameResetScore from './steps/pop-game-reset-score.es.png';
// Animate A Character
import animateCharPickBackdrop from './steps/pick-backdrop.LTR.gif';
import animateCharPickSprite from './steps/animate-char-pick-sprite.LTR.gif';
import animateCharSaySomething from './steps/animate-char-say-something.es.png';
import animateCharAddSound from './steps/animate-char-add-sound.es.png';
import animateCharTalk from './steps/animate-char-talk.es.png';
import animateCharMove from './steps/animate-char-move.es.png';
import animateCharJump from './steps/animate-char-jump.es.png';
import animateCharChangeColor from './steps/animate-char-change-color.es.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.LTR.gif';
import storyPickSprite from './steps/story-pick-sprite.LTR.gif';
import storySaySomething from './steps/story-say-something.es.png';
import storyPickSprite2 from './steps/story-pick-sprite2.LTR.gif';
import storyFlip from './steps/story-flip.es.gif';
import storyConversation from './steps/story-conversation.es.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.LTR.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.es.png';
import storyHideCharacter from './steps/story-hide-character.es.png';
import storyShowCharacter from './steps/story-show-character.es.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.es.gif';
import videoPet from './steps/video-pet.es.png';
import videoAnimate from './steps/video-animate.es.png';
import videoPop from './steps/video-pop.es.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.LTR.gif';
import flyChooseCharacter from './steps/fly-choose-character.LTR.png';
import flySaySomething from './steps/fly-say-something.es.png';
import flyMoveArrows from './steps/fly-make-interactive.es.png';
import flyChooseObject from './steps/fly-object-to-collect.LTR.png';
import flyFlyingObject from './steps/fly-flying-heart.es.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.LTR.png';
import flyAddScore from './steps/add-variable.es.gif';
import flyKeepScore from './steps/fly-keep-score.es.png';
import flyAddScenery from './steps/fly-choose-scenery.LTR.gif';
import flyMoveScenery from './steps/fly-move-scenery.es.png';
import flySwitchLooks from './steps/fly-switch-costume.es.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.LTR.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.LTR.png';
import pongBounceAround from './steps/pong-bounce-around.es.png';
import pongAddPaddle from './steps/pong-add-a-paddle.LTR.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.es.png';
import pongSelectBallSprite from './steps/pong-select-ball.LTR.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.es.png';
import pongAddAScore from './steps/add-variable.es.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.es.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.es.png';
import pongResetScore from './steps/pong-reset-score.es.png';
import pongAddLineSprite from './steps/pong-add-line.LTR.gif';
import pongGameOver from './steps/pong-game-over.es.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.es.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.es.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.LTR.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.LTR.png';
import imagineFlyAround from './steps/imagine-fly-around.es.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.LTR.png';
import imagineLeftRight from './steps/imagine-left-right.es.png';
import imagineUpDown from './steps/imagine-up-down.es.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.es.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.es.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.es.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.LTR.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.es.png';
import imagineRecordASound from './steps/imagine-record-a-sound.es.gif';
import imagineChooseSound from './steps/imagine-choose-sound.es.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.LTR.png';
// Add Effects
import addEffects from './steps/add-effects.es.png';
// Hide and Show
import hideAndShow from './steps/hide-show.es.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.es.png';
// Change Size
import changeSize from './steps/change-size.es.png';
// Spin
import spinTurn from './steps/spin-turn.es.png';
import spinPointInDirection from './steps/spin-point-in-direction.es.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.es.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.es.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.es.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.es.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.es.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.es.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.es.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.es.png';
import glideAroundPoint from './steps/glide-around-point.es.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.es.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.es.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.LTR.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.es.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.es.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.LTR.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.es.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.es.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.es.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.es.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.LTR.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.es.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.LTR.png';
import talesSaySomething from './steps/talking-3-say-something.es.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.LTR.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.es.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.LTR.png';
import talesMoveAround from './steps/talking-7-move-around.es.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.LTR.png';
import talesAnimateTalking from './steps/talking-9-animate.es.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.LTR.png';
import talesChooseSound from './steps/talking-11-choose-sound.es.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.es.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.es.png';
const esImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {esImages};

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.fr.gif';
import introSay from './steps/intro-2-say.fr.gif';
import introGreenFlag from './steps/intro-3-green-flag.fr.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.fr.gif';
import speechSaySomething from './steps/speech-say-something.fr.png';
import speechSetVoice from './steps/speech-set-voice.fr.png';
import speechMoveAround from './steps/speech-move-around.fr.png';
import speechAddBackdrop from './steps/pick-backdrop.LTR.gif';
import speechAddSprite from './steps/speech-add-sprite.LTR.gif';
import speechSong from './steps/speech-song.fr.png';
import speechChangeColor from './steps/speech-change-color.fr.png';
import speechSpin from './steps/speech-spin.fr.png';
import speechGrowShrink from './steps/speech-grow-shrink.fr.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.fr.png';
import cnGlide from './steps/cn-glide.fr.png';
import cnPickSprite from './steps/cn-pick-sprite.LTR.gif';
import cnCollect from './steps/cn-collect.fr.png';
import cnVariable from './steps/add-variable.fr.gif';
import cnScore from './steps/cn-score.fr.png';
import cnBackdrop from './steps/cn-backdrop.fr.png';
// Add sprite
import addSprite from './steps/add-sprite.LTR.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.LTR.gif';
import namePlaySound from './steps/name-play-sound.fr.png';
import namePickLetter2 from './steps/name-pick-letter2.LTR.gif';
import nameChangeColor from './steps/name-change-color.fr.png';
import nameSpin from './steps/name-spin.fr.png';
import nameGrow from './steps/name-grow.fr.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.LTR.gif';
import musicPlaySound from './steps/music-play-sound.fr.png';
import musicMakeSong from './steps/music-make-song.fr.png';
import musicMakeBeat from './steps/music-make-beat.fr.png';
import musicMakeBeatbox from './steps/music-make-beatbox.fr.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.LTR.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.LTR.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.fr.png';
import chaseGameUpDown from './steps/chase-game-up-down.fr.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.LTR.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.fr.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.fr.png';
import chaseGameAddVariable from './steps/add-variable.fr.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.fr.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.LTR.gif';
import popGamePlaySound from './steps/pop-game-play-sound.fr.png';
import popGameAddScore from './steps/add-variable.fr.gif';
import popGameChangeScore from './steps/pop-game-change-score.fr.png';
import popGameRandomPosition from './steps/pop-game-random-position.fr.png';
import popGameChangeColor from './steps/pop-game-change-color.fr.png';
import popGameResetScore from './steps/pop-game-reset-score.fr.png';
// Animate A Character
import animateCharPickBackdrop from './steps/pick-backdrop.LTR.gif';
import animateCharPickSprite from './steps/animate-char-pick-sprite.LTR.gif';
import animateCharSaySomething from './steps/animate-char-say-something.fr.png';
import animateCharAddSound from './steps/animate-char-add-sound.fr.png';
import animateCharTalk from './steps/animate-char-talk.fr.png';
import animateCharMove from './steps/animate-char-move.fr.png';
import animateCharJump from './steps/animate-char-jump.fr.png';
import animateCharChangeColor from './steps/animate-char-change-color.fr.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.LTR.gif';
import storyPickSprite from './steps/story-pick-sprite.LTR.gif';
import storySaySomething from './steps/story-say-something.fr.png';
import storyPickSprite2 from './steps/story-pick-sprite2.LTR.gif';
import storyFlip from './steps/story-flip.fr.gif';
import storyConversation from './steps/story-conversation.fr.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.LTR.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.fr.png';
import storyHideCharacter from './steps/story-hide-character.fr.png';
import storyShowCharacter from './steps/story-show-character.fr.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.fr.gif';
import videoPet from './steps/video-pet.fr.png';
import videoAnimate from './steps/video-animate.fr.png';
import videoPop from './steps/video-pop.fr.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.LTR.gif';
import flyChooseCharacter from './steps/fly-choose-character.LTR.png';
import flySaySomething from './steps/fly-say-something.fr.png';
import flyMoveArrows from './steps/fly-make-interactive.fr.png';
import flyChooseObject from './steps/fly-object-to-collect.LTR.png';
import flyFlyingObject from './steps/fly-flying-heart.fr.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.LTR.png';
import flyAddScore from './steps/add-variable.fr.gif';
import flyKeepScore from './steps/fly-keep-score.fr.png';
import flyAddScenery from './steps/fly-choose-scenery.LTR.gif';
import flyMoveScenery from './steps/fly-move-scenery.fr.png';
import flySwitchLooks from './steps/fly-switch-costume.fr.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.LTR.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.LTR.png';
import pongBounceAround from './steps/pong-bounce-around.fr.png';
import pongAddPaddle from './steps/pong-add-a-paddle.LTR.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.fr.png';
import pongSelectBallSprite from './steps/pong-select-ball.LTR.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.fr.png';
import pongAddAScore from './steps/add-variable.fr.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.fr.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.fr.png';
import pongResetScore from './steps/pong-reset-score.fr.png';
import pongAddLineSprite from './steps/pong-add-line.LTR.gif';
import pongGameOver from './steps/pong-game-over.fr.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.fr.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.fr.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.LTR.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.LTR.png';
import imagineFlyAround from './steps/imagine-fly-around.fr.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.LTR.png';
import imagineLeftRight from './steps/imagine-left-right.fr.png';
import imagineUpDown from './steps/imagine-up-down.fr.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.fr.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.fr.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.fr.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.LTR.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.fr.png';
import imagineRecordASound from './steps/imagine-record-a-sound.fr.gif';
import imagineChooseSound from './steps/imagine-choose-sound.fr.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.LTR.png';
// Add Effects
import addEffects from './steps/add-effects.fr.png';
// Hide and Show
import hideAndShow from './steps/hide-show.fr.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.fr.png';
// Change Size
import changeSize from './steps/change-size.fr.png';
// Spin
import spinTurn from './steps/spin-turn.fr.png';
import spinPointInDirection from './steps/spin-point-in-direction.fr.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.fr.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.fr.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.fr.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.fr.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.fr.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.fr.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.fr.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.fr.png';
import glideAroundPoint from './steps/glide-around-point.fr.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.fr.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.fr.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.LTR.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.fr.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.fr.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.LTR.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.fr.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.fr.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.fr.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.fr.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.LTR.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.fr.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.LTR.png';
import talesSaySomething from './steps/talking-3-say-something.fr.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.LTR.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.fr.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.LTR.png';
import talesMoveAround from './steps/talking-7-move-around.fr.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.LTR.png';
import talesAnimateTalking from './steps/talking-9-animate.fr.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.LTR.png';
import talesChooseSound from './steps/talking-11-choose-sound.fr.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.fr.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.fr.png';
const frImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {frImages};

View File

@@ -0,0 +1,3 @@
export default {
// tw: remove decks. we don't use them.
};

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.ja.gif';
import introSay from './steps/intro-2-say.ja.gif';
import introGreenFlag from './steps/intro-3-green-flag.ja.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.ja.gif';
import speechSaySomething from './steps/speech-say-something.ja.png';
import speechSetVoice from './steps/speech-set-voice.ja.png';
import speechMoveAround from './steps/speech-move-around.ja.png';
import speechAddBackdrop from './steps/pick-backdrop.LTR.gif';
import speechAddSprite from './steps/speech-add-sprite.LTR.gif';
import speechSong from './steps/speech-song.ja.png';
import speechChangeColor from './steps/speech-change-color.ja.png';
import speechSpin from './steps/speech-spin.ja.png';
import speechGrowShrink from './steps/speech-grow-shrink.ja.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.ja.png';
import cnGlide from './steps/cn-glide.ja.png';
import cnPickSprite from './steps/cn-pick-sprite.LTR.gif';
import cnCollect from './steps/cn-collect.ja.png';
import cnVariable from './steps/add-variable.ja.gif';
import cnScore from './steps/cn-score.ja.png';
import cnBackdrop from './steps/cn-backdrop.ja.png';
// Add sprite
import addSprite from './steps/add-sprite.LTR.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.LTR.gif';
import namePlaySound from './steps/name-play-sound.ja.png';
import namePickLetter2 from './steps/name-pick-letter2.LTR.gif';
import nameChangeColor from './steps/name-change-color.ja.png';
import nameSpin from './steps/name-spin.ja.png';
import nameGrow from './steps/name-grow.ja.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.LTR.gif';
import musicPlaySound from './steps/music-play-sound.ja.png';
import musicMakeSong from './steps/music-make-song.ja.png';
import musicMakeBeat from './steps/music-make-beat.ja.png';
import musicMakeBeatbox from './steps/music-make-beatbox.ja.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.LTR.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.LTR.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.ja.png';
import chaseGameUpDown from './steps/chase-game-up-down.ja.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.LTR.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.ja.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.ja.png';
import chaseGameAddVariable from './steps/add-variable.ja.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.ja.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.LTR.gif';
import popGamePlaySound from './steps/pop-game-play-sound.ja.png';
import popGameAddScore from './steps/add-variable.ja.gif';
import popGameChangeScore from './steps/pop-game-change-score.ja.png';
import popGameRandomPosition from './steps/pop-game-random-position.ja.png';
import popGameChangeColor from './steps/pop-game-change-color.ja.png';
import popGameResetScore from './steps/pop-game-reset-score.ja.png';
// Animate A Character
import animateCharPickBackdrop from './steps/pick-backdrop.LTR.gif';
import animateCharPickSprite from './steps/animate-char-pick-sprite.LTR.gif';
import animateCharSaySomething from './steps/animate-char-say-something.ja.png';
import animateCharAddSound from './steps/animate-char-add-sound.ja.png';
import animateCharTalk from './steps/animate-char-talk.ja.png';
import animateCharMove from './steps/animate-char-move.ja.png';
import animateCharJump from './steps/animate-char-jump.ja.png';
import animateCharChangeColor from './steps/animate-char-change-color.ja.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.LTR.gif';
import storyPickSprite from './steps/story-pick-sprite.LTR.gif';
import storySaySomething from './steps/story-say-something.ja.png';
import storyPickSprite2 from './steps/story-pick-sprite2.LTR.gif';
import storyFlip from './steps/story-flip.ja.gif';
import storyConversation from './steps/story-conversation.ja.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.LTR.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.ja.png';
import storyHideCharacter from './steps/story-hide-character.ja.png';
import storyShowCharacter from './steps/story-show-character.ja.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.ja.gif';
import videoPet from './steps/video-pet.ja.png';
import videoAnimate from './steps/video-animate.ja.png';
import videoPop from './steps/video-pop.ja.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.LTR.gif';
import flyChooseCharacter from './steps/fly-choose-character.LTR.png';
import flySaySomething from './steps/fly-say-something.ja.png';
import flyMoveArrows from './steps/fly-make-interactive.ja.png';
import flyChooseObject from './steps/fly-object-to-collect.LTR.png';
import flyFlyingObject from './steps/fly-flying-heart.ja.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.LTR.png';
import flyAddScore from './steps/add-variable.ja.gif';
import flyKeepScore from './steps/fly-keep-score.ja.png';
import flyAddScenery from './steps/fly-choose-scenery.LTR.gif';
import flyMoveScenery from './steps/fly-move-scenery.ja.png';
import flySwitchLooks from './steps/fly-switch-costume.ja.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.LTR.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.LTR.png';
import pongBounceAround from './steps/pong-bounce-around.ja.png';
import pongAddPaddle from './steps/pong-add-a-paddle.LTR.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.ja.png';
import pongSelectBallSprite from './steps/pong-select-ball.LTR.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.ja.png';
import pongAddAScore from './steps/add-variable.ja.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.ja.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.ja.png';
import pongResetScore from './steps/pong-reset-score.ja.png';
import pongAddLineSprite from './steps/pong-add-line.LTR.gif';
import pongGameOver from './steps/pong-game-over.ja.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.ja.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.ja.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.LTR.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.LTR.png';
import imagineFlyAround from './steps/imagine-fly-around.ja.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.LTR.png';
import imagineLeftRight from './steps/imagine-left-right.ja.png';
import imagineUpDown from './steps/imagine-up-down.ja.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.ja.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.ja.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.ja.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.LTR.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.ja.png';
import imagineRecordASound from './steps/imagine-record-a-sound.ja.gif';
import imagineChooseSound from './steps/imagine-choose-sound.ja.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.LTR.png';
// Add Effects
import addEffects from './steps/add-effects.ja.png';
// Hide and Show
import hideAndShow from './steps/hide-show.ja.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.ja.png';
// Change Size
import changeSize from './steps/change-size.ja.png';
// Spin
import spinTurn from './steps/spin-turn.ja.png';
import spinPointInDirection from './steps/spin-point-in-direction.ja.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.ja.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.ja.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.ja.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.ja.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.ja.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.ja.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.ja.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.ja.png';
import glideAroundPoint from './steps/glide-around-point.ja.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.ja.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.ja.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.LTR.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.ja.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.ja.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.LTR.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.ja.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.ja.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.ja.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.ja.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.LTR.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.ja.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.LTR.png';
import talesSaySomething from './steps/talking-3-say-something.ja.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.LTR.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.ja.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.LTR.png';
import talesMoveAround from './steps/talking-7-move-around.ja.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.LTR.png';
import talesAnimateTalking from './steps/talking-9-animate.ja.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.LTR.png';
import talesChooseSound from './steps/talking-11-choose-sound.ja.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.ja.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.ja.png';
const jaImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {jaImages};

View File

@@ -0,0 +1,405 @@
// Intro
import introMove from './steps/intro-1-move.pt_BR.gif';
import introSay from './steps/intro-2-say.pt_BR.gif';
import introGreenFlag from './steps/intro-3-green-flag.pt_BR.gif';
// Text to Speech
import speechAddExtension from './steps/speech-add-extension.pt_BR.gif';
import speechSaySomething from './steps/speech-say-something.pt_BR.png';
import speechSetVoice from './steps/speech-set-voice.pt_BR.png';
import speechMoveAround from './steps/speech-move-around.pt_BR.png';
import speechAddBackdrop from './steps/pick-backdrop.LTR.gif';
import speechAddSprite from './steps/speech-add-sprite.LTR.gif';
import speechSong from './steps/speech-song.pt_BR.png';
import speechChangeColor from './steps/speech-change-color.pt_BR.png';
import speechSpin from './steps/speech-spin.pt_BR.png';
import speechGrowShrink from './steps/speech-grow-shrink.pt_BR.png';
// Cartoon Network
import cnShowCharacter from './steps/cn-show-character.LTR.gif';
import cnSay from './steps/cn-say.pt_BR.png';
import cnGlide from './steps/cn-glide.pt_BR.png';
import cnPickSprite from './steps/cn-pick-sprite.LTR.gif';
import cnCollect from './steps/cn-collect.pt_BR.png';
import cnVariable from './steps/add-variable.pt_BR.gif';
import cnScore from './steps/cn-score.pt_BR.png';
import cnBackdrop from './steps/cn-backdrop.pt_BR.png';
// Add sprite
import addSprite from './steps/add-sprite.LTR.gif';
// Animate a name
import namePickLetter from './steps/name-pick-letter.LTR.gif';
import namePlaySound from './steps/name-play-sound.pt_BR.png';
import namePickLetter2 from './steps/name-pick-letter2.LTR.gif';
import nameChangeColor from './steps/name-change-color.pt_BR.png';
import nameSpin from './steps/name-spin.pt_BR.png';
import nameGrow from './steps/name-grow.pt_BR.png';
// Make Music
import musicPickInstrument from './steps/music-pick-instrument.LTR.gif';
import musicPlaySound from './steps/music-play-sound.pt_BR.png';
import musicMakeSong from './steps/music-make-song.pt_BR.png';
import musicMakeBeat from './steps/music-make-beat.pt_BR.png';
import musicMakeBeatbox from './steps/music-make-beatbox.pt_BR.png';
// Chase-Game
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.LTR.gif';
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.LTR.gif';
import chaseGameRightLeft from './steps/chase-game-right-left.pt_BR.png';
import chaseGameUpDown from './steps/chase-game-up-down.pt_BR.png';
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.LTR.gif';
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.pt_BR.png';
import chaseGamePlaySound from './steps/chase-game-play-sound.pt_BR.png';
import chaseGameAddVariable from './steps/add-variable.pt_BR.gif';
import chaseGameChangeScore from './steps/chase-game-change-score.pt_BR.png';
// Clicker-Game (Pop Game)
import popGamePickSprite from './steps/pop-game-pick-sprite.LTR.gif';
import popGamePlaySound from './steps/pop-game-play-sound.pt_BR.png';
import popGameAddScore from './steps/add-variable.pt_BR.gif';
import popGameChangeScore from './steps/pop-game-change-score.pt_BR.png';
import popGameRandomPosition from './steps/pop-game-random-position.pt_BR.png';
import popGameChangeColor from './steps/pop-game-change-color.pt_BR.png';
import popGameResetScore from './steps/pop-game-reset-score.pt_BR.png';
// Animate A Character
import animateCharPickBackdrop from './steps/pick-backdrop.LTR.gif';
import animateCharPickSprite from './steps/animate-char-pick-sprite.LTR.gif';
import animateCharSaySomething from './steps/animate-char-say-something.pt_BR.png';
import animateCharAddSound from './steps/animate-char-add-sound.pt_BR.png';
import animateCharTalk from './steps/animate-char-talk.pt_BR.png';
import animateCharMove from './steps/animate-char-move.pt_BR.png';
import animateCharJump from './steps/animate-char-jump.pt_BR.png';
import animateCharChangeColor from './steps/animate-char-change-color.pt_BR.png';
// Tell A Story
import storyPickBackdrop from './steps/story-pick-backdrop.LTR.gif';
import storyPickSprite from './steps/story-pick-sprite.LTR.gif';
import storySaySomething from './steps/story-say-something.pt_BR.png';
import storyPickSprite2 from './steps/story-pick-sprite2.LTR.gif';
import storyFlip from './steps/story-flip.pt_BR.gif';
import storyConversation from './steps/story-conversation.pt_BR.png';
import storyPickBackdrop2 from './steps/story-pick-backdrop2.LTR.gif';
import storySwitchBackdrop from './steps/story-switch-backdrop.pt_BR.png';
import storyHideCharacter from './steps/story-hide-character.pt_BR.png';
import storyShowCharacter from './steps/story-show-character.pt_BR.png';
// Video Sensing
import videoAddExtension from './steps/video-add-extension.pt_BR.gif';
import videoPet from './steps/video-pet.pt_BR.png';
import videoAnimate from './steps/video-animate.pt_BR.png';
import videoPop from './steps/video-pop.pt_BR.png';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.LTR.gif';
import flyChooseCharacter from './steps/fly-choose-character.LTR.png';
import flySaySomething from './steps/fly-say-something.pt_BR.png';
import flyMoveArrows from './steps/fly-make-interactive.pt_BR.png';
import flyChooseObject from './steps/fly-object-to-collect.LTR.png';
import flyFlyingObject from './steps/fly-flying-heart.pt_BR.png';
import flySelectFlyingSprite from './steps/fly-select-flyer.LTR.png';
import flyAddScore from './steps/add-variable.pt_BR.gif';
import flyKeepScore from './steps/fly-keep-score.pt_BR.png';
import flyAddScenery from './steps/fly-choose-scenery.LTR.gif';
import flyMoveScenery from './steps/fly-move-scenery.pt_BR.png';
import flySwitchLooks from './steps/fly-switch-costume.pt_BR.png';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.LTR.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.LTR.png';
import pongBounceAround from './steps/pong-bounce-around.pt_BR.png';
import pongAddPaddle from './steps/pong-add-a-paddle.LTR.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.pt_BR.png';
import pongSelectBallSprite from './steps/pong-select-ball.LTR.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.pt_BR.png';
import pongAddAScore from './steps/add-variable.pt_BR.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.pt_BR.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.pt_BR.png';
import pongResetScore from './steps/pong-reset-score.pt_BR.png';
import pongAddLineSprite from './steps/pong-add-line.LTR.gif';
import pongGameOver from './steps/pong-game-over.pt_BR.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.pt_BR.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.pt_BR.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.LTR.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.LTR.png';
import imagineFlyAround from './steps/imagine-fly-around.pt_BR.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.LTR.png';
import imagineLeftRight from './steps/imagine-left-right.pt_BR.png';
import imagineUpDown from './steps/imagine-up-down.pt_BR.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.pt_BR.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.pt_BR.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.pt_BR.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.LTR.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.pt_BR.png';
import imagineRecordASound from './steps/imagine-record-a-sound.pt_BR.gif';
import imagineChooseSound from './steps/imagine-choose-sound.pt_BR.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.LTR.png';
// Add Effects
import addEffects from './steps/add-effects.pt_BR.png';
// Hide and Show
import hideAndShow from './steps/hide-show.pt_BR.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.pt_BR.png';
// Change Size
import changeSize from './steps/change-size.pt_BR.png';
// Spin
import spinTurn from './steps/spin-turn.pt_BR.png';
import spinPointInDirection from './steps/spin-point-in-direction.pt_BR.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.pt_BR.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.pt_BR.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.pt_BR.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.pt_BR.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.pt_BR.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.pt_BR.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.pt_BR.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.pt_BR.png';
import glideAroundPoint from './steps/glide-around-point.pt_BR.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.pt_BR.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.pt_BR.png';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.LTR.png';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.pt_BR.png';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.pt_BR.png';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.LTR.png';
import codeCartoonJump from './steps/code-cartoon-07-jump.pt_BR.png';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.pt_BR.png';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.pt_BR.png';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.pt_BR.png';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.LTR.png';
// Talking Tales
import talesAddExtension from './steps/speech-add-extension.pt_BR.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.LTR.png';
import talesSaySomething from './steps/talking-3-say-something.pt_BR.png';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.LTR.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.pt_BR.png';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.LTR.png';
import talesMoveAround from './steps/talking-7-move-around.pt_BR.png';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.LTR.png';
import talesAnimateTalking from './steps/talking-9-animate.pt_BR.png';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.LTR.png';
import talesChooseSound from './steps/talking-11-choose-sound.pt_BR.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.pt_BR.png';
import talesAskAnswer from './steps/talking-13-ask-and-answer.pt_BR.png';
const ptBrImages = {
// Intro
introMove: introMove,
introSay: introSay,
introGreenFlag: introGreenFlag,
// Text to Speech
speechAddExtension: speechAddExtension,
speechSaySomething: speechSaySomething,
speechSetVoice: speechSetVoice,
speechMoveAround: speechMoveAround,
speechAddBackdrop: speechAddBackdrop,
speechAddSprite: speechAddSprite,
speechSong: speechSong,
speechChangeColor: speechChangeColor,
speechSpin: speechSpin,
speechGrowShrink: speechGrowShrink,
// Cartoon Network
cnShowCharacter: cnShowCharacter,
cnSay: cnSay,
cnGlide: cnGlide,
cnPickSprite: cnPickSprite,
cnCollect: cnCollect,
cnVariable: cnVariable,
cnScore: cnScore,
cnBackdrop: cnBackdrop,
// Add sprite
addSprite: addSprite,
// Animate a name
namePickLetter: namePickLetter,
namePlaySound: namePlaySound,
namePickLetter2: namePickLetter2,
nameChangeColor: nameChangeColor,
nameSpin: nameSpin,
nameGrow: nameGrow,
// Make-Music
musicPickInstrument: musicPickInstrument,
musicPlaySound: musicPlaySound,
musicMakeSong: musicMakeSong,
musicMakeBeat: musicMakeBeat,
musicMakeBeatbox: musicMakeBeatbox,
// Chase-Game
chaseGameAddBackdrop: chaseGameAddBackdrop,
chaseGameAddSprite1: chaseGameAddSprite1,
chaseGameRightLeft: chaseGameRightLeft,
chaseGameUpDown: chaseGameUpDown,
chaseGameAddSprite2: chaseGameAddSprite2,
chaseGameMoveRandomly: chaseGameMoveRandomly,
chaseGamePlaySound: chaseGamePlaySound,
chaseGameAddVariable: chaseGameAddVariable,
chaseGameChangeScore: chaseGameChangeScore,
// Make-A-Pop/Clicker Game
popGamePickSprite: popGamePickSprite,
popGamePlaySound: popGamePlaySound,
popGameAddScore: popGameAddScore,
popGameChangeScore: popGameChangeScore,
popGameRandomPosition: popGameRandomPosition,
popGameChangeColor: popGameChangeColor,
popGameResetScore: popGameResetScore,
// Animate A Character
animateCharPickBackdrop: animateCharPickBackdrop,
animateCharPickSprite: animateCharPickSprite,
animateCharSaySomething: animateCharSaySomething,
animateCharAddSound: animateCharAddSound,
animateCharTalk: animateCharTalk,
animateCharMove: animateCharMove,
animateCharJump: animateCharJump,
animateCharChangeColor: animateCharChangeColor,
// Tell A Story
storyPickBackdrop: storyPickBackdrop,
storyPickSprite: storyPickSprite,
storySaySomething: storySaySomething,
storyPickSprite2: storyPickSprite2,
storyFlip: storyFlip,
storyConversation: storyConversation,
storyPickBackdrop2: storyPickBackdrop2,
storySwitchBackdrop: storySwitchBackdrop,
storyHideCharacter: storyHideCharacter,
storyShowCharacter: storyShowCharacter,
// Video Sensing
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {ptBrImages};

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Some files were not shown because too many files have changed in this diff Show More