Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a83d9369dc | ||
|
|
012bb612ad | ||
|
|
b9e7bde676 | ||
|
|
4a26810956 | ||
|
|
92fd318aa8 | ||
|
|
bc2555ddc4 | ||
|
|
8ee9794304 | ||
|
|
30388ffa4f | ||
|
|
41e2909782 | ||
|
|
4048ed8c84 | ||
|
|
7a21b17d0c | ||
|
|
72bc6f3005 | ||
|
|
a8a7e0c92f | ||
|
|
333b91ccfd | ||
|
|
a3266fe641 | ||
|
|
10449a21b5 | ||
|
|
29f5d2a872 | ||
|
|
f8b3e4961c | ||
|
|
cca701e954 | ||
|
|
89c0807cba | ||
|
|
544a4c745d | ||
|
|
f6e9a7dd23 | ||
|
|
69132b6f43 | ||
|
|
aa88e2c6a6 | ||
|
|
b89734b0e9 | ||
|
|
d5eb058d00 | ||
|
|
fe308b6b58 | ||
|
|
ab14942c80 | ||
|
|
908cb2e5c6 | ||
|
|
6935aa0820 | ||
|
|
4ec8ac16a8 | ||
|
|
bfe2877d0b | ||
|
|
9d50e9a165 | ||
|
|
eb496cfe75 | ||
|
|
dfd01da913 | ||
|
|
c8bdb6ce09 | ||
|
|
3169c03e66 | ||
|
|
26323cccf2 | ||
|
|
2bb9a61539 | ||
|
|
4efd3fcbad | ||
|
|
170705440a | ||
|
|
a52fdaa032 | ||
|
|
6d972da227 | ||
|
|
96bd677ef9 | ||
|
|
b77bc7a8b4 | ||
|
|
7f06f55f83 | ||
|
|
04381439c2 | ||
|
|
7b658c60c7 | ||
|
|
0c68103eb6 | ||
|
|
65c787d0da | ||
|
|
0b40daf936 | ||
|
|
75607ebbe3 | ||
|
|
58b178f679 | ||
|
|
ffeebb03bd | ||
|
|
7c1f0d2739 | ||
|
|
ac0c6a4305 | ||
|
|
ff776dc90c | ||
|
|
516baedad9 | ||
|
|
1c8ba1d1eb | ||
|
|
16024769c8 | ||
|
|
ed9316266e | ||
|
|
103f55bd6e | ||
|
|
009d9db74f | ||
|
|
416bc85455 | ||
|
|
88a5ab8052 | ||
|
|
31454f7ca0 | ||
|
|
bfd0712c83 | ||
|
|
3c9d482a02 | ||
|
|
5c3694ec19 | ||
|
|
997abfeb27 | ||
|
|
df77245f0d | ||
|
|
5e2ea2ee64 | ||
|
|
655af4ed31 | ||
|
|
9d385faa84 | ||
|
|
455808fe26 | ||
|
|
c2dbdc6279 | ||
|
|
8faf8af5da | ||
|
|
cfaf1ee049 | ||
|
|
29d9bb0d41 | ||
|
|
9d185bd6c8 | ||
|
|
7c26245c53 | ||
|
|
4dc3f73e74 | ||
|
|
1a66060aa2 | ||
|
|
cf9e165ccf | ||
|
|
e877486daa | ||
|
|
1eb095e235 | ||
|
|
905e938c57 | ||
|
|
fe8b914f8d | ||
|
|
a48cc065c0 | ||
|
|
6e50909e0f | ||
|
|
6480b262de | ||
|
|
a0745e4f40 | ||
|
|
8eb9e37d67 | ||
|
|
69506d9c1d | ||
|
|
d37f1fcae1 | ||
|
|
dfb7b55145 | ||
|
|
6e335085d1 | ||
|
|
cde9660b12 | ||
|
|
abe782cdba | ||
|
|
96f3377733 | ||
|
|
e43011233f | ||
|
|
7c4d92ac08 | ||
|
|
3033717d67 | ||
|
|
5a2bec60d9 | ||
|
|
7e2f9b8361 | ||
|
|
5c1d3e38bd | ||
|
|
f8243eae9e | ||
|
|
14ead16a8a | ||
|
|
c064eaed4e | ||
|
|
5758626777 | ||
|
|
934396d87a | ||
|
|
130c5de4b0 | ||
|
|
09bcfba800 | ||
|
|
cb928b8490 | ||
|
|
4aba90c8fb | ||
|
|
308568a332 | ||
|
|
c87560aabc | ||
|
|
13f72153a3 | ||
|
|
b435ac3101 | ||
|
|
0d7a05d37a | ||
|
|
0fbbf55b51 | ||
|
|
a9c624b746 | ||
|
|
0caee112e0 | ||
|
|
17e28b2a24 | ||
|
|
7fcbae60bc | ||
|
|
9aabea4065 | ||
|
|
8a1b104f28 | ||
|
|
8f35eacf9f | ||
|
|
327983fc8c | ||
|
|
57ede34c29 | ||
|
|
d76eacd7e5 | ||
|
|
32f9ae614c | ||
|
|
490c56e46e | ||
|
|
c5290a2640 | ||
|
|
00f07c3801 | ||
|
|
a441cbbe60 | ||
|
|
8656148a91 | ||
|
|
b32e0ca6d7 | ||
|
|
5b4daec4ab | ||
|
|
aed66d49cf | ||
|
|
5d5331b0ad | ||
|
|
83b48a683a | ||
|
|
93477a19d6 | ||
|
|
a87b08d791 | ||
|
|
494269bcf5 | ||
|
|
c684774f06 | ||
|
|
9296438911 | ||
|
|
e9a9767f67 | ||
|
|
174bd295e1 | ||
|
|
57a9e0b8ca | ||
|
|
e89a8eab11 | ||
|
|
98274c8b8e | ||
|
|
c8617289bc | ||
|
|
e82e370e83 | ||
|
|
e2adf9bf14 | ||
|
|
881e17dc67 | ||
|
|
021063470d | ||
|
|
2b5196106a | ||
|
|
628bc2cd26 | ||
|
|
6d5a6d407b | ||
|
|
ce6df3633a | ||
|
|
fecc6f4f6f | ||
|
|
839deda612 | ||
|
|
dac1ece8fd | ||
|
|
fc8c242173 | ||
|
|
4fab686aea | ||
|
|
bfae437886 | ||
|
|
c9a989bfab | ||
|
|
9bd2ca246b | ||
|
|
f2b9732748 | ||
|
|
fda637edba | ||
|
|
a8ea46a357 | ||
|
|
37826cb901 | ||
|
|
6fec13c941 | ||
|
|
f86fd4995f | ||
|
|
596bdad97a | ||
|
|
2522ad378a | ||
|
|
09d9b51c20 | ||
|
|
3f73c6df7b | ||
|
|
fb85d6ab51 | ||
|
|
97b056ea87 | ||
|
|
05027a5aee | ||
|
|
f5d1dd7e03 | ||
|
|
e0f8497b48 | ||
|
|
0abb94bc81 | ||
|
|
66b3f43581 | ||
|
|
e0db28d82b | ||
|
|
cf86e4c432 | ||
|
|
28cd140d9f | ||
|
|
00a4db1213 | ||
|
|
5f6b89ec2a | ||
|
|
a4cb3b1eb5 | ||
|
|
8e3bd8c631 | ||
|
|
a74bb9afc7 | ||
|
|
a082d8329e | ||
|
|
9f9a780093 | ||
|
|
8c36ad3cc2 | ||
|
|
1b2555c7de | ||
|
|
996ed08908 | ||
|
|
7709580710 | ||
|
|
992f012ff3 | ||
|
|
c53b88dbad | ||
|
|
1fd9a1f689 | ||
|
|
f773ee2dab | ||
|
|
a7487fb26d | ||
|
|
ce5aa9fb7a | ||
|
|
790fc13157 | ||
|
|
face0d20d4 | ||
|
|
5c16407818 | ||
|
|
d8201fd4ad | ||
|
|
2b1cd4951d | ||
|
|
849fe8fc0c | ||
|
|
f600e6095e | ||
|
|
d952f774dd | ||
|
|
38ee27d896 | ||
|
|
73e1bc352b | ||
|
|
46ce75bf2a | ||
|
|
2c285fee8a | ||
|
|
d1c3cf8bb0 | ||
|
|
a09c7ddb53 | ||
|
|
fec81c93a3 | ||
|
|
2b3c70dfc3 | ||
|
|
db9236bef2 | ||
|
|
65642edc15 | ||
|
|
86b6e12453 |
No files matched your search
@@ -25,6 +25,7 @@ before_deploy:
|
||||
if [ -z "$BEFORE_DEPLOY_RAN" ]; then
|
||||
npm --no-git-tag-version version $RELEASE_VERSION
|
||||
if [ "$TRAVIS_BRANCH" == "master" ]; then export NPM_TAG=stable; fi
|
||||
if [[ "$TRAVIS_BRANCH" == hotfix/* ]]; then export NPM_TAG=hotfix; fi # double brackets are important for matching the wildcard
|
||||
git config --global user.email $(git log --pretty=format:"%ae" -n1)
|
||||
git config --global user.name $(git log --pretty=format:"%an" -n1)
|
||||
export BEFORE_DEPLOY_RAN=true
|
||||
@@ -36,6 +37,7 @@ deploy:
|
||||
- master
|
||||
- develop
|
||||
- smoke
|
||||
- hotfix/*
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
skip_cleanup: true
|
||||
email: $NPM_EMAIL
|
||||
@@ -65,7 +67,7 @@ deploy:
|
||||
- provider: script
|
||||
on:
|
||||
all_branches: true
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
condition: $TRAVIS_EVENT_TYPE != cron && ! $TRAVIS_BRANCH =~ ^greenkeeper/
|
||||
skip_cleanup: true
|
||||
script: npm run deploy -- -x -e $TRAVIS_BRANCH -r https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git
|
||||
- provider: script
|
||||
|
||||
@@ -30,29 +30,38 @@ Then go to [http://localhost:8601/](http://localhost:8601/) - the playground out
|
||||
|
||||
## Developing alongside other Scratch repositories
|
||||
|
||||
### Linking this code to another project's `node_modules/scratch-gui`
|
||||
### Getting another repo to point to this code
|
||||
|
||||
|
||||
If you wish to develop `scratch-gui` alongside other scratch repositories that depend on it, you may wish
|
||||
to have the other repositories use your local `scratch-gui` build instead of fetching the current production
|
||||
version of the scratch-gui that is found by default using `npm install`.
|
||||
|
||||
Here's how to link your local `scratch-gui` code to another project's `node_modules/scratch-gui`.
|
||||
|
||||
#### Configuration
|
||||
|
||||
If you wish to develop scratch-gui alongside other scratch repositories that depend on it, you may wish
|
||||
to have the other repositories use your local scratch-gui build instead of fetching the current production
|
||||
version of the scratch-gui that is found by default using `npm install`.
|
||||
1. In your local `scratch-gui` repository's top level:
|
||||
1. Make sure you have run `npm install`
|
||||
2. Build the `dist` directory by running `BUILD_MODE=dist npm run build`
|
||||
3. Establish a link to this repository by running `npm link`
|
||||
|
||||
To do this:
|
||||
1. Make sure you have run `npm install` from this (scratch-gui) repository's top level
|
||||
2. Make sure you have run `npm install` from the top level of each repository (such as scratch-www) that depends on scratch-gui
|
||||
3. From this (scratch-gui) repository's top level, build the `dist` directory by running `BUILD_MODE=dist npm run build`
|
||||
4. From this (scratch-gui) repository's top level, establish a link to this repository by running `npm link`
|
||||
5. From the top level of each repository that depends on scratch-gui, run `npm link scratch-gui`
|
||||
6. Build or run the repositories that depend on scratch-gui
|
||||
2. From the top level of each repository (such as `scratch-www`) that depends on `scratch-gui`:
|
||||
1. Make sure you have run `npm install`
|
||||
2. Run `npm link scratch-gui`
|
||||
3. Build or run the repositoriy
|
||||
|
||||
Instead of `BUILD_MODE=dist npm run build` you can also use `BUILD_MODE=dist npm run watch`, however this may be unreliable.
|
||||
#### Using `npm run watch`
|
||||
|
||||
Instead of `BUILD_MODE=dist npm run build`, you can use `BUILD_MODE=dist npm run watch` instead. This will watch for changes to your `scratch-gui` code, and automatically rebuild when there are changes. Sometimes this has been unreliable; if you are having problems, try going back to `BUILD_MODE=dist npm run build` until you resolve them.
|
||||
|
||||
#### Oh no! It didn't work!
|
||||
* Follow the recipe above step by step and don't change the order. It is especially important to run npm first because installing after the linking will reset the linking.
|
||||
* Make sure the repositories are siblings on your machine's file tree.
|
||||
* If you have multiple Terminal tabs or windows open for the different Scratch repositories, make sure to use the same node version in all of them.
|
||||
* In the worst case unlink the repositories by running `npm unlink` in both, and start over.
|
||||
|
||||
If you can't get linking to work right, try:
|
||||
* Follow the recipe above step by step and don't change the order. It is especially important to run `npm install` _before_ `npm link`, because installing after the linking will reset the linking.
|
||||
* Make sure the repositories are siblings on your machine's file tree, like `.../.../MY_SCRATCH_DEV_DIRECTORY/scratch-gui/` and `.../.../MY_SCRATCH_DEV_DIRECTORY/scratch-www/`.
|
||||
* Consistent node.js version: If you have multiple Terminal tabs or windows open for the different Scratch repositories, make sure to use the same node version in all of them.
|
||||
* If nothing else works, unlink the repositories by running `npm unlink` in both, and start over.
|
||||
|
||||
## Testing
|
||||
### Documentation
|
||||
@@ -121,6 +130,66 @@ If you want to watch the browser as it runs the test, rather than running headle
|
||||
USE_HEADLESS=no $(npm bin)/jest --runInBand test/integration/backpack.test.js
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ignoring optional dependencies
|
||||
|
||||
When running `npm install`, you can get warnings about optionsl dependencies:
|
||||
|
||||
```
|
||||
npm WARN optional Skipping failed optional dependency /chokidar/fsevents:
|
||||
npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.2.7
|
||||
```
|
||||
|
||||
You can suppress them by adding the `no-optional` switch:
|
||||
|
||||
```
|
||||
npm install --no-optional
|
||||
```
|
||||
|
||||
Further reading: [Stack Overflow](https://stackoverflow.com/questions/36725181/not-compatible-with-your-operating-system-or-architecture-fsevents1-0-11)
|
||||
|
||||
### Resolving dependencies
|
||||
|
||||
When installing for the first time, you can get warnings which need to be resolved:
|
||||
|
||||
```
|
||||
npm WARN eslint-config-scratch@5.0.0 requires a peer of babel-eslint@^8.0.1 but none was installed.
|
||||
npm WARN eslint-config-scratch@5.0.0 requires a peer of eslint@^4.0 but none was installed.
|
||||
npm WARN scratch-paint@0.2.0-prerelease.20190318170811 requires a peer of react-intl-redux@^0.7 but none was installed.
|
||||
npm WARN scratch-paint@0.2.0-prerelease.20190318170811 requires a peer of react-responsive@^4 but none was installed.
|
||||
```
|
||||
|
||||
You can check which versions are available:
|
||||
|
||||
```
|
||||
npm view react-intl-redux@0.* version
|
||||
```
|
||||
|
||||
You will neet do install the required version:
|
||||
|
||||
```
|
||||
npm install --no-optional --save-dev react-intl-redux@^0.7
|
||||
```
|
||||
|
||||
The dependency itself might have more missing dependencies, which will show up like this:
|
||||
|
||||
```
|
||||
user@machine:~/sources/scratch/scratch-gui (491-translatable-library-objects)$ npm install --no-optional --save-dev react-intl-redux@^0.7
|
||||
scratch-gui@0.1.0 /media/cuideigin/Linux/sources/scratch/scratch-gui
|
||||
├── react-intl-redux@0.7.0
|
||||
└── UNMET PEER DEPENDENCY react-responsive@5.0.0
|
||||
```
|
||||
|
||||
You will need to install those as well:
|
||||
|
||||
```
|
||||
npm install --no-optional --save-dev react-responsive@^5.0.0
|
||||
```
|
||||
|
||||
Further reading: [Stack Overflow](https://stackoverflow.com/questions/46602286/npm-requires-a-peer-of-but-all-peers-are-in-package-json-and-node-modules)
|
||||
|
||||
|
||||
## Publishing to GitHub Pages
|
||||
You can publish the GUI to github.io so that others on the Internet can view it.
|
||||
[Read the wiki for a step-by-step guide.](https://github.com/LLK/scratch-gui/wiki/Publishing-to-GitHub-Pages)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"babel-loader": "^8.0.4",
|
||||
"base64-loader": "1.0.0",
|
||||
"bowser": "1.9.4",
|
||||
"chromedriver": "2.44.1",
|
||||
"chromedriver": "74.0.0",
|
||||
"classnames": "2.2.6",
|
||||
"computed-style-to-inline-style": "3.0.0",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
@@ -56,7 +56,7 @@
|
||||
"eslint": "^5.0.1",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-react": "7.11.1",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"file-loader": "2.0.0",
|
||||
"get-float-time-domain-data": "0.1.0",
|
||||
"get-user-media-promise": "1.1.4",
|
||||
@@ -90,7 +90,7 @@
|
||||
"react-dom": "16.2.0",
|
||||
"react-draggable": "3.0.5",
|
||||
"react-ga": "2.5.3",
|
||||
"react-intl": "2.4.0",
|
||||
"react-intl": "2.8.0",
|
||||
"react-modal": "3.6.1",
|
||||
"react-popover": "0.5.10",
|
||||
"react-redux": "5.0.7",
|
||||
@@ -100,18 +100,19 @@
|
||||
"react-test-renderer": "16.2.0",
|
||||
"react-tooltip": "3.8.0",
|
||||
"react-virtualized": "9.20.1",
|
||||
"react-visibility-sensor": "5.0.2",
|
||||
"redux": "3.7.2",
|
||||
"redux-mock-store": "^1.2.3",
|
||||
"redux-throttle": "0.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20190114210212",
|
||||
"scratch-blocks": "0.1.0-prerelease.1552662801",
|
||||
"scratch-l10n": "3.1.20190220143732",
|
||||
"scratch-paint": "0.2.0-prerelease.20190311150519",
|
||||
"scratch-render": "0.1.0-prerelease.20190312125229",
|
||||
"scratch-storage": "1.2.2",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190304180800",
|
||||
"scratch-vm": "0.2.0-prerelease.20190318152140",
|
||||
"scratch-blocks": "0.1.0-prerelease.1557852594",
|
||||
"scratch-l10n": "3.3.20190516144548",
|
||||
"scratch-paint": "0.2.0-prerelease.20190516171147",
|
||||
"scratch-render": "0.1.0-prerelease.20190419202157",
|
||||
"scratch-storage": "1.3.1",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190419183947",
|
||||
"scratch-vm": "0.2.0-prerelease.20190515153227",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
|
||||
@@ -2,10 +2,18 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/z-index.css";
|
||||
|
||||
.card-container-overlay {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: $z-index-card;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
position:absolute;
|
||||
pointer-events: auto;
|
||||
z-index: $z-index-card;
|
||||
margin: 0.5rem 2rem;
|
||||
min-width: 468px;
|
||||
}
|
||||
|
||||
.left-card, .right-card {
|
||||
@@ -104,11 +112,38 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header-buttons-hidden {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.header-buttons-right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.header-buttons img {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.shrink-expand-button {
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.shrink-expand-button:hover, .all-button:hover {
|
||||
background-color: $ui-black-transparent;
|
||||
}
|
||||
|
||||
.remove-button, .all-button {
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
@@ -137,7 +172,7 @@
|
||||
}
|
||||
|
||||
.step-video {
|
||||
height: 337px;
|
||||
height: 256px;
|
||||
}
|
||||
|
||||
.step-image {
|
||||
@@ -188,7 +223,7 @@
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
height: 1rem;
|
||||
height: 1.25rem;
|
||||
margin: .125rem 0; /* To offset the .25rem difference in icon size */
|
||||
}
|
||||
|
||||
@@ -267,3 +302,7 @@
|
||||
background: $ui-white;
|
||||
box-shadow: 0px 0px 0px 2px $ui-black-transparent;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {Fragment} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
import styles from './card.css';
|
||||
|
||||
import shrinkIcon from './icon--shrink.svg';
|
||||
import expandIcon from './icon--expand.svg';
|
||||
|
||||
import rightArrow from './icon--next.svg';
|
||||
import leftArrow from './icon--prev.svg';
|
||||
|
||||
@@ -14,8 +18,8 @@ import closeIcon from './icon--close.svg';
|
||||
import {translateVideo} from '../../lib/libraries/decks/translate-video.js';
|
||||
import {translateImage} from '../../lib/libraries/decks/translate-image.js';
|
||||
|
||||
const CardHeader = ({onCloseCards, onShowAll, totalSteps, step}) => (
|
||||
<div className={styles.headerButtons}>
|
||||
const CardHeader = ({onCloseCards, onShrinkExpandCards, onShowAll, totalSteps, step, expanded}) => (
|
||||
<div className={expanded ? styles.headerButtons : classNames(styles.headerButtons, styles.headerButtonsHidden)}>
|
||||
<div
|
||||
className={styles.allButton}
|
||||
onClick={onShowAll}
|
||||
@@ -41,19 +45,42 @@ const CardHeader = ({onCloseCards, onShowAll, totalSteps, step}) => (
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={styles.removeButton}
|
||||
onClick={onCloseCards}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Close"
|
||||
description="Title for button to close how-to card"
|
||||
id="gui.cards.close"
|
||||
/>
|
||||
<img
|
||||
className={styles.closeIcon}
|
||||
src={closeIcon}
|
||||
/>
|
||||
<div className={styles.headerButtonsRight}>
|
||||
<div
|
||||
className={styles.shrinkExpandButton}
|
||||
onClick={onShrinkExpandCards}
|
||||
>
|
||||
<img
|
||||
draggable={false}
|
||||
src={expanded ? shrinkIcon : expandIcon}
|
||||
/>
|
||||
{expanded ?
|
||||
<FormattedMessage
|
||||
defaultMessage="Shrink"
|
||||
description="Title for button to shrink how-to card"
|
||||
id="gui.cards.shrink"
|
||||
/> :
|
||||
<FormattedMessage
|
||||
defaultMessage="Expand"
|
||||
description="Title for button to expand how-to card"
|
||||
id="gui.cards.expand"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className={styles.removeButton}
|
||||
onClick={onCloseCards}
|
||||
>
|
||||
<img
|
||||
className={styles.closeIcon}
|
||||
src={closeIcon}
|
||||
/>
|
||||
<FormattedMessage
|
||||
defaultMessage="Close"
|
||||
description="Title for button to close how-to card"
|
||||
id="gui.cards.close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -69,11 +96,11 @@ const VideoStep = ({video, dragging}) => (
|
||||
allowFullScreen
|
||||
allowTransparency="true"
|
||||
frameBorder="0"
|
||||
height="338"
|
||||
height="257"
|
||||
scrolling="no"
|
||||
src={`https://fast.wistia.net/embed/iframe/${video}?seo=false&videoFoam=true`}
|
||||
title="📹"
|
||||
width="600"
|
||||
width="466"
|
||||
/>
|
||||
<script
|
||||
async
|
||||
@@ -107,13 +134,13 @@ ImageStep.propTypes = {
|
||||
title: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
const NextPrevButtons = ({isRtl, onNextStep, onPrevStep}) => (
|
||||
const NextPrevButtons = ({isRtl, onNextStep, onPrevStep, expanded}) => (
|
||||
<Fragment>
|
||||
{onNextStep ? (
|
||||
<div>
|
||||
<div className={isRtl ? styles.leftCard : styles.rightCard} />
|
||||
<div className={expanded ? (isRtl ? styles.leftCard : styles.rightCard) : styles.hidden} />
|
||||
<div
|
||||
className={isRtl ? styles.leftButton : styles.rightButton}
|
||||
className={expanded ? (isRtl ? styles.leftButton : styles.rightButton) : styles.hidden}
|
||||
onClick={onNextStep}
|
||||
>
|
||||
<img
|
||||
@@ -125,9 +152,9 @@ const NextPrevButtons = ({isRtl, onNextStep, onPrevStep}) => (
|
||||
) : null}
|
||||
{onPrevStep ? (
|
||||
<div>
|
||||
<div className={isRtl ? styles.rightCard : styles.leftCard} />
|
||||
<div className={expanded ? (isRtl ? styles.rightCard : styles.leftCard) : styles.hidden} />
|
||||
<div
|
||||
className={isRtl ? styles.rightButton : styles.leftButton}
|
||||
className={expanded ? (isRtl ? styles.rightButton : styles.leftButton) : styles.hidden}
|
||||
onClick={onPrevStep}
|
||||
>
|
||||
<img
|
||||
@@ -141,13 +168,16 @@ const NextPrevButtons = ({isRtl, onNextStep, onPrevStep}) => (
|
||||
);
|
||||
|
||||
NextPrevButtons.propTypes = {
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
isRtl: PropTypes.bool,
|
||||
onNextStep: PropTypes.func,
|
||||
onPrevStep: PropTypes.func
|
||||
};
|
||||
CardHeader.propTypes = {
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
onCloseCards: PropTypes.func.isRequired,
|
||||
onShowAll: PropTypes.func.isRequired,
|
||||
onShrinkExpandCards: PropTypes.func.isRequired,
|
||||
step: PropTypes.number,
|
||||
totalSteps: PropTypes.number
|
||||
};
|
||||
@@ -219,6 +249,7 @@ const Cards = props => {
|
||||
locale,
|
||||
onActivateDeckFactory,
|
||||
onCloseCards,
|
||||
onShrinkExpandCards,
|
||||
onDrag,
|
||||
onStartDrag,
|
||||
onEndDrag,
|
||||
@@ -226,71 +257,95 @@ const Cards = props => {
|
||||
onNextStep,
|
||||
onPrevStep,
|
||||
step,
|
||||
expanded,
|
||||
...posProps
|
||||
} = props;
|
||||
let {x, y} = posProps;
|
||||
|
||||
if (activeDeckId === null) return;
|
||||
|
||||
// Tutorial cards need to calculate their own dragging bounds
|
||||
// to allow for dragging the cards off the left, right and bottom
|
||||
// edges of the workspace.
|
||||
const cardHorizontalDragOffset = 400; // ~80% of card width
|
||||
const cardVerticalDragOffset = expanded ? 257 : 0; // ~80% of card height, if expanded
|
||||
const menuBarHeight = 48; // TODO: get pre-calculated from elsewhere?
|
||||
const wideCardWidth = 500;
|
||||
|
||||
if (x === 0 && y === 0) {
|
||||
// initialize positions
|
||||
x = isRtl ? -292 : 292;
|
||||
// The tallest cards are about 385px high, and the default position is pinned
|
||||
x = isRtl ? (-190 - wideCardWidth - cardHorizontalDragOffset) : 292;
|
||||
x += cardHorizontalDragOffset;
|
||||
// The tallest cards are about 320px high, and the default position is pinned
|
||||
// to near the bottom of the blocks palette to allow room to work above.
|
||||
const tallCardHeight = 385;
|
||||
const tallCardHeight = 320;
|
||||
const bottomMargin = 60; // To avoid overlapping the backpack region
|
||||
y = window.innerHeight - tallCardHeight - bottomMargin;
|
||||
y = window.innerHeight - tallCardHeight - bottomMargin - menuBarHeight;
|
||||
}
|
||||
|
||||
const steps = content[activeDeckId].steps;
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
bounds="parent"
|
||||
position={{x: x, y: y}}
|
||||
onDrag={onDrag}
|
||||
onStart={onStartDrag}
|
||||
onStop={onEndDrag}
|
||||
// Custom overlay to act as the bounding parent for the draggable, using values from above
|
||||
<div
|
||||
className={styles.cardContainerOverlay}
|
||||
style={{
|
||||
width: `${window.innerWidth + (2 * cardHorizontalDragOffset)}px`,
|
||||
height: `${window.innerHeight - menuBarHeight + cardVerticalDragOffset}px`,
|
||||
top: `${menuBarHeight}px`,
|
||||
left: `${-cardHorizontalDragOffset}px`
|
||||
}}
|
||||
>
|
||||
<div className={styles.cardContainer}>
|
||||
<div className={styles.card}>
|
||||
<CardHeader
|
||||
step={step}
|
||||
totalSteps={steps.length}
|
||||
onCloseCards={onCloseCards}
|
||||
onShowAll={onShowAll}
|
||||
/>
|
||||
<div className={styles.stepBody}>
|
||||
{steps[step].deckIds ? (
|
||||
<PreviewsStep
|
||||
content={content}
|
||||
deckIds={steps[step].deckIds}
|
||||
onActivateDeckFactory={onActivateDeckFactory}
|
||||
onShowAll={onShowAll}
|
||||
/>
|
||||
) : (
|
||||
steps[step].video ? (
|
||||
<VideoStep
|
||||
dragging={dragging}
|
||||
video={translateVideo(steps[step].video, locale)}
|
||||
<Draggable
|
||||
bounds="parent"
|
||||
position={{x: x, y: y}}
|
||||
onDrag={onDrag}
|
||||
onStart={onStartDrag}
|
||||
onStop={onEndDrag}
|
||||
>
|
||||
<div className={styles.cardContainer}>
|
||||
<div className={styles.card}>
|
||||
<CardHeader
|
||||
expanded={expanded}
|
||||
step={step}
|
||||
totalSteps={steps.length}
|
||||
onCloseCards={onCloseCards}
|
||||
onShowAll={onShowAll}
|
||||
onShrinkExpandCards={onShrinkExpandCards}
|
||||
/>
|
||||
<div className={expanded ? styles.stepBody : styles.hidden}>
|
||||
{steps[step].deckIds ? (
|
||||
<PreviewsStep
|
||||
content={content}
|
||||
deckIds={steps[step].deckIds}
|
||||
onActivateDeckFactory={onActivateDeckFactory}
|
||||
onShowAll={onShowAll}
|
||||
/>
|
||||
) : (
|
||||
<ImageStep
|
||||
image={translateImage(steps[step].image, locale)}
|
||||
title={steps[step].title}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{steps[step].trackingPixel && steps[step].trackingPixel}
|
||||
steps[step].video ? (
|
||||
<VideoStep
|
||||
dragging={dragging}
|
||||
video={translateVideo(steps[step].video, locale)}
|
||||
/>
|
||||
) : (
|
||||
<ImageStep
|
||||
image={translateImage(steps[step].image, locale)}
|
||||
title={steps[step].title}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
{steps[step].trackingPixel && steps[step].trackingPixel}
|
||||
</div>
|
||||
<NextPrevButtons
|
||||
expanded={expanded}
|
||||
isRtl={isRtl}
|
||||
onNextStep={step < steps.length - 1 ? onNextStep : null}
|
||||
onPrevStep={step > 0 ? onPrevStep : null}
|
||||
/>
|
||||
</div>
|
||||
<NextPrevButtons
|
||||
isRtl={isRtl}
|
||||
onNextStep={step < steps.length - 1 ? onNextStep : null}
|
||||
onPrevStep={step > 0 ? onPrevStep : null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
</Draggable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -309,6 +364,7 @@ Cards.propTypes = {
|
||||
})
|
||||
}),
|
||||
dragging: PropTypes.bool.isRequired,
|
||||
expanded: PropTypes.bool.isRequired,
|
||||
isRtl: PropTypes.bool.isRequired,
|
||||
locale: PropTypes.string.isRequired,
|
||||
onActivateDeckFactory: PropTypes.func.isRequired,
|
||||
@@ -318,6 +374,7 @@ Cards.propTypes = {
|
||||
onNextStep: PropTypes.func.isRequired,
|
||||
onPrevStep: PropTypes.func.isRequired,
|
||||
onShowAll: PropTypes.func,
|
||||
onShrinkExpandCards: PropTypes.func.isRequired,
|
||||
onStartDrag: PropTypes.func,
|
||||
step: PropTypes.number.isRequired,
|
||||
x: PropTypes.number,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 52.2 (67145) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>General/Check Copy</title>
|
||||
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>Extensions/Connection/Close</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M10,7.73577763 L12.2675846,5.46819305 C12.8909754,4.84480221 13.8988379,4.84194841 14.5284447,5.47155526 C15.1536925,6.096803 15.1476685,7.11655392 14.531807,7.73241542 L12.2642224,10 L14.531807,12.2675846 C15.1551978,12.8909754 15.1580516,13.8988379 14.5284447,14.5284447 C13.903197,15.1536925 12.8834461,15.1476685 12.2675846,14.531807 L10,12.2642224 L7.73241542,14.531807 C7.10902458,15.1551978 6.10116212,15.1580516 5.47155526,14.5284447 C4.84630752,13.903197 4.85233155,12.8834461 5.46819305,12.2675846 L7.73577763,10 L5.46819305,7.73241542 C4.84480221,7.10902458 4.84194841,6.10116212 5.47155526,5.47155526 C6.096803,4.84630752 7.11655392,4.85233155 7.73241542,5.46819305 L10,7.73577763 Z" id="path-1"></path>
|
||||
<path d="M15.464935,15.467297 C14.7644059,16.1777705 13.6185877,16.1777705 12.9081142,15.467297 L9.99881899,12.5580018 L7.08841885,15.467297 C6.38236506,16.1733508 5.23765187,16.1733508 4.53159807,15.467297 C4.17912364,15.1148226 4.00012409,14.6485398 4.00012409,14.1888866 C4.00012409,13.7281285 4.17912364,13.2629506 4.53159807,12.9104762 L7.44089328,10.001181 L4.52717833,7.08636112 C4.17359897,6.73278176 3.99459941,6.26760391 4.00012409,5.80242606 C4.00012409,5.34166795 4.17359897,4.88201477 4.52717833,4.52954034 C5.23212719,3.82348655 6.37684038,3.82348655 7.08399911,4.52954034 L9.99881899,7.44325529 L12.9125339,4.52954034 C13.6185877,3.82348655 14.7644059,3.82348655 15.4704597,4.52954034 C16.1765134,5.2344892 16.1765134,6.38030733 15.4704597,7.08636112 L12.5545348,10.001181 L15.4704597,12.914896 C16.1765134,13.6209497 16.1765134,14.7557185 15.464935,15.467297" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="General/Check-Copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Extensions/Connection/Close" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="close-icon" fill="#FFFFFF" xlink:href="#path-1"></use>
|
||||
<g id="Color/White" mask="url(#mask-2)" fill="#FFFFFF">
|
||||
<use id="close" fill="#FFFFFF" xlink:href="#path-1"></use>
|
||||
<g id="White" mask="url(#mask-2)" fill="#FFFFFF">
|
||||
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -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 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>Tutorials/Expand</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M4.5,2 L15.5,2 C16.8807119,2 18,3.11928813 18,4.5 L18,5.5 C18,5.77614237 17.7761424,6 17.5,6 L2.5,6 C2.22385763,6 2,5.77614237 2,5.5 L2,4.5 C2,3.11928813 3.11928813,2 4.5,2 Z M2.5,7 L17.5,7 C17.7761424,7 18,7.22385763 18,7.5 L18,15.5 C18,16.8807119 16.8807119,18 15.5,18 L4.5,18 C3.11928813,18 2,16.8807119 2,15.5 L2,7.5 C2,7.22385763 2.22385763,7 2.5,7 Z M9.65509466,15.1636146 C9.745416,15.2533569 9.86700241,15.3031495 9.99727357,15.3031495 C10.1275447,15.3031495 10.2491311,15.2533569 10.3394525,15.1636146 L12.1574588,13.3456082 C12.3004676,13.2025994 12.3415754,12.9993764 12.2657286,12.8158388 C12.1898819,12.6334592 12.0109762,12.5153467 11.8100691,12.5153467 L11.1506075,12.5153467 L10.7899012,9.98808625 C10.7464775,9.64474938 10.4703026,9.36567961 10.1124912,9.30951827 C10.0783312,9.30546539 10.0435922,9.30314946 10.0094322,9.30314946 C9.61282891,9.30314946 9.27586085,9.59785176 9.22548933,9.98519134 L8.8740467,12.5153467 L8.18447804,12.5153467 C7.9858869,12.5153467 7.81450796,12.6265114 7.73692425,12.8065751 C7.65876155,12.9866388 7.69639544,13.1962306 7.83708829,13.3456082 L9.65509466,15.1636146 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Tutorials/Expand" 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="Combined-Shape" fill-rule="nonzero"></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: 1.9 KiB |
@@ -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 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>Tutorials/Shink</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M2.5,8 C2.22385763,8 2,7.77614237 2,7.5 C2,7.22385763 2.22385763,7 2.5,7 L3.5,7 C3.77614237,7 4,7.22385763 4,7.5 C4,7.77614237 3.77614237,8 3.5,8 L2.5,8 Z M5.5,8 C5.22385763,8 5,7.77614237 5,7.5 C5,7.22385763 5.22385763,7 5.5,7 L6.5,7 C6.77614237,7 7,7.22385763 7,7.5 C7,7.77614237 6.77614237,8 6.5,8 L5.5,8 Z M8.5,8 C8.22385763,8 8,7.77614237 8,7.5 C8,7.22385763 8.22385763,7 8.5,7 L9.5,7 C9.77614237,7 10,7.22385763 10,7.5 C10,7.77614237 9.77614237,8 9.5,8 L8.5,8 Z M11.5,8 C11.2238576,8 11,7.77614237 11,7.5 C11,7.22385763 11.2238576,7 11.5,7 L12.5,7 C12.7761424,7 13,7.22385763 13,7.5 C13,7.77614237 12.7761424,8 12.5,8 L11.5,8 Z M14.5,8 C14.2238576,8 14,7.77614237 14,7.5 C14,7.22385763 14.2238576,7 14.5,7 L15.5,7 C15.7761424,7 16,7.22385763 16,7.5 C16,7.77614237 15.7761424,8 15.5,8 L14.5,8 Z M17,7.5 C17,7.22385763 17.2238576,7 17.5,7 C17.7761424,7 18,7.22385763 18,7.5 L18,8.5 C18,8.77614237 17.7761424,9 17.5,9 C17.2238576,9 17,8.77614237 17,8.5 L17,7.5 Z M17,10.5 C17,10.2238576 17.2238576,10 17.5,10 C17.7761424,10 18,10.2238576 18,10.5 L18,11.5 C18,11.7761424 17.7761424,12 17.5,12 C17.2238576,12 17,11.7761424 17,11.5 L17,10.5 Z M17,13.5 C17,13.2238576 17.2238576,13 17.5,13 C17.7761424,13 18,13.2238576 18,13.5 L18,14.5 C18,14.7761424 17.7761424,15 17.5,15 C17.2238576,15 17,14.7761424 17,14.5 L17,13.5 Z M16.8130884,16.2258789 C16.9469778,15.9843665 17.2513011,15.8971208 17.4928135,16.0310102 C17.7343259,16.1648996 17.8215716,16.4692229 17.6876822,16.7107353 C17.4827416,17.0804113 17.1867239,17.3929429 16.8289716,17.6178178 C16.59518,17.7647739 16.286523,17.6943798 16.1395669,17.4605882 C15.9926108,17.2267966 16.0630049,16.9181396 16.2967965,16.7711835 C16.5118233,16.6360225 16.6900031,16.4479027 16.8130884,16.2258789 Z M14.5614675,17 C14.8376098,17 15.0614675,17.2238576 15.0614675,17.5 C15.0614675,17.7761424 14.8376098,18 14.5614675,18 L13.5614675,18 C13.2853251,18 13.0614675,17.7761424 13.0614675,17.5 C13.0614675,17.2238576 13.2853251,17 13.5614675,17 L14.5614675,17 Z M11.5614675,17 C11.8376098,17 12.0614675,17.2238576 12.0614675,17.5 C12.0614675,17.7761424 11.8376098,18 11.5614675,18 L10.5614675,18 C10.2853251,18 10.0614675,17.7761424 10.0614675,17.5 C10.0614675,17.2238576 10.2853251,17 10.5614675,17 L11.5614675,17 Z M8.56146746,17 C8.83760983,17 9.06146746,17.2238576 9.06146746,17.5 C9.06146746,17.7761424 8.83760983,18 8.56146746,18 L7.56146746,18 C7.28532508,18 7.06146746,17.7761424 7.06146746,17.5 C7.06146746,17.2238576 7.28532508,17 7.56146746,17 L8.56146746,17 Z M5.56146746,17 C5.83760983,17 6.06146746,17.2238576 6.06146746,17.5 C6.06146746,17.7761424 5.83760983,18 5.56146746,18 L4.56146746,18 C4.28532508,18 4.06146746,17.7761424 4.06146746,17.5 C4.06146746,17.2238576 4.28532508,17 4.56146746,17 L5.56146746,17 Z M3.22779886,16.2951753 C3.37445671,16.5291541 3.30366909,16.8377211 3.06969027,16.9843789 C2.83571145,17.1310368 2.52714447,17.0602491 2.38048662,16.8262703 C2.15682122,16.4694332 2.02609629,16.0609535 2.00352115,15.6335963 C1.98895424,15.3578384 2.20069137,15.1224836 2.47644927,15.1079167 C2.75220716,15.0933498 2.98756192,15.3050869 3.00212882,15.5808448 C3.01567642,15.8373067 3.0937811,16.0813625 3.22779886,16.2951753 Z M3,13.6229349 C3,13.8990773 2.77614237,14.1229349 2.5,14.1229349 C2.22385763,14.1229349 2,13.8990773 2,13.6229349 L2,12.6229349 C2,12.3467925 2.22385763,12.1229349 2.5,12.1229349 C2.77614237,12.1229349 3,12.3467925 3,12.6229349 L3,13.6229349 Z M3,10.6229349 C3,10.8990773 2.77614237,11.1229349 2.5,11.1229349 C2.22385763,11.1229349 2,10.8990773 2,10.6229349 L2,9.62293492 C2,9.34679254 2.22385763,9.12293492 2.5,9.12293492 C2.77614237,9.12293492 3,9.34679254 3,9.62293492 L3,10.6229349 Z M3,7.62293492 C3,7.89907729 2.77614237,8.12293492 2.5,8.12293492 C2.22385763,8.12293492 2,7.89907729 2,7.62293492 L2,7.5 C2,7.22385763 2.22385763,7 2.5,7 C2.77614237,7 3,7.22385763 3,7.5 L3,7.62293492 Z M4.5,2 L15.5,2 C16.8807119,2 18,3.11928813 18,4.5 L18,5.5 C18,5.77614237 17.7761424,6 17.5,6 L2.5,6 C2.22385763,6 2,5.77614237 2,5.5 L2,4.5 C2,3.11928813 3.11928813,2 4.5,2 Z M10.3449053,9.44268434 L12.1629117,11.2606907 C12.3036046,11.4100683 12.3412384,11.6196601 12.2630758,11.7997238 C12.185492,11.9797875 12.0141131,12.0909522 11.815522,12.0909522 L11.1259533,12.0909522 L10.7745107,14.6211076 C10.7241392,15.0084472 10.3871711,15.3031495 9.99056779,15.3031495 C9.9564078,15.3031495 9.92166882,15.3008335 9.88750883,15.2967806 C9.52969739,15.2406193 9.25352253,14.9615495 9.21009882,14.6182127 L8.84939246,12.0909522 L8.18993091,12.0909522 C7.98902384,12.0909522 7.81011812,11.9728397 7.73427135,11.7904601 C7.65842459,11.6069225 7.69953238,11.4036995 7.84254116,11.2606907 L9.66054753,9.44268434 C9.75086886,9.35294199 9.87245528,9.30314946 10.0027264,9.30314946 C10.1329976,9.30314946 10.254584,9.35294199 10.3449053,9.44268434 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Tutorials/Shink" 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="Rectangle" fill-rule="nonzero"></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: 5.5 KiB |
@@ -45,8 +45,8 @@ const AutoScanningStep = props => (
|
||||
src={radarIcon}
|
||||
/>
|
||||
<img
|
||||
className={styles.peripheralButtonImage}
|
||||
src={props.peripheralButtonImage}
|
||||
className={styles.connectionTipIcon}
|
||||
src={props.connectionTipIconURL}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
@@ -142,9 +142,9 @@ const AutoScanningStep = props => (
|
||||
);
|
||||
|
||||
AutoScanningStep.propTypes = {
|
||||
connectionTipIconURL: PropTypes.string,
|
||||
onRefresh: PropTypes.func,
|
||||
onStartScan: PropTypes.func,
|
||||
peripheralButtonImage: PropTypes.string,
|
||||
phase: PropTypes.oneOf(Object.keys(PHASES))
|
||||
};
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const ConnectedStep = props => (
|
||||
<div className={styles.peripheralActivity}>
|
||||
<img
|
||||
className={styles.peripheralActivityIcon}
|
||||
src={props.peripheralImage}
|
||||
src={props.connectionIconURL}
|
||||
/>
|
||||
<img
|
||||
className={styles.bluetoothConnectedIcon}
|
||||
@@ -64,9 +64,9 @@ const ConnectedStep = props => (
|
||||
);
|
||||
|
||||
ConnectedStep.propTypes = {
|
||||
connectionIconURL: PropTypes.string.isRequired,
|
||||
onCancel: PropTypes.func,
|
||||
onDisconnect: PropTypes.func,
|
||||
peripheralImage: PropTypes.string.isRequired
|
||||
onDisconnect: PropTypes.func
|
||||
};
|
||||
|
||||
export default ConnectedStep;
|
||||
@@ -18,7 +18,7 @@ const ConnectingStep = props => (
|
||||
<div className={styles.peripheralActivity}>
|
||||
<img
|
||||
className={styles.peripheralActivityIcon}
|
||||
src={props.peripheralImage}
|
||||
src={props.connectionIconURL}
|
||||
/>
|
||||
<img
|
||||
className={styles.bluetoothConnectingIcon}
|
||||
@@ -63,8 +63,8 @@ const ConnectingStep = props => (
|
||||
|
||||
ConnectingStep.propTypes = {
|
||||
connectingMessage: PropTypes.node.isRequired,
|
||||
onDisconnect: PropTypes.func,
|
||||
peripheralImage: PropTypes.string.isRequired
|
||||
connectionIconURL: PropTypes.string.isRequired,
|
||||
onDisconnect: PropTypes.func
|
||||
};
|
||||
|
||||
export default ConnectingStep;
|
||||
@@ -162,7 +162,7 @@
|
||||
height: 80px; */
|
||||
}
|
||||
|
||||
.peripheral-button-image {
|
||||
.connection-tip-icon {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const ConnectionModalComponent = props => (
|
||||
className={styles.modalContent}
|
||||
contentLabel={props.name}
|
||||
headerClassName={styles.header}
|
||||
headerImage={props.smallPeripheralImage}
|
||||
headerImage={props.connectionSmallIconURL}
|
||||
id="connectionModal"
|
||||
onHelp={props.onHelp}
|
||||
onRequestClose={props.onCancel}
|
||||
@@ -44,17 +44,21 @@ const ConnectionModalComponent = props => (
|
||||
);
|
||||
|
||||
ConnectionModalComponent.propTypes = {
|
||||
connectingMessage: PropTypes.node,
|
||||
connectingMessage: PropTypes.node.isRequired,
|
||||
connectionSmallIconURL: PropTypes.string,
|
||||
connectionTipIconURL: PropTypes.string,
|
||||
name: PropTypes.node,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onHelp: PropTypes.func.isRequired,
|
||||
peripheralButtonImage: PropTypes.string,
|
||||
phase: PropTypes.oneOf(Object.keys(PHASES)).isRequired,
|
||||
smallPeripheralImage: PropTypes.string,
|
||||
title: PropTypes.string.isRequired,
|
||||
useAutoScan: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
ConnectionModalComponent.defaultProps = {
|
||||
connectingMessage: 'Connecting'
|
||||
};
|
||||
|
||||
export {
|
||||
ConnectionModalComponent as default,
|
||||
PHASES
|
||||
|
||||
@@ -17,7 +17,7 @@ const ErrorStep = props => (
|
||||
<div className={styles.peripheralActivity}>
|
||||
<img
|
||||
className={styles.peripheralActivityIcon}
|
||||
src={props.peripheralImage}
|
||||
src={props.connectionIconURL}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
@@ -70,9 +70,9 @@ const ErrorStep = props => (
|
||||
);
|
||||
|
||||
ErrorStep.propTypes = {
|
||||
connectionIconURL: PropTypes.string.isRequired,
|
||||
onHelp: PropTypes.func,
|
||||
onScanning: PropTypes.func,
|
||||
peripheralImage: PropTypes.string.isRequired
|
||||
onScanning: PropTypes.func
|
||||
};
|
||||
|
||||
export default ErrorStep;
|
||||
@@ -23,7 +23,7 @@ class PeripheralTile extends React.Component {
|
||||
<Box className={styles.peripheralTileName}>
|
||||
<img
|
||||
className={styles.peripheralTileImage}
|
||||
src={this.props.smallPeripheralImage}
|
||||
src={this.props.connectionSmallIconURL}
|
||||
/>
|
||||
<Box className={styles.peripheralTileNameWrapper}>
|
||||
<Box className={styles.peripheralTileNameLabel}>
|
||||
@@ -77,11 +77,11 @@ class PeripheralTile extends React.Component {
|
||||
}
|
||||
|
||||
PeripheralTile.propTypes = {
|
||||
connectionSmallIconURL: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
onConnecting: PropTypes.func,
|
||||
peripheralId: PropTypes.string,
|
||||
rssi: PropTypes.number,
|
||||
smallPeripheralImage: PropTypes.string
|
||||
rssi: PropTypes.number
|
||||
};
|
||||
|
||||
export default PeripheralTile;
|
||||
@@ -34,11 +34,11 @@ const ScanningStep = props => (
|
||||
<div className={styles.peripheralTilePane}>
|
||||
{props.peripheralList.map(peripheral =>
|
||||
(<PeripheralTile
|
||||
connectionSmallIconURL={props.connectionSmallIconURL}
|
||||
key={peripheral.peripheralId}
|
||||
name={peripheral.name}
|
||||
peripheralId={peripheral.peripheralId}
|
||||
rssi={peripheral.rssi}
|
||||
smallPeripheralImage={props.smallPeripheralImage}
|
||||
onConnecting={props.onConnecting}
|
||||
/>)
|
||||
)}
|
||||
@@ -86,6 +86,7 @@ const ScanningStep = props => (
|
||||
);
|
||||
|
||||
ScanningStep.propTypes = {
|
||||
connectionSmallIconURL: PropTypes.string,
|
||||
onConnecting: PropTypes.func,
|
||||
onRefresh: PropTypes.func,
|
||||
peripheralList: PropTypes.arrayOf(PropTypes.shape({
|
||||
@@ -93,8 +94,7 @@ ScanningStep.propTypes = {
|
||||
rssi: PropTypes.number,
|
||||
peripheralId: PropTypes.string
|
||||
})),
|
||||
scanning: PropTypes.bool.isRequired,
|
||||
smallPeripheralImage: PropTypes.string
|
||||
scanning: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
ScanningStep.defaultProps = {
|
||||
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import ScratchImage from '../scratch-image/scratch-image.jsx';
|
||||
import styles from './library-item.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -35,10 +36,12 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<img
|
||||
className={styles.featuredImage}
|
||||
src={this.props.iconURL}
|
||||
/>
|
||||
{this.props.iconSource ? (
|
||||
<ScratchImage
|
||||
className={styles.featuredImage}
|
||||
imageSource={this.props.iconSource}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{this.props.insetIconURL ? (
|
||||
<div className={styles.libraryItemInsetImageContainer}>
|
||||
@@ -121,9 +124,9 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
{/* Layers of wrapping is to prevent layout thrashing on animation */}
|
||||
<Box className={styles.libraryItemImageContainerWrapper}>
|
||||
<Box className={styles.libraryItemImageContainer}>
|
||||
<img
|
||||
<ScratchImage
|
||||
className={styles.libraryItemImage}
|
||||
src={this.props.iconURL}
|
||||
imageSource={this.props.iconSource}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -146,7 +149,7 @@ LibraryItemComponent.propTypes = {
|
||||
extensionId: PropTypes.string,
|
||||
featured: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
iconURL: PropTypes.string,
|
||||
iconSource: ScratchImage.ImageSourcePropType,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
name: PropTypes.oneOfType([
|
||||
|
||||
@@ -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 storage from '../../lib/storage';
|
||||
|
||||
import styles from './library.css';
|
||||
|
||||
@@ -28,6 +29,47 @@ const messages = defineMessages({
|
||||
const ALL_TAG = {tag: 'all', intlLabel: messages.allTag};
|
||||
const tagListPrefix = [ALL_TAG];
|
||||
|
||||
/**
|
||||
* Find the AssetType which corresponds to a particular file extension. For example, 'png' => AssetType.ImageBitmap.
|
||||
* @param {string} fileExtension - the file extension to look up.
|
||||
* @returns {AssetType} - the AssetType corresponding to the extension, if any.
|
||||
*/
|
||||
const getAssetTypeForFileExtension = function (fileExtension) {
|
||||
const compareOptions = {
|
||||
sensitivity: 'accent',
|
||||
usage: 'search'
|
||||
};
|
||||
for (const assetTypeId in storage.AssetType) {
|
||||
const assetType = storage.AssetType[assetTypeId];
|
||||
if (fileExtension.localeCompare(assetType.runtimeFormat, compareOptions) === 0) {
|
||||
return assetType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Figure out an `imageSource` (URI or asset ID & type) for a library item's icon.
|
||||
* @param {object} item - either a library item or one of a library item's costumes.
|
||||
* @returns {object} - an `imageSource` ready to be passed to a `ScratchImage`.
|
||||
*/
|
||||
const getItemImageSource = function (item) {
|
||||
if (item.rawURL) {
|
||||
return {
|
||||
uri: item.rawURL
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: adjust libraries to be more storage-friendly; don't use split() here.
|
||||
const md5 = item.md5 || item.baseLayerMD5;
|
||||
if (md5) {
|
||||
const [assetId, fileExtension] = md5.split('.');
|
||||
return {
|
||||
assetId: assetId,
|
||||
assetType: getAssetTypeForFileExtension(fileExtension)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class LibraryComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -162,8 +204,10 @@ class LibraryComponent extends React.Component {
|
||||
})}
|
||||
ref={this.setFilteredDataRef}
|
||||
>
|
||||
{this.getFilteredData().map((dataItem, index) => (
|
||||
<LibraryItem
|
||||
{this.getFilteredData().map((dataItem, index) => {
|
||||
const iconSource = getItemImageSource(dataItem);
|
||||
const icons = dataItem.json && dataItem.json.costumes.map(getItemImageSource);
|
||||
return (<LibraryItem
|
||||
bluetoothRequired={dataItem.bluetoothRequired}
|
||||
collaborator={dataItem.collaborator}
|
||||
description={dataItem.description}
|
||||
@@ -171,9 +215,8 @@ class LibraryComponent extends React.Component {
|
||||
extensionId={dataItem.extensionId}
|
||||
featured={dataItem.featured}
|
||||
hidden={dataItem.hidden}
|
||||
iconMd5={dataItem.md5}
|
||||
iconRawURL={dataItem.rawURL}
|
||||
icons={dataItem.json && dataItem.json.costumes}
|
||||
iconSource={iconSource}
|
||||
icons={icons}
|
||||
id={index}
|
||||
insetIconURL={dataItem.insetIconURL}
|
||||
internetConnectionRequired={dataItem.internetConnectionRequired}
|
||||
@@ -182,8 +225,8 @@ class LibraryComponent extends React.Component {
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
))}
|
||||
/>);
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Feedback</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Feedback" fill="#4C97FF">
|
||||
<path d="M12.3811686,14.2770244 C12.3811686,14.3987659 12.3515563,14.506558 12.306433,14.6257633 L11.4998525,16.6002595 C11.2770558,16.478518 10.93299,16.3846755 10.4704753,16.3846755 C10.0234717,16.3846755 9.6949171,16.478518 9.47071028,16.58631 L8.66412979,14.6257633 C8.60490535,14.506558 8.59080429,14.3848164 8.59080429,14.2643429 C8.60490535,14.2503934 8.60490535,14.2237624 8.60490535,14.2098129 L8.60490535,14.1705005 C8.60490535,13.9828155 8.67964095,13.7938625 8.813601,13.6188589 C9.1576668,13.1775457 9.79926492,12.9086997 10.4859864,12.9086997 C11.1727079,12.9086997 11.8143061,13.1775457 12.1428607,13.6188589 C12.2472085,13.7684996 12.3360452,13.9409669 12.3670675,14.103289 C12.3811686,14.1565509 12.3811686,14.2098129 12.3811686,14.2643429 L12.3811686,14.2770244 Z M8.60490535,5.94153049 C8.60490535,5.50782611 8.89115681,5.12865181 9.33393002,4.89024122 C9.59056927,4.7520138 9.91771381,4.9143359 9.91771381,5.18445004 L9.91771381,11.6621167 C9.91771381,11.8168299 9.80490535,11.9550573 9.63851287,11.9918334 C9.46789007,12.0311458 9.3029077,12.0831396 9.14638596,12.1452786 C8.89115681,12.2467299 8.60490535,12.0844078 8.60490535,11.8345839 L8.60490535,5.94153049 Z M12.3670675,5.94153049 L12.3670675,11.8333158 C12.3670675,12.0780671 12.0892767,12.2505343 11.8410981,12.1516193 C11.6775258,12.0844078 11.5054929,12.032414 11.3278196,11.9931016 C11.1529665,11.9550573 11.0373378,11.8054166 11.0373378,11.6443627 L11.0373378,5.18191375 C11.0373378,4.9143359 11.3630722,4.75074565 11.6211216,4.88770493 C12.0709453,5.12611553 12.3670675,5.50528983 12.3670675,5.94153049 Z M10.4859864,1.48528137 C8.82911216,1.48528137 7.48528137,2.53149809 7.48528137,3.82119796 L7.48528137,14.2237624 C7.48528137,14.4786589 7.5148936,14.7335553 7.62065153,14.9757703 L9.1576668,18.7066425 C9.3522614,19.177123 9.87400053,19.4852814 10.4859864,19.4852814 C11.0979723,19.4852814 11.6197115,19.177123 11.8143061,18.7066425 L13.3513213,14.9757703 C13.4556692,14.7335553 13.4852814,14.4786589 13.4852814,14.2237624 L13.4852814,3.82119796 C13.4852814,2.53149809 12.1428607,1.48528137 10.4859864,1.48528137 Z" id="feedback" transform="translate(10.485281, 10.485281) rotate(45.000000) translate(-10.485281, -10.485281) "></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import {connect} from 'react-redux';
|
||||
import {compose} from 'redux';
|
||||
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import bindAll from 'lodash.bindall';
|
||||
@@ -27,6 +28,7 @@ import LoginDropdown from './login-dropdown.jsx';
|
||||
import SB3Downloader from '../../containers/sb3-downloader.jsx';
|
||||
import DeletionRestorer from '../../containers/deletion-restorer.jsx';
|
||||
import TurboMode from '../../containers/turbo-mode.jsx';
|
||||
import MenuBarHOC from '../../containers/menu-bar-hoc.jsx';
|
||||
|
||||
import {openTipsLibrary} from '../../reducers/modals';
|
||||
import {setPlayer} from '../../reducers/mode';
|
||||
@@ -63,7 +65,6 @@ import styles from './menu-bar.css';
|
||||
|
||||
import helpIcon from '../../lib/assets/icon--tutorials.svg';
|
||||
import mystuffIcon from './icon--mystuff.png';
|
||||
import feedbackIcon from './icon--feedback.svg';
|
||||
import profileIcon from './icon--profile.png';
|
||||
import remixIcon from './icon--remix.svg';
|
||||
import dropdownCaret from './dropdown-caret.svg';
|
||||
@@ -164,17 +165,14 @@ class MenuBar extends React.Component {
|
||||
document.removeEventListener('keydown', this.handleKeyPress);
|
||||
}
|
||||
handleClickNew () {
|
||||
let readyToReplaceProject = true;
|
||||
// if the project is dirty, and user owns the project, we will autosave.
|
||||
// but if they are not logged in and can't save, user should consider
|
||||
// downloading or logging in first.
|
||||
// Note that if user is logged in and editing someone else's project,
|
||||
// they'll lose their work.
|
||||
if (this.props.projectChanged && !this.props.canCreateNew) {
|
||||
readyToReplaceProject = confirm( // eslint-disable-line no-alert
|
||||
this.props.intl.formatMessage(sharedMessages.replaceProjectWarning)
|
||||
);
|
||||
}
|
||||
const readyToReplaceProject = this.props.confirmReadyToReplaceProject(
|
||||
this.props.intl.formatMessage(sharedMessages.replaceProjectWarning)
|
||||
);
|
||||
this.props.onRequestCloseFile();
|
||||
if (readyToReplaceProject) {
|
||||
this.props.onClickNew(this.props.canSave && this.props.canCreateNew);
|
||||
@@ -194,8 +192,8 @@ class MenuBar extends React.Component {
|
||||
this.props.onRequestCloseFile();
|
||||
}
|
||||
handleClickSeeCommunity (waitForUpdate) {
|
||||
if (this.props.canSave) { // save before transitioning to project page
|
||||
this.props.autoUpdateProject();
|
||||
if (this.props.shouldSaveBeforeTransition()) {
|
||||
this.props.autoUpdateProject(); // save before transitioning to project page
|
||||
waitForUpdate(true); // queue the transition to project page
|
||||
} else {
|
||||
waitForUpdate(false); // immediately transition to project page
|
||||
@@ -393,19 +391,17 @@ class MenuBar extends React.Component {
|
||||
</MenuSection>
|
||||
)}
|
||||
<MenuSection>
|
||||
<SBFileUploader onUpdateProjectTitle={this.props.onUpdateProjectTitle}>
|
||||
<SBFileUploader
|
||||
canSave={this.props.canSave}
|
||||
userOwnsProject={this.props.userOwnsProject}
|
||||
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
|
||||
>
|
||||
{(className, renderFileInput, loadProject) => (
|
||||
<MenuItem
|
||||
className={className}
|
||||
onClick={loadProject}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Load from your computer"
|
||||
description={
|
||||
'Menu bar item for uploading a project from your computer'
|
||||
}
|
||||
id="gui.menuBar.uploadFromComputer"
|
||||
/>
|
||||
{this.props.intl.formatMessage(sharedMessages.loadFromComputerTitle)}
|
||||
{renderFileInput()}
|
||||
</MenuItem>
|
||||
)}
|
||||
@@ -646,25 +642,6 @@ class MenuBar extends React.Component {
|
||||
) : (
|
||||
// ******** no login session is available, so don't show login stuff
|
||||
<React.Fragment>
|
||||
<div className={classNames(styles.menuBarItem, styles.feedbackButtonWrapper)}>
|
||||
<a
|
||||
className={styles.feedbackLink}
|
||||
href="https://scratch.mit.edu/discuss/topic/312261/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Button
|
||||
className={styles.feedbackButton}
|
||||
iconSrc={feedbackIcon}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Give Feedback"
|
||||
description="Label for feedback form modal button"
|
||||
id="gui.menuBar.giveFeedback"
|
||||
/>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
{this.props.showComingSoon ? (
|
||||
<React.Fragment>
|
||||
<MenuBarItemTooltip id="mystuff">
|
||||
@@ -728,6 +705,7 @@ MenuBar.propTypes = {
|
||||
canSave: PropTypes.bool,
|
||||
canShare: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
confirmReadyToReplaceProject: PropTypes.func,
|
||||
editMenuOpen: PropTypes.bool,
|
||||
enableCommunity: PropTypes.bool,
|
||||
fileMenuOpen: PropTypes.bool,
|
||||
@@ -737,6 +715,7 @@ MenuBar.propTypes = {
|
||||
isShowingProject: PropTypes.bool,
|
||||
isUpdating: PropTypes.bool,
|
||||
languageMenuOpen: PropTypes.bool,
|
||||
locale: PropTypes.string.isRequired,
|
||||
loginMenuOpen: PropTypes.bool,
|
||||
onClickAccount: PropTypes.func,
|
||||
onClickEdit: PropTypes.func,
|
||||
@@ -761,11 +740,12 @@ MenuBar.propTypes = {
|
||||
onShare: PropTypes.func,
|
||||
onToggleLoginOpen: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
projectChanged: PropTypes.bool,
|
||||
projectTitle: PropTypes.string,
|
||||
renderLogin: PropTypes.func,
|
||||
sessionExists: PropTypes.bool,
|
||||
shouldSaveBeforeTransition: PropTypes.func,
|
||||
showComingSoon: PropTypes.bool,
|
||||
userOwnsProject: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
@@ -774,7 +754,7 @@ MenuBar.defaultProps = {
|
||||
onShare: () => {}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loadingState = state.scratchGui.projectState.loadingState;
|
||||
const user = state.session && state.session.session && state.session.session.user;
|
||||
return {
|
||||
@@ -787,10 +767,11 @@ const mapStateToProps = state => {
|
||||
languageMenuOpen: languageMenuOpen(state),
|
||||
locale: state.locales.locale,
|
||||
loginMenuOpen: loginMenuOpen(state),
|
||||
projectChanged: state.scratchGui.projectChanged,
|
||||
projectTitle: state.scratchGui.projectTitle,
|
||||
sessionExists: state.session && typeof state.session.session !== 'undefined',
|
||||
username: user ? user.username : null,
|
||||
userOwnsProject: ownProps.authorUsername && user &&
|
||||
(ownProps.authorUsername === user.username),
|
||||
vm: state.scratchGui.vm
|
||||
};
|
||||
};
|
||||
@@ -815,7 +796,11 @@ const mapDispatchToProps = dispatch => ({
|
||||
onSeeCommunity: () => dispatch(setPlayer(true))
|
||||
});
|
||||
|
||||
export default injectIntl(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MenuBar));
|
||||
export default compose(
|
||||
injectIntl,
|
||||
MenuBarHOC,
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)
|
||||
)(MenuBar);
|
||||
@@ -0,0 +1,139 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
|
||||
import storage from '../../lib/storage';
|
||||
|
||||
class ScratchImage extends React.PureComponent {
|
||||
static init () {
|
||||
this._maxParallelism = 6;
|
||||
this._currentJobs = 0;
|
||||
this._pendingImages = new Set();
|
||||
}
|
||||
|
||||
static loadPendingImages () {
|
||||
if (this._currentJobs >= this._maxParallelism) {
|
||||
// already busy
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first visible image. If there aren't any, find the first non-visible image.
|
||||
let nextImage;
|
||||
for (const image of this._pendingImages) {
|
||||
if (image.isVisible) {
|
||||
nextImage = image;
|
||||
break;
|
||||
} else {
|
||||
nextImage = nextImage || image;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found an image to load:
|
||||
// 1) Remove it from the queue
|
||||
// 2) Load the image
|
||||
// 3) Pump the queue again
|
||||
if (nextImage) {
|
||||
this._pendingImages.delete(nextImage);
|
||||
const imageSource = nextImage.props.imageSource;
|
||||
++this._currentJobs;
|
||||
storage
|
||||
.load(imageSource.assetType, imageSource.assetId)
|
||||
.then(asset => {
|
||||
if (!nextImage.wasUnmounted) {
|
||||
const dataURI = asset.encodeDataURI();
|
||||
|
||||
nextImage.setState({
|
||||
imageURI: dataURI
|
||||
});
|
||||
}
|
||||
--this._currentJobs;
|
||||
this.loadPendingImages();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
Object.assign(this.state, this._loadImageSource(props.imageSource));
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const newState = this._loadImageSource(nextProps.imageSource);
|
||||
this.setState(newState);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.wasUnmounted = true;
|
||||
ScratchImage._pendingImages.delete(this);
|
||||
}
|
||||
/**
|
||||
* Calculate the state changes necessary to load the image specified in the provided source info. If the component
|
||||
* is mounted, call setState() with the return value of this function. If the component has not yet mounted, use
|
||||
* the return value of this function as initial state for the component.
|
||||
*
|
||||
* @param {object} imageSource - the new source for the image, including either assetId or URI
|
||||
* @returns {object} - the new state values, if any.
|
||||
*/
|
||||
_loadImageSource (imageSource) {
|
||||
if (imageSource) {
|
||||
if (imageSource.uri) {
|
||||
ScratchImage._pendingImages.delete(this);
|
||||
return {
|
||||
imageURI: imageSource.uri,
|
||||
lastRequestedAsset: null
|
||||
};
|
||||
}
|
||||
if (this.state.lastRequestedAsset !== imageSource.assetId) {
|
||||
ScratchImage._pendingImages.add(this);
|
||||
return {
|
||||
lastRequestedAsset: imageSource.assetId
|
||||
};
|
||||
}
|
||||
}
|
||||
// Nothing to do - don't change any state.
|
||||
return {};
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
src: _src,
|
||||
imageSource: _imageSource,
|
||||
...imgProps
|
||||
} = this.props;
|
||||
return (
|
||||
<VisibilitySensor
|
||||
intervalCheck
|
||||
scrollCheck
|
||||
>
|
||||
{
|
||||
({isVisible}) => {
|
||||
this.isVisible = isVisible;
|
||||
ScratchImage.loadPendingImages();
|
||||
return (
|
||||
<img
|
||||
src={this.state.imageURI}
|
||||
{...imgProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
</VisibilitySensor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ScratchImage.ImageSourcePropType = PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
assetId: PropTypes.string.isRequired,
|
||||
assetType: PropTypes.oneOf(Object.values(storage.AssetType)).isRequired
|
||||
}),
|
||||
PropTypes.shape({
|
||||
uri: PropTypes.string.isRequired
|
||||
})
|
||||
]);
|
||||
|
||||
ScratchImage.propTypes = {
|
||||
imageSource: ScratchImage.ImageSourcePropType.isRequired
|
||||
};
|
||||
|
||||
ScratchImage.init();
|
||||
|
||||
export default ScratchImage;
|
||||
@@ -67,7 +67,7 @@ class AutoScanningStep extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<ScanningStepComponent
|
||||
peripheralButtonImage={this.props.peripheralButtonImage}
|
||||
connectionTipIconURL={this.props.connectionTipIconURL}
|
||||
phase={this.state.phase}
|
||||
title={this.props.extensionId}
|
||||
onRefresh={this.handleRefresh}
|
||||
@@ -78,9 +78,9 @@ class AutoScanningStep extends React.Component {
|
||||
}
|
||||
|
||||
AutoScanningStep.propTypes = {
|
||||
connectionTipIconURL: PropTypes.string,
|
||||
extensionId: PropTypes.string.isRequired,
|
||||
onConnecting: PropTypes.func.isRequired,
|
||||
peripheralButtonImage: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -83,12 +83,15 @@ class Backpack extends React.Component {
|
||||
}
|
||||
handleDrop (dragInfo) {
|
||||
let payloader = null;
|
||||
let presaveAsset = null;
|
||||
switch (dragInfo.dragType) {
|
||||
case DragConstants.COSTUME:
|
||||
payloader = costumePayload;
|
||||
presaveAsset = dragInfo.payload.asset;
|
||||
break;
|
||||
case DragConstants.SOUND:
|
||||
payloader = soundPayload;
|
||||
presaveAsset = dragInfo.payload.asset;
|
||||
break;
|
||||
case DragConstants.SPRITE:
|
||||
payloader = spritePayload;
|
||||
@@ -102,6 +105,19 @@ class Backpack extends React.Component {
|
||||
// Creating the payload is async, so set loading before starting
|
||||
this.setState({loading: true}, () => {
|
||||
payloader(dragInfo.payload, this.props.vm)
|
||||
.then(payload => {
|
||||
// Force the asset to save to the asset server before storing in backpack
|
||||
// Ensures any asset present in the backpack is also on the asset server
|
||||
if (presaveAsset && !presaveAsset.clean) {
|
||||
return storage.store(
|
||||
presaveAsset.assetType,
|
||||
presaveAsset.dataFormat,
|
||||
presaveAsset.data,
|
||||
presaveAsset.assetId
|
||||
).then(() => payload);
|
||||
}
|
||||
return payload;
|
||||
})
|
||||
.then(payload => saveBackpackObject({
|
||||
host: this.props.host,
|
||||
token: this.props.token,
|
||||
|
||||
@@ -96,6 +96,21 @@ class Blocks extends React.Component {
|
||||
);
|
||||
this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
|
||||
|
||||
// Register buttons under new callback keys for creating variables,
|
||||
// lists, and procedures from extensions.
|
||||
|
||||
const toolboxWorkspace = this.workspace.getFlyout().getWorkspace();
|
||||
|
||||
const varListButtonCallback = type =>
|
||||
(() => this.ScratchBlocks.Variables.createVariable(this.workspace, null, type));
|
||||
const procButtonCallback = () => {
|
||||
this.ScratchBlocks.Procedures.createProcedureDefCallback_(this.workspace);
|
||||
};
|
||||
|
||||
toolboxWorkspace.registerButtonCallback('MAKE_A_VARIABLE', varListButtonCallback(''));
|
||||
toolboxWorkspace.registerButtonCallback('MAKE_A_LIST', varListButtonCallback('list'));
|
||||
toolboxWorkspace.registerButtonCallback('MAKE_A_PROCEDURE', procButtonCallback);
|
||||
|
||||
// Store the xml of the toolbox that is actually rendered.
|
||||
// This is used in componentDidUpdate instead of prevProps, because
|
||||
// the xml can change while e.g. on the costumes tab.
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import {
|
||||
activateDeck,
|
||||
closeCards,
|
||||
shrinkExpandCards,
|
||||
nextStep,
|
||||
prevStep,
|
||||
dragCard,
|
||||
@@ -46,6 +47,7 @@ const mapStateToProps = state => ({
|
||||
content: state.scratchGui.cards.content,
|
||||
activeDeckId: state.scratchGui.cards.activeDeckId,
|
||||
step: state.scratchGui.cards.step,
|
||||
expanded: state.scratchGui.cards.expanded,
|
||||
x: state.scratchGui.cards.x,
|
||||
y: state.scratchGui.cards.y,
|
||||
isRtl: state.locales.isRtl,
|
||||
@@ -60,6 +62,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
dispatch(closeCards());
|
||||
},
|
||||
onCloseCards: () => dispatch(closeCards()),
|
||||
onShrinkExpandCards: () => dispatch(shrinkExpandCards()),
|
||||
onNextStep: () => dispatch(nextStep()),
|
||||
onPrevStep: () => dispatch(prevStep()),
|
||||
onDrag: (e_, data) => dispatch(dragCard(data.x, data.y)),
|
||||
|
||||
@@ -107,15 +107,15 @@ class ConnectionModal extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<ConnectionModalComponent
|
||||
connectingMessage={this.state.extension.connectingMessage}
|
||||
connectingMessage={this.state.extension && this.state.extension.connectingMessage}
|
||||
connectionIconURL={this.state.extension && this.state.extension.connectionIconURL}
|
||||
connectionSmallIconURL={this.state.extension && this.state.extension.connectionSmallIconURL}
|
||||
connectionTipIconURL={this.state.extension && this.state.extension.connectionTipIconURL}
|
||||
extensionId={this.props.extensionId}
|
||||
name={this.state.extension.name}
|
||||
peripheralButtonImage={this.state.extension.peripheralButtonImage}
|
||||
peripheralImage={this.state.extension.peripheralImage}
|
||||
name={this.state.extension && this.state.extension.name}
|
||||
phase={this.state.phase}
|
||||
smallPeripheralImage={this.state.extension.smallPeripheralImage}
|
||||
title={this.props.extensionId}
|
||||
useAutoScan={this.state.extension.useAutoScan}
|
||||
useAutoScan={this.state.extension && this.state.extension.useAutoScan}
|
||||
vm={this.props.vm}
|
||||
onCancel={this.handleCancel}
|
||||
onConnected={this.handleConnected}
|
||||
|
||||
@@ -75,21 +75,17 @@ class LibraryItem extends React.PureComponent {
|
||||
const nextIconIndex = (this.state.iconIndex + 1) % this.props.icons.length;
|
||||
this.setState({iconIndex: nextIconIndex});
|
||||
}
|
||||
curIconMd5 () {
|
||||
curIconSource () {
|
||||
if (this.props.icons &&
|
||||
this.state.isRotatingIcon &&
|
||||
this.state.iconIndex < this.props.icons.length &&
|
||||
this.props.icons[this.state.iconIndex] &&
|
||||
this.props.icons[this.state.iconIndex].baseLayerMD5) {
|
||||
return this.props.icons[this.state.iconIndex].baseLayerMD5;
|
||||
this.props.icons[this.state.iconIndex]) {
|
||||
return this.props.icons[this.state.iconIndex];
|
||||
}
|
||||
return this.props.iconMd5;
|
||||
return this.props.iconSource;
|
||||
}
|
||||
render () {
|
||||
const iconMd5 = this.curIconMd5();
|
||||
const iconURL = iconMd5 ?
|
||||
`https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` :
|
||||
this.props.iconRawURL;
|
||||
const iconSource = this.curIconSource();
|
||||
return (
|
||||
<LibraryItemComponent
|
||||
bluetoothRequired={this.props.bluetoothRequired}
|
||||
@@ -99,8 +95,7 @@ class LibraryItem extends React.PureComponent {
|
||||
extensionId={this.props.extensionId}
|
||||
featured={this.props.featured}
|
||||
hidden={this.props.hidden}
|
||||
iconURL={iconURL}
|
||||
icons={this.props.icons}
|
||||
iconSource={iconSource}
|
||||
id={this.props.id}
|
||||
insetIconURL={this.props.insetIconURL}
|
||||
internetConnectionRequired={this.props.internetConnectionRequired}
|
||||
@@ -127,13 +122,8 @@ LibraryItem.propTypes = {
|
||||
extensionId: PropTypes.string,
|
||||
featured: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
iconMd5: PropTypes.string,
|
||||
iconRawURL: PropTypes.string,
|
||||
icons: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
baseLayerMD5: PropTypes.string
|
||||
})
|
||||
),
|
||||
iconSource: LibraryItemComponent.propTypes.iconSource, // single icon
|
||||
icons: PropTypes.arrayOf(LibraryItemComponent.propTypes.iconSource), // rotating icons
|
||||
id: PropTypes.number.isRequired,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import React from 'react';
|
||||
|
||||
const MenuBarHOC = function (WrappedComponent) {
|
||||
class MenuBarContainer extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
bindAll(this, [
|
||||
'confirmReadyToReplaceProject',
|
||||
'shouldSaveBeforeTransition'
|
||||
]);
|
||||
}
|
||||
confirmReadyToReplaceProject (message) {
|
||||
let readyToReplaceProject = true;
|
||||
if (this.props.projectChanged && !this.props.canCreateNew) {
|
||||
readyToReplaceProject = this.props.confirmWithMessage(message);
|
||||
}
|
||||
return readyToReplaceProject;
|
||||
}
|
||||
shouldSaveBeforeTransition () {
|
||||
return (this.props.canSave && this.props.projectChanged);
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
projectChanged,
|
||||
/* eslint-enable no-unused-vars */
|
||||
...props
|
||||
} = this.props;
|
||||
return (<WrappedComponent
|
||||
confirmReadyToReplaceProject={this.confirmReadyToReplaceProject}
|
||||
shouldSaveBeforeTransition={this.shouldSaveBeforeTransition}
|
||||
{...props}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
MenuBarContainer.propTypes = {
|
||||
canCreateNew: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
confirmWithMessage: PropTypes.func,
|
||||
projectChanged: PropTypes.bool
|
||||
};
|
||||
MenuBarContainer.defaultProps = {
|
||||
// default to using standard js confirm
|
||||
confirmWithMessage: message => (confirm(message)) // eslint-disable-line no-alert
|
||||
};
|
||||
const mapStateToProps = state => ({
|
||||
projectChanged: state.scratchGui.projectChanged
|
||||
});
|
||||
const mapDispatchToProps = () => ({});
|
||||
// Allow incoming props to override redux-provided props. Used to mock in tests.
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign(
|
||||
{}, stateProps, dispatchProps, ownProps
|
||||
);
|
||||
return connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mergeProps
|
||||
)(MenuBarContainer);
|
||||
};
|
||||
|
||||
export default MenuBarHOC;
|
||||
@@ -94,21 +94,28 @@ class SBFileUploader extends React.Component {
|
||||
intl,
|
||||
isShowingWithoutId,
|
||||
loadingState,
|
||||
projectChanged
|
||||
projectChanged,
|
||||
userOwnsProject
|
||||
} = this.props;
|
||||
|
||||
const thisFileInput = e.target;
|
||||
if (thisFileInput.files) { // Don't attempt to load if no file was selected
|
||||
this.fileToUpload = thisFileInput.files[0];
|
||||
|
||||
// Allow upload to continue only after confirmation if the project
|
||||
// has changed and is not showing with ID. If it has an ID, this operation
|
||||
// does not currently overwrite that project, so it is safe to do without confirmation.
|
||||
const uploadAllowed = (isShowingWithoutId && projectChanged) ?
|
||||
confirm(intl.formatMessage(sharedMessages.replaceProjectWarning)) : // eslint-disable-line no-alert
|
||||
true;
|
||||
|
||||
if (uploadAllowed) this.props.requestProjectUpload(loadingState);
|
||||
// If user owns the project, or user has changed the project,
|
||||
// we must confirm with the user that they really intend to replace it.
|
||||
// (If they don't own the project and haven't changed it, no need to confirm.)
|
||||
let uploadAllowed = true;
|
||||
if (userOwnsProject || (projectChanged && isShowingWithoutId)) {
|
||||
uploadAllowed = confirm( // eslint-disable-line no-alert
|
||||
intl.formatMessage(sharedMessages.replaceProjectWarning)
|
||||
);
|
||||
}
|
||||
if (uploadAllowed) {
|
||||
this.props.requestProjectUpload(loadingState);
|
||||
} else {
|
||||
this.props.closeFileMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
// called when file upload raw data is available in the reader
|
||||
@@ -118,12 +125,6 @@ class SBFileUploader extends React.Component {
|
||||
const filename = this.fileToUpload && this.fileToUpload.name;
|
||||
this.props.vm.loadProject(this.reader.result)
|
||||
.then(() => {
|
||||
// Remove the hash if any (without triggering a hash change event or a reload)
|
||||
try { // Can fail e.g. when GUI is loaded from static file (integration tests)
|
||||
history.replaceState({}, document.title, '.');
|
||||
} catch {
|
||||
// No fallback, just do not trigger promise catch below
|
||||
}
|
||||
this.props.onLoadingFinished(this.props.loadingState, true);
|
||||
// Reset the file input after project is loaded
|
||||
// This is necessary in case the user wants to reload a project
|
||||
@@ -170,6 +171,7 @@ SBFileUploader.propTypes = {
|
||||
canSave: PropTypes.bool, // eslint-disable-line react/no-unused-prop-types
|
||||
children: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
closeFileMenu: PropTypes.func,
|
||||
intl: intlShape.isRequired,
|
||||
isLoadingUpload: PropTypes.bool,
|
||||
isShowingWithoutId: PropTypes.bool,
|
||||
@@ -179,6 +181,7 @@ SBFileUploader.propTypes = {
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
projectChanged: PropTypes.bool,
|
||||
requestProjectUpload: PropTypes.func,
|
||||
userOwnsProject: PropTypes.bool,
|
||||
vm: PropTypes.shape({
|
||||
loadProject: PropTypes.func
|
||||
})
|
||||
@@ -198,6 +201,7 @@ const mapStateToProps = state => {
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
closeFileMenu: () => dispatch(closeFileMenu()),
|
||||
onLoadingFinished: (loadingState, success) => {
|
||||
dispatch(onLoadedProject(loadingState, ownProps.canSave, success));
|
||||
dispatch(closeLoadingProject());
|
||||
|
||||
@@ -54,10 +54,10 @@ class ScanningStep extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<ScanningStepComponent
|
||||
connectionSmallIconURL={this.props.connectionSmallIconURL}
|
||||
peripheralList={this.state.peripheralList}
|
||||
phase={this.state.phase}
|
||||
scanning={this.state.scanning}
|
||||
smallPeripheralImage={this.props.smallPeripheralImage}
|
||||
title={this.props.extensionId}
|
||||
onConnected={this.props.onConnected}
|
||||
onConnecting={this.props.onConnecting}
|
||||
@@ -68,10 +68,10 @@ class ScanningStep extends React.Component {
|
||||
}
|
||||
|
||||
ScanningStep.propTypes = {
|
||||
connectionSmallIconURL: PropTypes.string,
|
||||
extensionId: PropTypes.string.isRequired,
|
||||
onConnected: PropTypes.func.isRequired,
|
||||
onConnecting: PropTypes.func.isRequired,
|
||||
smallPeripheralImage: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -423,6 +423,7 @@ class Stage extends React.Component {
|
||||
Stage.propTypes = {
|
||||
isColorPicking: PropTypes.bool,
|
||||
isFullScreen: PropTypes.bool.isRequired,
|
||||
isStarted: PropTypes.bool,
|
||||
micIndicator: PropTypes.bool,
|
||||
onActivateColorPicker: PropTypes.func,
|
||||
onDeactivateColorPicker: PropTypes.func,
|
||||
|
||||
@@ -113,7 +113,8 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
|
||||
}
|
||||
AppStateWrapper.propTypes = {
|
||||
isFullScreen: PropTypes.bool,
|
||||
isPlayerOnly: PropTypes.bool
|
||||
isPlayerOnly: PropTypes.bool,
|
||||
showTelemetryModal: PropTypes.bool
|
||||
};
|
||||
return AppStateWrapper;
|
||||
};
|
||||
|
||||
@@ -246,10 +246,7 @@ export default function (vm) {
|
||||
lookupBlocks = vm.runtime.flyoutBlocks;
|
||||
}
|
||||
const sort = function (options) {
|
||||
options.sort((str1, str2) => str1.localeCompare(str2, [], {
|
||||
sensitivity: 'base',
|
||||
numeric: true
|
||||
}));
|
||||
options.sort(ScratchBlocks.scratchBlocksUtils.compareStrings);
|
||||
};
|
||||
// Get all the stage variables (no lists) so we can add them to menu when the stage is selected.
|
||||
const stageVariableOptions = vm.runtime.getTargetForStage().getAllVariableNamesInScopeByType('');
|
||||
@@ -323,5 +320,26 @@ export default function (vm) {
|
||||
vm.runtime.emit('PLAY_NOTE', noteNum, extensionId);
|
||||
};
|
||||
|
||||
// Use a collator's compare instead of localeCompare which internally
|
||||
// creates a collator. Using this is a lot faster in browsers that create a
|
||||
// collator for every localeCompare call.
|
||||
const collator = new Intl.Collator([], {
|
||||
sensitivity: 'base',
|
||||
numeric: true
|
||||
});
|
||||
ScratchBlocks.scratchBlocksUtils.compareStrings = function (str1, str2) {
|
||||
return collator.compare(str1, str2);
|
||||
};
|
||||
|
||||
// Blocks wants to know if 3D CSS transforms are supported. The cross
|
||||
// section of browsers Scratch supports and browsers that support 3D CSS
|
||||
// transforms will make the return always true.
|
||||
//
|
||||
// Shortcutting to true lets us skip an expensive style recalculation when
|
||||
// first loading the Scratch editor.
|
||||
ScratchBlocks.utils.is3dSupported = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
return ScratchBlocks;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg width="204" height="92" viewBox="0 0 204 92" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>@-webkit-keyframes wiggle{0%{transform:translate(0,-10px)}to{transform:translate(0,0)}}@keyframes wiggle{0%{transform:translate(0,-10px)}to{transform:translate(0,0)}}</style><defs><path d="M66.5 45c-.7 0-1.3-.2-1.7-.7l-9.1-8.8c-.7-.7-.9-1.7-.5-2.6.4-.8 1.2-1.4 2.2-1.4H61l1.7-12.2a4 4 0 0 1 4.5-3.3 4 4 0 0 1 3.3 3.3l1.8 12.2h3.3c1 0 2 .6 2.3 1.5.4.9.2 1.8-.5 2.5l-9.1 8.8c-.5.5-1 .7-1.7.7z" id="path-1"/><filter x="-15.2%" y="-12.1%" width="130.4%" height="124.1%" filterUnits="objectBoundingBox" id="filter-2"><feMorphology radius="1.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"/><feOffset in="shadowSpreadOuter1" result="shadowOffsetOuter1"/><feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/><feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.25 0" in="shadowOffsetOuter1"/></filter></defs><g id="boost-button-illustration" fill="none" fill-rule="evenodd"><g id="boost-top" transform="translate(4 4)" fill-rule="nonzero"><path d="M94 84H2a2 2 0 0 1-2-2V68h96v14a2 2 0 0 1-2 2z" id="Path" stroke="#7C87A5" fill="#FFF" stroke-linecap="round" stroke-linejoin="round"/><path id="Rectangle" stroke="#7C87A5" fill="#FFF" stroke-linecap="round" stroke-linejoin="round" transform="rotate(-90 98 42)" d="M72-56h52v196H72z"/><g id="Group" transform="translate(54 34)"><g opacity=".1" fill="#231F20"><path d="M4 15a3 3 0 0 1-3-3V4a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H4z" id="Path"/><path d="M12 2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4c0-1.1.9-2 2-2h8zm0-2H4a4 4 0 0 0-4 4v8a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z" id="Shape"/></g><path d="M2 4v8c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2z" id="Path" stroke="#389438" fill="#46B946" stroke-linecap="round" stroke-linejoin="round"/></g><path id="Rectangle" stroke="#231F20" fill="#231F20" opacity=".1" stroke-linecap="round" stroke-linejoin="round" transform="rotate(-90 86 42)" d="M84 32h4v20h-4z"/><g id="Group" transform="translate(76 16)" opacity=".5" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10H2a2 2 0 0 1-2-2V0h20v8a2 2 0 0 1-2 2h-2" id="Path" fill="#FFF"/><path id="Path" fill="#7C87A5" d="M14 12l-2-4-2 4zM6 8l2 4 2-4z"/></g><g id="Group" transform="translate(76 56)" opacity=".5" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2H2a2 2 0 0 0-2 2v8h20V4a2 2 0 0 0-2-2h-2" id="Path" fill="#FFF"/><path id="Path" fill="#7C87A5" d="M14 0l-2 4-2-4zM6 4l2-4 2 4z"/></g><g id="Group" opacity=".5"><g transform="translate(4 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(16 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(28 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(40 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(52 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(64 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g></g><g id="Group" opacity=".5"><g transform="translate(124 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(136 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(100 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(112 20)"><circle id="Oval" fill="#FFF" cx="4" cy="4" r="3.5"/><path d="M4 1a3 3 0 0 1 3 3 3 3 0 0 1-3 3 3 3 0 0 1-3-3 3 3 0 0 1 3-3zm0-1a4 4 0 1 0 0 8 4 4 0 0 0 0-8z" id="Shape" fill="#7C87A5"/></g><g transform="translate(148 20)"><circle id="Oval" fill="#FFF" cx="4"Line truncated
|
||||
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="204" height="72" viewBox="0 0 204 72" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path stroke="#7C87A5" fill="#FFF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" d="M100 24h100v40H100z"/><path d="M76 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M73.3 4v4H76V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M76 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M64 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M61.3 4v4H64V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M64 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M52 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M49.3 4v4H52V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M52 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M40 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M37.3 4v4H40V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M40 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M25.3 4v4H28V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 8H8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M13.3 4v4H16V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M16 8H8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M172 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M169.3 4v4h2.7V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M172 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M160 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M157.3 4v4h2.7V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M160 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M148 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M145.3 4v4h2.7V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M148 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M136 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M133.3 4v4h2.7V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M136 8h-8V5c0-.6.5-1 1-1h6c.5 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M124 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M121.3 4v4h2.7V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M124 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M112 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" fill="#FFF" fill-rule="nonzero"/><path d="M109.3 4v4h2.7V4.7c0-.4-.4-.7-1-.7h-1.7z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/><path d="M112 8h-8V5c0-.6.5-1 1-1h6c.6 0 1 .5 1 1v3z" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><path d="M198 8H6a2 2 0 0 0-2 2v14h196V10a2 2 0 0 0-2-2z" stroke="#7C87A5" fill="#FFF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"/><g stroke-linecap="round" stroke-linejoin="round"><path stroke="#7C87A5" fill="#E6E7E8" fill-rule="nonzero" d="M100 20h-2v-2H82v2h-2V10h20z"/><path stroke="#7C87A5" fill="#7C87A5" filLine truncated
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="76" height="133" xmlns="http://www.w3.org/2000/svg"><g transform="translate(2 2)" fill="none" fill-rule="evenodd"><g fill="#414757" fill-rule="nonzero" stroke="#414757" stroke-linecap="round" stroke-linejoin="round"><path d="M42 0v3H30V0h4.5v1.5h3V0zM34.5 3h3v6h-3zM34.5 3H33l.8.8-.8.7.8.8-.8.7h1.5zM34.5 6H33l.8.8-.8.7.8.8-.8.7h1.5z"/><path d="M37.5 2.3H39l-.8.7.8.8-.8.7.8.8h-1.5zM37.5 5.3H39l-.8.7.8.8-.8.7.8.8-.8.7h-.7zM34.5 3h3v6h-3zM34.5 3H33l.8.8-.8.7.8.8-.8.7h1.5zM34.5 6H33l.8.8-.8.7.8.8-.8.7h1.5z"/><path d="M37.5 2.3H39l-.8.7.8.8-.8.7.8.8h-1.5zM37.5 5.3H39l-.8.7.8.8-.8.7.8.8-.8.7h-.7z"/></g><g fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"><path d="M36 129a7.5 7.5 0 0 1-7.5-7.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5a4.5 4.5 0 0 0 9 0v-.5c0-1.2-.5-2.4-1.3-3.2l-2.5-2.5a7.4 7.4 0 0 1-2.2-5.3v-3.5c0-.8.7-1.5 1.5-1.5s1.5.7 1.5 1.5v3.5c0 1.2.5 2.4 1.3 3.2l2.5 2.5a7.4 7.4 0 0 1 2.2 5.3v.5c0 4.1-3.4 7.5-7.5 7.5zM31.5 93h9v8.3h-9z" stroke="#7C87A5" fill="#E6E7E8"/><path stroke="#231F20" fill="#231F20" opacity=".1" d="M31.5 93h2.3v8.3h-2.3zM38.3 93h2.3v8.3h-2.3z"/><path d="M42 105.8v1.4c0 .5-.3.8-.8.8H30.8a.8.8 0 0 1-.7-.8v-1.4c0-.5.3-.8.8-.8h1c.7 0 1.2-.5 1.2-1.1 0-.6-.5-1.1-1.1-1.1h-.4v-1.5h9v1.5h-.4c-.6 0-1.1.5-1.1 1 0 .7.5 1.2 1.1 1.2h1.1c.5 0 .8.3.8.8z" stroke="#7C87A5" fill="#8592AF"/><path d="M31.9 105c.6 0 1.1-.5 1.1-1.1 0-.6-.5-1.1-1.1-1.1h-.4 9-.4c-.6 0-1.1.5-1.1 1 0 .7.5 1.2 1.1 1.2H32z" stroke="#7C87A5" fill="#7C87A5"/></g><path d="M70.5 9h-69C.7 9 0 9.7 0 10.5v84c0 .8.7 1.5 1.5 1.5h69c.8 0 1.5-.7 1.5-1.5v-84c0-.8-.7-1.5-1.5-1.5zM39.9 39.2a4.2 4.2 0 0 1-7.8 0l-2.3-6.7a6.5 6.5 0 1 1 12.4 0l-2.3 6.7z" stroke="#7C87A5" fill="#8592AF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 15v75h60V15H6zm34 24.2a4.2 4.2 0 0 1-8 0l-2.2-6.7a6.5 6.5 0 1 1 12.4 0l-2.3 6.7z" stroke="#7C87A5" fill="#8592AF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 18v69h54V18H9zm27 6c4.4 0 7.6 4.3 6.2 8.5l-2.3 6.7a4.2 4.2 0 0 1-7.8 0l-2.3-6.7A6.5 6.5 0 0 1 36 24z" stroke="#7C87A5" fill="#5CB1D6" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#7C87A5" fill="#8592AF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" d="M63 87H9V69l54-12z"/><path d="M6 15L.7 9.7M66 15l5.3-5.3M.8 95.3L6 90M71.3 95.3L66 90M15 91.5V93M57 91.5V93M15 12v1.5M57 12v1.5M69 27h-1.5M69 81h-1.5M4.5 27H3M4.5 81H3" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><circle fill="#FFBF00" fill-rule="nonzero" cx="36" cy="52.5" r="4.5"/><circle stroke="#7C87A5" fill="#8592AF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" cx="25.5" cy="52.5" r="1.5"/><circle stroke="#7C87A5" fill="#8592AF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round" cx="46.5" cy="52.5" r="1.5"/><path d="M46.5 75h-21c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5h21c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5zM52.5 81h-33c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5h33c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5z" fill="#FFBF00" fill-rule="nonzero"/><path d="M1.3 94.7L6 90h60V15l4.7-4.7a.8.8 0 0 1 1.3.5v83.7c0 .8-.7 1.5-1.5 1.5H1.8c-.7 0-1-.8-.5-1.3z" stroke="#231F20" fill="#231F20" fill-rule="nonzero" opacity=".1" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><g transform="translate(8 .5)" fill="none" fill-rule="evenodd"><path d="M12 39a3 3 0 0 1-3-3c0-.6.4-1 1-1 .5 0 1 .4 1 1a1 1 0 1 0 2 0v-.2a1 1 0 0 0-.3-.7l-.8-.8a3 3 0 0 1-1-2.1V30c0-.6.5-1 1-1 .6 0 1 .4 1 1v2.2l.4.7.8.8c.6.6.9 1.3.9 2.1v.2a3 3 0 0 1-3 3z" stroke-opacity=".1" stroke="#000" fill="#E6E7E8"/><path d="M23.7.3A1 1 0 0 0 23 0H1a1 1 0 0 0-.7.3A1 1 0 0 0 0 1v26c0 .3.1.5.3.7.2.2.4.3.7.3h22c.3 0 .5-.1.7-.3.2-.2.3-.4.3-.7V1a1 1 0 0 0-.3-.7zM12 4c2 0 3.3 2 2.6 3.8L13.3 11a1.4 1.4 0 0 1-2.7 0L9.5 7.7l-.2-1C9.2 5.4 10.4 4 12 4z" stroke="#7C87A5" fill="#8592AF" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 2v24h20V2H2zm10 2c2 0 3.3 2 2.6 3.8L13.3 11a1.4 1.4 0 0 1-2.7 0L9.5 7.7l-.2-1C9.2 5.4 10.4 4 12 4z" stroke="#7C87A5" fill="#5CB1D6" fill-rule="nonzero" stroke-linecap="round" stroke-linejoin="round"/><path stroke="#7C87A5" fill="#8592AF" stroke-linecap="round" stroke-linejoin="round" d="M22 26H2v-6l20-4z"/><path d="M.3 27.7L2 26M.3.3L2 2M22 2L23.7.3M23.7 27.7L22 26" stroke="#7C87A5" stroke-linecap="round" stroke-linejoin="round"/><circle fill="#FFBF00" cx="12" cy="14.8" r="1.2"/><path stroke="#7C87A5" fill="#E6E7E8" stroke-linecap="round" stroke-linejoin="round" d="M10 28h4v4h-4z"/><path d="M15.5 22h-7a.5.5 0 0 1-.5-.5c0-.3.2-.5.5-.5h7c.3 0 .5.2.5.5s-.2.5-.5.5zM17.5 24h-11a.5.5 0 0 1-.5-.5c0-.3.2-.5.5-.5h11c.3 0 .5.2.5.5s-.2.5-.5.5z" fill="#FFBF00"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -1,31 +1,50 @@
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import musicImage from './music.png';
|
||||
import musicInsetImage from './music-small.svg';
|
||||
import penImage from './pen.png';
|
||||
import penInsetImage from './pen-small.svg';
|
||||
import videoImage from './video-sensing.png';
|
||||
import videoInsetImage from './video-sensing-small.svg';
|
||||
import translateImage from './translate.png';
|
||||
import translateInsetImage from './translate-small.png';
|
||||
import microbitImage from './microbit.png';
|
||||
import ev3Image from './ev3.png';
|
||||
import wedoImage from './wedo.png';
|
||||
import text2speechImage from './text2speech.png';
|
||||
import text2speechInsetImage from './text2speech-small.svg';
|
||||
import makeymakeyImage from './makeymakey.png';
|
||||
import makeymakeyInsetImage from './makeymakey-small.svg';
|
||||
import boostImage from './boost.png';
|
||||
import boostInsetImage from './boost-small.svg';
|
||||
import musicIconURL from './music/music.png';
|
||||
import musicInsetIconURL from './music/music-small.svg';
|
||||
|
||||
import microbitPeripheralImage from './peripheral-connection/microbit/microbit-illustration.svg';
|
||||
import microbitMenuImage from './peripheral-connection/microbit/microbit-small.svg';
|
||||
import ev3PeripheralImage from './peripheral-connection/ev3/ev3-hub-illustration.svg';
|
||||
import ev3MenuImage from './peripheral-connection/ev3/ev3-small.svg';
|
||||
import wedoPeripheralImage from './peripheral-connection/wedo/wedo-illustration.svg';
|
||||
import wedoMenuImage from './peripheral-connection/wedo/wedo-small.svg';
|
||||
import wedoButtonImage from './peripheral-connection/wedo/wedo-button-illustration.svg';
|
||||
import penIconURL from './pen/pen.png';
|
||||
import penInsetIconURL from './pen/pen-small.svg';
|
||||
|
||||
import videoSensingIconURL from './videoSensing/video-sensing.png';
|
||||
import videoSensingInsetIconURL from './videoSensing/video-sensing-small.svg';
|
||||
|
||||
import text2speechIconURL from './text2speech/text2speech.png';
|
||||
import text2speechInsetIconURL from './text2speech/text2speech-small.svg';
|
||||
|
||||
import translateIconURL from './translate/translate.png';
|
||||
import translateInsetIconURL from './translate/translate-small.png';
|
||||
|
||||
import makeymakeyIconURL from './makeymakey/makeymakey.png';
|
||||
import makeymakeyInsetIconURL from './makeymakey/makeymakey-small.svg';
|
||||
|
||||
import microbitIconURL from './microbit/microbit.png';
|
||||
import microbitInsetIconURL from './microbit/microbit-small.svg';
|
||||
import microbitConnectionIconURL from './microbit/microbit-illustration.svg';
|
||||
import microbitConnectionSmallIconURL from './microbit/microbit-small.svg';
|
||||
|
||||
import ev3IconURL from './ev3/ev3.png';
|
||||
import ev3InsetIconURL from './ev3/ev3-small.svg';
|
||||
import ev3ConnectionIconURL from './ev3/ev3-hub-illustration.svg';
|
||||
import ev3ConnectionSmallIconURL from './ev3/ev3-small.svg';
|
||||
|
||||
import wedo2IconURL from './wedo2/wedo.png'; // TODO: Rename file names to match variable/prop names?
|
||||
import wedo2InsetIconURL from './wedo2/wedo-small.svg';
|
||||
import wedo2ConnectionIconURL from './wedo2/wedo-illustration.svg';
|
||||
import wedo2ConnectionSmallIconURL from './wedo2/wedo-small.svg';
|
||||
import wedo2ConnectionTipIconURL from './wedo2/wedo-button-illustration.svg';
|
||||
|
||||
import boostIconURL from './boost/boost.png';
|
||||
import boostInsetIconURL from './boost/boost-small.svg';
|
||||
import boostConnectionIconURL from './boost/boost-illustration.svg';
|
||||
import boostConnectionSmallIconURL from './boost/boost-small.svg';
|
||||
import boostConnectionTipIconURL from './boost/boost-button-illustration.svg';
|
||||
|
||||
import gdxforIconURL from './gdxfor/gdxfor.png';
|
||||
import gdxforInsetIconURL from './gdxfor/gdxfor-small.svg';
|
||||
import gdxforConnectionIconURL from './gdxfor/gdxfor-illustration.svg';
|
||||
import gdxforConnectionSmallIconURL from './gdxfor/gdxfor-small.svg';
|
||||
|
||||
export default [
|
||||
{
|
||||
@@ -37,8 +56,8 @@ export default [
|
||||
/>
|
||||
),
|
||||
extensionId: 'music',
|
||||
iconURL: musicImage,
|
||||
insetIconURL: musicInsetImage,
|
||||
iconURL: musicIconURL,
|
||||
insetIconURL: musicInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Play instruments and drums."
|
||||
@@ -57,8 +76,8 @@ export default [
|
||||
/>
|
||||
),
|
||||
extensionId: 'pen',
|
||||
iconURL: penImage,
|
||||
insetIconURL: penInsetImage,
|
||||
iconURL: penIconURL,
|
||||
insetIconURL: penInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Draw with your sprites."
|
||||
@@ -77,8 +96,8 @@ export default [
|
||||
/>
|
||||
),
|
||||
extensionId: 'videoSensing',
|
||||
iconURL: videoImage,
|
||||
insetIconURL: videoInsetImage,
|
||||
iconURL: videoSensingIconURL,
|
||||
insetIconURL: videoSensingInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Sense motion with the camera."
|
||||
@@ -98,8 +117,8 @@ export default [
|
||||
),
|
||||
extensionId: 'text2speech',
|
||||
collaborator: 'Amazon Web Services',
|
||||
iconURL: text2speechImage,
|
||||
insetIconURL: text2speechInsetImage,
|
||||
iconURL: text2speechIconURL,
|
||||
insetIconURL: text2speechInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Make your projects talk."
|
||||
@@ -120,8 +139,8 @@ export default [
|
||||
),
|
||||
extensionId: 'translate',
|
||||
collaborator: 'Google',
|
||||
iconURL: translateImage,
|
||||
insetIconURL: translateInsetImage,
|
||||
iconURL: translateIconURL,
|
||||
insetIconURL: translateInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Translate text into many languages."
|
||||
@@ -136,8 +155,8 @@ export default [
|
||||
name: 'Makey Makey',
|
||||
extensionId: 'makeymakey',
|
||||
collaborator: 'JoyLabz',
|
||||
iconURL: makeymakeyImage,
|
||||
insetIconURL: makeymakeyInsetImage,
|
||||
iconURL: makeymakeyIconURL,
|
||||
insetIconURL: makeymakeyInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Make anything into a key."
|
||||
@@ -151,8 +170,8 @@ export default [
|
||||
name: 'micro:bit',
|
||||
extensionId: 'microbit',
|
||||
collaborator: 'micro:bit',
|
||||
iconURL: microbitImage,
|
||||
insetIconURL: microbitMenuImage,
|
||||
iconURL: microbitIconURL,
|
||||
insetIconURL: microbitInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Connect your projects with the world."
|
||||
@@ -166,8 +185,8 @@ export default [
|
||||
internetConnectionRequired: true,
|
||||
launchPeripheralConnectionFlow: true,
|
||||
useAutoScan: false,
|
||||
peripheralImage: microbitPeripheralImage,
|
||||
smallPeripheralImage: microbitMenuImage,
|
||||
connectionIconURL: microbitConnectionIconURL,
|
||||
connectionSmallIconURL: microbitConnectionSmallIconURL,
|
||||
connectingMessage: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Connecting"
|
||||
@@ -181,8 +200,8 @@ export default [
|
||||
name: 'LEGO MINDSTORMS EV3',
|
||||
extensionId: 'ev3',
|
||||
collaborator: 'LEGO',
|
||||
iconURL: ev3Image,
|
||||
insetIconURL: ev3MenuImage,
|
||||
iconURL: ev3IconURL,
|
||||
insetIconURL: ev3InsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Build interactive robots and more."
|
||||
@@ -196,8 +215,8 @@ export default [
|
||||
internetConnectionRequired: true,
|
||||
launchPeripheralConnectionFlow: true,
|
||||
useAutoScan: false,
|
||||
peripheralImage: ev3PeripheralImage,
|
||||
smallPeripheralImage: ev3MenuImage,
|
||||
connectionIconURL: ev3ConnectionIconURL,
|
||||
connectionSmallIconURL: ev3ConnectionSmallIconURL,
|
||||
connectingMessage: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Connecting. Make sure the pin on your EV3 is set to 1234."
|
||||
@@ -207,12 +226,43 @@ export default [
|
||||
),
|
||||
helpLink: 'https://scratch.mit.edu/ev3'
|
||||
},
|
||||
{
|
||||
name: 'LEGO BOOST',
|
||||
extensionId: 'boost',
|
||||
collaborator: 'LEGO',
|
||||
iconURL: boostIconURL,
|
||||
insetIconURL: boostInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Bring robotic creations to life."
|
||||
description="Description for the 'LEGO BOOST' extension"
|
||||
id="gui.extension.boost.description"
|
||||
/>
|
||||
),
|
||||
featured: true,
|
||||
disabled: false,
|
||||
bluetoothRequired: true,
|
||||
internetConnectionRequired: true,
|
||||
launchPeripheralConnectionFlow: true,
|
||||
useAutoScan: true,
|
||||
connectionIconURL: boostConnectionIconURL,
|
||||
connectionSmallIconURL: boostConnectionSmallIconURL,
|
||||
connectionTipIconURL: boostConnectionTipIconURL,
|
||||
connectingMessage: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Connecting"
|
||||
description="Message to help people connect to their BOOST."
|
||||
id="gui.extension.boost.connectingMessage"
|
||||
/>
|
||||
),
|
||||
helpLink: 'https://scratch.mit.edu/boost'
|
||||
},
|
||||
{
|
||||
name: 'LEGO Education WeDo 2.0',
|
||||
extensionId: 'wedo2',
|
||||
collaborator: 'LEGO',
|
||||
iconURL: wedoImage,
|
||||
insetIconURL: wedoMenuImage,
|
||||
iconURL: wedo2IconURL,
|
||||
insetIconURL: wedo2InsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Build with motors and sensors."
|
||||
@@ -226,9 +276,9 @@ export default [
|
||||
internetConnectionRequired: true,
|
||||
launchPeripheralConnectionFlow: true,
|
||||
useAutoScan: true,
|
||||
peripheralImage: wedoPeripheralImage,
|
||||
smallPeripheralImage: wedoMenuImage,
|
||||
peripheralButtonImage: wedoButtonImage,
|
||||
connectionIconURL: wedo2ConnectionIconURL,
|
||||
connectionSmallIconURL: wedo2ConnectionSmallIconURL,
|
||||
connectionTipIconURL: wedo2ConnectionTipIconURL,
|
||||
connectingMessage: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Connecting"
|
||||
@@ -239,21 +289,33 @@ export default [
|
||||
helpLink: 'https://scratch.mit.edu/wedo'
|
||||
},
|
||||
{
|
||||
name: 'LEGO BOOST',
|
||||
extensionId: 'boost',
|
||||
collaborator: 'LEGO',
|
||||
iconURL: boostImage,
|
||||
insetIconURL: boostInsetImage,
|
||||
name: 'Go Direct Force & Acceleration',
|
||||
extensionId: 'gdxfor',
|
||||
collaborator: 'Vernier',
|
||||
iconURL: gdxforIconURL,
|
||||
insetIconURL: gdxforInsetIconURL,
|
||||
description: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Bring robotic creations to life."
|
||||
description="Description for the 'LEGO BOOST' extension"
|
||||
id="gui.extension.boost.description"
|
||||
defaultMessage="Sense push, pull, motion, and spin."
|
||||
description="Description for the Vernier Go Direct Force and Acceleration sensor extension"
|
||||
id="gui.extension.gdxfor.description"
|
||||
/>
|
||||
),
|
||||
featured: true,
|
||||
disabled: true,
|
||||
disabled: false,
|
||||
bluetoothRequired: true,
|
||||
internetConnectionRequired: true
|
||||
internetConnectionRequired: true,
|
||||
launchPeripheralConnectionFlow: true,
|
||||
useAutoScan: false,
|
||||
connectionIconURL: gdxforConnectionIconURL,
|
||||
connectionSmallIconURL: gdxforConnectionSmallIconURL,
|
||||
connectingMessage: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Connecting"
|
||||
description="Message to help people connect to their force and acceleration sensor."
|
||||
id="gui.extension.gdxfor.connectingMessage"
|
||||
/>
|
||||
),
|
||||
helpLink: 'https://scratch.mit.edu/vernier'
|
||||
}
|
||||
];
|
||||
@@ -115,8 +115,10 @@ const ProjectFetcherHOC = function (WrappedComponent) {
|
||||
assetHost: PropTypes.string,
|
||||
canSave: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
isCreatingNew: PropTypes.bool,
|
||||
isFetchingWithId: PropTypes.bool,
|
||||
isLoadingProject: PropTypes.bool,
|
||||
isShowingProject: PropTypes.bool,
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
onActivateTab: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import queryString from 'query-string';
|
||||
import {connect} from 'react-redux';
|
||||
import VM from 'scratch-vm';
|
||||
import xhr from 'xhr';
|
||||
|
||||
import collectMetadata from '../lib/collect-metadata';
|
||||
import log from '../lib/log';
|
||||
import storage from '../lib/storage';
|
||||
import dataURItoBlob from '../lib/data-uri-to-blob';
|
||||
import saveProjectToServer from '../lib/save-project-to-server';
|
||||
|
||||
import {
|
||||
showAlertWithTimeout,
|
||||
@@ -60,7 +59,12 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
// but then it'd be hard to turn this listening off in our tests
|
||||
window.onbeforeunload = e => this.leavePageConfirm(e);
|
||||
}
|
||||
|
||||
// Allow the GUI consumer to pass in a function to receive a trigger
|
||||
// for triggering thumbnail or whole project saves.
|
||||
// These functions are called with null on unmount to prevent stale references.
|
||||
this.props.onSetProjectThumbnailer(this.getProjectThumbnail);
|
||||
this.props.onSetProjectSaver(this.tryToAutoSave);
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!this.props.isAnyCreatingNewState && prevProps.isAnyCreatingNewState) {
|
||||
@@ -117,6 +121,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
// window.onbeforeunload = undefined; // eslint-disable-line no-undefined
|
||||
// Remove project thumbnailer function since the components are unmounting
|
||||
this.props.onSetProjectThumbnailer(null);
|
||||
this.props.onSetProjectSaver(null);
|
||||
}
|
||||
leavePageConfirm (e) {
|
||||
if (this.props.projectChanged) {
|
||||
@@ -176,8 +181,8 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
createCopyToStorage () {
|
||||
this.props.onShowCreatingCopyAlert();
|
||||
return this.storeProject(null, {
|
||||
original_id: this.props.reduxProjectId,
|
||||
is_copy: 1,
|
||||
originalId: this.props.reduxProjectId,
|
||||
isCopy: 1,
|
||||
title: this.props.reduxProjectTitle
|
||||
})
|
||||
.then(response => {
|
||||
@@ -192,8 +197,8 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
createRemixToStorage () {
|
||||
this.props.onShowCreatingRemixAlert();
|
||||
return this.storeProject(null, {
|
||||
original_id: this.props.reduxProjectId,
|
||||
is_remix: 1,
|
||||
originalId: this.props.reduxProjectId,
|
||||
isRemix: 1,
|
||||
title: this.props.reduxProjectTitle
|
||||
})
|
||||
.then(response => {
|
||||
@@ -233,47 +238,8 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
() => (asset.clean = true)
|
||||
)
|
||||
)
|
||||
).then(() => {
|
||||
const opts = {
|
||||
body: savedVMState,
|
||||
// If we set json:true then the body is double-stringified, so don't
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
};
|
||||
const creatingProject = projectId === null || typeof projectId === 'undefined';
|
||||
let qs = queryString.stringify(requestParams);
|
||||
if (qs) qs = `?${qs}`;
|
||||
if (creatingProject) {
|
||||
Object.assign(opts, {
|
||||
method: 'post',
|
||||
url: `${storage.projectHost}/${qs}`
|
||||
});
|
||||
} else {
|
||||
Object.assign(opts, {
|
||||
method: 'put',
|
||||
url: `${storage.projectHost}/${projectId}${qs}`
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr(opts, (err, response) => {
|
||||
if (err) return reject(err);
|
||||
let body;
|
||||
try {
|
||||
// Since we didn't set json: true, we have to parse manually
|
||||
body = JSON.parse(response.body);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
body.id = projectId;
|
||||
if (creatingProject) {
|
||||
body.id = body['content-name'];
|
||||
}
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
})
|
||||
)
|
||||
.then(() => this.props.onUpdateProjectData(projectId, savedVMState, requestParams))
|
||||
.then(response => {
|
||||
this.props.onSetProjectUnchanged();
|
||||
const id = response.id.toString();
|
||||
@@ -350,6 +316,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
onRemixing,
|
||||
onSetProjectUnchanged,
|
||||
onSetProjectThumbnailer,
|
||||
onSetProjectSaver,
|
||||
onShowAlert,
|
||||
onShowCopySuccessAlert,
|
||||
onShowRemixSuccessAlert,
|
||||
@@ -358,6 +325,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
onShowSaveSuccessAlert,
|
||||
onShowSavingAlert,
|
||||
onUpdatedProject,
|
||||
onUpdateProjectData,
|
||||
onUpdateProjectThumbnail,
|
||||
reduxProjectId,
|
||||
reduxProjectTitle,
|
||||
@@ -375,6 +343,7 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
}
|
||||
|
||||
ProjectSaverComponent.propTypes = {
|
||||
autoSaveIntervalSecs: PropTypes.number.isRequired,
|
||||
autoSaveTimeoutId: PropTypes.number,
|
||||
canCreateNew: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
@@ -390,12 +359,16 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
isShowingWithoutId: PropTypes.bool,
|
||||
isUpdating: PropTypes.bool,
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
locale: PropTypes.string.isRequired,
|
||||
onAutoUpdateProject: PropTypes.func,
|
||||
onCreateProject: PropTypes.func,
|
||||
onCreatedProject: PropTypes.func,
|
||||
onProjectError: PropTypes.func,
|
||||
onProjectTelemetryEvent: PropTypes.func,
|
||||
onRemixing: PropTypes.func,
|
||||
onSetProjectSaver: PropTypes.func.isRequired,
|
||||
onSetProjectThumbnailer: PropTypes.func.isRequired,
|
||||
onSetProjectUnchanged: PropTypes.func.isRequired,
|
||||
onShowAlert: PropTypes.func,
|
||||
onShowCopySuccessAlert: PropTypes.func,
|
||||
onShowCreatingCopyAlert: PropTypes.func,
|
||||
@@ -403,17 +376,21 @@ const ProjectSaverHOC = function (WrappedComponent) {
|
||||
onShowRemixSuccessAlert: PropTypes.func,
|
||||
onShowSaveSuccessAlert: PropTypes.func,
|
||||
onShowSavingAlert: PropTypes.func,
|
||||
onUpdateProjectData: PropTypes.func.isRequired,
|
||||
onUpdateProjectThumbnail: PropTypes.func,
|
||||
onUpdatedProject: PropTypes.func,
|
||||
projectChanged: PropTypes.bool,
|
||||
reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
reduxProjectTitle: PropTypes.string,
|
||||
setAutoSaveTimeoutId: PropTypes.func.isRequired,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
ProjectSaverComponent.defaultProps = {
|
||||
autoSaveIntervalSecs: 120,
|
||||
onRemixing: () => {},
|
||||
onSetProjectThumbnailer: () => {}
|
||||
onSetProjectThumbnailer: () => {},
|
||||
onSetProjectSaver: () => {},
|
||||
onUpdateProjectData: saveProjectToServer
|
||||
};
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const loadingState = state.scratchGui.projectState.loadingState;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import queryString from 'query-string';
|
||||
import xhr from 'xhr';
|
||||
import storage from '../lib/storage';
|
||||
|
||||
/**
|
||||
* Save a project JSON to the project server.
|
||||
* This should eventually live in scratch-www.
|
||||
* @param {number} projectId the ID of the project, null if a new project.
|
||||
* @param {object} vmState the JSON project representation.
|
||||
* @param {object} params the request params.
|
||||
* @property {?number} params.originalId the original project ID if a copy/remix.
|
||||
* @property {?boolean} params.isCopy a flag indicating if this save is creating a copy.
|
||||
* @property {?boolean} params.isRemix a flag indicating if this save is creating a remix.
|
||||
* @property {?string} params.title the title of the project.
|
||||
* @return {Promise} A promise that resolves when the network request resolves.
|
||||
*/
|
||||
export default function (projectId, vmState, params) {
|
||||
const opts = {
|
||||
body: vmState,
|
||||
// If we set json:true then the body is double-stringified, so don't
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
};
|
||||
const creatingProject = projectId === null || typeof projectId === 'undefined';
|
||||
const queryParams = {};
|
||||
if (params.hasOwnProperty('originalId')) queryParams.original_id = params.originalId;
|
||||
if (params.hasOwnProperty('isCopy')) queryParams.is_copy = params.isCopy;
|
||||
if (params.hasOwnProperty('isRemix')) queryParams.is_remix = params.isRemix;
|
||||
if (params.hasOwnProperty('title')) queryParams.title = params.title;
|
||||
let qs = queryString.stringify(queryParams);
|
||||
if (qs) qs = `?${qs}`;
|
||||
if (creatingProject) {
|
||||
Object.assign(opts, {
|
||||
method: 'post',
|
||||
url: `${storage.projectHost}/${qs}`
|
||||
});
|
||||
} else {
|
||||
Object.assign(opts, {
|
||||
method: 'put',
|
||||
url: `${storage.projectHost}/${projectId}${qs}`
|
||||
});
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr(opts, (err, response) => {
|
||||
if (err) return reject(err);
|
||||
let body;
|
||||
try {
|
||||
// Since we didn't set json: true, we have to parse manually
|
||||
body = JSON.parse(response.body);
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
body.id = projectId;
|
||||
if (creatingProject) {
|
||||
body.id = body['content-name'];
|
||||
}
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -25,5 +25,10 @@ export default defineMessages({
|
||||
id: 'gui.sharedMessages.replaceProjectWarning',
|
||||
defaultMessage: 'Replace contents of the current project?',
|
||||
description: 'Confirmation that user wants to overwrite the current project contents'
|
||||
},
|
||||
loadFromComputerTitle: {
|
||||
id: 'gui.sharedMessages.loadFromComputerTitle',
|
||||
defaultMessage: 'Load from your computer',
|
||||
description: 'Title for uploading a project from your computer'
|
||||
}
|
||||
});
|
||||
@@ -60,9 +60,9 @@ const vmListenerHOC = function (WrappedComponent) {
|
||||
this.props.vm.postIOData('userData', {username: this.props.username});
|
||||
}
|
||||
|
||||
// Re-request a targets update when the shouldEmitTargetsUpdate state changes to true
|
||||
// Re-request a targets update when the shouldEmitUpdate state changes to true
|
||||
// i.e. when the editor transitions out of fullscreen/player only modes
|
||||
if (this.props.shouldEmitTargetsUpdate && !prevProps.shouldEmitTargetsUpdate) {
|
||||
if (this.props.shouldEmitUpdates && !prevProps.shouldEmitUpdates) {
|
||||
this.props.vm.emitTargetsUpdate(false /* Emit the event, but do not trigger project change */);
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ const vmListenerHOC = function (WrappedComponent) {
|
||||
}
|
||||
}
|
||||
handleProjectChanged () {
|
||||
if (this.props.shouldEmitUpdates) {
|
||||
if (this.props.shouldEmitUpdates && !this.props.projectChanged) {
|
||||
this.props.onProjectChanged();
|
||||
}
|
||||
}
|
||||
@@ -117,6 +117,7 @@ const vmListenerHOC = function (WrappedComponent) {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
attachKeyboardEvents,
|
||||
projectChanged,
|
||||
shouldEmitUpdates,
|
||||
onBlockDragUpdate,
|
||||
onGreenFlag,
|
||||
@@ -147,13 +148,16 @@ const vmListenerHOC = function (WrappedComponent) {
|
||||
onKeyUp: PropTypes.func,
|
||||
onMicListeningUpdate: PropTypes.func.isRequired,
|
||||
onMonitorsUpdate: PropTypes.func.isRequired,
|
||||
onProjectChanged: PropTypes.func.isRequired,
|
||||
onProjectRunStart: PropTypes.func.isRequired,
|
||||
onProjectRunStop: PropTypes.func.isRequired,
|
||||
onProjectSaved: PropTypes.func.isRequired,
|
||||
onRuntimeStarted: PropTypes.func.isRequired,
|
||||
onShowExtensionAlert: PropTypes.func.isRequired,
|
||||
onTargetsUpdate: PropTypes.func.isRequired,
|
||||
onTurboModeOff: PropTypes.func.isRequired,
|
||||
onTurboModeOn: PropTypes.func.isRequired,
|
||||
projectChanged: PropTypes.bool,
|
||||
shouldEmitUpdates: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
@@ -163,6 +167,7 @@ const vmListenerHOC = function (WrappedComponent) {
|
||||
onGreenFlag: () => ({})
|
||||
};
|
||||
const mapStateToProps = state => ({
|
||||
projectChanged: state.scratchGui.projectChanged,
|
||||
// Do not emit target or project updates in fullscreen or player only mode
|
||||
// or when recording sounds (it leads to garbled recordings on low-power machines)
|
||||
shouldEmitUpdates: !state.scratchGui.mode.isFullScreen && !state.scratchGui.mode.isPlayerOnly &&
|
||||
|
||||
@@ -107,6 +107,7 @@ const vmManagerHOC = function (WrappedComponent) {
|
||||
fontsLoaded: PropTypes.bool,
|
||||
isLoadingWithId: PropTypes.bool,
|
||||
isPlayerOnly: PropTypes.bool,
|
||||
isStarted: PropTypes.bool,
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
locale: PropTypes.string,
|
||||
messages: PropTypes.objectOf(PropTypes.string),
|
||||
|
||||
@@ -87,7 +87,7 @@ const reducer = function (state, action) {
|
||||
closeButton: true,
|
||||
extensionId: extensionId,
|
||||
extensionName: extension.name,
|
||||
iconURL: extension.smallPeripheralImage,
|
||||
iconURL: extension.connectionSmallIconURL,
|
||||
level: AlertLevels.WARN,
|
||||
showReconnect: true
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import analytics from '../lib/analytics';
|
||||
import decks from '../lib/libraries/decks/index.jsx';
|
||||
|
||||
const CLOSE_CARDS = 'scratch-gui/cards/CLOSE_CARDS';
|
||||
const SHRINK_EXPAND_CARDS = 'scratch-gui/cards/SHRINK_EXPAND_CARDS';
|
||||
const VIEW_CARDS = 'scratch-gui/cards/VIEW_CARDS';
|
||||
const ACTIVATE_DECK = 'scratch-gui/cards/ACTIVATE_DECK';
|
||||
const NEXT_STEP = 'scratch-gui/cards/NEXT_STEP';
|
||||
@@ -18,6 +19,7 @@ const initialState = {
|
||||
step: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
expanded: true,
|
||||
dragging: false
|
||||
};
|
||||
|
||||
@@ -28,6 +30,10 @@ const reducer = function (state, action) {
|
||||
return Object.assign({}, state, {
|
||||
visible: false
|
||||
});
|
||||
case SHRINK_EXPAND_CARDS:
|
||||
return Object.assign({}, state, {
|
||||
expanded: !state.expanded
|
||||
});
|
||||
case VIEW_CARDS:
|
||||
return Object.assign({}, state, {
|
||||
visible: true
|
||||
@@ -36,6 +42,9 @@ const reducer = function (state, action) {
|
||||
return Object.assign({}, state, {
|
||||
activeDeckId: action.activeDeckId,
|
||||
step: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
expanded: true,
|
||||
visible: true
|
||||
});
|
||||
case NEXT_STEP:
|
||||
@@ -92,6 +101,10 @@ const closeCards = function () {
|
||||
return {type: CLOSE_CARDS};
|
||||
};
|
||||
|
||||
const shrinkExpandCards = function () {
|
||||
return {type: SHRINK_EXPAND_CARDS};
|
||||
};
|
||||
|
||||
const nextStep = function () {
|
||||
return {type: NEXT_STEP};
|
||||
};
|
||||
@@ -118,6 +131,7 @@ export {
|
||||
activateDeck,
|
||||
viewCards,
|
||||
closeCards,
|
||||
shrinkExpandCards,
|
||||
nextStep,
|
||||
prevStep,
|
||||
dragCard,
|
||||
|
||||
@@ -107,6 +107,7 @@ const initTutorialCard = function (currentState, deckId) {
|
||||
visible: true,
|
||||
content: decks,
|
||||
activeDeckId: deckId,
|
||||
expanded: true,
|
||||
step: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -10,7 +10,6 @@ const DONE_LOADING_VM_WITHOUT_ID = 'scratch-gui/project-state/DONE_LOADING_VM_WI
|
||||
const DONE_REMIXING = 'scratch-gui/project-state/DONE_REMIXING';
|
||||
const DONE_UPDATING = 'scratch-gui/project-state/DONE_UPDATING';
|
||||
const DONE_UPDATING_BEFORE_COPY = 'scratch-gui/project-state/DONE_UPDATING_BEFORE_COPY';
|
||||
const DONE_UPDATING_BEFORE_FILE_UPLOAD = 'scratch-gui/project-state/DONE_UPDATING_BEFORE_FILE_UPLOAD';
|
||||
const DONE_UPDATING_BEFORE_NEW = 'scratch-gui/project-state/DONE_UPDATING_BEFORE_NEW';
|
||||
const RETURN_TO_SHOWING = 'scratch-gui/project-state/RETURN_TO_SHOWING';
|
||||
const SET_PROJECT_ID = 'scratch-gui/project-state/SET_PROJECT_ID';
|
||||
@@ -23,7 +22,6 @@ const START_MANUAL_UPDATING = 'scratch-gui/project-state/START_MANUAL_UPDATING';
|
||||
const START_REMIXING = 'scratch-gui/project-state/START_REMIXING';
|
||||
const START_UPDATING_BEFORE_CREATING_COPY = 'scratch-gui/project-state/START_UPDATING_BEFORE_CREATING_COPY';
|
||||
const START_UPDATING_BEFORE_CREATING_NEW = 'scratch-gui/project-state/START_UPDATING_BEFORE_CREATING_NEW';
|
||||
const START_UPDATING_BEFORE_FILE_UPLOAD = 'scratch-gui/project-state/START_UPDATING_BEFORE_FILE_UPLOAD';
|
||||
|
||||
const defaultProjectId = '0'; // hardcoded id of default project
|
||||
|
||||
@@ -43,7 +41,6 @@ const LoadingState = keyMirror({
|
||||
SHOWING_WITH_ID: null,
|
||||
SHOWING_WITHOUT_ID: null,
|
||||
UPDATING_BEFORE_COPY: null,
|
||||
UPDATING_BEFORE_FILE_UPLOAD: null,
|
||||
UPDATING_BEFORE_NEW: null
|
||||
});
|
||||
|
||||
@@ -91,7 +88,6 @@ const getIsUpdating = loadingState => (
|
||||
loadingState === LoadingState.AUTO_UPDATING ||
|
||||
loadingState === LoadingState.MANUAL_UPDATING ||
|
||||
loadingState === LoadingState.UPDATING_BEFORE_COPY ||
|
||||
loadingState === LoadingState.UPDATING_BEFORE_FILE_UPLOAD ||
|
||||
loadingState === LoadingState.UPDATING_BEFORE_NEW
|
||||
);
|
||||
const getIsShowingProject = loadingState => (
|
||||
@@ -203,13 +199,6 @@ const reducer = function (state, action) {
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case DONE_UPDATING_BEFORE_FILE_UPLOAD:
|
||||
if (state.loadingState === LoadingState.UPDATING_BEFORE_FILE_UPLOAD) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.LOADING_VM_FILE_UPLOAD
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case DONE_UPDATING_BEFORE_NEW:
|
||||
if (state.loadingState === LoadingState.UPDATING_BEFORE_NEW) {
|
||||
return Object.assign({}, state, {
|
||||
@@ -333,13 +322,6 @@ const reducer = function (state, action) {
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case START_UPDATING_BEFORE_FILE_UPLOAD:
|
||||
if (state.loadingState === LoadingState.SHOWING_WITH_ID) {
|
||||
return Object.assign({}, state, {
|
||||
loadingState: LoadingState.UPDATING_BEFORE_FILE_UPLOAD
|
||||
});
|
||||
}
|
||||
return state;
|
||||
case START_ERROR:
|
||||
// fatal errors: there's no correct editor state for us to show
|
||||
if ([
|
||||
@@ -360,7 +342,6 @@ const reducer = function (state, action) {
|
||||
LoadingState.MANUAL_UPDATING,
|
||||
LoadingState.REMIXING,
|
||||
LoadingState.UPDATING_BEFORE_COPY,
|
||||
LoadingState.UPDATING_BEFORE_FILE_UPLOAD,
|
||||
LoadingState.UPDATING_BEFORE_NEW
|
||||
].includes(state.loadingState)) {
|
||||
return Object.assign({}, state, {
|
||||
@@ -471,10 +452,6 @@ const doneUpdatingProject = loadingState => {
|
||||
return {
|
||||
type: DONE_UPDATING_BEFORE_COPY
|
||||
};
|
||||
case LoadingState.UPDATING_BEFORE_FILE_UPLOAD:
|
||||
return {
|
||||
type: DONE_UPDATING_BEFORE_FILE_UPLOAD
|
||||
};
|
||||
case LoadingState.UPDATING_BEFORE_NEW:
|
||||
return {
|
||||
type: DONE_UPDATING_BEFORE_NEW
|
||||
@@ -501,11 +478,8 @@ const requestNewProject = needSave => {
|
||||
|
||||
const requestProjectUpload = loadingState => {
|
||||
switch (loadingState) {
|
||||
case LoadingState.SHOWING_WITH_ID:
|
||||
return {
|
||||
type: START_UPDATING_BEFORE_FILE_UPLOAD
|
||||
};
|
||||
case LoadingState.NOT_LOADED:
|
||||
case LoadingState.SHOWING_WITH_ID:
|
||||
case LoadingState.SHOWING_WITHOUT_ID:
|
||||
return {
|
||||
type: START_LOADING_VM_FILE_UPLOAD
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('Working with the how-to library', () => {
|
||||
});
|
||||
|
||||
test('Backpack can be expanded with backpack host param', async () => {
|
||||
await loadUri(`${uri}?backpack_host=some-value`);
|
||||
await loadUri(`${uri}?backpack_host=https://backpack.scratch.mit.edu`);
|
||||
|
||||
// Try activating the backpack from the costumes tab to make sure it isn't pushed off
|
||||
await clickText('Costumes');
|
||||
|
||||
@@ -193,7 +193,11 @@ describe('Working with costumes', () => {
|
||||
.mouseMove(abbyElement)
|
||||
.perform();
|
||||
// wait for one of Abby's alternate costumes to appear
|
||||
await findByXpath('//img[@src="https://cdn.assets.scratch.mit.edu/internalapi/asset/b6e23922f23b49ddc6f62f675e77417c.svg/get/"]');
|
||||
const src1 = await abbyElement.findElement({css: 'img'}).getAttribute('src');
|
||||
await driver.sleep(300);
|
||||
const src2 = await abbyElement.findElement({css: 'img'}).getAttribute('src');
|
||||
const sourcesMatch = (src1 === src2);
|
||||
await expect(sourcesMatch).toBeFalsy(); // 'src' attribute should have changed by now
|
||||
const logs = await getLogs();
|
||||
await expect(logs).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ describe('Menu bar settings', () => {
|
||||
'//div[contains(@class, "menu-bar_menu-bar-item") and ' +
|
||||
'contains(@class, "menu-bar_hoverable")][span[text()="File"]]'
|
||||
);
|
||||
await findByXpath('//*[li[span[text()="Load from your computer"]] and not(@data-tip="tooltip")]');
|
||||
await findByXpath('//*[li[text()="Load from your computer"] and not(@data-tip="tooltip")]');
|
||||
});
|
||||
|
||||
test('File->Save should be enabled', async () => {
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import React from 'react';
|
||||
import {mount} from 'enzyme';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import MenuBarHOC from '../../../src/containers/menu-bar-hoc.jsx';
|
||||
|
||||
describe('Menu Bar HOC', () => {
|
||||
const mockStore = configureStore();
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore({
|
||||
scratchGui: {
|
||||
projectChanged: true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and HAS changed project will NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
canSave
|
||||
projectChanged
|
||||
// assume the user will click "cancel" on the confirm dialog
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and has NOT changed project will NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
canSave
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who is NOT owner and HAS changed project will NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew
|
||||
projectChanged
|
||||
canSave={false}
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged OUT user who HAS changed project WILL be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
projectChanged
|
||||
canCreateNew={false}
|
||||
canSave={false}
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(false);
|
||||
});
|
||||
|
||||
test('Logged OUT user who has NOT changed project WILL NOT be prompted to save', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canCreateNew={false}
|
||||
canSave={false}
|
||||
confirmWithMessage={() => (false)} // eslint-disable-line react/jsx-no-bind
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().confirmReadyToReplaceProject('message')).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and HAS changed project SHOULD save before transition to project page', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
projectChanged
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(true);
|
||||
});
|
||||
|
||||
test('Logged in user who IS owner and has NOT changed project should NOT save before transition', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canSave
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(false);
|
||||
});
|
||||
|
||||
test('Logged in user who is NOT owner and HAS changed project should NOT save before transition', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
projectChanged
|
||||
canSave={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(false);
|
||||
});
|
||||
|
||||
test('Logged in user who is NOT owner and has NOT changed project should NOT save before transition', () => {
|
||||
const Component = () => (<div />);
|
||||
const WrappedComponent = MenuBarHOC(Component);
|
||||
const wrapper = mount(
|
||||
<WrappedComponent
|
||||
canSave={false}
|
||||
projectChanged={false}
|
||||
store={store}
|
||||
/>
|
||||
);
|
||||
const child = wrapper.find(Component);
|
||||
expect(child.props().projectChanged).toBeUndefined();
|
||||
expect(child.props().shouldSaveBeforeTransition()).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -250,7 +250,7 @@ test('requestProjectUpload when showing project with id should load', () => {
|
||||
};
|
||||
const action = requestProjectUpload(initialState.loadingState);
|
||||
const resultState = projectStateReducer(initialState, action);
|
||||
expect(resultState.loadingState).toBe(LoadingState.UPDATING_BEFORE_FILE_UPLOAD);
|
||||
expect(resultState.loadingState).toBe(LoadingState.LOADING_VM_FILE_UPLOAD);
|
||||
});
|
||||
|
||||
test('requestProjectUpload when showing project without id should load', () => {
|
||||
|
||||
@@ -465,4 +465,27 @@ describe('projectSaverHOC', () => {
|
||||
expect(setThumb).toHaveBeenCalledTimes(2);
|
||||
expect(setThumb.mock.calls[1][0]).toBe(null);
|
||||
});
|
||||
|
||||
test('uses onSetProjectSaver on mount/unmount', () => {
|
||||
const Component = () => <div />;
|
||||
const WrappedComponent = projectSaverHOC(Component);
|
||||
const setSaver = jest.fn();
|
||||
const mounted = mount(
|
||||
<WrappedComponent
|
||||
store={store}
|
||||
vm={vm}
|
||||
onSetProjectSaver={setSaver}
|
||||
/>
|
||||
);
|
||||
// Set project saver should be called on mount
|
||||
expect(setSaver).toHaveBeenCalledTimes(1);
|
||||
|
||||
// And it should not pass that function on to wrapped element
|
||||
expect(mounted.find(Component).props().onSetProjectSaver).toBeUndefined();
|
||||
|
||||
// Unmounting should call it again with null
|
||||
mounted.unmount();
|
||||
expect(setSaver).toHaveBeenCalledTimes(2);
|
||||
expect(setSaver.mock.calls[1][0]).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,8 @@ const base = {
|
||||
include: [
|
||||
path.resolve(__dirname, 'src'),
|
||||
/node_modules[\\/]scratch-[^\\/]+[\\/]src/,
|
||||
/node_modules[\\/]pify/
|
||||
/node_modules[\\/]pify/,
|
||||
/node_modules[\\/]@vernier[\\/]godirect/
|
||||
],
|
||||
options: {
|
||||
// Explicitly disable babelrc so we don't catch various config
|
||||
|
||||