From c7f1408394c99112c8026b352083ae8fcbee0214 Mon Sep 17 00:00:00 2001 From: David Aylaian Date: Wed, 20 Feb 2019 10:22:48 -0500 Subject: [PATCH] Merge latest changes (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(package): update scratch-vm to version 0.2.0-prerelease.20190118221822 Closes #4390 * Remove transition on sprite tiles to make placholder not flash black. Also use white as the background since it gives the same impact and will not look bad if the filter isn't applied or takes a while to be applied * Optimize Waveform component to reduce renders and cap max nodes in svg. The waveform component was being re-rendered while the playhead changed, which is a costly operation because it has to calculate the full svg path data string. * Improve paint/composite time for sound editor while sound is playing. Forcing the playhead onto its own painting layer removes an expensive composite with the waveform. Animate using transforms instead of left prevents layouts of the rest of the page while playing a sound. * Update WeDo 2.0 extension name * Disable target updates while sound recording * Fix #4263: Remove Preview Modal * Fix #4262: Saving with Ctrl/Cmd + S * Fully delete preview modal. * chore(package): update scratch-render to version 0.1.0-prerelease.20190122223537 Closes #4378 * chore(package): update scratch-l10n to version 3.1.20190123150639 Closes #4408 * Move the mask on the audio meter outside the svg for repaint performance * Revert "Fix #4262: Saving with Ctrl/Cmd + S" This reverts commit e2ee0e06f7293795e7f6685ed1bb5e99bee52eb8. * Remove the redux throttle for targets update. This fixes the root cause of #2858, which is caused because we are rendering the sound editor based on the GUI editing target data, but retrieving the sound based on the vms editing target data. The throttle of the redux action was causing those to get out of sync while a project was running. The throttle is not needed because the vm runtime already batches those updates at 30 fps. The updates that come from top-level actions like switching sprites go out immediately. This is also a minor performance boost, because there was a few ms being spent in setting and checking timers that were not needed. * chore(package): update scratch-vm to version 0.2.0-prerelease.20190123164824 * Fix issue where dragPayload was being invalidated for sprites Use PureComponent instead since it is a simple shouldComponentUpdate check now. * Remove dead code * Throttle updates to assets on sprite selector item via HOC Adds unit tests for this HOC. * Throttle the watermark updates using the same HOC * Throttle updates to stage images also * chore(package): update scratch-blocks to version 0.1.0-prerelease.1548272895 * Use curried style for HOC configuration * Revert "chore(package): update scratch-l10n to version 3.1.20190123150639" * Update l10n * Add container with overflow hidden to prevent scrollbars from playhead * Actually fully remove preview modal. * Fix test broken in #4422 by updating the snapshot. * Use fontInliner instead of entire SVGRenderer for showing costume tiles. The fontInliner from the scratch-svg-renderer was changed recently to work entirely on strings, so we can use that instead of parsing/loading/possibly drawing/stringifying the entire SVG. This does not address the size-1 cache issue where the cache is invalidated any time there is more than 1 sprite. But that can be handled later. Using the 4x CPU slowdown in chrome dev tools, on a simple costume that just has a single text element the getCostumeUrl time goes from 10ms to 1ms, very important for updating in a timely way. * chore(package): update scratch-blocks to version 0.1.0-prerelease.1548354812 Closes #4426 * fixed mouse click issuue * Remove Irrelevant Tests * chore(package): update scratch-render to version 0.1.0-prerelease.20190125023926 * Save when Ctrl+S is pressed * Detect Cmd+S on macOS * chore(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190125192231 * rg "Try It" -l | xargs gsed -i "/Try It/d" * Sometimes local machine says no errors, but travis finds them... * Export costumes and sounds * Avoid double toolbox update when setting the locale * chore(package): update scratch-render to version 0.1.0-prerelease.20190128154859 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190128155421 * chore(package): update scratch-blocks to version 0.1.0-prerelease.1548691077 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190128162704 Closes #4443 * Move rounding into sprite info to avoid generating lots of garbage It also avoids repeating the costly object spread, which becomes a problem for projects with a lot of sprites * chore(package): update scratch-vm to version 0.2.0-prerelease.20190128192220 * Remove cruft * Remove References * Fix Lint * Different tests fail each time I run them. * Only update the toolbox when the blocks tab is visible. Need to store the rendered xml because the props can change while the blocks tab is hidden, but we re-render based on what was rendered, not the previous props (which may not have been rendered). This is similar to the `ThrottledPropertyHOC` in that there may be prop changes that do not get rendered, but in this case for a different reason. This increases the performance of switching between code and other editor tabs, as well as improving the "See Inside" performance. It makes switching to the code tab as fast as switching between sprites. * Add an integration test to cover renaming costumes updating the blocks It looks like an "test.only" was left in on one of these tests by mistake. * chore(package): update scratch-l10n to version 3.1.20190130142816 * Add test for menus not updating when adding new costumes I know we will want to change this eventually, so I added comments about that. But the next commit originally contained an inadvertent change to this functionality, so I wanted to add an integration test so that when we do change it, we * Make the toolbox xml reflect the costume, sound and backdrop name This invalidates the toolbox XML when switching back to the code tab after e.g. renaming a costume, making the code tab * Menus cover monitors fixes https://github.com/LLK/scratch-www/issues/2587 and https://github.com/LLK/scratch-gui/issues/4428 changed menu-bar z-index to one more than monitors so drop down menus cover monitors when opened. * chore(package): update scratch-vm to version 0.2.0-prerelease.20190130160436 * Include blocks update that improves toolbox layout perf * chore(package): update scratch-blocks to version 0.1.0-prerelease.1548864869 * Update project changes test to test a project change that should actually emit a change event. Clicking on blocks should not emit a project changed event. * Update package.json * chore(package): update scratch-blocks to version 0.1.0-prerelease.1548885087 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190130220715 * Make uploaded sprites not draggable * Call updateToolbox after adding new blocks from the backpack. This fixes the issue where you could not see any new variables or custom block callers, as well as fixing the issue where the toolbox did not update from then on. * Use more generic download-url and remove older download-text utility * Use "stable" tag on npm for master builds and "latest" for develop/smoke This is different of what it is currently, where master/smoke goes to "latest" and develop goes to "develop" * Export costumes without inlined fonts for now. Since the common use-case is download/edit/re-upload, and we do not want to explode the storage size of assets (which are considerably larger with fonts inlined), just do what scratch2 did and do not inline the fonts. This is not ideal if kids want to use the costumes for other purposes, but until we can create a way to strip inlined fonts before uploading to the asset server, this is the conservative way to go. * Use bowser for macOS detection * try building again * remove "try it" * retain showBranding after SET_FULL_SCREEN * add mode reducer unit tests * add empty object to abject assign * Initialize locale on ScratchBlocks before initial workspace injection This fixes a problem where having a non-default locale set during the initial load would display the blocks in the default toolbox in the wrong language, because the toolbox recycling toggle in setLocale does not work on initial load because there is no editing target to refresh. * Include a regression test for #4476 * Remove beforeunload callback that was breaking tests * remove isAdmin from mode reducer * Move blocks z-index up to match gui Override the z-index for the blocks drag layer. At 1000, it will be over everything in the gui other than the navbar itself (monitors on the stage, tutorials, alerts, dropdown menus, etc.) * Sort variable options in "of" block * Escape special characters in user defined strings that can appear in the toolbox xml. * fix(Typo): change 'dserver' to 'server' * Fix toolbox updating after see-inside. We can't rely on refreshWorkspace always causing a toolbox update, so make sure to update the toolbox manually if it is needed. * Add an integration test for toolbox updating after see-inside. I needed to update the player css because the editor side wasn't large enough to be useable. * require text-encoding only if it is needed * Wait until loader becomes stale to continue in project loader tests * Include the `waitUntilGone` helper in the other project load tests * Don't refer to scratchBlocksUtils from scratch-gui * Make file uploader call callback for each uploaded file * Add integration tests for multiple file upload Including sprites, backdrops, costumes and sounds inputs * There is no loading screen for blocks example, dont wait for it * Load files one at a time to enforce load order * chore(package): update scratch-blocks to version 0.1.0-prerelease.1549376808 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190205221329 Closes #4480 * chore(package): update scratch-l10n to version 3.1.20190206143031 * set an initial message number to skip rendering a second time * chore(package): update scratch-render to version 0.1.0-prerelease.20190206213754 * chore(package): update scratch-blocks to version 0.1.0-prerelease.1549534784 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190207134238 Closes #4503 * Use blob download instead of data-uri to enable large downloads This was already being used for sb3s and sprite3s, but the code was repeated in different places. Make a single helper that downloads blobs, and make all download paths use it. * Get the asset type correctly * chore(package): update scratch-vm to version 0.2.0-prerelease.20190207191220 Closes #4506 * Revert "Update scratch-render to the latest version 🚀" * chore(package): update scratch-vm to version 0.2.0-prerelease.20190207224121 Closes #4510 * chore(package): update scratch-l10n to version 3.1.20190207224638 * chore(package): update scratch-blocks to version 0.1.0-prerelease.1549643185 * chore(package): update scratch-render to version 0.1.0-prerelease.20190208165820 Closes #4513 * Gif upload v1: sprites, costumes * Pass function for naming costumes, add integration tests * Fix namer conventions to be less bizarre * Remove namer from costume and sound upload helpers to reduce spaghetti * Remove prune step from deployment for now It doesn't affect greenkeeper subdirectories, which are the ones that cause the most issues. And now the prune step itself is breaking builds. So until it works better, remove this step. * Name uploaded backdrops based on the name * Remove extra parens * Fix backdrop uploading from gif * Tag NPM releases Check if an NPM release just happened. And if so, tag the commit and push to GitHub. Thanks to @paulkaplan for pairing on this * Simplify disposal method handling * chore(package): update scratch-l10n to version 3.1.20190211142555 * Put the ask blocks text input above the monitors on the stage. * Support the isDiscrete flag on slider monitors * Add unit test to make sure isDiscrete becomes correct step size * Prompt user about file upload replacing the current project This only shows when you are on a project that is SHOWING_WITHOUT_ID and has changes. This is only the case when logged out, or in the desktop editor. * Make sure to put history in try/catch so it can be used with selenium Selenium loads the playground using the file:// protocol, where history.replaceState throws an error * Add integration test for uploading a file and seeing the prompt * Remove try-it step from more integration tests * Remove more try-it steps * Update to develop and remove try-it steps from new tests * chore(package): update scratch-vm to version 0.2.0-prerelease.20190212150857 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190212160344 * chore(package): update scratch-blocks to version 0.1.0-prerelease.1549990124 Closes #4540 * Share the replace project confirmation string between new and upload * chore(package): update scratch-l10n to version 3.1.20190213142844 Closes #4536 * Show an "Importing..." alert during file import * Make sure to only close import alert after import is done * chore(package): update scratch-vm to version 0.2.0-prerelease.20190213162739 * chore(package): update scratch-render to version 0.1.0-prerelease.20190213183713 Closes #4544 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190213190040 Closes #4548 * Refactor to handle errors from file reader * Add error handling for the costumeUpload function * chore(package): update scratch-l10n to version 3.1.20190213193054 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190213210403 * Fix loading single frame gifs by removing onDone in favor of indices Because the `onFrame` callback could do async work, it was possible for onDone to try to submit the costumes before the onFrame had produced any. This was subject to browsers different timing treatments of setTimeout vs. promise resolution. This makes the gif-decoder use the same style of progress API as the file uploader * move when the dragging drawable is extracted from the renderer Extracting the drawable art data is very expensive. We should delay doing that work as long as possible. As such it should wait until a few more branching return statements before we extract it. * Request a toolbox refresh after switching between tabs to fix #4561 * chore(package): update scratch-vm to version 0.2.0-prerelease.20190215190223 * chore(package): update scratch-l10n to version 3.1.20190218084652 --- .gitignore | 1 + .travis.yml | 19 +- README.md | 2 +- package.json | 20 +- src/components/action-menu/action-menu.jsx | 6 +- src/components/alerts/alert.css | 35 +- src/components/alerts/alert.jsx | 23 +- src/components/alerts/alerts.css | 4 + src/components/alerts/alerts.jsx | 40 +- src/components/alerts/inline-message.jsx | 1 + src/components/asset-panel/selector.css | 4 +- .../audio-trimmer/audio-trimmer.css | 22 +- .../audio-trimmer/audio-trimmer.jsx | 14 +- src/components/backpack/backpack.css | 14 +- src/components/blocks/blocks.css | 4 +- .../browser-modal/browser-modal.css | 14 +- .../browser-modal/browser-modal.jsx | 138 +- .../browser-modal/unsupported-browser.svg | 54 + src/components/cards/cards.jsx | 6 +- src/components/context-menu/context-menu.css | 8 + src/components/context-menu/context-menu.jsx | 10 + .../crash-message/crash-message.jsx | 13 + src/components/green-flag/green-flag.css | 3 +- src/components/gui/gui.css | 8 +- src/components/gui/gui.jsx | 37 +- src/components/icon-button/icon-button.css | 5 - src/components/import-modal/import-modal.css | 190 - src/components/import-modal/import-modal.jsx | 168 - src/components/library-item/library-item.css | 1 + src/components/library-item/library-item.jsx | 71 +- src/components/library/library.jsx | 59 +- src/components/loader/loader.css | 9 +- src/components/loader/loader.jsx | 17 +- src/components/menu-bar/login-dropdown.jsx | 5 + src/components/menu-bar/menu-bar.css | 4 + src/components/menu-bar/menu-bar.jsx | 95 +- src/components/meter/meter.css | 18 + src/components/meter/meter.jsx | 54 +- src/components/monitor-list/monitor-list.jsx | 1 + src/components/monitor/monitor.jsx | 59 +- src/components/monitor/slider-monitor.jsx | 5 +- src/components/preview-modal/happy-cat.png | Bin 3055 -> 0 bytes .../preview-modal/preview-modal.jsx | 144 - src/components/preview-modal/welcome.png | Bin 104787 -> 0 bytes src/components/prompt/prompt.jsx | 6 +- src/components/sound-editor/sound-editor.css | 1 + src/components/spinner/spinner.css | 25 +- src/components/spinner/spinner.jsx | 6 +- src/components/sprite-info/sprite-info.jsx | 17 +- .../sprite-selector-item.css | 19 +- .../sprite-selector-item.jsx | 48 +- .../sprite-selector/sprite-list.jsx | 105 +- .../sprite-selector/sprite-selector.css | 11 +- .../sprite-selector/sprite-selector.jsx | 31 +- .../stage-selector/stage-selector.css | 2 +- .../stage-selector/stage-selector.jsx | 5 +- .../stage-wrapper/stage-wrapper.jsx | 10 +- src/components/stage/stage.css | 7 +- src/components/stage/stage.jsx | 24 +- src/components/stop-all/stop-all.css | 11 +- .../telemetry-modal-header.png | Bin 0 -> 9806 bytes .../telemetry-modal.css} | 48 +- .../telemetry-modal/telemetry-modal.jsx | 140 + src/components/waveform/waveform.jsx | 103 +- .../unsupported.png | Bin src/components/webgl-modal/webgl-modal.css | 2 +- src/components/webgl-modal/webgl-modal.jsx | 2 +- src/containers/blocks.jsx | 91 +- src/containers/costume-library.jsx | 2 +- src/containers/costume-tab.jsx | 55 +- src/containers/error-boundary.jsx | 46 +- src/containers/green-flag-overlay.jsx | 21 +- src/containers/gui.jsx | 28 +- src/containers/import-modal.jsx | 115 - src/containers/library-item.jsx | 149 + src/containers/modal.jsx | 2 +- src/containers/monitor.jsx | 50 +- src/containers/paint-editor-wrapper.jsx | 5 - src/containers/preview-modal.jsx | 76 - src/containers/prompt.jsx | 8 +- src/containers/record-modal.jsx | 4 +- src/containers/recording-step.jsx | 2 +- src/containers/sb-file-uploader.jsx | 152 +- src/containers/sb3-downloader.jsx | 18 +- src/containers/sound-editor.jsx | 57 +- src/containers/sound-tab.jsx | 37 +- src/containers/sprite-library.jsx | 53 +- src/containers/sprite-selector-item.jsx | 20 +- src/containers/stage-selector.jsx | 45 +- src/containers/stage.jsx | 11 +- src/containers/target-pane.jsx | 57 +- src/containers/tips-library.jsx | 50 +- src/containers/watermark.jsx | 8 +- src/css/colors.css | 4 +- src/css/z-index.css | 5 +- src/lib/alerts/index.jsx | 14 + src/lib/analytics.js | 2 +- src/lib/app-state-hoc.jsx | 11 +- src/lib/audio/audio-recorder.js | 10 +- src/lib/audio/audio-util.js | 5 +- src/lib/backpack/sound-payload.js | 7 + src/lib/backpack/sprite-payload.js | 40 +- src/lib/blocks.js | 16 +- src/lib/cloud-manager-hoc.jsx | 2 +- src/lib/cloud-provider.js | 2 - src/lib/default-project/index.js | 11 +- src/lib/download-blob.js | 17 + src/lib/file-uploader.js | 95 +- src/lib/font-loader-hoc.jsx | 56 +- src/lib/get-costume-url.js | 6 +- src/lib/gif-decoder.js | 60 + src/lib/hash-parser-hoc.jsx | 5 - src/lib/import-csv.js | 23 + src/lib/isScratchDesktop.js | 35 + src/lib/libraries/backdrops.json | 94 +- src/lib/libraries/costumes.json | 2487 +++-- src/lib/libraries/decks/index.jsx | 9 +- src/lib/libraries/extensions/index.jsx | 2 +- src/lib/libraries/sounds.json | 290 +- src/lib/libraries/sprites.json | 8647 +++++++++-------- src/lib/make-toolbox-xml.js | 56 +- src/lib/project-fetcher-hoc.jsx | 20 +- src/lib/project-saver-hoc.jsx | 15 +- src/lib/query-parser-hoc.jsx | 4 +- src/lib/randomize-sprite-position.js | 16 + src/lib/screen-utils.js | 4 + src/lib/shared-messages.js | 5 + src/lib/sortable-hoc.jsx | 10 +- src/lib/storage.js | 2 +- src/lib/supported-browser.js | 34 +- src/lib/throttled-property-hoc.jsx | 49 + src/lib/variable-utils.js | 1 + src/lib/vm-listener-hoc.jsx | 20 +- src/lib/vm-manager-hoc.jsx | 3 +- src/playground/player.css | 8 + src/playground/player.jsx | 6 +- src/playground/render-gui.jsx | 54 +- src/reducers/alerts.js | 20 + src/reducers/fonts-loaded.js | 23 + src/reducers/gui.js | 9 +- src/reducers/modals.js | 40 +- src/reducers/mode.js | 12 +- src/reducers/project-state.js | 105 +- src/reducers/targets.js | 5 +- test/__mocks__/audio-buffer-player.js | 3 +- test/fixtures/movie.wav | Bin 0 -> 76348 bytes test/fixtures/paddleball.gif | Bin 0 -> 9512 bytes test/fixtures/project1.sb3 | Bin 0 -> 41936 bytes test/fixtures/sneaker.wav | Bin 0 -> 4668 bytes test/helpers/selenium-helper.js | 16 +- test/integration/backdrops.test.js | 27 +- test/integration/backpack.test.js | 3 - test/integration/blocks.test.js | 110 +- test/integration/connection-modal.test.js | 5 +- test/integration/costumes.test.js | 72 +- test/integration/examples.test.js | 14 +- test/integration/how-tos.test.js | 1 - test/integration/localization.test.js | 18 +- test/integration/menu-bar.test.js | 52 +- test/integration/project-loading.test.js | 83 +- test/integration/sounds.test.js | 26 +- test/integration/sprites.test.js | 84 +- test/integration/stage-size.test.js | 1 - test/integration/tutorials-shortcut.test.js | 6 + .../__snapshots__/sound-editor.test.jsx.snap | 27 +- .../sprite-selector-item.test.jsx.snap | 74 +- test/unit/components/monitor-list.test.jsx | 71 + .../unit/containers/sb-file-uploader.test.jsx | 4 +- test/unit/reducers/alerts-reducer.test.js | 30 + test/unit/reducers/mode-reducer.test.js | 53 + .../reducers/project-state-reducer.test.js | 46 +- .../unit/util/throttled-property-hoc.test.jsx | 54 + test/unit/util/vm-listener-hoc.test.jsx | 42 + 173 files changed, 9329 insertions(+), 7259 deletions(-) create mode 100644 src/components/alerts/alerts.css create mode 100644 src/components/browser-modal/unsupported-browser.svg delete mode 100644 src/components/import-modal/import-modal.css delete mode 100644 src/components/import-modal/import-modal.jsx delete mode 100644 src/components/preview-modal/happy-cat.png delete mode 100644 src/components/preview-modal/preview-modal.jsx delete mode 100644 src/components/preview-modal/welcome.png create mode 100644 src/components/telemetry-modal/telemetry-modal-header.png rename src/components/{preview-modal/preview-modal.css => telemetry-modal/telemetry-modal.css} (69%) create mode 100644 src/components/telemetry-modal/telemetry-modal.jsx rename src/components/{browser-modal => webgl-modal}/unsupported.png (100%) delete mode 100644 src/containers/import-modal.jsx create mode 100644 src/containers/library-item.jsx delete mode 100644 src/containers/preview-modal.jsx create mode 100644 src/lib/download-blob.js create mode 100644 src/lib/gif-decoder.js create mode 100644 src/lib/import-csv.js create mode 100644 src/lib/isScratchDesktop.js create mode 100644 src/lib/randomize-sprite-position.js create mode 100644 src/lib/throttled-property-hoc.jsx create mode 100644 src/reducers/fonts-loaded.js create mode 100644 test/fixtures/movie.wav create mode 100644 test/fixtures/paddleball.gif create mode 100644 test/fixtures/project1.sb3 create mode 100644 test/fixtures/sneaker.wav create mode 100644 test/unit/components/monitor-list.test.jsx create mode 100644 test/unit/reducers/mode-reducer.test.js create mode 100644 test/unit/util/throttled-property-hoc.test.jsx diff --git a/.gitignore b/.gitignore index 0b8a0db40e6..de7972e5eed 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # NPM /node_modules npm-* +/package-lock.json # Testing /.nyc_output diff --git a/.travis.yml b/.travis.yml index 9290080d2f3..e429117fd18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - NODE_ENV=production - SMOKE_URL=https://llk.github.io/scratch-gui/$TRAVIS_PULL_REQUEST_BRANCH - NPM_TAG=latest + - RELEASE_VERSION="0.1.0-prerelease.$(date +'%Y%m%d%H%M%S')" cache: directories: - node_modules @@ -22,8 +23,8 @@ script: before_deploy: - > if [ -z "$BEFORE_DEPLOY_RAN" ]; then - npm --no-git-tag-version version 0.1.0-prerelease.$(date +%Y%m%d%H%M%S) - if [ "$TRAVIS_BRANCH" == "develop" ]; then export NPM_TAG=develop; fi + npm --no-git-tag-version version $RELEASE_VERSION + if [ "$TRAVIS_BRANCH" == "master" ]; then export NPM_TAG=stable; fi git config --global user.email $(git log --pretty=format:"%ae" -n1) git config --global user.name $(git log --pretty=format:"%an" -n1) export BEFORE_DEPLOY_RAN=true @@ -40,6 +41,15 @@ deploy: email: $NPM_EMAIL api_key: $NPM_TOKEN tag: $NPM_TAG +- provider: script + on: + branch: + - master + - develop + - smoke + condition: $TRAVIS_EVENT_TYPE != cron + skip_cleanup: true + script: if npm info scratch-gui | grep -q $RELEASE_VERSION; then git tag $RELEASE_VERSION && git push https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git $RELEASE_VERSION; fi - provider: s3 on: branch: @@ -58,11 +68,6 @@ deploy: condition: $TRAVIS_EVENT_TYPE != cron skip_cleanup: true script: npm run deploy -- -x -e $TRAVIS_BRANCH -r https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git -- provider: script - on: - all_branches: true - condition: $TRAVIS_EVENT_TYPE != cron - script: npm run prune -- https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git - provider: script on: branch: develop diff --git a/README.md b/README.md index 1f49dca4f60..440a3a4b06a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ npm install https://github.com/LLK/scratch-gui.git ``` If you want to edit/play yourself: ```bash -git clone git@github.com:LLK/scratch-gui.git +git clone https://github.com/LLK/scratch-gui.git cd scratch-gui npm install ``` diff --git a/package.json b/package.json index 38d6aa3014c..10601ea34a8 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "eslint": "^5.0.1", "eslint-config-scratch": "^5.0.0", "eslint-plugin-import": "^2.8.0", - "eslint-plugin-react": "^7.5.1", + "eslint-plugin-react": "7.11.1", "file-loader": "2.0.0", "get-float-time-domain-data": "0.1.0", "get-user-media-promise": "1.1.4", @@ -76,6 +76,8 @@ "lodash.throttle": "4.0.1", "minilog": "3.1.0", "mkdirp": "^0.5.1", + "omggif": "1.0.9", + "papaparse": "4.6.2", "postcss-import": "^12.0.0", "postcss-loader": "^3.0.0", "postcss-simple-vars": "^5.0.1", @@ -102,14 +104,14 @@ "redux-mock-store": "^1.2.3", "redux-throttle": "0.1.1", "rimraf": "^2.6.1", - "scratch-audio": "0.1.0-prerelease.20181023202904", - "scratch-blocks": "0.1.0-prerelease.1544625383", - "scratch-l10n": "3.1.20181210144244", - "scratch-paint": "0.2.0-prerelease.20181211153626", - "scratch-render": "0.1.0-prerelease.20181127194508", - "scratch-storage": "1.2.0", - "scratch-svg-renderer": "0.2.0-prerelease.20181126212715", - "scratch-vm": "0.2.0-prerelease.20181210154926", + "scratch-audio": "0.1.0-prerelease.20190114210212", + "scratch-blocks": "0.1.0-prerelease.1549990124", + "scratch-l10n": "3.1.20190218084652", + "scratch-paint": "0.2.0-prerelease.20190114205252", + "scratch-render": "0.1.0-prerelease.20190213183713", + "scratch-storage": "1.2.2", + "scratch-svg-renderer": "0.2.0-prerelease.20190125192231", + "scratch-vm": "0.2.0-prerelease.20190215190223", "selenium-webdriver": "3.6.0", "startaudiocontext": "1.2.1", "style-loader": "^0.23.0", diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx index 36cf5d4449f..f9e50f3ff66 100644 --- a/src/components/action-menu/action-menu.jsx +++ b/src/components/action-menu/action-menu.jsx @@ -142,7 +142,7 @@ class ActionMenu extends React.Component {
{(moreButtons || []).map(({img, title, onClick: handleClick, - fileAccept, fileChange, fileInput}, keyId) => { + fileAccept, fileChange, fileInput, fileMultiple}, keyId) => { const isComingSoon = !handleClick; const hasFileInput = fileInput; const tooltipId = `${this.mainTooltipId}-${title}`; @@ -166,6 +166,7 @@ class ActionMenu extends React.Component { :last-child { + margin-left: 0; + margin-right: 0; +} diff --git a/src/components/alerts/alert.jsx b/src/components/alerts/alert.jsx index 4b6bdf493e3..db86bd4b8d6 100644 --- a/src/components/alerts/alert.jsx +++ b/src/components/alerts/alert.jsx @@ -34,14 +34,21 @@ const AlertComponent = ({ className={classNames(styles.alert, styles[level])} > {/* TODO: implement Rtl handling */} - {iconSpinner && ( - - )} - {iconURL && ( - + {(iconSpinner || iconURL) && ( +
+ {iconSpinner && ( + + )} + {iconURL && ( + + )} +
)}
{extensionName ? ( diff --git a/src/components/alerts/alerts.css b/src/components/alerts/alerts.css new file mode 100644 index 00000000000..3b7cf5221a8 --- /dev/null +++ b/src/components/alerts/alerts.css @@ -0,0 +1,4 @@ +.alerts-inner-container { + min-width: 200px; + max-width: 548px; +} diff --git a/src/components/alerts/alerts.jsx b/src/components/alerts/alerts.jsx index 21d6c01c45a..50acd7ce161 100644 --- a/src/components/alerts/alerts.jsx +++ b/src/components/alerts/alerts.jsx @@ -4,6 +4,8 @@ import PropTypes from 'prop-types'; import Box from '../box/box.jsx'; import Alert from '../../containers/alert.jsx'; +import styles from './alerts.css'; + const AlertsComponent = ({ alertsList, className, @@ -13,24 +15,26 @@ const AlertsComponent = ({ bounds="parent" className={className} > - {alertsList.map((a, index) => ( - - ))} + + {alertsList.map((a, index) => ( + + ))} + ); diff --git a/src/components/alerts/inline-message.jsx b/src/components/alerts/inline-message.jsx index 8a7330f7971..981e1984a1c 100644 --- a/src/components/alerts/inline-message.jsx +++ b/src/components/alerts/inline-message.jsx @@ -20,6 +20,7 @@ const InlineMessageComponent = ({ )} {content} diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css index 535c38b0be6..611d901b1da 100644 --- a/src/components/asset-panel/selector.css +++ b/src/components/asset-panel/selector.css @@ -56,7 +56,7 @@ $fade-out-distance: 100px; .list-item { width: 5rem; - min-height: 5rem; + height: 5rem; margin: 0.5rem auto; } @@ -72,6 +72,6 @@ $fade-out-distance: 100px; .list-item.placeholder { - background: black; + background: white; filter: opacity(15%) brightness(0%); } diff --git a/src/components/audio-trimmer/audio-trimmer.css b/src/components/audio-trimmer/audio-trimmer.css index fe276c3819e..63af2a0cb2a 100644 --- a/src/components/audio-trimmer/audio-trimmer.css +++ b/src/components/audio-trimmer/audio-trimmer.css @@ -12,6 +12,9 @@ $hover-scale: 2; left: 0; width: 100%; height: 100%; + + /* Force the browser to paint separately to avoid composite cost with waveform */ + transform: translateZ(0); } .trim-background { @@ -50,8 +53,25 @@ $hover-scale: 2; border: 1px solid $red-tertiary; } +.playhead-container { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + overflow: hidden; +} + .playhead { - border: 1px solid $motion-primary; + /* + Even though playhead is just a line, it is 100% width (the width of the waveform) + so that we can use transform: translateX() using percentages. + */ + width: 100%; + border-left: 1px solid $motion-primary; + border-top: none; + border-bottom: none; + border-right: none; } .start-trim-line { diff --git a/src/components/audio-trimmer/audio-trimmer.jsx b/src/components/audio-trimmer/audio-trimmer.jsx index d1b21c92664..87ca75a9803 100644 --- a/src/components/audio-trimmer/audio-trimmer.jsx +++ b/src/components/audio-trimmer/audio-trimmer.jsx @@ -32,12 +32,14 @@ const AudioTrimmer = props => ( )} {props.playhead ? ( - +
+
+
) : null} {props.trimEnd === null ? null : ( diff --git a/src/components/backpack/backpack.css b/src/components/backpack/backpack.css index 6eddfa905c4..25bc42d45b4 100644 --- a/src/components/backpack/backpack.css +++ b/src/components/backpack/backpack.css @@ -57,7 +57,7 @@ width: 100%; height: 100%; opacity: 0.75; - background-color: #8cbcff; + background-color: $drop-highlight; transition: all 0.25s ease; } @@ -69,8 +69,18 @@ } .backpack-item { - min-width: 4rem; + width: 4rem; + height: 4.5rem; margin: 0 0.25rem; + flex-shrink: 0; + + /* Need to hide overflow because of background setting below */ + overflow: hidden; +} + +.backpack-item > div { + /* Need to set the background to get blend-mode below to work */ + background: $ui-primary; } .backpack-item img { diff --git a/src/components/blocks/blocks.css b/src/components/blocks/blocks.css index e0b07ce26cc..583f587f797 100644 --- a/src/components/blocks/blocks.css +++ b/src/components/blocks/blocks.css @@ -1,5 +1,6 @@ @import "../../css/units.css"; @import "../../css/colors.css"; +@import "../../css/z-index.css"; .blocks { height: 100%; @@ -13,7 +14,7 @@ width: 100%; height: 100%; opacity: 0.75; - background-color: #8cbcff; + background-color: $drop-highlight; transition: all 0.25s ease; } @@ -80,6 +81,7 @@ This does not prevent user interaction on the blocks themselves. */ pointer-events: none; + z-index: $z-index-drag-layer; /* make blocks match gui drag layer */ } /* diff --git a/src/components/browser-modal/browser-modal.css b/src/components/browser-modal/browser-modal.css index a7f86ac4efe..2f4145bf665 100644 --- a/src/components/browser-modal/browser-modal.css +++ b/src/components/browser-modal/browser-modal.css @@ -27,17 +27,23 @@ } .illustration { + display: flex; + align-items: center; + justify-content: center; width: 100%; - height: 208px; - background-color: $motion-primary; - background-image: url('./unsupported.png'); - background-size: cover; + height: 100px; + background-color: $control-primary; } [dir="rtl"] .illustration { transform: scaleX(-1); } +.illustration img { + height: 80%; + width: auto; +} + .body { background: $ui-white; padding: 1.5rem 2.25rem; diff --git a/src/components/browser-modal/browser-modal.jsx b/src/components/browser-modal/browser-modal.jsx index 9afac0e3891..0419e9122d1 100644 --- a/src/components/browser-modal/browser-modal.jsx +++ b/src/components/browser-modal/browser-modal.jsx @@ -5,85 +5,107 @@ import Box from '../box/box.jsx'; import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl'; import styles from './browser-modal.css'; +import unhappyBrowser from './unsupported-browser.svg'; const messages = defineMessages({ label: { id: 'gui.unsupportedBrowser.label', defaultMessage: 'Browser is not supported', description: '' + }, + error: { + id: 'gui.unsupportedBrowser.errorLabel', + defaultMessage: 'An Error Occurred', + description: 'Heading shown when there is an unhandled exception in an unsupported browser' } }); -const BrowserModal = ({intl, ...props}) => ( - -
- +const BrowserModal = ({intl, ...props}) => { + const label = props.error ? messages.error : messages.label; + return ( + +
+ + + + + +

+ +

+

+ { /* eslint-disable max-len */ } + { + props.error ? : + } + { /* eslint-enable max-len */ } +

- -

- -

-

- { /* eslint-disable max-len */ } - - { /* eslint-enable max-len */ } -

+ + - - - +
-
- - - - ) - }} - /> -
- -
-
-); +
+ + ); +}; BrowserModal.propTypes = { + error: PropTypes.bool, intl: intlShape.isRequired, isRtl: PropTypes.bool, onBack: PropTypes.func.isRequired }; +BrowserModal.defaultProps = { + error: false +}; + const WrappedBrowserModal = injectIntl(BrowserModal); WrappedBrowserModal.setAppElement = ReactModal.setAppElement; diff --git a/src/components/browser-modal/unsupported-browser.svg b/src/components/browser-modal/unsupported-browser.svg new file mode 100644 index 00000000000..7749f7539b3 --- /dev/null +++ b/src/components/browser-modal/unsupported-browser.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/cards/cards.jsx b/src/components/cards/cards.jsx index 129d2ca5714..f137c91a003 100644 --- a/src/components/cards/cards.jsx +++ b/src/components/cards/cards.jsx @@ -234,7 +234,11 @@ const Cards = props => { if (x === 0 && y === 0) { // initialize positions x = isRtl ? -292 : 292; - y = 365; + // The tallest cards are about 385px high, and the default position is pinned + // to near the bottom of the blocks palette to allow room to work above. + const tallCardHeight = 385; + const bottomMargin = 60; // To avoid overlapping the backpack region + y = window.innerHeight - tallCardHeight - bottomMargin; } const steps = content[activeDeckId].steps; diff --git a/src/components/context-menu/context-menu.css b/src/components/context-menu/context-menu.css index 5a02ce797d9..84e2699f0a1 100644 --- a/src/components/context-menu/context-menu.css +++ b/src/components/context-menu/context-menu.css @@ -28,3 +28,11 @@ background: $motion-primary; color: white; } + +.menu-item-bordered { + border-top: 1px solid $ui-black-transparent; +} + +.menu-item-bordered:hover { + background: $error-primary; +} diff --git a/src/components/context-menu/context-menu.jsx b/src/components/context-menu/context-menu.jsx index 7d33fe641ef..677f8944190 100644 --- a/src/components/context-menu/context-menu.jsx +++ b/src/components/context-menu/context-menu.jsx @@ -1,5 +1,6 @@ import React from 'react'; import {ContextMenu, MenuItem} from 'react-contextmenu'; +import classNames from 'classnames'; import styles from './context-menu.css'; @@ -17,7 +18,16 @@ const StyledMenuItem = props => ( /> ); +const BorderedMenuItem = props => ( + +); + + export { + BorderedMenuItem, StyledContextMenu as ContextMenu, StyledMenuItem as MenuItem }; diff --git a/src/components/crash-message/crash-message.jsx b/src/components/crash-message/crash-message.jsx index 4fe8cc0ec3e..c3732ddc0cb 100644 --- a/src/components/crash-message/crash-message.jsx +++ b/src/components/crash-message/crash-message.jsx @@ -29,6 +29,18 @@ const CrashMessage = props => ( id="gui.crashMessage.description" />

+ {props.eventId && ( +

+ +

+ )} -
- {props.hasValidationError ? - -

- -

-
: null - } - - - - - - - - ) - }} - /> - - -
- -); - -ImportModal.propTypes = { - errorMessage: PropTypes.string.isRequired, - hasValidationError: PropTypes.bool.isRequired, - inputValue: PropTypes.string.isRequired, - intl: intlShape.isRequired, - isRtl: PropTypes.bool, - onCancel: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - onGoBack: PropTypes.func.isRequired, - onKeyPress: PropTypes.func.isRequired, - onViewProject: PropTypes.func.isRequired, - placeholder: PropTypes.string -}; - -export default injectIntl(ImportModal); diff --git a/src/components/library-item/library-item.css b/src/components/library-item/library-item.css index 55edcc2e33e..692e1b61278 100644 --- a/src/components/library-item/library-item.css +++ b/src/components/library-item/library-item.css @@ -8,6 +8,7 @@ justify-content: flex-start; flex-basis: 160px; height: 160px; + max-width: 160px; margin: $space; padding: 1rem 1rem 0 1rem; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/src/components/library-item/library-item.jsx b/src/components/library-item/library-item.jsx index 03a9d6b7886..3880a4cf613 100644 --- a/src/components/library-item/library-item.jsx +++ b/src/components/library-item/library-item.jsx @@ -1,4 +1,3 @@ -import bindAll from 'lodash.bindall'; import {FormattedMessage} from 'react-intl'; import PropTypes from 'prop-types'; import React from 'react'; @@ -10,42 +9,8 @@ import classNames from 'classnames'; import bluetoothIconURL from './bluetooth.svg'; import internetConnectionIconURL from './internet-connection.svg'; -class LibraryItem extends React.PureComponent { - constructor (props) { - super(props); - bindAll(this, [ - 'handleBlur', - 'handleClick', - 'handleFocus', - 'handleKeyPress', - 'handleMouseEnter', - 'handleMouseLeave' - ]); - } - handleBlur () { - this.props.onBlur(this.props.id); - } - handleFocus () { - this.props.onFocus(this.props.id); - } - handleClick (e) { - if (!this.props.disabled) { - this.props.onSelect(this.props.id); - } - e.preventDefault(); - } - handleKeyPress (e) { - if (e.key === ' ' || e.key === 'Enter') { - e.preventDefault(); - this.props.onSelect(this.props.id); - } - } - handleMouseEnter () { - this.props.onMouseEnter(this.props.id); - } - handleMouseLeave () { - this.props.onMouseLeave(this.props.id); - } +/* eslint-disable react/prefer-stateless-function */ +class LibraryItemComponent extends React.PureComponent { render () { return this.props.featured ? (
{this.props.disabled ? ( @@ -146,12 +111,12 @@ class LibraryItem extends React.PureComponent { )} role="button" tabIndex="0" - onBlur={this.handleBlur} - onClick={this.handleClick} - onFocus={this.handleFocus} - onKeyPress={this.handleKeyPress} - onMouseEnter={this.handleMouseEnter} - onMouseLeave={this.handleMouseLeave} + onBlur={this.props.onBlur} + onClick={this.props.onClick} + onFocus={this.props.onFocus} + onKeyPress={this.props.onKeyPress} + onMouseEnter={this.props.onMouseEnter} + onMouseLeave={this.props.onMouseLeave} > {/* Layers of wrapping is to prevent layout thrashing on animation */} @@ -167,8 +132,10 @@ class LibraryItem extends React.PureComponent { ); } } +/* eslint-enable react/prefer-stateless-function */ + -LibraryItem.propTypes = { +LibraryItemComponent.propTypes = { bluetoothRequired: PropTypes.bool, collaborator: PropTypes.string, description: PropTypes.oneOfType([ @@ -180,22 +147,22 @@ LibraryItem.propTypes = { featured: PropTypes.bool, hidden: PropTypes.bool, iconURL: PropTypes.string, - id: PropTypes.number.isRequired, insetIconURL: PropTypes.string, internetConnectionRequired: PropTypes.bool, name: PropTypes.oneOfType([ PropTypes.string, PropTypes.node ]), - onBlur: PropTypes.func, - onFocus: PropTypes.func, + onBlur: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired, + onKeyPress: PropTypes.func.isRequired, onMouseEnter: PropTypes.func.isRequired, - onMouseLeave: PropTypes.func.isRequired, - onSelect: PropTypes.func.isRequired + onMouseLeave: PropTypes.func.isRequired }; -LibraryItem.defaultProps = { +LibraryItemComponent.defaultProps = { disabled: false }; -export default LibraryItem; +export default LibraryItemComponent; diff --git a/src/components/library/library.jsx b/src/components/library/library.jsx index 8ecb76181a2..8073b0f4928 100644 --- a/src/components/library/library.jsx +++ b/src/components/library/library.jsx @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {defineMessages, injectIntl, intlShape} from 'react-intl'; -import LibraryItem from '../library-item/library-item.jsx'; +import LibraryItem from '../../containers/library-item.jsx'; import Modal from '../../containers/modal.jsx'; import Divider from '../divider/divider.jsx'; import Filter from '../filter/filter.jsx'; @@ -33,11 +33,9 @@ class LibraryComponent extends React.Component { constructor (props) { super(props); bindAll(this, [ - 'handleBlur', 'handleClose', 'handleFilterChange', 'handleFilterClear', - 'handleFocus', 'handleMouseEnter', 'handleMouseLeave', 'handleSelect', @@ -56,12 +54,6 @@ class LibraryComponent extends React.Component { this.scrollToTop(); } } - handleBlur (id) { - this.handleMouseLeave(id); - } - handleFocus (id) { - this.handleMouseEnter(id); - } handleSelect (id) { this.handleClose(); this.props.onItemSelected(this.getFilteredData()[id]); @@ -172,33 +164,28 @@ class LibraryComponent extends React.Component { })} ref={this.setFilteredDataRef} > - {this.getFilteredData().map((dataItem, index) => { - const scratchURL = dataItem.md5 ? - `https://cdn.assets.scratch.mit.edu/internalapi/asset/${dataItem.md5}/get/` : - dataItem.rawURL; - return ( -
); diff --git a/src/components/loader/loader.css b/src/components/loader/loader.css index 1b6ab40ce65..b57aa4c3aa2 100644 --- a/src/components/loader/loader.css +++ b/src/components/loader/loader.css @@ -2,7 +2,7 @@ @import "../../css/z-index.css"; .background { - position: fixed; + position: absolute; top: 0; left: 0; width: 100%; @@ -17,6 +17,13 @@ color: white; } +.fullscreen { + /* Break out of the layout using position: fixed to cover the whole screen */ + position: fixed; + /* Use the fullscreen stage z-index to allow covering full-screen mode */ + z-index: $z-index-stage-wrapper-overlay; +} + .block-animation { width: 125px; height: 150px; diff --git a/src/components/loader/loader.jsx b/src/components/loader/loader.jsx index e06b8bb35f6..c51621fc41a 100644 --- a/src/components/loader/loader.jsx +++ b/src/components/loader/loader.jsx @@ -1,5 +1,6 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; +import classNames from 'classnames'; import styles from './loader.css'; import PropTypes from 'prop-types'; @@ -119,15 +120,13 @@ class LoaderComponent extends React.Component { constructor (props) { super(props); this.state = { - messageNumber: 0 + messageNumber: this.chooseRandomMessage() }; } componentDidMount () { - this.chooseRandomMessage(); - // Start an interval to choose a new message every 5 seconds this.intervalId = setInterval(() => { - this.chooseRandomMessage(); + this.setState({messageNumber: this.chooseRandomMessage()}); }, 5000); } componentWillUnmount () { @@ -144,11 +143,15 @@ class LoaderComponent extends React.Component { break; } } - this.setState({messageNumber}); + return messageNumber; } render () { return ( -
+
- - Scratch - + Scratch
- - {this.props.canSave ? ( - - {saveNowMessage} - - ) : []} - {this.props.canCreateCopy ? ( - - {createCopyMessage} - - ) : []} - {this.props.canRemix ? ( - - {remixMessage} - - ) : []} - + {(this.props.canSave || this.props.canCreateCopy || this.props.canRemix) && ( + + {this.props.canSave ? ( + + {saveNowMessage} + + ) : []} + {this.props.canCreateCopy ? ( + + {createCopyMessage} + + ) : []} + {this.props.canRemix ? ( + + {remixMessage} + + ) : []} + + )} {(className, renderFileInput, loadProject) => ( @@ -715,6 +735,7 @@ MenuBar.propTypes = { onClickFile: PropTypes.func, onClickLanguage: PropTypes.func, onClickLogin: PropTypes.func, + onClickLogo: PropTypes.func, onClickNew: PropTypes.func, onClickRemix: PropTypes.func, onClickSave: PropTypes.func, @@ -731,6 +752,7 @@ MenuBar.propTypes = { onShare: PropTypes.func, onToggleLoginOpen: PropTypes.func, onUpdateProjectTitle: PropTypes.func, + projectChanged: PropTypes.bool, projectTitle: PropTypes.string, renderLogin: PropTypes.func, sessionExists: PropTypes.bool, @@ -754,6 +776,7 @@ const mapStateToProps = state => { isShowingProject: getIsShowingProject(loadingState), languageMenuOpen: languageMenuOpen(state), loginMenuOpen: loginMenuOpen(state), + projectChanged: state.scratchGui.projectChanged, projectTitle: state.scratchGui.projectTitle, sessionExists: state.session && typeof state.session.session !== 'undefined', username: user ? user.username : null diff --git a/src/components/meter/meter.css b/src/components/meter/meter.css index c16e920cd73..953f3641b75 100644 --- a/src/components/meter/meter.css +++ b/src/components/meter/meter.css @@ -1,3 +1,5 @@ +@import "../../css/colors.css"; + .green { fill: rgb(171, 220, 170); stroke: rgb(174, 211, 168); @@ -12,3 +14,19 @@ fill: rgb(251, 194, 142); stroke: rgb(235, 189, 142); } + +.mask-container { + position: relative; +} + +.mask { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + transform-origin: top; + will-change: transform; + background: $ui-primary; + opacity: 0.75; +} diff --git a/src/components/meter/meter.jsx b/src/components/meter/meter.jsx index 2d16ce681da..4f071191689 100644 --- a/src/components/meter/meter.jsx +++ b/src/components/meter/meter.jsx @@ -20,36 +20,40 @@ const Meter = props => { const barHeight = (height - (barSpacing * (nBars + 1))) / nBars; const nBarsToMask = nBars - Math.floor(level * nBars); + const scale = ((nBarsToMask * (barHeight + barSpacing)) + (barSpacing / 2)) / height; return ( - - {Array(nBars).fill(0) - .map((value, index) => ( - - ))} - + {Array(nBars).fill(0) + .map((value, index) => ( + + ))} + +
- +
); }; diff --git a/src/components/monitor-list/monitor-list.jsx b/src/components/monitor-list/monitor-list.jsx index f7b02080fdf..e4da715b0b4 100644 --- a/src/components/monitor-list/monitor-list.jsx +++ b/src/components/monitor-list/monitor-list.jsx @@ -27,6 +27,7 @@ const MonitorList = props => ( draggable={props.draggable} height={monitorData.height} id={monitorData.id} + isDiscrete={monitorData.isDiscrete} key={monitorData.id} max={monitorData.sliderMax} min={monitorData.sliderMin} diff --git a/src/components/monitor/monitor.jsx b/src/components/monitor/monitor.jsx index 7e6156ff005..22134327593 100644 --- a/src/components/monitor/monitor.jsx +++ b/src/components/monitor/monitor.jsx @@ -54,35 +54,52 @@ const MonitorComponent = props => ( })} - {props.mode === 'list' ? null : ReactDOM.createPortal(( + {ReactDOM.createPortal(( // Use a portal to render the context menu outside the flow to avoid // positioning conflicts between the monitors `transform: scale` and // the context menus `position: fixed`. For more details, see // http://meyerweb.com/eric/thoughts/2011/09/12/un-fixing-fixed-elements-with-css-transforms/ - - - - - - - {props.onSetModeToSlider ? ( + {props.onSetModeToDefault && + + + } + {props.onSetModeToLarge && + + + } + {props.onSetModeToSlider && - - ) : null} + } + {props.onImport && + + + } + {props.onExport && + + + } ), document.body)} @@ -100,9 +117,11 @@ MonitorComponent.propTypes = { label: PropTypes.string.isRequired, mode: PropTypes.oneOf(monitorModes), onDragEnd: PropTypes.func.isRequired, + onExport: PropTypes.func, + onImport: PropTypes.func, onNextMode: PropTypes.func.isRequired, - onSetModeToDefault: PropTypes.func.isRequired, - onSetModeToLarge: PropTypes.func.isRequired, + onSetModeToDefault: PropTypes.func, + onSetModeToLarge: PropTypes.func, onSetModeToSlider: PropTypes.func }; diff --git a/src/components/monitor/slider-monitor.jsx b/src/components/monitor/slider-monitor.jsx index b9cebfa140a..0271b140a03 100644 --- a/src/components/monitor/slider-monitor.jsx +++ b/src/components/monitor/slider-monitor.jsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import styles from './monitor.css'; -const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate}) => ( +const SliderMonitor = ({categoryColor, isDiscrete, label, min, max, value, onSliderUpdate}) => (
@@ -22,6 +22,7 @@ const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate}) className={classNames(styles.slider, 'no-drag')} // Class used on parent Draggable to prevent drags max={max} min={min} + step={isDiscrete ? 1 : 0.01} type="range" value={value} onChange={onSliderUpdate} @@ -33,6 +34,7 @@ const SliderMonitor = ({categoryColor, label, min, max, value, onSliderUpdate}) SliderMonitor.propTypes = { categoryColor: PropTypes.string.isRequired, + isDiscrete: PropTypes.bool, label: PropTypes.string.isRequired, max: PropTypes.number, min: PropTypes.number, @@ -44,6 +46,7 @@ SliderMonitor.propTypes = { }; SliderMonitor.defaultProps = { + isDiscrete: true, min: 0, max: 100 }; diff --git a/src/components/preview-modal/happy-cat.png b/src/components/preview-modal/happy-cat.png deleted file mode 100644 index 764761eea936c6488a8255433a83b75e8426cb08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3055 zcmV3{e8H0!2c$ma^0|Pc-Ojzv5 zIIe9BX>0>^Q&mkRNYkptb*id%l-5L5W8#296fdn%D!>)>7DH+qY+@S&!?3yo1F^$A zW}kWMeRnzi<2?oj#w;^#t4JK_rzhQamhbm>e%m=mNGbV!ZVJEW?HYk=1g;UdL~C4| zs5`JW@w>+75)1yXfgMgtSHKv+)Z`z~a3KT^!vQh;=N=WB?@{W(tBk1u7C{{JXh#`y^iaF5A{A?T2v+;9n z$lMv6?q;O#5Hb}kQy>I*KAbq?p&$vGV3{f>+vUwu{vx@5NCXJ)rwXzWD^2b zyB-f}s=)OmmM(F9pFS}PZPZM3JyP5aH$PA-0uXTi$`ZgY{hp2C!CI7HEnfCCJ+1Gf zDc$IbL{lZIs^S(duLNLQAE+X1%Pi4w3`dJyToGL ztOtvZEhWB$6UW*JcJHG3gSQwrZ8m|@k$HQMC7!2}xR~I2S&ZO#IJl~<9hw$_=47KmGVG-!uSXma(=o4l=u`rca+vI2=Y%6g=r}0S<`?ft7B5~*ZEY=~VkA^Js86k@R$h3HM-c3{7_aL( zRioCn%?AS3r z{^&!F96o$5Dx{8n;e>Uu6rNYiPL2Eb^YKR?a`fmClF1}eN~DxTV{x2`tNHA!n?VaA z1YF7wE=j<##*&--1ESFg@koeycg>`&tqsR<7(ad-D_1P1A|B_-KYkJ^CBE+?I(L8J zgp;d!>FVmj_kA9F{4pxxaaOKa&iL`;i#f+}NW^2rBOxM@D6O}?fhZ=E(N|pyb#AQu zyUtm^`F=beC*Iczy~T+3>h8VVasMOK)YPCT%6W-OlFeqZEQ{Zr*vIva_Ya(JZWHD4 za<**Q!n}F&h(sc1!42dWaa|V> z7>0pR6u!4%6Si&h{F>F&*48pu0d)HGX;f7m@Hw4MvvAQu0II60sH&>!|F7lLah9!l z4%0NzbRExiDHI9_B#Nq0uyZ*@)eTiujYW&@a|^b;WXG?!ZyrEk$FH})3G}aebk7?f z#zx*ewIZOSv@{K<#(w_#e~}B84XWj3%a-!!lBEO!0Sv#w{-o{byS!UgJ z7c*wgptiP#2@@s^yp%f7#8Ye5;5aUx=TTi<%}>|AKsX#8(C@wXb`lPU*|TR4TerSS zFc?HDCAkYsH~#Ox+|+Z$1m@3Q_*kqGmSPtinhqW+9wmi9 zEBQcAZy&N00M05)2tgMmQ9r zvZ9=k*Imc1ojdy@(}m5pZ8DiG*=&wUw=aAMVEc}@zcr*o{gDH^8bk5AuSTO$qNO_M zY|G_xJn{H)+S}WUmEY{yEP3=15;Zl&@OajYeV@E-vvsA8b@S&z`Bo?>A;V|J)2wm6v|J(b?UY>sX`((4( ztGW}atE=PLA3RGck+;i`+uT}a<9NgC)idI%ymPIgVVFd#H zS$N;xef;o;|44g#I~EFC$|hlyl92>FDQU5Dd^>vrfcpA+{$|ZzQ(IeGjBKt;E}tix z&5_M!ak@VJj~i~C|G<^t5?Av|dFnlVJH2huxH+rex>8JHD9@~i z5AHc*Smo7*VPF_Knx>=c8c47UHmPI>z3W~<@5_J?=)e3=j3$9}VExo6gV3oSGrRj(8j!;6%jkMEomg`VSj x>fpNZb5_0iWvwZ$z0_YLaE-t<0{^e=e*jg18^(ZpKhOXG002ovPDHLkV1lv9;#~j$ diff --git a/src/components/preview-modal/preview-modal.jsx b/src/components/preview-modal/preview-modal.jsx deleted file mode 100644 index babfc0a275c..00000000000 --- a/src/components/preview-modal/preview-modal.jsx +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ReactModal from 'react-modal'; -import Box from '../box/box.jsx'; -import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl'; - -import styles from './preview-modal.css'; -import catIcon from './happy-cat.png'; - -const messages = defineMessages({ - label: { - id: 'gui.previewInfo.label', - defaultMessage: 'Try Scratch 3.0', - description: 'Scratch 3.0 modal label - for accessibility' - }, - previewWelcome: { - defaultMessage: 'Welcome to the Scratch 3.0 Beta', - description: 'Header for Preview Info Modal', - id: 'gui.previewInfo.welcome' - }, - notNowTooltip: { - defaultMessage: 'Not Now', - description: 'Tooltip for Not Now button', - id: 'gui.previewModal.notnowtooltip' - }, - tryItTooltip: { - defaultMessage: 'Try It', - description: 'Tooltip for Try It button', - id: 'gui.previewModal.tryittooltip' - }, - viewProjectTooltip: { - defaultMessage: 'View 2.0 Project', - description: 'Tooltip for View 2.0 Project button', - id: 'gui.previewModal.viewprojecttooltip' - } -}); - -const PreviewModal = ({intl, ...props}) => ( - -
- - - -

- -

-

- { /* eslint-disable max-len */ } - - { /* eslint-enable max-len */ } -

- - - - - - - - - - - ) - }} - /> - -
-
-
-); - -PreviewModal.propTypes = { - intl: intlShape.isRequired, - isRtl: PropTypes.bool, - onCancel: PropTypes.func.isRequired, - onTryIt: PropTypes.func.isRequired, - onViewProject: PropTypes.func.isRequired -}; - -export default injectIntl(PreviewModal); diff --git a/src/components/preview-modal/welcome.png b/src/components/preview-modal/welcome.png deleted file mode 100644 index 1172df932b425ae538bcfe2d23430373310e325e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 104787 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 vl?y|Q ( @@ -137,16 +138,17 @@ const PromptComponent = props => ( PromptComponent.propTypes = { canAddCloudVariable: PropTypes.bool.isRequired, cloudSelected: PropTypes.bool.isRequired, + defaultValue: PropTypes.string, globalSelected: PropTypes.bool.isRequired, isStage: PropTypes.bool.isRequired, label: PropTypes.string.isRequired, onCancel: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, onCloudVarOptionChange: PropTypes.func, + onFocus: PropTypes.func.isRequired, onKeyPress: PropTypes.func.isRequired, onOk: PropTypes.func.isRequired, onScopeOptionSelection: PropTypes.func.isRequired, - placeholder: PropTypes.string, showCloudOption: PropTypes.bool.isRequired, showVariableOptions: PropTypes.bool.isRequired, title: PropTypes.string.isRequired diff --git a/src/components/sound-editor/sound-editor.css b/src/components/sound-editor/sound-editor.css index 4a0e03655da..279875fc8dc 100644 --- a/src/components/sound-editor/sound-editor.css +++ b/src/components/sound-editor/sound-editor.css @@ -79,6 +79,7 @@ $border-radius: 0.25rem; font-size: 0.85rem; transition: 0.2s; user-select: none; + margin: 0px; } .button > img { diff --git a/src/components/spinner/spinner.css b/src/components/spinner/spinner.css index a20e79b1371..3abc8ffbbea 100644 --- a/src/components/spinner/spinner.css +++ b/src/components/spinner/spinner.css @@ -1,19 +1,20 @@ @import "../../css/colors.css"; .spinner { - width: 1rem; - height: 1rem; + width: 1.25rem; + height: 1.25rem; display: inline-block; position: relative; border-radius: 50%; border-width: .1875rem; border-style: solid; border-color: $ui-white-transparent; + box-sizing: content-box; } .spinner::before, .spinner::after { - width: 1rem; - height: 1rem; + width: 1.25rem; + height: 1.25rem; content: ''; border-radius: 50%; display: block; @@ -48,17 +49,25 @@ } } -.spinner.orange { +.spinner.success { + border-color: $extensions-transparent; +} + +.spinner.success::after { + border-top-color: $extensions-primary; +} + +.spinner.warn { border-color: $error-transparent; } -.spinner.orange::after { +.spinner.warn::after { border-top-color: $error-primary; } -.spinner.white { +.spinner.info { border-color: $ui-white-transparent; } -.spinner.white::after { +.spinner.info::after { border-top-color: $ui-white; } diff --git a/src/components/spinner/spinner.jsx b/src/components/spinner/spinner.jsx index bb82fbf51c6..f25e3ebb187 100644 --- a/src/components/spinner/spinner.jsx +++ b/src/components/spinner/spinner.jsx @@ -7,6 +7,7 @@ import styles from './spinner.css'; const SpinnerComponent = function (props) { const { className, + level, small } = props; return ( @@ -14,6 +15,7 @@ const SpinnerComponent = function (props) { className={classNames( className, styles.spinner, + styles[level], {[styles.small]: small} )} /> @@ -21,9 +23,11 @@ const SpinnerComponent = function (props) { }; SpinnerComponent.propTypes = { className: PropTypes.string, + level: PropTypes.string, small: PropTypes.bool }; SpinnerComponent.defaultProps = { - className: '' + className: '', + level: 'info' }; export default SpinnerComponent; diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx index e9090bfe387..09c94d0162a 100644 --- a/src/components/sprite-info/sprite-info.jsx +++ b/src/components/sprite-info/sprite-info.jsx @@ -34,14 +34,15 @@ class SpriteInfo extends React.Component { shouldComponentUpdate (nextProps) { return ( this.props.rotationStyle !== nextProps.rotationStyle || - this.props.direction !== nextProps.direction || this.props.disabled !== nextProps.disabled || this.props.name !== nextProps.name || - this.props.size !== nextProps.size || this.props.stageSize !== nextProps.stageSize || this.props.visible !== nextProps.visible || - this.props.x !== nextProps.x || - this.props.y !== nextProps.y + // Only update these if rounded value has changed + Math.round(this.props.direction) !== Math.round(nextProps.direction) || + Math.round(this.props.size) !== Math.round(nextProps.size) || + Math.round(this.props.x) !== Math.round(nextProps.x) || + Math.round(this.props.y) !== Math.round(nextProps.y) ); } render () { @@ -110,7 +111,7 @@ class SpriteInfo extends React.Component { placeholder="x" tabIndex="0" type="text" - value={this.props.disabled ? '' : this.props.x} + value={this.props.disabled ? '' : Math.round(this.props.x)} onSubmit={this.props.onChangeX} /> @@ -137,7 +138,7 @@ class SpriteInfo extends React.Component { placeholder="y" tabIndex="0" type="text" - value={this.props.disabled ? '' : this.props.y} + value={this.props.disabled ? '' : Math.round(this.props.y)} onSubmit={this.props.onChangeY} /> @@ -237,14 +238,14 @@ class SpriteInfo extends React.Component { label={sizeLabel} tabIndex="0" type="text" - value={this.props.disabled ? '' : this.props.size} + value={this.props.disabled ? '' : Math.round(this.props.size)} onSubmit={this.props.onChangeSize} />

( disable={props.dragging} id={`${props.name}-${contextMenuId}`} > - {(props.selected && props.onDeleteButtonClick) ? ( - - ) : null } {typeof props.number === 'undefined' ? null : (
{props.number}
)} {props.costumeURL ? ( - +
+
+ +
+
) : null}
{props.name}
@@ -49,6 +46,13 @@ const SpriteSelectorItem = props => (
{props.details}
) : null}
+ {(props.selected && props.onDeleteButtonClick) ? ( + + ) : null } {props.onDuplicateButtonClick || props.onDeleteButtonClick || props.onExportButtonClick ? ( {props.onDuplicateButtonClick ? ( @@ -60,15 +64,6 @@ const SpriteSelectorItem = props => ( /> ) : null} - {props.onDeleteButtonClick ? ( - - - - ) : null } {props.onExportButtonClick ? ( ( /> ) : null } + {props.onDeleteButtonClick ? ( + + + + ) : null } ) : null} diff --git a/src/components/sprite-selector/sprite-list.jsx b/src/components/sprite-selector/sprite-list.jsx index 17ba2dc2153..e89898e47ce 100644 --- a/src/components/sprite-selector/sprite-list.jsx +++ b/src/components/sprite-selector/sprite-list.jsx @@ -8,9 +8,12 @@ import Box from '../box/box.jsx'; import SpriteSelectorItem from '../../containers/sprite-selector-item.jsx'; import SortableHOC from '../../lib/sortable-hoc.jsx'; import SortableAsset from '../asset-panel/sortable-asset.jsx'; +import ThrottledPropertyHOC from '../../lib/throttled-property-hoc.jsx'; import styles from './sprite-selector.css'; +const ThrottledSpriteSelectorItem = ThrottledPropertyHOC('asset', 500)(SpriteSelectorItem); + const SpriteList = function (props) { const { containerRef, @@ -34,62 +37,68 @@ const SpriteList = function (props) { return ( - {items.map((sprite, index) => { + + {items.map((sprite, index) => { - // If the sprite has just received a block drop, used for green highlight - const receivedBlocks = ( - hoveredTarget.sprite === sprite.id && + // If the sprite has just received a block drop, used for green highlight + const receivedBlocks = ( + hoveredTarget.sprite === sprite.id && sprite.id !== editingTarget && hoveredTarget.receivedBlocks - ); + ); - // If the sprite is indicating it can receive block dropping, used for blue highlight - let isRaised = !receivedBlocks && raised && sprite.id !== editingTarget; + // If the sprite is indicating it can receive block dropping, used for blue highlight + let isRaised = !receivedBlocks && raised && sprite.id !== editingTarget; - // A sprite is also raised if a costume or sound is being dragged. - // Note the absence of the self-sharing check: a sprite can share assets with itself. - // This is a quirk of 2.0, but seems worth leaving possible, it - // allows quick (albeit unusual) duplication of assets. - isRaised = isRaised || [ - DragConstants.COSTUME, - DragConstants.SOUND, - DragConstants.BACKPACK_COSTUME, - DragConstants.BACKPACK_SOUND, - DragConstants.BACKPACK_CODE].includes(draggingType); + // A sprite is also raised if a costume or sound is being dragged. + // Note the absence of the self-sharing check: a sprite can share assets with itself. + // This is a quirk of 2.0, but seems worth leaving possible, it + // allows quick (albeit unusual) duplication of assets. + isRaised = isRaised || [ + DragConstants.COSTUME, + DragConstants.SOUND, + DragConstants.BACKPACK_COSTUME, + DragConstants.BACKPACK_SOUND, + DragConstants.BACKPACK_CODE].includes(draggingType); - return ( - - - - ); - })} + return ( + + + + ); + })} + ); }; diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css index da89efab043..ffade4750e3 100644 --- a/src/components/sprite-selector/sprite-selector.css +++ b/src/components/sprite-selector/sprite-selector.css @@ -31,6 +31,7 @@ */ box-sizing: border-box; width: calc((100% / $sprites-per-row ) - $space); + max-width: 6rem; min-width: 4rem; min-height: 4rem; /* @todo: calc height same as width */ margin: calc($space / 2); @@ -54,6 +55,10 @@ overflow-y: auto; } +.scroll-wrapper-dragging { + background-color: $drop-highlight; +} + .items-wrapper { display: flex; flex-wrap: wrap; @@ -79,12 +84,12 @@ } .raised { - background-color: #8cbcff; + background-color: $drop-highlight; transition: all 0.25s ease; } .raised:hover { - background-color: #8cbcff; + background-color: $drop-highlight; transform: scale(1.05); } @@ -93,7 +98,7 @@ animation-duration: 500ms; animation-iteration-count: 1; animation-timing-function: ease-in-out; - background-color: #8cbcff; + background-color: $drop-highlight; } @keyframes wiggle { diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx index 981e44b0fe3..08e18844eff 100644 --- a/src/components/sprite-selector/sprite-selector.jsx +++ b/src/components/sprite-selector/sprite-selector.jsx @@ -100,20 +100,18 @@ const SpriteSelectorComponent = function (props) { onChangeY={onChangeSpriteY} /> - - sprites[id])} - raised={raised} - selectedId={selectedId} - onDeleteSprite={onDeleteSprite} - onDrop={onDrop} - onDuplicateSprite={onDuplicateSprite} - onExportSprite={onExportSprite} - onSelectSprite={onSelectSprite} - /> - + sprites[id])} + raised={raised} + selectedId={selectedId} + onDeleteSprite={onDeleteSprite} + onDrop={onDrop} + onDuplicateSprite={onDuplicateSprite} + onExportSprite={onExportSprite} + onSelectSprite={onSelectSprite} + /> { title: intl.formatMessage(messages.addBackdropFromFile), img: fileUploadIcon, onClick: onBackdropFileUploadClick, - fileAccept: '.svg, .png, .jpg, .jpeg', // Bitmap coming soon + fileAccept: '.svg, .png, .jpg, .jpeg, .gif', fileChange: onBackdropFileUpload, - fileInput: fileInputRef + fileInput: fileInputRef, + fileMultiple: true }, { title: intl.formatMessage(messages.addBackdropFromSurprise), img: surpriseIcon, diff --git a/src/components/stage-wrapper/stage-wrapper.jsx b/src/components/stage-wrapper/stage-wrapper.jsx index 021f6742ba6..77e9f55953b 100644 --- a/src/components/stage-wrapper/stage-wrapper.jsx +++ b/src/components/stage-wrapper/stage-wrapper.jsx @@ -6,13 +6,16 @@ import Box from '../box/box.jsx'; import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js'; import StageHeader from '../../containers/stage-header.jsx'; import Stage from '../../containers/stage.jsx'; +import Loader from '../loader/loader.jsx'; import styles from './stage-wrapper.css'; const StageWrapperComponent = function (props) { const { + isFullScreen, isRtl, isRendererSupported, + loading, stageSize, vm } = props; @@ -38,13 +41,18 @@ const StageWrapperComponent = function (props) { null } + {loading ? ( + + ) : null} ); }; StageWrapperComponent.propTypes = { + isFullScreen: PropTypes.bool, isRendererSupported: PropTypes.bool.isRequired, - isRtl: PropTypes.bool, + isRtl: PropTypes.bool.isRequired, + loading: PropTypes.bool, stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired, vm: PropTypes.instanceOf(VM).isRequired }; diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css index 7e0a59c2e78..19496c09bcf 100644 --- a/src/components/stage/stage.css +++ b/src/components/stage/stage.css @@ -9,9 +9,9 @@ */ display: block; - /* Attach border radius directly to canvas to prevent needing overflow:hidden; */ border-radius: $space; border: $stage-standard-border-width solid $ui-black-transparent; + overflow: hidden; /* @todo: This is for overriding the value being set somewhere. Where is it being set? */ background-color: transparent; @@ -128,6 +128,7 @@ to adjust for the border using a different method */ } .question-wrapper { + z-index: $z-index-stage-question; pointer-events: auto; } @@ -147,6 +148,8 @@ to adjust for the border using a different method */ align-items: center; background: rgba(0,0,0,0.25); border-radius: 0.5rem; + pointer-events: all; + cursor: pointer; } .green-flag-overlay { @@ -154,8 +157,6 @@ to adjust for the border using a different method */ border-radius: 100%; background: rgba(255,255,255,0.75); border: 3px solid $ui-white; - pointer-events: all; - cursor: pointer; display: flex; justify-content: center; align-items: center; diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx index f87a39b791c..cf81f4ec2c8 100644 --- a/src/components/stage/stage.jsx +++ b/src/components/stage/stage.jsx @@ -48,18 +48,25 @@ const StageComponent = props => { }} onDoubleClick={onDoubleClick} > - + > + + { /> {isStarted ? null : ( - - - + )} {isColorPicking && colorInfo ? ( diff --git a/src/components/stop-all/stop-all.css b/src/components/stop-all/stop-all.css index b2d878582f4..da0bcc7b315 100644 --- a/src/components/stop-all/stop-all.css +++ b/src/components/stop-all/stop-all.css @@ -1,3 +1,5 @@ +@import "../../css/colors.css"; + .stop-all { width: 2rem; height: 2rem; @@ -5,15 +7,14 @@ border-radius: 0.25rem; user-select: none; cursor: pointer; - transition: 0.2s ease-out; } -.stop-all { - opacity: 0.5; +.stop-all:hover { + background-color: $motion-light-transparent; } -.stop-all:hover { - transform: scale(1.2); +.stop-all { + opacity: 0.5; } .stop-all.is-active { diff --git a/src/components/telemetry-modal/telemetry-modal-header.png b/src/components/telemetry-modal/telemetry-modal-header.png new file mode 100644 index 0000000000000000000000000000000000000000..01faec7298dc22c0d8abadc22f3969cecabfecf9 GIT binary patch literal 9806 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 +
+ + + +

+

+ + ) + }} + />

+ + + + +
+
+ ); + } +} + +TelemetryModal.propTypes = { + intl: intlShape.isRequired, + isRtl: PropTypes.bool, + onCancel: PropTypes.func, + onOptIn: PropTypes.func.isRequired, + onOptOut: PropTypes.func.isRequired, + onRequestClose: PropTypes.func +}; + +export default injectIntl(TelemetryModal); diff --git a/src/components/waveform/waveform.jsx b/src/components/waveform/waveform.jsx index 1b2abe96ebf..48a0a4935c3 100644 --- a/src/components/waveform/waveform.jsx +++ b/src/components/waveform/waveform.jsx @@ -2,52 +2,65 @@ import React from 'react'; import PropTypes from 'prop-types'; import styles from './waveform.css'; -const Waveform = props => { - const { - width, - height, - data - } = props; - - const cappedData = [0, ...data, 0]; - - const points = [ - ...cappedData.map((v, i) => - [width * i / cappedData.length, height * v / 2] - ), - ...cappedData.reverse().map((v, i) => - [width * (cappedData.length - i - 1) / cappedData.length, -height * v / 2] - ) - ]; - - const pathComponents = points.map(([x, y], i) => { - const [nx, ny] = points[i < points.length - 1 ? i + 1 : 0]; - return `Q${x} ${y} ${(x + nx) / 2} ${(y + ny) / 2}`; - }); - - return ( - - - - i % takeEveryN === 0); + + const cappedData = [0, ...filteredData, 0]; + + const points = [ + ...cappedData.map((v, i) => + [width * i / cappedData.length, height * v / 2] + ), + ...cappedData.reverse().map((v, i) => + [width * (cappedData.length - i - 1) / cappedData.length, -height * v / 2] + ) + ]; + const pathComponents = points.map(([x, y], i) => { + const [nx, ny] = points[i < points.length - 1 ? i + 1 : 0]; + return `Q${x} ${y} ${(x + nx) / 2} ${(y + ny) / 2}`; + }); + + return ( + + - - - ); -}; + + + + + ); + } +} Waveform.propTypes = { data: PropTypes.arrayOf(PropTypes.number), diff --git a/src/components/browser-modal/unsupported.png b/src/components/webgl-modal/unsupported.png similarity index 100% rename from src/components/browser-modal/unsupported.png rename to src/components/webgl-modal/unsupported.png diff --git a/src/components/webgl-modal/webgl-modal.css b/src/components/webgl-modal/webgl-modal.css index 10eed71f1d4..85fcc0d0137 100644 --- a/src/components/webgl-modal/webgl-modal.css +++ b/src/components/webgl-modal/webgl-modal.css @@ -30,7 +30,7 @@ width: 100%; height: 208px; background-color: $motion-primary; - background-image: url('../browser-modal/unsupported.png'); + background-image: url('./unsupported.png'); background-size: cover; } diff --git a/src/components/webgl-modal/webgl-modal.jsx b/src/components/webgl-modal/webgl-modal.jsx index faf5ac55171..8c11014eb62 100644 --- a/src/components/webgl-modal/webgl-modal.jsx +++ b/src/components/webgl-modal/webgl-modal.jsx @@ -39,7 +39,7 @@ const WebGlModal = ({intl, ...props}) => ( webGlLink: ( { - this.updateToolbox(); - }, 0); + // Only rerender the toolbox when the blocks are visible and the xml is + // different from the previously rendered toolbox xml. + // Do not check against prevProps.toolboxXML because that may not have been rendered. + if (this.props.isVisible && this.props.toolboxXML !== this._renderedToolboxXML) { + this.requestToolboxUpdate(); } + if (this.props.isVisible === prevProps.isVisible) { if (this.props.stageSize !== prevProps.stageSize) { // force workspace to redraw for the new stage size @@ -158,7 +162,7 @@ class Blocks extends React.Component { this.setLocale(); } else { this.props.vm.refreshWorkspace(); - this.updateToolbox(); + this.requestToolboxUpdate(); } window.dispatchEvent(new Event('resize')); @@ -171,15 +175,22 @@ class Blocks extends React.Component { this.workspace.dispose(); clearTimeout(this.toolboxUpdateTimeout); } - + requestToolboxUpdate () { + clearTimeout(this.toolboxUpdateTimeout); + this.toolboxUpdateTimeout = setTimeout(() => { + this.updateToolbox(); + }, 0); + } setLocale () { - this.workspace.getFlyout().setRecyclingEnabled(false); this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); this.props.vm.setLocale(this.props.locale, this.props.messages) .then(() => { + this.workspace.getFlyout().setRecyclingEnabled(false); this.props.vm.refreshWorkspace(); - this.updateToolbox(); - this.workspace.getFlyout().setRecyclingEnabled(true); + this.requestToolboxUpdate(); + this.withToolboxUpdates(() => { + this.workspace.getFlyout().setRecyclingEnabled(true); + }); }); } @@ -189,6 +200,8 @@ class Blocks extends React.Component { const categoryId = this.workspace.toolbox_.getSelectedCategoryId(); const offset = this.workspace.toolbox_.getCategoryScrollOffset(); this.workspace.updateToolbox(this.props.toolboxXML); + this._renderedToolboxXML = this.props.toolboxXML; + // In order to catch any changes that mutate the toolbox during "normal runtime" // (variable changes/etc), re-enable toolbox refresh. // Using the setter function will rerender the entire toolbox which we just rendered. @@ -233,7 +246,7 @@ class Blocks extends React.Component { this.props.vm.addListener('EXTENSION_ADDED', this.handleExtensionAdded); this.props.vm.addListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate); this.props.vm.addListener('PERIPHERAL_CONNECTED', this.handleStatusButtonUpdate); - this.props.vm.addListener('PERIPHERAL_DISCONNECT_ERROR', this.handleStatusButtonUpdate); + this.props.vm.addListener('PERIPHERAL_DISCONNECTED', this.handleStatusButtonUpdate); } detachVM () { this.props.vm.removeListener('SCRIPT_GLOW_ON', this.onScriptGlowOn); @@ -246,7 +259,7 @@ class Blocks extends React.Component { this.props.vm.removeListener('EXTENSION_ADDED', this.handleExtensionAdded); this.props.vm.removeListener('BLOCKSINFO_UPDATE', this.handleBlocksInfoUpdate); this.props.vm.removeListener('PERIPHERAL_CONNECTED', this.handleStatusButtonUpdate); - this.props.vm.removeListener('PERIPHERAL_DISCONNECT_ERROR', this.handleStatusButtonUpdate); + this.props.vm.removeListener('PERIPHERAL_DISCONNECTED', this.handleStatusButtonUpdate); } updateToolboxBlockValue (id, value) { @@ -262,7 +275,7 @@ class Blocks extends React.Component { } onTargetsUpdate () { - if (this.props.vm.editingTarget) { + if (this.props.vm.editingTarget && this.workspace.getFlyout()) { ['glide', 'move', 'set'].forEach(prefix => { this.updateToolboxBlockValue(`${prefix}x`, Math.round(this.props.vm.editingTarget.x).toString()); this.updateToolboxBlockValue(`${prefix}y`, Math.round(this.props.vm.editingTarget.y).toString()); @@ -297,12 +310,32 @@ class Blocks extends React.Component { onVisualReport (data) { this.workspace.reportValue(data.id, data.value); } + getToolboxXML () { + // Use try/catch because this requires digging pretty deep into the VM + // Code inside intentionally ignores several error situations (no stage, etc.) + // Because they would get caught by this try/catch + try { + let {editingTarget: target, runtime} = this.props.vm; + const stage = runtime.getTargetForStage(); + if (!target) target = stage; // If no editingTarget, use the stage + + const stageCostumes = stage.getCostumes(); + const targetCostumes = target.getCostumes(); + const targetSounds = target.getSounds(); + const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML(); + return makeToolboxXML(target.isStage, target.id, dynamicBlocksXML, + targetCostumes[0].name, + stageCostumes[0].name, + targetSounds.length > 0 ? targetSounds[0].name : '' + ); + } catch { + return null; + } + } onWorkspaceUpdate (data) { // When we change sprites, update the toolbox to have the new sprite's blocks - if (this.props.vm.editingTarget) { - const target = this.props.vm.editingTarget; - const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML(); - const toolboxXML = makeToolboxXML(target.isStage, target.id, dynamicBlocksXML); + const toolboxXML = this.getToolboxXML(); + if (toolboxXML) { this.props.updateToolboxState(toolboxXML); } @@ -350,12 +383,9 @@ class Blocks extends React.Component { // this actually defines blocks and MUST run regardless of the UI state this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json).filter(x => x)); - // update the toolbox view: this can be skipped if we're not looking at a target, etc. - const runtime = this.props.vm.runtime; - const target = runtime.getEditingTarget() || runtime.getTargetForStage(); - if (target) { - const dynamicBlocksXML = runtime.getBlocksXML(); - const toolboxXML = makeToolboxXML(target.isStage, target.id, dynamicBlocksXML); + // Update the toolbox with new blocks + const toolboxXML = this.getToolboxXML(); + if (toolboxXML) { this.props.updateToolboxState(toolboxXML); } } @@ -426,6 +456,7 @@ class Blocks extends React.Component { .then(blocks => this.props.vm.shareBlocksToTarget(blocks, this.props.vm.editingTarget.id)) .then(() => { this.props.vm.refreshWorkspace(); + this.updateToolbox(); // To show new variables/custom blocks }); } render () { @@ -460,9 +491,9 @@ class Blocks extends React.Component { /> {this.state.prompt ? ( 2 ? item.info[2] : 1, skinId: null }; - this.props.vm.addCostume(item.md5, vmCostume); + this.props.vm.addCostumeFromLibrary(item.md5, vmCostume); analytics.event({ category: 'library', action: 'Select Costume', diff --git a/src/containers/costume-tab.jsx b/src/containers/costume-tab.jsx index 321c25c9ad1..6be2cf26849 100644 --- a/src/containers/costume-tab.jsx +++ b/src/containers/costume-tab.jsx @@ -13,6 +13,7 @@ import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; import DragConstants from '../lib/drag-constants'; import {emptyCostume} from '../lib/empty-assets'; import sharedMessages from '../lib/shared-messages'; +import downloadBlob from '../lib/download-blob'; import { closeCameraCapture, @@ -27,6 +28,7 @@ import { } from '../reducers/editor-tab'; import {setRestore} from '../reducers/restore-deletion'; +import {showStandardAlert, closeAlertWithId} from '../reducers/alerts'; import addLibraryBackdropIcon from '../components/asset-panel/icon--add-backdrop-lib.svg'; import addLibraryCostumeIcon from '../components/asset-panel/icon--add-costume-lib.svg'; @@ -86,6 +88,7 @@ class CostumeTab extends React.Component { 'handleSelectCostume', 'handleDeleteCostume', 'handleDuplicateCostume', + 'handleExportCostume', 'handleNewCostume', 'handleNewBlankCostume', 'handleSurpriseCostume', @@ -150,8 +153,20 @@ class CostumeTab extends React.Component { handleDuplicateCostume (costumeIndex) { this.props.vm.duplicateCostume(costumeIndex); } - handleNewCostume (costume) { - this.props.vm.addCostume(costume.md5, costume); + handleExportCostume (costumeIndex) { + const item = this.props.vm.editingTarget.sprite.costumes[costumeIndex]; + const blob = new Blob([item.asset.data], {type: item.asset.assetType.contentType}); + downloadBlob(`${item.name}.${item.asset.dataFormat}`, blob); + } + handleNewCostume (costume, fromCostumeLibrary) { + const costumes = Array.isArray(costume) ? costume : [costume]; + + return Promise.all(costumes.map(c => { + if (fromCostumeLibrary) { + return this.props.vm.addCostumeFromLibrary(c.md5, c); + } + return this.props.vm.addCostume(c.md5, c); + })); } handleNewBlankCostume () { const name = this.props.vm.editingTarget.isStage ? @@ -173,7 +188,7 @@ class CostumeTab extends React.Component { bitmapResolution: item.info.length > 2 ? item.info[2] : 1, skinId: null }; - this.handleNewCostume(vmCostume); + this.handleNewCostume(vmCostume, true /* fromCostumeLibrary */); } handleSurpriseBackdrop () { const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)]; @@ -189,14 +204,26 @@ class CostumeTab extends React.Component { } handleCostumeUpload (e) { const storage = this.props.vm.runtime.storage; - handleFileUpload(e.target, (buffer, fileType, fileName) => { - costumeUpload(buffer, fileType, fileName, storage, this.handleNewCostume); - }); + this.props.onShowImporting(); + handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => { + costumeUpload(buffer, fileType, storage, vmCostumes => { + vmCostumes.forEach((costume, i) => { + costume.name = `${fileName}${i ? i + 1 : ''}`; + }); + this.handleNewCostume(vmCostumes).then(() => { + if (fileIndex === fileCount - 1) { + this.props.onCloseImporting(); + } + }); + }, this.props.onCloseImporting); + }, this.props.onCloseImporting); } handleCameraBuffer (buffer) { const storage = this.props.vm.runtime.storage; - const name = this.props.intl.formatMessage(messages.costume, {index: 1}); - costumeUpload(buffer, 'image/png', name, storage, this.handleNewCostume); + costumeUpload(buffer, 'image/png', storage, vmCostumes => { + vmCostumes[0].name = this.props.intl.formatMessage(messages.costume, {index: 1}); + this.handleNewCostume(vmCostumes); + }); } handleFileUploadClick () { this.fileInput.click(); @@ -280,9 +307,10 @@ class CostumeTab extends React.Component { title: intl.formatMessage(addFileMessage), img: fileUploadIcon, onClick: this.handleFileUploadClick, - fileAccept: '.svg, .png, .jpg, .jpeg', + fileAccept: '.svg, .png, .jpg, .jpeg, .gif', fileChange: this.handleCostumeUpload, - fileInput: this.setFileInput + fileInput: this.setFileInput, + fileMultiple: true }, { title: intl.formatMessage(messages.addSurpriseCostumeMsg), @@ -308,6 +336,7 @@ class CostumeTab extends React.Component { this.handleDeleteCostume : null} onDrop={this.handleDrop} onDuplicateClick={this.handleDuplicateCostume} + onExportClick={this.handleExportCostume} onItemClick={this.handleSelectCostume} > {target.costumes ? @@ -334,10 +363,12 @@ CostumeTab.propTypes = { intl: intlShape, isRtl: PropTypes.bool, onActivateSoundsTab: PropTypes.func.isRequired, + onCloseImporting: PropTypes.func.isRequired, onNewCostumeFromCameraClick: PropTypes.func.isRequired, onNewLibraryBackdropClick: PropTypes.func.isRequired, onNewLibraryCostumeClick: PropTypes.func.isRequired, onRequestCloseCameraModal: PropTypes.func.isRequired, + onShowImporting: PropTypes.func.isRequired, sprites: PropTypes.shape({ id: PropTypes.shape({ costumes: PropTypes.arrayOf(PropTypes.shape({ @@ -382,7 +413,9 @@ const mapDispatchToProps = dispatch => ({ }, dispatchUpdateRestore: restoreState => { dispatch(setRestore(restoreState)); - } + }, + onCloseImporting: () => dispatch(closeAlertWithId('importingAsset')), + onShowImporting: () => dispatch(showStandardAlert('importingAsset')) }); export default errorBoundaryHOC('Costume Tab')( diff --git a/src/containers/error-boundary.jsx b/src/containers/error-boundary.jsx index 6485a41b84b..0b7704ba229 100644 --- a/src/containers/error-boundary.jsx +++ b/src/containers/error-boundary.jsx @@ -1,18 +1,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import {connect} from 'react-redux'; -import bowser from 'bowser'; import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; import CrashMessageComponent from '../components/crash-message/crash-message.jsx'; import log from '../lib/log.js'; -import supportedBrowser from '../lib/supported-browser'; -import analytics from '../lib/analytics'; +import {recommendedBrowser} from '../lib/supported-browser'; class ErrorBoundary extends React.Component { constructor (props) { super(props); this.state = { - hasError: false + hasError: false, + errorId: null }; } @@ -23,24 +22,23 @@ class ErrorBoundary extends React.Component { message: 'Unknown error' }; - // Display fallback UI - this.setState({hasError: true}); - - // Log errors to analytics, separating supported browsers from unsupported. - if (supportedBrowser()) { - analytics.event({ - category: 'error', - action: this.props.action, - label: error.message - }); - } else { - analytics.event({ - category: 'Unsupported Browser Error', - action: `(Unsupported Browser) ${this.props.action}`, - label: `${bowser.name} ${error.message}` + // Log errors to analytics, leaving out browsers that are not in our recommended set + if (recommendedBrowser() && window.Sentry) { + window.Sentry.withScope(scope => { + Object.keys(info).forEach(key => { + scope.setExtra(key, info[key]); + }); + scope.setExtra('action', this.props.action); + window.Sentry.captureException(error); }); } + // Display fallback UI + this.setState({ + hasError: true, + errorId: window.Sentry ? window.Sentry.lastEventId() : null + }); + // Log error locally for debugging as well. log.error(`Unhandled Error: ${error.stack}\nComponent stack: ${info.componentStack}`); } @@ -55,10 +53,16 @@ class ErrorBoundary extends React.Component { render () { if (this.state.hasError) { - if (supportedBrowser()) { - return ; + if (recommendedBrowser()) { + return ( + + ); } return (); diff --git a/src/containers/green-flag-overlay.jsx b/src/containers/green-flag-overlay.jsx index 2d0e4135ff2..d6dcd44ccd7 100644 --- a/src/containers/green-flag-overlay.jsx +++ b/src/containers/green-flag-overlay.jsx @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import {connect} from 'react-redux'; import VM from 'scratch-vm'; +import Box from '../components/box/box.jsx'; import greenFlag from '../components/green-flag/icon--green-flag.svg'; class GreenFlagOverlay extends React.Component { @@ -23,15 +24,18 @@ class GreenFlagOverlay extends React.Component { if (this.props.isStarted) return null; return ( -
- -
+
+ +
+
+ ); } } @@ -39,7 +43,8 @@ class GreenFlagOverlay extends React.Component { GreenFlagOverlay.propTypes = { className: PropTypes.string, isStarted: PropTypes.bool, - vm: PropTypes.instanceOf(VM) + vm: PropTypes.instanceOf(VM), + wrapperClass: PropTypes.string }; const mapStateToProps = state => ({ diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 9935b3a7e25..173f1218a6e 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -7,7 +7,6 @@ import VM from 'scratch-vm'; import {defineMessages, injectIntl, intlShape} from 'react-intl'; import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; -import {openExtensionLibrary} from '../reducers/modals'; import { getIsError, getIsShowingProject @@ -22,7 +21,9 @@ import { import { closeCostumeLibrary, - closeBackdropLibrary + closeBackdropLibrary, + closeTelemetryModal, + openExtensionLibrary } from '../reducers/modals'; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; @@ -36,6 +37,7 @@ import vmManagerHOC from '../lib/vm-manager-hoc.jsx'; 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: { @@ -47,6 +49,7 @@ const messages = defineMessages({ class GUI extends React.Component { componentDidMount () { + setIsScratchDesktop(this.props.isScratchDesktop); this.setReduxTitle(this.props.projectTitle); this.props.onStorageInit(storage); } @@ -57,6 +60,11 @@ class GUI extends React.Component { 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') { @@ -78,7 +86,9 @@ class GUI extends React.Component { cloudHost, error, isError, + isScratchDesktop, isShowingProject, + onProjectLoaded, onStorageInit, onUpdateProjectId, onUpdateReduxProjectTitle, @@ -109,26 +119,29 @@ GUI.propTypes = { cloudHost: PropTypes.string, error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), fetchingProject: PropTypes.bool, - importInfoVisible: PropTypes.bool, intl: intlShape, isError: PropTypes.bool, isLoading: PropTypes.bool, + isScratchDesktop: PropTypes.bool, isShowingProject: PropTypes.bool, loadingStateVisible: PropTypes.bool, + onProjectLoaded: PropTypes.func, onSeeCommunity: PropTypes.func, onStorageInit: PropTypes.func, onUpdateProjectId: PropTypes.func, onUpdateProjectTitle: PropTypes.func, onUpdateReduxProjectTitle: PropTypes.func, - previewInfoVisible: PropTypes.bool, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), projectTitle: PropTypes.string, + telemetryModalVisible: PropTypes.bool, vm: PropTypes.instanceOf(VM).isRequired }; GUI.defaultProps = { + isScratchDesktop: false, onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(), + onProjectLoaded: () => {}, onUpdateProjectId: () => {} }; @@ -144,19 +157,19 @@ const mapStateToProps = state => { costumeLibraryVisible: state.scratchGui.modals.costumeLibrary, costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX, error: state.scratchGui.projectState.error, - importInfoVisible: state.scratchGui.modals.importInfo, isError: getIsError(loadingState), + isFullScreen: state.scratchGui.mode.isFullScreen, isPlayerOnly: state.scratchGui.mode.isPlayerOnly, isRtl: state.locales.isRtl, isShowingProject: getIsShowingProject(loadingState), loadingStateVisible: state.scratchGui.modals.loadingProject, - previewInfoVisible: state.scratchGui.modals.previewInfo, projectId: state.scratchGui.projectState.projectId, + soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, targetIsStage: ( state.scratchGui.targets.stage && state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget ), - soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, + telemetryModalVisible: state.scratchGui.modals.telemetryModal, tipsLibraryVisible: state.scratchGui.modals.tipsLibrary, vm: state.scratchGui.vm }; @@ -169,6 +182,7 @@ const mapDispatchToProps = dispatch => ({ onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()), onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), + onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()), onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title)) }); diff --git a/src/containers/import-modal.jsx b/src/containers/import-modal.jsx deleted file mode 100644 index 1c75929cf56..00000000000 --- a/src/containers/import-modal.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import bindAll from 'lodash.bindall'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {connect} from 'react-redux'; - -import ImportModalComponent from '../components/import-modal/import-modal.jsx'; - -import { - closeImportInfo, - openPreviewInfo -} from '../reducers/modals'; - -class ImportModal extends React.Component { - constructor (props) { - super(props); - bindAll(this, [ - 'handleViewProject', - 'handleCancel', - 'handleChange', - 'handleGoBack', - 'handleKeyPress' - ]); - - this.state = { - inputValue: '', - hasValidationError: false, - errorMessage: '' - }; - } - handleKeyPress (event) { - if (event.key === 'Enter') this.handleViewProject(); - } - handleViewProject () { - const inputValue = this.state.inputValue; - const projectId = this.validate(inputValue); - - if (projectId) { - const projectLink = document.createElement('a'); - document.body.appendChild(projectLink); - projectLink.href = `#${projectId}`; - projectLink.click(); - document.body.removeChild(projectLink); - this.props.onViewProject(); - } else { - this.setState({ - hasValidationError: true, - errorMessage: `invalidFormatError`}); - } - } - handleChange (e) { - this.setState({inputValue: e.target.value, hasValidationError: false}); - } - validate (input) { - const urlMatches = input.match(/^(?:https?:\/\/)?scratch\.mit\.edu\/projects\/(\d+)/); - if (urlMatches && urlMatches.length > 0) { - return urlMatches[1]; - } - const projectIdMatches = input.match(/^#?(\d+)$/); - if (projectIdMatches && projectIdMatches.length > 0) { - return projectIdMatches[1]; - } - return null; - } - handleCancel () { - this.props.onCancel(); - } - handleGoBack () { - this.props.onBack(); - } - render () { - return ( - - ); - } -} - -ImportModal.propTypes = { - isRtl: PropTypes.bool, - onBack: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - onViewProject: PropTypes.func -}; - -const mapStateToProps = state => ({ - isRtl: state.locales.isRtl -}); - -const mapDispatchToProps = dispatch => ({ - onBack: () => { - dispatch(closeImportInfo()); - dispatch(openPreviewInfo()); - }, - onCancel: () => { - dispatch(closeImportInfo()); - }, - onViewProject: () => { - dispatch(closeImportInfo()); - } -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ImportModal); diff --git a/src/containers/library-item.jsx b/src/containers/library-item.jsx new file mode 100644 index 00000000000..6177b916fd6 --- /dev/null +++ b/src/containers/library-item.jsx @@ -0,0 +1,149 @@ +import bindAll from 'lodash.bindall'; +import PropTypes from 'prop-types'; +import React from 'react'; +import {injectIntl} from 'react-intl'; + +import LibraryItemComponent from '../components/library-item/library-item.jsx'; + +class LibraryItem extends React.PureComponent { + constructor (props) { + super(props); + bindAll(this, [ + 'handleBlur', + 'handleClick', + 'handleFocus', + 'handleKeyPress', + 'handleMouseEnter', + 'handleMouseLeave', + 'rotateIcon', + 'startRotatingIcons', + 'stopRotatingIcons' + ]); + this.state = { + iconIndex: 0, + isRotatingIcon: false + }; + } + componentWillUnmount () { + clearInterval(this.intervalId); + } + handleBlur (id) { + this.handleMouseLeave(id); + } + handleClick (e) { + if (!this.props.disabled) { + this.props.onSelect(this.props.id); + } + e.preventDefault(); + } + handleFocus (id) { + this.handleMouseEnter(id); + } + handleKeyPress (e) { + if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); + this.props.onSelect(this.props.id); + } + } + handleMouseEnter () { + this.props.onMouseEnter(this.props.id); + if (this.props.icons && this.props.icons.length) { + this.stopRotatingIcons(); + this.setState({ + isRotatingIcon: true + }, this.startRotatingIcons); + } + } + handleMouseLeave () { + this.props.onMouseLeave(this.props.id); + if (this.props.icons && this.props.icons.length) { + this.setState({ + isRotatingIcon: false + }, this.stopRotatingIcons); + } + } + startRotatingIcons () { + this.rotateIcon(); + this.intervalId = setInterval(this.rotateIcon, 300); + } + stopRotatingIcons () { + if (this.intervalId) { + this.intervalId = clearInterval(this.intervalId); + } + } + rotateIcon () { + const nextIconIndex = (this.state.iconIndex + 1) % this.props.icons.length; + this.setState({iconIndex: nextIconIndex}); + } + curIconMd5 () { + if (this.props.icons && + this.state.isRotatingIcon && + this.state.iconIndex < this.props.icons.length && + this.props.icons[this.state.iconIndex] && + this.props.icons[this.state.iconIndex].baseLayerMD5) { + return this.props.icons[this.state.iconIndex].baseLayerMD5; + } + return this.props.iconMd5; + } + render () { + const iconMd5 = this.curIconMd5(); + const iconURL = iconMd5 ? + `https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` : + this.props.iconRawURL; + return ( +
+
+ /> +
- + 5
- 5 +
+ +
-
@@ -49,6 +45,18 @@ exports[`SpriteSelectorItemComponent matches snapshot when given a number and de 480 x 360
+
+ +
+
+ +