Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeb5cfd930 | ||
|
|
99bdad90d6 | ||
|
|
1f5ac6e383 | ||
|
|
9cb5f3f96d | ||
|
|
09b14ed4d4 | ||
|
|
5859af7efa | ||
|
|
d4e71024fb | ||
|
|
a83d9369dc | ||
|
|
012bb612ad | ||
|
|
b9e7bde676 | ||
|
|
992f012ff3 | ||
|
|
c53b88dbad |
No files matched your search
@@ -1,207 +0,0 @@
|
||||
version: 2.1
|
||||
aliases:
|
||||
- &save_git_cache
|
||||
save_cache:
|
||||
paths:
|
||||
- .git
|
||||
key: v1-git-{{ .Revision }}
|
||||
- &restore_git_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-git-{{ .Revision }}
|
||||
- v1-git-
|
||||
- &save_build_cache
|
||||
save_cache:
|
||||
paths:
|
||||
- build
|
||||
key: v1-build-{{ .Revision }}
|
||||
- &restore_build_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-build-{{ .Revision }}
|
||||
- &save_dist_cache
|
||||
save_cache:
|
||||
paths:
|
||||
- dist
|
||||
key: v1-dist-{{ .Revision }}
|
||||
- &restore_dist_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-dist-{{ .Revision }}
|
||||
- &save_npm_cache
|
||||
save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: v1-npm-{{ checksum "package-lock.json" }}
|
||||
- &restore_npm_cache
|
||||
restore_cache:
|
||||
keys:
|
||||
- v1-npm-{{ checksum "package-lock.json" }}
|
||||
- v1-npm-
|
||||
- &defaults
|
||||
docker:
|
||||
- image: circleci/node:8.16-browsers
|
||||
working_directory: ~/repo
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run: npm install
|
||||
- *save_git_cache
|
||||
- *save_npm_cache
|
||||
lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run:
|
||||
name: Lint
|
||||
command: npm run test:lint -- --quiet --output-file test-results/eslint/results.xml --format junit
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
unit:
|
||||
<<: *defaults
|
||||
environment:
|
||||
JEST_JUNIT_OUTPUT_NAME: results.xml
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run:
|
||||
name: Unit
|
||||
environment:
|
||||
JEST_JUNIT_OUTPUT_DIR: test-results/unit
|
||||
command: npm run test:unit -- --reporters="default" --reporters="jest-junit" --coverage --coverageReporters=text --coverageReporters=lcov
|
||||
- store_artifacts:
|
||||
path: coverage
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
build:
|
||||
<<: *defaults
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
NODE_OPTIONS: --max-old-space-size=4000
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run:
|
||||
name: Build
|
||||
command: npm run build
|
||||
- *save_build_cache
|
||||
- *save_dist_cache
|
||||
store_build:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_build_cache
|
||||
- store_artifacts:
|
||||
path: build
|
||||
store_dist:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_dist_cache
|
||||
- store_artifacts:
|
||||
path: dist
|
||||
integration:
|
||||
<<: *defaults
|
||||
parallelism: 2
|
||||
environment:
|
||||
JEST_JUNIT_OUTPUT_NAME: results.txt
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- *restore_build_cache
|
||||
- run:
|
||||
name: Integration
|
||||
environment:
|
||||
JEST_JUNIT_OUTPUT_DIR: test-results/integration
|
||||
command: |
|
||||
export TESTFILES=$(circleci tests glob "test/integration/*.test.js" | circleci tests split --split-by=timings)
|
||||
$(npm bin)/jest ${TESTFILES} --reporters="default" --reporters="jest-junit"
|
||||
- store_test_results:
|
||||
path: test-results
|
||||
|
||||
deploy-npm:
|
||||
<<: *defaults
|
||||
environment:
|
||||
NODE_OPTIONS: --max-old-space-size=4000
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- *restore_dist_cache
|
||||
- checkout
|
||||
- run: |
|
||||
echo export RELEASE_VERSION="0.1.0-prerelease.$(date +'%Y%m%d%H%M%S')" >> $BASH_ENV
|
||||
echo export NPM_TAG=circlelatest >> $BASH_ENV
|
||||
if [ "$CIRCLE_BRANCH" == "master" ]
|
||||
then echo export NPM_TAG=circlestable >> $BASH_ENV
|
||||
fi
|
||||
if [[ "$CIRCLE_BRANCH" == hotfix/* ]] # double brackets are important for matching the wildcard
|
||||
then echo export NPM_TAG=circlehotfix >> $BASH_ENV
|
||||
fi
|
||||
- run: npm version --no-git-tag-version $RELEASE_VERSION
|
||||
- run: |
|
||||
npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN
|
||||
npm publish --tag $NPM_TAG
|
||||
- run: git tag $RELEASE_VERSION
|
||||
- run: git push $CIRCLE_REPOSITORY_URL $RELEASE_VERSION
|
||||
|
||||
deploy-gh-pages:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- *restore_build_cache
|
||||
- run: |
|
||||
git config --global user.email $(git log --pretty=format:"%ae" -n1)
|
||||
git config --global user.name $(git log --pretty=format:"%an" -n1)
|
||||
- run: npm run deploy -- -e $CIRCLE_BRANCH
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-test-deploy:
|
||||
jobs:
|
||||
- setup
|
||||
- lint:
|
||||
requires:
|
||||
- setup
|
||||
- unit:
|
||||
requires:
|
||||
- setup
|
||||
- build:
|
||||
requires:
|
||||
- setup
|
||||
- integration:
|
||||
requires:
|
||||
- build
|
||||
- store_build:
|
||||
requires:
|
||||
- build
|
||||
- store_dist:
|
||||
requires:
|
||||
- build
|
||||
# Disable deployment while running in parallel with Travis
|
||||
# - deploy-npm:
|
||||
# requires:
|
||||
# - lint
|
||||
# - unit
|
||||
# - integration
|
||||
# - build
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - master
|
||||
# - develop
|
||||
# - /^hotfix\/.*/
|
||||
# - deploy-gh-pages:
|
||||
# requires:
|
||||
# - lint
|
||||
# - unit
|
||||
# - integration
|
||||
# - build
|
||||
@@ -12,7 +12,6 @@
|
||||
*.svg binary
|
||||
|
||||
# Prefer LF for most file types
|
||||
*.css text eol=lf
|
||||
*.frag text eol=lf
|
||||
*.htm text eol=lf
|
||||
*.html text eol=lf
|
||||
|
||||
@@ -14,6 +14,6 @@ npm-*
|
||||
/dist
|
||||
/.opt-in
|
||||
|
||||
# Generated translation files
|
||||
# generated translation files
|
||||
/translations
|
||||
/locale
|
||||
@@ -5,9 +5,6 @@
|
||||
/node_modules
|
||||
npm-*
|
||||
|
||||
# Double copies of all the static assets and tutorial gifs
|
||||
/src
|
||||
|
||||
# Testing
|
||||
/.nyc_output
|
||||
/coverage
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
language: node_js
|
||||
sudo: required
|
||||
dist: xenial
|
||||
dist: trusty
|
||||
addons:
|
||||
chrome: stable
|
||||
node_js:
|
||||
- 10
|
||||
- 8
|
||||
env:
|
||||
global:
|
||||
- CHROMEDRIVER_VERSION=LATEST
|
||||
- NODE_ENV=production
|
||||
- NODE_OPTIONS=--max-old-space-size=7250
|
||||
- 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
|
||||
before_install:
|
||||
# package-lock.json was introduced in npm@5
|
||||
- '[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest' # skipped when using node 9
|
||||
- npm install -g greenkeeper-lockfile
|
||||
install:
|
||||
- npm --production=false install
|
||||
- npm --production=false update
|
||||
before_script: greenkeeper-lockfile-update
|
||||
script:
|
||||
- npm test
|
||||
- if [ "$TRAVIS_EVENT_TYPE" == "pull_request" ] && [ "$TRAVIS_BRANCH" == "master" ]; then npm run test:smoke; fi
|
||||
after_script: greenkeeper-lockfile-upload
|
||||
before_deploy:
|
||||
- >
|
||||
if [ -z "$BEFORE_DEPLOY_RAN" ]; then
|
||||
@@ -35,6 +42,7 @@ deploy:
|
||||
branch:
|
||||
- master
|
||||
- develop
|
||||
- smoke
|
||||
- hotfix/*
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
skip_cleanup: true
|
||||
@@ -47,15 +55,25 @@ deploy:
|
||||
- master
|
||||
- develop
|
||||
- smoke
|
||||
- hotfix/*
|
||||
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:
|
||||
- master
|
||||
- $PREVIEW_BRANCH
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
access_key_id: $AWS_ACCESS_KEY_ID
|
||||
secret_access_key: $AWS_SECRET_ACCESS_KEY
|
||||
bucket: $AWS_BUCKET_NAME
|
||||
acl: public_read
|
||||
skip_cleanup: true
|
||||
local_dir: build
|
||||
- provider: script
|
||||
on:
|
||||
all_branches: true
|
||||
condition: $TRAVIS_EVENT_TYPE != cron && ! $TRAVIS_BRANCH =~ ^greenkeeper/
|
||||
tags: false # Don't push tags to gh-pages
|
||||
skip_cleanup: true
|
||||
script: npm run deploy -- -x -e $TRAVIS_BRANCH -r https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git
|
||||
- provider: script
|
||||
@@ -64,3 +82,5 @@ deploy:
|
||||
condition: $TRAVIS_EVENT_TYPE == cron
|
||||
skip_cleanup: true
|
||||
script: npm run i18n:src && npm run i18n:push
|
||||
after_deploy:
|
||||
- 'curl -X POST -H "Fastly-Key: $FASTLY_TOKEN" -H "Accept: application/json" https://api.fastly.com/service/$FASTLY_SERVICE_ID/purge_all'
|
||||
@@ -18,8 +18,6 @@ cd scratch-gui
|
||||
npm install
|
||||
```
|
||||
|
||||
**You may want to add `--depth=1` to the `git clone` command because there are some [large files in the git repository history](https://github.com/LLK/scratch-gui/issues/5140).**
|
||||
|
||||
## Getting started
|
||||
Running the project requires Node.js to be installed.
|
||||
|
||||
@@ -76,7 +74,7 @@ See [jest cli docs](https://facebook.github.io/jest/docs/en/cli.html#content) fo
|
||||
|
||||
*NOTE: If you're a windows user, please run these scripts in Windows `cmd.exe` instead of Git Bash/MINGW64.*
|
||||
|
||||
Before running any tests, make sure you have run `npm install` from this (scratch-gui) repository's top level.
|
||||
Before running any test, make sure you have run `npm install` from this (scratch-gui) repository's top level.
|
||||
|
||||
#### Main testing command
|
||||
|
||||
@@ -196,60 +194,5 @@ Further reading: [Stack Overflow](https://stackoverflow.com/questions/46602286/n
|
||||
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)
|
||||
|
||||
## Understanding the project state machine
|
||||
|
||||
Since so much code throughout scratch-gui depends on the state of the project, which goes through many different phases of loading, displaying and saving, we created a "finite state machine" to make it clear which state it is in at any moment. This is contained in the file src/reducers/project-state.js .
|
||||
|
||||
It can be hard to understand the code in src/reducers/project-state.js . There are several types of data and functions used, which relate to each other:
|
||||
|
||||
### Loading states
|
||||
|
||||
These include state constant strings like:
|
||||
|
||||
* `NOT_LOADED` (the default state),
|
||||
* `ERROR`,
|
||||
* `FETCHING_WITH_ID`,
|
||||
* `LOADING_VM_WITH_ID`,
|
||||
* `REMIXING`,
|
||||
* `SHOWING_WITH_ID`,
|
||||
* `SHOWING_WITHOUT_ID`,
|
||||
* etc.
|
||||
|
||||
### Transitions
|
||||
|
||||
These are names for the action which causes a state change. Some examples are:
|
||||
|
||||
* `START_FETCHING_NEW`,
|
||||
* `DONE_FETCHING_WITH_ID`,
|
||||
* `DONE_LOADING_VM_WITH_ID`,
|
||||
* `SET_PROJECT_ID`,
|
||||
* `START_AUTO_UPDATING`,
|
||||
|
||||
### How transitions relate to loading states
|
||||
|
||||
As this diagram of the project state machine shows, various transition actions can move us from one loading state to another:
|
||||
|
||||

|
||||
|
||||
_Note: for clarity, the diagram above excludes states and transitions relating to error handling._
|
||||
|
||||
#### Example
|
||||
|
||||
Here's an example of how states transition.
|
||||
|
||||
Suppose a user clicks on a project, and the page starts to load with url https://scratch.mit.edu/projects/123456 .
|
||||
|
||||
Here's what will happen in the project state machine:
|
||||
|
||||

|
||||
|
||||
1. When the app first mounts, the project state is `NOT_LOADED`.
|
||||
2. The `SET_PROJECT_ID` redux action is dispatched (from src/lib/project-fetcher-hoc.jsx), with `projectId` set to `123456`. This transitions the state from `NOT_LOADED` to `FETCHING_WITH_ID`.
|
||||
3. The `FETCHING_WITH_ID` state. In src/lib/project-fetcher-hoc.jsx, the `projectId` value `123456` is used to request the data for that project from the server.
|
||||
4. When the server responds with the data, src/lib/project-fetcher-hoc.jsx dispatches the `DONE_FETCHING_WITH_ID` action, with `projectData` set. This transitions the state from `FETCHING_WITH_ID` to `LOADING_VM_WITH_ID`.
|
||||
5. The `LOADING_VM_WITH_ID` state. In src/lib/vm-manager-hoc.jsx, we load the `projectData` into Scratch's virtual machine ("the vm").
|
||||
6. When loading is done, src/lib/vm-manager-hoc.jsx dispatches the `DONE_LOADING_VM_WITH_ID` action. This transitions the state from `LOADING_VM_WITH_ID` to `SHOWING_WITH_ID`
|
||||
7. The `SHOWING_WITH_ID` state. Now the project appears normally and is playable and editable.
|
||||
|
||||
## Donate
|
||||
We provide [Scratch](https://scratch.mit.edu) free of charge, and want to keep it that way! Please consider making a [donation](https://secure.donationpay.org/scratchfoundation/) to support our continued engineering, design, community, and resource development efforts. Donations of any size are appreciated. Thank you!
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1381px" height="1012px" viewBox="-0.5 -0.5 1381 1012" content="<mxfile host="www.draw.io" modified="2020-02-04T16:31:44.889Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36" etag="8a2X5Jj65jOGG04xBBh1" version="12.6.4" type="device"><diagram name="Page-1" id="58cdce13-f638-feb5-8d6f-7d28b1aa9fa0">7V1Zd6O4Ev41OffJHImdRydxujOT7XSSXuYlh9g4YcY2GUw6yfz6KxYBEqtBErjbeYkthMCo9FXVV6XiSDlZv3/y7ZfnS2/hrI5ksHg/Uk6PZBmqEKJ/YctH0gKBFrc8+e4iacsabt3/nKQRJK2v7sLZEh0Dz1sF7gvZOPc2G2ceEG2273tvZLeltyKv+mI/JVcEWcPt3F45hW7f3EXwnP6MXPfPjvv0nFza1JIDj/b8nyffe90k1zuSlWX0Fx9e23ispP/22V54b7kmZXaknPieF8Sf1u8nzip8uPixxeedVRxN79t3NkGbExRTfrTgo6ZB+LgAS2OSjPDTXr06+CdENxp84IeDRkDzgL4cPwfrFWqD6CP6IS/h8W1g+8FtYAfh8aW7Wp14K8+PTlRA9Bd2DnzvHyd3ZLlMjkSPzllkg8ZPJzw099buPPm8sh+d1XH6rPFIG28TXdbbBGf22l2F4vfV8Rf2xk6aE1GDcvK97O7slfu0QW1z9AwddPB44fpIyFwvbNx6r+EUHhcfNH5yjh8477mm5MF/cry1E/gfqEtyVMeLIlklE6gnUvGWyZySND3npA232YmUP6VDZzONPiSTXT7x/z3CCw98/uv8z5/G2fftuxxsLieqWZzsBVoYyVfPD569J29jr2ZZ6/H81f+ZzljW5cLzXpLGv50g+EievP0aeKTkOO9u8D2cV0lLvv3IHTl9T6Y8+vKBv2zQz82dFH79kT+WnRZ9w+dVzhqaVn/uVKyJZJqQZD85Qc3jS55e+Mhq5993Vnbg/iSxps9knn9Tzq//Mh+Xp856onvw6uz934lSspL1VZAIPjHL+r+vHj4w2UYTNUUdIHh5zw6iT0/h/9Prq9nDxfX09Pzq08PXy4dv53efH85P8eDoXuPx494FgUKLIyAFoLDewiXkIjCeJgfW7mIRiZrvoJuzH6Ohwul88dxNED037fhIO60GhgyG8R0VlmmqTJLx87ohE5eaZVO5zidAUlVFjQdrPffJcDfhT8zGope+t1xukUzSspLeRCvxqRL4StHBUxu3bV/sTStxgmXiVCtJ8dDk5R59uqWtPFtl11959mIbzrLv/Y1m/9QO7PDRbBBMoSewrhTrqptpkn9Sw709u4Fz+2JH2POGrClqafjzBDdltahPQ4Ge1+vTkWhQBtoyXY4f1PecskwVaF5b6ry0JSzMbbOy7KUfIdDzGlICWGGyVZKEQg9HvHF8Fz2ycCZ7aFG9qEULfQzWGjQ6der79keuQ6I2KkFW0wxC1CBlQxcwGdR0Rx/i63dF51LZM3RG2j2eT7ByN84Er5roaLi8tCJc3s7uHm6+XP8xO7nLQXUGg28uWowpmp4vwivIur0OgS0BxPBxdUXPkVgPjSZBFbxVWRl1xoNpQtJPMJiYEhMKUHUOlkXps1E64CapNQV5GcxwrvQxyEN5C+WAUub4jxVQ0E2E97HXQIJM27m7eUKtentYwT5db1hBLommkvSDzAZVVBKrhKGKJsgaY2VUxWOYxg6kB09TrNyx5WKOFWTG1EmhgbpGDhHfaHJWJjq72nW6SbkQ9XadpdZ152TXyYxguACzd9Mvd3na5uz8YvZwfxO27A1mtkRJTF+wML5kA1DCyQYmddLFmMgWB6CsWvjMpevqOpatWWtZOtAg3WgQjQKlSRqTGowHMfbFnhcbNTCKSraKaB1JxICX7okiBmezu5PPoe6hWN7MdEd4VyBi90oxsYwpGE2KSVGxrZAiQT/NxFfzGA2yxTKkUCVqYgIKTyViDJa+tw5HdJAX4B9iCiNRpnRMQbMGVqVQeEyhk/ZUCO0pAWjs5P2Gh9g4sjhfqNGTxR25u7IK6S0otF1f4cp2gNhSFW7ygdnWiQDX93ddALffjZCGQ0oR5vjB1jfidlUFZtmNIRNi4wVJD3SXQUj4kTf67PhOOKnbzf/C6yBjwdlubSQDWrUrlTYX7nYPeVD2phOsB31kO2mqbpJOPWDi1dNxGgnIVv5PhIuPfwpzKz630q9m3x5OZ2fT+4u7/mu6Z1ZGu3V9sKDE0BFCszJkW3mcyxqw50ugQ2MxsTpYT7l5rX/6zmYxDVOLw5FenE3ckjx3s3a6W84bnp+VswzqZqfeImrBOuhDsQ6lcK13mLTxhoPrQr21k8LeQu3FBbEKB9dzQZVKpJoLOpg4eM3UWjgQ9qSDsEvMwWapWyNCPJdU/koMGaEk0cJZ2q+roCXreTBuhjVuBqeHtC700C+3P6MuVWEAIqhf7Kwp1Z4fOVOlbMv4k71Vus25AiwzNTUyIiP308BpqgBJK1ADMNHI5eHAIXeDwRzWAMmoz3QvXdI9cIQdLYxza4FMnsIoowlCi/K/k9Spqpymwgl452rhhPLjvbOeyiXNEoSCd9cPt9Ovsz0DsY6eQ82irgMxS4MWISITyAbIdIlKeoKKKOdCrZSvfo5EYz6dAFeCySAU3xq6JhTlKu3slrCNpVylgRSSE/Yekxt2w/vfOFF8HURyJCB8cnCturlWpj4gb1zuGHThIflYO2239RGeVeZoMfetzJa+lczatepkE6kGKVpqw/Y9ur9p8Td4qvXRXm22QVAhowcA4uTyPKu2944j04B0I11r6NhZxHkjChOTS5MlqsaIbIiyubQu+/x+D7KqNaCOq6IIbEri7Lc55f7mdHoX4sfx7Oz6y+zh5Mss/n5yffPjN4aPeCFVwgeSWquUIerJWfDgmcqDWYOy2oTlBRWlHVRACQCTtL+Sc2sAo2wDX/dkyF7Igl38sSALK4OoHFkup1f304sUYPYGS7I9wnJ6gucvHD/X+Sz64wI8emMiHTAU0m7BPkBfzlshRwWSZYL8nyoMnIa1YgAgXUNdV9vaMqZMARQAewRQeMCxAFR1nK4fgxgD1PT+7roMnkj6cGzwJB6Q6i2hsNCabJDQwahWClQhWlC5VF4yf1hXJCis0oHepdQBO0gKK8VkkBQqgYg9bGM07UZYda530M8yYl6Dqh/wVJdxZAE8X2aX598PmNO42uq8L6iRkMOm0qMmqSTGTFS00oRhTJdN3b0xJjNeZDZMTLOZwdwPKp9NhdppqKpUHCPGr94JAfR1tAbyWzNr+/Nhv3E8WTSPlOOlR+/6cQCyxi3lumlSLLTOBMp0XbLIyL/Ox1wqlbYucT1moTy5ZSyPMo2MJtuIgNpSCGNmJZVv5Rce1VNIoFKs+kQnuj9MKAOuwMaNH2dZdPPXhr5qCKh1Gy1QWvK1fzU8HvW6y5UqKMykSK/QVOUc8IX0eEuiikI+ubtXyAsA+eWJ7mzdaWQ0VwP11h1M5bjrCeiSAuxBXpvNfqGidyzhEDaXHFYBmQPKiEfT0AKnCoRaEhCWBtpUHaNrwaHbz9ffyusNHZIHBZTtUbATO9zGrC7Kt4e+jVjXPA2LlqzSkoUFkmVRBXySc2u0biaqlTGhneha3nyLphiSSiKNKsuSBXMMPrW1tYKBKSphw5TMXDRS2e0yjHZ+aJR/o8pK7V039Oe0r4NXLZJoX0cZr1O3qW1vMxKFc95ac1lcgHc0Mao+iIkhUaZA044jlvUIKwhIAXtDts6mfFtHtKkDVyM8KV6dLGIYP8zDtvRxmD/0tvThzR+1S+GdvuYPnRhj1Js/JdYJadRUzkwjZyCquKCB43+pDqcmlJFtUXUdvrYCr53wMQmwl0Eg4Yo/XsiVyEPVt4Ogp9LHm8Go/aZAMpR82TwML/ytAmPYbD/iZSCgHs8yd84gYuUpn8p810LrYqsqazjsV2KDb27xL8EuNpcjZ/jmIRz6ZeQ54CAzRViaHDCjXHENsnG0R05dcdtDQ5S6dDmzwxA+AeailqFSXRSDsp4YpeAYNDvZEKku9K+qyJFy6P36i9jfKh8i4eMM/cjNW1Flk0xnZAPPikZX/5hwKf9R/ir0YeumQbmDXbdrRhDnuHhr409mAd67Ym4B2xsw0TD69ReSTYS3VnFK/S7brj/69G/2iKjUk21o9aoGm+A3lVqhwBQR+Tu2LKrCN4a+Cy9/ONC/AkrnDE//6uMpndNQKDDHmuS2a7fZYsB4GyQseTtcXcrKSHYjydVcCoN3yTRuRsqs/a7XYXO3VbHl0atP8bunGv0NXQEUH6SxcTiIQXmwQbULu88KKVW0OywOsmXh/mxzVX61BXcMQlfEl1Fz9EtG9uN2D5TTP+RgFjExi7IX4WIGePCSgro8pF2EX8uX2kZKhxB5DwtH0M5KOhlUbVlqmRnVWb1Bm0GO296VrBFvYVQQTpmFAS2TTNHsyWgKMCKqs9P6GRFxIaQGE6IfIVAQWcZvhmtnTRxeDzdOpTwCskIdUCkDyQL5WECYyAa6lfs1x1BurnWdhZHRGNXMOgedvYcZ5xzUdMWGyVzgUaNSOLDQ9CUCoCQT42qSQtSSE1ZUJcVP1mo9LB92UOoLine truncated
|
||||
|
Before Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 27 KiB |
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "npm run clean && webpack --progress --colors --bail",
|
||||
"clean": "rimraf ./build && mkdirp build && rimraf ./dist && mkdirp dist",
|
||||
"deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"Build for $(git log --pretty=format:%H -n1) [skip ci]\"",
|
||||
"deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"Build for $(git log --pretty=format:%H -n1)\"",
|
||||
"prune": "./prune-gh-pages.sh",
|
||||
"i18n:push": "tx-push-src scratch-editor interface translations/en.json",
|
||||
"i18n:src": "rimraf ./translations/messages/src && babel src > tmp.js && rimraf tmp.js && build-i18n-src ./translations/messages/src ./translations/ && npm run i18n:push",
|
||||
@@ -44,7 +44,7 @@
|
||||
"babel-loader": "^8.0.4",
|
||||
"base64-loader": "1.0.0",
|
||||
"bowser": "1.9.4",
|
||||
"chromedriver": "80.0.0",
|
||||
"chromedriver": "76.0.0",
|
||||
"classnames": "2.2.6",
|
||||
"computed-style-to-inline-style": "3.0.0",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
@@ -67,7 +67,6 @@
|
||||
"immutable": "3.8.2",
|
||||
"intl": "1.2.5",
|
||||
"jest": "^21.0.0",
|
||||
"jest-junit": "^7.0.0",
|
||||
"js-base64": "2.4.9",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.bindall": "4.4.0",
|
||||
@@ -78,9 +77,9 @@
|
||||
"lodash.pick": "4.4.0",
|
||||
"lodash.throttle": "4.0.1",
|
||||
"minilog": "3.1.0",
|
||||
"mkdirp": "^1.0.3",
|
||||
"mkdirp": "^0.5.1",
|
||||
"omggif": "1.0.9",
|
||||
"papaparse": "5.1.1",
|
||||
"papaparse": "4.6.2",
|
||||
"postcss-import": "^12.0.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-simple-vars": "^5.0.1",
|
||||
@@ -103,18 +102,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.20200528195344",
|
||||
"scratch-blocks": "0.1.0-prerelease.20200528193139",
|
||||
"scratch-l10n": "3.9.20200528181319",
|
||||
"scratch-paint": "0.2.0-prerelease.20200529184518",
|
||||
"scratch-render": "0.1.0-prerelease.20200512203149",
|
||||
"scratch-storage": "1.3.3",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20200507183648",
|
||||
"scratch-vm": "0.2.0-prerelease.20200527195112",
|
||||
"scratch-audio": "0.1.0-prerelease.20190114210212",
|
||||
"scratch-l10n": "3.5.20190827223456",
|
||||
"scratch-blocks": "0.1.0-prerelease.1566984014",
|
||||
"scratch-paint": "0.2.0-prerelease.20190822205009",
|
||||
"scratch-render": "0.1.0-prerelease.20190822204747",
|
||||
"scratch-storage": "1.3.2",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20190822202608",
|
||||
"scratch-vm": "0.2.0-prerelease.20190822194548",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
@@ -139,8 +139,7 @@
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",
|
||||
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js",
|
||||
"editor-msgs(\\.js)?$": "<rootDir>/test/__mocks__/editor-msgs-mock.js"
|
||||
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
padding-right: 1rem;
|
||||
margin-bottom: 7px;
|
||||
min-height: 1.5rem;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.alert.warn {
|
||||
|
||||
@@ -50,16 +50,8 @@ $fade-out-distance: 100px;
|
||||
overflow-y: scroll;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-area:after {
|
||||
/* Make sure there is room to scroll beyond the last tile */
|
||||
content: '';
|
||||
display: block;
|
||||
height: 70px;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
order: 99999999;
|
||||
padding-bottom: 70px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
|
||||
@@ -309,7 +309,6 @@ $fade-out-distance: 15px;
|
||||
z-index: $z-index-alerts;
|
||||
position: absolute;
|
||||
margin-top: 53px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -89,12 +89,12 @@ const GUIComponent = props => {
|
||||
loading,
|
||||
logo,
|
||||
renderLogin,
|
||||
onClickAbout,
|
||||
onClickAccountNav,
|
||||
onCloseAccountNav,
|
||||
onLogOut,
|
||||
onOpenRegistration,
|
||||
onToggleLoginOpen,
|
||||
onUpdateProjectTitle,
|
||||
onActivateCostumesTab,
|
||||
onActivateSoundsTab,
|
||||
onActivateTab,
|
||||
@@ -106,7 +106,6 @@ const GUIComponent = props => {
|
||||
onRequestCloseTelemetryModal,
|
||||
onSeeCommunity,
|
||||
onShare,
|
||||
onShowPrivacyPolicy,
|
||||
onTelemetryModalCancel,
|
||||
onTelemetryModalOptIn,
|
||||
onTelemetryModalOptOut,
|
||||
@@ -164,7 +163,6 @@ const GUIComponent = props => {
|
||||
onOptIn={onTelemetryModalOptIn}
|
||||
onOptOut={onTelemetryModalOptOut}
|
||||
onRequestClose={onRequestCloseTelemetryModal}
|
||||
onShowPrivacyPolicy={onShowPrivacyPolicy}
|
||||
/>
|
||||
) : null}
|
||||
{loading ? (
|
||||
@@ -221,7 +219,6 @@ const GUIComponent = props => {
|
||||
logo={logo}
|
||||
renderLogin={renderLogin}
|
||||
showComingSoon={showComingSoon}
|
||||
onClickAbout={onClickAbout}
|
||||
onClickAccountNav={onClickAccountNav}
|
||||
onClickLogo={onClickLogo}
|
||||
onCloseAccountNav={onCloseAccountNav}
|
||||
@@ -231,6 +228,7 @@ const GUIComponent = props => {
|
||||
onSeeCommunity={onSeeCommunity}
|
||||
onShare={onShare}
|
||||
onToggleLoginOpen={onToggleLoginOpen}
|
||||
onUpdateProjectTitle={onUpdateProjectTitle}
|
||||
/>
|
||||
<Box className={styles.bodyWrapper}>
|
||||
<Box className={styles.flexWrapper}>
|
||||
@@ -392,7 +390,6 @@ GUIComponent.propTypes = {
|
||||
onActivateCostumesTab: PropTypes.func,
|
||||
onActivateSoundsTab: PropTypes.func,
|
||||
onActivateTab: PropTypes.func,
|
||||
onClickAbout: PropTypes.func,
|
||||
onClickAccountNav: PropTypes.func,
|
||||
onClickLogo: PropTypes.func,
|
||||
onCloseAccountNav: PropTypes.func,
|
||||
@@ -404,12 +401,12 @@ GUIComponent.propTypes = {
|
||||
onRequestCloseTelemetryModal: PropTypes.func,
|
||||
onSeeCommunity: PropTypes.func,
|
||||
onShare: PropTypes.func,
|
||||
onShowPrivacyPolicy: PropTypes.func,
|
||||
onTabSelect: PropTypes.func,
|
||||
onTelemetryModalCancel: PropTypes.func,
|
||||
onTelemetryModalOptIn: PropTypes.func,
|
||||
onTelemetryModalOptOut: PropTypes.func,
|
||||
onToggleLoginOpen: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
renderLogin: PropTypes.func,
|
||||
showComingSoon: PropTypes.bool,
|
||||
soundsTabVisible: PropTypes.bool,
|
||||
@@ -436,6 +433,7 @@ GUIComponent.defaultProps = {
|
||||
isCreating: false,
|
||||
isShared: false,
|
||||
loading: false,
|
||||
onUpdateProjectTitle: () => {},
|
||||
showComingSoon: false,
|
||||
stageSizeMode: STAGE_SIZE_MODES.large
|
||||
};
|
||||
|
||||
@@ -139,9 +139,7 @@
|
||||
.featured-description {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
line-height: 1.375rem;
|
||||
padding-top: .3125rem;
|
||||
padding-bottom: .25rem;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.featured-extension-metadata {
|
||||
|
||||
@@ -4,6 +4,7 @@ import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import PlayButton from '../../containers/play-button.jsx';
|
||||
import ScratchImage from '../scratch-image/scratch-image.jsx';
|
||||
import styles from './library-item.css';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -36,10 +37,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}>
|
||||
@@ -127,9 +130,9 @@ class LibraryItemComponent extends React.PureComponent {
|
||||
onMouseEnter={this.props.showPlayButton ? this.props.onMouseEnter : null}
|
||||
onMouseLeave={this.props.showPlayButton ? this.props.onMouseLeave : null}
|
||||
>
|
||||
<img
|
||||
<ScratchImage
|
||||
className={styles.libraryItemImage}
|
||||
src={this.props.iconURL}
|
||||
imageSource={this.props.iconSource}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -159,7 +162,7 @@ LibraryItemComponent.propTypes = {
|
||||
extensionId: PropTypes.string,
|
||||
featured: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
iconURL: PropTypes.string,
|
||||
iconSource: ScratchImage.ImageSourcePropType,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
isPlaying: PropTypes.bool,
|
||||
|
||||
@@ -10,6 +10,7 @@ import Divider from '../divider/divider.jsx';
|
||||
import Filter from '../filter/filter.jsx';
|
||||
import TagButton from '../../containers/tag-button.jsx';
|
||||
import Spinner from '../spinner/spinner.jsx';
|
||||
import storage from '../../lib/storage';
|
||||
|
||||
import styles from './library.css';
|
||||
|
||||
@@ -29,6 +30,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);
|
||||
@@ -208,8 +250,10 @@ class LibraryComponent extends React.Component {
|
||||
})}
|
||||
ref={this.setFilteredDataRef}
|
||||
>
|
||||
{this.state.loaded ? this.getFilteredData().map((dataItem, index) => (
|
||||
<LibraryItem
|
||||
{this.state.loaded ? 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}
|
||||
@@ -217,9 +261,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}
|
||||
@@ -230,8 +273,8 @@ class LibraryComponent extends React.Component {
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onSelect={this.handleSelect}
|
||||
/>
|
||||
)) : (
|
||||
/>);
|
||||
}) : (
|
||||
<div className={styles.spinnerWrapper}>
|
||||
<Spinner
|
||||
large
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M10 2c4.418 0 8 3.582 8 8s-3.582 8-8 8-8-3.582-8-8 3.582-8 8-8zm-.072 10.42c-.256 0-.472.076-.648.227-.176.151-.264.338-.264.56 0 .25.091.445.273.584.182.139.395.209.639.209.236 0 .444-.07.624-.211s.27-.335.27-.581c0-.222-.086-.409-.258-.56-.172-.151-.384-.227-.636-.227zm.144-6.42c-.624 0-1.17.113-1.638.338-.468.225-.824.51-1.068.853-.244.343-.366.683-.366 1.017 0 .162.077.313.231.452.154.139.343.209.567.209.38 0 .638-.199.774-.597.144-.38.32-.668.528-.864.208-.196.532-.293.972-.293.376 0 .683.097.921.291.238.194.357.432.357.713 0 .144-.039.278-.117.402-.078.123-.174.235-.288.336-.114.1-.299.249-.555.446-.292.225-.524.42-.696.584-.172.164-.31.354-.414.571-.104.217-.156.473-.156.769 0 .236.071.414.213.534.142.12.317.18.525.18.4 0 .638-.183.714-.55l.099-.362.093-.206.183-.227.327-.288c.492-.387.833-.663 1.023-.827.19-.164.354-.358.492-.584.138-.225.207-.488.207-.787 0-.38-.121-.733-.363-1.057-.242-.324-.585-.58-1.029-.769-.444-.188-.956-.283-1.536-.283z" id="a"/></defs><g transform="translate(-2 -2)" fill="none"><mask><use xlink:href="#a"/></mask><use fill="#fff" xlink:href="#a"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -218,9 +218,3 @@
|
||||
.mystuff > a {
|
||||
background-image: url("/images/mystuff.png");
|
||||
}
|
||||
|
||||
.about-icon {
|
||||
height: 1.25rem;
|
||||
margin: 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -69,7 +69,6 @@ import profileIcon from './icon--profile.png';
|
||||
import remixIcon from './icon--remix.svg';
|
||||
import dropdownCaret from './dropdown-caret.svg';
|
||||
import languageIcon from '../language-selector/language-icon.svg';
|
||||
import aboutIcon from './icon--about.svg';
|
||||
|
||||
import scratchLogo from './scratch-logo.svg';
|
||||
|
||||
@@ -142,19 +141,6 @@ MenuItemTooltip.propTypes = {
|
||||
isRtl: PropTypes.bool
|
||||
};
|
||||
|
||||
const AboutButton = props => (
|
||||
<Button
|
||||
className={classNames(styles.menuBarItem, styles.hoverable)}
|
||||
iconClassName={styles.aboutIcon}
|
||||
iconSrc={aboutIcon}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
);
|
||||
|
||||
AboutButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
class MenuBar extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -325,8 +311,6 @@ class MenuBar extends React.Component {
|
||||
{remixMessage}
|
||||
</Button>
|
||||
);
|
||||
// Show the About button only if we have a handler for it (like in the desktop app)
|
||||
const aboutButton = this.props.onClickAbout ? <AboutButton onClick={this.props.onClickAbout} /> : null;
|
||||
return (
|
||||
<Box
|
||||
className={classNames(
|
||||
@@ -411,6 +395,7 @@ class MenuBar extends React.Component {
|
||||
<SBFileUploader
|
||||
canSave={this.props.canSave}
|
||||
userOwnsProject={this.props.userOwnsProject}
|
||||
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
|
||||
>
|
||||
{(className, renderFileInput, handleLoadProject) => (
|
||||
<MenuItem
|
||||
@@ -510,6 +495,7 @@ class MenuBar extends React.Component {
|
||||
>
|
||||
<ProjectTitleInput
|
||||
className={classNames(styles.titleFieldGrowable)}
|
||||
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
|
||||
/>
|
||||
</MenuBarItemTooltip>
|
||||
</div>
|
||||
@@ -705,8 +691,6 @@ class MenuBar extends React.Component {
|
||||
</React.Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{aboutButton}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -740,7 +724,6 @@ MenuBar.propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
loginMenuOpen: PropTypes.bool,
|
||||
logo: PropTypes.string,
|
||||
onClickAbout: PropTypes.func,
|
||||
onClickAccount: PropTypes.func,
|
||||
onClickEdit: PropTypes.func,
|
||||
onClickFile: PropTypes.func,
|
||||
@@ -763,6 +746,7 @@ MenuBar.propTypes = {
|
||||
onSeeCommunity: PropTypes.func,
|
||||
onShare: PropTypes.func,
|
||||
onToggleLoginOpen: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
projectTitle: PropTypes.string,
|
||||
renderLogin: PropTypes.func,
|
||||
sessionExists: PropTypes.bool,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import React from 'react';
|
||||
import {defineMessages, intlShape, injectIntl} from 'react-intl';
|
||||
import {setProjectTitle} from '../../reducers/project-title';
|
||||
|
||||
import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
|
||||
import Input from '../forms/input.jsx';
|
||||
@@ -19,27 +19,39 @@ const messages = defineMessages({
|
||||
}
|
||||
});
|
||||
|
||||
const ProjectTitleInput = ({
|
||||
className,
|
||||
intl,
|
||||
onSubmit,
|
||||
projectTitle
|
||||
}) => (
|
||||
<BufferedInput
|
||||
className={classNames(styles.titleField, className)}
|
||||
maxLength="100"
|
||||
placeholder={intl.formatMessage(messages.projectTitlePlaceholder)}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value={projectTitle}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
);
|
||||
class ProjectTitleInput extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleUpdateProjectTitle'
|
||||
]);
|
||||
}
|
||||
// call onUpdateProjectTitle if it is defined (only defined when gui
|
||||
// is used within scratch-www)
|
||||
handleUpdateProjectTitle (newTitle) {
|
||||
if (this.props.onUpdateProjectTitle) {
|
||||
this.props.onUpdateProjectTitle(newTitle);
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<BufferedInput
|
||||
className={classNames(styles.titleField, this.props.className)}
|
||||
maxLength="100"
|
||||
placeholder={this.props.intl.formatMessage(messages.projectTitlePlaceholder)}
|
||||
tabIndex="0"
|
||||
type="text"
|
||||
value={this.props.projectTitle}
|
||||
onSubmit={this.handleUpdateProjectTitle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProjectTitleInput.propTypes = {
|
||||
className: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
onSubmit: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
projectTitle: PropTypes.string
|
||||
};
|
||||
|
||||
@@ -47,9 +59,7 @@ const mapStateToProps = state => ({
|
||||
projectTitle: state.scratchGui.projectTitle
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSubmit: title => dispatch(setProjectTitle(title))
|
||||
});
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default injectIntl(connect(
|
||||
mapStateToProps,
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
margin: 0 5px;
|
||||
border-radius: calc($space / 2);
|
||||
padding: 0 2px;
|
||||
white-space: pre-wrap;
|
||||
transform: translateZ(0); /* Fixes flickering in Safari */
|
||||
}
|
||||
|
||||
@@ -54,7 +53,6 @@
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
white-space: pre-wrap;
|
||||
transform: translateZ(0); /* Fixes flickering in Safari */
|
||||
}
|
||||
|
||||
@@ -151,7 +149,7 @@
|
||||
overflow: hidden; /* Don't let long values escape container */
|
||||
text-overflow: ellipsis;
|
||||
user-select: text; /* Allow selecting list values for 2.0 compatibility, only relevant in player */
|
||||
white-space: pre;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-input {
|
||||
|
||||
@@ -20,10 +20,8 @@
|
||||
border: 1px solid $ui-black-transparent;
|
||||
border-radius: 5px;
|
||||
padding: 3px;
|
||||
/* Force these to be on their own render layer because they update often */
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
}
|
||||
.meter-container {
|
||||
margin-right: 5px;
|
||||
height: 180px;
|
||||
|
||||
@@ -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;
|
||||
@@ -265,7 +265,6 @@ const SoundEditor = props => (
|
||||
onClick={props.onSlower}
|
||||
/>
|
||||
<IconButton
|
||||
disabled={props.tooLoud}
|
||||
className={classNames(styles.effectButton, styles.flipInRtl)}
|
||||
img={louderIcon}
|
||||
title={<FormattedMessage {...messages.louder} />}
|
||||
@@ -341,7 +340,6 @@ SoundEditor.propTypes = {
|
||||
onUndo: PropTypes.func.isRequired,
|
||||
playhead: PropTypes.number,
|
||||
setRef: PropTypes.func,
|
||||
tooLoud: PropTypes.bool.isRequired,
|
||||
trimEnd: PropTypes.number,
|
||||
trimStart: PropTypes.number
|
||||
};
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -101,9 +101,6 @@ class TelemetryModal extends React.PureComponent {
|
||||
privacyPolicyLink: (<a
|
||||
className={styles.privacyPolicyLink}
|
||||
href="https://scratch.mit.edu/privacy_policy/"
|
||||
onClick={this.props.onShowPrivacyPolicy}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<FormattedMessage {...messages.privacyPolicyLink} />
|
||||
</a>)
|
||||
@@ -137,8 +134,7 @@ TelemetryModal.propTypes = {
|
||||
onCancel: PropTypes.func,
|
||||
onOptIn: PropTypes.func.isRequired,
|
||||
onOptOut: PropTypes.func.isRequired,
|
||||
onRequestClose: PropTypes.func,
|
||||
onShowPrivacyPolicy: PropTypes.func
|
||||
onRequestClose: PropTypes.func
|
||||
};
|
||||
|
||||
export default injectIntl(TelemetryModal);
|
||||
@@ -340,9 +340,9 @@ class Blocks extends React.Component {
|
||||
const targetSounds = target.getSounds();
|
||||
const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML();
|
||||
return makeToolboxXML(target.isStage, target.id, dynamicBlocksXML,
|
||||
targetCostumes[targetCostumes.length - 1].name,
|
||||
stageCostumes[stageCostumes.length - 1].name,
|
||||
targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : ''
|
||||
targetCostumes[0].name,
|
||||
stageCostumes[0].name,
|
||||
targetSounds.length > 0 ? targetSounds[0].name : ''
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
|
||||
@@ -4,13 +4,14 @@ import {compose} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
import ReactModal from 'react-modal';
|
||||
import VM from 'scratch-vm';
|
||||
import {injectIntl, intlShape} from 'react-intl';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
|
||||
import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
|
||||
import {
|
||||
getIsError,
|
||||
getIsShowingProject
|
||||
} from '../reducers/project-state';
|
||||
import {setProjectTitle} from '../reducers/project-title';
|
||||
import {
|
||||
activateTab,
|
||||
BLOCKS_TAB_INDEX,
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
|
||||
import LocalizationHOC from '../lib/localization-hoc.jsx';
|
||||
import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
|
||||
import TitledHOC from '../lib/titled-hoc.jsx';
|
||||
import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
|
||||
import QueryParserHOC from '../lib/query-parser-hoc.jsx';
|
||||
import storage from '../lib/storage';
|
||||
@@ -39,9 +39,18 @@ import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx';
|
||||
import GUIComponent from '../components/gui/gui.jsx';
|
||||
import {setIsScratchDesktop} from '../lib/isScratchDesktop.js';
|
||||
|
||||
const messages = defineMessages({
|
||||
defaultProjectTitle: {
|
||||
id: 'gui.gui.defaultProjectTitle',
|
||||
description: 'Default title for project',
|
||||
defaultMessage: 'Scratch Project'
|
||||
}
|
||||
});
|
||||
|
||||
class GUI extends React.Component {
|
||||
componentDidMount () {
|
||||
setIsScratchDesktop(this.props.isScratchDesktop);
|
||||
this.setReduxTitle(this.props.projectTitle);
|
||||
this.props.onStorageInit(storage);
|
||||
this.props.onVmInit(this.props.vm);
|
||||
}
|
||||
@@ -49,12 +58,24 @@ class GUI extends React.Component {
|
||||
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
|
||||
this.props.onUpdateProjectId(this.props.projectId);
|
||||
}
|
||||
if (this.props.projectTitle !== prevProps.projectTitle) {
|
||||
this.setReduxTitle(this.props.projectTitle);
|
||||
}
|
||||
if (this.props.isShowingProject && !prevProps.isShowingProject) {
|
||||
// this only notifies container when a project changes from not yet loaded to loaded
|
||||
// At this time the project view in www doesn't need to know when a project is unloaded
|
||||
this.props.onProjectLoaded();
|
||||
}
|
||||
}
|
||||
setReduxTitle (newTitle) {
|
||||
if (newTitle === null || typeof newTitle === 'undefined') {
|
||||
this.props.onUpdateReduxProjectTitle(
|
||||
this.props.intl.formatMessage(messages.defaultProjectTitle)
|
||||
);
|
||||
} else {
|
||||
this.props.onUpdateReduxProjectTitle(newTitle);
|
||||
}
|
||||
}
|
||||
render () {
|
||||
if (this.props.isError) {
|
||||
throw new Error(
|
||||
@@ -71,9 +92,11 @@ class GUI extends React.Component {
|
||||
onProjectLoaded,
|
||||
onStorageInit,
|
||||
onUpdateProjectId,
|
||||
onUpdateReduxProjectTitle,
|
||||
onVmInit,
|
||||
projectHost,
|
||||
projectId,
|
||||
projectTitle,
|
||||
/* eslint-enable no-unused-vars */
|
||||
children,
|
||||
fetchingProject,
|
||||
@@ -108,9 +131,12 @@ GUI.propTypes = {
|
||||
onSeeCommunity: PropTypes.func,
|
||||
onStorageInit: PropTypes.func,
|
||||
onUpdateProjectId: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
onUpdateReduxProjectTitle: PropTypes.func,
|
||||
onVmInit: PropTypes.func,
|
||||
projectHost: PropTypes.string,
|
||||
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
projectTitle: PropTypes.string,
|
||||
telemetryModalVisible: PropTypes.bool,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
@@ -160,7 +186,8 @@ const mapDispatchToProps = dispatch => ({
|
||||
onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)),
|
||||
onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()),
|
||||
onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()),
|
||||
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal())
|
||||
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()),
|
||||
onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
|
||||
});
|
||||
|
||||
const ConnectedGUI = injectIntl(connect(
|
||||
@@ -177,7 +204,6 @@ const WrappedGui = compose(
|
||||
FontLoaderHOC,
|
||||
QueryParserHOC,
|
||||
ProjectFetcherHOC,
|
||||
TitledHOC,
|
||||
ProjectSaverHOC,
|
||||
vmListenerHOC,
|
||||
vmManagerHOC,
|
||||
|
||||
@@ -91,23 +91,17 @@ class LibraryItem extends React.PureComponent {
|
||||
const nextIconIndex = (this.state.iconIndex + 1) % this.props.icons.length;
|
||||
this.setState({iconIndex: nextIconIndex});
|
||||
}
|
||||
curIconMd5 () {
|
||||
const iconMd5Prop = this.props.iconMd5;
|
||||
curIconSource () {
|
||||
if (this.props.icons &&
|
||||
this.state.isRotatingIcon &&
|
||||
this.state.iconIndex < this.props.icons.length) {
|
||||
const icon = this.props.icons[this.state.iconIndex] || {};
|
||||
return icon.md5ext || // 3.0 library format
|
||||
icon.baseLayerMD5 || // 2.0 library format, TODO GH-5084
|
||||
iconMd5Prop;
|
||||
this.state.iconIndex < this.props.icons.length &&
|
||||
this.props.icons[this.state.iconIndex]) {
|
||||
return this.props.icons[this.state.iconIndex];
|
||||
}
|
||||
return iconMd5Prop;
|
||||
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}
|
||||
@@ -117,8 +111,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}
|
||||
@@ -149,14 +142,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, // 2.0 library format, TODO GH-5084
|
||||
md5ext: PropTypes.string // 3.0 library format
|
||||
})
|
||||
),
|
||||
iconSource: LibraryItemComponent.propTypes.iconSource, // single icon
|
||||
icons: PropTypes.arrayOf(LibraryItemComponent.propTypes.iconSource), // rotating icons
|
||||
id: PropTypes.number.isRequired,
|
||||
insetIconURL: PropTypes.string,
|
||||
internetConnectionRequired: PropTypes.bool,
|
||||
|
||||
@@ -44,10 +44,10 @@ class RecordingStep extends React.Component {
|
||||
alert(this.props.intl.formatMessage(messages.alertMsg)); // eslint-disable-line no-alert
|
||||
}
|
||||
handleLevelUpdate (level) {
|
||||
this.setState({
|
||||
level: level,
|
||||
levels: this.props.recording ? (this.state.levels || []).concat([level]) : this.state.levels
|
||||
});
|
||||
this.setState({level});
|
||||
if (this.props.recording) {
|
||||
this.setState({levels: (this.state.levels || []).concat([level])});
|
||||
}
|
||||
}
|
||||
handleRecord () {
|
||||
this.audioRecorder.startRecording();
|
||||
|
||||
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {defineMessages, injectIntl, intlShape} from 'react-intl';
|
||||
import {setProjectTitle} from '../reducers/project-title';
|
||||
|
||||
import log from '../lib/log';
|
||||
import sharedMessages from '../lib/shared-messages';
|
||||
@@ -132,7 +131,7 @@ class SBFileUploader extends React.Component {
|
||||
// This is necessary in case the user wants to reload a project
|
||||
if (filename) {
|
||||
const uploadedProjectTitle = this.getProjectTitleFromFilename(filename);
|
||||
this.props.onReceivedProjectTitle(uploadedProjectTitle);
|
||||
this.props.onUpdateProjectTitle(uploadedProjectTitle);
|
||||
}
|
||||
this.resetFileInput();
|
||||
})
|
||||
@@ -180,9 +179,9 @@ SBFileUploader.propTypes = {
|
||||
loadingState: PropTypes.oneOf(LoadingStates),
|
||||
onLoadingFinished: PropTypes.func,
|
||||
onLoadingStarted: PropTypes.func,
|
||||
onUpdateProjectTitle: PropTypes.func,
|
||||
projectChanged: PropTypes.bool,
|
||||
requestProjectUpload: PropTypes.func,
|
||||
onReceivedProjectTitle: PropTypes.func,
|
||||
userOwnsProject: PropTypes.bool,
|
||||
vm: PropTypes.shape({
|
||||
loadProject: PropTypes.func
|
||||
@@ -210,8 +209,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
dispatch(closeFileMenu());
|
||||
},
|
||||
requestProjectUpload: loadingState => dispatch(requestProjectUpload(loadingState)),
|
||||
onLoadingStarted: () => dispatch(openLoadingProject()),
|
||||
onReceivedProjectTitle: title => dispatch(setProjectTitle(title))
|
||||
onLoadingStarted: () => dispatch(openLoadingProject())
|
||||
});
|
||||
|
||||
// Allow incoming props to override redux-provided props. Used to mock in tests.
|
||||
|
||||
@@ -6,12 +6,7 @@ import VM from 'scratch-vm';
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {
|
||||
computeChunkedRMS,
|
||||
encodeAndAddSoundToVM,
|
||||
downsampleIfNeeded,
|
||||
dropEveryOtherSample
|
||||
} from '../lib/audio/audio-util.js';
|
||||
import {computeChunkedRMS, encodeAndAddSoundToVM, SOUND_BYTE_LIMIT} from '../lib/audio/audio-util.js';
|
||||
import AudioEffects from '../lib/audio/audio-effects.js';
|
||||
import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx';
|
||||
import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js';
|
||||
@@ -19,8 +14,6 @@ import log from '../lib/log.js';
|
||||
|
||||
const UNDO_STACK_SIZE = 99;
|
||||
|
||||
const MAX_RMS = 1.2;
|
||||
|
||||
class SoundEditor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -44,8 +37,7 @@ class SoundEditor extends React.Component {
|
||||
'paste',
|
||||
'handleKeyPress',
|
||||
'handleContainerClick',
|
||||
'setRef',
|
||||
'resampleBufferToRate'
|
||||
'setRef'
|
||||
]);
|
||||
this.state = {
|
||||
copyBuffer: null,
|
||||
@@ -138,32 +130,45 @@ class SoundEditor extends React.Component {
|
||||
});
|
||||
}
|
||||
submitNewSamples (samples, sampleRate, skipUndo) {
|
||||
return downsampleIfNeeded({samples, sampleRate}, this.resampleBufferToRate)
|
||||
.then(({samples: newSamples, sampleRate: newSampleRate}) =>
|
||||
WavEncoder.encode({
|
||||
sampleRate: newSampleRate,
|
||||
channelData: [newSamples]
|
||||
}).then(wavBuffer => {
|
||||
if (!skipUndo) {
|
||||
this.redoStack = [];
|
||||
if (this.undoStack.length >= UNDO_STACK_SIZE) {
|
||||
this.undoStack.shift(); // Drop the first element off the array
|
||||
}
|
||||
this.undoStack.push(this.getUndoItem());
|
||||
}
|
||||
this.resetState(newSamples, newSampleRate);
|
||||
this.props.vm.updateSoundBuffer(
|
||||
this.props.soundIndex,
|
||||
this.audioBufferPlayer.buffer,
|
||||
new Uint8Array(wavBuffer));
|
||||
return true; // Edit was successful
|
||||
})
|
||||
)
|
||||
.catch(e => {
|
||||
// Encoding failed, or the sound was too large to save so edit is rejected
|
||||
log.error(`Encountered error while trying to encode sound update: ${e}`);
|
||||
return false; // Edit was not applied
|
||||
// Encode the new sound into a wav so that it can be stored
|
||||
let wavBuffer = null;
|
||||
try {
|
||||
wavBuffer = WavEncoder.encode.sync({
|
||||
sampleRate: sampleRate,
|
||||
channelData: [samples]
|
||||
});
|
||||
|
||||
if (wavBuffer.byteLength > SOUND_BYTE_LIMIT) {
|
||||
// Cancel the sound update by setting to null
|
||||
wavBuffer = null;
|
||||
log.error(`Refusing to encode sound larger than ${SOUND_BYTE_LIMIT} bytes`);
|
||||
}
|
||||
} catch (e) {
|
||||
// This error state is mostly for the mock sounds used during testing.
|
||||
// Any incorrect sound buffer trying to get interpretd as a Wav file
|
||||
// should yield this error.
|
||||
// This can also happen if the sound is too be allocated in memory.
|
||||
log.error(`Encountered error while trying to encode sound update: ${e}`);
|
||||
}
|
||||
|
||||
// Do not submit sound if it could not be encoded (i.e. if too large)
|
||||
if (wavBuffer) {
|
||||
if (!skipUndo) {
|
||||
this.redoStack = [];
|
||||
if (this.undoStack.length >= UNDO_STACK_SIZE) {
|
||||
this.undoStack.shift(); // Drop the first element off the array
|
||||
}
|
||||
this.undoStack.push(this.getUndoItem());
|
||||
}
|
||||
this.resetState(samples, sampleRate);
|
||||
this.props.vm.updateSoundBuffer(
|
||||
this.props.soundIndex,
|
||||
this.audioBufferPlayer.buffer,
|
||||
new Uint8Array(wavBuffer));
|
||||
|
||||
return true; // Update succeeded
|
||||
}
|
||||
return false; // Update failed
|
||||
}
|
||||
handlePlay () {
|
||||
this.audioBufferPlayer.stop();
|
||||
@@ -202,15 +207,13 @@ class SoundEditor extends React.Component {
|
||||
newSamples.set(firstPart, 0);
|
||||
newSamples.set(secondPart, firstPart.length);
|
||||
}
|
||||
this.submitNewSamples(newSamples, sampleRate).then(() => {
|
||||
this.setState({
|
||||
trimStart: null,
|
||||
trimEnd: null
|
||||
});
|
||||
this.submitNewSamples(newSamples, sampleRate);
|
||||
this.setState({
|
||||
trimStart: null,
|
||||
trimEnd: null
|
||||
});
|
||||
}
|
||||
handleDeleteInverse () {
|
||||
// Delete everything outside of the trimmers
|
||||
const {samples, sampleRate} = this.copyCurrentBuffer();
|
||||
const sampleCount = samples.length;
|
||||
const startIndex = Math.floor(this.state.trimStart * sampleCount);
|
||||
@@ -219,13 +222,10 @@ class SoundEditor extends React.Component {
|
||||
if (clippedSamples.length === 0) {
|
||||
clippedSamples = new Float32Array(1);
|
||||
}
|
||||
this.submitNewSamples(clippedSamples, sampleRate).then(success => {
|
||||
if (success) {
|
||||
this.setState({
|
||||
trimStart: null,
|
||||
trimEnd: null
|
||||
});
|
||||
}
|
||||
this.submitNewSamples(clippedSamples, sampleRate);
|
||||
this.setState({
|
||||
trimStart: null,
|
||||
trimEnd: null
|
||||
});
|
||||
}
|
||||
handleUpdateTrim (trimStart, trimEnd) {
|
||||
@@ -255,26 +255,16 @@ class SoundEditor extends React.Component {
|
||||
effects.process((renderedBuffer, adjustedTrimStart, adjustedTrimEnd) => {
|
||||
const samples = renderedBuffer.getChannelData(0);
|
||||
const sampleRate = renderedBuffer.sampleRate;
|
||||
this.submitNewSamples(samples, sampleRate).then(success => {
|
||||
if (success) {
|
||||
if (this.state.trimStart === null) {
|
||||
this.handlePlay();
|
||||
} else {
|
||||
this.setState({trimStart: adjustedTrimStart, trimEnd: adjustedTrimEnd}, this.handlePlay);
|
||||
}
|
||||
const success = this.submitNewSamples(samples, sampleRate);
|
||||
if (success) {
|
||||
if (this.state.trimStart === null) {
|
||||
this.handlePlay();
|
||||
} else {
|
||||
this.setState({trimStart: adjustedTrimStart, trimEnd: adjustedTrimEnd}, this.handlePlay);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
tooLoud () {
|
||||
const numChunks = this.state.chunkLevels.length;
|
||||
const startIndex = this.state.trimStart === null ?
|
||||
0 : Math.floor(this.state.trimStart * numChunks);
|
||||
const endIndex = this.state.trimEnd === null ?
|
||||
numChunks - 1 : Math.ceil(this.state.trimEnd * numChunks);
|
||||
const trimChunks = this.state.chunkLevels.slice(startIndex, endIndex);
|
||||
return Math.max(...trimChunks) > MAX_RMS;
|
||||
}
|
||||
getUndoItem () {
|
||||
return {
|
||||
...this.copyCurrentBuffer(),
|
||||
@@ -286,22 +276,16 @@ class SoundEditor extends React.Component {
|
||||
this.redoStack.push(this.getUndoItem());
|
||||
const {samples, sampleRate, trimStart, trimEnd} = this.undoStack.pop();
|
||||
if (samples) {
|
||||
return this.submitNewSamples(samples, sampleRate, true).then(success => {
|
||||
if (success) {
|
||||
this.setState({trimStart: trimStart, trimEnd: trimEnd}, this.handlePlay);
|
||||
}
|
||||
});
|
||||
this.submitNewSamples(samples, sampleRate, true);
|
||||
this.setState({trimStart: trimStart, trimEnd: trimEnd}, this.handlePlay);
|
||||
}
|
||||
}
|
||||
handleRedo () {
|
||||
const {samples, sampleRate, trimStart, trimEnd} = this.redoStack.pop();
|
||||
if (samples) {
|
||||
this.undoStack.push(this.getUndoItem());
|
||||
return this.submitNewSamples(samples, sampleRate, true).then(success => {
|
||||
if (success) {
|
||||
this.setState({trimStart: trimStart, trimEnd: trimEnd}, this.handlePlay);
|
||||
}
|
||||
});
|
||||
this.submitNewSamples(samples, sampleRate, true);
|
||||
this.setState({trimStart: trimStart, trimEnd: trimEnd}, this.handlePlay);
|
||||
}
|
||||
}
|
||||
handleCopy () {
|
||||
@@ -327,39 +311,25 @@ class SoundEditor extends React.Component {
|
||||
});
|
||||
}
|
||||
resampleBufferToRate (buffer, newRate) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sampleRateRatio = newRate / buffer.sampleRate;
|
||||
const newLength = sampleRateRatio * buffer.samples.length;
|
||||
let offlineContext;
|
||||
// Try to use either OfflineAudioContext or webkitOfflineAudioContext to resample
|
||||
// The constructors will throw if trying to resample at an unsupported rate
|
||||
// (e.g. Safari/webkitOAC does not support lower than 44khz).
|
||||
try {
|
||||
if (window.OfflineAudioContext) {
|
||||
offlineContext = new window.OfflineAudioContext(1, newLength, newRate);
|
||||
} else if (window.webkitOfflineAudioContext) {
|
||||
offlineContext = new window.webkitOfflineAudioContext(1, newLength, newRate);
|
||||
}
|
||||
} catch {
|
||||
// If no OAC available and downsampling by 2, downsample by dropping every other sample.
|
||||
if (newRate === buffer.sampleRate / 2) {
|
||||
return resolve(dropEveryOtherSample(buffer));
|
||||
}
|
||||
return reject('Could not resample');
|
||||
return new Promise(resolve => {
|
||||
if (window.OfflineAudioContext) {
|
||||
const sampleRateRatio = newRate / buffer.sampleRate;
|
||||
const newLength = sampleRateRatio * buffer.samples.length;
|
||||
const offlineContext = new window.OfflineAudioContext(1, newLength, newRate);
|
||||
const source = offlineContext.createBufferSource();
|
||||
const audioBuffer = offlineContext.createBuffer(1, buffer.samples.length, buffer.sampleRate);
|
||||
audioBuffer.getChannelData(0).set(buffer.samples);
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(offlineContext.destination);
|
||||
source.start();
|
||||
offlineContext.startRendering();
|
||||
offlineContext.oncomplete = ({renderedBuffer}) => {
|
||||
resolve({
|
||||
samples: renderedBuffer.getChannelData(0),
|
||||
sampleRate: newRate
|
||||
});
|
||||
};
|
||||
}
|
||||
const source = offlineContext.createBufferSource();
|
||||
const audioBuffer = offlineContext.createBuffer(1, buffer.samples.length, buffer.sampleRate);
|
||||
audioBuffer.getChannelData(0).set(buffer.samples);
|
||||
source.buffer = audioBuffer;
|
||||
source.connect(offlineContext.destination);
|
||||
source.start();
|
||||
offlineContext.startRendering();
|
||||
offlineContext.oncomplete = ({renderedBuffer}) => {
|
||||
resolve({
|
||||
samples: renderedBuffer.getChannelData(0),
|
||||
sampleRate: newRate
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
paste () {
|
||||
@@ -370,11 +340,8 @@ class SoundEditor extends React.Component {
|
||||
const newSamples = new Float32Array(newLength);
|
||||
newSamples.set(samples, 0);
|
||||
newSamples.set(this.state.copyBuffer.samples, samples.length);
|
||||
this.submitNewSamples(newSamples, this.props.sampleRate, false).then(success => {
|
||||
if (success) {
|
||||
this.handlePlay();
|
||||
}
|
||||
});
|
||||
this.submitNewSamples(newSamples, this.props.sampleRate, false);
|
||||
this.handlePlay();
|
||||
} else {
|
||||
// else replace the selection with the pasted sound
|
||||
const trimStartSamples = this.state.trimStart * samples.length;
|
||||
@@ -393,14 +360,11 @@ class SoundEditor extends React.Component {
|
||||
const newDurationSeconds = newSamples.length / this.state.copyBuffer.sampleRate;
|
||||
const adjustedTrimStart = trimStartSeconds / newDurationSeconds;
|
||||
const adjustedTrimEnd = trimEndSeconds / newDurationSeconds;
|
||||
this.submitNewSamples(newSamples, this.props.sampleRate, false).then(success => {
|
||||
if (success) {
|
||||
this.setState({
|
||||
trimStart: adjustedTrimStart,
|
||||
trimEnd: adjustedTrimEnd
|
||||
}, this.handlePlay);
|
||||
}
|
||||
});
|
||||
this.submitNewSamples(newSamples, this.props.sampleRate, false);
|
||||
this.setState({
|
||||
trimStart: adjustedTrimStart,
|
||||
trimEnd: adjustedTrimEnd
|
||||
}, this.handlePlay);
|
||||
}
|
||||
}
|
||||
handlePaste () {
|
||||
@@ -435,7 +399,6 @@ class SoundEditor extends React.Component {
|
||||
name={this.props.name}
|
||||
playhead={this.state.playhead}
|
||||
setRef={this.setRef}
|
||||
tooLoud={this.tooLoud()}
|
||||
trimEnd={this.state.trimEnd}
|
||||
trimStart={this.state.trimStart}
|
||||
onChangeName={this.handleChangeName}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import GUI from './containers/gui.jsx';
|
||||
import AppStateHOC from './lib/app-state-hoc.jsx';
|
||||
import TitledHOC from './lib/titled-hoc.jsx';
|
||||
import GuiReducer, {guiInitialState, guiMiddleware, initEmbedded, initFullScreen, initPlayer} from './reducers/gui';
|
||||
import LocalesReducer, {localesInitialState, initLocale} from './reducers/locales';
|
||||
import {ScratchPaintReducer} from 'scratch-paint';
|
||||
@@ -16,6 +17,7 @@ const guiReducers = {
|
||||
export {
|
||||
GUI as default,
|
||||
AppStateHOC,
|
||||
TitledHOC,
|
||||
setAppElement,
|
||||
guiReducers,
|
||||
guiInitialState,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'get-float-time-domain-data';
|
||||
import getUserMedia from 'get-user-media-promise';
|
||||
import SharedAudioContext from './shared-audio-context.js';
|
||||
import {computeRMS, computeChunkedRMS} from './audio-util.js';
|
||||
import {computeRMS} from './audio-util.js';
|
||||
|
||||
class AudioRecorder {
|
||||
constructor () {
|
||||
this.audioContext = new SharedAudioContext();
|
||||
this.bufferLength = 8192;
|
||||
this.bufferLength = 1024;
|
||||
|
||||
this.userMediaStream = null;
|
||||
this.mediaStreamSource = null;
|
||||
@@ -51,7 +51,7 @@ class AudioRecorder {
|
||||
this.userMediaStream = userMediaStream;
|
||||
this.mediaStreamSource = this.audioContext.createMediaStreamSource(userMediaStream);
|
||||
this.sourceNode = this.audioContext.createGain();
|
||||
this.scriptProcessorNode = this.audioContext.createScriptProcessor(this.bufferLength, 1, 1);
|
||||
this.scriptProcessorNode = this.audioContext.createScriptProcessor(this.bufferLength, 2, 2);
|
||||
|
||||
this.scriptProcessorNode.onaudioprocess = processEvent => {
|
||||
if (this.recording && !this.disposed) {
|
||||
@@ -68,9 +68,9 @@ class AudioRecorder {
|
||||
|
||||
const update = () => {
|
||||
if (this.disposed) return;
|
||||
requestAnimationFrame(update);
|
||||
this.analyserNode.getFloatTimeDomainData(dataArray);
|
||||
onUpdate(computeRMS(dataArray));
|
||||
requestAnimationFrame(update);
|
||||
};
|
||||
|
||||
requestAnimationFrame(update);
|
||||
@@ -83,16 +83,7 @@ class AudioRecorder {
|
||||
}
|
||||
|
||||
stop () {
|
||||
const buffer = new Float32Array(this.buffers.length * this.bufferLength);
|
||||
|
||||
let offset = 0;
|
||||
for (let i = 0; i < this.buffers.length; i++) {
|
||||
const bufferChunk = this.buffers[i];
|
||||
buffer.set(bufferChunk, offset);
|
||||
offset += bufferChunk.length;
|
||||
}
|
||||
|
||||
const chunkLevels = computeChunkedRMS(buffer);
|
||||
const chunkLevels = this.buffers.map(buffer => computeRMS(buffer));
|
||||
const maxRMS = Math.max.apply(null, chunkLevels);
|
||||
const threshold = maxRMS / 8;
|
||||
|
||||
@@ -114,6 +105,15 @@ class AudioRecorder {
|
||||
trimEnd = 1;
|
||||
}
|
||||
|
||||
const buffer = new Float32Array(this.buffers.length * this.bufferLength);
|
||||
|
||||
let offset = 0;
|
||||
for (let i = 0; i < this.buffers.length; i++) {
|
||||
const bufferChunk = this.buffers[i];
|
||||
buffer.set(bufferChunk, offset);
|
||||
offset += bufferChunk.length;
|
||||
}
|
||||
|
||||
return {
|
||||
levels: chunkLevels,
|
||||
samples: buffer,
|
||||
|
||||
@@ -59,57 +59,9 @@ const encodeAndAddSoundToVM = function (vm, samples, sampleRate, name, callback)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@typedef SoundBuffer
|
||||
@type {Object}
|
||||
@property {Float32Array} samples Array of audio samples
|
||||
@property {number} sampleRate Audio sample rate
|
||||
*/
|
||||
|
||||
/**
|
||||
* Downsample the given buffer to try to reduce file size below SOUND_BYTE_LIMIT
|
||||
* @param {SoundBuffer} buffer - Buffer to resample
|
||||
* @param {function(SoundBuffer):Promise<SoundBuffer>} resampler - resampler function
|
||||
* @returns {SoundBuffer} Downsampled buffer with half the sample rate
|
||||
*/
|
||||
const downsampleIfNeeded = (buffer, resampler) => {
|
||||
const {samples, sampleRate} = buffer;
|
||||
const duration = samples.length / sampleRate;
|
||||
const encodedByteLength = samples.length * 2; /* bitDepth 16 bit */
|
||||
// Resolve immediately if already within byte limit
|
||||
if (encodedByteLength < SOUND_BYTE_LIMIT) {
|
||||
return Promise.resolve({samples, sampleRate});
|
||||
}
|
||||
// If encodeable at 22khz, resample and call submitNewSamples again
|
||||
if (duration * 22050 * 2 < SOUND_BYTE_LIMIT) {
|
||||
return resampler({samples, sampleRate}, 22050);
|
||||
}
|
||||
// Cannot save this sound at 22khz, refuse to edit
|
||||
// In the future we could introduce further compression here
|
||||
return Promise.reject('Sound too large to save, refusing to edit');
|
||||
};
|
||||
|
||||
/**
|
||||
* Drop every other sample of an audio buffer as a last-resort way of downsampling.
|
||||
* @param {SoundBuffer} buffer - Buffer to resample
|
||||
* @returns {SoundBuffer} Downsampled buffer with half the sample rate
|
||||
*/
|
||||
const dropEveryOtherSample = buffer => {
|
||||
const newLength = Math.floor(buffer.samples.length / 2);
|
||||
const newSamples = new Float32Array(newLength);
|
||||
for (let i = 0; i < newLength; i++) {
|
||||
newSamples[i] = buffer.samples[i * 2];
|
||||
}
|
||||
return {
|
||||
samples: newSamples,
|
||||
sampleRate: buffer.sampleRate / 2
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
computeRMS,
|
||||
computeChunkedRMS,
|
||||
encodeAndAddSoundToVM,
|
||||
downsampleIfNeeded,
|
||||
dropEveryOtherSample
|
||||
SOUND_BYTE_LIMIT
|
||||
};
|
||||
@@ -3,20 +3,15 @@ const jpegThumbnail = dataUrl => new Promise((resolve, reject) => {
|
||||
image.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const maxDimension = 96; // 3x the maximum displayed size of 32px
|
||||
|
||||
if (image.height > image.width) {
|
||||
canvas.height = maxDimension;
|
||||
canvas.width = (maxDimension / image.height) * image.width;
|
||||
} else {
|
||||
canvas.width = maxDimension;
|
||||
canvas.height = (maxDimension / image.width) * image.height;
|
||||
}
|
||||
// TODO we may want to draw to a smaller, fixed size to optimize file size
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
|
||||
ctx.fillStyle = 'white'; // Create white background, since jpeg doesn't have transparency
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
ctx.drawImage(image, 0, 0);
|
||||
// TODO we can play with the `quality` option here to optimize file size
|
||||
resolve(canvas.toDataURL('image/jpeg', 0.92 /* quality */)); // Default quality is 0.92
|
||||
};
|
||||
image.onerror = err => {
|
||||
|
||||
@@ -54,10 +54,13 @@ const cloudManagerHOC = function (WrappedComponent) {
|
||||
canUseCloud (props) {
|
||||
return !!(props.cloudHost && props.username && props.vm && props.projectId && props.hasCloudPermission);
|
||||
}
|
||||
shouldNotModifyCloudData (props) {
|
||||
return (props.hasEverEnteredEditor && !props.canSave);
|
||||
}
|
||||
shouldConnect (props) {
|
||||
return !this.isConnected() && this.canUseCloud(props) &&
|
||||
props.isShowingWithId && props.vm.runtime.hasCloudData() &&
|
||||
props.canModifyCloudData;
|
||||
!this.shouldNotModifyCloudData(props);
|
||||
}
|
||||
shouldDisconnect (props, prevProps) {
|
||||
return this.isConnected() &&
|
||||
@@ -67,7 +70,7 @@ const cloudManagerHOC = function (WrappedComponent) {
|
||||
(props.projectId !== prevProps.projectId) ||
|
||||
(props.username !== prevProps.username) ||
|
||||
// Editing someone else's project
|
||||
!props.canModifyCloudData
|
||||
this.shouldNotModifyCloudData(props)
|
||||
);
|
||||
}
|
||||
isConnected () {
|
||||
@@ -99,11 +102,11 @@ const cloudManagerHOC = function (WrappedComponent) {
|
||||
render () {
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
canModifyCloudData,
|
||||
cloudHost,
|
||||
projectId,
|
||||
username,
|
||||
hasCloudPermission,
|
||||
hasEverEnteredEditor,
|
||||
isShowingWithId,
|
||||
onShowCloudInfo,
|
||||
/* eslint-enable no-unused-vars */
|
||||
@@ -121,30 +124,23 @@ const cloudManagerHOC = function (WrappedComponent) {
|
||||
}
|
||||
|
||||
CloudManager.propTypes = {
|
||||
canModifyCloudData: PropTypes.bool.isRequired,
|
||||
canSave: PropTypes.bool.isRequired,
|
||||
cloudHost: PropTypes.string,
|
||||
hasCloudPermission: PropTypes.bool,
|
||||
isShowingWithId: PropTypes.bool.isRequired,
|
||||
hasEverEnteredEditor: PropTypes.bool,
|
||||
isShowingWithId: PropTypes.bool,
|
||||
onShowCloudInfo: PropTypes.func,
|
||||
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
username: PropTypes.string,
|
||||
vm: PropTypes.instanceOf(VM).isRequired
|
||||
};
|
||||
|
||||
CloudManager.defaultProps = {
|
||||
cloudHost: null,
|
||||
hasCloudPermission: false,
|
||||
onShowCloudInfo: () => {},
|
||||
username: null
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const mapStateToProps = state => {
|
||||
const loadingState = state.scratchGui.projectState.loadingState;
|
||||
return {
|
||||
hasEverEnteredEditor: state.scratchGui.mode.hasEverEnteredEditor,
|
||||
isShowingWithId: getIsShowingWithId(loadingState),
|
||||
projectId: state.scratchGui.projectState.projectId,
|
||||
// if you're editing someone else's project, you can't modify cloud data
|
||||
canModifyCloudData: (!state.scratchGui.mode.hasEverEnteredEditor || ownProps.canSave)
|
||||
projectId: state.scratchGui.projectState.projectId
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -8,23 +8,11 @@ export default (filename, blob) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('download' in HTMLAnchorElement.prototype) {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
downloadLink.href = url;
|
||||
downloadLink.download = filename;
|
||||
downloadLink.type = blob.type;
|
||||
downloadLink.click();
|
||||
document.body.removeChild(downloadLink);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} else {
|
||||
// iOS Safari, open a new page and set href to data-uri
|
||||
let popup = window.open('', '_blank');
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = function () {
|
||||
popup.location.href = reader.result;
|
||||
popup = null;
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
downloadLink.href = url;
|
||||
downloadLink.download = filename;
|
||||
downloadLink.type = blob.type;
|
||||
downloadLink.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(downloadLink);
|
||||
};
|
||||
@@ -78,7 +78,7 @@ const DropAreaHOC = function (dragTypes) {
|
||||
}
|
||||
}
|
||||
render () {
|
||||
const componentProps = omit(this.props, ['onDrop', 'dragInfo', 'componentRef']);
|
||||
const componentProps = omit(this.props, ['onDrop', 'dragInfo']);
|
||||
return (
|
||||
<WrappedComponent
|
||||
containerRef={this.setRef}
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Basketball 1",
|
||||
"md5": "ae21eac3d1814aee1d37ae82ea287816.svg",
|
||||
"md5": "e494c4f44897d94e0541f7036a302449.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"sports",
|
||||
@@ -178,7 +178,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Blue Sky 2 ",
|
||||
"md5": "8eb8790be5507fdccf73e7c1570bbbab.svg",
|
||||
"md5": "5a906c2f272b77f59f0ef207857a8b3a.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"outdoors",
|
||||
@@ -286,7 +286,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Circles",
|
||||
"md5": "c9847be305920807c5597d81576dd0c4.svg",
|
||||
"md5": "4e29033ec2b891a8f1ca21242811d403.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"patterns"
|
||||
@@ -491,7 +491,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Hearts",
|
||||
"md5": "f98526ccb0eec3ac7d6c8f8ab502825e.svg",
|
||||
"md5": "26d3418b2fbc1af2c5fea82e1c3df1db.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"patterns"
|
||||
@@ -1050,7 +1050,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Underwater 1",
|
||||
"md5": "d3344650f594bcecdf46aa4a9441badd.svg",
|
||||
"md5": "fb907f72b310acc8b95cbf2d2cccabc9.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"ocean",
|
||||
@@ -1175,7 +1175,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Witch House",
|
||||
"md5": "30085b2d27beb5acdbe895d8b3e64b04.svg",
|
||||
"md5": "597b9a9813fe5d8d387283138a0b8f2b.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"fantasy",
|
||||
@@ -1190,7 +1190,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Woods",
|
||||
"md5": "f3eb165d6f3fd23370f97079f2e631bf.svg",
|
||||
"md5": "d26cfd278999c9e37d6af75c296a58df.svg",
|
||||
"type": "backdrop",
|
||||
"tags": [
|
||||
"fantasy",
|
||||
|
||||
@@ -3,7 +3,7 @@ import introMove from './steps/intro-move.en.gif';
|
||||
import introSayHello from './steps/intro-say-hello.en.gif';
|
||||
|
||||
// Text to Speech
|
||||
import speechAddExtension from './steps/talking-1-add-extension.en.gif';
|
||||
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';
|
||||
@@ -15,12 +15,12 @@ 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.gif';
|
||||
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.gif';
|
||||
import cnPickSprite from './steps/cn-pick-sprite.en.gif';
|
||||
import cnCollect from './steps/cn-collect.en.gif';
|
||||
import cnVariable from './steps/add-variable.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';
|
||||
|
||||
@@ -43,20 +43,20 @@ 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.gif';
|
||||
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/add-variable.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/add-variable.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';
|
||||
@@ -73,22 +73,22 @@ 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.gif';
|
||||
import storyPickSprite from './steps/story-pick-sprite.gif';
|
||||
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.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.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.png';
|
||||
import videoAnimate from './steps/video-animate.en.png';
|
||||
import videoPop from './steps/video-pop.en.png';
|
||||
import videoPet from './steps/video-pet.en.gif';
|
||||
import videoAnimate from './steps/video-animate.en.gif';
|
||||
import videoPop from './steps/video-pop.en.gif';
|
||||
|
||||
// Make it Fly
|
||||
import flyChooseBackdrop from './steps/fly-choose-backdrop.en.gif';
|
||||
@@ -98,7 +98,7 @@ import flyMoveArrows from './steps/fly-make-interactive.en.gif';
|
||||
import flyChooseObject from './steps/fly-object-to-collect.en.gif';
|
||||
import flyFlyingObject from './steps/fly-flying-heart.en.gif';
|
||||
import flySelectFlyingSprite from './steps/fly-select-flyer.en.jpg';
|
||||
import flyAddScore from './steps/add-variable.en.gif';
|
||||
import flyAddScore from './steps/fly-add-score.en.gif';
|
||||
import flyKeepScore from './steps/fly-keep-score.en.gif';
|
||||
import flyAddScenery from './steps/fly-choose-scenery.en.gif';
|
||||
import flyMoveScenery from './steps/fly-move-scenery.en.gif';
|
||||
@@ -106,32 +106,32 @@ import flySwitchLooks from './steps/fly-switch-costume.en.gif';
|
||||
|
||||
// Pong
|
||||
import pongAddBackdrop from './steps/pong-add-backdrop.en.png';
|
||||
import pongAddBallSprite from './steps/pong-add-ball-sprite.png';
|
||||
import pongAddBallSprite from './steps/pong-add-ball-sprite.en.png';
|
||||
import pongBounceAround from './steps/pong-bounce-around.en.gif';
|
||||
import pongAddPaddle from './steps/pong-add-a-paddle.en.gif';
|
||||
import pongMoveThePaddle from './steps/pong-move-the-paddle.en.gif';
|
||||
import pongSelectBallSprite from './steps/pong-select-ball.png';
|
||||
import pongSelectBallSprite from './steps/pong-select-ball.en.png';
|
||||
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.en.png';
|
||||
import pongAddAScore from './steps/add-variable.en.gif';
|
||||
import pongAddAScore from './steps/pong-add-score.en.gif';
|
||||
import pongChooseScoreFromMenu from './steps/pong-choose-score.en.png';
|
||||
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.en.png';
|
||||
import pongResetScore from './steps/pong-reset-score.en.png';
|
||||
import pongAddLineSprite from './steps/pong-add-line.en.gif';
|
||||
import pongGameOver from './steps/pong-game-over.en.png';
|
||||
|
||||
// Imagine a World
|
||||
// Imagine If
|
||||
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.en.png';
|
||||
import imagineClickGreenFlag from './steps/imagine-click-green-flag.en.png';
|
||||
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.png';
|
||||
import imagineChooseSprite from './steps/imagine-choose-any-sprite.png';
|
||||
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.en.png';
|
||||
import imagineChooseSprite from './steps/imagine-choose-any-sprite.en.png';
|
||||
import imagineFlyAround from './steps/imagine-fly-around.en.png';
|
||||
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.png';
|
||||
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.en.png';
|
||||
import imagineLeftRight from './steps/imagine-left-right.en.png';
|
||||
import imagineUpDown from './steps/imagine-up-down.en.png';
|
||||
import imagineChangeCostumes from './steps/imagine-change-costumes.en.png';
|
||||
import imagineGlideToPoint from './steps/imagine-glide-to-point.en.png';
|
||||
import imagineGrowShrink from './steps/imagine-grow-shrink.en.png';
|
||||
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.png';
|
||||
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.en.png';
|
||||
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.en.gif';
|
||||
import imagineRecordASound from './steps/imagine-record-a-sound.en.gif';
|
||||
import imagineChooseSound from './steps/imagine-choose-sound.en.png';
|
||||
@@ -170,34 +170,6 @@ import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.en.png';
|
||||
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.en.png';
|
||||
import glideAroundPoint from './steps/glide-around-point.en.png';
|
||||
|
||||
// Code a Cartoon
|
||||
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.en.png';
|
||||
import codeCartoonAnimate from './steps/code-cartoon-02-animate.en.gif';
|
||||
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.png';
|
||||
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.en.gif';
|
||||
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.en.gif';
|
||||
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.png';
|
||||
import codeCartoonJump from './steps/code-cartoon-07-jump.en.gif';
|
||||
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.en.gif';
|
||||
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.en.gif';
|
||||
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.en.gif';
|
||||
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.en.jpg';
|
||||
|
||||
// Talking Tales
|
||||
import talesAddExtension from './steps/talking-1-add-extension.en.gif';
|
||||
import talesChooseSprite from './steps/talking-2-choose-sprite.en.png';
|
||||
import talesSaySomething from './steps/talking-3-say-something.en.gif';
|
||||
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.en.png';
|
||||
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.en.gif';
|
||||
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.en.png';
|
||||
import talesMoveAround from './steps/talking-7-move-around.en.gif';
|
||||
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.en.png';
|
||||
import talesAnimateTalking from './steps/talking-9-animate.en.gif';
|
||||
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.en.png';
|
||||
import talesChooseSound from './steps/talking-11-choose-sound.en.gif';
|
||||
import talesDanceMoves from './steps/talking-12-dance-moves.en.gif';
|
||||
import talesAskAnswer from './steps/talking-13-ask-and-answer.en.gif';
|
||||
|
||||
const enImages = {
|
||||
// Intro
|
||||
introMove: introMove,
|
||||
@@ -304,7 +276,7 @@ const enImages = {
|
||||
flyAddScenery: flyAddScenery,
|
||||
flyMoveScenery: flyMoveScenery,
|
||||
flySwitchLooks: flySwitchLooks,
|
||||
|
||||
|
||||
// Pong
|
||||
pongAddBackdrop: pongAddBackdrop,
|
||||
pongAddBallSprite: pongAddBallSprite,
|
||||
@@ -319,8 +291,8 @@ const enImages = {
|
||||
pongResetScore: pongResetScore,
|
||||
pongAddLineSprite: pongAddLineSprite,
|
||||
pongGameOver: pongGameOver,
|
||||
|
||||
// Imagine a World
|
||||
|
||||
// Imagine If
|
||||
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
|
||||
imagineClickGreenFlag: imagineClickGreenFlag,
|
||||
imagineChooseBackdrop: imagineChooseBackdrop,
|
||||
@@ -336,26 +308,26 @@ const enImages = {
|
||||
imagineSwitchBackdrops: imagineSwitchBackdrops,
|
||||
imagineRecordASound: imagineRecordASound,
|
||||
imagineChooseSound: imagineChooseSound,
|
||||
|
||||
|
||||
// Add a Backdrop
|
||||
addBackdrop: addBackdrop,
|
||||
|
||||
|
||||
// Add Effects
|
||||
addEffects: addEffects,
|
||||
|
||||
|
||||
// Hide and Show
|
||||
hideAndShow: hideAndShow,
|
||||
|
||||
|
||||
// Switch Costumes
|
||||
switchCostumes: switchCostumes,
|
||||
|
||||
|
||||
// Change Size
|
||||
changeSize: changeSize,
|
||||
|
||||
|
||||
// Spin
|
||||
spinTurn: spinTurn,
|
||||
spinPointInDirection: spinPointInDirection,
|
||||
|
||||
|
||||
// Record a Sound
|
||||
recordASoundSoundsTab: recordASoundSoundsTab,
|
||||
recordASoundClickRecord: recordASoundClickRecord,
|
||||
@@ -369,35 +341,9 @@ const enImages = {
|
||||
|
||||
// Glide Around
|
||||
glideAroundBackAndForth: glideAroundBackAndForth,
|
||||
glideAroundPoint: glideAroundPoint,
|
||||
|
||||
// Code a Cartoon
|
||||
codeCartoonSaySomething: codeCartoonSaySomething,
|
||||
codeCartoonAnimate: codeCartoonAnimate,
|
||||
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
|
||||
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
|
||||
codeCartoonGrowShrink: codeCartoonGrowShrink,
|
||||
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
|
||||
codeCartoonJump: codeCartoonJump,
|
||||
codeCartoonChangeScenes: codeCartoonChangeScenes,
|
||||
codeCartoonGlideAround: codeCartoonGlideAround,
|
||||
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
|
||||
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
|
||||
|
||||
// Talking Tales
|
||||
talesAddExtension: talesAddExtension,
|
||||
talesChooseSprite: talesChooseSprite,
|
||||
talesSaySomething: talesSaySomething,
|
||||
talesAskAnswer: talesAskAnswer,
|
||||
talesChooseBackdrop: talesChooseBackdrop,
|
||||
talesSwitchBackdrop: talesSwitchBackdrop,
|
||||
talesChooseAnotherSprite: talesChooseAnotherSprite,
|
||||
talesMoveAround: talesMoveAround,
|
||||
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
|
||||
talesAnimateTalking: talesAnimateTalking,
|
||||
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
|
||||
talesChooseSound: talesChooseSound,
|
||||
talesDanceMoves: talesDanceMoves
|
||||
glideAroundPoint: glideAroundPoint
|
||||
|
||||
};
|
||||
|
||||
|
||||
export {enImages};
|
||||
@@ -3,227 +3,119 @@ 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.png';
|
||||
import speechSetVoice from './steps/speech-set-voice.es.png';
|
||||
import speechMoveAround from './steps/speech-move-around.es.png';
|
||||
import speechAddBackdrop from './steps/speech-add-backdrop.gif';
|
||||
import speechAddSprite from './steps/speech-add-sprite.gif';
|
||||
import speechSong from './steps/speech-song.es.png';
|
||||
import speechChangeColor from './steps/speech-change-color.es.png';
|
||||
import speechSpin from './steps/speech-spin.es.png';
|
||||
import speechGrowShrink from './steps/speech-grow-shrink.es.png';
|
||||
|
||||
// Cartoon Network
|
||||
import cnShowCharacter from './steps/cn-show-character.gif';
|
||||
import cnSay from './steps/cn-say.es.png';
|
||||
import cnGlide from './steps/cn-glide.es.png';
|
||||
import cnPickSprite from './steps/cn-pick-sprite.gif';
|
||||
import cnCollect from './steps/cn-collect.es.png';
|
||||
import cnVariable from './steps/add-variable.es.gif';
|
||||
import cnScore from './steps/cn-score.es.png';
|
||||
import cnBackdrop from './steps/cn-backdrop.es.png';
|
||||
// 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.gif';
|
||||
import addSprite from './steps/add-sprite.es.gif';
|
||||
|
||||
// Animate a name
|
||||
import namePickLetter from './steps/name-pick-letter.gif';
|
||||
import namePlaySound from './steps/name-play-sound.es.png';
|
||||
import namePickLetter2 from './steps/name-pick-letter2.gif';
|
||||
import nameChangeColor from './steps/name-change-color.es.png';
|
||||
import nameSpin from './steps/name-spin.es.png';
|
||||
import nameGrow from './steps/name-grow.es.png';
|
||||
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.gif';
|
||||
import musicPlaySound from './steps/music-play-sound.es.png';
|
||||
import musicMakeSong from './steps/music-make-song.es.png';
|
||||
import musicMakeBeat from './steps/music-make-beat.es.png';
|
||||
import musicMakeBeatbox from './steps/music-make-beatbox.es.png';
|
||||
// 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.gif';
|
||||
import chaseGameAddSprite1 from './steps/chase-game-add-sprite1.gif';
|
||||
import chaseGameRightLeft from './steps/chase-game-right-left.es.png';
|
||||
import chaseGameUpDown from './steps/chase-game-up-down.es.png';
|
||||
import chaseGameAddSprite2 from './steps/chase-game-add-sprite2.gif';
|
||||
import chaseGameMoveRandomly from './steps/chase-game-move-randomly.es.png';
|
||||
import chaseGamePlaySound from './steps/chase-game-play-sound.es.png';
|
||||
import chaseGameAddVariable from './steps/add-variable.es.gif';
|
||||
import chaseGameChangeScore from './steps/chase-game-change-score.es.png';
|
||||
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.gif';
|
||||
import popGamePlaySound from './steps/pop-game-play-sound.es.png';
|
||||
import popGameAddScore from './steps/add-variable.es.gif';
|
||||
import popGameChangeScore from './steps/pop-game-change-score.es.png';
|
||||
import popGameRandomPosition from './steps/pop-game-random-position.es.png';
|
||||
import popGameChangeColor from './steps/pop-game-change-color.es.png';
|
||||
import popGameResetScore from './steps/pop-game-reset-score.es.png';
|
||||
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.gif';
|
||||
import animateCharPickSprite from './steps/animate-char-pick-sprite.gif';
|
||||
import animateCharSaySomething from './steps/animate-char-say-something.es.png';
|
||||
import animateCharAddSound from './steps/animate-char-add-sound.es.png';
|
||||
import animateCharTalk from './steps/animate-char-talk.es.png';
|
||||
import animateCharMove from './steps/animate-char-move.es.png';
|
||||
import animateCharJump from './steps/animate-char-jump.es.png';
|
||||
import animateCharChangeColor from './steps/animate-char-change-color.es.png';
|
||||
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.gif';
|
||||
import storyPickSprite from './steps/story-pick-sprite.gif';
|
||||
import storySaySomething from './steps/story-say-something.es.png';
|
||||
import storyPickSprite2 from './steps/story-pick-sprite2.gif';
|
||||
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.png';
|
||||
import storyPickBackdrop2 from './steps/story-pick-backdrop2.gif';
|
||||
import storySwitchBackdrop from './steps/story-switch-backdrop.es.png';
|
||||
import storyHideCharacter from './steps/story-hide-character.es.png';
|
||||
import storyShowCharacter from './steps/story-show-character.es.png';
|
||||
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.png';
|
||||
import videoAnimate from './steps/video-animate.es.png';
|
||||
import videoPop from './steps/video-pop.es.png';
|
||||
|
||||
// Make it Fly
|
||||
import flyChooseBackdrop from './steps/fly-choose-backdrop.gif';
|
||||
import flyChooseCharacter from './steps/fly-choose-character.png';
|
||||
import flySaySomething from './steps/fly-say-something.es.png';
|
||||
import flyMoveArrows from './steps/fly-make-interactive.es.png';
|
||||
import flyChooseObject from './steps/fly-object-to-collect.png';
|
||||
import flyFlyingObject from './steps/fly-flying-heart.es.png';
|
||||
import flySelectFlyingSprite from './steps/fly-select-flyer.png';
|
||||
import flyAddScore from './steps/add-variable.es.gif';
|
||||
import flyKeepScore from './steps/fly-keep-score.es.png';
|
||||
import flyAddScenery from './steps/fly-choose-scenery.gif';
|
||||
import flyMoveScenery from './steps/fly-move-scenery.es.png';
|
||||
import flySwitchLooks from './steps/fly-switch-costume.es.png';
|
||||
|
||||
// Pong
|
||||
import pongAddBackdrop from './steps/pong-add-backdrop.png';
|
||||
import pongAddBallSprite from './steps/pong-add-ball-sprite.png';
|
||||
import pongBounceAround from './steps/pong-bounce-around.es.png';
|
||||
import pongAddPaddle from './steps/pong-add-a-paddle.gif';
|
||||
import pongMoveThePaddle from './steps/pong-move-the-paddle.es.png';
|
||||
import pongSelectBallSprite from './steps/pong-select-ball.png';
|
||||
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.es.png';
|
||||
import pongAddAScore from './steps/add-variable.es.gif';
|
||||
import pongChooseScoreFromMenu from './steps/pong-choose-score.es.png';
|
||||
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.es.png';
|
||||
import pongResetScore from './steps/pong-reset-score.es.png';
|
||||
import pongAddLineSprite from './steps/pong-add-line.gif';
|
||||
import pongGameOver from './steps/pong-game-over.es.png';
|
||||
|
||||
// Imagine a World
|
||||
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.es.png';
|
||||
import imagineClickGreenFlag from './steps/imagine-click-green-flag.es.png';
|
||||
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.png';
|
||||
import imagineChooseSprite from './steps/imagine-choose-any-sprite.png';
|
||||
import imagineFlyAround from './steps/imagine-fly-around.es.png';
|
||||
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.png';
|
||||
import imagineLeftRight from './steps/imagine-left-right.es.png';
|
||||
import imagineUpDown from './steps/imagine-up-down.es.png';
|
||||
import imagineChangeCostumes from './steps/imagine-change-costumes.es.png';
|
||||
import imagineGlideToPoint from './steps/imagine-glide-to-point.es.png';
|
||||
import imagineGrowShrink from './steps/imagine-grow-shrink.es.png';
|
||||
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.png';
|
||||
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.es.png';
|
||||
import imagineRecordASound from './steps/imagine-record-a-sound.es.gif';
|
||||
import imagineChooseSound from './steps/imagine-choose-sound.es.png';
|
||||
|
||||
// Add a Backdrop
|
||||
import addBackdrop from './steps/add-backdrop.png';
|
||||
|
||||
// Add Effects
|
||||
import addEffects from './steps/add-effects.es.png';
|
||||
|
||||
// Hide and Show
|
||||
import hideAndShow from './steps/hide-show.es.png';
|
||||
|
||||
// Switch Costumes
|
||||
import switchCostumes from './steps/switch-costumes.es.png';
|
||||
|
||||
// Change Size
|
||||
import changeSize from './steps/change-size.es.png';
|
||||
|
||||
// Spin
|
||||
import spinTurn from './steps/spin-turn.es.png';
|
||||
import spinPointInDirection from './steps/spin-point-in-direction.es.png';
|
||||
|
||||
// Record a Sound
|
||||
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.es.png';
|
||||
import recordASoundClickRecord from './steps/record-a-sound-click-record.es.png';
|
||||
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.es.png';
|
||||
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.es.png';
|
||||
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.es.png';
|
||||
|
||||
// Use Arrow Keys
|
||||
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.es.png';
|
||||
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.es.png';
|
||||
|
||||
// Glide Around
|
||||
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.es.png';
|
||||
import glideAroundPoint from './steps/glide-around-point.es.png';
|
||||
|
||||
// Code a Cartoon
|
||||
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.es.png';
|
||||
import codeCartoonAnimate from './steps/code-cartoon-02-animate.es.png';
|
||||
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.png';
|
||||
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.es.png';
|
||||
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.es.png';
|
||||
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.png';
|
||||
import codeCartoonJump from './steps/code-cartoon-07-jump.es.png';
|
||||
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.es.png';
|
||||
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.es.png';
|
||||
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.es.png';
|
||||
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.png';
|
||||
|
||||
// Talking Tales
|
||||
import talesAddExtension from './steps/speech-add-extension.es.gif';
|
||||
import talesChooseSprite from './steps/talking-2-choose-sprite.png';
|
||||
import talesSaySomething from './steps/talking-3-say-something.es.png';
|
||||
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.png';
|
||||
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.es.png';
|
||||
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.png';
|
||||
import talesMoveAround from './steps/talking-7-move-around.es.png';
|
||||
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.png';
|
||||
import talesAnimateTalking from './steps/talking-9-animate.es.png';
|
||||
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.png';
|
||||
import talesChooseSound from './steps/talking-11-choose-sound.es.gif';
|
||||
import talesDanceMoves from './steps/talking-12-dance-moves.es.png';
|
||||
import talesAskAnswer from './steps/talking-13-ask-and-answer.es.png';
|
||||
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,
|
||||
// // 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,
|
||||
@@ -289,115 +181,8 @@ const esImages = {
|
||||
videoAddExtension: videoAddExtension,
|
||||
videoPet: videoPet,
|
||||
videoAnimate: videoAnimate,
|
||||
videoPop: videoPop,
|
||||
videoPop: videoPop
|
||||
|
||||
// Make it Fly
|
||||
flyChooseBackdrop: flyChooseBackdrop,
|
||||
flyChooseCharacter: flyChooseCharacter,
|
||||
flySaySomething: flySaySomething,
|
||||
flyMoveArrows: flyMoveArrows,
|
||||
flyChooseObject: flyChooseObject,
|
||||
flyFlyingObject: flyFlyingObject,
|
||||
flySelectFlyingSprite: flySelectFlyingSprite,
|
||||
flyAddScore: flyAddScore,
|
||||
flyKeepScore: flyKeepScore,
|
||||
flyAddScenery: flyAddScenery,
|
||||
flyMoveScenery: flyMoveScenery,
|
||||
flySwitchLooks: flySwitchLooks,
|
||||
|
||||
// Pong
|
||||
pongAddBackdrop: pongAddBackdrop,
|
||||
pongAddBallSprite: pongAddBallSprite,
|
||||
pongBounceAround: pongBounceAround,
|
||||
pongAddPaddle: pongAddPaddle,
|
||||
pongMoveThePaddle: pongMoveThePaddle,
|
||||
pongSelectBallSprite: pongSelectBallSprite,
|
||||
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
|
||||
pongAddAScore: pongAddAScore,
|
||||
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
|
||||
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
|
||||
pongResetScore: pongResetScore,
|
||||
pongAddLineSprite: pongAddLineSprite,
|
||||
pongGameOver: pongGameOver,
|
||||
|
||||
// Imagine a World
|
||||
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
|
||||
imagineClickGreenFlag: imagineClickGreenFlag,
|
||||
imagineChooseBackdrop: imagineChooseBackdrop,
|
||||
imagineChooseSprite: imagineChooseSprite,
|
||||
imagineFlyAround: imagineFlyAround,
|
||||
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
|
||||
imagineLeftRight: imagineLeftRight,
|
||||
imagineUpDown: imagineUpDown,
|
||||
imagineChangeCostumes: imagineChangeCostumes,
|
||||
imagineGlideToPoint: imagineGlideToPoint,
|
||||
imagineGrowShrink: imagineGrowShrink,
|
||||
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
|
||||
imagineSwitchBackdrops: imagineSwitchBackdrops,
|
||||
imagineRecordASound: imagineRecordASound,
|
||||
imagineChooseSound: imagineChooseSound,
|
||||
|
||||
// Add a Backdrop
|
||||
addBackdrop: addBackdrop,
|
||||
|
||||
// Add Effects
|
||||
addEffects: addEffects,
|
||||
|
||||
// Hide and Show
|
||||
hideAndShow: hideAndShow,
|
||||
|
||||
// Switch Costumes
|
||||
switchCostumes: switchCostumes,
|
||||
|
||||
// Change Size
|
||||
changeSize: changeSize,
|
||||
|
||||
// Spin
|
||||
spinTurn: spinTurn,
|
||||
spinPointInDirection: spinPointInDirection,
|
||||
|
||||
// Record a Sound
|
||||
recordASoundSoundsTab: recordASoundSoundsTab,
|
||||
recordASoundClickRecord: recordASoundClickRecord,
|
||||
recordASoundPressRecordButton: recordASoundPressRecordButton,
|
||||
recordASoundChooseSound: recordASoundChooseSound,
|
||||
recordASoundPlayYourSound: recordASoundPlayYourSound,
|
||||
|
||||
// Use Arrow Keys
|
||||
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
|
||||
moveArrowKeysUpDown: moveArrowKeysUpDown,
|
||||
|
||||
// Glide Around
|
||||
glideAroundBackAndForth: glideAroundBackAndForth,
|
||||
glideAroundPoint: glideAroundPoint,
|
||||
|
||||
// Code a Cartoon
|
||||
codeCartoonSaySomething: codeCartoonSaySomething,
|
||||
codeCartoonAnimate: codeCartoonAnimate,
|
||||
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
|
||||
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
|
||||
codeCartoonGrowShrink: codeCartoonGrowShrink,
|
||||
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
|
||||
codeCartoonJump: codeCartoonJump,
|
||||
codeCartoonChangeScenes: codeCartoonChangeScenes,
|
||||
codeCartoonGlideAround: codeCartoonGlideAround,
|
||||
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
|
||||
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
|
||||
|
||||
// Talking Tales
|
||||
talesAddExtension: talesAddExtension,
|
||||
talesChooseSprite: talesChooseSprite,
|
||||
talesSaySomething: talesSaySomething,
|
||||
talesAskAnswer: talesAskAnswer,
|
||||
talesChooseBackdrop: talesChooseBackdrop,
|
||||
talesSwitchBackdrop: talesSwitchBackdrop,
|
||||
talesChooseAnotherSprite: talesChooseAnotherSprite,
|
||||
talesMoveAround: talesMoveAround,
|
||||
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
|
||||
talesAnimateTalking: talesAnimateTalking,
|
||||
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
|
||||
talesChooseSound: talesChooseSound,
|
||||
talesDanceMoves: talesDanceMoves
|
||||
};
|
||||
|
||||
export {esImages};
|
||||
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 652 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 802 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 520 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 718 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 872 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 669 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 541 KiB |
|
Before Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 710 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 31 KiB |