Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a83d9369dc | ||
|
|
012bb612ad | ||
|
|
b9e7bde676 | ||
|
|
992f012ff3 | ||
|
|
c53b88dbad |
No files matched your search
@@ -4,6 +4,7 @@
|
||||
# NPM
|
||||
/node_modules
|
||||
npm-*
|
||||
/package-lock.json
|
||||
|
||||
# Testing
|
||||
/.nyc_output
|
||||
|
||||
@@ -14,18 +14,12 @@ env:
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
before_install:
|
||||
# package-lock.json was introduced in npm@5
|
||||
- '[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest' # skipped when using node 9
|
||||
- npm install -g greenkeeper-lockfile
|
||||
install:
|
||||
- npm --production=false install
|
||||
- npm --production=false update
|
||||
before_script: greenkeeper-lockfile-update
|
||||
script:
|
||||
- npm test
|
||||
- if [ "$TRAVIS_EVENT_TYPE" == "pull_request" ] && [ "$TRAVIS_BRANCH" == "master" ]; then npm run test:smoke; fi
|
||||
after_script: greenkeeper-lockfile-upload
|
||||
before_deploy:
|
||||
- >
|
||||
if [ -z "$BEFORE_DEPLOY_RAN" ]; then
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
"react-dom": "16.2.0",
|
||||
"react-draggable": "3.0.5",
|
||||
"react-ga": "2.5.3",
|
||||
"react-intl": "2.9.0",
|
||||
"react-intl": "2.8.0",
|
||||
"react-modal": "3.6.1",
|
||||
"react-popover": "0.5.10",
|
||||
"react-redux": "5.0.7",
|
||||
@@ -100,18 +100,19 @@
|
||||
"react-test-renderer": "16.2.0",
|
||||
"react-tooltip": "3.8.0",
|
||||
"react-virtualized": "9.20.1",
|
||||
"react-visibility-sensor": "5.0.2",
|
||||
"redux": "3.7.2",
|
||||
"redux-mock-store": "^1.2.3",
|
||||
"redux-throttle": "0.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20190114210212",
|
||||
"scratch-l10n": "3.3.20190711005050",
|
||||
"scratch-blocks": "0.1.0-prerelease.1561987297",
|
||||
"scratch-paint": "0.2.0-prerelease.20190624133806",
|
||||
"scratch-render": "0.1.0-prerelease.20190605151415",
|
||||
"scratch-storage": "1.3.2",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190523193400",
|
||||
"scratch-vm": "0.2.0-prerelease.20190628161443",
|
||||
"scratch-blocks": "0.1.0-prerelease.1557852594",
|
||||
"scratch-l10n": "3.3.20190516144548",
|
||||
"scratch-paint": "0.2.0-prerelease.20190516171147",
|
||||
"scratch-render": "0.1.0-prerelease.20190419202157",
|
||||
"scratch-storage": "1.3.1",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190419183947",
|
||||
"scratch-vm": "0.2.0-prerelease.20190515153227",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
||||
@@ -162,7 +162,6 @@
|
||||
}
|
||||
|
||||
.step-body {
|
||||
width: 100%;
|
||||
background: $ui-white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -170,9 +169,6 @@
|
||||
align-items: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
/* Min height prevents layout changing when images change */
|
||||
min-height: 256px;
|
||||
}
|
||||
|
||||
.step-video {
|
||||
@@ -181,8 +177,6 @@
|
||||
|
||||
.step-image {
|
||||
max-width: 450px;
|
||||
max-height: 200px;
|
||||
object-fit: contain;
|
||||
background: #F9F9F9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0.5rem;
|
||||
@@ -281,6 +275,14 @@
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.video-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.steps-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -85,67 +85,32 @@ const CardHeader = ({onCloseCards, onShrinkExpandCards, onShowAll, totalSteps, s
|
||||
</div>
|
||||
);
|
||||
|
||||
class VideoStep extends React.Component {
|
||||
|
||||
componentDidMount () {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://fast.wistia.com/embed/medias/${this.props.video}.jsonp`;
|
||||
script.async = true;
|
||||
script.setAttribute('id', 'wistia-video-content');
|
||||
document.body.appendChild(script);
|
||||
|
||||
const script2 = document.createElement('script');
|
||||
script2.src = 'https://fast.wistia.com/assets/external/E-v1.js';
|
||||
script2.async = true;
|
||||
script2.setAttribute('id', 'wistia-video-api');
|
||||
document.body.appendChild(script2);
|
||||
}
|
||||
|
||||
// We use the Wistia API here to update or pause the video dynamically:
|
||||
// https://wistia.com/support/developers/player-api
|
||||
componentDidUpdate (prevProps) {
|
||||
// Ensure the wistia API is loaded and available
|
||||
if (!(window.Wistia && window.Wistia.api)) return;
|
||||
|
||||
// Get a handle on the currently loaded video
|
||||
const video = window.Wistia.api(prevProps.video);
|
||||
|
||||
// Reset the video source if a new video has been chosen from the library
|
||||
if (prevProps.video !== this.props.video) {
|
||||
video.replaceWith(this.props.video);
|
||||
}
|
||||
|
||||
// Pause the video if the modal is being shrunken
|
||||
if (!this.props.expanded) {
|
||||
video.pause();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const script = document.getElementById('wistia-video-content');
|
||||
script.parentNode.removeChild(script);
|
||||
|
||||
const script2 = document.getElementById('wistia-video-api');
|
||||
script2.parentNode.removeChild(script2);
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={styles.stepVideo}>
|
||||
<div
|
||||
className={`wistia_embed wistia_async_${this.props.video}`}
|
||||
id="video-div"
|
||||
style={{height: `257px`, width: `466px`}}
|
||||
>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
// Video step needs to know if the card is being dragged to cover the video
|
||||
// so that the mouseup is not swallowed by the iframe.
|
||||
const VideoStep = ({video, dragging}) => (
|
||||
<div className={styles.stepVideo}>
|
||||
{dragging ? (
|
||||
<div className={styles.videoCover} />
|
||||
) : null}
|
||||
<iframe
|
||||
allowFullScreen
|
||||
allowTransparency="true"
|
||||
frameBorder="0"
|
||||
height="257"
|
||||
scrolling="no"
|
||||
src={`https://fast.wistia.net/embed/iframe/${video}?seo=false&videoFoam=true`}
|
||||
title="📹"
|
||||
width="466"
|
||||
/>
|
||||
<script
|
||||
async
|
||||
src="https://fast.wistia.net/assets/external/E-v1.js"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
VideoStep.propTypes = {
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
dragging: PropTypes.bool.isRequired,
|
||||
video: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
@@ -158,7 +123,6 @@ const ImageStep = ({title, image}) => (
|
||||
<img
|
||||
className={styles.stepImage}
|
||||
draggable={false}
|
||||
key={image} /* Use src as key to prevent hanging around on slow connections */
|
||||
src={image}
|
||||
/>
|
||||
</div>
|
||||
@@ -292,7 +256,6 @@ const Cards = props => {
|
||||
onShowAll,
|
||||
onNextStep,
|
||||
onPrevStep,
|
||||
showVideos,
|
||||
step,
|
||||
expanded,
|
||||
...posProps
|
||||
@@ -335,7 +298,6 @@ const Cards = props => {
|
||||
>
|
||||
<Draggable
|
||||
bounds="parent"
|
||||
cancel="#video-div" // disable dragging on video div
|
||||
position={{x: x, y: y}}
|
||||
onDrag={onDrag}
|
||||
onStart={onStartDrag}
|
||||
@@ -361,18 +323,10 @@ const Cards = props => {
|
||||
/>
|
||||
) : (
|
||||
steps[step].video ? (
|
||||
showVideos ? (
|
||||
<VideoStep
|
||||
dragging={dragging}
|
||||
expanded={expanded}
|
||||
video={translateVideo(steps[step].video, locale)}
|
||||
/>
|
||||
) : ( // Else show the deck image and title
|
||||
<ImageStep
|
||||
image={content[activeDeckId].img}
|
||||
title={content[activeDeckId].name}
|
||||
/>
|
||||
)
|
||||
<VideoStep
|
||||
dragging={dragging}
|
||||
video={translateVideo(steps[step].video, locale)}
|
||||
/>
|
||||
) : (
|
||||
<ImageStep
|
||||
image={translateImage(steps[step].image, locale)}
|
||||
@@ -422,19 +376,9 @@ Cards.propTypes = {
|
||||
onShowAll: PropTypes.func,
|
||||
onShrinkExpandCards: PropTypes.func.isRequired,
|
||||
onStartDrag: PropTypes.func,
|
||||
showVideos: PropTypes.bool,
|
||||
step: PropTypes.number.isRequired,
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number
|
||||
};
|
||||
|
||||
Cards.defaultProps = {
|
||||
showVideos: true
|
||||
};
|
||||
|
||||
export {
|
||||
Cards as default,
|
||||
// Others exported for testability
|
||||
ImageStep,
|
||||
VideoStep
|
||||
};
|
||||
export default Cards;
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import ScratchImage from '../scratch-image/scratch-image.jsx';
|
||||
import styles from './library-item.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -35,10 +36,12 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<img
|
||||
className={styles.featuredImage}
|
||||
src={this.props.iconURL}
|
||||
/>
|
||||
{this.props.iconSource ? (
|
||||
<ScratchImage
|
||||
className={styles.featuredImage}
|
||||
imageSource={this.props.iconSource}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{this.props.insetIconURL ? (
|
||||
<div className={styles.libraryItemInsetImageContainer}>
|
||||
@@ -121,9 +124,9 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
{/* Layers of wrapping is to prevent layout thrashing on animation */}
|
||||
<Box className={styles.libraryItemImageContainerWrapper}>
|
||||
<Box className={styles.libraryItemImageContainer}>
|
||||
<img
|
||||
<ScratchImage
|
||||
className={styles.libraryItemImage}
|
||||
src={this.props.iconURL}
|
||||
imageSource={this.props.iconSource}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -146,7 +149,7 @@ LibraryItemComponent.propTypes = {
|
||||
extensionId: PropTypes.string,
|
||||
featured: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
iconURL: PropTypes.string,
|
||||
iconSource: ScratchImage.ImageSourcePropType,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
name: PropTypes.oneOfType([
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
padding: 0.5rem;
|
||||
height: calc(100% - $library-header-height);
|
||||
}
|
||||
|
||||
.library-scroll-grid.withFilterBar {
|
||||
@@ -60,11 +59,3 @@
|
||||
height: $library-filter-bar-height;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spinner-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import Modal from '../../containers/modal.jsx';
|
||||
import Divider from '../divider/divider.jsx';
|
||||
import Filter from '../filter/filter.jsx';
|
||||
import TagButton from '../../containers/tag-button.jsx';
|
||||
import Spinner from '../spinner/spinner.jsx';
|
||||
import storage from '../../lib/storage';
|
||||
|
||||
import styles from './library.css';
|
||||
|
||||
@@ -29,6 +29,47 @@ const messages = defineMessages({
|
||||
const ALL_TAG = {tag: 'all', intlLabel: messages.allTag};
|
||||
const tagListPrefix = [ALL_TAG];
|
||||
|
||||
/**
|
||||
* Find the AssetType which corresponds to a particular file extension. For example, 'png' => AssetType.ImageBitmap.
|
||||
* @param {string} fileExtension - the file extension to look up.
|
||||
* @returns {AssetType} - the AssetType corresponding to the extension, if any.
|
||||
*/
|
||||
const getAssetTypeForFileExtension = function (fileExtension) {
|
||||
const compareOptions = {
|
||||
sensitivity: 'accent',
|
||||
usage: 'search'
|
||||
};
|
||||
for (const assetTypeId in storage.AssetType) {
|
||||
const assetType = storage.AssetType[assetTypeId];
|
||||
if (fileExtension.localeCompare(assetType.runtimeFormat, compareOptions) === 0) {
|
||||
return assetType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Figure out an `imageSource` (URI or asset ID & type) for a library item's icon.
|
||||
* @param {object} item - either a library item or one of a library item's costumes.
|
||||
* @returns {object} - an `imageSource` ready to be passed to a `ScratchImage`.
|
||||
*/
|
||||
const getItemImageSource = function (item) {
|
||||
if (item.rawURL) {
|
||||
return {
|
||||
uri: item.rawURL
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: adjust libraries to be more storage-friendly; don't use split() here.
|
||||
const md5 = item.md5 || item.baseLayerMD5;
|
||||
if (md5) {
|
||||
const [assetId, fileExtension] = md5.split('.');
|
||||
return {
|
||||
assetId: assetId,
|
||||
assetType: getAssetTypeForFileExtension(fileExtension)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class LibraryComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -45,16 +86,9 @@ class LibraryComponent extends React.Component {
|
||||
this.state = {
|
||||
selectedItem: null,
|
||||
filterQuery: '',
|
||||
selectedTag: ALL_TAG.tag,
|
||||
loaded: false
|
||||
selectedTag: ALL_TAG.tag
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
// Allow the spinner to display before loading the content
|
||||
setTimeout(() => {
|
||||
this.setState({loaded: true});
|
||||
});
|
||||
}
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (prevState.filterQuery !== this.state.filterQuery ||
|
||||
prevState.selectedTag !== this.state.selectedTag) {
|
||||
@@ -170,8 +204,10 @@ class LibraryComponent extends React.Component {
|
||||
})}
|
||||
ref={this.setFilteredDataRef}
|
||||
>
|
||||
{this.state.loaded ? this.getFilteredData().map((dataItem, index) => (
|
||||
<LibraryItem
|
||||
{this.getFilteredData().map((dataItem, index) => {
|
||||
const iconSource = getItemImageSource(dataItem);
|
||||
const icons = dataItem.json && dataItem.json.costumes.map(getItemImageSource);
|
||||
return (<LibraryItem
|
||||
bluetoothRequired={dataItem.bluetoothRequired}
|
||||
collaborator={dataItem.collaborator}
|
||||
description={dataItem.description}
|
||||
@@ -179,26 +215,18 @@ class LibraryComponent extends React.Component {
|
||||
extensionId={dataItem.extensionId}
|
||||
featured={dataItem.featured}
|
||||
hidden={dataItem.hidden}
|
||||
iconMd5={dataItem.md5}
|
||||
iconRawURL={dataItem.rawURL}
|
||||
icons={dataItem.json && dataItem.json.costumes}
|
||||
iconSource={iconSource}
|
||||
icons={icons}
|
||||
id={index}
|
||||
insetIconURL={dataItem.insetIconURL}
|
||||
internetConnectionRequired={dataItem.internetConnectionRequired}
|
||||
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
|
||||
key={`item_${index}`}
|
||||
name={dataItem.name}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
)) : (
|
||||
<div className={styles.spinnerWrapper}>
|
||||
<Spinner
|
||||
large
|
||||
level="primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>);
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -91,8 +91,7 @@ LoupeComponent.propTypes = {
|
||||
color: PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number,
|
||||
a: PropTypes.number
|
||||
b: PropTypes.number
|
||||
}),
|
||||
data: PropTypes.instanceOf(Uint8Array),
|
||||
width: PropTypes.number,
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
|
||||
import storage from '../../lib/storage';
|
||||
|
||||
class ScratchImage extends React.PureComponent {
|
||||
static init () {
|
||||
this._maxParallelism = 6;
|
||||
this._currentJobs = 0;
|
||||
this._pendingImages = new Set();
|
||||
}
|
||||
|
||||
static loadPendingImages () {
|
||||
if (this._currentJobs >= this._maxParallelism) {
|
||||
// already busy
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first visible image. If there aren't any, find the first non-visible image.
|
||||
let nextImage;
|
||||
for (const image of this._pendingImages) {
|
||||
if (image.isVisible) {
|
||||
nextImage = image;
|
||||
break;
|
||||
} else {
|
||||
nextImage = nextImage || image;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found an image to load:
|
||||
// 1) Remove it from the queue
|
||||
// 2) Load the image
|
||||
// 3) Pump the queue again
|
||||
if (nextImage) {
|
||||
this._pendingImages.delete(nextImage);
|
||||
const imageSource = nextImage.props.imageSource;
|
||||
++this._currentJobs;
|
||||
storage
|
||||
.load(imageSource.assetType, imageSource.assetId)
|
||||
.then(asset => {
|
||||
if (!nextImage.wasUnmounted) {
|
||||
const dataURI = asset.encodeDataURI();
|
||||
|
||||
nextImage.setState({
|
||||
imageURI: dataURI
|
||||
});
|
||||
}
|
||||
--this._currentJobs;
|
||||
this.loadPendingImages();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
Object.assign(this.state, this._loadImageSource(props.imageSource));
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const newState = this._loadImageSource(nextProps.imageSource);
|
||||
this.setState(newState);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.wasUnmounted = true;
|
||||
ScratchImage._pendingImages.delete(this);
|
||||
}
|
||||
/**
|
||||
* Calculate the state changes necessary to load the image specified in the provided source info. If the component
|
||||
* is mounted, call setState() with the return value of this function. If the component has not yet mounted, use
|
||||
* the return value of this function as initial state for the component.
|
||||
*
|
||||
* @param {object} imageSource - the new source for the image, including either assetId or URI
|
||||
* @returns {object} - the new state values, if any.
|
||||
*/
|
||||
_loadImageSource (imageSource) {
|
||||
if (imageSource) {
|
||||
if (imageSource.uri) {
|
||||
ScratchImage._pendingImages.delete(this);
|
||||
return {
|
||||
imageURI: imageSource.uri,
|
||||
lastRequestedAsset: null
|
||||
};
|
||||
}
|
||||
if (this.state.lastRequestedAsset !== imageSource.assetId) {
|
||||
ScratchImage._pendingImages.add(this);
|
||||
return {
|
||||
lastRequestedAsset: imageSource.assetId
|
||||
};
|
||||
}
|
||||
}
|
||||
// Nothing to do - don't change any state.
|
||||
return {};
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
src: _src,
|
||||
imageSource: _imageSource,
|
||||
...imgProps
|
||||
} = this.props;
|
||||
return (
|
||||
<VisibilitySensor
|
||||
intervalCheck
|
||||
scrollCheck
|
||||
>
|
||||
{
|
||||
({isVisible}) => {
|
||||
this.isVisible = isVisible;
|
||||
ScratchImage.loadPendingImages();
|
||||
return (
|
||||
<img
|
||||
src={this.state.imageURI}
|
||||
{...imgProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
</VisibilitySensor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ScratchImage.ImageSourcePropType = PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
assetId: PropTypes.string.isRequired,
|
||||
assetType: PropTypes.oneOf(Object.values(storage.AssetType)).isRequired
|
||||
}),
|
||||
PropTypes.shape({
|
||||
uri: PropTypes.string.isRequired
|
||||
})
|
||||
]);
|
||||
|
||||
ScratchImage.propTypes = {
|
||||
imageSource: ScratchImage.ImageSourcePropType.isRequired
|
||||
};
|
||||
|
||||
ScratchImage.init();
|
||||
|
||||
export default ScratchImage;
|
||||
@@ -39,16 +39,6 @@
|
||||
height: .5rem;
|
||||
}
|
||||
|
||||
.large {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.large::before, .large::after {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
@@ -81,10 +71,3 @@
|
||||
.spinner.info::after {
|
||||
border-top-color: $ui-white;
|
||||
}
|
||||
|
||||
.spinner.primary {
|
||||
border-color: $motion-transparent;
|
||||
}
|
||||
.spinner.primary::after {
|
||||
border-top-color: $motion-primary;
|
||||
}
|
||||
@@ -8,8 +8,7 @@ const SpinnerComponent = function (props) {
|
||||
const {
|
||||
className,
|
||||
level,
|
||||
small,
|
||||
large
|
||||
small
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
@@ -17,24 +16,18 @@ const SpinnerComponent = function (props) {
|
||||
className,
|
||||
styles.spinner,
|
||||
styles[level],
|
||||
{
|
||||
[styles.small]: small,
|
||||
[styles.large]: large
|
||||
}
|
||||
{[styles.small]: small}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
SpinnerComponent.propTypes = {
|
||||
className: PropTypes.string,
|
||||
large: PropTypes.bool,
|
||||
level: PropTypes.string,
|
||||
small: PropTypes.bool
|
||||
};
|
||||
SpinnerComponent.defaultProps = {
|
||||
className: '',
|
||||
large: false,
|
||||
level: 'info',
|
||||
small: false
|
||||
level: 'info'
|
||||
};
|
||||
export default SpinnerComponent;
|
||||
@@ -20,6 +20,7 @@
|
||||
cursor: pointer;
|
||||
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.sprite-selector-item.is-selected {
|
||||
|
||||
@@ -23,9 +23,8 @@ const SpriteSelectorItem = props => (
|
||||
onMouseDown: props.onMouseDown,
|
||||
onTouchStart: props.onMouseDown
|
||||
}}
|
||||
disable={props.preventContextMenu}
|
||||
disable={props.dragging}
|
||||
id={`${props.name}-${contextMenuId}`}
|
||||
ref={props.componentRef}
|
||||
>
|
||||
{typeof props.number === 'undefined' ? null : (
|
||||
<div className={styles.number}>{props.number}</div>
|
||||
@@ -90,9 +89,9 @@ const SpriteSelectorItem = props => (
|
||||
|
||||
SpriteSelectorItem.propTypes = {
|
||||
className: PropTypes.string,
|
||||
componentRef: PropTypes.func,
|
||||
costumeURL: PropTypes.string,
|
||||
details: PropTypes.string,
|
||||
dragging: PropTypes.bool,
|
||||
name: PropTypes.string.isRequired,
|
||||
number: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
@@ -102,7 +101,6 @@ SpriteSelectorItem.propTypes = {
|
||||
onMouseDown: PropTypes.func,
|
||||
onMouseEnter: PropTypes.func,
|
||||
onMouseLeave: PropTypes.func,
|
||||
preventContextMenu: PropTypes.bool,
|
||||
selected: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -110,8 +110,7 @@ SpriteList.propTypes = {
|
||||
editingTarget: PropTypes.string,
|
||||
hoveredTarget: PropTypes.shape({
|
||||
hoveredSprite: PropTypes.string,
|
||||
receivedBlocks: PropTypes.bool,
|
||||
sprite: PropTypes.string
|
||||
receivedBlocks: PropTypes.bool
|
||||
}),
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
costume: PropTypes.shape({
|
||||
|
||||
@@ -68,8 +68,6 @@
|
||||
padding-left: calc($space / 2);
|
||||
padding-right: calc($space / 2);
|
||||
padding-bottom: $space;
|
||||
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
|
||||
@@ -2,6 +2,7 @@ import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
import {connect} from 'react-redux';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import backdropLibraryContent from '../lib/libraries/backdrops.json';
|
||||
@@ -32,7 +33,7 @@ class BackdropLibrary extends React.Component {
|
||||
bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
|
||||
skinId: null
|
||||
};
|
||||
// Do not switch to stage, just add the backdrop
|
||||
this.props.vm.setEditingTarget(this.props.stageID);
|
||||
this.props.vm.addBackdrop(item.md5, vmBackdrop);
|
||||
}
|
||||
render () {
|
||||
@@ -52,7 +53,17 @@ class BackdropLibrary extends React.Component {
|
||||
BackdropLibrary.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onRequestClose: PropTypes.func,
|
||||
stageID: PropTypes.string.isRequired,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(BackdropLibrary);
|
||||
const mapStateToProps = state => ({
|
||||
stageID: state.scratchGui.targets.stage.id
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default injectIntl(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BackdropLibrary));
|
||||
@@ -17,7 +17,6 @@ import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
|
||||
import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants';
|
||||
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
|
||||
import DragConstants from '../lib/drag-constants';
|
||||
import defineDynamicBlock from '../lib/define-dynamic-block';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {updateToolbox} from '../reducers/toolbox';
|
||||
@@ -394,50 +393,20 @@ class Blocks extends React.Component {
|
||||
// workspace to be 'undone' here.
|
||||
this.workspace.clearUndo();
|
||||
}
|
||||
handleExtensionAdded (categoryInfo) {
|
||||
const defineBlocks = blockInfoArray => {
|
||||
if (blockInfoArray && blockInfoArray.length > 0) {
|
||||
const staticBlocksJson = [];
|
||||
const dynamicBlocksInfo = [];
|
||||
blockInfoArray.forEach(blockInfo => {
|
||||
if (blockInfo.info && blockInfo.info.isDynamic) {
|
||||
dynamicBlocksInfo.push(blockInfo);
|
||||
} else if (blockInfo.json) {
|
||||
staticBlocksJson.push(blockInfo.json);
|
||||
}
|
||||
// otherwise it's a non-block entry such as '---'
|
||||
});
|
||||
handleExtensionAdded (blocksInfo) {
|
||||
// select JSON from each block info object then reject the pseudo-blocks which don't have JSON, like separators
|
||||
// this actually defines blocks and MUST run regardless of the UI state
|
||||
this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json).filter(x => x));
|
||||
|
||||
this.ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson);
|
||||
dynamicBlocksInfo.forEach(blockInfo => {
|
||||
// This is creating the block factory / constructor -- NOT a specific instance of the block.
|
||||
// The factory should only know static info about the block: the category info and the opcode.
|
||||
// Anything else will be picked up from the XML attached to the block instance.
|
||||
const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`;
|
||||
const blockDefinition =
|
||||
defineDynamicBlock(this.ScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
|
||||
this.ScratchBlocks.Blocks[extendedOpcode] = blockDefinition;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// scratch-blocks implements a menu or custom field as a special kind of block ("shadow" block)
|
||||
// these actually define blocks and MUST run regardless of the UI state
|
||||
defineBlocks(
|
||||
Object.getOwnPropertyNames(categoryInfo.customFieldTypes)
|
||||
.map(fieldTypeName => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition));
|
||||
defineBlocks(categoryInfo.menus);
|
||||
defineBlocks(categoryInfo.blocks);
|
||||
|
||||
// Update the toolbox with new blocks if possible
|
||||
// Update the toolbox with new blocks
|
||||
const toolboxXML = this.getToolboxXML();
|
||||
if (toolboxXML) {
|
||||
this.props.updateToolboxState(toolboxXML);
|
||||
}
|
||||
}
|
||||
handleBlocksInfoUpdate (categoryInfo) {
|
||||
handleBlocksInfoUpdate (blocksInfo) {
|
||||
// @todo Later we should replace this to avoid all the warnings from redefining blocks.
|
||||
this.handleExtensionAdded(categoryInfo);
|
||||
this.handleExtensionAdded(blocksInfo);
|
||||
}
|
||||
handleCategorySelected (categoryId) {
|
||||
const extension = extensionData.find(ext => ext.extensionId === categoryId);
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
|
||||
import CardsComponent from '../components/cards/cards.jsx';
|
||||
import {loadImageData} from '../lib/libraries/decks/translate-image.js';
|
||||
import {notScratchDesktop} from '../lib/isScratchDesktop';
|
||||
|
||||
class Cards extends React.Component {
|
||||
componentDidMount () {
|
||||
@@ -53,8 +52,7 @@ const mapStateToProps = state => ({
|
||||
y: state.scratchGui.cards.y,
|
||||
isRtl: state.locales.isRtl,
|
||||
locale: state.locales.locale,
|
||||
dragging: state.scratchGui.cards.dragging,
|
||||
showVideos: notScratchDesktop()
|
||||
dragging: state.scratchGui.cards.dragging
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
@@ -52,7 +52,6 @@ class GUI extends React.Component {
|
||||
setIsScratchDesktop(this.props.isScratchDesktop);
|
||||
this.setReduxTitle(this.props.projectTitle);
|
||||
this.props.onStorageInit(storage);
|
||||
this.props.onVmInit(this.props.vm);
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
|
||||
@@ -93,7 +92,6 @@ class GUI extends React.Component {
|
||||
onStorageInit,
|
||||
onUpdateProjectId,
|
||||
onUpdateReduxProjectTitle,
|
||||
onVmInit,
|
||||
projectHost,
|
||||
projectId,
|
||||
projectTitle,
|
||||
@@ -133,7 +131,6 @@ GUI.propTypes = {
|
||||
onUpdateProjectId: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
onUpdateReduxProjectTitle: PropTypes.func,
|
||||
onVmInit: PropTypes.func,
|
||||
projectHost: PropTypes.string,
|
||||
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
projectTitle: PropTypes.string,
|
||||
@@ -145,8 +142,7 @@ GUI.defaultProps = {
|
||||
isScratchDesktop: false,
|
||||
onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(),
|
||||
onProjectLoaded: () => {},
|
||||
onUpdateProjectId: () => {},
|
||||
onVmInit: (/* vm */) => {}
|
||||
onUpdateProjectId: () => {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
|
||||
@@ -75,21 +75,17 @@ class LibraryItem extends React.PureComponent {
|
||||
const nextIconIndex = (this.state.iconIndex + 1) % this.props.icons.length;
|
||||
this.setState({iconIndex: nextIconIndex});
|
||||
}
|
||||
curIconMd5 () {
|
||||
curIconSource () {
|
||||
if (this.props.icons &&
|
||||
this.state.isRotatingIcon &&
|
||||
this.state.iconIndex < this.props.icons.length &&
|
||||
this.props.icons[this.state.iconIndex] &&
|
||||
this.props.icons[this.state.iconIndex].baseLayerMD5) {
|
||||
return this.props.icons[this.state.iconIndex].baseLayerMD5;
|
||||
this.props.icons[this.state.iconIndex]) {
|
||||
return this.props.icons[this.state.iconIndex];
|
||||
}
|
||||
return this.props.iconMd5;
|
||||
return this.props.iconSource;
|
||||
}
|
||||
render () {
|
||||
const iconMd5 = this.curIconMd5();
|
||||
const iconURL = iconMd5 ?
|
||||
`https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` :
|
||||
this.props.iconRawURL;
|
||||
const iconSource = this.curIconSource();
|
||||
return (
|
||||
<LibraryItemComponent
|
||||
bluetoothRequired={this.props.bluetoothRequired}
|
||||
@@ -99,8 +95,7 @@ class LibraryItem extends React.PureComponent {
|
||||
extensionId={this.props.extensionId}
|
||||
featured={this.props.featured}
|
||||
hidden={this.props.hidden}
|
||||
iconURL={iconURL}
|
||||
icons={this.props.icons}
|
||||
iconSource={iconSource}
|
||||
id={this.props.id}
|
||||
insetIconURL={this.props.insetIconURL}
|
||||
internetConnectionRequired={this.props.internetConnectionRequired}
|
||||
@@ -127,13 +122,8 @@ LibraryItem.propTypes = {
|
||||
extensionId: PropTypes.string,
|
||||
featured: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
iconMd5: PropTypes.string,
|
||||
iconRawURL: PropTypes.string,
|
||||
icons: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
baseLayerMD5: PropTypes.string
|
||||
})
|
||||
),
|
||||
iconSource: LibraryItemComponent.propTypes.iconSource, // single icon
|
||||
icons: PropTypes.arrayOf(LibraryItemComponent.propTypes.iconSource), // rotating icons
|
||||
id: PropTypes.number.isRequired,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
|
||||
@@ -6,19 +6,19 @@ import {connect} from 'react-redux';
|
||||
import {setHoveredSprite} from '../reducers/hovered-target';
|
||||
import {updateAssetDrag} from '../reducers/asset-drag';
|
||||
import storage from '../lib/storage';
|
||||
import {getEventXY} from '../lib/touch-utils';
|
||||
import VM from 'scratch-vm';
|
||||
import getCostumeUrl from '../lib/get-costume-url';
|
||||
import DragRecognizer from '../lib/drag-recognizer';
|
||||
import {getEventXY} from '../lib/touch-utils';
|
||||
|
||||
import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
|
||||
|
||||
const dragThreshold = 3; // Same as the block drag threshold
|
||||
|
||||
class SpriteSelectorItem extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'getCostumeData',
|
||||
'setRef',
|
||||
'handleClick',
|
||||
'handleDelete',
|
||||
'handleDuplicate',
|
||||
@@ -26,22 +26,9 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handleMouseDown',
|
||||
'handleDragEnd',
|
||||
'handleDrag',
|
||||
'handleTouchEnd'
|
||||
'handleMouseMove',
|
||||
'handleMouseUp'
|
||||
]);
|
||||
|
||||
this.dragRecognizer = new DragRecognizer({
|
||||
onDrag: this.handleDrag,
|
||||
onDragEnd: this.handleDragEnd
|
||||
});
|
||||
}
|
||||
componentDidMount () {
|
||||
document.addEventListener('touchend', this.handleTouchEnd);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('touchend', this.handleTouchEnd);
|
||||
this.dragRecognizer.reset();
|
||||
}
|
||||
getCostumeData () {
|
||||
if (this.props.costumeURL) return this.props.costumeURL;
|
||||
@@ -49,7 +36,12 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
|
||||
return getCostumeUrl(this.props.asset);
|
||||
}
|
||||
handleDragEnd () {
|
||||
handleMouseUp () {
|
||||
this.initialOffset = null;
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||
window.removeEventListener('touchend', this.handleMouseUp);
|
||||
window.removeEventListener('touchmove', this.handleMouseMove);
|
||||
if (this.props.dragging) {
|
||||
this.props.onDrag({
|
||||
img: null,
|
||||
@@ -63,26 +55,29 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
this.noClick = false;
|
||||
});
|
||||
}
|
||||
handleDrag (currentOffset) {
|
||||
this.props.onDrag({
|
||||
img: this.getCostumeData(),
|
||||
currentOffset: currentOffset,
|
||||
dragging: true,
|
||||
dragType: this.props.dragType,
|
||||
index: this.props.index,
|
||||
payload: this.props.dragPayload
|
||||
});
|
||||
this.noClick = true;
|
||||
}
|
||||
handleTouchEnd (e) {
|
||||
const {x, y} = getEventXY(e);
|
||||
const {top, left, bottom, right} = this.ref.getBoundingClientRect();
|
||||
if (x >= left && x <= right && y >= top && y <= bottom) {
|
||||
this.handleMouseEnter();
|
||||
handleMouseMove (e) {
|
||||
const currentOffset = getEventXY(e);
|
||||
const dx = currentOffset.x - this.initialOffset.x;
|
||||
const dy = currentOffset.y - this.initialOffset.y;
|
||||
if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) {
|
||||
this.props.onDrag({
|
||||
img: this.getCostumeData(),
|
||||
currentOffset: currentOffset,
|
||||
dragging: true,
|
||||
dragType: this.props.dragType,
|
||||
index: this.props.index,
|
||||
payload: this.props.dragPayload
|
||||
});
|
||||
this.noClick = true;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
handleMouseDown (e) {
|
||||
this.dragRecognizer.start(e);
|
||||
this.initialOffset = getEventXY(e);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
window.addEventListener('mousemove', this.handleMouseMove);
|
||||
window.addEventListener('touchend', this.handleMouseUp);
|
||||
window.addEventListener('touchmove', this.handleMouseMove);
|
||||
}
|
||||
handleClick (e) {
|
||||
e.preventDefault();
|
||||
@@ -108,10 +103,6 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
handleMouseEnter () {
|
||||
this.props.dispatchSetHoveredSprite(this.props.id);
|
||||
}
|
||||
setRef (component) {
|
||||
// Access the DOM node using .elem because it is going through ContextMenuTrigger
|
||||
this.ref = component && component.elem;
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
@@ -131,9 +122,7 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
} = this.props;
|
||||
return (
|
||||
<SpriteSelectorItemComponent
|
||||
componentRef={this.setRef}
|
||||
costumeURL={this.getCostumeData()}
|
||||
preventContextMenu={this.dragRecognizer.gestureInProgress()}
|
||||
onClick={this.handleClick}
|
||||
onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
|
||||
onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
|
||||
|
||||
@@ -15,7 +15,6 @@ import ThrottledPropertyHOC from '../lib/throttled-property-hoc.jsx';
|
||||
import {emptyCostume} from '../lib/empty-assets';
|
||||
import sharedMessages from '../lib/shared-messages';
|
||||
import {fetchCode} from '../lib/backpack-api';
|
||||
import {getEventXY} from '../lib/touch-utils';
|
||||
|
||||
import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx';
|
||||
|
||||
@@ -47,26 +46,11 @@ class StageSelector extends React.Component {
|
||||
'handleBackdropUpload',
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handleTouchEnd',
|
||||
'handleDrop',
|
||||
'setFileInput',
|
||||
'setRef'
|
||||
'setFileInput'
|
||||
]);
|
||||
}
|
||||
componentDidMount () {
|
||||
document.addEventListener('touchend', this.handleTouchEnd);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('touchend', this.handleTouchEnd);
|
||||
}
|
||||
handleTouchEnd (e) {
|
||||
const {x, y} = getEventXY(e);
|
||||
const {top, left, bottom, right} = this.ref.getBoundingClientRect();
|
||||
if (x >= left && x <= right && y >= top && y <= bottom) {
|
||||
this.handleMouseEnter();
|
||||
}
|
||||
}
|
||||
addBackdropFromLibraryItem (item, shouldActivateTab = true) {
|
||||
addBackdropFromLibraryItem (item) {
|
||||
const vmBackdrop = {
|
||||
name: item.name,
|
||||
md5: item.md5,
|
||||
@@ -75,30 +59,25 @@ class StageSelector extends React.Component {
|
||||
bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
|
||||
skinId: null
|
||||
};
|
||||
this.handleNewBackdrop(vmBackdrop, shouldActivateTab);
|
||||
this.handleNewBackdrop(vmBackdrop);
|
||||
}
|
||||
handleClick () {
|
||||
this.props.onSelect(this.props.id);
|
||||
}
|
||||
handleNewBackdrop (backdrops_, shouldActivateTab = true) {
|
||||
handleNewBackdrop (backdrops_) {
|
||||
const backdrops = Array.isArray(backdrops_) ? backdrops_ : [backdrops_];
|
||||
return Promise.all(backdrops.map(backdrop =>
|
||||
this.props.vm.addBackdrop(backdrop.md5, backdrop)
|
||||
)).then(() => {
|
||||
if (shouldActivateTab) {
|
||||
return this.props.onActivateTab(COSTUMES_TAB_INDEX);
|
||||
}
|
||||
});
|
||||
)).then(() =>
|
||||
this.props.onActivateTab(COSTUMES_TAB_INDEX)
|
||||
);
|
||||
}
|
||||
handleSurpriseBackdrop (e) {
|
||||
e.stopPropagation(); // Prevent click from falling through to selecting stage.
|
||||
handleSurpriseBackdrop () {
|
||||
// @todo should this not add a backdrop you already have?
|
||||
const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)];
|
||||
this.addBackdropFromLibraryItem(item, false);
|
||||
this.addBackdropFromLibraryItem(item);
|
||||
}
|
||||
handleEmptyBackdrop (e) {
|
||||
e.stopPropagation(); // Prevent click from falling through to stage selector, select it manually below
|
||||
this.props.vm.setEditingTarget(this.props.id);
|
||||
handleEmptyBackdrop () {
|
||||
this.handleNewBackdrop(emptyCostume(this.props.intl.formatMessage(sharedMessages.backdrop, {index: 1})));
|
||||
}
|
||||
handleBackdropUpload (e) {
|
||||
@@ -106,7 +85,6 @@ class StageSelector extends React.Component {
|
||||
this.props.onShowImporting();
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
|
||||
costumeUpload(buffer, fileType, storage, vmCostumes => {
|
||||
this.props.vm.setEditingTarget(this.props.id);
|
||||
vmCostumes.forEach((costume, i) => {
|
||||
costume.name = `${fileName}${i ? i + 1 : ''}`;
|
||||
});
|
||||
@@ -118,8 +96,7 @@ class StageSelector extends React.Component {
|
||||
}, this.props.onCloseImporting);
|
||||
}, this.props.onCloseImporting);
|
||||
}
|
||||
handleFileUploadClick (e) {
|
||||
e.stopPropagation(); // Prevent click from selecting the stage, that is handled manually in backdrop upload
|
||||
handleFileUploadClick () {
|
||||
this.fileInput.click();
|
||||
}
|
||||
handleMouseEnter () {
|
||||
@@ -153,16 +130,12 @@ class StageSelector extends React.Component {
|
||||
setFileInput (input) {
|
||||
this.fileInput = input;
|
||||
}
|
||||
setRef (ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
render () {
|
||||
const componentProps = omit(this.props, [
|
||||
'asset', 'dispatchSetHoveredSprite', 'id', 'intl',
|
||||
'onActivateTab', 'onSelect', 'onShowImporting', 'onCloseImporting']);
|
||||
return (
|
||||
<DroppableThrottledStage
|
||||
componentRef={this.setRef}
|
||||
fileInputRef={this.setFileInput}
|
||||
onBackdropFileUpload={this.handleBackdropUpload}
|
||||
onBackdropFileUploadClick={this.handleFileUploadClick}
|
||||
|
||||
@@ -105,10 +105,7 @@ class TargetPane extends React.Component {
|
||||
}
|
||||
}
|
||||
handleSurpriseSpriteClick () {
|
||||
const surpriseSprites = spriteLibraryContent.filter(sprite =>
|
||||
(sprite.tags.indexOf('letters') === -1) && (sprite.tags.indexOf('numbers') === -1)
|
||||
);
|
||||
const item = surpriseSprites[Math.floor(Math.random() * surpriseSprites.length)];
|
||||
const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
|
||||
randomizeSpritePosition(item);
|
||||
this.props.vm.addSprite(JSON.stringify(item.json))
|
||||
.then(this.handleActivateBlocksTab);
|
||||
|
||||
@@ -62,16 +62,10 @@ class TipsLibrary extends React.PureComponent {
|
||||
}
|
||||
render () {
|
||||
const decksLibraryThumbnailData = Object.keys(decksLibraryContent)
|
||||
.filter(id => {
|
||||
if (notScratchDesktop()) return true; // Do not filter anything in online editor
|
||||
const deck = decksLibraryContent[id];
|
||||
.filter(id =>
|
||||
// Scratch Desktop doesn't want tutorials with `requiredProjectId`
|
||||
if (deck.hasOwnProperty('requiredProjectId')) return false;
|
||||
// Scratch Desktop should not load tutorials that are _only_ videos
|
||||
if (deck.steps.filter(s => s.title).length === 0) return false;
|
||||
// Allow any other tutorials
|
||||
return true;
|
||||
})
|
||||
notScratchDesktop() || !decksLibraryContent[id].hasOwnProperty('requiredProjectId')
|
||||
)
|
||||
.map(id => ({
|
||||
rawURL: decksLibraryContent[id].img,
|
||||
id: id,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<g id="Layer_3">
|
||||
<path fill="#FAA51D" stroke="#000000" d="M22.462,79.039c-2.415-0.451-5.304-1.309-7.742-3.503
		C9.268,70.629,7.526,62.535,3.672,64.622c-3.856,2.088-3.782,15.165,8.353,19.194c4.182,1.391,7.998,1.396,11.091,1.312
		c0.811-0.025,7.717-0.654,10.079-4.074c2.361-3.42,0.719-4.272-0.09-4.744C32.295,75.838,25.878,79.677,22.462,79.039z"/>
|
||||
<path fill="#FFFFFF" d="M4.236,64.877c-1.989,0.613-3.075,4.998-2.076,8.484c0.998,3.49,2.634,5.022,3.863,6.398
		c1.528,1.038-0.72-2.402,1.361-4.15c2.075-1.744,5.733-0.914,5.733-0.914s-2.909-3.987-4.57-6.396
		C6.975,65.988,6.359,64.375,4.236,64.877z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FAA51D" d="M38.217,86.756c0,0-8.832,6.2-17.071,8.412l0.086,0.215c1.247,1.824,5.87,7.497-0.334,9.496
		c-5.333,1.717-15.12-13.104-10.821-15.902c2.626-1.713,4.892-0.252,4.892-0.252s3.474-1.07,6.001-2.345
		c4.303-2.161,5.784-3.453,5.784-3.453s4.184-4.306,6.856-4.137C36.281,78.96,41.669,83.504,38.217,86.756z"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.232,95.383c1.247,1.824,5.87,7.497-0.334,9.496
		c-5.333,1.717-15.329-13.344-11.03-16.145c2.626-1.713,5.101-0.01,5.101-0.01s3.474-1.072,6.001-2.348
		c4.303-2.161,5.784-3.453,5.784-3.453"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M38.217,86.756c0,0-10.123,7.107-18.804,8.819"/>
|
||||
</g>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.169,74.885c0,0,1.235,0.165,4.744,3.676
	c3.509,3.508,6.026,2.16,8.911,0.724c2.877-1.443,10.537-6.126,6.49-9.817c-4.049-3.688-6.207,1.146-9.715,2.405
	c-3.512,1.26-5.061-2.487-6.858-4.287c-0.589-0.593-1.188-1.099-1.729-1.505c0,0-0.971-0.76-1.906,2.79
	C51.172,72.412,50.162,73.415,52.169,74.885z"/>
|
||||
<g id="Layer_2_1_">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M46.753,82.012c1.188-0.912,2.397-2.402,3.951-4.713
		c1.296-1.927,2.7-5.578,2.7-5.578c0.875-2.521,1.934-6.576-1.902-7.296c-1.553-0.291-4.079-0.098-7.67-0.776
		c-3.593-0.681-6.798-2.522-9.517,2.233c-2.718,4.757-9.59,8.271-1.056,16.563c0,0,4.901,3.842,10.764,9.639
		c4.831,4.775,12.045,10.602,12.045,10.602s18.972,2.188,19.535-0.693c1.922-9.79-14.777-6.911-14.777-6.911
		s-4.605-3.933-6.725-5.794c-3.478-3.059-11.125-10.771-11.125-10.771"/>
|
||||
<path fill="#FFFFFF" d="M51.253,75.434c0,0,2.47-2.66-2.469-5.317c-4.939-2.657-7.213-0.017-8.739,1.521
		c-2.644,2.655,3.443,6.611,3.443,6.611l3.176,3.204c0,0,1.738-1.647,2.499-2.979C50.036,77.26,51.253,75.434,51.253,75.434"/>
|
||||
</g>
|
||||
<g id="Layer_8"/>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M29.926,73.218c0.749-0.571,2.889-2.202,4.854-3.657
	c2.428-1.799,6.117-5.849,1.077-7.646c-5.04-1.801-7.507,1.604-11.519,4.946c-2.159,1.801-5.308,2.699-4.319,6.209
	c0.993,3.511,4.862,13.408,11.789,10.17c6.929-3.239-1.799-9.18-3.06-11.157"/>
|
||||
<g id="Layer_2">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.709,14.156c-1.54-0.143-4.75-0.316-6.518-0.231
		c-4.728,0.225-9.224,1.928-9.224,1.928L23.949,7.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224
		c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.729,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823
		s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L62.822,5.647l-7.452,7.204L52.709,14.156z"/>
|
||||
<path fill="#FFFFFF" d="M76.42,35.066l-2.482-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277
		L41.46,41.17c0,0-8.437,0.928-8.739,6.081C32.048,58.704,46.1,63.479,51.425,63.783c2.905,0.167,8.235-0.338,12.277-1.141
		c17.752-3.234,22.551-13.919,21.31-19.635c-1.242-5.714-7.978-7.196-7.978-7.196L76.42,35.066z"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M10.673,46.155c0,0,4.107,0.374,5.974,0.268
		c1.865-0.107,5.492-0.587,5.492-0.587"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.656,40.671c0,0,4.549-0.743,6.859-1.549
		c2.715-0.942,4.543-2.545,4.543-2.545"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M22.337,41.909c0,0-2.384-1.777-6.117-3.43
		c-4.134-1.831-6.405-2.303-6.405-2.303"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M82.117,46.622c0,0,2.726,1.104,5.533,1.385
		c2.77,0.276,4.646,0.11,4.646,0.11"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M52.35,14.212
		c2.84,0.7,3.887,1.469,3.887,1.469"/>
|
||||
<line fill="none" stroke="#000000" x1="33.898" y1="13.684" x2="39.956" y2="18.042"/>
|
||||
</g>
|
||||
<g id="Layer_5">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M71.84,25.366c2.924,4.479,3.033,9.591,0.242,11.415
		c-2.793,1.825-7.426-0.332-10.354-4.813c-2.933-4.48-3.037-9.589-0.244-11.415C64.275,18.73,68.913,20.884,71.84,25.366z"/>
|
||||
<path fill="#231F20" d="M71.089,32.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956
		c0-1.08,0.81-1.958,1.803-1.958C70.287,30.564,71.089,31.442,71.089,32.522"/>
|
||||
</g>
|
||||
<g id="Layer_7">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M47.867,28.619c2.926,4.48,2.619,9.862-0.681,12.015
		c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C39.891,22.253,44.938,24.137,47.867,28.619z"/>
|
||||
<path fill="#231F20" d="M46.079,34.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957
		c0-1.08,0.811-1.957,1.803-1.957C45.274,32.55,46.079,33.427,46.079,34.507"/>
|
||||
</g>
|
||||
<path fill="#5E4A42" stroke="#000000" d="M59.766,37.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345
	c-1.283,0.142-6.125-2.353-6.195-3.919C55.206,37.355,58.055,37.926,59.766,37.926z"/>
|
||||
<g id="Layer_4">
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M46.774,45.235c0,0,10.347,3.054,14.217,3.897
		c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C45.176,55.863,45.345,49.975,46.774,45.235z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.5 KiB |
@@ -0,0 +1,37 @@
|
||||
<svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<g id="Layer_8">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M23.754,82.506c-2.871-1.402-10.511-2.518-14.771-6.695
		c-9.431-9.246-3.824-21.732,0.704-21.797c3.611-0.061,3.363,5.482,4.622,11.145c0.875,3.939,1.673,6.988,6.407,9.674
		c4.735,2.688,6.918,2.916,6.918,2.916s3.258,0.791,1.596,2.562C27.568,82.076,23.754,82.506,23.754,82.506z"/>
|
||||
<path fill="#FFFFFF" d="M14.291,66.369c-0.115-0.459-0.222-0.928-0.33-1.413c-1.172-5.271-0.941-10.432-4.303-10.377
		c-3.433,0.05-7.53,7.771-4.19,15.285c0.193-0.143,0.399-0.336,0.569-0.588c0.451-0.664,0.821-2.969,2.654-3.688
		C9.989,65.082,12.778,65.869,14.291,66.369z"/>
|
||||
</g>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.543,61.473c0,0,1.455,2.342,1.519,4.969
	c0.065,2.723-0.351,6.984,1.804,8.158c3.381,1.842,9.959-0.463,10.174,5.191c0.211,5.652-10.152,3.623-13.299,2.547
	c-2.695-0.926-4.559-1.758-5.18-5.182c0,0-3.764-13.969-0.196-15.59C50.933,59.945,52.543,61.473,52.543,61.473z"/>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M14.61,89.584c0,0,1.504,10.391-5.922,8.387
	c-5.586-1.51-5.705-20.68-0.406-20.691c3.237-0.006,4.425,2.859,4.425,2.859s4.385,4.812,6.95,4.312
	c2.565-0.502,7.978-6.703,7.978-6.703s2.875-2.539,6.146,0.023c3.271,2.561,1.521,6.178,1.521,6.178s-6.07,7.072-11.014,8.072
	c-4.945,1.002-9.375-2.568-11.58-3.451"/>
|
||||
<g id="Layer_6">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M42.435,76.238c0,0,4.866,4.062,8.166,7.52
		c4.384,4.592-3.339,15.039-3.339,15.039s11.234,6.467,2.998,11c-2.657,1.467-15.661-13.225-15.661-13.225s7.979-7.475,4.972-10.172
		c-4.964-4.449-10.771-4.959-11.6-9.824c-0.502-2.957,0.613-4.502,2.658-7.053c1.354-1.689,2.084-3.182,2.084-3.182
		s1.135-7.238,4.289-8.254c3.153-1.014,8.842,0.057,10.758,0.395c1.914,0.338,6.645,1.877,4.617,9.922c0,0-1.451,3.771-2.789,5.762
		c-1.604,2.389-2.854,3.924-4.08,4.867"/>
|
||||
<path fill="#FFFFFF" d="M50.182,72.631c0,0,2.55-2.748-2.551-5.494c-5.102-2.746-7.449-0.016-9.024,1.572
		c-2.731,2.742,3.334,7.453,3.334,7.453l3.383,2.564c0,0,1.916-1.582,2.701-2.957C48.807,74.396,50.182,72.631,50.182,72.631"/>
|
||||
</g>
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M24.428,65.395c0.939,0.057,3.625,0.213,6.064,0.391
	c3.014,0.217,8.451-0.451,5.801-5.098c-2.649-4.65-6.74-3.674-11.961-3.754c-2.812-0.043-5.785-1.412-7.322,1.895
	c-1.534,3.312-5.051,13.338,2.313,15.396c7.366,2.057,4.618-8.137,4.95-10.457"/>
|
||||
<g id="Layer_2">
|
||||
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M51.709,10.156c-1.54-0.143-4.75-0.316-6.518-0.231
		c-4.728,0.225-9.224,1.928-9.224,1.928L22.949,3.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224
		c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.728,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823
		s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L61.822,1.647L54.37,8.851L51.709,10.156z"/>
|
||||
<path fill="#FFFFFF" d="M75.42,31.066l-2.483-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277
		l-7.784,3.417c0,0-8.437,0.928-8.739,6.081C31.048,54.704,45.1,59.478,50.425,59.783c2.905,0.167,8.235-0.337,12.277-1.14
		c17.752-3.235,22.551-13.92,21.309-19.635c-1.242-5.714-7.977-7.196-7.977-7.196L75.42,31.066z"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M9.673,42.155c0,0,4.107,0.374,5.974,0.268
		c1.865-0.107,5.492-0.587,5.492-0.587"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M80.656,36.671c0,0,4.549-0.743,6.859-1.549
		c2.715-0.942,4.543-2.545,4.543-2.545"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.337,37.909c0,0-2.384-1.777-6.117-3.43
		c-4.134-1.831-6.405-2.303-6.405-2.303"/>
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.117,42.622c0,0,2.726,1.104,5.533,1.385
		c2.77,0.276,4.647,0.11,4.647,0.11"/>
|
||||
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M51.349,10.212
		c2.84,0.7,3.888,1.469,3.888,1.469"/>
|
||||
<line fill="none" stroke="#000000" x1="32.898" y1="9.684" x2="38.956" y2="14.042"/>
|
||||
</g>
|
||||
<g id="Layer_5">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M70.84,21.366c2.924,4.479,3.033,9.591,0.242,11.415
		c-2.793,1.825-7.426-0.332-10.354-4.813c-2.932-4.48-3.037-9.589-0.244-11.415C63.275,14.73,67.913,16.884,70.84,21.366z"/>
|
||||
<path fill="#231F20" d="M70.089,28.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956
		c0-1.08,0.81-1.958,1.803-1.958C69.287,26.564,70.089,27.442,70.089,28.522"/>
|
||||
</g>
|
||||
<g id="Layer_7">
|
||||
<path fill="#FFFFFF" stroke="#231F20" d="M46.867,24.619c2.926,4.48,2.619,9.862-0.681,12.015
		c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C38.891,18.253,43.938,20.137,46.867,24.619z"/>
|
||||
<path fill="#231F20" d="M45.079,30.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957
		c0-1.08,0.811-1.957,1.803-1.957C44.274,28.55,45.079,29.427,45.079,30.507"/>
|
||||
</g>
|
||||
<path fill="#5E4A42" stroke="#000000" d="M58.766,33.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345
	c-1.283,0.142-6.125-2.353-6.195-3.919C54.206,33.355,57.055,33.926,58.766,33.926z"/>
|
||||
<g id="Layer_4">
|
||||
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M45.774,41.235c0,0,10.347,3.054,14.217,3.897
		c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C44.176,51.863,44.345,45.975,45.774,41.235z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.1 KiB |
@@ -1,42 +0,0 @@
|
||||
<svg width="96px" height="101px" viewBox="0 0 96 101" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
|
||||
<g>
|
||||
<title>costume1.1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="costume1" transform="translate(-13.000000, -10.000000)">
|
||||
<g id="costume1.1" transform="translate(13.000000, 10.000000)">
|
||||
<g id="tail" transform="translate(0.000000, 59.000000)">
|
||||
<path d="M21.9,14.8 C19.5,14.3 16.6,13.5 14.2,11.3 C8.7,6.4 7,-1.7 3.2,0.4 C-0.7,2.5 -0.6,15.6 11.6,19.6 C15.8,21 19.6,21 22.7,20.9 C23.5,20.9 30.4,20.2 32.8,16.8 C35.2,13.4 33.5,12.5 32.7,12.1 C31.8,11.6 25.3,15.4 21.9,14.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3.8,0.6 C1.8,1.2 0.8,5.4 1.8,8.9 C2.8,12.4 4.4,14.2 5.7,15.5 C5.5,14.8 5.1,12.6 6.8,11.3 C8.9,9.6 12.6,10.5 12.6,10.5 C12.6,10.5 9.5,6.7 7.9,4 C6.3,1.7 5.8,0.2 3.8,0.6 Z" id="detail" fill="#FFFFFF"/>
|
||||
</g>
|
||||
<path d="M37.7,81.5 C35.9,82.7 29.7,87.1 21.8,89.6 L21.4,89.7 C21,89.8 20.8,90.3 21,90.7 C22.7,93.1 25.8,97.9 20.3,99.6 C15,101.3 5.1,87.2 9.3,83.5 C11.2,82.1 12.9,82.8 13.8,83.2 C14.3,83.4 14.8,83.4 15.3,83.3 C16.5,82.9 18.7,82.1 20.4,81.2 C24.7,79 25.7,78.1 27.7,76.6 C29.7,75.1 34.3,71.4 38,74.6 C41.2,77.3 39.4,80.3 37.7,81.5 Z" id="leg" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M53.6,60.7 C54.1,61.1 60.2,68.3 62.2,66.5 C64.6,64.4 67.9,60.3 71.5,63.6 C75.1,66.9 68.3,72.5 65.4,74 C58.5,77.1 52.9,71.2 51.7,69.6 C50.5,68 48.4,65.3 48.4,62.7 C48.5,59.9 51.9,59.2 53.6,60.7 Z" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="body-and-leg" transform="translate(28.000000, 57.000000)">
|
||||
<path d="M18.2,19.7 C19.4,18.8 20.6,17.3 22.2,15 C23.5,13.1 24.9,9.4 24.9,9.4 C25.8,6.9 26.4,2.1 23.1,2.2 C20.9,2.3 18.9,2 15.5,1.5 C9.5,0.3 8.4,-0.5 5.9,3.6 C3.2,8.4 -3.7,11.9 4.8,20.2 C4.8,20.2 9.7,24 15.6,29.8 C19.6,33.7 25.9,39.3 28.1,41.2 C28.6,41.6 29.2,41.8 29.8,41.9 C39.5,42.8 46.7,41.8 46.7,37.5 C46.7,30.3 32.4,32.8 32.4,32.8 C32.4,32.8 27.8,28.9 25.7,27 L18.2,19.7 Z" id="body" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.6,13 C22.6,13 24.5,10.5 20.2,7.8 C15.7,4.9 14,8.1 12.2,10.5 C10.2,13.6 12.2,15.1 14.2,16.9 C15.8,18.4 17.3,19.6 17.3,19.6 C17.3,19.6 20.4,17.5 22.6,13 Z" id="tummy" fill="#FFFFFF"/>
|
||||
</g>
|
||||
<path d="M30.2,68.4 C32.4,71.2 35.8,74.7 31.5,77.6 C25.6,80.9 20.7,70.9 19.7,67.4 C18.8,64.3 21.4,62.3 23.6,60.6 C27.9,57.5 31.5,54.7 35.5,56.2 C40.5,58 36.9,62 34.4,63.8 C32.9,64.9 31.4,66.1 30.3,66.8 C30,67.3 29.9,67.9 30.2,68.4 Z" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="head" transform="translate(14.000000, 0.000000)">
|
||||
<path d="M39.1,9 C36.8,8.6 34.4,8.4 31.6,8.6 C26.9,8.8 22.4,10.5 22.4,10.5 L10.3,2.6 C9.9,2.4 9.4,2.7 9.5,3.1 L11.6,21 C12.2,20.2 1,33.8 8.1,45.2 C15.2,56.6 30.3,61.7 49.1,58 C67.9,54.3 72.3,43.5 71.1,37.8 C69.9,32.1 62.8,30 62.8,30 C62.8,30 62.7,25.5 59.5,20 C57.6,16.7 51.2,12 51.2,12 L48.6,1.3 C48.5,0.9 48,0.8 47.7,1 L39.1,9 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19"/>
|
||||
<path d="M62.5,30.4 C62.5,30.4 69.4,32.2 70.6,37.9 C71.8,43.6 67,53.9 48.4,57.5 C24.2,62.5 12.7,48.1 19.4,37.5 C26.1,26.8 37.6,35.9 46,35.3 C53.2,34.8 54,28.5 62.5,30.4 Z" id="face" fill="#FFFFFF"/>
|
||||
<path d="M31,41.1 C31,40.7 31.4,40.4 31.8,40.5 C33.7,41.2 39.1,42.8 45.1,43.2 C50.5,43.5 53.7,43.2 55.2,42.9 C55.7,42.8 56.1,43.3 55.9,43.8 C55,46.5 51.2,54 40.7,53.4 C31.6,52.4 30.7,46 31,41.1 Z" id="mouth" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M69,35.4 C69,35.4 76.2,35.3 80.9,31.5" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M69.4,41.3 C69.4,41.3 73.3,43.2 79.6,42.7" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M45.6,32.7 C47.7,32.7 49.9,32.9 50,33.6 C50.1,35 48.6,37.8 47,37.9 C45.2,38.1 41,35.6 41,34 C40.9,32.8 43.6,32.7 45.6,32.7 Z" id="nose" stroke="#001026" stroke-width="1.2" fill="#001026" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M0.6,31.2 C0.6,31.2 9.2,34 12.7,37.1" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.3,41.2 C1.3,41.2 8.7,42.3 13,40.6" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="eye" transform="translate(44.000000, 15.000000)">
|
||||
<path d="M13.4,6 C16.3,10.5 16.4,15.6 13.6,17.4 C10.8,19.2 6.2,17.1 3.2,12.6 C0.3,8.1 0.2,3 3,1.2 C5.8,-0.7 10.5,1.5 13.4,6 Z" id="pupil" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13,11.7 C13,12.8 12.2,13.7 11.2,13.7 C10.2,13.7 9.4,12.8 9.4,11.7 C9.4,10.6 10.2,9.7 11.2,9.7 C12.2,9.7 13,10.6 13,11.7" id="pupil" fill="#001026"/>
|
||||
</g>
|
||||
<g id="eye" transform="translate(19.000000, 18.000000)">
|
||||
<path d="M13.6,5.8 C16.6,10.2 16.4,15.6 13.7,17.5 C10.4,19.4 6,18 3,13.6 C-0.1,9.2 -0.3,3.5 2.8,1.3 C5.9,-1 10.6,1.4 13.6,5.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13,11.6 C13,12.7 12.2,13.6 11.2,13.6 C10.2,13.6 9.4,12.7 9.4,11.6 C9.4,10.5 10.2,9.6 11.2,9.6 C12.2,9.7 13,10.5 13,11.6" id="pupil" fill="#001026"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1,42 +0,0 @@
|
||||
<svg width="93px" height="106px" viewBox="0 0 93 106" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
|
||||
<g>
|
||||
<title>costume2.1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="cotsume2" transform="translate(-16.000000, -10.000000)">
|
||||
<g id="costume2.1" transform="translate(16.250000, 9.500000)">
|
||||
<g id="tail" transform="translate(0.000000, 53.000000)">
|
||||
<path d="M12,14.8 C8.9,7 10.6,0 6.3,0.4 C1.9,0.8 -3.2,12.5 6.4,21.1 C15.3,29 24.4,26.1 28,24 C31.5,21.8 28.8,20.6 28.2,19.8 C27.6,19 16.4,26.1 12,14.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6.8,0.9 C4.3,0.5 2.1,4.1 1.6,7.6 C1.4,11.2 2,13.3 2.7,15 C2.8,14.3 3.7,11.9 6.6,11.4 C9,11 10.6,13 10.6,13 C10.6,13 9.6,10 9.1,5.6 C8.7,2.9 8.8,1.3 6.8,0.9 Z" id="detail" fill="#FFFFFF"/>
|
||||
</g>
|
||||
<path d="M32.9,83.2 C32.2,84.1 30.2,87.6 25.4,90.6 C22.1,92.7 13.6,89.2 13.6,89.2 C14.5,98.4 7,98.2 5.6,95.2 C4.5,92.8 4.7,82.5 6.9,81.7 C10.3,80.5 14.9,82.5 19,82.5 C20.2,82.5 21.5,79.9 24.3,77.1 C26.3,75.1 28.9,71.4 33.1,74 C36.7,76.3 34.8,80.5 32.9,83.2 Z" id="leg" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M50.1,62 C50.4,64.2 50.6,72.6 53,73.9 C55.3,75.1 62.3,73.1 62.7,78 C63.1,82.8 56.6,82.9 51.2,81.4 C44.2,79.5 41,68.8 45,62.2 C46.5,59.9 49.8,59.7 50.1,62 Z" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="body-and-leg" transform="translate(24.000000, 57.000000)">
|
||||
<path d="M18.2,21.3 C19.4,20.4 21.2,18 22.8,15.6 C24.1,13.7 25.5,10 25.5,10 C26.4,7.5 27,2.7 23.7,2.8 C21.5,2.9 19.5,2.6 16.1,2.1 C10.1,0.9 8.9,-0.3 6.5,4.2 C2,12.5 -4,18.4 4.2,23.2 C6.2,24.4 9.4,26.4 12.2,28.4 C14.2,30.3 11.2,34.4 9.1,36.8 C8.2,37.9 8.2,39.5 9.3,40.6 C16.6,47.7 18.8,50.2 21.6,47.1 C25.4,42.9 19.4,38.8 19.4,38.8 C19.4,38.8 24.2,31.3 23.6,28 C22.9,24.8 18.2,21.3 18.2,21.3 Z" id="body" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M22.7284271,14.6028611 C22.7284271,14.6028611 24.6284271,12.1028611 20.3284271,9.40286109 C15.8284271,6.50286109 14.1284271,9.70286109 12.3284271,12.1028611 C10.3284271,15.2028611 12.3284271,16.7028611 14.3284271,18.5028611 C15.9284271,20.0028611 17.4284271,21.2028611 17.4284271,21.2028611 C17.4284271,21.2028611 20.5284271,19.1028611 22.7284271,14.6028611 Z" id="tummy" fill="#FFFFFF"/>
|
||||
</g>
|
||||
<path d="M32.3,65.2 C30.1,66.2 26,64.4 24.9,64.6 C24.6,64.7 22.8,69.9 21.7,73 C20.2,77.4 13.4,76.1 13,71.5 C12.7,67.2 16.6,58.9 17.9,57.4 C20.8,53.9 30.6591797,56.7082962 37.2463379,58.456543" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="head" transform="translate(10.750000, 0.500000)">
|
||||
<path d="M39.1,9 C36.8,8.6 34.4,8.4 31.6,8.6 C26.9,8.8 22.4,10.5 22.4,10.5 L10.3,2.6 C9.9,2.4 9.4,2.7 9.5,3.1 L11.6,21 C12.2,20.2 1,33.8 8.1,45.2 C15.2,56.6 30.3,61.7 49.1,58 C67.9,54.3 72.3,43.5 71.1,37.8 C69.9,32.1 62.8,30 62.8,30 C62.8,30 62.7,25.5 59.5,20 C57.6,16.7 51.2,12 51.2,12 L48.6,1.3 C48.5,0.9 48,0.8 47.7,1 L39.1,9 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19"/>
|
||||
<path d="M62.5,30.4 C62.5,30.4 69.4,32.2 70.6,37.9 C71.8,43.6 67,53.9 48.4,57.5 C24.2,62.5 12.7,48.1 19.4,37.5 C26.1,26.8 37.6,35.9 46,35.3 C53.2,34.8 54,28.5 62.5,30.4 Z" id="face" fill="#FFFFFF"/>
|
||||
<path d="M31,41.1 C31,40.7 31.4,40.4 31.8,40.5 C33.7,41.2 39.1,42.8 45.1,43.2 C50.5,43.5 53.7,43.2 55.2,42.9 C55.7,42.8 56.1,43.3 55.9,43.8 C55,46.5 51.2,54 40.7,53.4 C31.6,52.4 30.7,46 31,41.1 Z" id="mouth" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M69,35.4 C69,35.4 76.2,35.3 80.9,31.5" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M69.4,41.3 C69.4,41.3 73.3,43.2 79.6,42.7" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M45.6,32.7 C47.7,32.7 49.9,32.9 50,33.6 C50.1,35 48.6,37.8 47,37.9 C45.2,38.1 41,35.6 41,34 C40.9,32.8 43.6,32.7 45.6,32.7 Z" id="nose" stroke="#001026" stroke-width="1.2" fill="#001026" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M0.6,31.2 C0.6,31.2 9.2,34 12.7,37.1" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.3,41.2 C1.3,41.2 8.7,42.3 13,40.6" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<g id="eye" transform="translate(44.000000, 15.000000)">
|
||||
<path d="M13.4,6 C16.3,10.5 16.4,15.6 13.6,17.4 C10.8,19.2 6.2,17.1 3.2,12.6 C0.3,8.1 0.2,3 3,1.2 C5.8,-0.7 10.5,1.5 13.4,6 Z" id="pupil" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13,11.7 C13,12.8 12.2,13.7 11.2,13.7 C10.2,13.7 9.4,12.8 9.4,11.7 C9.4,10.6 10.2,9.7 11.2,9.7 C12.2,9.7 13,10.6 13,11.7" id="pupil" fill="#001026"/>
|
||||
</g>
|
||||
<g id="eye" transform="translate(19.000000, 18.000000)">
|
||||
<path d="M13.6,5.8 C16.6,10.2 16.4,15.6 13.7,17.5 C10.4,19.4 6,18 3,13.6 C-0.1,9.2 -0.3,3.5 2.8,1.3 C5.9,-1 10.6,1.4 13.6,5.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13,11.6 C13,12.7 12.2,13.6 11.2,13.6 C10.2,13.6 9.4,12.7 9.4,11.6 C9.4,10.5 10.2,9.6 11.2,9.6 C12.2,9.7 13,10.5 13,11.6" id="pupil" fill="#001026"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -4,8 +4,8 @@ import projectData from './project-data';
|
||||
import popWav from '!arraybuffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav';
|
||||
import meowWav from '!arraybuffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav';
|
||||
import backdrop from '!raw-loader!./cd21514d0531fdffb22204e0ec5ed84a.svg';
|
||||
import costume1 from '!raw-loader!./b7853f557e4426412e64bb3da6531a99.svg';
|
||||
import costume2 from '!raw-loader!./e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg';
|
||||
import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg';
|
||||
import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg';
|
||||
/* eslint-enable import/no-unresolved */
|
||||
|
||||
const defaultProject = translator => {
|
||||
@@ -40,12 +40,12 @@ const defaultProject = translator => {
|
||||
dataFormat: 'SVG',
|
||||
data: encoder.encode(backdrop)
|
||||
}, {
|
||||
id: 'b7853f557e4426412e64bb3da6531a99',
|
||||
id: '09dc888b0b7df19f70d81588ae73420e',
|
||||
assetType: 'ImageVector',
|
||||
dataFormat: 'SVG',
|
||||
data: encoder.encode(costume1)
|
||||
}, {
|
||||
id: 'e6ddc55a6ddd9cc9d84fe0b4c21e016f',
|
||||
id: '3696356a03a8d938318876a593572843',
|
||||
assetType: 'ImageVector',
|
||||
dataFormat: 'SVG',
|
||||
data: encoder.encode(costume2)
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// 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';
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
// TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime
|
||||
const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode) => ({
|
||||
init: function () {
|
||||
const blockJson = {
|
||||
type: extendedOpcode,
|
||||
inputsInline: true,
|
||||
category: categoryInfo.name,
|
||||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3
|
||||
};
|
||||
// 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;
|
||||
@@ -1,125 +0,0 @@
|
||||
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) {
|
||||
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._onDragEnd();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
_isDrag () {
|
||||
return this._gestureState === DragRecognizer.STATE_DRAG;
|
||||
}
|
||||
|
||||
_isScroll () {
|
||||
return this._gestureState === DragRecognizer.STATE_SCROLL;
|
||||
}
|
||||
}
|
||||
|
||||
export default DragRecognizer;
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Andie-a",
|
||||
"md5": "d2c433e02481b10584e34ccdcf5a046a.svg",
|
||||
"md5": "419ff5a76adbb4b076c072f68267fdeb.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -87,33 +87,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
80,
|
||||
65,
|
||||
86,
|
||||
81,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-b",
|
||||
"md5": "045f92ad80d72521098ede6d7c80549a.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
"basketball",
|
||||
"people",
|
||||
"wheelchair",
|
||||
"handicap",
|
||||
"handicapable",
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
40,
|
||||
91,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-c",
|
||||
"md5": "c9bad70ad371b8d6d97daed7ab9def98.svg",
|
||||
"md5": "a6a766912b4e43064ee3f389c72925b4.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -126,13 +107,13 @@
|
||||
],
|
||||
"info": [
|
||||
46,
|
||||
49,
|
||||
106,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-d",
|
||||
"md5": "7c9db638cd671898ff07771112faff0f.svg",
|
||||
"name": "Andie-c",
|
||||
"md5": "fac1025857d2c28391cb31f39a80df54.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -144,8 +125,27 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
71,
|
||||
57,
|
||||
52,
|
||||
65,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-d",
|
||||
"md5": "7af50835e05d43e5e07cd2d641e7401e.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
"basketball",
|
||||
"people",
|
||||
"wheelchair",
|
||||
"handicap",
|
||||
"handicapable",
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
87,
|
||||
72,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -1069,7 +1069,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Ben-a",
|
||||
"md5": "322e13bd17d02acfdc048a5ef2167be8.svg",
|
||||
"md5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1079,14 +1079,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
54,
|
||||
53,
|
||||
69,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ben-b",
|
||||
"md5": "59ba1e80ba75b1d06c37db4b9b4fde27.svg",
|
||||
"md5": "90db137cd0743204d02fdc9be1c8eb94.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1096,14 +1096,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
54,
|
||||
69,
|
||||
53,
|
||||
67,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ben-c",
|
||||
"md5": "940ec2fa85d666dde95a51bcbccd266e.svg",
|
||||
"md5": "5f05d41ede773b3bb379286e86a5b869.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1113,14 +1113,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
73,
|
||||
71,
|
||||
72,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ben-d",
|
||||
"md5": "3043a50a91b9d484a8cd32eb82f2d88f.svg",
|
||||
"md5": "6f6e246567c0639c758ed4f6930be76f.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1130,8 +1130,8 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
44,
|
||||
71,
|
||||
42,
|
||||
72,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -3628,17 +3628,16 @@
|
||||
},
|
||||
{
|
||||
"name": "Earth",
|
||||
"md5": "64220361b80d2812e9f27fc88ef39027.svg",
|
||||
"md5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"space",
|
||||
"planet",
|
||||
"earth",
|
||||
"globe"
|
||||
"earth"
|
||||
],
|
||||
"info": [
|
||||
55,
|
||||
55,
|
||||
41,
|
||||
40,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -6179,7 +6178,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-a",
|
||||
"md5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
|
||||
"md5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6190,13 +6189,13 @@
|
||||
],
|
||||
"info": [
|
||||
51,
|
||||
62,
|
||||
63,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-b",
|
||||
"md5": "075f382883e60dba8094b3dc30918f60.svg",
|
||||
"md5": "2e2c43bbd7b18524fb132f1426ca3134.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6213,7 +6212,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-c",
|
||||
"md5": "fbe81c40d4a880c8799c458359758629.svg",
|
||||
"md5": "5c0d24c53805e16358a2f6f6cc4a456e.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6224,13 +6223,13 @@
|
||||
],
|
||||
"info": [
|
||||
68,
|
||||
62,
|
||||
61,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-d",
|
||||
"md5": "68a784d4d0340475a6e6feff3b28b470.svg",
|
||||
"md5": "ebe2b20b99c1e1b373fd2b02be48aab9.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6241,7 +6240,7 @@
|
||||
],
|
||||
"info": [
|
||||
40,
|
||||
62,
|
||||
61,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -9661,7 +9660,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Sun",
|
||||
"md5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
|
||||
"md5": "55c931c65456822c0c56a2b30e3e550d.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"space",
|
||||
@@ -9672,8 +9671,8 @@
|
||||
"nuclear"
|
||||
],
|
||||
"info": [
|
||||
54,
|
||||
54,
|
||||
72,
|
||||
72,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -10583,24 +10582,6 @@
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Wizard Girl",
|
||||
"md5": "dd40d3a2565990bcc24d2b36cd446394.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"people",
|
||||
"female",
|
||||
"girl",
|
||||
"wizard",
|
||||
"magic",
|
||||
"fantasy"
|
||||
],
|
||||
"info": [
|
||||
80,
|
||||
91,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Wizard Hat",
|
||||
"md5": "2da561a7e5cbfcda613f4750020d4aa5.svg",
|
||||
@@ -12701,4 +12682,4 @@
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -90,86 +90,6 @@ import videoPet from './steps/video-pet.en.gif';
|
||||
import videoAnimate from './steps/video-animate.en.gif';
|
||||
import videoPop from './steps/video-pop.en.gif';
|
||||
|
||||
// Make it Fly
|
||||
import flyChooseBackdrop from './steps/fly-choose-backdrop.en.gif';
|
||||
import flyChooseCharacter from './steps/fly-choose-character.en.gif';
|
||||
import flySaySomething from './steps/fly-say-something.en.gif';
|
||||
import flyMoveArrows from './steps/fly-make-interactive.en.gif';
|
||||
import flyChooseObject from './steps/fly-object-to-collect.en.gif';
|
||||
import flyFlyingObject from './steps/fly-flying-heart.en.gif';
|
||||
import flySelectFlyingSprite from './steps/fly-select-flyer.en.jpg';
|
||||
import flyAddScore from './steps/fly-add-score.en.gif';
|
||||
import flyKeepScore from './steps/fly-keep-score.en.gif';
|
||||
import flyAddScenery from './steps/fly-choose-scenery.en.gif';
|
||||
import flyMoveScenery from './steps/fly-move-scenery.en.gif';
|
||||
import flySwitchLooks from './steps/fly-switch-costume.en.gif';
|
||||
|
||||
// Pong
|
||||
import pongAddBackdrop from './steps/pong-add-backdrop.en.png';
|
||||
import pongAddBallSprite from './steps/pong-add-ball-sprite.en.png';
|
||||
import pongBounceAround from './steps/pong-bounce-around.en.gif';
|
||||
import pongAddPaddle from './steps/pong-add-a-paddle.en.gif';
|
||||
import pongMoveThePaddle from './steps/pong-move-the-paddle.en.gif';
|
||||
import pongSelectBallSprite from './steps/pong-select-ball.en.png';
|
||||
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.en.png';
|
||||
import pongAddAScore from './steps/pong-add-score.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.en.gif';
|
||||
import pongGameOver from './steps/pong-game-over.en.png';
|
||||
|
||||
// Imagine If
|
||||
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.en.png';
|
||||
import imagineChooseSprite from './steps/imagine-choose-any-sprite.en.png';
|
||||
import imagineFlyAround from './steps/imagine-fly-around.en.png';
|
||||
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.en.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.en.png';
|
||||
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.en.gif';
|
||||
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.en.png';
|
||||
|
||||
// Add Effectsf
|
||||
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';
|
||||
|
||||
const enImages = {
|
||||
// Intro
|
||||
introMove: introMove,
|
||||
@@ -261,89 +181,8 @@ const enImages = {
|
||||
videoAddExtension: videoAddExtension,
|
||||
videoPet: videoPet,
|
||||
videoAnimate: videoAnimate,
|
||||
videoPop: videoPop,
|
||||
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 If
|
||||
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
|
||||
|
||||
};
|
||||
|
||||
|
||||
export {enImages};
|
||||
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 339 KiB |
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 473 KiB |
|
Before Width: | Height: | Size: 179 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 169 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 69 KiB |
@@ -74,12 +74,6 @@ const videos = {
|
||||
'en': '3jvl8zgjo2',
|
||||
'ja': 'q7zq9vu0wb',
|
||||
'ja-Hira': 'q7zq9vu0wb'
|
||||
},
|
||||
'make-it-fly': {
|
||||
en: 'zbtdx2dem9'
|
||||
},
|
||||
'pong-game': {
|
||||
en: '8m48dv0ens'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Andie",
|
||||
"md5": "d2c433e02481b10584e34ccdcf5a046a.svg",
|
||||
"md5": "419ff5a76adbb4b076c072f68267fdeb.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -138,7 +138,7 @@
|
||||
"objName": "Andie",
|
||||
"sounds": [
|
||||
{
|
||||
"soundName": "Basketball Bounce",
|
||||
"soundName": "basketball bounce",
|
||||
"soundID": -1,
|
||||
"md5": "1727f65b5f22d151685b8e5917456a60.wav",
|
||||
"sampleCount": 8099,
|
||||
@@ -149,40 +149,40 @@
|
||||
"costumes": [
|
||||
{
|
||||
"costumeName": "andie-a",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "d2c433e02481b10584e34ccdcf5a046a.svg",
|
||||
"baseLayerID": 0,
|
||||
"baseLayerMD5": "419ff5a76adbb4b076c072f68267fdeb.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 80,
|
||||
"rotationCenterY": 65
|
||||
"rotationCenterX": 86,
|
||||
"rotationCenterY": 81
|
||||
},
|
||||
{
|
||||
"costumeName": "andie-b",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "045f92ad80d72521098ede6d7c80549a.svg",
|
||||
"baseLayerMD5": "a6a766912b4e43064ee3f389c72925b4.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 40,
|
||||
"rotationCenterY": 91
|
||||
"rotationCenterX": 46,
|
||||
"rotationCenterY": 106
|
||||
},
|
||||
{
|
||||
"costumeName": "andie-c",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "c9bad70ad371b8d6d97daed7ab9def98.svg",
|
||||
"baseLayerMD5": "fac1025857d2c28391cb31f39a80df54.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 46,
|
||||
"rotationCenterY": 49
|
||||
"rotationCenterX": 52,
|
||||
"rotationCenterY": 65
|
||||
},
|
||||
{
|
||||
"costumeName": "andie-d",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "7c9db638cd671898ff07771112faff0f.svg",
|
||||
"baseLayerMD5": "7af50835e05d43e5e07cd2d641e7401e.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 71,
|
||||
"rotationCenterY": 57
|
||||
"rotationCenterX": 87,
|
||||
"rotationCenterY": 72
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -150,
|
||||
"scratchY": 90,
|
||||
"scratchX": -129,
|
||||
"scratchY": -73,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -1442,7 +1442,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Ben",
|
||||
"md5": "322e13bd17d02acfdc048a5ef2167be8.svg",
|
||||
"md5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1460,18 +1460,18 @@
|
||||
"objName": "Ben",
|
||||
"sounds": [
|
||||
{
|
||||
"soundName": "Goal Cheer",
|
||||
"soundName": "pop",
|
||||
"soundID": -1,
|
||||
"md5": "a434069c58e79d42f5d21abb1c318919.wav",
|
||||
"sampleCount": 84096,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
|
||||
"sampleCount": 258,
|
||||
"rate": 11025,
|
||||
"format": ""
|
||||
},
|
||||
{
|
||||
"soundName": "Referee Whistle",
|
||||
"soundName": "basketball bounce",
|
||||
"soundID": -1,
|
||||
"md5": "8468b9b3f11a665ee4d215afd8463b97.wav",
|
||||
"sampleCount": 14034,
|
||||
"md5": "1727f65b5f22d151685b8e5917456a60.wav",
|
||||
"sampleCount": 8099,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}
|
||||
@@ -1480,39 +1480,39 @@
|
||||
{
|
||||
"costumeName": "ben-a",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "322e13bd17d02acfdc048a5ef2167be8.svg",
|
||||
"baseLayerMD5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterX": 53,
|
||||
"rotationCenterY": 69
|
||||
},
|
||||
{
|
||||
"costumeName": "ben-b",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "59ba1e80ba75b1d06c37db4b9b4fde27.svg",
|
||||
"baseLayerMD5": "90db137cd0743204d02fdc9be1c8eb94.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterY": 69
|
||||
"rotationCenterX": 53,
|
||||
"rotationCenterY": 67
|
||||
},
|
||||
{
|
||||
"costumeName": "ben-c",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "940ec2fa85d666dde95a51bcbccd266e.svg",
|
||||
"baseLayerMD5": "5f05d41ede773b3bb379286e86a5b869.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 73,
|
||||
"rotationCenterY": 71
|
||||
"rotationCenterX": 71,
|
||||
"rotationCenterY": 72
|
||||
},
|
||||
{
|
||||
"costumeName": "ben-d",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "3043a50a91b9d484a8cd32eb82f2d88f.svg",
|
||||
"baseLayerMD5": "6f6e246567c0639c758ed4f6930be76f.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 44,
|
||||
"rotationCenterY": 71
|
||||
"rotationCenterX": 42,
|
||||
"rotationCenterY": 72
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -15,
|
||||
"scratchY": 93,
|
||||
"scratchX": -63,
|
||||
"scratchY": -94,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -5434,13 +5434,12 @@
|
||||
},
|
||||
{
|
||||
"name": "Earth",
|
||||
"md5": "64220361b80d2812e9f27fc88ef39027.svg",
|
||||
"md5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"space",
|
||||
"planet",
|
||||
"earth",
|
||||
"globe"
|
||||
"earth"
|
||||
],
|
||||
"info": [
|
||||
0,
|
||||
@@ -5461,17 +5460,17 @@
|
||||
],
|
||||
"costumes": [
|
||||
{
|
||||
"costumeName": "earth",
|
||||
"costumeName": "Earth",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "64220361b80d2812e9f27fc88ef39027.svg",
|
||||
"baseLayerMD5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 55,
|
||||
"rotationCenterY": 55
|
||||
"rotationCenterX": 41,
|
||||
"rotationCenterY": 40
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 142,
|
||||
"scratchY": -53,
|
||||
"scratchX": 57,
|
||||
"scratchY": -39,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -8843,7 +8842,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jordyn",
|
||||
"md5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
|
||||
"md5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -8855,24 +8854,16 @@
|
||||
"info": [
|
||||
0,
|
||||
4,
|
||||
2
|
||||
1
|
||||
],
|
||||
"json": {
|
||||
"objName": "Jordyn",
|
||||
"sounds": [
|
||||
{
|
||||
"soundName": "Goal Cheer",
|
||||
"soundName": "basketball bounce",
|
||||
"soundID": -1,
|
||||
"md5": "199b30c8b4fe0642e849924bd1e1b463.wav",
|
||||
"sampleCount": 84411,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
},
|
||||
{
|
||||
"soundName": "Referee Whistle",
|
||||
"soundID": -1,
|
||||
"md5": "7d91d95d841dc6cf1282914306a4674a.wav",
|
||||
"sampleCount": 14238,
|
||||
"md5": "1727f65b5f22d151685b8e5917456a60.wav",
|
||||
"sampleCount": 8099,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}
|
||||
@@ -8881,15 +8872,15 @@
|
||||
{
|
||||
"costumeName": "jordyn-a",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
|
||||
"baseLayerMD5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 51,
|
||||
"rotationCenterY": 62
|
||||
"rotationCenterY": 63
|
||||
},
|
||||
{
|
||||
"costumeName": "jordyn-b",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "075f382883e60dba8094b3dc30918f60.svg",
|
||||
"baseLayerMD5": "2e2c43bbd7b18524fb132f1426ca3134.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 51,
|
||||
"rotationCenterY": 63
|
||||
@@ -8897,23 +8888,23 @@
|
||||
{
|
||||
"costumeName": "jordyn-c",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "fbe81c40d4a880c8799c458359758629.svg",
|
||||
"baseLayerMD5": "5c0d24c53805e16358a2f6f6cc4a456e.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 68,
|
||||
"rotationCenterY": 62
|
||||
"rotationCenterY": 61
|
||||
},
|
||||
{
|
||||
"costumeName": "jordyn-d",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "68a784d4d0340475a6e6feff3b28b470.svg",
|
||||
"baseLayerMD5": "ebe2b20b99c1e1b373fd2b02be48aab9.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 40,
|
||||
"rotationCenterY": 62
|
||||
"rotationCenterY": 61
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 61,
|
||||
"scratchY": 93,
|
||||
"scratchX": -164,
|
||||
"scratchY": -41,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -14381,7 +14372,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Sun",
|
||||
"md5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
|
||||
"md5": "55c931c65456822c0c56a2b30e3e550d.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"space",
|
||||
@@ -14412,15 +14403,15 @@
|
||||
{
|
||||
"costumeName": "sun",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
|
||||
"baseLayerMD5": "55c931c65456822c0c56a2b30e3e550d.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterY": 54
|
||||
"rotationCenterX": 72,
|
||||
"rotationCenterY": 72
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -3,
|
||||
"scratchY": -42,
|
||||
"scratchX": 16,
|
||||
"scratchY": -28,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -15787,56 +15778,6 @@
|
||||
"spriteInfo": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Wizard Girl",
|
||||
"md5": "dd40d3a2565990bcc24d2b36cd446394.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"people",
|
||||
"female",
|
||||
"girl",
|
||||
"wizard",
|
||||
"magic",
|
||||
"fantasy"
|
||||
],
|
||||
"info": [
|
||||
0,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"json": {
|
||||
"objName": "Wizard Girl",
|
||||
"sounds": [
|
||||
{
|
||||
"soundName": "Magic Spell",
|
||||
"soundID": -1,
|
||||
"md5": "1cb60ecdb1075c8769cb346d5c2a22c7.wav",
|
||||
"sampleCount": 43077,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}
|
||||
],
|
||||
"costumes": [
|
||||
{
|
||||
"costumeName": "wizard girl",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "dd40d3a2565990bcc24d2b36cd446394.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 80,
|
||||
"rotationCenterY": 91
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -147,
|
||||
"scratchY": -71,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
"isDraggable": false,
|
||||
"visible": true,
|
||||
"spriteInfo": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Wizard Hat",
|
||||
"md5": "2da561a7e5cbfcda613f4750020d4aa5.svg",
|
||||
@@ -20471,4 +20412,4 @@
|
||||
"spriteInfo": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -716,16 +716,13 @@ const xmlClose = '</xml>';
|
||||
/**
|
||||
* @param {!boolean} isStage - Whether the toolbox is for a stage-type target.
|
||||
* @param {?string} targetId - The current editing target
|
||||
* @param {?Array.<object>} categoriesXML - optional array of `{id,xml}` for categories. This can include both core
|
||||
* and other extensions: core extensions will be placed in the normal Scratch order; others will go at the bottom.
|
||||
* @property {string} id - the extension / category ID.
|
||||
* @property {string} xml - the `<category>...</category>` XML for this extension / category.
|
||||
* @param {?string} categoriesXML - null for default toolbox, or an XML string with <category> elements.
|
||||
* @param {?string} costumeName - The name of the default selected costume dropdown.
|
||||
* @param {?string} backdropName - The name of the default selected backdrop dropdown.
|
||||
* @param {?string} soundName - The name of the default selected sound dropdown.
|
||||
* @returns {string} - a ScratchBlocks-style XML document for the contents of the toolbox.
|
||||
*/
|
||||
const makeToolboxXML = function (isStage, targetId, categoriesXML = [],
|
||||
const makeToolboxXML = function (isStage, targetId, categoriesXML,
|
||||
costumeName = '', backdropName = '', soundName = '') {
|
||||
const gap = [categorySeparator];
|
||||
|
||||
@@ -733,41 +730,21 @@ const makeToolboxXML = function (isStage, targetId, categoriesXML = [],
|
||||
backdropName = xmlEscape(backdropName);
|
||||
soundName = xmlEscape(soundName);
|
||||
|
||||
categoriesXML = categoriesXML.slice();
|
||||
const moveCategory = categoryId => {
|
||||
const index = categoriesXML.findIndex(categoryInfo => categoryInfo.id === categoryId);
|
||||
if (index >= 0) {
|
||||
// remove the category from categoriesXML and return its XML
|
||||
const [categoryInfo] = categoriesXML.splice(index, 1);
|
||||
return categoryInfo.xml;
|
||||
}
|
||||
// return `undefined`
|
||||
};
|
||||
const motionXML = moveCategory('motion') || motion(isStage, targetId);
|
||||
const looksXML = moveCategory('looks') || looks(isStage, targetId, costumeName, backdropName);
|
||||
const soundXML = moveCategory('sound') || sound(isStage, targetId, soundName);
|
||||
const eventsXML = moveCategory('event') || events(isStage, targetId);
|
||||
const controlXML = moveCategory('control') || control(isStage, targetId);
|
||||
const sensingXML = moveCategory('sensing') || sensing(isStage, targetId);
|
||||
const operatorsXML = moveCategory('operators') || operators(isStage, targetId);
|
||||
const variablesXML = moveCategory('data') || variables(isStage, targetId);
|
||||
const myBlocksXML = moveCategory('procedures') || myBlocks(isStage, targetId);
|
||||
|
||||
const everything = [
|
||||
xmlOpen,
|
||||
motionXML, gap,
|
||||
looksXML, gap,
|
||||
soundXML, gap,
|
||||
eventsXML, gap,
|
||||
controlXML, gap,
|
||||
sensingXML, gap,
|
||||
operatorsXML, gap,
|
||||
variablesXML, gap,
|
||||
myBlocksXML
|
||||
motion(isStage, targetId), gap,
|
||||
looks(isStage, targetId, costumeName, backdropName), gap,
|
||||
sound(isStage, targetId, soundName), gap,
|
||||
events(isStage, targetId), gap,
|
||||
control(isStage, targetId), gap,
|
||||
sensing(isStage, targetId), gap,
|
||||
operators(isStage, targetId), gap,
|
||||
variables(isStage, targetId), gap,
|
||||
myBlocks(isStage, targetId)
|
||||
];
|
||||
|
||||
for (const extensionCategory of categoriesXML) {
|
||||
everything.push(gap, extensionCategory.xml);
|
||||
if (categoriesXML) {
|
||||
everything.push(gap, categoriesXML);
|
||||
}
|
||||
|
||||
everything.push(xmlClose);
|
||||
|
||||
@@ -234,14 +234,9 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
asset.dataFormat,
|
||||
asset.data,
|
||||
asset.assetId
|
||||
).then(response => {
|
||||
// Asset servers respond with {status: ok} for successful POSTs
|
||||
if (response.status !== 'ok') {
|
||||
// Errors include a `code` property, e.g. "Forbidden"
|
||||
return Promise.reject(response.code);
|
||||
}
|
||||
asset.clean = true;
|
||||
})
|
||||
).then(
|
||||
() => (asset.clean = true)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(() => this.props.onUpdateProjectData(projectId, savedVMState, requestParams))
|
||||
|
||||
@@ -45,7 +45,6 @@ export default function (projectId, vmState, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr(opts, (err, response) => {
|
||||
if (err) return reject(err);
|
||||
if (response.statusCode !== 200) return reject(response.statusCode);
|
||||
let body;
|
||||
try {
|
||||
// Since we didn't set json: true, we have to parse manually
|
||||
|
||||