Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
765a5d3b95 | ||
|
|
351c0ba9b1 | ||
|
|
ac30e17222 | ||
|
|
bcdb2d5f3c | ||
|
|
e70a99e52d | ||
|
|
c6230ffc66 | ||
|
|
edaf9badcf | ||
|
|
c4122ec03f | ||
|
|
288aa721b6 | ||
|
|
dd2841f349 | ||
|
|
c9808e0eee | ||
|
|
6bf9fac393 | ||
|
|
6d482fdd5e | ||
|
|
48c50692fc | ||
|
|
4e47cc875a | ||
|
|
415ecee8ba | ||
|
|
d1e6960cd4 | ||
|
|
f2ac39fe0e | ||
|
|
8e67cd413a | ||
|
|
4418231892 | ||
|
|
8eee001c12 | ||
|
|
a290fb0227 | ||
|
|
42ddb2387a | ||
|
|
1e51907e74 | ||
|
|
568a75480f | ||
|
|
460a0f1969 | ||
|
|
8d4f86dc3b | ||
|
|
56f9f02798 | ||
|
|
abf1b731bd | ||
|
|
8dade6d387 | ||
|
|
73eb44c0f6 | ||
|
|
b190d29d46 | ||
|
|
f858590358 | ||
|
|
0795c511de | ||
|
|
de678d8663 | ||
|
|
5c60052775 | ||
|
|
a0edb49c0a | ||
|
|
1282f659f9 | ||
|
|
f7b91a79e6 | ||
|
|
426546c36e | ||
|
|
404d9ed65f | ||
|
|
753aebdba7 | ||
|
|
9d4218531b | ||
|
|
b56020be3e | ||
|
|
b5c5e28b59 | ||
|
|
8a2bcb3f53 | ||
|
|
e5ae5b7035 | ||
|
|
413df6ed65 | ||
|
|
0ec46056f0 | ||
|
|
9dcb1f8126 | ||
|
|
4e9c8aea01 | ||
|
|
7d748ce59d | ||
|
|
198cf00498 | ||
|
|
145592b2e1 | ||
|
|
6fe8f7b2ec | ||
|
|
3cbfa7cfc3 | ||
|
|
30a35d7651 | ||
|
|
ab31d36251 | ||
|
|
7362bd999c | ||
|
|
b4a0879584 | ||
|
|
8274a8c6c7 | ||
|
|
cdffd567b6 | ||
|
|
c7003b4765 | ||
|
|
4e3f2160ae | ||
|
|
095fea939d | ||
|
|
26e4bc2ad2 | ||
|
|
18241a5cbd | ||
|
|
d8cd3e38c7 | ||
|
|
0bbb11012d | ||
|
|
212c998980 | ||
|
|
e359efd97e | ||
|
|
547640bfcc | ||
|
|
d7a2264c0d | ||
|
|
0bb91add74 | ||
|
|
cea2dd9ab9 | ||
|
|
c424f25da1 | ||
|
|
7c1931749c | ||
|
|
4708ccc7dc | ||
|
|
9e0a239ec8 | ||
|
|
8a2c001531 | ||
|
|
044b958e08 | ||
|
|
a2bbed65d5 | ||
|
|
cf62045e26 | ||
|
|
164ddbe4f4 | ||
|
|
6b5eb032ad | ||
|
|
8d3a85860e | ||
|
|
cbb265e9f0 | ||
|
|
948fb2f747 | ||
|
|
b0ca6dfcaa | ||
|
|
61eb31ea66 | ||
|
|
eed4c36b7b | ||
|
|
b5a3a53780 | ||
|
|
a6302ecc14 | ||
|
|
2a9d74adb4 | ||
|
|
e5772a5902 | ||
|
|
abdeb8dbdb | ||
|
|
8611c9ff1a | ||
|
|
f7244e6c89 | ||
|
|
4b7f72c00c | ||
|
|
1476c88a54 | ||
|
|
c061f2e2f0 | ||
|
|
dc49c0ebe4 | ||
|
|
11d4273982 | ||
|
|
7755f76215 | ||
|
|
6e44bfea95 | ||
|
|
fa8ceacbcf | ||
|
|
d19c468336 | ||
|
|
86edd829c0 | ||
|
|
bdc9c1e7de | ||
|
|
6e007b1e4a | ||
|
|
356c1d9811 | ||
|
|
723a2bf117 | ||
|
|
7b7e5e6e72 | ||
|
|
3fcbf74c71 | ||
|
|
8f4a6ce255 | ||
|
|
9e0853aa9a | ||
|
|
c4480b9ade | ||
|
|
1a588880bf | ||
|
|
e2c2392a70 | ||
|
|
5160b5ae22 | ||
|
|
8be847b68a | ||
|
|
e7ed24f1ef | ||
|
|
13e324b52d | ||
|
|
5c7b221799 | ||
|
|
7398a30223 | ||
|
|
637b9fd225 | ||
|
|
88442708c2 | ||
|
|
d7759317c4 | ||
|
|
fd3045eb9d | ||
|
|
09ae22f329 | ||
|
|
365e3b3bce | ||
|
|
a6fe8585fb | ||
|
|
930b2ec05c | ||
|
|
c9d1535967 | ||
|
|
550849c93f | ||
|
|
8128902cd6 | ||
|
|
b350049ca7 | ||
|
|
e0e07929e8 | ||
|
|
128b196220 | ||
|
|
5b806dff27 | ||
|
|
9c5d358c3e | ||
|
|
ab89ee1a4f | ||
|
|
dc82e5f653 | ||
|
|
0a6c75eb93 | ||
|
|
38455a7108 | ||
|
|
ebe310bc65 | ||
|
|
5bc3198263 | ||
|
|
5d6669db6d | ||
|
|
519ffe3b91 | ||
|
|
717c773800 | ||
|
|
935a4520da | ||
|
|
7685dad40a | ||
|
|
98ceb00bb4 | ||
|
|
77974839b0 |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
last 3 versions
|
||||
Safari >= 8
|
||||
iOS >= 8
|
||||
@@ -4,7 +4,6 @@
|
||||
# NPM
|
||||
/node_modules
|
||||
npm-*
|
||||
/package-lock.json
|
||||
|
||||
# Testing
|
||||
/.nyc_output
|
||||
|
||||
@@ -14,12 +14,18 @@ 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
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"react-draggable": "3.0.5",
|
||||
"react-ga": "2.5.3",
|
||||
"react-intl": "2.9.0",
|
||||
"react-modal": "3.6.1",
|
||||
"react-modal": "3.9.1",
|
||||
"react-popover": "0.5.10",
|
||||
"react-redux": "5.0.7",
|
||||
"react-responsive": "5.0.0",
|
||||
@@ -105,13 +105,13 @@
|
||||
"redux-throttle": "0.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20190114210212",
|
||||
"scratch-l10n": "3.3.20190529144051",
|
||||
"scratch-blocks": "0.1.0-prerelease.1559136797",
|
||||
"scratch-paint": "0.2.0-prerelease.20190524133325",
|
||||
"scratch-render": "0.1.0-prerelease.20190528231524",
|
||||
"scratch-l10n": "3.5.20190724160319",
|
||||
"scratch-blocks": "0.1.0-prerelease.1561987297",
|
||||
"scratch-paint": "0.2.0-prerelease.20190724154115",
|
||||
"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.20190524155459",
|
||||
"scratch-vm": "0.2.0-prerelease.20190722173823",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
}
|
||||
|
||||
.step-body {
|
||||
width: 100%;
|
||||
background: $ui-white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -169,6 +170,9 @@
|
||||
align-items: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
/* Min height prevents layout changing when images change */
|
||||
min-height: 256px;
|
||||
}
|
||||
|
||||
.step-video {
|
||||
@@ -177,6 +181,8 @@
|
||||
|
||||
.step-image {
|
||||
max-width: 450px;
|
||||
max-height: 200px;
|
||||
object-fit: contain;
|
||||
background: #F9F9F9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
@@ -86,6 +86,7 @@ const CardHeader = ({onCloseCards, onShrinkExpandCards, onShowAll, totalSteps, s
|
||||
);
|
||||
|
||||
class VideoStep extends React.Component {
|
||||
|
||||
componentDidMount () {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://fast.wistia.com/embed/medias/${this.props.video}.jsonp`;
|
||||
@@ -99,12 +100,27 @@ class VideoStep extends React.Component {
|
||||
script2.setAttribute('id', 'wistia-video-api');
|
||||
document.body.appendChild(script2);
|
||||
}
|
||||
componentDidUpdate () {
|
||||
const video = window.Wistia.api(`${this.props.video}`);
|
||||
|
||||
// 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);
|
||||
@@ -112,6 +128,7 @@ class VideoStep extends React.Component {
|
||||
const script2 = document.getElementById('wistia-video-api');
|
||||
script2.parentNode.removeChild(script2);
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={styles.stepVideo}>
|
||||
@@ -141,6 +158,7 @@ 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>
|
||||
@@ -274,6 +292,7 @@ const Cards = props => {
|
||||
onShowAll,
|
||||
onNextStep,
|
||||
onPrevStep,
|
||||
showVideos,
|
||||
step,
|
||||
expanded,
|
||||
...posProps
|
||||
@@ -342,11 +361,18 @@ const Cards = props => {
|
||||
/>
|
||||
) : (
|
||||
steps[step].video ? (
|
||||
<VideoStep
|
||||
dragging={dragging}
|
||||
expanded={expanded}
|
||||
video={translateVideo(steps[step].video, locale)}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<ImageStep
|
||||
image={translateImage(steps[step].image, locale)}
|
||||
@@ -396,9 +422,19 @@ 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
|
||||
};
|
||||
|
||||
export default Cards;
|
||||
Cards.defaultProps = {
|
||||
showVideos: true
|
||||
};
|
||||
|
||||
export {
|
||||
Cards as default,
|
||||
// Others exported for testability
|
||||
ImageStep,
|
||||
VideoStep
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
|
||||
/* the delete button has .25rem invisible 'slop' around the visible button
|
||||
to make it easier to tap on a touch device */
|
||||
.delete-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
overflow: hidden; /* Mask the icon animation */
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease-out;
|
||||
|
||||
}
|
||||
|
||||
.delete-button-visible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
overflow: hidden; /* Mask the icon animation */
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
box-shadow: 0px 0px 0px 2px $motion-transparent;
|
||||
background-color: $motion-primary;
|
||||
color: $ui-white;
|
||||
border-radius: 50%;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease-out;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
position: relative;
|
||||
margin: 0.25rem;
|
||||
user-select: none;
|
||||
transform-origin: 50%;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './delete-button.css';
|
||||
import deleteIcon from './icon--delete.svg';
|
||||
|
||||
const DeleteButton = props => (
|
||||
<div
|
||||
aria-label="Delete"
|
||||
className={classNames(
|
||||
styles.deleteButton,
|
||||
props.className
|
||||
)}
|
||||
role="button"
|
||||
tabIndex={props.tabIndex}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<div className={styles.deleteButtonVisible}>
|
||||
<img
|
||||
className={styles.deleteIcon}
|
||||
src={deleteIcon}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
DeleteButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
tabIndex: PropTypes.number
|
||||
};
|
||||
|
||||
DeleteButton.defaultProps = {
|
||||
tabIndex: 0
|
||||
};
|
||||
|
||||
export default DeleteButton;
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
|
||||
<title>Sound/General/Delete</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M4.54751641,6.99994966 L15.4523042,6.99994966 C15.7284466,6.99994966 15.9523042,7.22380729 15.9523042,7.49994966 C15.9523042,7.51506367 15.9516189,7.5301699 15.9502504,7.54522183 L15.1651793,16.1801783 C15.0715275,17.2102489 14.207924,17.9989808 13.1736049,17.9990897 L6.82662224,17.9997575 C5.79213514,17.9998663 4.92828345,17.2110677 4.83462539,16.180829 L4.04956981,7.54521753 C4.02456905,7.27020922 4.22724022,7.02700381 4.50224854,7.00200306 C4.51729904,7.00063483 4.53240384,6.99994966 4.54751641,6.99994966 Z M7.33333333,4 L7.88603796,2.34188612 C7.95409498,2.13771505 8.14516441,2 8.36037961,2 L11.6396204,2 C11.8548356,2 12.045905,2.13771505 12.113962,2.34188612 L12.6666667,4 L16.5,4 C16.7761424,4 17,4.22385763 17,4.5 L17,5.5 C17,5.77614237 16.7761424,6 16.5,6 L3.5,6 C3.22385763,6 3,5.77614237 3,5.5 L3,4.5 C3,4.22385763 3.22385763,4 3.5,4 L7.33333333,4 Z M8.38742589,4 L11.6125741,4 L11.2792408,3 L8.72075922,3 L8.38742589,4 Z M10,11.7204812 L11.5952436,10.1252376 C11.7905057,9.92997548 12.1070882,9.92997548 12.3023504,10.1252376 L12.3747624,10.1976496 C12.5700245,10.3929118 12.5700245,10.7094943 12.3747624,10.9047564 L10.7795188,12.5 L12.3747624,14.0952436 C12.5700245,14.2905057 12.5700245,14.6070882 12.3747624,14.8023504 L12.3023504,14.8747624 C12.1070882,15.0700245 11.7905057,15.0700245 11.5952436,14.8747624 L10,13.2795188 L8.40475641,14.8747624 C8.20949427,15.0700245 7.89291178,15.0700245 7.69764963,14.8747624 L7.62523762,14.8023504 C7.42997548,14.6070882 7.42997548,14.2905057 7.62523762,14.0952436 L9.22048121,12.5 L7.62523762,10.9047564 C7.42997548,10.7094943 7.42997548,10.3929118 7.62523762,10.1976496 L7.69764963,10.1252376 C7.89291178,9.92997548 8.20949427,9.92997548 8.40475641,10.1252376 L10,11.7204812 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Sound/General/Delete" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Trash-Can"></g>
|
||||
<g id="White" mask="url(#mask-2)" fill="#FFFFFF">
|
||||
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -66,8 +66,10 @@ const GUIComponent = props => {
|
||||
backpackVisible,
|
||||
blocksTabVisible,
|
||||
cardsVisible,
|
||||
canChangeLanguage,
|
||||
canCreateNew,
|
||||
canEditTitle,
|
||||
canManageFiles,
|
||||
canRemix,
|
||||
canSave,
|
||||
canCreateCopy,
|
||||
@@ -85,6 +87,7 @@ const GUIComponent = props => {
|
||||
isRtl,
|
||||
isShared,
|
||||
loading,
|
||||
logo,
|
||||
renderLogin,
|
||||
onClickAccountNav,
|
||||
onCloseAccountNav,
|
||||
@@ -129,11 +132,7 @@ const GUIComponent = props => {
|
||||
};
|
||||
|
||||
if (isRendererSupported === null) {
|
||||
if (vm.renderer) {
|
||||
isRendererSupported = true;
|
||||
} else {
|
||||
isRendererSupported = Renderer.isSupported();
|
||||
}
|
||||
isRendererSupported = Renderer.isSupported();
|
||||
}
|
||||
|
||||
return (<MediaQuery minWidth={layout.fullSizeMinWidth}>{isFullSize => {
|
||||
@@ -206,15 +205,18 @@ const GUIComponent = props => {
|
||||
authorId={authorId}
|
||||
authorThumbnailUrl={authorThumbnailUrl}
|
||||
authorUsername={authorUsername}
|
||||
canChangeLanguage={canChangeLanguage}
|
||||
canCreateCopy={canCreateCopy}
|
||||
canCreateNew={canCreateNew}
|
||||
canEditTitle={canEditTitle}
|
||||
canManageFiles={canManageFiles}
|
||||
canRemix={canRemix}
|
||||
canSave={canSave}
|
||||
canShare={canShare}
|
||||
className={styles.menuBarPosition}
|
||||
enableCommunity={enableCommunity}
|
||||
isShared={isShared}
|
||||
logo={logo}
|
||||
renderLogin={renderLogin}
|
||||
showComingSoon={showComingSoon}
|
||||
onClickAccountNav={onClickAccountNav}
|
||||
@@ -363,9 +365,11 @@ GUIComponent.propTypes = {
|
||||
backpackVisible: PropTypes.bool,
|
||||
basePath: PropTypes.string,
|
||||
blocksTabVisible: PropTypes.bool,
|
||||
canChangeLanguage: PropTypes.bool,
|
||||
canCreateCopy: PropTypes.bool,
|
||||
canCreateNew: PropTypes.bool,
|
||||
canEditTitle: PropTypes.bool,
|
||||
canManageFiles: PropTypes.bool,
|
||||
canRemix: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
canShare: PropTypes.bool,
|
||||
@@ -382,6 +386,7 @@ GUIComponent.propTypes = {
|
||||
isRtl: PropTypes.bool,
|
||||
isShared: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
logo: PropTypes.string,
|
||||
onActivateCostumesTab: PropTypes.func,
|
||||
onActivateSoundsTab: PropTypes.func,
|
||||
onActivateTab: PropTypes.func,
|
||||
@@ -415,8 +420,10 @@ GUIComponent.defaultProps = {
|
||||
backpackHost: null,
|
||||
backpackVisible: false,
|
||||
basePath: './',
|
||||
canChangeLanguage: true,
|
||||
canCreateNew: false,
|
||||
canEditTitle: false,
|
||||
canManageFiles: true,
|
||||
canRemix: false,
|
||||
canSave: false,
|
||||
canCreateCopy: false,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
overflow-y: auto;
|
||||
height: auto;
|
||||
padding: 0.5rem;
|
||||
height: calc(100% - $library-header-height);
|
||||
}
|
||||
|
||||
.library-scroll-grid.withFilterBar {
|
||||
@@ -59,3 +60,11 @@
|
||||
height: $library-filter-bar-height;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spinner-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -9,6 +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 styles from './library.css';
|
||||
|
||||
@@ -44,9 +45,16 @@ class LibraryComponent extends React.Component {
|
||||
this.state = {
|
||||
selectedItem: null,
|
||||
filterQuery: '',
|
||||
selectedTag: ALL_TAG.tag
|
||||
selectedTag: ALL_TAG.tag,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
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) {
|
||||
@@ -162,7 +170,7 @@ class LibraryComponent extends React.Component {
|
||||
})}
|
||||
ref={this.setFilteredDataRef}
|
||||
>
|
||||
{this.getFilteredData().map((dataItem, index) => (
|
||||
{this.state.loaded ? this.getFilteredData().map((dataItem, index) => (
|
||||
<LibraryItem
|
||||
bluetoothRequired={dataItem.bluetoothRequired}
|
||||
collaborator={dataItem.collaborator}
|
||||
@@ -177,13 +185,20 @@ class LibraryComponent extends React.Component {
|
||||
id={index}
|
||||
insetIconURL={dataItem.insetIconURL}
|
||||
internetConnectionRequired={dataItem.internetConnectionRequired}
|
||||
key={`item_${index}`}
|
||||
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
|
||||
name={dataItem.name}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
))}
|
||||
)) : (
|
||||
<div className={styles.spinnerWrapper}>
|
||||
<Spinner
|
||||
large
|
||||
level="primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -91,7 +91,8 @@ LoupeComponent.propTypes = {
|
||||
color: PropTypes.shape({
|
||||
r: PropTypes.number,
|
||||
g: PropTypes.number,
|
||||
b: PropTypes.number
|
||||
b: PropTypes.number,
|
||||
a: PropTypes.number
|
||||
}),
|
||||
data: PropTypes.instanceOf(Uint8Array),
|
||||
width: PropTypes.number,
|
||||
|
||||
@@ -154,7 +154,7 @@ class MenuBar extends React.Component {
|
||||
'handleKeyPress',
|
||||
'handleLanguageMouseUp',
|
||||
'handleRestoreOption',
|
||||
'handleSaveToComputer',
|
||||
'getSaveToComputerHandler',
|
||||
'restoreOptionMessage'
|
||||
]);
|
||||
}
|
||||
@@ -225,7 +225,7 @@ class MenuBar extends React.Component {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
handleSaveToComputer (downloadProjectCallback) {
|
||||
getSaveToComputerHandler (downloadProjectCallback) {
|
||||
return () => {
|
||||
this.props.onRequestCloseFile();
|
||||
downloadProjectCallback();
|
||||
@@ -327,11 +327,11 @@ class MenuBar extends React.Component {
|
||||
[styles.clickable]: typeof this.props.onClickLogo !== 'undefined'
|
||||
})}
|
||||
draggable={false}
|
||||
src={scratchLogo}
|
||||
src={this.props.logo}
|
||||
onClick={this.props.onClickLogo}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
{(this.props.canChangeLanguage) && (<div
|
||||
className={classNames(styles.menuBarItem, styles.hoverable, styles.languageMenu)}
|
||||
>
|
||||
<div>
|
||||
@@ -345,82 +345,86 @@ class MenuBar extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<LanguageSelector label={this.props.intl.formatMessage(ariaMessages.language)} />
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles.menuBarItem, styles.hoverable, {
|
||||
[styles.active]: this.props.fileMenuOpen
|
||||
})}
|
||||
onMouseUp={this.props.onClickFile}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="File"
|
||||
description="Text for file dropdown menu"
|
||||
id="gui.menuBar.file"
|
||||
/>
|
||||
<MenuBarMenu
|
||||
className={classNames(styles.menuBarMenu)}
|
||||
open={this.props.fileMenuOpen}
|
||||
place={this.props.isRtl ? 'left' : 'right'}
|
||||
onRequestClose={this.props.onRequestCloseFile}
|
||||
</div>)}
|
||||
{(this.props.canManageFiles) && (
|
||||
<div
|
||||
className={classNames(styles.menuBarItem, styles.hoverable, {
|
||||
[styles.active]: this.props.fileMenuOpen
|
||||
})}
|
||||
onMouseUp={this.props.onClickFile}
|
||||
>
|
||||
<MenuSection>
|
||||
<MenuItem
|
||||
isRtl={this.props.isRtl}
|
||||
onClick={this.handleClickNew}
|
||||
>
|
||||
{newProjectMessage}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
{(this.props.canSave || this.props.canCreateCopy || this.props.canRemix) && (
|
||||
<FormattedMessage
|
||||
defaultMessage="File"
|
||||
description="Text for file dropdown menu"
|
||||
id="gui.menuBar.file"
|
||||
/>
|
||||
<MenuBarMenu
|
||||
className={classNames(styles.menuBarMenu)}
|
||||
open={this.props.fileMenuOpen}
|
||||
place={this.props.isRtl ? 'left' : 'right'}
|
||||
onRequestClose={this.props.onRequestCloseFile}
|
||||
>
|
||||
<MenuSection>
|
||||
{this.props.canSave ? (
|
||||
<MenuItem onClick={this.handleClickSave}>
|
||||
{saveNowMessage}
|
||||
</MenuItem>
|
||||
) : []}
|
||||
{this.props.canCreateCopy ? (
|
||||
<MenuItem onClick={this.handleClickSaveAsCopy}>
|
||||
{createCopyMessage}
|
||||
</MenuItem>
|
||||
) : []}
|
||||
{this.props.canRemix ? (
|
||||
<MenuItem onClick={this.handleClickRemix}>
|
||||
{remixMessage}
|
||||
</MenuItem>
|
||||
) : []}
|
||||
<MenuItem
|
||||
isRtl={this.props.isRtl}
|
||||
onClick={this.handleClickNew}
|
||||
>
|
||||
{newProjectMessage}
|
||||
</MenuItem>
|
||||
</MenuSection>
|
||||
)}
|
||||
<MenuSection>
|
||||
<SBFileUploader
|
||||
canSave={this.props.canSave}
|
||||
userOwnsProject={this.props.userOwnsProject}
|
||||
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
|
||||
>
|
||||
{(className, renderFileInput, loadProject) => (
|
||||
{(this.props.canSave || this.props.canCreateCopy || this.props.canRemix) && (
|
||||
<MenuSection>
|
||||
{this.props.canSave && (
|
||||
<MenuItem onClick={this.handleClickSave}>
|
||||
{saveNowMessage}
|
||||
</MenuItem>
|
||||
)}
|
||||
{this.props.canCreateCopy && (
|
||||
<MenuItem onClick={this.handleClickSaveAsCopy}>
|
||||
{createCopyMessage}
|
||||
</MenuItem>
|
||||
)}
|
||||
{this.props.canRemix && (
|
||||
<MenuItem onClick={this.handleClickRemix}>
|
||||
{remixMessage}
|
||||
</MenuItem>
|
||||
)}
|
||||
</MenuSection>
|
||||
)}
|
||||
<MenuSection>
|
||||
<SBFileUploader
|
||||
canSave={this.props.canSave}
|
||||
userOwnsProject={this.props.userOwnsProject}
|
||||
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
|
||||
>
|
||||
{(className, renderFileInput, handleLoadProject) => (
|
||||
<MenuItem
|
||||
className={className}
|
||||
onClick={handleLoadProject}
|
||||
>
|
||||
{/* eslint-disable max-len */}
|
||||
{this.props.intl.formatMessage(sharedMessages.loadFromComputerTitle)}
|
||||
{/* eslint-enable max-len */}
|
||||
{renderFileInput()}
|
||||
</MenuItem>
|
||||
)}
|
||||
</SBFileUploader>
|
||||
<SB3Downloader>{(className, downloadProjectCallback) => (
|
||||
<MenuItem
|
||||
className={className}
|
||||
onClick={loadProject}
|
||||
onClick={this.getSaveToComputerHandler(downloadProjectCallback)}
|
||||
>
|
||||
{this.props.intl.formatMessage(sharedMessages.loadFromComputerTitle)}
|
||||
{renderFileInput()}
|
||||
<FormattedMessage
|
||||
defaultMessage="Save to your computer"
|
||||
description="Menu bar item for downloading a project to your computer" // eslint-disable-line max-len
|
||||
id="gui.menuBar.downloadToComputer"
|
||||
/>
|
||||
</MenuItem>
|
||||
)}
|
||||
</SBFileUploader>
|
||||
<SB3Downloader>{(className, downloadProjectCallback) => (
|
||||
<MenuItem
|
||||
className={className}
|
||||
onClick={this.handleSaveToComputer(downloadProjectCallback)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Save to your computer"
|
||||
description="Menu bar item for downloading a project to your computer"
|
||||
id="gui.menuBar.downloadToComputer"
|
||||
/>
|
||||
</MenuItem>
|
||||
)}</SB3Downloader>
|
||||
</MenuSection>
|
||||
</MenuBarMenu>
|
||||
</div>
|
||||
)}</SB3Downloader>
|
||||
</MenuSection>
|
||||
</MenuBarMenu>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={classNames(styles.menuBarItem, styles.hoverable, {
|
||||
[styles.active]: this.props.editMenuOpen
|
||||
@@ -698,9 +702,11 @@ MenuBar.propTypes = {
|
||||
authorThumbnailUrl: PropTypes.string,
|
||||
authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
autoUpdateProject: PropTypes.func,
|
||||
canChangeLanguage: PropTypes.bool,
|
||||
canCreateCopy: PropTypes.bool,
|
||||
canCreateNew: PropTypes.bool,
|
||||
canEditTitle: PropTypes.bool,
|
||||
canManageFiles: PropTypes.bool,
|
||||
canRemix: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
canShare: PropTypes.bool,
|
||||
@@ -717,6 +723,7 @@ MenuBar.propTypes = {
|
||||
languageMenuOpen: PropTypes.bool,
|
||||
locale: PropTypes.string.isRequired,
|
||||
loginMenuOpen: PropTypes.bool,
|
||||
logo: PropTypes.string,
|
||||
onClickAccount: PropTypes.func,
|
||||
onClickEdit: PropTypes.func,
|
||||
onClickFile: PropTypes.func,
|
||||
@@ -751,6 +758,7 @@ MenuBar.propTypes = {
|
||||
};
|
||||
|
||||
MenuBar.defaultProps = {
|
||||
logo: scratchLogo,
|
||||
onShare: () => {}
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,16 @@
|
||||
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);
|
||||
@@ -71,3 +81,10 @@
|
||||
.spinner.info::after {
|
||||
border-top-color: $ui-white;
|
||||
}
|
||||
|
||||
.spinner.primary {
|
||||
border-color: $motion-transparent;
|
||||
}
|
||||
.spinner.primary::after {
|
||||
border-top-color: $motion-primary;
|
||||
}
|
||||
@@ -8,7 +8,8 @@ const SpinnerComponent = function (props) {
|
||||
const {
|
||||
className,
|
||||
level,
|
||||
small
|
||||
small,
|
||||
large
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
@@ -16,18 +17,24 @@ const SpinnerComponent = function (props) {
|
||||
className,
|
||||
styles.spinner,
|
||||
styles[level],
|
||||
{[styles.small]: small}
|
||||
{
|
||||
[styles.small]: small,
|
||||
[styles.large]: large
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
SpinnerComponent.propTypes = {
|
||||
className: PropTypes.string,
|
||||
large: PropTypes.bool,
|
||||
level: PropTypes.string,
|
||||
small: PropTypes.bool
|
||||
};
|
||||
SpinnerComponent.defaultProps = {
|
||||
className: '',
|
||||
level: 'info'
|
||||
large: false,
|
||||
level: 'info',
|
||||
small: false
|
||||
};
|
||||
export default SpinnerComponent;
|
||||
@@ -20,7 +20,6 @@
|
||||
cursor: pointer;
|
||||
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.sprite-selector-item.is-selected {
|
||||
@@ -95,16 +94,16 @@
|
||||
|
||||
.delete-button {
|
||||
position: absolute;
|
||||
top: 0.125rem;
|
||||
top: -.625rem;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
[dir="ltr"] .delete-button {
|
||||
right: 0.125rem;
|
||||
right: -.625rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .delete-button {
|
||||
left: 0.125rem;
|
||||
left: -.625rem;
|
||||
}
|
||||
|
||||
.number {
|
||||
|
||||
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import CloseButton from '../close-button/close-button.jsx';
|
||||
import DeleteButton from '../delete-button/delete-button.jsx';
|
||||
import styles from './sprite-selector-item.css';
|
||||
import {ContextMenuTrigger} from 'react-contextmenu';
|
||||
import {DangerousMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
|
||||
@@ -23,8 +23,9 @@ const SpriteSelectorItem = props => (
|
||||
onMouseDown: props.onMouseDown,
|
||||
onTouchStart: props.onMouseDown
|
||||
}}
|
||||
disable={props.dragging}
|
||||
disable={props.preventContextMenu}
|
||||
id={`${props.name}-${contextMenuId}`}
|
||||
ref={props.componentRef}
|
||||
>
|
||||
{typeof props.number === 'undefined' ? null : (
|
||||
<div className={styles.number}>{props.number}</div>
|
||||
@@ -47,9 +48,8 @@ const SpriteSelectorItem = props => (
|
||||
) : null}
|
||||
</div>
|
||||
{(props.selected && props.onDeleteButtonClick) ? (
|
||||
<CloseButton
|
||||
<DeleteButton
|
||||
className={styles.deleteButton}
|
||||
size={CloseButton.SIZE_SMALL}
|
||||
onClick={props.onDeleteButtonClick}
|
||||
/>
|
||||
) : null }
|
||||
@@ -89,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,
|
||||
@@ -101,6 +101,7 @@ SpriteSelectorItem.propTypes = {
|
||||
onMouseDown: PropTypes.func,
|
||||
onMouseEnter: PropTypes.func,
|
||||
onMouseLeave: PropTypes.func,
|
||||
preventContextMenu: PropTypes.bool,
|
||||
selected: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,8 @@ SpriteList.propTypes = {
|
||||
editingTarget: PropTypes.string,
|
||||
hoveredTarget: PropTypes.shape({
|
||||
hoveredSprite: PropTypes.string,
|
||||
receivedBlocks: PropTypes.bool
|
||||
receivedBlocks: PropTypes.bool,
|
||||
sprite: PropTypes.string
|
||||
}),
|
||||
items: PropTypes.arrayOf(PropTypes.shape({
|
||||
costume: PropTypes.shape({
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
padding-left: calc($space / 2);
|
||||
padding-right: calc($space / 2);
|
||||
padding-bottom: $space;
|
||||
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
|
||||
@@ -2,7 +2,6 @@ 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';
|
||||
@@ -33,7 +32,7 @@ class BackdropLibrary extends React.Component {
|
||||
bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
|
||||
skinId: null
|
||||
};
|
||||
this.props.vm.setEditingTarget(this.props.stageID);
|
||||
// Do not switch to stage, just add the backdrop
|
||||
this.props.vm.addBackdrop(item.md5, vmBackdrop);
|
||||
}
|
||||
render () {
|
||||
@@ -53,17 +52,7 @@ class BackdropLibrary extends React.Component {
|
||||
BackdropLibrary.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onRequestClose: PropTypes.func,
|
||||
stageID: PropTypes.string.isRequired,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
stageID: state.scratchGui.targets.stage.id
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default injectIntl(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BackdropLibrary));
|
||||
export default injectIntl(BackdropLibrary);
|
||||
@@ -17,6 +17,7 @@ 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';
|
||||
@@ -393,20 +394,50 @@ class Blocks extends React.Component {
|
||||
// workspace to be 'undone' here.
|
||||
this.workspace.clearUndo();
|
||||
}
|
||||
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));
|
||||
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 '---'
|
||||
});
|
||||
|
||||
// Update the toolbox with new blocks
|
||||
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
|
||||
const toolboxXML = this.getToolboxXML();
|
||||
if (toolboxXML) {
|
||||
this.props.updateToolboxState(toolboxXML);
|
||||
}
|
||||
}
|
||||
handleBlocksInfoUpdate (blocksInfo) {
|
||||
handleBlocksInfoUpdate (categoryInfo) {
|
||||
// @todo Later we should replace this to avoid all the warnings from redefining blocks.
|
||||
this.handleExtensionAdded(blocksInfo);
|
||||
this.handleExtensionAdded(categoryInfo);
|
||||
}
|
||||
handleCategorySelected (categoryId) {
|
||||
const extension = extensionData.find(ext => ext.extensionId === categoryId);
|
||||
|
||||
@@ -19,6 +19,7 @@ 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 () {
|
||||
@@ -52,7 +53,8 @@ const mapStateToProps = state => ({
|
||||
y: state.scratchGui.cards.y,
|
||||
isRtl: state.locales.isRtl,
|
||||
locale: state.locales.locale,
|
||||
dragging: state.scratchGui.cards.dragging
|
||||
dragging: state.scratchGui.cards.dragging,
|
||||
showVideos: notScratchDesktop()
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
@@ -35,13 +35,10 @@ import storage from '../lib/storage';
|
||||
import vmListenerHOC from '../lib/vm-listener-hoc.jsx';
|
||||
import vmManagerHOC from '../lib/vm-manager-hoc.jsx';
|
||||
import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx';
|
||||
import Loader from '../components/loader/loader.jsx';
|
||||
|
||||
// import GUIComponent from '../components/gui/gui.jsx';
|
||||
import GUIComponent from '../components/gui/gui.jsx';
|
||||
import {setIsScratchDesktop} from '../lib/isScratchDesktop.js';
|
||||
|
||||
import VideoProvider from '../lib/video/video-provider';
|
||||
|
||||
const messages = defineMessages({
|
||||
defaultProjectTitle: {
|
||||
id: 'gui.gui.defaultProjectTitle',
|
||||
@@ -55,22 +52,7 @@ class GUI extends React.Component {
|
||||
setIsScratchDesktop(this.props.isScratchDesktop);
|
||||
this.setReduxTitle(this.props.projectTitle);
|
||||
this.props.onStorageInit(storage);
|
||||
|
||||
// Use setTimeout. Do not use requestAnimationFrame or a resolved
|
||||
// Promise. We want this work delayed until after the data request is
|
||||
// made.
|
||||
setTimeout(this.ensureRenderer.bind(this));
|
||||
|
||||
// Once the GUI component has been rendered, always render GUI and do
|
||||
// not revert back to a Loader in this component.
|
||||
//
|
||||
// This makes GUI container not a pure component. We don't want to use
|
||||
// state for this. That would possibly cause a full second render of GUI
|
||||
// after the first one.
|
||||
const {fontsLoaded, fetchingProject, isLoading} = this.props;
|
||||
this.isAfterGUI = this.isAfterGUI || (
|
||||
fontsLoaded && !fetchingProject && !isLoading
|
||||
);
|
||||
this.props.onVmInit(this.props.vm);
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
|
||||
@@ -84,17 +66,6 @@ class GUI extends React.Component {
|
||||
// At this time the project view in www doesn't need to know when a project is unloaded
|
||||
this.props.onProjectLoaded();
|
||||
}
|
||||
|
||||
// Once the GUI component has been rendered, always render GUI and do
|
||||
// not revert back to a Loader in this component.
|
||||
//
|
||||
// This makes GUI container not a pure component. We don't want to use
|
||||
// state for this. That would possibly cause a full second render of GUI
|
||||
// after the first one.
|
||||
const {fontsLoaded, fetchingProject, isLoading} = this.props;
|
||||
this.isAfterGUI = this.isAfterGUI || (
|
||||
fontsLoaded && !fetchingProject && !isLoading
|
||||
);
|
||||
}
|
||||
setReduxTitle (newTitle) {
|
||||
if (newTitle === null || typeof newTitle === 'undefined') {
|
||||
@@ -105,36 +76,6 @@ class GUI extends React.Component {
|
||||
this.props.onUpdateReduxProjectTitle(newTitle);
|
||||
}
|
||||
}
|
||||
ensureRenderer () {
|
||||
if (this.props.vm.renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait to load svg-renderer and render after the data request. This
|
||||
// way the data request is made earlier.
|
||||
const Renderer = require('scratch-render');
|
||||
const {
|
||||
SVGRenderer: V2SVGAdapter,
|
||||
BitmapAdapter: V2BitmapAdapter
|
||||
} = require('scratch-svg-renderer');
|
||||
|
||||
const vm = this.props.vm;
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.renderer = new Renderer(this.canvas);
|
||||
vm.attachRenderer(this.renderer);
|
||||
|
||||
vm.attachV2SVGAdapter(new V2SVGAdapter());
|
||||
vm.attachV2BitmapAdapter(new V2BitmapAdapter());
|
||||
|
||||
// Only attach a video provider once because it is stateful
|
||||
vm.setVideoProvider(new VideoProvider());
|
||||
|
||||
// Calling draw a single time before any project is loaded just
|
||||
// makes the canvas white instead of solid black–needed because it
|
||||
// is not possible to use CSS to style the canvas to have a
|
||||
// different default color
|
||||
vm.renderer.draw();
|
||||
}
|
||||
render () {
|
||||
if (this.props.isError) {
|
||||
throw new Error(
|
||||
@@ -145,7 +86,6 @@ class GUI extends React.Component {
|
||||
assetHost,
|
||||
cloudHost,
|
||||
error,
|
||||
fontsLoaded,
|
||||
isError,
|
||||
isScratchDesktop,
|
||||
isShowingProject,
|
||||
@@ -153,6 +93,7 @@ class GUI extends React.Component {
|
||||
onStorageInit,
|
||||
onUpdateProjectId,
|
||||
onUpdateReduxProjectTitle,
|
||||
onVmInit,
|
||||
projectHost,
|
||||
projectId,
|
||||
projectTitle,
|
||||
@@ -163,16 +104,6 @@ class GUI extends React.Component {
|
||||
loadingStateVisible,
|
||||
...componentProps
|
||||
} = this.props;
|
||||
|
||||
if (!this.isAfterGUI && (
|
||||
!fontsLoaded || fetchingProject || isLoading
|
||||
)) {
|
||||
// Make sure a renderer exists.
|
||||
if (fontsLoaded && !fetchingProject) this.ensureRenderer();
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
const GUIComponent = require('../components/gui/gui.jsx').default;
|
||||
return (
|
||||
<GUIComponent
|
||||
loading={fetchingProject || isLoading || loadingStateVisible}
|
||||
@@ -190,7 +121,6 @@ GUI.propTypes = {
|
||||
cloudHost: PropTypes.string,
|
||||
error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
fetchingProject: PropTypes.bool,
|
||||
fontsLoaded: PropTypes.bool,
|
||||
intl: intlShape,
|
||||
isError: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
@@ -203,6 +133,7 @@ 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,
|
||||
@@ -214,7 +145,8 @@ GUI.defaultProps = {
|
||||
isScratchDesktop: false,
|
||||
onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(),
|
||||
onProjectLoaded: () => {},
|
||||
onUpdateProjectId: () => {}
|
||||
onUpdateProjectId: () => {},
|
||||
onVmInit: (/* vm */) => {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
@@ -229,7 +161,6 @@ const mapStateToProps = state => {
|
||||
costumeLibraryVisible: state.scratchGui.modals.costumeLibrary,
|
||||
costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
|
||||
error: state.scratchGui.projectState.error,
|
||||
fontsLoaded: state.scratchGui.fontsLoaded,
|
||||
isError: getIsError(loadingState),
|
||||
isFullScreen: state.scratchGui.mode.isFullScreen,
|
||||
isPlayerOnly: state.scratchGui.mode.isPlayerOnly,
|
||||
|
||||
@@ -26,13 +26,14 @@ import {
|
||||
/**
|
||||
* SBFileUploader component passes a file input, load handler and props to its child.
|
||||
* It expects this child to be a function with the signature
|
||||
* function (renderFileInput, loadProject) {}
|
||||
* function (renderFileInput, handleLoadProject) {}
|
||||
* The component can then be used to attach project loading functionality
|
||||
* to any other component:
|
||||
*
|
||||
* <SBFileUploader>{(renderFileInput, loadProject) => (
|
||||
* <SBFileUploader>{(className, renderFileInput, handleLoadProject) => (
|
||||
* <MyCoolComponent
|
||||
* onClick={loadProject}
|
||||
* className={className}
|
||||
* onClick={handleLoadProject}
|
||||
* >
|
||||
* {renderFileInput()}
|
||||
* </MyCoolComponent>
|
||||
|
||||
@@ -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,9 +26,22 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handleMouseDown',
|
||||
'handleMouseMove',
|
||||
'handleMouseUp'
|
||||
'handleDragEnd',
|
||||
'handleDrag',
|
||||
'handleTouchEnd'
|
||||
]);
|
||||
|
||||
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;
|
||||
@@ -36,12 +49,7 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
|
||||
return getCostumeUrl(this.props.asset);
|
||||
}
|
||||
handleMouseUp () {
|
||||
this.initialOffset = null;
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove);
|
||||
window.removeEventListener('touchend', this.handleMouseUp);
|
||||
window.removeEventListener('touchmove', this.handleMouseMove);
|
||||
handleDragEnd () {
|
||||
if (this.props.dragging) {
|
||||
this.props.onDrag({
|
||||
img: null,
|
||||
@@ -55,29 +63,26 @@ class SpriteSelectorItem extends React.PureComponent {
|
||||
this.noClick = false;
|
||||
});
|
||||
}
|
||||
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;
|
||||
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();
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
handleMouseDown (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);
|
||||
this.dragRecognizer.start(e);
|
||||
}
|
||||
handleClick (e) {
|
||||
e.preventDefault();
|
||||
@@ -103,6 +108,10 @@ 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 */
|
||||
@@ -122,7 +131,9 @@ 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,6 +15,7 @@ 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';
|
||||
|
||||
@@ -46,11 +47,26 @@ class StageSelector extends React.Component {
|
||||
'handleBackdropUpload',
|
||||
'handleMouseEnter',
|
||||
'handleMouseLeave',
|
||||
'handleTouchEnd',
|
||||
'handleDrop',
|
||||
'setFileInput'
|
||||
'setFileInput',
|
||||
'setRef'
|
||||
]);
|
||||
}
|
||||
addBackdropFromLibraryItem (item) {
|
||||
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) {
|
||||
const vmBackdrop = {
|
||||
name: item.name,
|
||||
md5: item.md5,
|
||||
@@ -59,25 +75,30 @@ class StageSelector extends React.Component {
|
||||
bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
|
||||
skinId: null
|
||||
};
|
||||
this.handleNewBackdrop(vmBackdrop);
|
||||
this.handleNewBackdrop(vmBackdrop, shouldActivateTab);
|
||||
}
|
||||
handleClick () {
|
||||
this.props.onSelect(this.props.id);
|
||||
}
|
||||
handleNewBackdrop (backdrops_) {
|
||||
handleNewBackdrop (backdrops_, shouldActivateTab = true) {
|
||||
const backdrops = Array.isArray(backdrops_) ? backdrops_ : [backdrops_];
|
||||
return Promise.all(backdrops.map(backdrop =>
|
||||
this.props.vm.addBackdrop(backdrop.md5, backdrop)
|
||||
)).then(() =>
|
||||
this.props.onActivateTab(COSTUMES_TAB_INDEX)
|
||||
);
|
||||
)).then(() => {
|
||||
if (shouldActivateTab) {
|
||||
return this.props.onActivateTab(COSTUMES_TAB_INDEX);
|
||||
}
|
||||
});
|
||||
}
|
||||
handleSurpriseBackdrop () {
|
||||
handleSurpriseBackdrop (e) {
|
||||
e.stopPropagation(); // Prevent click from falling through to selecting stage.
|
||||
// @todo should this not add a backdrop you already have?
|
||||
const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)];
|
||||
this.addBackdropFromLibraryItem(item);
|
||||
this.addBackdropFromLibraryItem(item, false);
|
||||
}
|
||||
handleEmptyBackdrop () {
|
||||
handleEmptyBackdrop (e) {
|
||||
e.stopPropagation(); // Prevent click from falling through to stage selector, select it manually below
|
||||
this.props.vm.setEditingTarget(this.props.id);
|
||||
this.handleNewBackdrop(emptyCostume(this.props.intl.formatMessage(sharedMessages.backdrop, {index: 1})));
|
||||
}
|
||||
handleBackdropUpload (e) {
|
||||
@@ -85,6 +106,7 @@ 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 : ''}`;
|
||||
});
|
||||
@@ -96,7 +118,8 @@ class StageSelector extends React.Component {
|
||||
}, this.props.onCloseImporting);
|
||||
}, this.props.onCloseImporting);
|
||||
}
|
||||
handleFileUploadClick () {
|
||||
handleFileUploadClick (e) {
|
||||
e.stopPropagation(); // Prevent click from selecting the stage, that is handled manually in backdrop upload
|
||||
this.fileInput.click();
|
||||
}
|
||||
handleMouseEnter () {
|
||||
@@ -130,12 +153,16 @@ 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}
|
||||
|
||||
@@ -62,10 +62,16 @@ class TipsLibrary extends React.PureComponent {
|
||||
}
|
||||
render () {
|
||||
const decksLibraryThumbnailData = Object.keys(decksLibraryContent)
|
||||
.filter(id =>
|
||||
.filter(id => {
|
||||
if (notScratchDesktop()) return true; // Do not filter anything in online editor
|
||||
const deck = decksLibraryContent[id];
|
||||
// Scratch Desktop doesn't want tutorials with `requiredProjectId`
|
||||
notScratchDesktop() || !decksLibraryContent[id].hasOwnProperty('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;
|
||||
})
|
||||
.map(id => ({
|
||||
rawURL: decksLibraryContent[id].img,
|
||||
id: id,
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// 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;
|
||||
@@ -0,0 +1,125 @@
|
||||
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": "419ff5a76adbb4b076c072f68267fdeb.svg",
|
||||
"md5": "d2c433e02481b10584e34ccdcf5a046a.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -87,14 +87,33 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
86,
|
||||
81,
|
||||
80,
|
||||
65,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-b",
|
||||
"md5": "a6a766912b4e43064ee3f389c72925b4.svg",
|
||||
"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",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -107,32 +126,13 @@
|
||||
],
|
||||
"info": [
|
||||
46,
|
||||
106,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-c",
|
||||
"md5": "fac1025857d2c28391cb31f39a80df54.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
"basketball",
|
||||
"people",
|
||||
"wheelchair",
|
||||
"handicap",
|
||||
"handicapable",
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
52,
|
||||
65,
|
||||
49,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Andie-d",
|
||||
"md5": "7af50835e05d43e5e07cd2d641e7401e.svg",
|
||||
"md5": "7c9db638cd671898ff07771112faff0f.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -144,8 +144,8 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
87,
|
||||
72,
|
||||
71,
|
||||
57,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -1069,7 +1069,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Ben-a",
|
||||
"md5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
|
||||
"md5": "322e13bd17d02acfdc048a5ef2167be8.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1079,14 +1079,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
53,
|
||||
54,
|
||||
69,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ben-b",
|
||||
"md5": "90db137cd0743204d02fdc9be1c8eb94.svg",
|
||||
"md5": "59ba1e80ba75b1d06c37db4b9b4fde27.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1096,14 +1096,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
53,
|
||||
67,
|
||||
54,
|
||||
69,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ben-c",
|
||||
"md5": "5f05d41ede773b3bb379286e86a5b869.svg",
|
||||
"md5": "940ec2fa85d666dde95a51bcbccd266e.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1113,14 +1113,14 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
73,
|
||||
71,
|
||||
72,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ben-d",
|
||||
"md5": "6f6e246567c0639c758ed4f6930be76f.svg",
|
||||
"md5": "3043a50a91b9d484a8cd32eb82f2d88f.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1130,8 +1130,8 @@
|
||||
"alex eben meyer"
|
||||
],
|
||||
"info": [
|
||||
42,
|
||||
72,
|
||||
44,
|
||||
71,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -3628,16 +3628,17 @@
|
||||
},
|
||||
{
|
||||
"name": "Earth",
|
||||
"md5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
|
||||
"md5": "64220361b80d2812e9f27fc88ef39027.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"space",
|
||||
"planet",
|
||||
"earth"
|
||||
"earth",
|
||||
"globe"
|
||||
],
|
||||
"info": [
|
||||
41,
|
||||
40,
|
||||
55,
|
||||
55,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -6178,7 +6179,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-a",
|
||||
"md5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
|
||||
"md5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6189,13 +6190,13 @@
|
||||
],
|
||||
"info": [
|
||||
51,
|
||||
63,
|
||||
62,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-b",
|
||||
"md5": "2e2c43bbd7b18524fb132f1426ca3134.svg",
|
||||
"md5": "075f382883e60dba8094b3dc30918f60.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6212,7 +6213,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-c",
|
||||
"md5": "5c0d24c53805e16358a2f6f6cc4a456e.svg",
|
||||
"md5": "fbe81c40d4a880c8799c458359758629.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6223,13 +6224,13 @@
|
||||
],
|
||||
"info": [
|
||||
68,
|
||||
61,
|
||||
62,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Jordyn-d",
|
||||
"md5": "ebe2b20b99c1e1b373fd2b02be48aab9.svg",
|
||||
"md5": "68a784d4d0340475a6e6feff3b28b470.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -6240,7 +6241,7 @@
|
||||
],
|
||||
"info": [
|
||||
40,
|
||||
61,
|
||||
62,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -9660,7 +9661,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Sun",
|
||||
"md5": "55c931c65456822c0c56a2b30e3e550d.svg",
|
||||
"md5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
|
||||
"type": "costume",
|
||||
"tags": [
|
||||
"space",
|
||||
@@ -9671,8 +9672,8 @@
|
||||
"nuclear"
|
||||
],
|
||||
"info": [
|
||||
72,
|
||||
72,
|
||||
54,
|
||||
54,
|
||||
1
|
||||
]
|
||||
},
|
||||
@@ -10582,6 +10583,24 @@
|
||||
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",
|
||||
@@ -12682,4 +12701,4 @@
|
||||
1
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -90,6 +90,86 @@ 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,
|
||||
@@ -181,8 +261,89 @@ 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};
|
||||
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 352 KiB |
|
After Width: | Height: | Size: 339 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 120 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 197 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 473 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -74,6 +74,12 @@ 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": "419ff5a76adbb4b076c072f68267fdeb.svg",
|
||||
"md5": "d2c433e02481b10584e34ccdcf5a046a.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": 0,
|
||||
"baseLayerMD5": "419ff5a76adbb4b076c072f68267fdeb.svg",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "d2c433e02481b10584e34ccdcf5a046a.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 86,
|
||||
"rotationCenterY": 81
|
||||
"rotationCenterX": 80,
|
||||
"rotationCenterY": 65
|
||||
},
|
||||
{
|
||||
"costumeName": "andie-b",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "a6a766912b4e43064ee3f389c72925b4.svg",
|
||||
"baseLayerMD5": "045f92ad80d72521098ede6d7c80549a.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 46,
|
||||
"rotationCenterY": 106
|
||||
"rotationCenterX": 40,
|
||||
"rotationCenterY": 91
|
||||
},
|
||||
{
|
||||
"costumeName": "andie-c",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "fac1025857d2c28391cb31f39a80df54.svg",
|
||||
"baseLayerMD5": "c9bad70ad371b8d6d97daed7ab9def98.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 52,
|
||||
"rotationCenterY": 65
|
||||
"rotationCenterX": 46,
|
||||
"rotationCenterY": 49
|
||||
},
|
||||
{
|
||||
"costumeName": "andie-d",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "7af50835e05d43e5e07cd2d641e7401e.svg",
|
||||
"baseLayerMD5": "7c9db638cd671898ff07771112faff0f.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 87,
|
||||
"rotationCenterY": 72
|
||||
"rotationCenterX": 71,
|
||||
"rotationCenterY": 57
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -129,
|
||||
"scratchY": -73,
|
||||
"scratchX": -150,
|
||||
"scratchY": 90,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -1442,7 +1442,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Ben",
|
||||
"md5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
|
||||
"md5": "322e13bd17d02acfdc048a5ef2167be8.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -1460,18 +1460,18 @@
|
||||
"objName": "Ben",
|
||||
"sounds": [
|
||||
{
|
||||
"soundName": "pop",
|
||||
"soundName": "Goal Cheer",
|
||||
"soundID": -1,
|
||||
"md5": "83a9787d4cb6f3b7632b4ddfebf74367.wav",
|
||||
"sampleCount": 258,
|
||||
"rate": 11025,
|
||||
"format": ""
|
||||
"md5": "a434069c58e79d42f5d21abb1c318919.wav",
|
||||
"sampleCount": 84096,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
},
|
||||
{
|
||||
"soundName": "basketball bounce",
|
||||
"soundName": "Referee Whistle",
|
||||
"soundID": -1,
|
||||
"md5": "1727f65b5f22d151685b8e5917456a60.wav",
|
||||
"sampleCount": 8099,
|
||||
"md5": "8468b9b3f11a665ee4d215afd8463b97.wav",
|
||||
"sampleCount": 14034,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}
|
||||
@@ -1480,39 +1480,39 @@
|
||||
{
|
||||
"costumeName": "ben-a",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
|
||||
"baseLayerMD5": "322e13bd17d02acfdc048a5ef2167be8.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 53,
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterY": 69
|
||||
},
|
||||
{
|
||||
"costumeName": "ben-b",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "90db137cd0743204d02fdc9be1c8eb94.svg",
|
||||
"baseLayerMD5": "59ba1e80ba75b1d06c37db4b9b4fde27.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 53,
|
||||
"rotationCenterY": 67
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterY": 69
|
||||
},
|
||||
{
|
||||
"costumeName": "ben-c",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "5f05d41ede773b3bb379286e86a5b869.svg",
|
||||
"baseLayerMD5": "940ec2fa85d666dde95a51bcbccd266e.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 71,
|
||||
"rotationCenterY": 72
|
||||
"rotationCenterX": 73,
|
||||
"rotationCenterY": 71
|
||||
},
|
||||
{
|
||||
"costumeName": "ben-d",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "6f6e246567c0639c758ed4f6930be76f.svg",
|
||||
"baseLayerMD5": "3043a50a91b9d484a8cd32eb82f2d88f.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 42,
|
||||
"rotationCenterY": 72
|
||||
"rotationCenterX": 44,
|
||||
"rotationCenterY": 71
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -63,
|
||||
"scratchY": -94,
|
||||
"scratchX": -15,
|
||||
"scratchY": 93,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -5434,12 +5434,13 @@
|
||||
},
|
||||
{
|
||||
"name": "Earth",
|
||||
"md5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
|
||||
"md5": "64220361b80d2812e9f27fc88ef39027.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"space",
|
||||
"planet",
|
||||
"earth"
|
||||
"earth",
|
||||
"globe"
|
||||
],
|
||||
"info": [
|
||||
0,
|
||||
@@ -5460,17 +5461,17 @@
|
||||
],
|
||||
"costumes": [
|
||||
{
|
||||
"costumeName": "Earth",
|
||||
"costumeName": "earth",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
|
||||
"baseLayerMD5": "64220361b80d2812e9f27fc88ef39027.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 41,
|
||||
"rotationCenterY": 40
|
||||
"rotationCenterX": 55,
|
||||
"rotationCenterY": 55
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 57,
|
||||
"scratchY": -39,
|
||||
"scratchX": 142,
|
||||
"scratchY": -53,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -8842,7 +8843,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Jordyn",
|
||||
"md5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
|
||||
"md5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -8854,16 +8855,24 @@
|
||||
"info": [
|
||||
0,
|
||||
4,
|
||||
1
|
||||
2
|
||||
],
|
||||
"json": {
|
||||
"objName": "Jordyn",
|
||||
"sounds": [
|
||||
{
|
||||
"soundName": "basketball bounce",
|
||||
"soundName": "Goal Cheer",
|
||||
"soundID": -1,
|
||||
"md5": "1727f65b5f22d151685b8e5917456a60.wav",
|
||||
"sampleCount": 8099,
|
||||
"md5": "199b30c8b4fe0642e849924bd1e1b463.wav",
|
||||
"sampleCount": 84411,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
},
|
||||
{
|
||||
"soundName": "Referee Whistle",
|
||||
"soundID": -1,
|
||||
"md5": "7d91d95d841dc6cf1282914306a4674a.wav",
|
||||
"sampleCount": 14238,
|
||||
"rate": 22050,
|
||||
"format": "adpcm"
|
||||
}
|
||||
@@ -8872,15 +8881,15 @@
|
||||
{
|
||||
"costumeName": "jordyn-a",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
|
||||
"baseLayerMD5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 51,
|
||||
"rotationCenterY": 63
|
||||
"rotationCenterY": 62
|
||||
},
|
||||
{
|
||||
"costumeName": "jordyn-b",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "2e2c43bbd7b18524fb132f1426ca3134.svg",
|
||||
"baseLayerMD5": "075f382883e60dba8094b3dc30918f60.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 51,
|
||||
"rotationCenterY": 63
|
||||
@@ -8888,23 +8897,23 @@
|
||||
{
|
||||
"costumeName": "jordyn-c",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "5c0d24c53805e16358a2f6f6cc4a456e.svg",
|
||||
"baseLayerMD5": "fbe81c40d4a880c8799c458359758629.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 68,
|
||||
"rotationCenterY": 61
|
||||
"rotationCenterY": 62
|
||||
},
|
||||
{
|
||||
"costumeName": "jordyn-d",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "ebe2b20b99c1e1b373fd2b02be48aab9.svg",
|
||||
"baseLayerMD5": "68a784d4d0340475a6e6feff3b28b470.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 40,
|
||||
"rotationCenterY": 61
|
||||
"rotationCenterY": 62
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": -164,
|
||||
"scratchY": -41,
|
||||
"scratchX": 61,
|
||||
"scratchY": 93,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -14372,7 +14381,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Sun",
|
||||
"md5": "55c931c65456822c0c56a2b30e3e550d.svg",
|
||||
"md5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
|
||||
"type": "sprite",
|
||||
"tags": [
|
||||
"space",
|
||||
@@ -14403,15 +14412,15 @@
|
||||
{
|
||||
"costumeName": "sun",
|
||||
"baseLayerID": -1,
|
||||
"baseLayerMD5": "55c931c65456822c0c56a2b30e3e550d.svg",
|
||||
"baseLayerMD5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
|
||||
"bitmapResolution": 1,
|
||||
"rotationCenterX": 72,
|
||||
"rotationCenterY": 72
|
||||
"rotationCenterX": 54,
|
||||
"rotationCenterY": 54
|
||||
}
|
||||
],
|
||||
"currentCostumeIndex": 0,
|
||||
"scratchX": 16,
|
||||
"scratchY": -28,
|
||||
"scratchX": -3,
|
||||
"scratchY": -42,
|
||||
"scale": 1,
|
||||
"direction": 90,
|
||||
"rotationStyle": "normal",
|
||||
@@ -15778,6 +15787,56 @@
|
||||
"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",
|
||||
@@ -20412,4 +20471,4 @@
|
||||
"spriteInfo": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -716,13 +716,16 @@ const xmlClose = '</xml>';
|
||||
/**
|
||||
* @param {!boolean} isStage - Whether the toolbox is for a stage-type target.
|
||||
* @param {?string} targetId - The current editing target
|
||||
* @param {?string} categoriesXML - null for default toolbox, or an XML string with <category> elements.
|
||||
* @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} 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];
|
||||
|
||||
@@ -730,21 +733,41 @@ 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,
|
||||
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)
|
||||
motionXML, gap,
|
||||
looksXML, gap,
|
||||
soundXML, gap,
|
||||
eventsXML, gap,
|
||||
controlXML, gap,
|
||||
sensingXML, gap,
|
||||
operatorsXML, gap,
|
||||
variablesXML, gap,
|
||||
myBlocksXML
|
||||
];
|
||||
|
||||
if (categoriesXML) {
|
||||
everything.push(gap, categoriesXML);
|
||||
for (const extensionCategory of categoriesXML) {
|
||||
everything.push(gap, extensionCategory.xml);
|
||||
}
|
||||
|
||||
everything.push(xmlClose);
|
||||
|
||||
@@ -234,9 +234,14 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
asset.dataFormat,
|
||||
asset.data,
|
||||
asset.assetId
|
||||
).then(
|
||||
() => (asset.clean = true)
|
||||
)
|
||||
).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(() => this.props.onUpdateProjectData(projectId, savedVMState, requestParams))
|
||||
|
||||
@@ -45,6 +45,7 @@ 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
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('Working with backdrops', () => {
|
||||
await driver.quit();
|
||||
});
|
||||
|
||||
test('Adding a backdrop from the library', async () => {
|
||||
test('Adding a backdrop from the library should not switch to stage', async () => {
|
||||
await loadUri(uri);
|
||||
|
||||
// Start on the sounds tab of sprite1 to test switching behavior
|
||||
@@ -37,12 +37,11 @@ describe('Working with backdrops', () => {
|
||||
await el.sendKeys('blue');
|
||||
await clickText('Blue Sky'); // Adds the backdrop
|
||||
|
||||
// Make sure the stage is selected and the sound tab remains selected.
|
||||
// This is different from Scratch2 which selected backdrop tab automatically
|
||||
// See issue #3500
|
||||
await clickText('pop', scope.soundsTab);
|
||||
// Make sure the sprite is still selected, and that the tab has not changed
|
||||
await clickText('Meow', scope.soundsTab);
|
||||
|
||||
// Make sure the backdrop was actually added by going to the backdrops tab
|
||||
await clickXpath('//span[text()="Stage"]');
|
||||
await clickText('Backdrops');
|
||||
await clickText('Blue Sky', scope.costumesTab);
|
||||
|
||||
@@ -50,7 +49,48 @@ describe('Working with backdrops', () => {
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('Adding multiple backdrops at the same time', async () => {
|
||||
test('Adding backdrop via paint should switch to stage', async () => {
|
||||
await loadUri(uri);
|
||||
|
||||
const buttonXpath = '//button[@aria-label="Choose a Backdrop"]';
|
||||
const paintXpath = `${buttonXpath}/following-sibling::div//button[@aria-label="Paint"]`;
|
||||
|
||||
const el = await findByXpath(buttonXpath);
|
||||
await driver.actions().mouseMove(el)
|
||||
.perform();
|
||||
await driver.sleep(500); // Wait for thermometer menu to come up
|
||||
await clickXpath(paintXpath);
|
||||
|
||||
// Stage should become selected and costume tab activated
|
||||
await findByText('backdrop2', scope.costumesTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('Adding backdrop via surprise should not switch to stage', async () => {
|
||||
await loadUri(uri);
|
||||
|
||||
// Start on the sounds tab of sprite1 to test switching behavior
|
||||
await clickText('Sounds');
|
||||
|
||||
const buttonXpath = '//button[@aria-label="Choose a Backdrop"]';
|
||||
const surpriseXpath = `${buttonXpath}/following-sibling::div//button[@aria-label="Surprise"]`;
|
||||
|
||||
const el = await findByXpath(buttonXpath);
|
||||
await driver.actions().mouseMove(el)
|
||||
.perform();
|
||||
await driver.sleep(500); // Wait for thermometer menu to come up
|
||||
await clickXpath(surpriseXpath);
|
||||
|
||||
// Make sure the sprite is still selected, and that the tab has not changed
|
||||
await clickText('Meow', scope.soundsTab);
|
||||
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
test('Adding multiple backdrops from file should switch to stage', async () => {
|
||||
const files = [
|
||||
path.resolve(__dirname, '../fixtures/gh-3582-png.png'),
|
||||
path.resolve(__dirname, '../fixtures/100-100.svg')
|
||||
@@ -67,7 +107,7 @@ describe('Working with backdrops', () => {
|
||||
const input = await findByXpath(fileXpath);
|
||||
await input.sendKeys(files.join('\n'));
|
||||
|
||||
await clickXpath('//span[text()="Stage"]');
|
||||
// Should have been switched to stage/costume tab already
|
||||
await findByText('gh-3582-png', scope.costumesTab);
|
||||
await findByText('100-100', scope.costumesTab);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('player example', () => {
|
||||
await driver.quit();
|
||||
});
|
||||
|
||||
test('Load a project by ID', async () => {
|
||||
test.skip('Player: load a project by ID', async () => {
|
||||
const projectId = '96708228';
|
||||
await loadUri(`${uri}#${projectId}`);
|
||||
await clickXpath('//img[@title="Go"]');
|
||||
@@ -56,7 +56,7 @@ describe('blocks example', () => {
|
||||
await driver.quit();
|
||||
});
|
||||
|
||||
test('Load a project by ID', async () => {
|
||||
test.skip('Blocks: load a project by ID', async () => {
|
||||
const projectId = '96708228';
|
||||
await loadUri(`${uri}#${projectId}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
@@ -75,7 +75,8 @@ describe('blocks example', () => {
|
||||
await expect(projectRequests).toEqual(['https://projects.scratch.mit.edu/96708228']);
|
||||
});
|
||||
|
||||
test('Change categories', async () => {
|
||||
// skipping per https://github.com/LLK/scratch-gui/issues/4902 until we have better approach
|
||||
test.skip('Change categories', async () => {
|
||||
await loadUri(`${uri}`);
|
||||
await clickText('Looks');
|
||||
await clickText('Sound');
|
||||
|
||||