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 | ||
|
|
1a194a13e6 | ||
|
|
634c780ce1 | ||
|
|
7709580710 | ||
|
|
db487efd62 | ||
|
|
b26ac6d62b | ||
|
|
e5ba46f499 | ||
|
|
2d1638e909 | ||
|
|
437106fa33 | ||
|
|
992f012ff3 | ||
|
|
c53b88dbad | ||
|
|
1fd9a1f689 | ||
|
|
33f3d1d79b | ||
|
|
5051ba2105 | ||
|
|
551b7b98ce | ||
|
|
a3e8dd1aab | ||
|
|
bb0e1e54cd | ||
|
|
98ec67d728 | ||
|
|
f773ee2dab | ||
|
|
a7487fb26d | ||
|
|
3adacd3f2a | ||
|
|
1dd3e1d26a | ||
|
|
ce5aa9fb7a | ||
|
|
846a174f13 | ||
|
|
c779c5fe77 | ||
|
|
e2a2dcf0fd | ||
|
|
3e33c10b04 | ||
|
|
f0ba4180c7 | ||
|
|
f6b9f11d3b | ||
|
|
d71a9114b7 | ||
|
|
5c4f50c1b8 | ||
|
|
4d671ddfab | ||
|
|
94901b33fe | ||
|
|
f6dc2726b5 | ||
|
|
7b4c0657c6 | ||
|
|
39279fa0ef | ||
|
|
5f2f95a6db | ||
|
|
790fc13157 | ||
|
|
face0d20d4 | ||
|
|
5c16407818 | ||
|
|
d8201fd4ad | ||
|
|
2b1cd4951d | ||
|
|
849fe8fc0c | ||
|
|
f600e6095e | ||
|
|
d952f774dd | ||
|
|
38ee27d896 | ||
|
|
73e1bc352b | ||
|
|
46ce75bf2a | ||
|
|
2c285fee8a | ||
|
|
d1c3cf8bb0 | ||
|
|
a09c7ddb53 | ||
|
|
fec81c93a3 | ||
|
|
2b3c70dfc3 | ||
|
|
db9236bef2 | ||
|
|
fb383ee5f5 | ||
|
|
3a33e7ea86 | ||
|
|
8e51409e20 | ||
|
|
f29b82b636 | ||
|
|
31625d6807 | ||
|
|
dadef73bfa | ||
|
|
268ad88281 | ||
|
|
51f9aafec1 | ||
|
|
6ea53f63ae | ||
|
|
e746b98d83 | ||
|
|
f72d0b5985 | ||
|
|
832dabbb51 | ||
|
|
c5b5931e11 | ||
|
|
f0e518df8a | ||
|
|
158409d4d8 | ||
|
|
abf49b3115 | ||
|
|
4af0d9fe27 | ||
|
|
a627552c8e | ||
|
|
127e1a9632 | ||
|
|
438e0f9cf7 | ||
|
|
1e532745b7 | ||
|
|
db811553c9 | ||
|
|
ef05591d20 | ||
|
|
c83d85e368 | ||
|
|
d8d39b1d61 | ||
|
|
f364994a46 | ||
|
|
9273612f99 | ||
|
|
880bc530b7 | ||
|
|
c109c10db6 | ||
|
|
5258254562 | ||
|
|
43a0ed8635 | ||
|
|
f2122bab42 | ||
|
|
c29e943c6b | ||
|
|
d3cc72db4d | ||
|
|
b2b4da2f68 | ||
|
|
782fc9aebf | ||
|
|
7c557db8b4 | ||
|
|
b76593cabb | ||
|
|
ced6e8df18 | ||
|
|
0ea314d98a | ||
|
|
fe00c71d28 | ||
|
|
22d96ca94b | ||
|
|
08ec713bff | ||
|
|
e9c9758618 | ||
|
|
e51ec837e8 | ||
|
|
6468395ba5 | ||
|
|
686dbb5245 | ||
|
|
8783d1dfa7 | ||
|
|
c6ab3c8e42 | ||
|
|
26de4b2e51 | ||
|
|
0dd0fd8e7a | ||
|
|
8fcfe7c7d7 | ||
|
|
2de31ba0ed | ||
|
|
742e9e327a | ||
|
|
9efcadad5d | ||
|
|
a4ea94bd39 | ||
|
|
60f2f85734 | ||
|
|
6d5183f99d | ||
|
|
3b340d02af | ||
|
|
2ab48e011e | ||
|
|
ce82a52fce | ||
|
|
9a2990f301 | ||
|
|
d431c3f8ba | ||
|
|
2d3632b014 | ||
|
|
d301794463 | ||
|
|
1a2c3bd0b7 | ||
|
|
9977ec63d5 | ||
|
|
0f803dfc41 | ||
|
|
6f101523b4 | ||
|
|
e3dbf435da | ||
|
|
215019262b | ||
|
|
40a5ff783a | ||
|
|
1cfccde0c4 | ||
|
|
0b557ab520 | ||
|
|
2c6519a129 | ||
|
|
208060e441 | ||
|
|
a10704c44b | ||
|
|
4c53f700c5 | ||
|
|
a0f54fda89 | ||
|
|
ac0f004e5a | ||
|
|
324dc1717c | ||
|
|
31e087c305 | ||
|
|
09493ec894 | ||
|
|
7621fd0c08 | ||
|
|
8c24c62e38 | ||
|
|
59ca7fc771 | ||
|
|
66c06d0a35 | ||
|
|
541c43482d | ||
|
|
7847f9e775 | ||
|
|
a182335f4a | ||
|
|
86239e56ab | ||
|
|
cf418fd262 | ||
|
|
4927e12e80 | ||
|
|
9896a24472 | ||
|
|
a4406eb542 | ||
|
|
1aa241749b | ||
|
|
ce3046972a | ||
|
|
c9d9f0bd4f | ||
|
|
ac0705c12c | ||
|
|
735140f276 | ||
|
|
ebd372a090 | ||
|
|
24ca96971e | ||
|
|
ade61132a7 | ||
|
|
4500c4f781 | ||
|
|
cae9d9a683 | ||
|
|
9bef38e977 | ||
|
|
773b8e28d4 | ||
|
|
d51c984b90 | ||
|
|
361a9d7393 | ||
|
|
29e738cb92 | ||
|
|
4ef98ec0fe | ||
|
|
0ae9d70685 | ||
|
|
3993d5c8f1 | ||
|
|
7563eddb2b | ||
|
|
b879fd412b | ||
|
|
1d617214b4 | ||
|
|
e2113cf1e0 | ||
|
|
3856fbc6c5 | ||
|
|
fb9c3eed74 | ||
|
|
b618f2493c | ||
|
|
8dad7d8ec0 | ||
|
|
a18ea5a92c | ||
|
|
d17824d7e3 | ||
|
|
474a00ed72 | ||
|
|
44cb5e8706 | ||
|
|
0002f5181c | ||
|
|
7c1d720e17 | ||
|
|
a150bc99db | ||
|
|
b29862fc96 | ||
|
|
38fb199533 | ||
|
|
232d6610ae | ||
|
|
b0e2ea7d20 | ||
|
|
28635d8028 | ||
|
|
ba277a5238 | ||
|
|
489467e63d | ||
|
|
88651e6e48 | ||
|
|
5e3c251405 | ||
|
|
41ba125641 | ||
|
|
da65b41366 | ||
|
|
34be9ac23d | ||
|
|
e20566a242 | ||
|
|
65f9f7f16d | ||
|
|
71a251448c | ||
|
|
96668b5b04 | ||
|
|
781c682e60 | ||
|
|
c9cfda8ca8 | ||
|
|
65756fcc50 | ||
|
|
7eabc4d9cd | ||
|
|
8a46b8f71b | ||
|
|
9166cb12d5 | ||
|
|
66a9607bc7 | ||
|
|
c6c5d61039 | ||
|
|
cc80ee345f | ||
|
|
9232b1c656 | ||
|
|
fcfff18426 | ||
|
|
3d588321ca | ||
|
|
aaa8fd20e4 | ||
|
|
9da0937ebb | ||
|
|
4e1a4aedf3 | ||
|
|
f9c9148bab | ||
|
|
71f8d8f5e8 | ||
|
|
8459c67cee | ||
|
|
5b75c2e971 | ||
|
|
28fbb5352c | ||
|
|
e2ee0e06f7 | ||
|
|
9c6bb6984b | ||
|
|
65642edc15 | ||
|
|
86b6e12453 |
No files matched your search
@@ -10,6 +10,7 @@ env:
|
||||
- NODE_ENV=production
|
||||
- SMOKE_URL=https://llk.github.io/scratch-gui/$TRAVIS_PULL_REQUEST_BRANCH
|
||||
- NPM_TAG=latest
|
||||
- RELEASE_VERSION="0.1.0-prerelease.$(date +'%Y%m%d%H%M%S')"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
@@ -22,8 +23,9 @@ script:
|
||||
before_deploy:
|
||||
- >
|
||||
if [ -z "$BEFORE_DEPLOY_RAN" ]; then
|
||||
npm --no-git-tag-version version 0.1.0-prerelease.$(date +%Y%m%d%H%M%S)
|
||||
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
|
||||
@@ -35,11 +37,21 @@ deploy:
|
||||
- master
|
||||
- develop
|
||||
- smoke
|
||||
- hotfix/*
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
skip_cleanup: true
|
||||
email: $NPM_EMAIL
|
||||
api_key: $NPM_TOKEN
|
||||
tag: $NPM_TAG
|
||||
- provider: script
|
||||
on:
|
||||
branch:
|
||||
- master
|
||||
- develop
|
||||
- smoke
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
skip_cleanup: true
|
||||
script: if npm info scratch-gui | grep -q $RELEASE_VERSION; then git tag $RELEASE_VERSION && git push https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git $RELEASE_VERSION; fi
|
||||
- provider: s3
|
||||
on:
|
||||
branch:
|
||||
@@ -55,14 +67,9 @@ 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
|
||||
on:
|
||||
all_branches: true
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
script: npm run prune -- https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git
|
||||
- provider: script
|
||||
on:
|
||||
branch: develop
|
||||
|
||||
@@ -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",
|
||||
@@ -76,6 +76,7 @@
|
||||
"lodash.throttle": "4.0.1",
|
||||
"minilog": "3.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"omggif": "1.0.9",
|
||||
"papaparse": "4.6.2",
|
||||
"postcss-import": "^12.0.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
@@ -89,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",
|
||||
@@ -99,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.1549534784",
|
||||
"scratch-l10n": "3.1.20190206143031",
|
||||
"scratch-paint": "0.2.0-prerelease.20190114205252",
|
||||
"scratch-render": "0.1.0-prerelease.20190206213754",
|
||||
"scratch-storage": "1.2.2",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190125192231",
|
||||
"scratch-vm": "0.2.0-prerelease.20190207191220",
|
||||
"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",
|
||||
|
||||
@@ -142,7 +142,7 @@ class ActionMenu extends React.Component {
|
||||
<div className={styles.moreButtonsOuter}>
|
||||
<div className={styles.moreButtons}>
|
||||
{(moreButtons || []).map(({img, title, onClick: handleClick,
|
||||
fileAccept, fileChange, fileInput}, keyId) => {
|
||||
fileAccept, fileChange, fileInput, fileMultiple}, keyId) => {
|
||||
const isComingSoon = !handleClick;
|
||||
const hasFileInput = fileInput;
|
||||
const tooltipId = `${this.mainTooltipId}-${title}`;
|
||||
@@ -166,6 +166,7 @@ class ActionMenu extends React.Component {
|
||||
<input
|
||||
accept={fileAccept}
|
||||
className={styles.fileInput}
|
||||
multiple={fileMultiple}
|
||||
ref={fileInput}
|
||||
type="file"
|
||||
onChange={fileChange}
|
||||
@@ -198,7 +199,8 @@ ActionMenu.propTypes = {
|
||||
onClick: PropTypes.func, // Optional, "coming soon" if no callback provided
|
||||
fileAccept: PropTypes.string, // Optional, only for file upload
|
||||
fileChange: PropTypes.func, // Optional, only for file upload
|
||||
fileInput: PropTypes.func // Optional, only for file upload
|
||||
fileInput: PropTypes.func, // Optional, only for file upload
|
||||
fileMultiple: PropTypes.bool // Optional, only for file upload
|
||||
})),
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.node.isRequired,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
color: $ui-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -12,9 +16,10 @@ import helpIcon from '../../lib/assets/icon--tutorials.svg';
|
||||
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}
|
||||
@@ -40,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>
|
||||
);
|
||||
@@ -68,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
|
||||
@@ -106,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
|
||||
@@ -124,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
|
||||
@@ -140,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
|
||||
};
|
||||
@@ -218,6 +249,7 @@ const Cards = props => {
|
||||
locale,
|
||||
onActivateDeckFactory,
|
||||
onCloseCards,
|
||||
onShrinkExpandCards,
|
||||
onDrag,
|
||||
onStartDrag,
|
||||
onEndDrag,
|
||||
@@ -225,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={steps[step].image}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -308,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,
|
||||
@@ -317,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;
|
||||
@@ -1,34 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="52px" height="52px" viewBox="0 0 52 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51.1 (57501) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>scratchlink</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M6.599912,0 L37.399912,0 C41.045312,0 43.999912,2.95504 43.999912,6.6 L43.999912,37.4 C43.999912,41.04496 41.045312,44 37.399912,44 L6.599912,44 C2.954952,44 -8.8e-05,41.04496 -8.8e-05,37.4 L-8.8e-05,6.6 C-8.8e-05,2.95504 2.954952,0 6.599912,0" id="path-1"></path>
|
||||
<filter x="-13.6%" y="-13.6%" width="127.3%" height="127.3%" filterUnits="objectBoundingBox" id="filter-2">
|
||||
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
|
||||
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
|
||||
<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" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="R1_Extension" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="No-Comms-with-Scratch-Link-or-Bluetooth" transform="translate(-506.000000, -262.000000)">
|
||||
<g id="No-connection-v2" transform="translate(-2.000000, 0.000000)">
|
||||
<g id="Modal" transform="translate(407.000000, 187.000000)">
|
||||
<g id="Scratch-Link" transform="translate(53.000000, 75.000000)">
|
||||
<g id="scratchlink" transform="translate(52.000000, 4.000000)">
|
||||
<g id="Fill-1">
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
|
||||
<use fill="#25AFF4" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
<path d="M35.854764,19.82882 C36.027684,20.8681 36.062444,21.91574 35.988084,22.9427 L39.440764,24.78542 C39.984604,25.05162 40.240684,25.68522 40.034324,26.25458 L38.559444,30.3237 C38.353084,30.89438 37.748524,31.2169 37.159364,31.07082 L33.333564,30.26386 C32.718004,31.10382 32.018404,31.8835 31.217604,32.56902 L32.519124,36.64606 C32.716684,37.21982 32.448724,37.85034 31.898724,38.10686 L27.975684,39.93638 C27.425684,40.19246 26.770524,39.9927 26.458124,39.47262 L24.171444,35.85494 C23.131724,36.02742 22.084524,36.06218 21.057124,35.98826 L19.215724,39.43918 C18.949084,39.9839 18.314604,40.23998 17.744364,40.0323 L13.669084,38.54906 C13.099284,38.34138 12.777644,37.7377 12.923284,37.14898 L13.730684,33.32186 C12.896444,32.71818 12.116324,32.01858 11.431244,31.21778 L7.354204,32.5193 C6.780004,32.71686 6.149924,32.4489 5.893404,31.8989 L4.063884,27.97542 C3.807364,27.42542 4.007564,26.7707 4.527204,26.45786 L8.133884,24.1769 C7.972404,23.13146 7.920924,22.0781 8.006284,21.04586 L4.556684,19.20446 C4.011524,18.93782 3.755884,18.30202 3.964004,17.73178 L5.449444,13.66838 C5.658004,13.09902 6.262124,12.77826 6.850404,12.92478 L10.666964,13.73614 C11.270204,12.9019 11.981684,12.1165 12.782044,11.43098 L11.480964,7.35394 C11.283404,6.78018 11.551364,6.14966 12.101364,5.89314 L16.024404,4.06362 C16.574404,3.8071 17.229564,4.0073 17.541964,4.52782 L19.828644,8.14506 C20.868364,7.97258 21.927444,7.9321 22.960564,7.9893 L24.796244,4.55466 C25.062884,4.01038 25.697364,3.75474 26.266724,3.96198 L30.325724,5.4395 C30.895524,5.64674 31.217164,6.25086 31.071084,6.83914 L30.263684,10.6667 C31.109804,11.2651 31.883764,11.98186 32.580724,12.77694 L36.646324,11.4807 C37.220084,11.28314 37.850164,11.5511 38.106684,12.1011 L39.936204,16.02458 C40.192724,16.57458 39.992524,17.2293 39.472444,17.54214 L35.854764,19.82882 Z" id="Fill-4" fill-opacity="0.15" fill="#231F20"></path>
|
||||
<path d="M30.417684,24.430956 C30.445844,26.694756 29.431644,28.874076 27.638204,30.404836 C26.209964,31.635956 24.408604,32.343916 22.535524,32.412996 C21.669604,33.092796 20.619324,33.496276 19.495124,33.567996 C19.375004,33.575476 19.254884,33.580316 19.134764,33.580316 C16.263324,33.580316 13.845964,31.337196 13.626404,28.473236 C13.623764,28.439796 13.621124,28.394036 13.618484,28.335076 C13.615844,28.314836 13.613644,28.294156 13.613644,28.271276 C13.557324,27.065236 13.585484,26.068636 13.621124,25.435036 C13.654564,24.850276 13.674804,23.797356 13.677444,23.552276 L13.677444,23.536876 C13.692844,22.680636 13.904924,21.845516 14.290804,21.096636 C14.180804,20.258876 14.183444,19.361716 14.300924,18.421876 L14.316324,18.276236 C14.318964,18.258196 14.318964,18.237516 14.321164,18.219916 C14.323804,18.204516 14.326444,18.186916 14.329084,18.168876 C14.425884,17.368956 14.594844,16.058196 15.310284,14.714436 C16.500924,12.460756 18.759444,11.116996 21.350164,11.116996 L21.460164,11.116996 C22.275044,10.662476 23.192444,10.419596 24.134924,10.419596 L24.234804,10.419596 C27.234284,10.473276 29.674524,12.951356 29.674524,15.945996 C29.674524,15.979436 29.572004,19.571596 29.457164,20.800516 C30.078004,21.886436 30.404924,23.117996 30.417684,24.430956" id="Fill-6" fill="#FFFFFF"></path>
|
||||
<path d="M27.86278,24.459116 C27.88302,25.959076 27.19354,27.425596 25.96946,28.470596 C24.92974,29.367316 23.60094,29.863196 22.2343,29.863196 C21.9989,29.860556 21.7613,29.847796 21.52898,29.816996 C21.4753,29.888716 21.41898,29.957796 21.36046,30.023796 C20.83378,30.619116 20.10822,30.971996 19.31622,31.017756 C19.25506,31.023036 19.1961,31.025676 19.13494,31.025676 C17.58922,31.025676 16.28858,29.814356 16.17374,28.271276 C16.1711,28.240476 16.16846,28.209676 16.16846,28.176676 L16.16626,28.153796 C16.11478,27.055116 16.1403,26.150476 16.1711,25.585956 C16.20938,24.931676 16.23006,23.820236 16.23226,23.579996 C16.24502,22.828916 16.53366,22.146916 17.00138,21.628156 C16.76906,20.823396 16.69514,19.857596 16.8377,18.715356 L16.85574,18.549476 C16.85838,18.536716 16.85838,18.523956 16.86102,18.511196 C16.94286,17.841516 17.06518,16.852836 17.56854,15.907716 C18.32006,14.486956 19.69946,13.672076 21.3499,13.672076 C21.4423,13.672076 21.5391,13.674716 21.63854,13.679556 C21.81278,13.689676 21.98878,13.710356 22.15994,13.741156 C22.69674,13.258036 23.40954,12.959276 24.19142,12.974236 C25.80622,13.002396 27.11918,14.336476 27.11918,15.945996 C27.11918,15.981636 26.97618,20.281756 26.9071,20.654876 C26.86618,20.887196 26.79754,21.109396 26.70778,21.319276 C27.45138,22.177276 27.85266,23.248236 27.86278,24.459116" id="Fill-8" fill="#F9A83A"></path>
|
||||
<path d="M21.41766,21.36794 C20.65162,21.2487 20.24286,20.7361 20.44614,19.15034 L20.46946,18.95894 C20.64018,17.58174 20.78362,17.27506 21.44626,17.3107 C21.6403,17.32302 21.86382,17.44578 22.0869,17.62882 C22.29414,17.86862 22.75394,18.17882 23.0131,18.79306 C23.19922,19.24362 23.26302,19.53754 23.28942,19.85434 L23.33078,20.24242 L23.33078,20.24066 C23.38534,20.50642 23.58906,20.7251 23.87066,20.77218 C24.23278,20.83862 24.58258,20.5975 24.64638,20.2345 C24.6543,20.19138 24.81886,16.01534 24.81886,15.94714 C24.81886,15.5793 24.52362,15.28054 24.15358,15.27438 C23.78354,15.27218 23.4839,15.5727 23.4839,15.93966 C23.4839,15.94802 23.48214,16.52442 23.47026,17.10522 C22.94358,16.53322 22.26994,16.01974 21.5149,15.9753 C19.49574,15.8675 19.2753,17.72386 19.14242,18.79394 L19.12218,18.98358 C18.85026,21.09778 19.55514,22.43186 21.21218,22.69146 C23.0131,22.97394 24.21518,23.38754 24.22618,24.49546 C24.23278,24.92622 24.00618,25.36842 23.61106,25.70766 C23.13454,26.1129 22.49654,26.2955 21.89594,26.19694 C21.70938,26.16834 21.5347,26.1173 21.3675,26.06142 C21.13166,25.9303 20.53678,25.56818 20.23538,25.15106 C19.98106,24.79906 19.89306,24.24554 19.86534,23.89442 C19.86886,23.7369 19.8693,23.6335 19.8693,23.62162 C19.8759,23.25202 19.57934,22.95062 19.2137,22.94182 C18.84234,22.9339 18.5383,23.23222 18.53126,23.60138 C18.53126,23.6137 18.51454,24.92226 18.46658,25.7103 C18.39794,26.97926 18.46658,28.05462 18.46658,28.10126 C18.49386,28.46998 18.8111,28.74806 19.18026,28.72342 C19.54766,28.70362 19.82838,28.38418 19.80286,28.01502 C19.80286,28.0093 19.76986,27.48878 19.77426,26.76806 C20.26398,27.0809 20.90726,27.38494 21.67418,27.51562 C22.67298,27.68326 23.6951,27.39418 24.47522,26.72318 C25.17482,26.12522 25.57522,25.30858 25.56422,24.48182 C25.54002,22.01254 22.75394,21.57914 21.41766,21.36794" id="Fill-10" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg width="52px" height="52px" viewBox="0 0 52 52" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
style="enable-background:new 0 0 52 52;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0FBD8C;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#F9A83A;}
|
||||
</style>
|
||||
<title>Scratch Link</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="_x35_2x52-for-the-dialog">
|
||||
<g id="Group">
|
||||
<path id="bg" class="st0" d="M41.4,5c3.1,0,5.6,2.5,5.6,5.6v30.8c0,3.1-2.5,5.6-5.6,5.6H10.6C7.5,47,5,44.5,5,41.4V10.6
|
||||
C5,7.5,7.5,5,10.6,5H41.4z"/>
|
||||
<path id="scratch-outline-2" class="st1" d="M28.8,28.9c0,2.7-1.1,5.4-3.2,7.2c-1.7,1.5-3.8,2.3-5.9,2.4c-1,0.8-2.2,1.3-3.5,1.4
|
||||
c-0.1,0-0.3,0-0.4,0c-3.3,0-6.1-2.7-6.4-6.2c0,0,0-0.1,0-0.2c0,0,0,0,0-0.1c-0.1-1.5,0-2.7,0-3.4c0-0.7,0.1-2,0.1-2.3v0
|
||||
c0-1,0.3-2,0.7-2.9c-0.1-1-0.1-2.1,0-3.2l0-0.2c0,0,0,0,0-0.1c0,0,0,0,0-0.1c0.1-1,0.3-2.5,1.1-4.2c1.4-2.7,4-4.3,7-4.3h0.1
|
||||
c0.9-0.5,2-0.8,3.1-0.8h0.1c3.5,0.1,6.3,3.1,6.3,6.7c0,0-0.1,4.4-0.3,5.9C28.4,25.9,28.7,27.4,28.8,28.9"/>
|
||||
<path id="scratch-outline-1" class="st2" d="M25.8,29c0,1.8-0.8,3.6-2.2,4.8c-1.2,1.1-2.8,1.7-4.3,1.7c-0.3,0-0.5,0-0.8-0.1
|
||||
c-0.1,0.1-0.1,0.2-0.2,0.2c-0.6,0.7-1.5,1.1-2.4,1.2c-0.1,0-0.1,0-0.2,0c-1.8,0-3.3-1.5-3.4-3.3c0,0,0-0.1,0-0.1l0,0
|
||||
c-0.1-1.3,0-2.4,0-3.1c0-0.8,0.1-2.1,0.1-2.4c0-0.9,0.3-1.7,0.9-2.4c-0.3-1-0.4-2.1-0.2-3.5l0-0.2c0,0,0,0,0,0
|
||||
c0.1-0.8,0.2-2,0.8-3.1c0.9-1.7,2.5-2.7,4.4-2.7c0.1,0,0.2,0,0.3,0c0.2,0,0.4,0,0.6,0.1c0.6-0.6,1.5-0.9,2.4-0.9
|
||||
c1.9,0,3.4,1.6,3.4,3.6c0,0-0.2,5.2-0.2,5.7c0,0.3-0.1,0.5-0.2,0.8C25.3,26.2,25.8,27.5,25.8,29"/>
|
||||
<path id="scratch-fill" class="st1" d="M18.3,25.3c-0.9-0.1-1.4-0.8-1.1-2.7l0-0.2c0.2-1.7,0.4-2,1.1-2c0.2,0,0.5,0.2,0.7,0.4
|
||||
c0.2,0.3,0.8,0.7,1.1,1.4c0.2,0.5,0.3,0.9,0.3,1.3l0,0.5v0c0.1,0.3,0.3,0.6,0.6,0.6c0.4,0.1,0.8-0.2,0.9-0.6
|
||||
c0-0.1,0.2-5.1,0.2-5.2c0-0.4-0.3-0.8-0.8-0.8c-0.4,0-0.8,0.4-0.8,0.8c0,0,0,0.7,0,1.4c-0.6-0.7-1.4-1.3-2.3-1.4
|
||||
c-2.3-0.1-2.6,2.1-2.8,3.4l0,0.2c-0.3,2.5,0.5,4.2,2.4,4.5c2.1,0.3,3.5,0.8,3.5,2.2c0,0.5-0.3,1.1-0.7,1.5c-0.6,0.5-1.3,0.7-2,0.6
|
||||
c-0.2,0-0.4-0.1-0.6-0.2c-0.3-0.2-1-0.6-1.3-1.1c-0.3-0.4-0.4-1.1-0.4-1.5c0-0.2,0-0.3,0-0.3c0-0.4-0.3-0.8-0.8-0.8
|
||||
c-0.4,0-0.8,0.3-0.8,0.8c0,0,0,1.6-0.1,2.5c-0.1,1.5,0,2.8,0,2.9c0,0.4,0.4,0.8,0.8,0.8c0.4,0,0.8-0.4,0.7-0.9c0,0,0-0.6,0-1.5
|
||||
c0.6,0.4,1.3,0.7,2.2,0.9c1.2,0.2,2.3-0.1,3.3-1c0.8-0.7,1.3-1.7,1.3-2.7C23.1,26,19.9,25.5,18.3,25.3"/>
|
||||
<path id="signal" class="st1" d="M37.7,36.9c-0.2,0-0.4-0.1-0.5-0.2c-0.3-0.3-0.3-0.8,0-1.1c2.6-2.6,4-6,4-9.6
|
||||
c0-3.6-1.4-7.1-4-9.7c-0.3-0.3-0.3-0.8,0-1.1c0.3-0.3,0.8-0.3,1,0c2.8,2.9,4.4,6.7,4.4,10.7c0,4-1.6,7.9-4.4,10.7
|
||||
C38.1,36.9,37.9,36.9,37.7,36.9z M35,33.5c-0.2,0-0.4-0.1-0.5-0.2c-0.3-0.3-0.3-0.8,0-1.1c1.7-1.7,2.6-3.9,2.6-6.3
|
||||
c0-2.4-0.9-4.6-2.6-6.3c-0.3-0.3-0.3-0.8,0-1.1c0.3-0.3,0.8-0.3,1,0c1.9,2,3,4.6,3,7.3c0,2.8-1.1,5.4-3,7.3
|
||||
C35.4,33.5,35.2,33.5,35,33.5z M32.3,30.1c-0.2,0-0.4-0.1-0.5-0.2c-0.3-0.3-0.3-0.8,0-1.1c0.8-0.8,1.2-1.8,1.2-2.9
|
||||
c0-1.1-0.4-2.1-1.2-2.9c-0.3-0.3-0.3-0.8,0-1.1c0.3-0.3,0.8-0.3,1,0c1,1,1.6,2.4,1.6,3.9c0,1.5-0.6,2.9-1.6,3.9
|
||||
C32.7,30.1,32.5,30.1,32.3,30.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -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 = {
|
||||
|
||||
@@ -33,6 +33,6 @@
|
||||
border-top: 1px solid $ui-black-transparent;
|
||||
}
|
||||
|
||||
.menu-item-bordered:hover {
|
||||
.menu-item-danger:hover {
|
||||
background: $error-primary;
|
||||
}
|
||||
@@ -25,9 +25,17 @@ const BorderedMenuItem = props => (
|
||||
/>
|
||||
);
|
||||
|
||||
const DangerousMenuItem = props => (
|
||||
<MenuItem
|
||||
{...props}
|
||||
attributes={{className: classNames(styles.menuItem, styles.menuItemBordered, styles.menuItemDanger)}}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
export {
|
||||
BorderedMenuItem,
|
||||
DangerousMenuItem,
|
||||
StyledContextMenu as ContextMenu,
|
||||
StyledMenuItem as MenuItem
|
||||
};
|
||||
@@ -23,8 +23,6 @@ import BackdropLibrary from '../../containers/backdrop-library.jsx';
|
||||
import Watermark from '../../containers/watermark.jsx';
|
||||
|
||||
import Backpack from '../../containers/backpack.jsx';
|
||||
import PreviewModal from '../../containers/preview-modal.jsx';
|
||||
import ImportModal from '../../containers/import-modal.jsx';
|
||||
import WebGlModal from '../../containers/webgl-modal.jsx';
|
||||
import TipsLibrary from '../../containers/tips-library.jsx';
|
||||
import Cards from '../../containers/cards.jsx';
|
||||
@@ -80,7 +78,6 @@ const GUIComponent = props => {
|
||||
costumeLibraryVisible,
|
||||
costumesTabVisible,
|
||||
enableCommunity,
|
||||
importInfoVisible,
|
||||
intl,
|
||||
isCreating,
|
||||
isFullScreen,
|
||||
@@ -100,6 +97,7 @@ const GUIComponent = props => {
|
||||
onActivateTab,
|
||||
onClickLogo,
|
||||
onExtensionButtonClick,
|
||||
onProjectTelemetryEvent,
|
||||
onRequestCloseBackdropLibrary,
|
||||
onRequestCloseCostumeLibrary,
|
||||
onRequestCloseTelemetryModal,
|
||||
@@ -108,7 +106,6 @@ const GUIComponent = props => {
|
||||
onTelemetryModalCancel,
|
||||
onTelemetryModalOptIn,
|
||||
onTelemetryModalOptOut,
|
||||
previewInfoVisible,
|
||||
showComingSoon,
|
||||
soundsTabVisible,
|
||||
stageSizeMode,
|
||||
@@ -157,9 +154,6 @@ const GUIComponent = props => {
|
||||
dir={isRtl ? 'rtl' : 'ltr'}
|
||||
{...componentProps}
|
||||
>
|
||||
{previewInfoVisible ? (
|
||||
<PreviewModal />
|
||||
) : null}
|
||||
{telemetryModalVisible ? (
|
||||
<TelemetryModal
|
||||
onCancel={onTelemetryModalCancel}
|
||||
@@ -174,9 +168,6 @@ const GUIComponent = props => {
|
||||
{isCreating ? (
|
||||
<Loader messageId="gui.loader.creating" />
|
||||
) : null}
|
||||
{importInfoVisible ? (
|
||||
<ImportModal />
|
||||
) : null}
|
||||
{isRendererSupported ? null : (
|
||||
<WebGlModal isRtl={isRtl} />
|
||||
)}
|
||||
@@ -227,6 +218,7 @@ const GUIComponent = props => {
|
||||
onCloseAccountNav={onCloseAccountNav}
|
||||
onLogOut={onLogOut}
|
||||
onOpenRegistration={onOpenRegistration}
|
||||
onProjectTelemetryEvent={onProjectTelemetryEvent}
|
||||
onSeeCommunity={onSeeCommunity}
|
||||
onShare={onShare}
|
||||
onToggleLoginOpen={onToggleLoginOpen}
|
||||
@@ -379,7 +371,6 @@ GUIComponent.propTypes = {
|
||||
costumeLibraryVisible: PropTypes.bool,
|
||||
costumesTabVisible: PropTypes.bool,
|
||||
enableCommunity: PropTypes.bool,
|
||||
importInfoVisible: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
isCreating: PropTypes.bool,
|
||||
isFullScreen: PropTypes.bool,
|
||||
@@ -407,7 +398,6 @@ GUIComponent.propTypes = {
|
||||
onTelemetryModalOptOut: PropTypes.func,
|
||||
onToggleLoginOpen: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
previewInfoVisible: PropTypes.bool,
|
||||
renderLogin: PropTypes.func,
|
||||
showComingSoon: PropTypes.bool,
|
||||
soundsTabVisible: PropTypes.bool,
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
@import "../../css/typography.css";
|
||||
@import "../../css/z-index.css";
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: $z-index-modal;
|
||||
background-color: $ui-modal-overlay;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
border: .25rem solid $ui-white-transparent;
|
||||
padding: 0;
|
||||
border-radius: $space;
|
||||
user-select: none;
|
||||
width: 500px;
|
||||
color: $text-primary;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO figure out how to remove filter altogether
|
||||
since it is null...
|
||||
Modal header has 3 items:
|
||||
|x title filter|
|
||||
|
||||
Use the same width for both side item containers,
|
||||
so that title remains centered
|
||||
*/
|
||||
$sides: 20rem;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
height: $library-header-height;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
background-color: $extensions-primary;
|
||||
}
|
||||
|
||||
.header-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.header-item-filter {
|
||||
display: flex;
|
||||
flex-basis: $sides;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-item-title {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
letter-spacing: 0.4px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.header-item-title h2 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.header-item-close {
|
||||
display: flex;
|
||||
flex-basis: $sides;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
[dir="rtl"] .header-item-close img {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.body {
|
||||
background: $ui-white;
|
||||
padding: 1.5rem 2.25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
margin: 1.5rem 0;
|
||||
font-weight: bolder;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border: 1px solid;
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ok-input-container {
|
||||
border-color: $motion-primary;
|
||||
box-shadow: 0 0 0 0.2rem $motion-transparent;
|
||||
}
|
||||
|
||||
.bad-input-container {
|
||||
border-color: $data-primary;
|
||||
box-shadow: 0 0 0 0.2rem hsla(30, 100%, 55%, 0.15);
|
||||
}
|
||||
|
||||
.input-row input {
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
height: 3rem;
|
||||
color: $text-primary;
|
||||
font-size: .875rem;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.input-row input::placeholder {
|
||||
font-style: italic;
|
||||
color: $text-primary-transparent;
|
||||
}
|
||||
|
||||
.input-row button {
|
||||
padding: 0.5rem 2rem;
|
||||
font-weight: bold;
|
||||
font-size: .875rem;
|
||||
cursor: pointer;
|
||||
border: 0px solid $extensions-primary;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.input-row button.ok-button {
|
||||
background: $extensions-primary;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error-row {
|
||||
margin: 1.5rem 0;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: hsla(30, 100%, 55%, 0.25);
|
||||
color: $data-primary;
|
||||
border: 1px solid $data-primary;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.error-row p {
|
||||
font-size: 0.875rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Confirmation buttons at the bottom of the modal */
|
||||
.button-row {
|
||||
margin: 1.5rem 0;
|
||||
font-weight: bolder;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
border: 1px solid $motion-primary;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: white;
|
||||
font-weight: bold;
|
||||
font-size: .875rem;
|
||||
cursor: pointer;
|
||||
color: $motion-primary;
|
||||
}
|
||||
|
||||
.faq-link-text {
|
||||
margin: 2rem 0 .5rem 0;
|
||||
font-size: .875rem;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.faq-link {
|
||||
color: $motion-primary;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactModal from 'react-modal';
|
||||
import Box from '../box/box.jsx';
|
||||
import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import CloseButton from '../close-button/close-button.jsx';
|
||||
|
||||
import styles from './import-modal.css';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'gui.importInfo.title',
|
||||
defaultMessage: 'View a Scratch 2.0 Project',
|
||||
description: 'Scratch 2.0 import modal label - for accessibility'
|
||||
},
|
||||
formDescription: {
|
||||
defaultMessage:
|
||||
'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Beta will not be saved.',
|
||||
description: 'Import project message',
|
||||
id: 'gui.importInfo.betamessage'
|
||||
},
|
||||
previewFormDescription: {
|
||||
defaultMessage:
|
||||
'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Preview will not be saved.',
|
||||
description: 'Import project message',
|
||||
id: 'gui.importInfo.message'
|
||||
},
|
||||
invalidFormatError: {
|
||||
id: 'gui.importInfo.invalidFormatError',
|
||||
defaultMessage: 'Uh oh, that project link or id doesn\'t look quite right.',
|
||||
description: 'Invalid project link or id message'
|
||||
}
|
||||
});
|
||||
|
||||
const ImportModal = ({intl, ...props}) => (
|
||||
<ReactModal
|
||||
isOpen
|
||||
className={styles.modalContent}
|
||||
contentLabel={intl.formatMessage({...messages.title})}
|
||||
overlayClassName={styles.modalOverlay}
|
||||
onRequestClose={props.onCancel}
|
||||
>
|
||||
<div dir={props.isRtl ? 'rtl' : 'ltr'} >
|
||||
<Box>
|
||||
<div className={styles.header}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.headerItem,
|
||||
styles.headerItemClose
|
||||
)}
|
||||
>
|
||||
<CloseButton
|
||||
buttonType="back"
|
||||
size={CloseButton.SIZE_LARGE}
|
||||
onClick={props.onGoBack}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.headerItem,
|
||||
styles.headerItemTitle
|
||||
)}
|
||||
>
|
||||
<h2>
|
||||
{intl.formatMessage({...messages.title})}
|
||||
</h2>
|
||||
</div>
|
||||
<div className={classNames(styles.headerItem, styles.headerItemFilter)}>
|
||||
{null}
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<Box className={styles.body}>
|
||||
<p>
|
||||
{intl.formatMessage({...messages.formDescription})}
|
||||
</p>
|
||||
<Box
|
||||
className={classNames(styles.inputRow,
|
||||
(props.hasValidationError ? styles.badInputContainer : styles.okInputContainer))
|
||||
}
|
||||
>
|
||||
<input
|
||||
autoFocus
|
||||
placeholder={props.placeholder}
|
||||
value={props.inputValue}
|
||||
onChange={props.onChange}
|
||||
onKeyPress={props.onKeyPress}
|
||||
/>
|
||||
<button
|
||||
className={styles.okButton}
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: 'View Project',
|
||||
description: 'Tooltip for View button',
|
||||
id: 'gui.importModal.viewprojecttooltip'
|
||||
})}
|
||||
onClick={props.onViewProject}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="View"
|
||||
description="Label for button to load a scratch 2.0 project"
|
||||
id="gui.importModal.viewproject"
|
||||
/>
|
||||
</button>
|
||||
</Box>
|
||||
{props.hasValidationError ?
|
||||
<Box className={styles.errorRow}>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
{...messages[`${props.errorMessage}`]}
|
||||
/>
|
||||
</p>
|
||||
</Box> : null
|
||||
}
|
||||
<Box className={styles.buttonRow}>
|
||||
<button
|
||||
onClick={props.onGoBack}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Go Back"
|
||||
description="Label for button to back out of importing a project"
|
||||
id="gui.importInfo.goback"
|
||||
/>
|
||||
</button>
|
||||
</Box>
|
||||
<Box className={styles.faqLinkText}>
|
||||
<FormattedMessage
|
||||
defaultMessage="To learn more, go to the {previewFaqLink}."
|
||||
description="Invitation to try 3.0 preview"
|
||||
id="gui.importInfo.previewfaq"
|
||||
values={{
|
||||
previewFaqLink: (
|
||||
<a
|
||||
className={styles.faqLink}
|
||||
href="//scratch.mit.edu/3faq"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="FAQ"
|
||||
description="link to Scratch 3.0 FAQ page"
|
||||
id="gui.importInfo.previewfaqlinktext"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
|
||||
ImportModal.propTypes = {
|
||||
errorMessage: PropTypes.string.isRequired,
|
||||
hasValidationError: PropTypes.bool.isRequired,
|
||||
inputValue: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
isRtl: PropTypes.bool,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onGoBack: PropTypes.func.isRequired,
|
||||
onKeyPress: PropTypes.func.isRequired,
|
||||
onViewProject: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.string
|
||||
};
|
||||
|
||||
export default injectIntl(ImportModal);
|
||||
@@ -178,6 +178,10 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.featured-extension-metadata-detail img{
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.coming-soon-text {
|
||||
position: absolute;
|
||||
background-color: $data-primary;
|
||||
|
||||
@@ -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}>
|
||||
@@ -72,12 +75,12 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
<div
|
||||
className={styles.featuredExtensionMetadataDetail}
|
||||
>
|
||||
<img
|
||||
src={this.props.bluetoothRequired ?
|
||||
bluetoothIconURL :
|
||||
internetConnectionIconURL
|
||||
}
|
||||
/>
|
||||
{this.props.bluetoothRequired ? (
|
||||
<img src={bluetoothIconURL} />
|
||||
) : null}
|
||||
{this.props.internetConnectionRequired ? (
|
||||
<img src={internetConnectionIconURL} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -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,7 +9,7 @@ import Modal from '../../containers/modal.jsx';
|
||||
import Divider from '../divider/divider.jsx';
|
||||
import Filter from '../filter/filter.jsx';
|
||||
import TagButton from '../../containers/tag-button.jsx';
|
||||
import analytics from '../../lib/analytics';
|
||||
import storage from '../../lib/storage';
|
||||
|
||||
import styles from './library.css';
|
||||
|
||||
@@ -29,6 +29,47 @@ const messages = defineMessages({
|
||||
const ALL_TAG = {tag: 'all', intlLabel: messages.allTag};
|
||||
const tagListPrefix = [ALL_TAG];
|
||||
|
||||
/**
|
||||
* Find the AssetType which corresponds to a particular file extension. For example, 'png' => AssetType.ImageBitmap.
|
||||
* @param {string} fileExtension - the file extension to look up.
|
||||
* @returns {AssetType} - the AssetType corresponding to the extension, if any.
|
||||
*/
|
||||
const getAssetTypeForFileExtension = function (fileExtension) {
|
||||
const compareOptions = {
|
||||
sensitivity: 'accent',
|
||||
usage: 'search'
|
||||
};
|
||||
for (const assetTypeId in storage.AssetType) {
|
||||
const assetType = storage.AssetType[assetTypeId];
|
||||
if (fileExtension.localeCompare(assetType.runtimeFormat, compareOptions) === 0) {
|
||||
return assetType;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Figure out an `imageSource` (URI or asset ID & type) for a library item's icon.
|
||||
* @param {object} item - either a library item or one of a library item's costumes.
|
||||
* @returns {object} - an `imageSource` ready to be passed to a `ScratchImage`.
|
||||
*/
|
||||
const getItemImageSource = function (item) {
|
||||
if (item.rawURL) {
|
||||
return {
|
||||
uri: item.rawURL
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: adjust libraries to be more storage-friendly; don't use split() here.
|
||||
const md5 = item.md5 || item.baseLayerMD5;
|
||||
if (md5) {
|
||||
const [assetId, fileExtension] = md5.split('.');
|
||||
return {
|
||||
assetId: assetId,
|
||||
assetType: getAssetTypeForFileExtension(fileExtension)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class LibraryComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -60,7 +101,6 @@ class LibraryComponent extends React.Component {
|
||||
}
|
||||
handleClose () {
|
||||
this.props.onRequestClose();
|
||||
analytics.pageview(`/${this.props.id}/search?q=${this.state.filterQuery}`);
|
||||
}
|
||||
handleTagClick (tag) {
|
||||
this.setState({
|
||||
@@ -164,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}
|
||||
@@ -173,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}
|
||||
@@ -184,8 +225,8 @@ class LibraryComponent extends React.Component {
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
))}
|
||||
/>);
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -120,15 +120,13 @@ class LoaderComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
messageNumber: 0
|
||||
messageNumber: this.chooseRandomMessage()
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
this.chooseRandomMessage();
|
||||
|
||||
// Start an interval to choose a new message every 5 seconds
|
||||
this.intervalId = setInterval(() => {
|
||||
this.chooseRandomMessage();
|
||||
this.setState({messageNumber: this.chooseRandomMessage()});
|
||||
}, 5000);
|
||||
}
|
||||
componentWillUnmount () {
|
||||
@@ -145,7 +143,7 @@ class LoaderComponent extends React.Component {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.setState({messageNumber});
|
||||
return messageNumber;
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
color: $ui-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -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,11 +1,14 @@
|
||||
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';
|
||||
import bowser from 'bowser';
|
||||
import React from 'react';
|
||||
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import Button from '../button/button.jsx';
|
||||
import CommunityButton from './community-button.jsx';
|
||||
@@ -25,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';
|
||||
@@ -55,11 +59,12 @@ import {
|
||||
loginMenuOpen
|
||||
} from '../../reducers/menus';
|
||||
|
||||
import collectMetadata from '../../lib/collect-metadata';
|
||||
|
||||
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';
|
||||
@@ -67,13 +72,8 @@ import languageIcon from '../language-selector/language-icon.svg';
|
||||
|
||||
import scratchLogo from './scratch-logo.svg';
|
||||
|
||||
const messages = defineMessages({
|
||||
confirmNav: {
|
||||
id: 'gui.menuBar.confirmNewWithoutSaving',
|
||||
defaultMessage: 'Replace contents of the current project?',
|
||||
description: 'message for prompting user to confirm that they want to create new project without saving'
|
||||
}
|
||||
});
|
||||
import sharedMessages from '../../lib/shared-messages';
|
||||
|
||||
const ariaMessages = defineMessages({
|
||||
language: {
|
||||
id: 'gui.menuBar.LanguageSelector',
|
||||
@@ -151,10 +151,10 @@ class MenuBar extends React.Component {
|
||||
'handleClickSaveAsCopy',
|
||||
'handleClickSeeCommunity',
|
||||
'handleClickShare',
|
||||
'handleCloseFileMenuAndThen',
|
||||
'handleKeyPress',
|
||||
'handleLanguageMouseUp',
|
||||
'handleRestoreOption',
|
||||
'handleSaveToComputer',
|
||||
'restoreOptionMessage'
|
||||
]);
|
||||
}
|
||||
@@ -165,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(messages.confirmNav)
|
||||
);
|
||||
}
|
||||
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);
|
||||
@@ -195,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
|
||||
@@ -221,12 +218,6 @@ class MenuBar extends React.Component {
|
||||
this.props.onRequestCloseEdit();
|
||||
};
|
||||
}
|
||||
handleCloseFileMenuAndThen (fn) {
|
||||
return () => {
|
||||
this.props.onRequestCloseFile();
|
||||
fn();
|
||||
};
|
||||
}
|
||||
handleKeyPress (event) {
|
||||
const modifier = bowser.mac ? event.metaKey : event.ctrlKey;
|
||||
if (modifier && event.key === 's') {
|
||||
@@ -234,6 +225,16 @@ class MenuBar extends React.Component {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
handleSaveToComputer (downloadProjectCallback) {
|
||||
return () => {
|
||||
this.props.onRequestCloseFile();
|
||||
downloadProjectCallback();
|
||||
if (this.props.onProjectTelemetryEvent) {
|
||||
const metadata = collectMetadata(this.props.vm, this.props.projectTitle, this.props.locale);
|
||||
this.props.onProjectTelemetryEvent('projectDidSave', metadata);
|
||||
}
|
||||
};
|
||||
}
|
||||
handleLanguageMouseUp (e) {
|
||||
if (!this.props.languageMenuOpen) {
|
||||
this.props.onClickLanguage(e);
|
||||
@@ -390,27 +391,25 @@ 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>
|
||||
)}
|
||||
</SBFileUploader>
|
||||
<SB3Downloader>{(className, downloadProject) => (
|
||||
<SB3Downloader>{(className, downloadProjectCallback) => (
|
||||
<MenuItem
|
||||
className={className}
|
||||
onClick={this.handleCloseFileMenuAndThen(downloadProject)}
|
||||
onClick={this.handleSaveToComputer(downloadProjectCallback)}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Save to your computer"
|
||||
@@ -643,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">
|
||||
@@ -725,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,
|
||||
@@ -734,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,
|
||||
@@ -748,6 +730,7 @@ MenuBar.propTypes = {
|
||||
onLogOut: PropTypes.func,
|
||||
onOpenRegistration: PropTypes.func,
|
||||
onOpenTipLibrary: PropTypes.func,
|
||||
onProjectTelemetryEvent: PropTypes.func,
|
||||
onRequestCloseAccount: PropTypes.func,
|
||||
onRequestCloseEdit: PropTypes.func,
|
||||
onRequestCloseFile: PropTypes.func,
|
||||
@@ -757,19 +740,21 @@ 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,
|
||||
username: PropTypes.string
|
||||
userOwnsProject: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -780,11 +765,14 @@ const mapStateToProps = state => {
|
||||
isUpdating: getIsUpdating(loadingState),
|
||||
isShowingProject: getIsShowingProject(loadingState),
|
||||
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
|
||||
username: user ? user.username : null,
|
||||
userOwnsProject: ownProps.authorUsername && user &&
|
||||
(ownProps.authorUsername === user.username),
|
||||
vm: state.scratchGui.vm
|
||||
};
|
||||
};
|
||||
|
||||
@@ -808,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);
|
||||
@@ -37,6 +37,10 @@
|
||||
background-color: $ui-black-transparent;
|
||||
}
|
||||
|
||||
.menu-item.hoverable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
border-top: 1px solid $ui-black-transparent;
|
||||
}
|
||||
@@ -39,7 +39,11 @@ const MenuItem = ({
|
||||
onClick
|
||||
}) => (
|
||||
<li
|
||||
className={classNames(styles.menuItem, className)}
|
||||
className={classNames(
|
||||
styles.menuItem,
|
||||
styles.hoverable,
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -27,6 +27,7 @@ const MonitorList = props => (
|
||||
draggable={props.draggable}
|
||||
height={monitorData.height}
|
||||
id={monitorData.id}
|
||||
isDiscrete={monitorData.isDiscrete}
|
||||
key={monitorData.id}
|
||||
max={monitorData.sliderMax}
|
||||
min={monitorData.sliderMin}
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import Draggable from 'react-draggable';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
import {ContextMenuTrigger} from 'react-contextmenu';
|
||||
import {ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
|
||||
import {BorderedMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
|
||||
import Box from '../box/box.jsx';
|
||||
import DefaultMonitor from './default-monitor.jsx';
|
||||
import LargeMonitor from './large-monitor.jsx';
|
||||
@@ -84,6 +84,14 @@ const MonitorComponent = props => (
|
||||
id="gui.monitor.contextMenu.slider"
|
||||
/>
|
||||
</MenuItem>}
|
||||
{props.onSliderPromptOpen && props.mode === 'slider' &&
|
||||
<BorderedMenuItem onClick={props.onSliderPromptOpen}>
|
||||
<FormattedMessage
|
||||
defaultMessage="change slider range"
|
||||
description="Menu item to change the slider range"
|
||||
id="gui.monitor.contextMenu.sliderRange"
|
||||
/>
|
||||
</BorderedMenuItem>}
|
||||
{props.onImport &&
|
||||
<MenuItem onClick={props.onImport}>
|
||||
<FormattedMessage
|
||||
@@ -122,7 +130,8 @@ MonitorComponent.propTypes = {
|
||||
onNextMode: PropTypes.func.isRequired,
|
||||
onSetModeToDefault: PropTypes.func,
|
||||
onSetModeToLarge: PropTypes.func,
|
||||
onSetModeToSlider: PropTypes.func
|
||||
onSetModeToSlider: PropTypes.func,
|
||||
onSliderPromptOpen: PropTypes.func
|
||||
};
|
||||
|
||||
MonitorComponent.defaultProps = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import classNames from 'classnames';
|
||||
|
||||
import styles from './monitor.css';
|
||||
|
||||
const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate}) => (
|
||||
const SliderMonitor = ({categoryColor, isDiscrete, label, min, max, value, onSliderUpdate}) => (
|
||||
<div className={styles.defaultMonitor}>
|
||||
<div className={styles.row}>
|
||||
<div className={styles.label}>
|
||||
@@ -22,6 +22,7 @@ const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate})
|
||||
className={classNames(styles.slider, 'no-drag')} // Class used on parent Draggable to prevent drags
|
||||
max={max}
|
||||
min={min}
|
||||
step={isDiscrete ? 1 : 0.01}
|
||||
type="range"
|
||||
value={value}
|
||||
onChange={onSliderUpdate}
|
||||
@@ -33,6 +34,7 @@ const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate})
|
||||
|
||||
SliderMonitor.propTypes = {
|
||||
categoryColor: PropTypes.string.isRequired,
|
||||
isDiscrete: PropTypes.bool,
|
||||
label: PropTypes.string.isRequired,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
@@ -44,6 +46,7 @@ SliderMonitor.propTypes = {
|
||||
};
|
||||
|
||||
SliderMonitor.defaultProps = {
|
||||
isDiscrete: true,
|
||||
min: 0,
|
||||
max: 100
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,108 +0,0 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
@import "../../css/typography.css";
|
||||
@import "../../css/z-index.css";
|
||||
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: $z-index-modal;
|
||||
background-color: $ui-modal-overlay;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 100px auto;
|
||||
outline: none;
|
||||
border: .25rem solid $ui-white-transparent;
|
||||
padding: 0;
|
||||
border-radius: $space;
|
||||
user-select: none;
|
||||
width: 500px;
|
||||
|
||||
color: $text-primary;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
width: 100%;
|
||||
height: 208px;
|
||||
background-color: $motion-primary;
|
||||
background-image: url('./welcome.png');
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.body {
|
||||
background: $ui-white;
|
||||
padding: 1.5rem 2.25rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Confirmation buttons at the bottom of the modal */
|
||||
.button-row {
|
||||
margin: 1.5rem 0;
|
||||
font-weight: bolder;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
border: 1px solid $motion-primary;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
background: white;
|
||||
font-weight: bold;
|
||||
font-size: .875rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-row button.ok-button {
|
||||
background: $motion-primary;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.button-row button.no-button {
|
||||
color: $motion-primary;
|
||||
}
|
||||
|
||||
.button-row button.view-project-button {
|
||||
background: $extensions-primary;
|
||||
border-color: $extensions-primary;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[dir="ltr"] .button-row button + button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .button-row button + button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.cat-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[dir="ltr"] .cat-icon {
|
||||
margin-left: .125rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .cat-icon {
|
||||
margin-right: .125rem;
|
||||
}
|
||||
|
||||
.faq-link-text {
|
||||
margin: 2rem 0 .5rem 0;
|
||||
font-size: .875rem;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.faq-link {
|
||||
color: $motion-primary;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactModal from 'react-modal';
|
||||
import Box from '../box/box.jsx';
|
||||
import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
|
||||
|
||||
import styles from './preview-modal.css';
|
||||
import catIcon from './happy-cat.png';
|
||||
|
||||
const messages = defineMessages({
|
||||
label: {
|
||||
id: 'gui.previewInfo.label',
|
||||
defaultMessage: 'Try Scratch 3.0',
|
||||
description: 'Scratch 3.0 modal label - for accessibility'
|
||||
},
|
||||
previewWelcome: {
|
||||
defaultMessage: 'Welcome to the Scratch 3.0 Beta',
|
||||
description: 'Header for Preview Info Modal',
|
||||
id: 'gui.previewInfo.welcome'
|
||||
},
|
||||
notNowTooltip: {
|
||||
defaultMessage: 'Not Now',
|
||||
description: 'Tooltip for Not Now button',
|
||||
id: 'gui.previewModal.notnowtooltip'
|
||||
},
|
||||
tryItTooltip: {
|
||||
defaultMessage: 'Try It',
|
||||
description: 'Tooltip for Try It button',
|
||||
id: 'gui.previewModal.tryittooltip'
|
||||
},
|
||||
viewProjectTooltip: {
|
||||
defaultMessage: 'View 2.0 Project',
|
||||
description: 'Tooltip for View 2.0 Project button',
|
||||
id: 'gui.previewModal.viewprojecttooltip'
|
||||
}
|
||||
});
|
||||
|
||||
const PreviewModal = ({intl, ...props}) => (
|
||||
<ReactModal
|
||||
isOpen
|
||||
className={styles.modalContent}
|
||||
contentLabel={intl.formatMessage({...messages.label})}
|
||||
overlayClassName={styles.modalOverlay}
|
||||
onRequestClose={props.onTryIt}
|
||||
>
|
||||
<div dir={props.isRtl ? 'rtl' : 'ltr'} >
|
||||
<Box className={styles.illustration} />
|
||||
|
||||
<Box className={styles.body}>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="Welcome to the Scratch 3.0 Beta"
|
||||
description="Header for Beta Info Modal"
|
||||
id="gui.previewInfo.betawelcome"
|
||||
/>
|
||||
</h2>
|
||||
<p>
|
||||
{ /* eslint-disable max-len */ }
|
||||
<FormattedMessage
|
||||
defaultMessage="We're working on the next generation of Scratch. We're excited for you to try it!"
|
||||
description="Invitation to try 3.0 Beta"
|
||||
id="gui.previewInfo.invitation"
|
||||
/>
|
||||
{ /* eslint-enable max-len */ }
|
||||
</p>
|
||||
|
||||
<Box className={styles.buttonRow}>
|
||||
<button
|
||||
className={styles.noButton}
|
||||
title={intl.formatMessage(messages.notNowTooltip)}
|
||||
onClick={props.onCancel}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Not Now"
|
||||
description="Label for button to back out of trying Scratch 3.0 Beta"
|
||||
id="gui.previewInfo.notnow"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={styles.okButton}
|
||||
title={intl.formatMessage(messages.tryItTooltip)}
|
||||
onClick={props.onTryIt}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Try It! {caticon}"
|
||||
description="Label for button to try Scratch 3.0 Beta"
|
||||
id="gui.previewModal.tryit"
|
||||
values={{
|
||||
caticon: (
|
||||
<img
|
||||
className={styles.catIcon}
|
||||
src={catIcon}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={styles.viewProjectButton}
|
||||
title={intl.formatMessage(messages.viewProjectTooltip)}
|
||||
onClick={props.onViewProject}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="View 2.0 Project"
|
||||
description="Label for button to import a 2.0 project"
|
||||
id="gui.previewModal.viewproject"
|
||||
/>
|
||||
</button>
|
||||
</Box>
|
||||
<Box className={styles.faqLinkText}>
|
||||
<FormattedMessage
|
||||
defaultMessage="To learn more, go to the {previewFaqLink}."
|
||||
description="Invitation to try 3.0 Beta"
|
||||
id="gui.previewInfo.previewfaq"
|
||||
values={{
|
||||
previewFaqLink: (
|
||||
<a
|
||||
className={styles.faqLink}
|
||||
href="//scratch.mit.edu/3faq"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="FAQ"
|
||||
description="link to Scratch 3.0 FAQ page"
|
||||
id="gui.previewInfo.previewfaqlinktext"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</ReactModal>
|
||||
);
|
||||
|
||||
PreviewModal.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
isRtl: PropTypes.bool,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onTryIt: PropTypes.func.isRequired,
|
||||
onViewProject: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(PreviewModal);
|
||||
|
Before Width: | Height: | Size: 102 KiB |
@@ -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;
|
||||
@@ -0,0 +1,55 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
|
||||
.modal-content {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.body {
|
||||
background: $ui-white;
|
||||
padding: 1.5rem 2.25rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
|
||||
.min-input, .max-input {
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
border: 1px solid $ui-black-transparent;
|
||||
border-radius: 5px;
|
||||
padding: 0 1rem;
|
||||
height: 3rem;
|
||||
color: $text-primary-transparent;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
font-weight: bolder;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
background: white;
|
||||
border: 1px solid $ui-black-transparent;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.button-row button.ok-button {
|
||||
background: $motion-primary;
|
||||
border: $motion-primary;
|
||||
color: white;
|
||||
}
|
||||
|
||||
[dir="ltr"] .button-row button + button {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
[dir="rtl"] .button-row button + button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import {defineMessages, FormattedMessage, intlShape, injectIntl} from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import Modal from '../../containers/modal.jsx';
|
||||
|
||||
import styles from './slider-prompt.css';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
minValue: {
|
||||
defaultMessage: 'Minimum value',
|
||||
description: 'Label of slider modal',
|
||||
id: 'gui.sliderModal.min'
|
||||
},
|
||||
maxValue: {
|
||||
defaultMessage: 'Maximum value',
|
||||
description: 'Label of slider modal',
|
||||
id: 'gui.sliderModal.max'
|
||||
},
|
||||
title: {
|
||||
defaultMessage: 'Change slider range',
|
||||
description: 'Title of slider modal',
|
||||
id: 'gui.sliderModal.title'
|
||||
}
|
||||
});
|
||||
|
||||
const SliderPromptComponent = props => (
|
||||
<Modal
|
||||
className={styles.modalContent}
|
||||
contentLabel={props.intl.formatMessage(messages.title)}
|
||||
id="sliderPrompt"
|
||||
onRequestClose={props.onCancel}
|
||||
>
|
||||
<Box className={styles.body}>
|
||||
<Box className={styles.label}>
|
||||
{props.intl.formatMessage(messages.minValue)}
|
||||
</Box>
|
||||
<Box>
|
||||
<input
|
||||
className={styles.minInput}
|
||||
name={props.intl.formatMessage(messages.minValue)}
|
||||
pattern="-?[0-9]*(\.[0-9]+)?"
|
||||
type="text"
|
||||
value={props.minValue}
|
||||
onChange={props.onChangeMin}
|
||||
onKeyPress={props.onKeyPress}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={styles.label}>
|
||||
{props.intl.formatMessage(messages.maxValue)}
|
||||
</Box>
|
||||
<Box>
|
||||
<input
|
||||
className={styles.maxInput}
|
||||
name={props.intl.formatMessage(messages.maxValue)}
|
||||
pattern="-?[0-9]*(\.[0-9]+)?"
|
||||
type="text"
|
||||
value={props.maxValue}
|
||||
onChange={props.onChangeMax}
|
||||
onKeyPress={props.onKeyPress}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={styles.buttonRow}>
|
||||
<button
|
||||
className={styles.cancelButton}
|
||||
onClick={props.onCancel}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Cancel"
|
||||
description="Button in prompt for cancelling the dialog"
|
||||
id="gui.sliderPrompt.cancel"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className={styles.okButton}
|
||||
onClick={props.onOk}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="OK"
|
||||
description="Button in prompt for confirming the dialog"
|
||||
id="gui.sliderPrompt.ok"
|
||||
/>
|
||||
</button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
SliderPromptComponent.propTypes = {
|
||||
intl: intlShape,
|
||||
maxValue: PropTypes.string,
|
||||
minValue: PropTypes.string,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onChangeMax: PropTypes.func.isRequired,
|
||||
onChangeMin: PropTypes.func.isRequired,
|
||||
onKeyPress: PropTypes.func.isRequired,
|
||||
onOk: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default injectIntl(SliderPromptComponent);
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import CloseButton from '../close-button/close-button.jsx';
|
||||
import styles from './sprite-selector-item.css';
|
||||
import {ContextMenuTrigger} from 'react-contextmenu';
|
||||
import {BorderedMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
|
||||
import {DangerousMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
// react-contextmenu requires unique id to match trigger and context menu
|
||||
@@ -74,13 +74,13 @@ const SpriteSelectorItem = props => (
|
||||
</MenuItem>
|
||||
) : null }
|
||||
{props.onDeleteButtonClick ? (
|
||||
<BorderedMenuItem onClick={props.onDeleteButtonClick}>
|
||||
<DangerousMenuItem onClick={props.onDeleteButtonClick}>
|
||||
<FormattedMessage
|
||||
defaultMessage="delete"
|
||||
description="Menu item to delete in the right click menu"
|
||||
id="gui.spriteSelectorItem.contextMenuDelete"
|
||||
/>
|
||||
</BorderedMenuItem>
|
||||
</DangerousMenuItem>
|
||||
) : null }
|
||||
</ContextMenu>
|
||||
) : null}
|
||||
|
||||
@@ -120,9 +120,10 @@ const SpriteSelectorComponent = function (props) {
|
||||
title: intl.formatMessage(messages.addSpriteFromFile),
|
||||
img: fileUploadIcon,
|
||||
onClick: onFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg, .sprite2, .sprite3',
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg, .sprite2, .sprite3, .gif',
|
||||
fileChange: onSpriteUpload,
|
||||
fileInput: spriteFileInput
|
||||
fileInput: spriteFileInput,
|
||||
fileMultiple: true
|
||||
}, {
|
||||
title: intl.formatMessage(messages.addSpriteFromSurprise),
|
||||
img: surpriseIcon,
|
||||
|
||||
@@ -102,9 +102,10 @@ const StageSelector = props => {
|
||||
title: intl.formatMessage(messages.addBackdropFromFile),
|
||||
img: fileUploadIcon,
|
||||
onClick: onBackdropFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg', // Bitmap coming soon
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg, .gif',
|
||||
fileChange: onBackdropFileUpload,
|
||||
fileInput: fileInputRef
|
||||
fileInput: fileInputRef,
|
||||
fileMultiple: true
|
||||
}, {
|
||||
title: intl.formatMessage(messages.addBackdropFromSurprise),
|
||||
img: surpriseIcon,
|
||||
|
||||
@@ -128,6 +128,7 @@ to adjust for the border using a different method */
|
||||
}
|
||||
|
||||
.question-wrapper {
|
||||
z-index: $z-index-stage-question;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
import {connect} from 'react-redux';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import backdropLibraryContent from '../lib/libraries/backdrops.json';
|
||||
import backdropTags from '../lib/libraries/backdrop-tags';
|
||||
import LibraryComponent from '../components/library/library.jsx';
|
||||
@@ -36,11 +35,6 @@ class BackdropLibrary extends React.Component {
|
||||
};
|
||||
this.props.vm.setEditingTarget(this.props.stageID);
|
||||
this.props.vm.addBackdrop(item.md5, vmBackdrop);
|
||||
analytics.event({
|
||||
category: 'library',
|
||||
action: 'Select Backdrop',
|
||||
label: item.name
|
||||
});
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
|
||||
@@ -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.
|
||||
@@ -162,6 +177,7 @@ class Blocks extends React.Component {
|
||||
this.setLocale();
|
||||
} else {
|
||||
this.props.vm.refreshWorkspace();
|
||||
this.requestToolboxUpdate();
|
||||
}
|
||||
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
activateDeck,
|
||||
closeCards,
|
||||
shrinkExpandCards,
|
||||
nextStep,
|
||||
prevStep,
|
||||
dragCard,
|
||||
@@ -15,12 +18,36 @@ import {
|
||||
} from '../reducers/modals';
|
||||
|
||||
import CardsComponent from '../components/cards/cards.jsx';
|
||||
import {loadImageData} from '../lib/libraries/decks/translate-image.js';
|
||||
|
||||
class Cards extends React.Component {
|
||||
componentDidMount () {
|
||||
if (this.props.locale !== 'en') {
|
||||
loadImageData(this.props.locale);
|
||||
}
|
||||
}
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.props.locale !== prevProps.locale) {
|
||||
loadImageData(this.props.locale);
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<CardsComponent {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Cards.propTypes = {
|
||||
locale: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
visible: state.scratchGui.cards.visible,
|
||||
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,
|
||||
@@ -35,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)),
|
||||
@@ -45,4 +73,4 @@ const mapDispatchToProps = dispatch => ({
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CardsComponent);
|
||||
)(Cards);
|
||||
@@ -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}
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
import VM from 'scratch-vm';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import ControlsComponent from '../components/controls/controls.jsx';
|
||||
|
||||
class Controls extends React.Component {
|
||||
@@ -24,19 +23,11 @@ class Controls extends React.Component {
|
||||
this.props.vm.start();
|
||||
}
|
||||
this.props.vm.greenFlag();
|
||||
analytics.event({
|
||||
category: 'general',
|
||||
action: 'Green Flag'
|
||||
});
|
||||
}
|
||||
}
|
||||
handleStopAllClick (e) {
|
||||
e.preventDefault();
|
||||
this.props.vm.stopAll();
|
||||
analytics.event({
|
||||
category: 'general',
|
||||
action: 'Stop Button'
|
||||
});
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import costumeLibraryContent from '../lib/libraries/costumes.json';
|
||||
import spriteTags from '../lib/libraries/sprite-tags';
|
||||
import LibraryComponent from '../components/library/library.jsx';
|
||||
@@ -38,11 +37,6 @@ class CostumeLibrary extends React.PureComponent {
|
||||
skinId: null
|
||||
};
|
||||
this.props.vm.addCostumeFromLibrary(item.md5, vmCostume);
|
||||
analytics.event({
|
||||
category: 'library',
|
||||
action: 'Select Costume',
|
||||
label: item.name
|
||||
});
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from '../reducers/editor-tab';
|
||||
|
||||
import {setRestore} from '../reducers/restore-deletion';
|
||||
import {showStandardAlert, closeAlertWithId} from '../reducers/alerts';
|
||||
|
||||
import addLibraryBackdropIcon from '../components/asset-panel/icon--add-backdrop-lib.svg';
|
||||
import addLibraryCostumeIcon from '../components/asset-panel/icon--add-costume-lib.svg';
|
||||
@@ -158,11 +159,14 @@ class CostumeTab extends React.Component {
|
||||
downloadBlob(`${item.name}.${item.asset.dataFormat}`, blob);
|
||||
}
|
||||
handleNewCostume (costume, fromCostumeLibrary) {
|
||||
if (fromCostumeLibrary) {
|
||||
this.props.vm.addCostumeFromLibrary(costume.md5, costume);
|
||||
} else {
|
||||
this.props.vm.addCostume(costume.md5, costume);
|
||||
}
|
||||
const costumes = Array.isArray(costume) ? costume : [costume];
|
||||
|
||||
return Promise.all(costumes.map(c => {
|
||||
if (fromCostumeLibrary) {
|
||||
return this.props.vm.addCostumeFromLibrary(c.md5, c);
|
||||
}
|
||||
return this.props.vm.addCostume(c.md5, c);
|
||||
}));
|
||||
}
|
||||
handleNewBlankCostume () {
|
||||
const name = this.props.vm.editingTarget.isStage ?
|
||||
@@ -200,14 +204,26 @@ class CostumeTab extends React.Component {
|
||||
}
|
||||
handleCostumeUpload (e) {
|
||||
const storage = this.props.vm.runtime.storage;
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName) => {
|
||||
costumeUpload(buffer, fileType, fileName, storage, this.handleNewCostume);
|
||||
});
|
||||
this.props.onShowImporting();
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
|
||||
costumeUpload(buffer, fileType, storage, vmCostumes => {
|
||||
vmCostumes.forEach((costume, i) => {
|
||||
costume.name = `${fileName}${i ? i + 1 : ''}`;
|
||||
});
|
||||
this.handleNewCostume(vmCostumes).then(() => {
|
||||
if (fileIndex === fileCount - 1) {
|
||||
this.props.onCloseImporting();
|
||||
}
|
||||
});
|
||||
}, this.props.onCloseImporting);
|
||||
}, this.props.onCloseImporting);
|
||||
}
|
||||
handleCameraBuffer (buffer) {
|
||||
const storage = this.props.vm.runtime.storage;
|
||||
const name = this.props.intl.formatMessage(messages.costume, {index: 1});
|
||||
costumeUpload(buffer, 'image/png', name, storage, this.handleNewCostume);
|
||||
costumeUpload(buffer, 'image/png', storage, vmCostumes => {
|
||||
vmCostumes[0].name = this.props.intl.formatMessage(messages.costume, {index: 1});
|
||||
this.handleNewCostume(vmCostumes);
|
||||
});
|
||||
}
|
||||
handleFileUploadClick () {
|
||||
this.fileInput.click();
|
||||
@@ -291,9 +307,10 @@ class CostumeTab extends React.Component {
|
||||
title: intl.formatMessage(addFileMessage),
|
||||
img: fileUploadIcon,
|
||||
onClick: this.handleFileUploadClick,
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg',
|
||||
fileAccept: '.svg, .png, .jpg, .jpeg, .gif',
|
||||
fileChange: this.handleCostumeUpload,
|
||||
fileInput: this.setFileInput
|
||||
fileInput: this.setFileInput,
|
||||
fileMultiple: true
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.addSurpriseCostumeMsg),
|
||||
@@ -346,10 +363,12 @@ CostumeTab.propTypes = {
|
||||
intl: intlShape,
|
||||
isRtl: PropTypes.bool,
|
||||
onActivateSoundsTab: PropTypes.func.isRequired,
|
||||
onCloseImporting: PropTypes.func.isRequired,
|
||||
onNewCostumeFromCameraClick: PropTypes.func.isRequired,
|
||||
onNewLibraryBackdropClick: PropTypes.func.isRequired,
|
||||
onNewLibraryCostumeClick: PropTypes.func.isRequired,
|
||||
onRequestCloseCameraModal: PropTypes.func.isRequired,
|
||||
onShowImporting: PropTypes.func.isRequired,
|
||||
sprites: PropTypes.shape({
|
||||
id: PropTypes.shape({
|
||||
costumes: PropTypes.arrayOf(PropTypes.shape({
|
||||
@@ -394,7 +413,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
dispatchUpdateRestore: restoreState => {
|
||||
dispatch(setRestore(restoreState));
|
||||
}
|
||||
},
|
||||
onCloseImporting: () => dispatch(closeAlertWithId('importingAsset')),
|
||||
onShowImporting: () => dispatch(showStandardAlert('importingAsset'))
|
||||
});
|
||||
|
||||
export default errorBoundaryHOC('Costume Tab')(
|
||||
|
||||
@@ -6,7 +6,6 @@ import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import extensionLibraryContent from '../lib/libraries/extensions/index.jsx';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import LibraryComponent from '../components/library/library.jsx';
|
||||
import extensionIcon from '../components/action-menu/icon--sprite.svg';
|
||||
|
||||
@@ -46,18 +45,6 @@ class ExtensionLibrary extends React.PureComponent {
|
||||
});
|
||||
}
|
||||
}
|
||||
let gaLabel = '';
|
||||
if (typeof (item.name) === 'string') {
|
||||
gaLabel = item.name;
|
||||
} else {
|
||||
// Name is localized, get the default message for the gaLabel
|
||||
gaLabel = item.name.props.defaultMessage;
|
||||
}
|
||||
analytics.event({
|
||||
category: 'library',
|
||||
action: 'Select Extension',
|
||||
label: gaLabel
|
||||
});
|
||||
}
|
||||
render () {
|
||||
const extensionLibraryThumbnailData = extensionLibraryContent.map(extension => ({
|
||||
|
||||
@@ -119,7 +119,6 @@ GUI.propTypes = {
|
||||
cloudHost: PropTypes.string,
|
||||
error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||
fetchingProject: PropTypes.bool,
|
||||
importInfoVisible: PropTypes.bool,
|
||||
intl: intlShape,
|
||||
isError: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
@@ -132,7 +131,6 @@ GUI.propTypes = {
|
||||
onUpdateProjectId: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
onUpdateReduxProjectTitle: PropTypes.func,
|
||||
previewInfoVisible: PropTypes.bool,
|
||||
projectHost: PropTypes.string,
|
||||
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
projectTitle: PropTypes.string,
|
||||
@@ -159,14 +157,12 @@ const mapStateToProps = state => {
|
||||
costumeLibraryVisible: state.scratchGui.modals.costumeLibrary,
|
||||
costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
|
||||
error: state.scratchGui.projectState.error,
|
||||
importInfoVisible: state.scratchGui.modals.importInfo,
|
||||
isError: getIsError(loadingState),
|
||||
isFullScreen: state.scratchGui.mode.isFullScreen,
|
||||
isPlayerOnly: state.scratchGui.mode.isPlayerOnly,
|
||||
isRtl: state.locales.isRtl,
|
||||
isShowingProject: getIsShowingProject(loadingState),
|
||||
loadingStateVisible: state.scratchGui.modals.loadingProject,
|
||||
previewInfoVisible: state.scratchGui.modals.previewInfo,
|
||||
projectId: state.scratchGui.projectState.projectId,
|
||||
soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX,
|
||||
targetIsStage: (
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import ImportModalComponent from '../components/import-modal/import-modal.jsx';
|
||||
|
||||
import {
|
||||
closeImportInfo,
|
||||
openPreviewInfo
|
||||
} from '../reducers/modals';
|
||||
|
||||
class ImportModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleViewProject',
|
||||
'handleCancel',
|
||||
'handleChange',
|
||||
'handleGoBack',
|
||||
'handleKeyPress'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
inputValue: '',
|
||||
hasValidationError: false,
|
||||
errorMessage: ''
|
||||
};
|
||||
}
|
||||
handleKeyPress (event) {
|
||||
if (event.key === 'Enter') this.handleViewProject();
|
||||
}
|
||||
handleViewProject () {
|
||||
const inputValue = this.state.inputValue;
|
||||
const projectId = this.validate(inputValue);
|
||||
|
||||
if (projectId) {
|
||||
const projectLink = document.createElement('a');
|
||||
document.body.appendChild(projectLink);
|
||||
projectLink.href = `#${projectId}`;
|
||||
projectLink.click();
|
||||
document.body.removeChild(projectLink);
|
||||
this.props.onViewProject();
|
||||
} else {
|
||||
this.setState({
|
||||
hasValidationError: true,
|
||||
errorMessage: `invalidFormatError`});
|
||||
}
|
||||
}
|
||||
handleChange (e) {
|
||||
this.setState({inputValue: e.target.value, hasValidationError: false});
|
||||
}
|
||||
validate (input) {
|
||||
const urlMatches = input.match(/^(?:https?:\/\/)?scratch\.mit\.edu\/projects\/(\d+)/);
|
||||
if (urlMatches && urlMatches.length > 0) {
|
||||
return urlMatches[1];
|
||||
}
|
||||
const projectIdMatches = input.match(/^#?(\d+)$/);
|
||||
if (projectIdMatches && projectIdMatches.length > 0) {
|
||||
return projectIdMatches[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
handleCancel () {
|
||||
this.props.onCancel();
|
||||
}
|
||||
handleGoBack () {
|
||||
this.props.onBack();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<ImportModalComponent
|
||||
errorMessage={this.state.errorMessage}
|
||||
hasValidationError={this.state.hasValidationError}
|
||||
inputValue={this.state.inputValue}
|
||||
isRtl={this.props.isRtl}
|
||||
placeholder="scratch.mit.edu/projects/123456789"
|
||||
onCancel={this.handleCancel}
|
||||
onChange={this.handleChange}
|
||||
onGoBack={this.handleGoBack}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onViewProject={this.handleViewProject}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportModal.propTypes = {
|
||||
isRtl: PropTypes.bool,
|
||||
onBack: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onViewProject: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isRtl: state.locales.isRtl
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onBack: () => {
|
||||
dispatch(closeImportInfo());
|
||||
dispatch(openPreviewInfo());
|
||||
},
|
||||
onCancel: () => {
|
||||
dispatch(closeImportInfo());
|
||||
},
|
||||
onViewProject: () => {
|
||||
dispatch(closeImportInfo());
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ImportModal);
|
||||
@@ -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;
|
||||
@@ -9,6 +9,7 @@ import {addMonitorRect, getInitialPosition, resizeMonitorRect, removeMonitorRect
|
||||
import {getVariable, setVariableValue} from '../lib/variable-utils';
|
||||
import importCSV from '../lib/import-csv';
|
||||
import downloadBlob from '../lib/download-blob';
|
||||
import SliderPrompt from './slider-prompt.jsx';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {Map} from 'immutable';
|
||||
@@ -42,10 +43,16 @@ class Monitor extends React.Component {
|
||||
'handleSetModeToDefault',
|
||||
'handleSetModeToLarge',
|
||||
'handleSetModeToSlider',
|
||||
'handleSliderPromptClose',
|
||||
'handleSliderPromptOk',
|
||||
'handleSliderPromptOpen',
|
||||
'handleImport',
|
||||
'handleExport',
|
||||
'setElement'
|
||||
]);
|
||||
this.state = {
|
||||
sliderPrompt: false
|
||||
};
|
||||
}
|
||||
componentDidMount () {
|
||||
let rect;
|
||||
@@ -135,6 +142,23 @@ class Monitor extends React.Component {
|
||||
mode: 'slider'
|
||||
}));
|
||||
}
|
||||
handleSliderPromptClose () {
|
||||
this.setState({sliderPrompt: false});
|
||||
}
|
||||
handleSliderPromptOpen () {
|
||||
this.setState({sliderPrompt: true});
|
||||
}
|
||||
handleSliderPromptOk (min, max, isDiscrete) {
|
||||
const realMin = Math.min(min, max);
|
||||
const realMax = Math.max(min, max);
|
||||
this.props.vm.runtime.requestUpdateMonitor(Map({
|
||||
id: this.props.id,
|
||||
sliderMin: realMin,
|
||||
sliderMax: realMax,
|
||||
isDiscrete: isDiscrete
|
||||
}));
|
||||
this.handleSliderPromptClose();
|
||||
}
|
||||
setElement (monitorElt) {
|
||||
this.element = monitorElt;
|
||||
}
|
||||
@@ -164,24 +188,35 @@ class Monitor extends React.Component {
|
||||
const showSliderOption = availableModes(this.props.opcode).indexOf('slider') !== -1;
|
||||
const isList = this.props.mode === 'list';
|
||||
return (
|
||||
<MonitorComponent
|
||||
componentRef={this.setElement}
|
||||
{...monitorProps}
|
||||
draggable={this.props.draggable}
|
||||
height={this.props.height}
|
||||
max={this.props.max}
|
||||
min={this.props.min}
|
||||
mode={this.props.mode}
|
||||
targetId={this.props.targetId}
|
||||
width={this.props.width}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
onExport={isList ? this.handleExport : null}
|
||||
onImport={isList ? this.handleImport : null}
|
||||
onNextMode={this.handleNextMode}
|
||||
onSetModeToDefault={isList ? null : this.handleSetModeToDefault}
|
||||
onSetModeToLarge={isList ? null : this.handleSetModeToLarge}
|
||||
onSetModeToSlider={showSliderOption ? this.handleSetModeToSlider : null}
|
||||
/>
|
||||
<React.Fragment>
|
||||
{this.state.sliderPrompt && <SliderPrompt
|
||||
isDiscrete={this.props.isDiscrete}
|
||||
maxValue={parseFloat(this.props.max)}
|
||||
minValue={parseFloat(this.props.min)}
|
||||
onCancel={this.handleSliderPromptClose}
|
||||
onOk={this.handleSliderPromptOk}
|
||||
/>}
|
||||
<MonitorComponent
|
||||
componentRef={this.setElement}
|
||||
{...monitorProps}
|
||||
draggable={this.props.draggable}
|
||||
height={this.props.height}
|
||||
isDiscrete={this.props.isDiscrete}
|
||||
max={this.props.max}
|
||||
min={this.props.min}
|
||||
mode={this.props.mode}
|
||||
targetId={this.props.targetId}
|
||||
width={this.props.width}
|
||||
onDragEnd={this.handleDragEnd}
|
||||
onExport={isList ? this.handleExport : null}
|
||||
onImport={isList ? this.handleImport : null}
|
||||
onNextMode={this.handleNextMode}
|
||||
onSetModeToDefault={isList ? null : this.handleSetModeToDefault}
|
||||
onSetModeToLarge={isList ? null : this.handleSetModeToLarge}
|
||||
onSetModeToSlider={showSliderOption ? this.handleSetModeToSlider : null}
|
||||
onSliderPromptOpen={this.handleSliderPromptOpen}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -192,6 +227,7 @@ Monitor.propTypes = {
|
||||
height: PropTypes.number,
|
||||
id: PropTypes.string.isRequired,
|
||||
intl: intlShape,
|
||||
isDiscrete: PropTypes.bool,
|
||||
max: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
mode: PropTypes.oneOf(['default', 'slider', 'large', 'list']),
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import tabletFullScreen from '../lib/tablet-full-screen';
|
||||
|
||||
import PreviewModalComponent from '../components/preview-modal/preview-modal.jsx';
|
||||
|
||||
import {
|
||||
closePreviewInfo,
|
||||
openImportInfo
|
||||
} from '../reducers/modals';
|
||||
|
||||
class PreviewModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleTryIt',
|
||||
'handleCancel',
|
||||
'handleViewProject'
|
||||
]);
|
||||
|
||||
this.state = {
|
||||
previewing: false
|
||||
};
|
||||
}
|
||||
handleTryIt () {
|
||||
this.setState({previewing: true});
|
||||
// try to run in fullscreen mode on tablets.
|
||||
tabletFullScreen();
|
||||
this.props.onTryIt();
|
||||
}
|
||||
handleCancel () {
|
||||
window.location.replace('https://scratch.mit.edu');
|
||||
}
|
||||
handleViewProject () {
|
||||
this.props.onViewProject();
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<PreviewModalComponent
|
||||
isRtl={this.props.isRtl}
|
||||
previewing={this.state.previewing}
|
||||
onCancel={this.handleCancel}
|
||||
onTryIt={this.handleTryIt}
|
||||
onViewProject={this.handleViewProject}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PreviewModal.propTypes = {
|
||||
isRtl: PropTypes.bool,
|
||||
onTryIt: PropTypes.func,
|
||||
onViewProject: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isRtl: state.locales.isRtl
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onTryIt: () => {
|
||||
dispatch(closePreviewInfo());
|
||||
},
|
||||
onViewProject: () => {
|
||||
dispatch(closePreviewInfo());
|
||||
dispatch(openImportInfo());
|
||||
}
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PreviewModal);
|
||||
@@ -4,11 +4,13 @@ import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import log from '../lib/log';
|
||||
import sharedMessages from '../lib/shared-messages';
|
||||
|
||||
import {
|
||||
LoadingStates,
|
||||
getIsLoadingUpload,
|
||||
getIsShowingWithoutId,
|
||||
onLoadedProject,
|
||||
requestProjectUpload
|
||||
} from '../reducers/project-state';
|
||||
@@ -88,10 +90,32 @@ class SBFileUploader extends React.Component {
|
||||
}
|
||||
// called when user has finished selecting a file to upload
|
||||
handleChange (e) {
|
||||
const {
|
||||
intl,
|
||||
isShowingWithoutId,
|
||||
loadingState,
|
||||
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];
|
||||
this.props.requestProjectUpload(this.props.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
|
||||
@@ -101,13 +125,6 @@ class SBFileUploader extends React.Component {
|
||||
const filename = this.fileToUpload && this.fileToUpload.name;
|
||||
this.props.vm.loadProject(this.reader.result)
|
||||
.then(() => {
|
||||
analytics.event({
|
||||
category: 'project',
|
||||
action: 'Import Project File',
|
||||
nonInteraction: true
|
||||
});
|
||||
// Remove the hash if any (without triggering a hash change event or a reload)
|
||||
history.replaceState({}, document.title, '.');
|
||||
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
|
||||
@@ -154,13 +171,17 @@ 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,
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
onLoadingFinished: PropTypes.func,
|
||||
onLoadingStarted: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
projectChanged: PropTypes.bool,
|
||||
requestProjectUpload: PropTypes.func,
|
||||
userOwnsProject: PropTypes.bool,
|
||||
vm: PropTypes.shape({
|
||||
loadProject: PropTypes.func
|
||||
})
|
||||
@@ -172,12 +193,15 @@ const mapStateToProps = state => {
|
||||
const loadingState = state.scratchGui.projectState.loadingState;
|
||||
return {
|
||||
isLoadingUpload: getIsLoadingUpload(loadingState),
|
||||
isShowingWithoutId: getIsShowingWithoutId(loadingState),
|
||||
loadingState: loadingState,
|
||||
projectChanged: state.scratchGui.projectChanged,
|
||||
vm: state.scratchGui.vm
|
||||
};
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import SliderPromptComponent from '../components/slider-prompt/slider-prompt.jsx';
|
||||
|
||||
class SliderPrompt extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleOk',
|
||||
'handleCancel',
|
||||
'handleChangeMin',
|
||||
'handleChangeMax',
|
||||
'handleKeyPress',
|
||||
'validates',
|
||||
'shouldBeDiscrete'
|
||||
]);
|
||||
|
||||
const {isDiscrete, minValue, maxValue} = this.props;
|
||||
this.state = {
|
||||
// For internal use, convert values to strings based on isDiscrete
|
||||
// This is because `<input />` always returns values as strings.
|
||||
minValue: isDiscrete ? minValue.toFixed(0) : minValue.toFixed(2),
|
||||
maxValue: isDiscrete ? maxValue.toFixed(0) : maxValue.toFixed(2)
|
||||
};
|
||||
}
|
||||
handleKeyPress (event) {
|
||||
if (event.key === 'Enter') this.handleOk();
|
||||
}
|
||||
handleOk () {
|
||||
const {minValue, maxValue} = this.state;
|
||||
if (!this.validates(minValue, maxValue)) {
|
||||
this.props.onCancel();
|
||||
return;
|
||||
}
|
||||
this.props.onOk(
|
||||
parseFloat(minValue),
|
||||
parseFloat(maxValue),
|
||||
this.shouldBeDiscrete(minValue, maxValue));
|
||||
}
|
||||
handleCancel () {
|
||||
this.props.onCancel();
|
||||
}
|
||||
handleChangeMin (e) {
|
||||
this.setState({minValue: e.target.value});
|
||||
}
|
||||
handleChangeMax (e) {
|
||||
this.setState({maxValue: e.target.value});
|
||||
}
|
||||
shouldBeDiscrete (min, max) {
|
||||
return min.indexOf('.') + max.indexOf('.') === -2; // Both -1
|
||||
}
|
||||
validates (min, max) {
|
||||
return isFinite(min) && isFinite(max);
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<SliderPromptComponent
|
||||
maxValue={this.state.maxValue}
|
||||
minValue={this.state.minValue}
|
||||
onCancel={this.handleCancel}
|
||||
onChangeMax={this.handleChangeMax}
|
||||
onChangeMin={this.handleChangeMin}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onOk={this.handleOk}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SliderPrompt.propTypes = {
|
||||
isDiscrete: PropTypes.bool,
|
||||
maxValue: PropTypes.number,
|
||||
minValue: PropTypes.number,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onOk: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
SliderPrompt.defaultProps = {
|
||||
maxValue: 100,
|
||||
minValue: 0,
|
||||
isDiscrete: true
|
||||
};
|
||||
|
||||
export default SliderPrompt;
|
||||
@@ -5,7 +5,6 @@ import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
import VM from 'scratch-vm';
|
||||
import AudioEngine from 'scratch-audio';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import LibraryComponent from '../components/library/library.jsx';
|
||||
|
||||
import soundIcon from '../components/asset-panel/icon--sound.svg';
|
||||
@@ -125,11 +124,6 @@ class SoundLibrary extends React.PureComponent {
|
||||
this.props.vm.addSound(vmSound).then(() => {
|
||||
this.props.onNewSound();
|
||||
});
|
||||
analytics.event({
|
||||
category: 'library',
|
||||
action: 'Select Sound',
|
||||
label: soundItem.name
|
||||
});
|
||||
}
|
||||
render () {
|
||||
// @todo need to use this hack to avoid library using md5 for image
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
} from '../reducers/editor-tab';
|
||||
|
||||
import {setRestore} from '../reducers/restore-deletion';
|
||||
import {showStandardAlert, closeAlertWithId} from '../reducers/alerts';
|
||||
|
||||
class SoundTab extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -129,12 +130,18 @@ class SoundTab extends React.Component {
|
||||
|
||||
handleSoundUpload (e) {
|
||||
const storage = this.props.vm.runtime.storage;
|
||||
const handleSound = newSound => this.props.vm.addSound(newSound)
|
||||
.then(() => this.handleNewSound());
|
||||
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName) => {
|
||||
soundUpload(buffer, fileType, fileName, storage, handleSound);
|
||||
});
|
||||
this.props.onShowImporting();
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
|
||||
soundUpload(buffer, fileType, storage, newSound => {
|
||||
newSound.name = fileName;
|
||||
this.props.vm.addSound(newSound).then(() => {
|
||||
this.handleNewSound();
|
||||
if (fileIndex === fileCount - 1) {
|
||||
this.props.onCloseImporting();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, this.props.onCloseImporting);
|
||||
}
|
||||
|
||||
handleDrop (dropInfo) {
|
||||
@@ -223,7 +230,8 @@ class SoundTab extends React.Component {
|
||||
onClick: this.handleFileUploadClick,
|
||||
fileAccept: '.wav, .mp3',
|
||||
fileChange: this.handleSoundUpload,
|
||||
fileInput: this.setFileInput
|
||||
fileInput: this.setFileInput,
|
||||
fileMultiple: true
|
||||
}, {
|
||||
title: intl.formatMessage(messages.surpriseSound),
|
||||
img: surpriseIcon,
|
||||
@@ -273,9 +281,11 @@ SoundTab.propTypes = {
|
||||
intl: intlShape,
|
||||
isRtl: PropTypes.bool,
|
||||
onActivateCostumesTab: PropTypes.func.isRequired,
|
||||
onCloseImporting: PropTypes.func.isRequired,
|
||||
onNewSoundFromLibraryClick: PropTypes.func.isRequired,
|
||||
onNewSoundFromRecordingClick: PropTypes.func.isRequired,
|
||||
onRequestCloseSoundLibrary: PropTypes.func.isRequired,
|
||||
onShowImporting: PropTypes.func.isRequired,
|
||||
soundLibraryVisible: PropTypes.bool,
|
||||
soundRecorderVisible: PropTypes.bool,
|
||||
sprites: PropTypes.shape({
|
||||
@@ -316,7 +326,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
dispatchUpdateRestore: restoreState => {
|
||||
dispatch(setRestore(restoreState));
|
||||
}
|
||||
},
|
||||
onCloseImporting: () => dispatch(closeAlertWithId('importingAsset')),
|
||||
onShowImporting: () => dispatch(showStandardAlert('importingAsset'))
|
||||
});
|
||||
|
||||
export default errorBoundaryHOC('Sound Tab')(
|
||||
|
||||
@@ -4,7 +4,6 @@ import React from 'react';
|
||||
import {injectIntl, intlShape, defineMessages} from 'react-intl';
|
||||
import VM from 'scratch-vm';
|
||||
|
||||
import analytics from '../lib/analytics';
|
||||
import spriteLibraryContent from '../lib/libraries/sprites.json';
|
||||
import randomizeSpritePosition from '../lib/randomize-sprite-position';
|
||||
import spriteTags from '../lib/libraries/sprite-tags';
|
||||
@@ -32,11 +31,6 @@ class SpriteLibrary extends React.PureComponent {
|
||||
this.props.vm.addSprite(JSON.stringify(item.json)).then(() => {
|
||||
this.props.onActivateBlocksTab();
|
||||
});
|
||||
analytics.event({
|
||||
category: 'library',
|
||||
action: 'Select Sprite',
|
||||
label: item.name
|
||||
});
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
|
||||
@@ -7,6 +7,7 @@ import {intlShape, injectIntl} from 'react-intl';
|
||||
import {connect} from 'react-redux';
|
||||
import {openBackdropLibrary} from '../reducers/modals';
|
||||
import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab';
|
||||
import {showStandardAlert, closeAlertWithId} from '../reducers/alerts';
|
||||
import {setHoveredSprite} from '../reducers/hovered-target';
|
||||
import DragConstants from '../lib/drag-constants';
|
||||
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
|
||||
@@ -63,9 +64,13 @@ class StageSelector extends React.Component {
|
||||
handleClick () {
|
||||
this.props.onSelect(this.props.id);
|
||||
}
|
||||
handleNewBackdrop (backdrop) {
|
||||
this.props.vm.addBackdrop(backdrop.md5, backdrop).then(() =>
|
||||
this.props.onActivateTab(COSTUMES_TAB_INDEX));
|
||||
handleNewBackdrop (backdrops_) {
|
||||
const backdrops = Array.isArray(backdrops_) ? backdrops_ : [backdrops_];
|
||||
return Promise.all(backdrops.map(backdrop =>
|
||||
this.props.vm.addBackdrop(backdrop.md5, backdrop)
|
||||
)).then(() =>
|
||||
this.props.onActivateTab(COSTUMES_TAB_INDEX)
|
||||
);
|
||||
}
|
||||
handleSurpriseBackdrop () {
|
||||
// @todo should this not add a backdrop you already have?
|
||||
@@ -77,9 +82,19 @@ class StageSelector extends React.Component {
|
||||
}
|
||||
handleBackdropUpload (e) {
|
||||
const storage = this.props.vm.runtime.storage;
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName) => {
|
||||
costumeUpload(buffer, fileType, fileName, storage, this.handleNewBackdrop);
|
||||
});
|
||||
this.props.onShowImporting();
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
|
||||
costumeUpload(buffer, fileType, storage, vmCostumes => {
|
||||
vmCostumes.forEach((costume, i) => {
|
||||
costume.name = `${fileName}${i ? i + 1 : ''}`;
|
||||
});
|
||||
this.handleNewBackdrop(vmCostumes).then(() => {
|
||||
if (fileIndex === fileCount - 1) {
|
||||
this.props.onCloseImporting();
|
||||
}
|
||||
});
|
||||
}, this.props.onCloseImporting);
|
||||
}, this.props.onCloseImporting);
|
||||
}
|
||||
handleFileUploadClick () {
|
||||
this.fileInput.click();
|
||||
@@ -117,7 +132,8 @@ class StageSelector extends React.Component {
|
||||
}
|
||||
render () {
|
||||
const componentProps = omit(this.props, [
|
||||
'asset', 'dispatchSetHoveredSprite', 'id', 'intl', 'onActivateTab', 'onSelect']);
|
||||
'asset', 'dispatchSetHoveredSprite', 'id', 'intl',
|
||||
'onActivateTab', 'onSelect', 'onShowImporting', 'onCloseImporting']);
|
||||
return (
|
||||
<DroppableThrottledStage
|
||||
fileInputRef={this.setFileInput}
|
||||
@@ -138,7 +154,9 @@ StageSelector.propTypes = {
|
||||
...StageSelectorComponent.propTypes,
|
||||
id: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
onSelect: PropTypes.func
|
||||
onCloseImporting: PropTypes.func,
|
||||
onSelect: PropTypes.func,
|
||||
onShowImporting: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, {asset, id}) => ({
|
||||
@@ -159,7 +177,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
dispatchSetHoveredSprite: spriteId => {
|
||||
dispatch(setHoveredSprite(spriteId));
|
||||
}
|
||||
},
|
||||
onCloseImporting: () => dispatch(closeAlertWithId('importingAsset')),
|
||||
onShowImporting: () => dispatch(showStandardAlert('importingAsset'))
|
||||
});
|
||||
|
||||
export default injectIntl(connect(
|
||||
|
||||
@@ -112,7 +112,9 @@ class Stage extends React.Component {
|
||||
}
|
||||
startColorPickingLoop () {
|
||||
this.intervalId = setInterval(() => {
|
||||
this.setState({colorInfo: this.getColorInfo(this.pickX, this.pickY)});
|
||||
if (typeof this.pickX === 'number') {
|
||||
this.setState({colorInfo: this.getColorInfo(this.pickX, this.pickY)});
|
||||
}
|
||||
}, 30);
|
||||
}
|
||||
stopColorPickingLoop () {
|
||||
@@ -175,9 +177,11 @@ class Stage extends React.Component {
|
||||
const {x, y} = getEventXY(e);
|
||||
const mousePosition = [x - this.rect.left, y - this.rect.top];
|
||||
|
||||
// Set the pickX/Y for the color picker loop to pick up
|
||||
this.pickX = mousePosition[0];
|
||||
this.pickY = mousePosition[1];
|
||||
if (this.props.isColorPicking) {
|
||||
// Set the pickX/Y for the color picker loop to pick up
|
||||
this.pickX = mousePosition[0];
|
||||
this.pickY = mousePosition[1];
|
||||
}
|
||||
|
||||
if (this.state.mouseDown && !this.state.isDragging) {
|
||||
const distanceFromMouseDown = Math.sqrt(
|
||||
@@ -231,38 +235,11 @@ class Stage extends React.Component {
|
||||
this.onStopDrag(mousePosition[0], mousePosition[1]);
|
||||
}
|
||||
this.props.vm.postIOData('mouse', data);
|
||||
}
|
||||
onMouseDown (e) {
|
||||
this.updateRect();
|
||||
const {x, y} = getEventXY(e);
|
||||
const mousePosition = [x - this.rect.left, y - this.rect.top];
|
||||
if (e.button === 0 || (window.TouchEvent && e instanceof TouchEvent)) {
|
||||
this.setState({
|
||||
mouseDown: true,
|
||||
mouseDownPosition: mousePosition,
|
||||
mouseDownTimeoutId: setTimeout(
|
||||
this.onStartDrag.bind(this, mousePosition[0], mousePosition[1]),
|
||||
400
|
||||
)
|
||||
});
|
||||
}
|
||||
const data = {
|
||||
isDown: true,
|
||||
x: mousePosition[0],
|
||||
y: mousePosition[1],
|
||||
canvasWidth: this.rect.width,
|
||||
canvasHeight: this.rect.height
|
||||
};
|
||||
this.props.vm.postIOData('mouse', data);
|
||||
if (e.preventDefault) {
|
||||
// Prevent default to prevent touch from dragging page
|
||||
e.preventDefault();
|
||||
// But we do want any active input to be blurred
|
||||
if (document.activeElement && document.activeElement.blur) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
if (this.props.isColorPicking) {
|
||||
|
||||
if (this.props.isColorPicking &&
|
||||
mousePosition[0] > 0 && mousePosition[0] < this.rect.width &&
|
||||
mousePosition[1] > 0 && mousePosition[1] < this.rect.height
|
||||
) {
|
||||
const {r, g, b} = this.state.colorInfo.color;
|
||||
const componentToString = c => {
|
||||
const hex = c.toString(16);
|
||||
@@ -271,6 +248,47 @@ class Stage extends React.Component {
|
||||
const colorString = `#${componentToString(r)}${componentToString(g)}${componentToString(b)}`;
|
||||
this.props.onDeactivateColorPicker(colorString);
|
||||
this.setState({colorInfo: null});
|
||||
this.pickX = null;
|
||||
this.pickY = null;
|
||||
}
|
||||
}
|
||||
onMouseDown (e) {
|
||||
this.updateRect();
|
||||
const {x, y} = getEventXY(e);
|
||||
const mousePosition = [x - this.rect.left, y - this.rect.top];
|
||||
if (this.props.isColorPicking) {
|
||||
// Set the pickX/Y for the color picker loop to pick up
|
||||
this.pickX = mousePosition[0];
|
||||
this.pickY = mousePosition[1];
|
||||
// Immediately update the color picker info
|
||||
this.setState({colorInfo: this.getColorInfo(this.pickX, this.pickY)});
|
||||
} else {
|
||||
if (e.button === 0 || (window.TouchEvent && e instanceof TouchEvent)) {
|
||||
this.setState({
|
||||
mouseDown: true,
|
||||
mouseDownPosition: mousePosition,
|
||||
mouseDownTimeoutId: setTimeout(
|
||||
this.onStartDrag.bind(this, mousePosition[0], mousePosition[1]),
|
||||
400
|
||||
)
|
||||
});
|
||||
}
|
||||
const data = {
|
||||
isDown: true,
|
||||
x: mousePosition[0],
|
||||
y: mousePosition[1],
|
||||
canvasWidth: this.rect.width,
|
||||
canvasHeight: this.rect.height
|
||||
};
|
||||
this.props.vm.postIOData('mouse', data);
|
||||
if (e.preventDefault) {
|
||||
// Prevent default to prevent touch from dragging page
|
||||
e.preventDefault();
|
||||
// But we do want any active input to be blurred
|
||||
if (document.activeElement && document.activeElement.blur) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onWheel (e) {
|
||||
@@ -320,7 +338,6 @@ class Stage extends React.Component {
|
||||
if (this.state.dragId) return;
|
||||
const drawableId = this.renderer.pick(x, y);
|
||||
if (drawableId === null) return;
|
||||
const drawableData = this.renderer.extractDrawable(drawableId, x, y);
|
||||
const targetId = this.props.vm.getTargetIdForDrawableId(drawableId);
|
||||
if (targetId === null) return;
|
||||
|
||||
@@ -332,6 +349,9 @@ class Stage extends React.Component {
|
||||
// Dragging always brings the target to the front
|
||||
target.goToFront();
|
||||
|
||||
// Extract the drawable art
|
||||
const drawableData = this.renderer.extractDrawable(drawableId, x, y);
|
||||
|
||||
this.props.vm.startDrag(targetId);
|
||||
this.setState({
|
||||
isDragging: true,
|
||||
@@ -403,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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bindAll from 'lodash.bindall';
|
||||
import React from 'react';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {intlShape, injectIntl} from 'react-intl';
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
openSpriteLibrary,
|
||||
closeSpriteLibrary
|
||||
} from '../reducers/modals';
|
||||
|
||||
import {activateTab, COSTUMES_TAB_INDEX, BLOCKS_TAB_INDEX} from '../reducers/editor-tab';
|
||||
import {setReceivedBlocks} from '../reducers/hovered-target';
|
||||
import {showStandardAlert, closeAlertWithId} from '../reducers/alerts';
|
||||
import {setRestore} from '../reducers/restore-deletion';
|
||||
import DragConstants from '../lib/drag-constants';
|
||||
import TargetPaneComponent from '../components/target-pane/target-pane.jsx';
|
||||
@@ -127,7 +127,7 @@ class TargetPane extends React.Component {
|
||||
this.props.onActivateTab(BLOCKS_TAB_INDEX);
|
||||
}
|
||||
handleNewSprite (spriteJSONString) {
|
||||
this.props.vm.addSprite(spriteJSONString)
|
||||
return this.props.vm.addSprite(spriteJSONString)
|
||||
.then(this.handleActivateBlocksTab);
|
||||
}
|
||||
handleFileUploadClick () {
|
||||
@@ -135,10 +135,16 @@ class TargetPane extends React.Component {
|
||||
}
|
||||
handleSpriteUpload (e) {
|
||||
const storage = this.props.vm.runtime.storage;
|
||||
const costumeSuffix = this.props.intl.formatMessage(sharedMessages.costume, {index: 1});
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName) => {
|
||||
spriteUpload(buffer, fileType, fileName, storage, this.handleNewSprite, costumeSuffix);
|
||||
});
|
||||
this.props.onShowImporting();
|
||||
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
|
||||
spriteUpload(buffer, fileType, fileName, storage, newSprite => {
|
||||
this.handleNewSprite(newSprite).then(() => {
|
||||
if (fileIndex === fileCount - 1) {
|
||||
this.props.onCloseImporting();
|
||||
}
|
||||
});
|
||||
}, this.props.onCloseImporting);
|
||||
}, this.props.onCloseImporting);
|
||||
}
|
||||
setFileInput (input) {
|
||||
this.fileInput = input;
|
||||
@@ -197,6 +203,8 @@ class TargetPane extends React.Component {
|
||||
onReceivedBlocks, // eslint-disable-line no-unused-vars
|
||||
onHighlightTarget, // eslint-disable-line no-unused-vars
|
||||
dispatchUpdateRestore, // eslint-disable-line no-unused-vars
|
||||
onShowImporting, // eslint-disable-line no-unused-vars
|
||||
onCloseImporting, // eslint-disable-line no-unused-vars
|
||||
...componentProps
|
||||
} = this.props;
|
||||
return (
|
||||
@@ -233,6 +241,8 @@ const {
|
||||
|
||||
TargetPane.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onCloseImporting: PropTypes.func,
|
||||
onShowImporting: PropTypes.func,
|
||||
...targetPaneProps
|
||||
};
|
||||
|
||||
@@ -264,7 +274,9 @@ const mapDispatchToProps = dispatch => ({
|
||||
},
|
||||
onHighlightTarget: id => {
|
||||
dispatch(highlightTarget(id));
|
||||
}
|
||||
},
|
||||
onCloseImporting: () => dispatch(closeAlertWithId('importingAsset')),
|
||||
onShowImporting: () => dispatch(showStandardAlert('importingAsset'))
|
||||
});
|
||||
|
||||
export default injectIntl(connect(
|
||||
|
||||
@@ -10,6 +10,7 @@ $z-index-stage-indicator: 45;
|
||||
$z-index-add-button: 46;
|
||||
$z-index-tooltip: 47; /* tooltips should go over add buttons if they overlap */
|
||||
$z-index-monitor: 48; /* monitors go over add buttons */
|
||||
$z-index-stage-question: 49; /* "ask" block text input goes above monitors */
|
||||
|
||||
$z-index-card: 480;
|
||||
$z-index-alerts: 490;
|
||||
|
||||
@@ -198,6 +198,20 @@ const alerts = [
|
||||
closeButton: true,
|
||||
level: AlertLevels.SUCCESS,
|
||||
maxDisplaySecs: 15
|
||||
},
|
||||
{
|
||||
alertId: 'importingAsset',
|
||||
alertType: AlertTypes.STANDARD,
|
||||
clearList: [],
|
||||
content: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Importing…"
|
||||
description="Message indicating that project is in process of importing"
|
||||
id="gui.alerts.importing"
|
||||
/>
|
||||
),
|
||||
iconSpinner: true,
|
||||
level: AlertLevels.SUCCESS
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
|
||||
guiMiddleware,
|
||||
initFullScreen,
|
||||
initPlayer,
|
||||
initPreviewInfo,
|
||||
initTelemetryModal
|
||||
} = guiRedux;
|
||||
const {ScratchPaintReducer} = require('scratch-paint');
|
||||
@@ -66,8 +65,6 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
|
||||
}
|
||||
} else if (props.showTelemetryModal) {
|
||||
initializedGui = initTelemetryModal(initializedGui);
|
||||
} else if (props.showPreviewInfo) {
|
||||
initializedGui = initPreviewInfo(initializedGui);
|
||||
}
|
||||
reducers = {
|
||||
locales: localesReducer,
|
||||
@@ -100,7 +97,6 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
|
||||
const {
|
||||
isFullScreen, // eslint-disable-line no-unused-vars
|
||||
isPlayerOnly, // eslint-disable-line no-unused-vars
|
||||
showPreviewInfo, // eslint-disable-line no-unused-vars
|
||||
showTelemetryModal, // eslint-disable-line no-unused-vars
|
||||
...componentProps
|
||||
} = this.props;
|
||||
@@ -118,7 +114,7 @@ const AppStateHOC = function (WrappedComponent, localesOnly) {
|
||||
AppStateWrapper.propTypes = {
|
||||
isFullScreen: PropTypes.bool,
|
||||
isPlayerOnly: PropTypes.bool,
|
||||
showPreviewInfo: PropTypes.bool
|
||||
showTelemetryModal: PropTypes.bool
|
||||
};
|
||||
return AppStateWrapper;
|
||||
};
|
||||
|
||||
@@ -245,8 +245,12 @@ export default function (vm) {
|
||||
// The block was in the flyout so look up future block info there.
|
||||
lookupBlocks = vm.runtime.flyoutBlocks;
|
||||
}
|
||||
const sort = function (options) {
|
||||
options.sort(ScratchBlocks.scratchBlocksUtils.compareStrings);
|
||||
};
|
||||
// Get all the stage variables (no lists) so we can add them to menu when the stage is selected.
|
||||
const stageVariableOptions = vm.runtime.getTargetForStage().getAllVariableNamesInScopeByType('');
|
||||
sort(stageVariableOptions);
|
||||
const stageVariableMenuItems = stageVariableOptions.map(variable => [variable, variable]);
|
||||
if (sensingOfBlock.inputs.OBJECT.shadow !== sensingOfBlock.inputs.OBJECT.block) {
|
||||
// There's a block dropped on top of the menu. It'd be nice to evaluate it and
|
||||
@@ -265,6 +269,7 @@ export default function (vm) {
|
||||
// The target should exist, but there are ways for it not to (e.g. #4203).
|
||||
if (target) {
|
||||
spriteVariableOptions = target.getAllVariableNamesInScopeByType('', true);
|
||||
sort(spriteVariableOptions);
|
||||
}
|
||||
const spriteVariableMenuItems = spriteVariableOptions.map(variable => [variable, variable]);
|
||||
return spriteOptions.concat(spriteVariableMenuItems);
|
||||
@@ -315,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,39 @@
|
||||
/**
|
||||
* Report a telemetry event.
|
||||
* @param {string} event - one of `projectWasCreated`, `projectDidLoad`, `projectDidSave`, `projectWasUploaded`
|
||||
*/
|
||||
// TODO make a telemetry HOC and move this stuff there
|
||||
const collectMetadata = function (vm, projectName = '', locale = '') {
|
||||
// TODO move most or all of this into a collectMetadata() method on the VM/Runtime
|
||||
const metadata = {
|
||||
projectName: projectName,
|
||||
language: locale,
|
||||
spriteCount: 0,
|
||||
blocksCount: 0,
|
||||
costumesCount: 0,
|
||||
listsCount: 0,
|
||||
scriptCount: 0,
|
||||
soundsCount: 0,
|
||||
variablesCount: 0
|
||||
};
|
||||
|
||||
for (const target of vm.runtime.targets) {
|
||||
++metadata.spriteCount;
|
||||
metadata.blocksCount += Object.keys(target.sprite.blocks._blocks).length;
|
||||
metadata.costumesCount += target.sprite.costumes_.length;
|
||||
metadata.scriptCount += target.sprite.blocks._scripts.length;
|
||||
metadata.soundsCount += target.sprite.sounds.length;
|
||||
for (const variableName in target.variables) {
|
||||
const variable = target.variables[variableName];
|
||||
if (variable.type === 'list') {
|
||||
++metadata.listsCount;
|
||||
} else {
|
||||
++metadata.variablesCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
};
|
||||
|
||||
export default collectMetadata;
|
||||
@@ -1,4 +1,3 @@
|
||||
import {TextEncoder} from 'text-encoding';
|
||||
import projectData from './project-data';
|
||||
|
||||
/* eslint-disable import/no-unresolved */
|
||||
@@ -9,8 +8,16 @@ import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg';
|
||||
import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg';
|
||||
/* eslint-enable import/no-unresolved */
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const defaultProject = translator => {
|
||||
let _TextEncoder;
|
||||
if (typeof TextEncoder === 'undefined') {
|
||||
_TextEncoder = require('text-encoding').TextEncoder;
|
||||
} else {
|
||||
/* global TextEncoder */
|
||||
_TextEncoder = TextEncoder;
|
||||
}
|
||||
const encoder = new _TextEncoder();
|
||||
|
||||
const projectJson = projectData(translator);
|
||||
return [{
|
||||
id: 0,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {BitmapAdapter} from 'scratch-svg-renderer';
|
||||
import log from './log.js';
|
||||
import randomizeSpritePosition from './randomize-sprite-position.js';
|
||||
import gifDecoder from './gif-decoder';
|
||||
|
||||
/**
|
||||
* Extract the file name given a string of the form fileName + ext
|
||||
@@ -19,24 +20,29 @@ const extractFileName = function (nameExt) {
|
||||
* and a function to handle loading the file.
|
||||
* @param {Input} fileInput The <input/> element that contains the file being loaded
|
||||
* @param {Function} onload The function that handles loading the file
|
||||
* @param {Function} onerror The function that handles any error loading the file
|
||||
*/
|
||||
const handleFileUpload = function (fileInput, onload) {
|
||||
let thisFile = null;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// Reset the file input value now that we have everything we need
|
||||
// so that the user can upload the same sound multiple times if
|
||||
// they choose
|
||||
fileInput.value = null;
|
||||
const fileType = thisFile.type;
|
||||
const fileName = extractFileName(thisFile.name);
|
||||
|
||||
onload(reader.result, fileType, fileName);
|
||||
const handleFileUpload = function (fileInput, onload, onerror) {
|
||||
const readFile = (i, files) => {
|
||||
if (i === files.length) {
|
||||
// Reset the file input value now that we have everything we need
|
||||
// so that the user can upload the same sound multiple times if
|
||||
// they choose
|
||||
fileInput.value = null;
|
||||
return;
|
||||
}
|
||||
const file = files[i];
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const fileType = file.type;
|
||||
const fileName = extractFileName(file.name);
|
||||
onload(reader.result, fileType, fileName, i, files.length);
|
||||
readFile(i + 1, files);
|
||||
};
|
||||
reader.onerror = onerror;
|
||||
reader.readAsArrayBuffer(file);
|
||||
};
|
||||
if (fileInput.files) {
|
||||
thisFile = fileInput.files[0];
|
||||
reader.readAsArrayBuffer(thisFile);
|
||||
}
|
||||
readFile(0, fileInput.files);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,7 +61,6 @@ const handleFileUpload = function (fileInput, onload) {
|
||||
* Create an asset (costume, sound) with storage and return an object representation
|
||||
* of the asset to track in the VM.
|
||||
* @param {ScratchStorage} storage The storage to cache the asset in
|
||||
* @param {string} fileName The name of the asset
|
||||
* @param {AssetType} assetType A ScratchStorage AssetType indicating what kind of
|
||||
* asset this is.
|
||||
* @param {string} dataFormat The format of this data (typically the file extension)
|
||||
@@ -63,7 +68,7 @@ const handleFileUpload = function (fileInput, onload) {
|
||||
* @return {VMAsset} An object representing this asset and relevant information
|
||||
* which can be used to look up the data in storage
|
||||
*/
|
||||
const createVMAsset = function (storage, fileName, assetType, dataFormat, data) {
|
||||
const createVMAsset = function (storage, assetType, dataFormat, data) {
|
||||
const asset = storage.createAsset(
|
||||
assetType,
|
||||
dataFormat,
|
||||
@@ -73,7 +78,7 @@ const createVMAsset = function (storage, fileName, assetType, dataFormat, data)
|
||||
);
|
||||
|
||||
return {
|
||||
name: fileName,
|
||||
name: null, // Needs to be set by caller
|
||||
dataFormat: dataFormat,
|
||||
asset: asset,
|
||||
md5: `${asset.assetId}.${dataFormat}`,
|
||||
@@ -86,13 +91,13 @@ const createVMAsset = function (storage, fileName, assetType, dataFormat, data)
|
||||
* @param {ArrayBuffer | string} fileData The costume data to load (this can be a base64 string
|
||||
* iff the image is a bitmap)
|
||||
* @param {string} fileType The MIME type of this file
|
||||
* @param {string} costumeName The user-readable name to use for the costume.
|
||||
* @param {ScratchStorage} storage The ScratchStorage instance to cache the costume data
|
||||
* @param {Function} handleCostume The function to execute on the costume object returned after
|
||||
* caching this costume in storage - This function should be responsible for
|
||||
* adding the costume to the VM and handling other UI flow that should come after adding the costume
|
||||
* @param {Function} handleError The function to execute if there is an error parsing the costume
|
||||
*/
|
||||
const costumeUpload = function (fileData, fileType, costumeName, storage, handleCostume) {
|
||||
const costumeUpload = function (fileData, fileType, storage, handleCostume, handleError = () => {}) {
|
||||
let costumeFormat = null;
|
||||
let assetType = null;
|
||||
switch (fileType) {
|
||||
@@ -111,8 +116,20 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle
|
||||
assetType = storage.AssetType.ImageBitmap;
|
||||
break;
|
||||
}
|
||||
case 'image/gif': {
|
||||
let costumes = [];
|
||||
gifDecoder(fileData, (frameNumber, dataUrl, numFrames) => {
|
||||
costumeUpload(dataUrl, 'image/png', storage, costumes_ => {
|
||||
costumes = costumes.concat(costumes_);
|
||||
if (frameNumber === numFrames - 1) {
|
||||
handleCostume(costumes);
|
||||
}
|
||||
}, handleError);
|
||||
});
|
||||
return; // Abandon this load, do not try to load gif itself
|
||||
}
|
||||
default:
|
||||
log.warn(`Encountered unexpected file type: ${fileType}`);
|
||||
handleError(`Encountered unexpected file type: ${fileType}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,12 +137,11 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle
|
||||
const addCostumeFromBuffer = function (dataBuffer) {
|
||||
const vmCostume = createVMAsset(
|
||||
storage,
|
||||
costumeName,
|
||||
assetType,
|
||||
costumeFormat,
|
||||
dataBuffer
|
||||
);
|
||||
handleCostume(vmCostume);
|
||||
handleCostume([vmCostume]);
|
||||
};
|
||||
|
||||
if (costumeFormat === storage.DataFormat.SVG) {
|
||||
@@ -137,9 +153,7 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle
|
||||
} else {
|
||||
// otherwise it's a bitmap
|
||||
bitmapAdapter.importBitmap(fileData, fileType).then(addCostumeFromBuffer)
|
||||
.catch(e => {
|
||||
log.error(e);
|
||||
});
|
||||
.catch(handleError);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -148,13 +162,12 @@ const costumeUpload = function (fileData, fileType, costumeName, storage, handle
|
||||
* @param {ArrayBuffer} fileData The sound data to load
|
||||
* @param {string} fileType The MIME type of this file; This function will exit
|
||||
* early if the fileType is unexpected.
|
||||
* @param {string} soundName The user-readable name to use for the sound.
|
||||
* @param {ScratchStorage} storage The ScratchStorage instance to cache the sound data
|
||||
* @param {Function} handleSound The function to execute on the sound object of type VMAsset
|
||||
* This function should be responsible for adding the sound to the VM
|
||||
* as well as handling other UI flow that should come after adding the sound
|
||||
*/
|
||||
const soundUpload = function (fileData, fileType, soundName, storage, handleSound) {
|
||||
const soundUpload = function (fileData, fileType, storage, handleSound) {
|
||||
let soundFormat;
|
||||
switch (fileType) {
|
||||
case 'audio/mp3':
|
||||
@@ -176,7 +189,6 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun
|
||||
|
||||
const vmSound = createVMAsset(
|
||||
storage,
|
||||
soundName,
|
||||
storage.AssetType.Sound,
|
||||
soundFormat,
|
||||
new Uint8Array(fileData));
|
||||
@@ -184,8 +196,7 @@ const soundUpload = function (fileData, fileType, soundName, storage, handleSoun
|
||||
handleSound(vmSound);
|
||||
};
|
||||
|
||||
const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite, costumeSuffix) {
|
||||
const costumeName = costumeSuffix || 'costume1';
|
||||
const spriteUpload = function (fileData, fileType, spriteName, storage, handleSprite, handleError = () => {}) {
|
||||
switch (fileType) {
|
||||
case '':
|
||||
case 'application/zip': { // We think this is a .sprite2 or .sprite3 file
|
||||
@@ -194,9 +205,13 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp
|
||||
}
|
||||
case 'image/svg+xml':
|
||||
case 'image/png':
|
||||
case 'image/jpeg': {
|
||||
case 'image/jpeg':
|
||||
case 'image/gif': {
|
||||
// Make a sprite from an image by making it a costume first
|
||||
costumeUpload(fileData, fileType, `${spriteName}-${costumeName}`, storage, (vmCostume => {
|
||||
costumeUpload(fileData, fileType, storage, vmCostumes => {
|
||||
vmCostumes.forEach((costume, i) => {
|
||||
costume.name = `${spriteName}${i ? i + 1 : ''}`;
|
||||
});
|
||||
const newSprite = {
|
||||
name: spriteName,
|
||||
isStage: false,
|
||||
@@ -210,17 +225,17 @@ const spriteUpload = function (fileData, fileType, spriteName, storage, handleSp
|
||||
currentCostume: 0,
|
||||
blocks: {},
|
||||
variables: {},
|
||||
costumes: [vmCostume],
|
||||
costumes: vmCostumes,
|
||||
sounds: [] // TODO are all of these necessary?
|
||||
};
|
||||
randomizeSpritePosition(newSprite);
|
||||
// TODO probably just want sprite upload to handle this object directly
|
||||
handleSprite(JSON.stringify(newSprite));
|
||||
}));
|
||||
}, handleError);
|
||||
return;
|
||||
}
|
||||
default: {
|
||||
log.warn(`Encountered unexpected file type: ${fileType}`);
|
||||
handleError(`Encountered unexpected file type: ${fileType}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import {GifReader} from 'omggif';
|
||||
|
||||
export default (arrayBuffer, onFrame) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const gifReader = new GifReader(new Uint8Array(arrayBuffer));
|
||||
const numFrames = gifReader.numFrames();
|
||||
canvas.width = gifReader.width;
|
||||
canvas.height = gifReader.height;
|
||||
|
||||
let imageData = ctx.createImageData(canvas.width, canvas.height);
|
||||
let previousData = ctx.createImageData(canvas.width, canvas.height);
|
||||
|
||||
const loadFrame = i => {
|
||||
const framePixels = [];
|
||||
gifReader.decodeAndBlitFrameRGBA(i, framePixels);
|
||||
const {x, y, width, height, disposal} = gifReader.frameInfo(i);
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let column = 0; column < width; column++) {
|
||||
const indexOffset = 4 * (x + (y * canvas.width));
|
||||
const j = indexOffset + (4 * (column + (row * canvas.width)));
|
||||
if (framePixels[j + 3]) {
|
||||
imageData.data[j + 0] = framePixels[j + 0];
|
||||
imageData.data[j + 1] = framePixels[j + 1];
|
||||
imageData.data[j + 2] = framePixels[j + 2];
|
||||
imageData.data[j + 3] = framePixels[j + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
const dataUrl = canvas.toDataURL();
|
||||
|
||||
switch (disposal) {
|
||||
case 2: // "Return to background", blank out the current frame
|
||||
ctx.clearRect(x, y, width, height);
|
||||
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
break;
|
||||
case 3: // "Restore to previous", copy previous data to current
|
||||
imageData = ctx.createImageData(canvas.width, canvas.height);
|
||||
imageData.data.set(previousData.data);
|
||||
break;
|
||||
default: // 0 and 1, as well as 4+ modes = do-not-dispose, so cache frame
|
||||
previousData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
break;
|
||||
|
||||
}
|
||||
onFrame(i, dataUrl, numFrames);
|
||||
|
||||
if (i < numFrames - 1) {
|
||||
setTimeout(() => {
|
||||
loadFrame(i + 1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
loadFrame(0);
|
||||
};
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
setProjectId
|
||||
} from '../reducers/project-state';
|
||||
|
||||
import {closePreviewInfo} from '../reducers/modals';
|
||||
|
||||
/* Higher Order Component to get the project id from location.hash
|
||||
* @param {React.Component} WrappedComponent: component to render
|
||||
* @returns {React.Component} component with hash parsing behavior
|
||||
@@ -74,9 +72,6 @@ const HashParserHOC = function (WrappedComponent) {
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
setProjectId: projectId => {
|
||||
dispatch(setProjectId(projectId));
|
||||
if (projectId !== defaultProjectId) {
|
||||
dispatch(closePreviewInfo());
|
||||
}
|
||||
}
|
||||
});
|
||||
// Allow incoming props to override redux-provided props. Used to mock in tests.
|
||||
|
||||
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 308 KiB |
@@ -0,0 +1,188 @@
|
||||
// Intro
|
||||
import introMove from './steps/intro-move.en.gif';
|
||||
import introSayHello from './steps/intro-say-hello.en.gif';
|
||||
|
||||
// Text to Speech
|
||||
import speechAddExtension from './steps/speech-add-extension.en.gif';
|
||||
import speechSaySomething from './steps/speech-say-something.en.gif';
|
||||
import speechSetVoice from './steps/speech-set-voice.en.gif';
|
||||
import speechMoveAround from './steps/speech-move-around.en.gif';
|
||||
import speechAddBackdrop from './steps/speech-add-backdrop.en.gif';
|
||||
import speechAddSprite from './steps/speech-add-sprite.en.gif';
|
||||
import speechSong from './steps/speech-song.en.gif';
|
||||
import speechChangeColor from './steps/speech-change-color.en.gif';
|
||||
import speechSpin from './steps/speech-spin.en.gif';
|
||||
import speechGrowShrink from './steps/speech-grow-shrink.en.gif';
|
||||
|
||||
// Cartoon Network
|
||||
import cnShowCharacter from './steps/cn-show-character.en.gif';
|
||||
import cnSay from './steps/cn-say.en.gif';
|
||||
import cnGlide from './steps/cn-glide.en.gif';
|
||||
import cnPickSprite from './steps/cn-pick-sprite.en.gif';
|
||||
import cnCollect from './steps/cn-collect.en.gif';
|
||||
import cnVariable from './steps/cn-variable.en.gif';
|
||||
import cnScore from './steps/cn-score.en.gif';
|
||||
import cnBackdrop from './steps/cn-backdrop.en.gif';
|
||||
|
||||
// Add sprite
|
||||
import addSprite from './steps/add-sprite.en.gif';
|
||||
|
||||
// Animate a name
|
||||
import namePickLetter from './steps/name-pick-letter.en.gif';
|
||||
import namePlaySound from './steps/name-play-sound.en.gif';
|
||||
import namePickLetter2 from './steps/name-pick-letter2.en.gif';
|
||||
import nameChangeColor from './steps/name-change-color.en.gif';
|
||||
import nameSpin from './steps/name-spin.en.gif';
|
||||
import nameGrow from './steps/name-grow.en.gif';
|
||||
|
||||
// Make-Music
|
||||
import musicPickInstrument from './steps/music-pick-instrument.en.gif';
|
||||
import musicPlaySound from './steps/music-play-sound.en.gif';
|
||||
import musicMakeSong from './steps/music-make-song.en.gif';
|
||||
import musicMakeBeat from './steps/music-make-beat.en.gif';
|
||||
import musicMakeBeatbox from './steps/music-make-beatbox.en.gif';
|
||||
|
||||
// Chase-Game
|
||||
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.en.gif';
|
||||
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.en.gif';
|
||||
import chaseGameRightLeft from './steps/chase-game-right-left.en.gif';
|
||||
import chaseGameUpDown from './steps/chase-game-up-down.en.gif';
|
||||
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.en.gif';
|
||||
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.en.gif';
|
||||
import chaseGamePlaySound from './steps/chase-game-play-sound.en.gif';
|
||||
import chaseGameAddVariable from './steps/chase-game-add-variable.en.gif';
|
||||
import chaseGameChangeScore from './steps/chase-game-change-score.en.gif';
|
||||
|
||||
// Clicker-Game (Pop Game)
|
||||
import popGamePickSprite from './steps/pop-game-pick-sprite.en.gif';
|
||||
import popGamePlaySound from './steps/pop-game-play-sound.en.gif';
|
||||
import popGameAddScore from './steps/pop-game-add-score.en.gif';
|
||||
import popGameChangeScore from './steps/pop-game-change-score.en.gif';
|
||||
import popGameRandomPosition from './steps/pop-game-random-position.en.gif';
|
||||
import popGameChangeColor from './steps/pop-game-change-color.en.gif';
|
||||
import popGameResetScore from './steps/pop-game-reset-score.en.gif';
|
||||
|
||||
// Animate A Character
|
||||
import animateCharPickBackdrop from './steps/animate-char-pick-backdrop.en.gif';
|
||||
import animateCharPickSprite from './steps/animate-char-pick-sprite.en.gif';
|
||||
import animateCharSaySomething from './steps/animate-char-say-something.en.gif';
|
||||
import animateCharAddSound from './steps/animate-char-add-sound.en.gif';
|
||||
import animateCharTalk from './steps/animate-char-talk.en.gif';
|
||||
import animateCharMove from './steps/animate-char-move.en.gif';
|
||||
import animateCharJump from './steps/animate-char-jump.en.gif';
|
||||
import animateCharChangeColor from './steps/animate-char-change-color.en.gif';
|
||||
|
||||
// Tell A Story
|
||||
import storyPickBackdrop from './steps/story-pick-backdrop.en.gif';
|
||||
import storyPickSprite from './steps/story-pick-sprite.en.gif';
|
||||
import storySaySomething from './steps/story-say-something.en.gif';
|
||||
import storyPickSprite2 from './steps/story-pick-sprite2.en.gif';
|
||||
import storyFlip from './steps/story-flip.en.gif';
|
||||
import storyConversation from './steps/story-conversation.en.gif';
|
||||
import storyPickBackdrop2 from './steps/story-pick-backdrop2.en.gif';
|
||||
import storySwitchBackdrop from './steps/story-switch-backdrop.en.gif';
|
||||
import storyHideCharacter from './steps/story-hide-character.en.gif';
|
||||
import storyShowCharacter from './steps/story-show-character.en.gif';
|
||||
|
||||
// Video Sensing
|
||||
import videoAddExtension from './steps/video-add-extension.en.gif';
|
||||
import videoPet from './steps/video-pet.en.gif';
|
||||
import videoAnimate from './steps/video-animate.en.gif';
|
||||
import videoPop from './steps/video-pop.en.gif';
|
||||
|
||||
const enImages = {
|
||||
// Intro
|
||||
introMove: introMove,
|
||||
introSayHello: introSayHello,
|
||||
|
||||
// Text to Speech
|
||||
speechAddExtension: speechAddExtension,
|
||||
speechSaySomething: speechSaySomething,
|
||||
speechSetVoice: speechSetVoice,
|
||||
speechMoveAround: speechMoveAround,
|
||||
speechAddBackdrop: speechAddBackdrop,
|
||||
speechAddSprite: speechAddSprite,
|
||||
speechSong: speechSong,
|
||||
speechChangeColor: speechChangeColor,
|
||||
speechSpin: speechSpin,
|
||||
speechGrowShrink: speechGrowShrink,
|
||||
|
||||
// Cartoon Network
|
||||
cnShowCharacter: cnShowCharacter,
|
||||
cnSay: cnSay,
|
||||
cnGlide: cnGlide,
|
||||
cnPickSprite: cnPickSprite,
|
||||
cnCollect: cnCollect,
|
||||
cnVariable: cnVariable,
|
||||
cnScore: cnScore,
|
||||
cnBackdrop: cnBackdrop,
|
||||
|
||||
// Add sprite
|
||||
addSprite: addSprite,
|
||||
|
||||
// Animate a name
|
||||
namePickLetter: namePickLetter,
|
||||
namePlaySound: namePlaySound,
|
||||
namePickLetter2: namePickLetter2,
|
||||
nameChangeColor: nameChangeColor,
|
||||
nameSpin: nameSpin,
|
||||
nameGrow: nameGrow,
|
||||
|
||||
// Make-Music
|
||||
musicPickInstrument: musicPickInstrument,
|
||||
musicPlaySound: musicPlaySound,
|
||||
musicMakeSong: musicMakeSong,
|
||||
musicMakeBeat: musicMakeBeat,
|
||||
musicMakeBeatbox: musicMakeBeatbox,
|
||||
|
||||
// Chase-Game
|
||||
chaseGameAddBackdrop: chaseGameAddBackdrop,
|
||||
chaseGameAddSprite1: chaseGameAddSprite1,
|
||||
chaseGameRightLeft: chaseGameRightLeft,
|
||||
chaseGameUpDown: chaseGameUpDown,
|
||||
chaseGameAddSprite2: chaseGameAddSprite2,
|
||||
chaseGameMoveRandomly: chaseGameMoveRandomly,
|
||||
chaseGamePlaySound: chaseGamePlaySound,
|
||||
chaseGameAddVariable: chaseGameAddVariable,
|
||||
chaseGameChangeScore: chaseGameChangeScore,
|
||||
|
||||
// Make-A-Pop/Clicker Game
|
||||
popGamePickSprite: popGamePickSprite,
|
||||
popGamePlaySound: popGamePlaySound,
|
||||
popGameAddScore: popGameAddScore,
|
||||
popGameChangeScore: popGameChangeScore,
|
||||
popGameRandomPosition: popGameRandomPosition,
|
||||
popGameChangeColor: popGameChangeColor,
|
||||
popGameResetScore: popGameResetScore,
|
||||
|
||||
// Animate A Character
|
||||
animateCharPickBackdrop: animateCharPickBackdrop,
|
||||
animateCharPickSprite: animateCharPickSprite,
|
||||
animateCharSaySomething: animateCharSaySomething,
|
||||
animateCharAddSound: animateCharAddSound,
|
||||
animateCharTalk: animateCharTalk,
|
||||
animateCharMove: animateCharMove,
|
||||
animateCharJump: animateCharJump,
|
||||
animateCharChangeColor: animateCharChangeColor,
|
||||
|
||||
// Tell A Story
|
||||
storyPickBackdrop: storyPickBackdrop,
|
||||
storyPickSprite: storyPickSprite,
|
||||
storySaySomething: storySaySomething,
|
||||
storyPickSprite2: storyPickSprite2,
|
||||
storyFlip: storyFlip,
|
||||
storyConversation: storyConversation,
|
||||
storyPickBackdrop2: storyPickBackdrop2,
|
||||
storySwitchBackdrop: storySwitchBackdrop,
|
||||
storyHideCharacter: storyHideCharacter,
|
||||
storyShowCharacter: storyShowCharacter,
|
||||
|
||||
// Video Sensing
|
||||
videoAddExtension: videoAddExtension,
|
||||
videoPet: videoPet,
|
||||
videoAnimate: videoAnimate,
|
||||
videoPop: videoPop
|
||||
|
||||
};
|
||||
|
||||
export {enImages};
|
||||
@@ -0,0 +1,188 @@
|
||||
// Intro
|
||||
import introMove from './steps/intro-move.es.gif';
|
||||
import introSayHello from './steps/intro-say-hello.es.gif';
|
||||
|
||||
// Text to Speech
|
||||
// import speechAddExtension from './steps/speech-add-extension.es.gif';
|
||||
// import speechSaySomething from './steps/speech-say-something.es.gif';
|
||||
// import speechSetVoice from './steps/speech-set-voice.es.gif';
|
||||
// import speechMoveAround from './steps/speech-move-around.es.gif';
|
||||
// import speechAddBackdrop from './steps/speech-add-backdrop.es.gif';
|
||||
// import speechAddSprite from './steps/speech-add-sprite.es.gif';
|
||||
// import speechSong from './steps/speech-song.es.gif';
|
||||
// import speechChangeColor from './steps/speech-change-color.es.gif';
|
||||
// import speechSpin from './steps/speech-spin.es.gif';
|
||||
// import speechGrowShrink from './steps/speech-grow-shrink.es.gif';
|
||||
//
|
||||
// // Cartoon Network
|
||||
// import cnShowCharacter from './steps/cn-show-character.es.gif';
|
||||
// import cnSay from './steps/cn-say.es.gif';
|
||||
// import cnGlide from './steps/cn-glide.es.gif';
|
||||
// import cnPickSprite from './steps/cn-pick-sprite.es.gif';
|
||||
// import cnCollect from './steps/cn-collect.es.gif';
|
||||
// import cnVariable from './steps/cn-variable.es.gif';
|
||||
// import cnScore from './steps/cn-score.es.gif';
|
||||
// import cnBackdrop from './steps/cn-backdrop.es.gif';
|
||||
|
||||
// Add sprite
|
||||
import addSprite from './steps/add-sprite.es.gif';
|
||||
|
||||
// Animate a name
|
||||
import namePickLetter from './steps/name-pick-letter.es.gif';
|
||||
import namePlaySound from './steps/name-play-sound.es.gif';
|
||||
import namePickLetter2 from './steps/name-pick-letter2.es.gif';
|
||||
import nameChangeColor from './steps/name-change-color.es.gif';
|
||||
import nameSpin from './steps/name-spin.es.gif';
|
||||
import nameGrow from './steps/name-grow.es.gif';
|
||||
|
||||
// Make-Music
|
||||
import musicPickInstrument from './steps/music-pick-instrument.es.gif';
|
||||
import musicPlaySound from './steps/music-play-sound.es.gif';
|
||||
import musicMakeSong from './steps/music-make-song.es.gif';
|
||||
import musicMakeBeat from './steps/music-make-beat.es.gif';
|
||||
import musicMakeBeatbox from './steps/music-make-beatbox.es.gif';
|
||||
|
||||
// Chase-Game
|
||||
import chaseGameAddBackdrop from './steps/chase-game-add-backdrop.es.gif';
|
||||
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.es.gif';
|
||||
import chaseGameRightLeft from './steps/chase-game-right-left.es.gif';
|
||||
import chaseGameUpDown from './steps/chase-game-up-down.es.gif';
|
||||
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.es.gif';
|
||||
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.es.gif';
|
||||
import chaseGamePlaySound from './steps/chase-game-play-sound.es.gif';
|
||||
import chaseGameAddVariable from './steps/chase-game-add-variable.es.gif';
|
||||
import chaseGameChangeScore from './steps/chase-game-change-score.es.gif';
|
||||
|
||||
// Clicker-Game (Pop Game)
|
||||
import popGamePickSprite from './steps/pop-game-pick-sprite.es.gif';
|
||||
import popGamePlaySound from './steps/pop-game-play-sound.es.gif';
|
||||
import popGameAddScore from './steps/pop-game-add-score.es.gif';
|
||||
import popGameChangeScore from './steps/pop-game-change-score.es.gif';
|
||||
import popGameRandomPosition from './steps/pop-game-random-position.es.gif';
|
||||
import popGameChangeColor from './steps/pop-game-change-color.es.gif';
|
||||
import popGameResetScore from './steps/pop-game-reset-score.es.gif';
|
||||
|
||||
// Animate A Character
|
||||
import animateCharPickBackdrop from './steps/animate-char-pick-backdrop.es.gif';
|
||||
import animateCharPickSprite from './steps/animate-char-pick-sprite.es.gif';
|
||||
import animateCharSaySomething from './steps/animate-char-say-something.es.gif';
|
||||
import animateCharAddSound from './steps/animate-char-add-sound.es.gif';
|
||||
import animateCharTalk from './steps/animate-char-talk.es.gif';
|
||||
import animateCharMove from './steps/animate-char-move.es.gif';
|
||||
import animateCharJump from './steps/animate-char-jump.es.gif';
|
||||
import animateCharChangeColor from './steps/animate-char-change-color.es.gif';
|
||||
|
||||
// Tell A Story
|
||||
import storyPickBackdrop from './steps/story-pick-backdrop.es.gif';
|
||||
import storyPickSprite from './steps/story-pick-sprite.es.gif';
|
||||
import storySaySomething from './steps/story-say-something.es.gif';
|
||||
import storyPickSprite2 from './steps/story-pick-sprite2.es.gif';
|
||||
import storyFlip from './steps/story-flip.es.gif';
|
||||
import storyConversation from './steps/story-conversation.es.gif';
|
||||
import storyPickBackdrop2 from './steps/story-pick-backdrop2.es.gif';
|
||||
import storySwitchBackdrop from './steps/story-switch-backdrop.es.gif';
|
||||
import storyHideCharacter from './steps/story-hide-character.es.gif';
|
||||
import storyShowCharacter from './steps/story-show-character.es.gif';
|
||||
|
||||
// Video Sensing
|
||||
import videoAddExtension from './steps/video-add-extension.es.gif';
|
||||
import videoPet from './steps/video-pet.es.gif';
|
||||
import videoAnimate from './steps/video-animate.es.gif';
|
||||
import videoPop from './steps/video-pop.es.gif';
|
||||
|
||||
const esImages = {
|
||||
// Intro
|
||||
introMove: introMove,
|
||||
introSayHello: introSayHello,
|
||||
|
||||
// // Text to Speech
|
||||
// speechAddExtension: speechAddExtension,
|
||||
// speechSaySomething: speechSaySomething,
|
||||
// speechSetVoice: speechSetVoice,
|
||||
// speechMoveAround: speechMoveAround,
|
||||
// speechAddBackdrop: speechAddBackdrop,
|
||||
// speechAddSprite: speechAddSprite,
|
||||
// speechSong: speechSong,
|
||||
// speechChangeColor: speechChangeColor,
|
||||
// speechSpin: speechSpin,
|
||||
// speechGrowShrink: speechGrowShrink,
|
||||
//
|
||||
// // Cartoon Network
|
||||
// cnShowCharacter: cnShowCharacter,
|
||||
// cnSay: cnSay,
|
||||
// cnGlide: cnGlide,
|
||||
// cnPickSprite: cnPickSprite,
|
||||
// cnCollect: cnCollect,
|
||||
// cnVariable: cnVariable,
|
||||
// cnScore: cnScore,
|
||||
// cnBackdrop: cnBackdrop,
|
||||
|
||||
// Add sprite
|
||||
addSprite: addSprite,
|
||||
|
||||
// Animate a name
|
||||
namePickLetter: namePickLetter,
|
||||
namePlaySound: namePlaySound,
|
||||
namePickLetter2: namePickLetter2,
|
||||
nameChangeColor: nameChangeColor,
|
||||
nameSpin: nameSpin,
|
||||
nameGrow: nameGrow,
|
||||
|
||||
// Make-Music
|
||||
musicPickInstrument: musicPickInstrument,
|
||||
musicPlaySound: musicPlaySound,
|
||||
musicMakeSong: musicMakeSong,
|
||||
musicMakeBeat: musicMakeBeat,
|
||||
musicMakeBeatbox: musicMakeBeatbox,
|
||||
|
||||
// Chase-Game
|
||||
chaseGameAddBackdrop: chaseGameAddBackdrop,
|
||||
chaseGameAddSprite1: chaseGameAddSprite1,
|
||||
chaseGameRightLeft: chaseGameRightLeft,
|
||||
chaseGameUpDown: chaseGameUpDown,
|
||||
chaseGameAddSprite2: chaseGameAddSprite2,
|
||||
chaseGameMoveRandomly: chaseGameMoveRandomly,
|
||||
chaseGamePlaySound: chaseGamePlaySound,
|
||||
chaseGameAddVariable: chaseGameAddVariable,
|
||||
chaseGameChangeScore: chaseGameChangeScore,
|
||||
|
||||
// Make-A-Pop/Clicker Game
|
||||
popGamePickSprite: popGamePickSprite,
|
||||
popGamePlaySound: popGamePlaySound,
|
||||
popGameAddScore: popGameAddScore,
|
||||
popGameChangeScore: popGameChangeScore,
|
||||
popGameRandomPosition: popGameRandomPosition,
|
||||
popGameChangeColor: popGameChangeColor,
|
||||
popGameResetScore: popGameResetScore,
|
||||
|
||||
// Animate A Character
|
||||
animateCharPickBackdrop: animateCharPickBackdrop,
|
||||
animateCharPickSprite: animateCharPickSprite,
|
||||
animateCharSaySomething: animateCharSaySomething,
|
||||
animateCharAddSound: animateCharAddSound,
|
||||
animateCharTalk: animateCharTalk,
|
||||
animateCharMove: animateCharMove,
|
||||
animateCharJump: animateCharJump,
|
||||
animateCharChangeColor: animateCharChangeColor,
|
||||
|
||||
// Tell A Story
|
||||
storyPickBackdrop: storyPickBackdrop,
|
||||
storyPickSprite: storyPickSprite,
|
||||
storySaySomething: storySaySomething,
|
||||
storyPickSprite2: storyPickSprite2,
|
||||
storyFlip: storyFlip,
|
||||
storyConversation: storyConversation,
|
||||
storyPickBackdrop2: storyPickBackdrop2,
|
||||
storySwitchBackdrop: storySwitchBackdrop,
|
||||
storyHideCharacter: storyHideCharacter,
|
||||
storyShowCharacter: storyShowCharacter,
|
||||
|
||||
// Video Sensing
|
||||
videoAddExtension: videoAddExtension,
|
||||
videoPet: videoPet,
|
||||
videoAnimate: videoAnimate,
|
||||
videoPop: videoPop
|
||||
|
||||
};
|
||||
|
||||
export {esImages};
|
||||
@@ -1,120 +1,51 @@
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
// Tutorial thumbnails: Avoid using any text that would need to be
|
||||
// translated in thumbnails.
|
||||
// Intro
|
||||
import libraryIntro from './intro/lib-getting-started.jpg';
|
||||
import stepMove from './intro/intro1.gif';
|
||||
import stepMoveSayHello from './intro/intro2.gif';
|
||||
import libraryIntro from './thumbnails/getting-started.jpg';
|
||||
|
||||
// Text to Speech
|
||||
import libraryTXTSpeech from './txt/lib_txt-to-speech.jpg';
|
||||
import stepAddTXTextension from './txt/01_hoc-add-extensions.gif';
|
||||
import stepTXTSpeech from './txt/02_hoc-say-something.gif';
|
||||
import stepTXTSetVoice from './txt/03_hoc-set-voice.gif';
|
||||
import stepTXTMove from './txt/04_hoc-move-around.gif';
|
||||
import stepTXTBackdrop from './txt/05_hoc-add-backdrop.gif';
|
||||
import stepTXTAddSprite from './txt/06_hoc-add-character.gif';
|
||||
import stepTXTSong from './txt/07_hoc-perform-song.gif';
|
||||
import stepTXTColor from './txt/08_hoc-color-click.gif';
|
||||
import stepTXTSpin from './txt/09_hoc-spin.gif';
|
||||
import stepTXTGrow from './txt/10_hoc-grow-shrink.gif';
|
||||
import libraryTXTSpeech from './thumbnails/text-to-speech.jpg';
|
||||
|
||||
// Cartoon Network
|
||||
import libraryCartoonNetwork from './cartoonnetwork/lib_CartoonNetwork.jpg';
|
||||
import stepCNcharacter from './cartoonnetwork/1-cn-hideshow.gif';
|
||||
import stepCNsay from './cartoonnetwork/2-cn-say.gif';
|
||||
import stepCNglide from './cartoonnetwork/3-cn-glide.gif';
|
||||
import stepCNpicksprite from './cartoonnetwork/4-cn-pick-gem.gif';
|
||||
import stepCNcollect from './cartoonnetwork/5-cn-collect.gif';
|
||||
import stepCNvariable from './cartoonnetwork/6-cn-variable.gif';
|
||||
import stepCNscore from './cartoonnetwork/7-cn-score.gif';
|
||||
import stepCNbackdrop from './cartoonnetwork/8-cn-change-backdrop.gif';
|
||||
import libraryCartoonNetwork from './thumbnails/cartoon-network.jpg';
|
||||
|
||||
// Add sprite
|
||||
import libraryAddSprite from './sprite/cover-add-sprite.jpg';
|
||||
import stepAddSprite from './sprite/intro-choose-sprite.gif';
|
||||
import libraryAddSprite from './thumbnails/add-sprite.jpg';
|
||||
|
||||
// Animate a name
|
||||
import libraryAnimate from './animate/lib_animate-a-name.jpg';
|
||||
import stepAnimatePickLetter from './animate/animate-name-pick-a-letter.gif';
|
||||
import stepAnimatePlaySound from './animate/animate-name-play-sound.gif';
|
||||
import stepAnimatePickLetter2 from './animate/animate-name-pick-a-letter2.gif';
|
||||
import stepAnimateChangeColor from './animate/animate-name-change-color.gif';
|
||||
import stepAnimateSpin from './animate/animate-name-spin.gif';
|
||||
import stepAnimateGrow from './animate/animate-name-grow.gif';
|
||||
|
||||
import libraryAnimate from './thumbnails/animate-a-name.jpg';
|
||||
|
||||
// Make-Music
|
||||
import libraryMakeMusic from './make-music/lib-make-music.jpg';
|
||||
import stepPickInstrument from './make-music/make-music-pick-instrument.gif';
|
||||
import stepPlaySoundClick from './make-music/make-music-play-sound.gif';
|
||||
import stepMakeSong from './make-music/make-music-make-song.gif';
|
||||
import stepMakeBeat from './make-music/make-music-make-beat.gif';
|
||||
import stepMakeBeatbox from './make-music/make-music-beatbox.gif';
|
||||
import libraryMakeMusic from './thumbnails/make-music.jpg';
|
||||
|
||||
// Chase-Game
|
||||
import libraryChaseGame from './chase-game/lib-chasegame.jpg';
|
||||
import stepBG from './chase-game/chase-game-add-backdrop.gif';
|
||||
import stepAddOcto from './chase-game/chase-game-add-sprite1.gif';
|
||||
import stepLeftRight from './chase-game/chase-game-move-rightleft.gif';
|
||||
import stepUpDown from './chase-game/chase-game-move-updown.gif';
|
||||
import stepAddStar from './chase-game/chase-game-add-sprite2.gif';
|
||||
import stepMoveRandom from './chase-game/chase-game-move-randomly.gif';
|
||||
import stepWhenTouch from './chase-game/chase-game-touching.gif';
|
||||
import stepScoreVariable from './chase-game/chase-game-add-variable.gif';
|
||||
import stepScoreWhenTouch from './chase-game/chase-game-change-score.gif';
|
||||
import libraryChaseGame from './thumbnails/chase-game.jpg';
|
||||
|
||||
// Make-A-Game
|
||||
import libraryMakeAGame from './game/lib-pop.jpg';
|
||||
import stepGamePickSprite from './game/game-pick-sprite.gif';
|
||||
import stepGamePlaySound from './game/game-play-sound.gif';
|
||||
import stepGameAddScore from './game/game-add-score.gif';
|
||||
import stepGameChangeScore from './game/game-change-score.gif';
|
||||
import stepRandom from './game/game-random-position.gif';
|
||||
import stepGameChangeColor from './game/game-change-color.gif';
|
||||
import stepResetScore from './game/game-reset-score.gif';
|
||||
import libraryMakeAGame from './thumbnails/pop-game.jpg';
|
||||
|
||||
// Animate A Character
|
||||
import libraryAnimateChar from './animate-char/lib_Animate_a_Character.jpg';
|
||||
import stepAnimateCharPickBackdrop from './animate-char/animate-char-pick-backdrop.gif';
|
||||
import stepAnimateCharPickSprite from './animate-char/animate-char-pick-sprite.gif';
|
||||
import stepAnimateCharSaySomething from './animate-char/animate-char-say-something.gif';
|
||||
import stepAnimateCharAddSound from './animate-char/animate-char-add-sound.gif';
|
||||
import stepAnimateCharTalk from './animate-char/animate-char-talk.gif';
|
||||
import stepAnimateCharMove from './animate-char/animate-char-move.gif';
|
||||
import stepAnimateCharJump from './animate-char/animate-char-jump.gif';
|
||||
import stepAnimateCharChangeColor from './animate-char/animate-char-change-color.gif';
|
||||
import libraryAnimateChar from './thumbnails/animate-a-character.jpg';
|
||||
|
||||
// Tell A Story
|
||||
import libraryStory from './story/lib-tell-a-story.jpg';
|
||||
import stepStoryPickBackdrop from './story/story-pick-backdrop.gif';
|
||||
import stepStoryPickSprite from './story/story-pick-sprite.gif';
|
||||
import stepStoryCharSaySomething from './story/story-say-something.gif';
|
||||
import stepStoryCharAddAnotherSprite from './story/story-pick-another-sprite.gif';
|
||||
import stepStoryFlip from './story/story-flip.gif';
|
||||
import stepStoryConversation from './story/story-conversation.gif';
|
||||
import stepStoryAddAnotherBackdrop from './story/story-add-background-2.gif';
|
||||
import stepStorySwitchBackdrop from './story/story-switch-backdrop.gif';
|
||||
import stepStoryHide from './story/story-hide-character.gif';
|
||||
import stepStoryShow from './story/story-show-character.gif';
|
||||
import libraryStory from './thumbnails/tell-a-story.jpg';
|
||||
|
||||
// Video Sensing
|
||||
import libraryVideoSens from './videosensing/lib_video_sensing.jpg';
|
||||
import stepVideoSensAddExtension from './videosensing/videosens-add-extension.gif';
|
||||
import stepVideoSensPet from './videosensing/videosens-pet-cat.gif';
|
||||
import stepVideoSensAnimate from './videosensing/videosens-animate.gif';
|
||||
import stepVideoSensPop from './videosensing/videosens-pop.gif';
|
||||
import libraryVideoSens from './thumbnails/video-sensing.jpg';
|
||||
|
||||
// Videos
|
||||
import recordASound from './videos/record-a-sound.jpg';
|
||||
import glideAroundThumb from './videos/glide-around.jpg';
|
||||
import changeSizeThumb from './videos/change-size.jpg';
|
||||
import switchCostumeThumb from './videos/animate-sprite.jpg';
|
||||
import hideAndShowThumb from './videos/hide-and-show.jpg';
|
||||
import addBackdropThumb from './videos/add-backdrop.jpg';
|
||||
import addEffectsThumb from './videos/add-effects.jpg';
|
||||
import moveArrowKeysThumb from './videos/move-arrow-keys.jpg';
|
||||
import spinThumb from './videos/spin.jpg';
|
||||
import recordASound from './thumbnails/record-a-sound.jpg';
|
||||
import glideAroundThumb from './thumbnails/glide-around.jpg';
|
||||
import changeSizeThumb from './thumbnails/change-size.jpg';
|
||||
import switchCostumeThumb from './thumbnails/animate-sprite.jpg';
|
||||
import hideAndShowThumb from './thumbnails/hide-and-show.jpg';
|
||||
import addBackdropThumb from './thumbnails/add-backdrop.jpg';
|
||||
import addEffectsThumb from './thumbnails/add-effects.jpg';
|
||||
import moveArrowKeysThumb from './thumbnails/move-arrow-keys.jpg';
|
||||
import spinThumb from './thumbnails/spin.jpg';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -138,7 +69,7 @@ export default {
|
||||
id="gui.howtos.intro-move.step_stepMove"
|
||||
/>
|
||||
),
|
||||
image: stepMove
|
||||
image: 'introMove'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -147,7 +78,7 @@ export default {
|
||||
id="gui.howtos.add-a-move-block.step_stepMoveSayHello"
|
||||
/>
|
||||
),
|
||||
image: stepMoveSayHello
|
||||
image: 'introSayHello'
|
||||
}, {
|
||||
deckIds: [
|
||||
'add-a-backdrop',
|
||||
@@ -180,16 +111,16 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_AddTXTextension"
|
||||
/>
|
||||
),
|
||||
image: stepAddTXTextension
|
||||
image: 'speechAddExtension'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
defaultMessage="Say Something"
|
||||
description="Step name for 'Say Something' step"
|
||||
id="gui.howtos. say-it-out-loud.step_TXTSpeech"
|
||||
id="gui.howtos.say-it-out-loud.step_TXTSpeech"
|
||||
/>
|
||||
),
|
||||
image: stepTXTSpeech
|
||||
image: 'speechSaySomething'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -198,7 +129,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud_TXTSetVoice"
|
||||
/>
|
||||
),
|
||||
image: stepTXTSetVoice
|
||||
image: 'speechSetVoice'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -207,7 +138,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTMove"
|
||||
/>
|
||||
),
|
||||
image: stepTXTMove
|
||||
image: 'speechMoveAround'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -216,7 +147,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTBackdrop"
|
||||
/>
|
||||
),
|
||||
image: stepTXTBackdrop
|
||||
image: 'speechAddBackdrop'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -225,7 +156,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTAddSprite"
|
||||
/>
|
||||
),
|
||||
image: stepTXTAddSprite
|
||||
image: 'speechAddSprite'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -234,7 +165,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTSong"
|
||||
/>
|
||||
),
|
||||
image: stepTXTSong
|
||||
image: 'speechSong'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -243,7 +174,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTColor"
|
||||
/>
|
||||
),
|
||||
image: stepTXTColor
|
||||
image: 'speechChangeColor'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -252,7 +183,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTSpin"
|
||||
/>
|
||||
),
|
||||
image: stepTXTSpin
|
||||
image: 'speechSpin'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -261,7 +192,7 @@ export default {
|
||||
id="gui.howtos.say-it-out-loud.step_TXTGrow"
|
||||
/>
|
||||
),
|
||||
image: stepTXTGrow
|
||||
image: 'speechGrowShrink'
|
||||
}, {
|
||||
deckIds: [
|
||||
'animate-a-name',
|
||||
@@ -295,7 +226,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNcharacter"
|
||||
/>
|
||||
),
|
||||
image: stepCNcharacter
|
||||
image: 'cnShowCharacter'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -304,7 +235,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNsay"
|
||||
/>
|
||||
),
|
||||
image: stepCNsay
|
||||
image: 'cnSay'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -313,7 +244,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNglide"
|
||||
/>
|
||||
),
|
||||
image: stepCNglide
|
||||
image: 'cnGlide'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -322,7 +253,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNpicksprite"
|
||||
/>
|
||||
),
|
||||
image: stepCNpicksprite
|
||||
image: 'cnPickSprite'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -331,7 +262,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNcollect"
|
||||
/>
|
||||
),
|
||||
image: stepCNcollect
|
||||
image: 'cnCollect'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -340,7 +271,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNvariable"
|
||||
/>
|
||||
),
|
||||
image: stepCNvariable
|
||||
image: 'cnVariable'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -349,7 +280,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNscore"
|
||||
/>
|
||||
),
|
||||
image: stepCNscore
|
||||
image: 'cnScore'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -358,7 +289,7 @@ export default {
|
||||
id="gui.howtos.cartoon-network.step_CNbackdrop"
|
||||
/>
|
||||
),
|
||||
image: stepCNbackdrop
|
||||
image: 'cnBackdrop'
|
||||
},
|
||||
{
|
||||
video: '6o76f5ivo1'
|
||||
@@ -392,7 +323,7 @@ export default {
|
||||
id="gui.howtos.animate-a-name.step_AnimatePickLetter"
|
||||
/>
|
||||
),
|
||||
image: stepAnimatePickLetter
|
||||
image: 'namePickLetter'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -401,7 +332,7 @@ export default {
|
||||
id="gui.howtos.animate-a-name.step_AnimatePlaySound"
|
||||
/>
|
||||
),
|
||||
image: stepAnimatePlaySound
|
||||
image: 'namePlaySound'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -410,7 +341,7 @@ export default {
|
||||
id="gui.howtos.animate-a-name.step_AnimatePickLetter2"
|
||||
/>
|
||||
),
|
||||
image: stepAnimatePickLetter2
|
||||
image: 'namePickLetter2'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -419,7 +350,7 @@ export default {
|
||||
id="gui.howtos.animate-a-name.step_AnimateChangeColor"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateChangeColor
|
||||
image: 'nameChangeColor'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -428,7 +359,7 @@ export default {
|
||||
id="gui.howtos.animate-a-name.step_AnimateSpin"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateSpin
|
||||
image: 'nameSpin'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -437,7 +368,7 @@ export default {
|
||||
id="gui.howtos.animate-a-name.step_AnimateGrow"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateGrow
|
||||
image: 'nameGrow'
|
||||
}, {
|
||||
deckIds: [
|
||||
'add-a-backdrop',
|
||||
@@ -468,7 +399,7 @@ export default {
|
||||
id="gui.howtos.Make-Music.step_PickInstrument"
|
||||
/>
|
||||
),
|
||||
image: stepPickInstrument
|
||||
image: 'musicPickInstrument'
|
||||
},
|
||||
{
|
||||
title: (
|
||||
@@ -478,7 +409,7 @@ export default {
|
||||
id="gui.howtos.Make-Music.step_PlaySoundClick"
|
||||
/>
|
||||
),
|
||||
image: stepPlaySoundClick
|
||||
image: 'musicPlaySound'
|
||||
},
|
||||
{
|
||||
title: (
|
||||
@@ -488,7 +419,7 @@ export default {
|
||||
id="gui.howtos.Make-Music.step_MakeSong"
|
||||
/>
|
||||
),
|
||||
image: stepMakeSong
|
||||
image: 'musicMakeSong'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -497,7 +428,7 @@ export default {
|
||||
id="gui.howtos.make-music.step_MakeBeat"
|
||||
/>
|
||||
),
|
||||
image: stepMakeBeat
|
||||
image: 'musicMakeBeat'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -506,7 +437,7 @@ export default {
|
||||
id="gui.howtos.make-music.step_MakeBeatBox"
|
||||
/>
|
||||
),
|
||||
image: stepMakeBeatbox
|
||||
image: 'musicMakeBeatbox'
|
||||
}, {
|
||||
deckIds: [
|
||||
'add-a-backdrop',
|
||||
@@ -537,7 +468,7 @@ export default {
|
||||
id="gui.howtos.Make-A-Game.step_GamePickSprite"
|
||||
/>
|
||||
),
|
||||
image: stepGamePickSprite
|
||||
image: 'popGamePickSprite'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -546,7 +477,7 @@ export default {
|
||||
id="gui.howtos.make-a-game.step_GamePlaySound"
|
||||
/>
|
||||
),
|
||||
image: stepGamePlaySound
|
||||
image: 'popGamePlaySound'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -555,7 +486,7 @@ export default {
|
||||
id="gui.howtos.make-a-game.step_GameAddScore"
|
||||
/>
|
||||
),
|
||||
image: stepGameAddScore
|
||||
image: 'popGameAddScore'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -565,7 +496,7 @@ export default {
|
||||
/>
|
||||
),
|
||||
|
||||
image: stepGameChangeScore
|
||||
image: 'popGameChangeScore'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -574,7 +505,7 @@ export default {
|
||||
id="gui.howtos.make-a-game.step_Random"
|
||||
/>
|
||||
),
|
||||
image: stepRandom
|
||||
image: 'popGameRandomPosition'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -583,7 +514,7 @@ export default {
|
||||
id="gui.howtos.make-music.step_GameChangeColor"
|
||||
/>
|
||||
),
|
||||
image: stepGameChangeColor
|
||||
image: 'popGameChangeColor'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -592,7 +523,7 @@ export default {
|
||||
id="gui.howtos.make-music.step_ResetScore"
|
||||
/>
|
||||
),
|
||||
image: stepResetScore
|
||||
image: 'popGameResetScore'
|
||||
}, {
|
||||
deckIds: [
|
||||
'add-a-backdrop',
|
||||
@@ -624,7 +555,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.step_BG"
|
||||
/>
|
||||
),
|
||||
image: stepBG
|
||||
image: 'chaseGameAddBackdrop'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -633,7 +564,7 @@ export default {
|
||||
id="gui.howtos.chase-game.step_AddOcto"
|
||||
/>
|
||||
),
|
||||
image: stepAddOcto
|
||||
image: 'chaseGameAddSprite1'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -642,7 +573,7 @@ export default {
|
||||
id="gui.howtos.make-music.step_LeftRight"
|
||||
/>
|
||||
),
|
||||
image: stepLeftRight
|
||||
image: 'chaseGameRightLeft'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -651,7 +582,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.step_UpDown"
|
||||
/>
|
||||
),
|
||||
image: stepUpDown
|
||||
image: 'chaseGameUpDown'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -660,7 +591,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.step_AddStar"
|
||||
/>
|
||||
),
|
||||
image: stepAddStar
|
||||
image: 'chaseGameAddSprite2'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -669,7 +600,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.step_MoveRandom"
|
||||
/>
|
||||
),
|
||||
image: stepMoveRandom
|
||||
image: 'chaseGameMoveRandomly'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -678,7 +609,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.step_WhenTouch"
|
||||
/>
|
||||
),
|
||||
image: stepWhenTouch
|
||||
image: 'chaseGamePlaySound'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -687,7 +618,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.step_ScoreVariable"
|
||||
/>
|
||||
),
|
||||
image: stepScoreVariable
|
||||
image: 'chaseGameAddVariable'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -696,7 +627,7 @@ export default {
|
||||
id="gui.howtos.Chase-Game.ScoreWhenTouch"
|
||||
/>
|
||||
),
|
||||
image: stepScoreWhenTouch
|
||||
image: 'chaseGameChangeScore'
|
||||
}, {
|
||||
deckIds: [
|
||||
'add-effects',
|
||||
@@ -726,7 +657,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_addbg"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharPickBackdrop
|
||||
image: 'animateCharPickBackdrop'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -735,7 +666,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_addsprite"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharPickSprite
|
||||
image: 'animateCharPickSprite'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -744,7 +675,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_saysomething"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharSaySomething
|
||||
image: 'animateCharSaySomething'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -753,7 +684,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_addsound"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharAddSound
|
||||
image: 'animateCharAddSound'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -762,7 +693,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_animatetalking"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharTalk
|
||||
image: 'animateCharTalk'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -771,7 +702,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_arrowkeys"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharMove
|
||||
image: 'animateCharMove'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -780,7 +711,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_jump"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharJump
|
||||
image: 'animateCharJump'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -789,7 +720,7 @@ export default {
|
||||
id="gui.howtos.animate-char.step_changecolor"
|
||||
/>
|
||||
),
|
||||
image: stepAnimateCharChangeColor
|
||||
image: 'animateCharChangeColor'
|
||||
}, {
|
||||
deckIds: [
|
||||
'Chase-Game',
|
||||
@@ -819,7 +750,7 @@ export default {
|
||||
id="gui.howtos.story.step_addbg"
|
||||
/>
|
||||
),
|
||||
image: stepStoryPickBackdrop
|
||||
image: 'storyPickBackdrop'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -828,7 +759,7 @@ export default {
|
||||
id="gui.howtos.story.step_addsprite"
|
||||
/>
|
||||
),
|
||||
image: stepStoryPickSprite
|
||||
image: 'storyPickSprite'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -837,7 +768,7 @@ export default {
|
||||
id="gui.howtos.story.step_saysomething"
|
||||
/>
|
||||
),
|
||||
image: stepStoryCharSaySomething
|
||||
image: 'storySaySomething'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -846,7 +777,7 @@ export default {
|
||||
id="gui.howtos.story.step_addanothersprite"
|
||||
/>
|
||||
),
|
||||
image: stepStoryCharAddAnotherSprite
|
||||
image: 'storyPickSprite2'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -855,7 +786,7 @@ export default {
|
||||
id="gui.howtos.story.step_flip"
|
||||
/>
|
||||
),
|
||||
image: stepStoryFlip
|
||||
image: 'storyFlip'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -864,7 +795,7 @@ export default {
|
||||
id="gui.howtos.story.step_conversation"
|
||||
/>
|
||||
),
|
||||
image: stepStoryConversation
|
||||
image: 'storyConversation'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -873,7 +804,7 @@ export default {
|
||||
id="gui.howtos.story.addanotherbg"
|
||||
/>
|
||||
),
|
||||
image: stepStoryAddAnotherBackdrop
|
||||
image: 'storyPickBackdrop2'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -882,7 +813,7 @@ export default {
|
||||
id="gui.howtos.story.step_swithbg"
|
||||
/>
|
||||
),
|
||||
image: stepStorySwitchBackdrop
|
||||
image: 'storySwitchBackdrop'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -891,7 +822,7 @@ export default {
|
||||
id="gui.howtos.story.step_hidewizard"
|
||||
/>
|
||||
),
|
||||
image: stepStoryHide
|
||||
image: 'storyHideCharacter'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -900,7 +831,7 @@ export default {
|
||||
id="gui.howtos.story.step_showwizard"
|
||||
/>
|
||||
),
|
||||
image: stepStoryShow
|
||||
image: 'storyShowCharacter'
|
||||
},
|
||||
{
|
||||
deckIds: [
|
||||
@@ -931,7 +862,7 @@ export default {
|
||||
id="gui.howtos.videosens.step_addextension"
|
||||
/>
|
||||
),
|
||||
image: stepVideoSensAddExtension
|
||||
image: 'videoAddExtension'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -940,7 +871,7 @@ export default {
|
||||
id="gui.howtos.videosens.step_pet"
|
||||
/>
|
||||
),
|
||||
image: stepVideoSensPet
|
||||
image: 'videoPet'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -949,7 +880,7 @@ export default {
|
||||
id="gui.howtos.videosens.step_animate"
|
||||
/>
|
||||
),
|
||||
image: stepVideoSensAnimate
|
||||
image: 'videoAnimate'
|
||||
}, {
|
||||
title: (
|
||||
<FormattedMessage
|
||||
@@ -958,7 +889,7 @@ export default {
|
||||
id="gui.howtos.videosens.step_pop"
|
||||
/>
|
||||
),
|
||||
image: stepVideoSensPop
|
||||
image: 'videoPop'
|
||||
}, {
|
||||
deckIds: [
|
||||
'Make-Music',
|
||||
@@ -987,7 +918,7 @@ export default {
|
||||
id="gui.howtos.add-sprite.step_addSprite"
|
||||
/>
|
||||
),
|
||||
image: stepAddSprite
|
||||
image: 'addSprite'
|
||||
},
|
||||
{
|
||||
deckIds: [
|
||||
|
||||
|
Before Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 652 KiB |
|
After Width: | Height: | Size: 802 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 718 KiB |