├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── .tx └── config ├── LICENSE ├── README.md ├── TRADEMARK ├── package.json ├── prune-gh-pages.sh ├── src ├── .eslintrc.js ├── components │ ├── action-menu │ │ ├── action-menu.css │ │ ├── action-menu.jsx │ │ ├── icon--backdrop.svg │ │ ├── icon--camera.svg │ │ ├── icon--file-upload.svg │ │ ├── icon--paint.svg │ │ ├── icon--search.svg │ │ ├── icon--sprite.svg │ │ └── icon--surprise.svg │ ├── alerts │ │ ├── alert.css │ │ ├── alert.jsx │ │ ├── alerts.css │ │ ├── alerts.jsx │ │ ├── inline-message.css │ │ └── inline-message.jsx │ ├── asset-panel │ │ ├── asset-panel.css │ │ ├── asset-panel.jsx │ │ ├── icon--add-backdrop-lib.svg │ │ ├── icon--add-blank-costume.svg │ │ ├── icon--add-costume-lib.svg │ │ ├── icon--add-sound-lib.svg │ │ ├── icon--add-sound-record.svg │ │ ├── icon--sound-rtl.svg │ │ ├── icon--sound.svg │ │ ├── selector.css │ │ ├── selector.jsx │ │ └── sortable-asset.jsx │ ├── audio-trimmer │ │ ├── audio-trimmer.css │ │ ├── audio-trimmer.jsx │ │ └── icon--handle.svg │ ├── backpack │ │ ├── backpack.css │ │ └── backpack.jsx │ ├── blocks │ │ ├── blocks.css │ │ └── blocks.jsx │ ├── box │ │ ├── box.css │ │ └── box.jsx │ ├── browser-modal │ │ ├── browser-modal.css │ │ ├── browser-modal.jsx │ │ └── unsupported-browser.svg │ ├── button │ │ ├── button.css │ │ └── button.jsx │ ├── camera-modal │ │ ├── camera-modal.css │ │ ├── camera-modal.jsx │ │ └── icon--back.svg │ ├── cards │ │ ├── card.css │ │ ├── cards.jsx │ │ ├── icon--close.svg │ │ ├── icon--next.svg │ │ └── icon--prev.svg │ ├── close-button │ │ ├── close-button.css │ │ ├── close-button.jsx │ │ ├── icon--close-orange.svg │ │ └── icon--close.svg │ ├── coming-soon │ │ ├── aww-cat.png │ │ ├── coming-soon.css │ │ ├── coming-soon.jsx │ │ └── cool-cat.png │ ├── connection-modal │ │ ├── auto-scanning-step.jsx │ │ ├── connected-step.jsx │ │ ├── connecting-step.jsx │ │ ├── connection-modal.css │ │ ├── connection-modal.jsx │ │ ├── dots.jsx │ │ ├── error-step.jsx │ │ ├── icons │ │ │ ├── back.svg │ │ │ ├── bluetooth-white.svg │ │ │ ├── bluetooth.svg │ │ │ ├── cancel.svg │ │ │ ├── close.svg │ │ │ ├── help.svg │ │ │ ├── refresh.svg │ │ │ ├── scratchlink.svg │ │ │ └── searching.png │ │ ├── peripheral-tile.jsx │ │ ├── scanning-step.jsx │ │ └── unavailable-step.jsx │ ├── context-menu │ │ ├── context-menu.css │ │ └── context-menu.jsx │ ├── controls │ │ ├── controls.css │ │ └── controls.jsx │ ├── crash-message │ │ ├── crash-message.css │ │ ├── crash-message.jsx │ │ └── reload.svg │ ├── custom-procedures │ │ ├── custom-procedures.css │ │ ├── custom-procedures.jsx │ │ ├── icon--boolean-input.svg │ │ ├── icon--label.svg │ │ └── icon--text-input.svg │ ├── direction-picker │ │ ├── dial.css │ │ ├── dial.jsx │ │ ├── direction-picker.css │ │ ├── direction-picker.jsx │ │ ├── icon--all-around.svg │ │ ├── icon--dial.svg │ │ ├── icon--dont-rotate.svg │ │ ├── icon--handle.svg │ │ └── icon--left-right.svg │ ├── divider │ │ ├── divider.css │ │ └── divider.jsx │ ├── drag-layer │ │ ├── drag-layer.css │ │ └── drag-layer.jsx │ ├── filter │ │ ├── filter.css │ │ ├── filter.jsx │ │ ├── icon--filter.svg │ │ └── icon--x.svg │ ├── forms │ │ ├── buffered-input-hoc.jsx │ │ ├── input.css │ │ ├── input.jsx │ │ ├── label.css │ │ └── label.jsx │ ├── green-flag │ │ ├── green-flag.css │ │ ├── green-flag.jsx │ │ └── icon--green-flag.svg │ ├── gui │ │ ├── gui.css │ │ ├── gui.jsx │ │ ├── icon--code.svg │ │ ├── icon--costumes.svg │ │ ├── icon--extensions.svg │ │ └── icon--sounds.svg │ ├── icon-button │ │ ├── icon-button.css │ │ └── icon-button.jsx │ ├── language-selector │ │ ├── language-icon.svg │ │ ├── language-selector.css │ │ └── language-selector.jsx │ ├── library-item │ │ ├── bluetooth.svg │ │ ├── internet-connection.svg │ │ ├── library-item.css │ │ └── library-item.jsx │ ├── library │ │ ├── library.css │ │ └── library.jsx │ ├── loader │ │ ├── bottom-block.svg │ │ ├── loader.css │ │ ├── loader.jsx │ │ ├── middle-block.svg │ │ └── top-block.svg │ ├── loupe │ │ ├── loupe.css │ │ └── loupe.jsx │ ├── menu-bar │ │ ├── account-nav.css │ │ ├── account-nav.jsx │ │ ├── author-info.css │ │ ├── author-info.jsx │ │ ├── community-button.css │ │ ├── community-button.jsx │ │ ├── dropdown-caret.svg │ │ ├── icon--feedback.svg │ │ ├── icon--mystuff.png │ │ ├── icon--profile.png │ │ ├── icon--remix.svg │ │ ├── icon--see-community.svg │ │ ├── login-dropdown.css │ │ ├── login-dropdown.jsx │ │ ├── menu-bar-menu.jsx │ │ ├── menu-bar.css │ │ ├── menu-bar.jsx │ │ ├── project-title-input.css │ │ ├── project-title-input.jsx │ │ ├── save-status.css │ │ ├── save-status.jsx │ │ ├── scratch-logo.svg │ │ ├── share-button.css │ │ ├── share-button.jsx │ │ ├── user-avatar.css │ │ └── user-avatar.jsx │ ├── menu │ │ ├── menu.css │ │ └── menu.jsx │ ├── meter │ │ ├── meter.css │ │ └── meter.jsx │ ├── mic-indicator │ │ ├── mic-indicator.css │ │ ├── mic-indicator.jsx │ │ └── mic-indicator.svg │ ├── modal │ │ ├── modal.css │ │ └── modal.jsx │ ├── monitor-list │ │ ├── monitor-list.css │ │ └── monitor-list.jsx │ ├── monitor │ │ ├── default-monitor.jsx │ │ ├── large-monitor.jsx │ │ ├── list-monitor-scroller.jsx │ │ ├── list-monitor.jsx │ │ ├── monitor.css │ │ ├── monitor.jsx │ │ └── slider-monitor.jsx │ ├── prompt │ │ ├── icon--dropdown-caret.svg │ │ ├── prompt.css │ │ └── prompt.jsx │ ├── question │ │ ├── icon--enter.svg │ │ ├── question.css │ │ └── question.jsx │ ├── record-modal │ │ ├── icon--back.svg │ │ ├── icon--play.svg │ │ ├── icon--stop-playback.svg │ │ ├── icon--stop-recording.svg │ │ ├── playback-step.jsx │ │ ├── record-modal.css │ │ ├── record-modal.jsx │ │ └── recording-step.jsx │ ├── sound-editor │ │ ├── icon--echo.svg │ │ ├── icon--faster.svg │ │ ├── icon--louder.svg │ │ ├── icon--lounder.svg │ │ ├── icon--redo.svg │ │ ├── icon--reverse.svg │ │ ├── icon--robot.svg │ │ ├── icon--slower.svg │ │ ├── icon--softer.svg │ │ ├── icon--trim-confirm.svg │ │ ├── icon--trim.svg │ │ ├── icon--undo.svg │ │ ├── sound-editor.css │ │ └── sound-editor.jsx │ ├── spinner │ │ ├── spinner.css │ │ └── spinner.jsx │ ├── sprite-info │ │ ├── icon--draggable-off.svg │ │ ├── icon--draggable-on.svg │ │ ├── icon--hide.svg │ │ ├── icon--show.svg │ │ ├── icon--x.svg │ │ ├── icon--y.svg │ │ ├── sprite-info.css │ │ └── sprite-info.jsx │ ├── sprite-selector-item │ │ ├── sprite-selector-item.css │ │ └── sprite-selector-item.jsx │ ├── sprite-selector │ │ ├── sprite-list.jsx │ │ ├── sprite-selector.css │ │ └── sprite-selector.jsx │ ├── stage-header │ │ ├── icon--fullscreen.svg │ │ ├── icon--large-stage.svg │ │ ├── icon--small-stage.svg │ │ ├── icon--unfullscreen.svg │ │ ├── stage-header.css │ │ └── stage-header.jsx │ ├── stage-selector │ │ ├── stage-selector.css │ │ └── stage-selector.jsx │ ├── stage-wrapper │ │ ├── stage-wrapper.css │ │ └── stage-wrapper.jsx │ ├── stage │ │ ├── stage.css │ │ └── stage.jsx │ ├── stop-all │ │ ├── icon--stop-all.svg │ │ ├── stop-all.css │ │ └── stop-all.jsx │ ├── tag-button │ │ ├── tag-button.css │ │ └── tag-button.jsx │ ├── target-pane │ │ ├── target-pane.css │ │ └── target-pane.jsx │ ├── telemetry-modal │ │ ├── telemetry-modal-header.png │ │ ├── telemetry-modal.css │ │ └── telemetry-modal.jsx │ ├── turbo-mode │ │ ├── icon--turbo.svg │ │ ├── turbo-mode.css │ │ └── turbo-mode.jsx │ ├── watermark │ │ ├── watermark.css │ │ └── watermark.jsx │ ├── waveform │ │ ├── waveform.css │ │ └── waveform.jsx │ └── webgl-modal │ │ ├── unsupported.png │ │ ├── webgl-modal.css │ │ └── webgl-modal.jsx ├── containers │ ├── account-nav.jsx │ ├── alert.jsx │ ├── alerts.jsx │ ├── audio-trimmer.jsx │ ├── auto-scanning-step.jsx │ ├── backdrop-library.jsx │ ├── backpack.jsx │ ├── blocks.jsx │ ├── camera-modal.jsx │ ├── cards.jsx │ ├── connection-modal.jsx │ ├── controls.jsx │ ├── costume-library.jsx │ ├── costume-tab.jsx │ ├── custom-procedures.jsx │ ├── deletion-restorer.jsx │ ├── direction-picker.jsx │ ├── dom-element-renderer.jsx │ ├── drag-layer.jsx │ ├── error-boundary.jsx │ ├── extension-library.jsx │ ├── green-flag-overlay.jsx │ ├── gui.jsx │ ├── inline-messages.jsx │ ├── language-selector.jsx │ ├── library-item.jsx │ ├── list-monitor.jsx │ ├── menu-item.jsx │ ├── menu.jsx │ ├── modal.jsx │ ├── monitor-list.jsx │ ├── monitor.jsx │ ├── paint-editor-wrapper.jsx │ ├── playback-step.jsx │ ├── project-watcher.jsx │ ├── prompt.jsx │ ├── question.jsx │ ├── record-modal.jsx │ ├── recording-step.jsx │ ├── sb-file-uploader.jsx │ ├── sb3-downloader.jsx │ ├── scanning-step.jsx │ ├── slider-monitor.jsx │ ├── sound-editor.jsx │ ├── sound-library.jsx │ ├── sound-tab.jsx │ ├── sprite-info.jsx │ ├── sprite-library.jsx │ ├── sprite-selector-item.jsx │ ├── stage-header.jsx │ ├── stage-selector.jsx │ ├── stage-wrapper.jsx │ ├── stage.jsx │ ├── tag-button.jsx │ ├── target-highlight.jsx │ ├── target-pane.jsx │ ├── tips-library.jsx │ ├── turbo-mode.jsx │ ├── watermark.jsx │ └── webgl-modal.jsx ├── css │ ├── colors.css │ ├── typography.css │ ├── units.css │ └── z-index.css ├── examples │ └── extensions │ │ ├── .eslintrc.js │ │ └── example-extension.js ├── index.js ├── lib │ ├── alerts │ │ └── index.jsx │ ├── analytics.js │ ├── app-state-hoc.jsx │ ├── assets │ │ ├── icon--back.svg │ │ ├── icon--help.svg │ │ ├── icon--success.svg │ │ ├── icon--tutorials.svg │ │ └── placeholder.svg │ ├── audio │ │ ├── audio-buffer-player.js │ │ ├── audio-effects.js │ │ ├── audio-recorder.js │ │ ├── audio-util.js │ │ ├── effects │ │ │ ├── echo-effect.js │ │ │ ├── robot-effect.js │ │ │ └── volume-effect.js │ │ └── shared-audio-context.js │ ├── backpack-api.js │ ├── backpack │ │ ├── block-to-image.js │ │ ├── code-payload.js │ │ ├── costume-payload.js │ │ ├── jpeg-thumbnail.js │ │ ├── sound-payload.js │ │ ├── sound-thumbnail.jpg │ │ └── sprite-payload.js │ ├── blocks.js │ ├── cloud-manager-hoc.jsx │ ├── cloud-provider.js │ ├── connected-intl-provider.jsx │ ├── data-uri-to-blob.js │ ├── default-project │ │ ├── 09dc888b0b7df19f70d81588ae73420e.svg │ │ ├── 3696356a03a8d938318876a593572843.svg │ │ ├── 83a9787d4cb6f3b7632b4ddfebf74367.wav │ │ ├── 83c36d806dc92327b9e7049a565c6bff.wav │ │ ├── cd21514d0531fdffb22204e0ec5ed84a.svg │ │ ├── index.js │ │ └── project-data.js │ ├── detect-locale.js │ ├── download-blob.js │ ├── drag-constants.js │ ├── drag-utils.js │ ├── drop-area-hoc.jsx │ ├── empty-assets.js │ ├── error-boundary-hoc.jsx │ ├── file-uploader.js │ ├── font-loader-hoc.jsx │ ├── get-costume-url.js │ ├── gif-decoder.js │ ├── hash-parser-hoc.jsx │ ├── import-csv.js │ ├── isScratchDesktop.js │ ├── layout-constants.js │ ├── libraries │ │ ├── backdrop-tags.js │ │ ├── backdrops.json │ │ ├── costumes.json │ │ ├── decks │ │ │ ├── animate-char │ │ │ │ ├── animate-char-add-sound.gif │ │ │ │ ├── animate-char-change-color.gif │ │ │ │ ├── animate-char-jump.gif │ │ │ │ ├── animate-char-move.gif │ │ │ │ ├── animate-char-pick-another-sprite.gif │ │ │ │ ├── animate-char-pick-backdrop.gif │ │ │ │ ├── animate-char-pick-sprite.gif │ │ │ │ ├── animate-char-say-something.gif │ │ │ │ ├── animate-char-talk.gif │ │ │ │ └── lib_Animate_a_Character.jpg │ │ │ ├── animate │ │ │ │ ├── animate-name-change-color.gif │ │ │ │ ├── animate-name-grow.gif │ │ │ │ ├── animate-name-pick-a-letter.gif │ │ │ │ ├── animate-name-pick-a-letter2.gif │ │ │ │ ├── animate-name-play-sound.gif │ │ │ │ ├── animate-name-spin.gif │ │ │ │ └── lib_animate-a-name.jpg │ │ │ ├── cartoonnetwork │ │ │ │ ├── 1-cn-hideshow.gif │ │ │ │ ├── 2-cn-say.gif │ │ │ │ ├── 3-cn-glide.gif │ │ │ │ ├── 4-cn-pick-gem.gif │ │ │ │ ├── 5-cn-collect.gif │ │ │ │ ├── 6-cn-variable.gif │ │ │ │ ├── 7-cn-score.gif │ │ │ │ ├── 8-cn-change-backdrop.gif │ │ │ │ └── lib_CartoonNetwork.jpg │ │ │ ├── chase-game │ │ │ │ ├── chase-game-add-backdrop.gif │ │ │ │ ├── chase-game-add-sprite1.gif │ │ │ │ ├── chase-game-add-sprite2.gif │ │ │ │ ├── chase-game-add-variable.gif │ │ │ │ ├── chase-game-change-score.gif │ │ │ │ ├── chase-game-move-randomly.gif │ │ │ │ ├── chase-game-move-rightleft.gif │ │ │ │ ├── chase-game-move-updown.gif │ │ │ │ ├── chase-game-touching-score.gif │ │ │ │ ├── chase-game-touching.gif │ │ │ │ └── lib-chasegame.jpg │ │ │ ├── game │ │ │ │ ├── game-add-score.gif │ │ │ │ ├── game-change-color.gif │ │ │ │ ├── game-change-score.gif │ │ │ │ ├── game-pick-sprite.gif │ │ │ │ ├── game-play-sound.gif │ │ │ │ ├── game-random-position.gif │ │ │ │ ├── game-reset-score.gif │ │ │ │ └── lib-pop.jpg │ │ │ ├── index.jsx │ │ │ ├── intro │ │ │ │ ├── intro-coverimage.jpg │ │ │ │ ├── intro1.gif │ │ │ │ ├── intro2.gif │ │ │ │ └── lib-getting-started.jpg │ │ │ ├── make-music │ │ │ │ ├── lib-make-music.jpg │ │ │ │ ├── make-music-beatbox.gif │ │ │ │ ├── make-music-make-beat.gif │ │ │ │ ├── make-music-make-song.gif │ │ │ │ ├── make-music-pick-instrument.gif │ │ │ │ └── make-music-play-sound.gif │ │ │ ├── sprite │ │ │ │ ├── cover-add-sprite.jpg │ │ │ │ └── intro-choose-sprite.gif │ │ │ ├── story │ │ │ │ ├── lib-tell-a-story.jpg │ │ │ │ ├── story-add-background-2.gif │ │ │ │ ├── story-conversation.gif │ │ │ │ ├── story-flip.gif │ │ │ │ ├── story-hide-character.gif │ │ │ │ ├── story-pick-another-sprite.gif │ │ │ │ ├── story-pick-backdrop.gif │ │ │ │ ├── story-pick-sprite.gif │ │ │ │ ├── story-say-something.gif │ │ │ │ ├── story-show-character.gif │ │ │ │ └── story-switch-backdrop.gif │ │ │ ├── translate-video.js │ │ │ ├── txt │ │ │ │ ├── 01_hoc-add-extensions.gif │ │ │ │ ├── 02_hoc-say-something.gif │ │ │ │ ├── 03_hoc-set-voice.gif │ │ │ │ ├── 04_hoc-move-around.gif │ │ │ │ ├── 05_hoc-add-backdrop.gif │ │ │ │ ├── 06_hoc-add-character.gif │ │ │ │ ├── 07_hoc-perform-song.gif │ │ │ │ ├── 08_hoc-color-click.gif │ │ │ │ ├── 09_hoc-spin.gif │ │ │ │ ├── 10_hoc-grow-shrink.gif │ │ │ │ └── lib_txt-to-speech.jpg │ │ │ ├── videos │ │ │ │ ├── add-backdrop.jpg │ │ │ │ ├── add-effects.jpg │ │ │ │ ├── animate-sprite.jpg │ │ │ │ ├── change-size.jpg │ │ │ │ ├── glide-around.jpg │ │ │ │ ├── hide-and-show.jpg │ │ │ │ ├── move-arrow-keys.jpg │ │ │ │ ├── record-a-sound.jpg │ │ │ │ └── spin.jpg │ │ │ └── videosensing │ │ │ │ ├── animate-char-jump.gif │ │ │ │ ├── lib_video_sensing.jpg │ │ │ │ ├── videosens-add-extension.gif │ │ │ │ ├── videosens-animate.gif │ │ │ │ ├── videosens-pet-cat.gif │ │ │ │ └── videosens-pop.gif │ │ ├── extensions │ │ │ ├── boost.png │ │ │ ├── ev3.png │ │ │ ├── index.jsx │ │ │ ├── makeymakey-small.svg │ │ │ ├── makeymakey.png │ │ │ ├── microbit.png │ │ │ ├── music-small.svg │ │ │ ├── music.png │ │ │ ├── pen-small.svg │ │ │ ├── pen.png │ │ │ ├── peripheral-connection │ │ │ │ ├── ev3 │ │ │ │ │ ├── ev3-hub-illustration.svg │ │ │ │ │ └── ev3-small.svg │ │ │ │ ├── microbit │ │ │ │ │ ├── microbit-illustration.svg │ │ │ │ │ └── microbit-small.svg │ │ │ │ └── wedo │ │ │ │ │ ├── wedo-button-illustration.svg │ │ │ │ │ ├── wedo-illustration.svg │ │ │ │ │ └── wedo-small.svg │ │ │ ├── physics.png │ │ │ ├── physicsIcon.svg │ │ │ ├── speech.png │ │ │ ├── text2speech-small.svg │ │ │ ├── text2speech.png │ │ │ ├── translate-small.png │ │ │ ├── translate.png │ │ │ ├── video-sensing-small.svg │ │ │ ├── video-sensing.png │ │ │ └── wedo.png │ │ ├── sound-tags.js │ │ ├── sounds.json │ │ ├── sprite-tags.js │ │ ├── sprites.json │ │ ├── tag-messages.js │ │ └── tutorial-tags.js │ ├── locale-utils.js │ ├── localization-hoc.jsx │ ├── log.js │ ├── make-toolbox-xml.js │ ├── monitor-adapter.js │ ├── opcode-labels.js │ ├── project-fetcher-hoc.jsx │ ├── project-saver-hoc.jsx │ ├── query-parser-hoc.jsx │ ├── randomize-sprite-position.js │ ├── screen-utils.js │ ├── shared-messages.js │ ├── sortable-hoc.jsx │ ├── storage.js │ ├── supported-browser.js │ ├── tablet-full-screen.js │ ├── throttled-property-hoc.jsx │ ├── titled-hoc.jsx │ ├── touch-utils.js │ ├── tutorial-from-url.js │ ├── variable-utils.js │ ├── video │ │ ├── camera.js │ │ ├── modal-video-manager.js │ │ └── video-provider.js │ ├── vm-listener-hoc.jsx │ └── vm-manager-hoc.jsx ├── playground │ ├── blocks-only.css │ ├── blocks-only.jsx │ ├── compatibility-testing.jsx │ ├── index.css │ ├── index.ejs │ ├── index.jsx │ ├── player.css │ ├── player.jsx │ └── render-gui.jsx ├── reducers │ ├── alerts.js │ ├── asset-drag.js │ ├── block-drag.js │ ├── cards.js │ ├── color-picker.js │ ├── connection-modal.js │ ├── custom-procedures.js │ ├── editor-tab.js │ ├── fonts-loaded.js │ ├── gui.js │ ├── hovered-target.js │ ├── locales.js │ ├── menus.js │ ├── mic-indicator.js │ ├── modals.js │ ├── mode.js │ ├── monitor-layout.js │ ├── monitors.js │ ├── project-changed.js │ ├── project-state.js │ ├── project-title.js │ ├── restore-deletion.js │ ├── stage-size.js │ ├── targets.js │ ├── timeout.js │ ├── toolbox.js │ ├── vm-status.js │ └── vm.js └── test.js ├── static └── favicon.ico ├── test ├── .eslintrc.js ├── __mocks__ │ ├── audio-buffer-player.js │ ├── audio-effects.js │ ├── fileMock.js │ └── styleMock.js ├── fixtures │ ├── 100-100.svg │ ├── gh-3582-png.png │ ├── movie.wav │ ├── paddleball.gif │ ├── project1.sb3 │ └── sneaker.wav ├── helpers │ ├── enzyme-setup.js │ ├── intl-helpers.jsx │ └── selenium-helper.js ├── integration │ ├── backdrops.test.js │ ├── backpack.test.js │ ├── blocks.test.js │ ├── connection-modal.test.js │ ├── costumes.test.js │ ├── examples.test.js │ ├── how-tos.test.js │ ├── localization.test.js │ ├── menu-bar.test.js │ ├── project-loading.test.js │ ├── sounds.test.js │ ├── sprites.test.js │ ├── stage-size.test.js │ └── tutorials-shortcut.test.js ├── smoke │ └── browser.test.js └── unit │ ├── components │ ├── __snapshots__ │ │ ├── button.test.jsx.snap │ │ ├── icon-button.test.jsx.snap │ │ ├── sound-editor.test.jsx.snap │ │ └── sprite-selector-item.test.jsx.snap │ ├── button.test.jsx │ ├── controls.test.jsx │ ├── icon-button.test.jsx │ ├── monitor-list.test.jsx │ ├── sound-editor.test.jsx │ └── sprite-selector-item.test.jsx │ ├── containers │ ├── save-status.test.jsx │ ├── sb-file-uploader.test.jsx │ ├── sound-editor.test.jsx │ └── sprite-selector-item.test.jsx │ ├── reducers │ ├── alerts-reducer.test.js │ ├── mode-reducer.test.js │ ├── monitor-layout-reducer.test.js │ └── project-state-reducer.test.js │ └── util │ ├── audio-effects.test.js │ ├── audio-util.test.js │ ├── cloud-manager-hoc.test.jsx │ ├── cloud-provider.test.js │ ├── code-payload.test.js │ ├── detect-locale.test.js │ ├── drag-utils.test.js │ ├── get-costume-url.test.js │ ├── hash-project-loader-hoc.test.jsx │ ├── opcode-labels.test.js │ ├── project-fetcher-hoc.test.jsx │ ├── project-saver-hoc.test.jsx │ ├── throttled-property-hoc.test.jsx │ ├── translate-video.test.js │ ├── tutorial-from-url.test.js │ ├── vm-listener-hoc.test.jsx │ └── vm-manager-hoc.test.jsx └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-syntax-dynamic-import", 4 | "@babel/plugin-transform-async-to-generator", 5 | "@babel/plugin-proposal-object-rest-spread", 6 | ["react-intl", { 7 | "messagesDir": "./translations/messages/" 8 | }]], 9 | "presets": [ 10 | ["@babel/preset-env", {"targets": {"browsers": ["last 3 versions", "Safari >= 8", "iOS >= 8"]}}], 11 | "@babel/preset-react" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | [*.{js,html}] 11 | indent_style = space 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | build/* 3 | dist/* 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['scratch', 'scratch/node'] 3 | }; 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly specify line endings for as many files as possible. 5 | # People who (for example) rsync between Windows and Linux need this. 6 | 7 | # File types which we know are binary 8 | 9 | # Treat SVG files as binary so that their contents don't change due to line 10 | # endings. The contents of SVGs must not change from the way they're stored 11 | # on assets.scratch.mit.edu so that MD5 calculations don't change. 12 | *.svg binary 13 | 14 | # Prefer LF for most file types 15 | *.frag text eol=lf 16 | *.htm text eol=lf 17 | *.html text eol=lf 18 | *.iml text eol=lf 19 | *.js text eol=lf 20 | *.js.map text eol=lf 21 | *.json text eol=lf 22 | *.jsx text eol=lf 23 | *.md text eol=lf 24 | *.vert text eol=lf 25 | *.xml text eol=lf 26 | *.yml text eol=lf 27 | 28 | # Prefer LF for these files 29 | .editorconfig text eol=lf 30 | .eslintrc text eol=lf 31 | .gitattributes text eol=lf 32 | .gitignore text eol=lf 33 | .gitmodules text eol=lf 34 | LICENSE text eol=lf 35 | Makefile text eol=lf 36 | README text eol=lf 37 | TRADEMARK text eol=lf 38 | 39 | # Use CRLF for Windows-specific file types 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected Behavior 2 | 3 | _Please describe what should happen_ 4 | 5 | ### Actual Behavior 6 | 7 | _Describe what actually happens_ 8 | 9 | ### Steps to Reproduce 10 | 11 | _Explain what someone needs to do in order to see what's described in *Actual behavior* above_ 12 | 13 | ### Operating System and Browser 14 | 15 | _e.g. Mac OS 10.11.6 Safari 10.0_ 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Resolves 2 | 3 | _What Github issue does this resolve (please include link)?_ 4 | 5 | - Resolves # 6 | 7 | ### Proposed Changes 8 | 9 | _Describe what this Pull Request does_ 10 | 11 | ### Reason for Changes 12 | 13 | _Explain why these changes should be made_ 14 | 15 | ### Test Coverage 16 | 17 | _Please show how you have added tests to cover your changes_ 18 | 19 | ### Browser Coverage 20 | Check the OS/browser combinations tested (At least 2) 21 | 22 | Mac 23 | * [ ] Chrome 24 | * [ ] Firefox 25 | * [ ] Safari 26 | 27 | Windows 28 | * [ ] Chrome 29 | * [ ] Firefox 30 | * [ ] Edge 31 | 32 | Chromebook 33 | * [ ] Chrome 34 | 35 | iPad 36 | * [ ] Safari 37 | 38 | Android Tablet 39 | * [ ] Chrome 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS 2 | .DS_Store 3 | 4 | # NPM 5 | /node_modules 6 | npm-* 7 | /package-lock.json 8 | 9 | # Testing 10 | /.nyc_output 11 | /coverage 12 | 13 | # Build 14 | /build 15 | /dist 16 | /.opt-in 17 | 18 | # generated translation files 19 | /translations 20 | /locale 21 | .idea/checkstyle-idea.xml 22 | .idea/codeStyles/codeStyleConfig.xml 23 | .idea/codeStyles/Project.xml 24 | .idea/compiler.xml 25 | .idea/encodings.xml 26 | .idea/inspectionProfiles/Project_Default.xml 27 | .idea/misc.xml 28 | .idea/modules.xml 29 | .idea/scratch-gui.iml 30 | .idea/vcs.xml 31 | .idea/workspace.xml 32 | src/lib/libraries/extensions/physics.ai 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Mac OS 2 | .DS_Store 3 | 4 | # NPM 5 | /node_modules 6 | npm-* 7 | 8 | # Testing 9 | /.nyc_output 10 | /coverage 11 | /test 12 | 13 | # Build 14 | /.opt-in 15 | /build 16 | 17 | # generated translation files 18 | /translations 19 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [scratch-editor.interface] 5 | file_filter = translations/.json 6 | source_file = translations/en.json 7 | source_lang = en 8 | type = CHROME 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Massachusetts Institute of Technology 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /TRADEMARK: -------------------------------------------------------------------------------- 1 | The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission. 2 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['scratch', 'scratch/es6', 'scratch/react', 'plugin:import/errors'], 4 | env: { 5 | browser: true 6 | }, 7 | globals: { 8 | process: true 9 | }, 10 | rules: { 11 | 'import/no-mutable-exports': 'error', 12 | 'import/no-commonjs': 'error', 13 | 'import/no-amd': 'error', 14 | 'import/no-nodejs-modules': 'error', 15 | 'react/jsx-no-literals': 'error', 16 | 'no-confusing-arrow': ['error', { 17 | 'allowParens': true 18 | }] 19 | }, 20 | settings: { 21 | react: { 22 | version: '16.2' // Prevent 16.3 lifecycle method errors 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/action-menu/icon--camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | camera 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/alerts/alerts.css: -------------------------------------------------------------------------------- 1 | .alerts-inner-container { 2 | min-width: 200px; 3 | max-width: 548px; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/alerts/alerts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import Box from '../box/box.jsx'; 5 | import Alert from '../../containers/alert.jsx'; 6 | 7 | import styles from './alerts.css'; 8 | 9 | const AlertsComponent = ({ 10 | alertsList, 11 | className, 12 | onCloseAlert 13 | }) => ( 14 | 18 | 19 | {alertsList.map((a, index) => ( 20 | 36 | ))} 37 | 38 | 39 | ); 40 | 41 | AlertsComponent.propTypes = { 42 | alertsList: PropTypes.arrayOf(PropTypes.object), 43 | className: PropTypes.string, 44 | onCloseAlert: PropTypes.func 45 | }; 46 | 47 | export default AlertsComponent; 48 | -------------------------------------------------------------------------------- /src/components/alerts/inline-message.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | 4 | .inline-message { 5 | color: $ui-white; 6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 7 | display: flex; 8 | justify-content: end; 9 | align-items: center; 10 | font-size: .8125rem; 11 | } 12 | 13 | .success { 14 | color: $ui-white-dim; 15 | } 16 | 17 | .info { 18 | color: $ui-white; 19 | } 20 | 21 | .warn { 22 | color: $error-light; 23 | } 24 | 25 | .spinner { 26 | margin-right: $space; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/alerts/inline-message.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from 'classnames'; 4 | 5 | import Spinner from '../spinner/spinner.jsx'; 6 | import {AlertLevels} from '../../lib/alerts/index.jsx'; 7 | 8 | import styles from './inline-message.css'; 9 | 10 | const InlineMessageComponent = ({ 11 | content, 12 | iconSpinner, 13 | level 14 | }) => ( 15 |
18 | {/* TODO: implement Rtl handling */} 19 | {iconSpinner && ( 20 | 25 | )} 26 | {content} 27 |
28 | ); 29 | 30 | InlineMessageComponent.propTypes = { 31 | content: PropTypes.element, 32 | iconSpinner: PropTypes.bool, 33 | level: PropTypes.string 34 | }; 35 | 36 | InlineMessageComponent.defaultProps = { 37 | level: AlertLevels.INFO 38 | }; 39 | 40 | export default InlineMessageComponent; 41 | -------------------------------------------------------------------------------- /src/components/asset-panel/asset-panel.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | @import "../../css/colors.css"; 3 | 4 | .wrapper { 5 | display: flex; 6 | flex-grow: 1; 7 | border: 1px solid $ui-black-transparent; 8 | background: white; 9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | font-size: 0.85rem; 11 | } 12 | 13 | [dir="ltr"] .wrapper { 14 | border-top-right-radius: $space; 15 | border-bottom-right-radius: $space; 16 | } 17 | 18 | [dir="rtl"] .wrapper { 19 | border-top-left-radius: $space; 20 | border-bottom-left-radius: $space; 21 | } 22 | 23 | .detail-area { 24 | display: flex; 25 | flex-grow: 1; 26 | flex-shrink: 1; 27 | overflow-y: auto; 28 | } 29 | 30 | [dir="ltr"] .detail-area { 31 | border-left: 1px solid $ui-black-transparent; 32 | } 33 | 34 | [dir="rtl"] .detail-area { 35 | border-right: 1px solid $ui-black-transparent; 36 | } 37 | -------------------------------------------------------------------------------- /src/components/asset-panel/asset-panel.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Box from '../box/box.jsx'; 4 | import Selector from './selector.jsx'; 5 | import styles from './asset-panel.css'; 6 | 7 | const AssetPanel = props => ( 8 | 9 | 13 | 14 | {props.children} 15 | 16 | 17 | ); 18 | 19 | AssetPanel.propTypes = { 20 | ...Selector.propTypes 21 | }; 22 | 23 | export default AssetPanel; 24 | -------------------------------------------------------------------------------- /src/components/asset-panel/sortable-asset.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import bindAll from 'lodash.bindall'; 5 | 6 | class SortableAsset extends React.Component { 7 | constructor (props) { 8 | super(props); 9 | bindAll(this, [ 10 | 'setRef' 11 | ]); 12 | } 13 | componentDidMount () { 14 | this.props.onAddSortable(this.ref); 15 | } 16 | componentWillUnmount () { 17 | this.props.onRemoveSortable(this.ref); 18 | } 19 | setRef (ref) { 20 | this.ref = ref; 21 | } 22 | render () { 23 | return ( 24 |
31 | {this.props.children} 32 |
33 | ); 34 | } 35 | } 36 | 37 | SortableAsset.propTypes = { 38 | children: PropTypes.node.isRequired, 39 | className: PropTypes.string, 40 | index: PropTypes.number.isRequired, 41 | onAddSortable: PropTypes.func.isRequired, 42 | onRemoveSortable: PropTypes.func.isRequired 43 | }; 44 | 45 | export default SortableAsset; 46 | -------------------------------------------------------------------------------- /src/components/audio-trimmer/icon--handle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | handle 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/blocks/blocks.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import classNames from 'classnames'; 3 | import React from 'react'; 4 | import Box from '../box/box.jsx'; 5 | import styles from './blocks.css'; 6 | 7 | const BlocksComponent = props => { 8 | const { 9 | containerRef, 10 | dragOver, 11 | ...componentProps 12 | } = props; 13 | return ( 14 | 21 | ); 22 | }; 23 | BlocksComponent.propTypes = { 24 | containerRef: PropTypes.func, 25 | dragOver: PropTypes.bool 26 | }; 27 | export default BlocksComponent; 28 | -------------------------------------------------------------------------------- /src/components/box/box.css: -------------------------------------------------------------------------------- 1 | .box { 2 | } 3 | -------------------------------------------------------------------------------- /src/components/button/button.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | 3 | .outlined-button { 4 | cursor: pointer; 5 | border-radius: $form-radius; 6 | font-weight: bold; 7 | display: flex; 8 | flex-direction: row; 9 | align-items: center; 10 | padding-left: .75rem; 11 | padding-right: .75rem; 12 | user-select: none; 13 | } 14 | 15 | .icon { 16 | height: 1.5rem; 17 | } 18 | 19 | [dir="ltr"] .icon { 20 | margin-right: .5rem; 21 | } 22 | 23 | [dir="rtl"] .icon { 24 | margin-left: .5rem; 25 | } 26 | 27 | .content { 28 | white-space: nowrap; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/button/button.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import styles from './button.css'; 6 | 7 | const ButtonComponent = ({ 8 | className, 9 | disabled, 10 | iconClassName, 11 | iconSrc, 12 | onClick, 13 | children, 14 | ...props 15 | }) => { 16 | 17 | if (disabled) { 18 | onClick = function () {}; 19 | } 20 | 21 | const icon = iconSrc && ( 22 | 27 | ); 28 | 29 | return ( 30 | 39 | {icon} 40 |
{children}
41 |
42 | ); 43 | }; 44 | 45 | ButtonComponent.propTypes = { 46 | children: PropTypes.node, 47 | className: PropTypes.string, 48 | disabled: PropTypes.bool, 49 | iconClassName: PropTypes.string, 50 | iconSrc: PropTypes.string, 51 | onClick: PropTypes.func 52 | }; 53 | 54 | export default ButtonComponent; 55 | -------------------------------------------------------------------------------- /src/components/cards/icon--next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Next 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/cards/icon--prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Previous 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/close-button/icon--close.svg: -------------------------------------------------------------------------------- 1 | icon--add -------------------------------------------------------------------------------- /src/components/coming-soon/aww-cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/coming-soon/aww-cat.png -------------------------------------------------------------------------------- /src/components/coming-soon/cool-cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/coming-soon/cool-cat.png -------------------------------------------------------------------------------- /src/components/connection-modal/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | back 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/connection-modal/icons/cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | cancel 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/connection-modal/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | close 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/connection-modal/icons/searching.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/connection-modal/icons/searching.png -------------------------------------------------------------------------------- /src/components/context-menu/context-menu.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | @import "../../css/z-index.css"; 4 | 5 | .context-menu { 6 | min-width: 130px; 7 | padding: 5px 0; /* The white strip at the top and bottom of the menu */ 8 | margin: 2px 0 0; /* To keep the menu below the cursor comfortably */ 9 | font-size: 0.85rem; 10 | text-align: left; 11 | background-color: $ui-white; 12 | border: 1px solid $ui-black-transparent; 13 | border-radius: calc($space / 2); 14 | box-shadow: 0px 0px 5px 1px $ui-black-transparent; 15 | pointer-events: none; 16 | transition: opacity 0.2s ease; 17 | z-index: $z-index-context-menu; 18 | } 19 | 20 | .menu-item { 21 | padding: 8px 12px; 22 | white-space: nowrap; 23 | cursor: pointer; 24 | transition: 0.1s ease; 25 | } 26 | 27 | .menu-item:hover { 28 | background: $motion-primary; 29 | color: white; 30 | } 31 | 32 | .menu-item-bordered { 33 | border-top: 1px solid $ui-black-transparent; 34 | } 35 | 36 | .menu-item-bordered:hover { 37 | background: $error-primary; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/context-menu/context-menu.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ContextMenu, MenuItem} from 'react-contextmenu'; 3 | import classNames from 'classnames'; 4 | 5 | import styles from './context-menu.css'; 6 | 7 | const StyledContextMenu = props => ( 8 | 12 | ); 13 | 14 | const StyledMenuItem = props => ( 15 | 19 | ); 20 | 21 | const BorderedMenuItem = props => ( 22 | 26 | ); 27 | 28 | 29 | export { 30 | BorderedMenuItem, 31 | StyledContextMenu as ContextMenu, 32 | StyledMenuItem as MenuItem 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/controls/controls.css: -------------------------------------------------------------------------------- 1 | .controls-container { 2 | display: flex; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/crash-message/crash-message.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | @import "../../css/typography.css"; 4 | 5 | .crash-wrapper { 6 | background-color: $motion-primary; 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | .body { 14 | width: 35%; 15 | color: white; 16 | text-align: center; 17 | } 18 | 19 | .reloadButton { 20 | border: 1px solid $motion-primary; 21 | border-radius: 0.25rem; 22 | padding: 0.5rem 2rem; 23 | background: white; 24 | color: $motion-primary; 25 | font-weight: bold; 26 | font-size: 0.875rem; 27 | cursor: pointer; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/custom-procedures/icon--boolean-input.svg: -------------------------------------------------------------------------------- 1 | R1_ C.Procedure Editble Inputs -------------------------------------------------------------------------------- /src/components/custom-procedures/icon--label.svg: -------------------------------------------------------------------------------- 1 | R1_ C.Procedure Editble Inputstext 2 | -------------------------------------------------------------------------------- /src/components/custom-procedures/icon--text-input.svg: -------------------------------------------------------------------------------- 1 | R1_ C.Procedure Editble Inputs -------------------------------------------------------------------------------- /src/components/direction-picker/dial.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .container { 4 | padding: 1rem; 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | user-select: none; 9 | } 10 | 11 | .dial-container { 12 | position: relative; 13 | } 14 | 15 | .dial-face, .dial-handle, .gauge { 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | overflow: visible; 20 | } 21 | 22 | .dial-face { 23 | width: 100%; 24 | } 25 | 26 | $dial-size: 40px; 27 | 28 | .dial-handle { 29 | cursor: pointer; 30 | width: $dial-size; 31 | height: $dial-size; 32 | /* Use margin to make positioning via top/left easier */ 33 | margin-left: calc($dial-size / -2); 34 | margin-top: calc($dial-size / -2); 35 | } 36 | 37 | .gauge-path { 38 | fill: $motion-transparent; 39 | stroke: $motion-primary; 40 | stroke-width: 1px; 41 | } 42 | -------------------------------------------------------------------------------- /src/components/direction-picker/direction-picker.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .button-row { 4 | display: flex; 5 | flex-direction: row; 6 | justify-content: center; 7 | 8 | } 9 | 10 | .icon-button { 11 | margin: 0.25rem; 12 | border: none; 13 | background: none; 14 | outline: none; 15 | cursor: pointer; 16 | user-select: none; 17 | } 18 | 19 | .icon-button:active > img { 20 | width: 20px; 21 | height: 20px; 22 | transform: scale(1.15); 23 | } 24 | 25 | .icon-button > img { 26 | transition: transform 0.1s; 27 | filter: grayscale(100%); 28 | } 29 | 30 | .icon-button.active > img { 31 | filter: none; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/direction-picker/icon--all-around.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | all-around-active 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/direction-picker/icon--handle.svg: -------------------------------------------------------------------------------- 1 | 01_Dial Elements -------------------------------------------------------------------------------- /src/components/divider/divider.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .divider { 4 | border-right: 1px dashed $ui-black-transparent; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/divider/divider.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import styles from './divider.css'; 6 | 7 | const Divider = ({className}) => ( 8 |
9 | ); 10 | 11 | Divider.propTypes = { 12 | className: PropTypes.string 13 | }; 14 | 15 | export default Divider; 16 | -------------------------------------------------------------------------------- /src/components/drag-layer/drag-layer.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | @import "../../css/colors.css"; 3 | @import "../../css/z-index.css"; 4 | 5 | .drag-layer { 6 | position: fixed; 7 | pointer-events: none; 8 | z-index: $z-index-drag-layer; 9 | left: 0; 10 | top: 0; 11 | width: 100%; 12 | height: 100% 13 | } 14 | 15 | .image-wrapper { 16 | /* Absolute allows wrapper to snuggly fit image */ 17 | position: absolute; 18 | } 19 | 20 | .image { 21 | max-width: 80px; 22 | max-height: 80px; 23 | min-width: 50px; 24 | min-height: 50px; 25 | 26 | /* Center the dragging image on the given position */ 27 | margin-left: -50%; 28 | margin-top: -50%; 29 | 30 | padding: 0.25rem; 31 | border: 2px solid $motion-primary; 32 | background: $ui-white; 33 | border-radius: 0.5rem; 34 | 35 | /* Use the same drop shadow as stage dragging */ 36 | box-shadow: 5px 5px 5px $ui-black-transparent; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/drag-layer/drag-layer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './drag-layer.css'; 4 | 5 | /* eslint no-confusing-arrow: ["error", {"allowParens": true}] */ 6 | const DragLayer = ({dragging, img, currentOffset}) => (dragging ? ( 7 |
8 |
14 | 18 |
19 |
20 | ) : null); 21 | 22 | DragLayer.propTypes = { 23 | currentOffset: PropTypes.shape({ 24 | x: PropTypes.number.isRequired, 25 | y: PropTypes.number.isRequired 26 | }), 27 | dragging: PropTypes.bool.isRequired, 28 | img: PropTypes.string 29 | }; 30 | 31 | export default DragLayer; 32 | -------------------------------------------------------------------------------- /src/components/filter/icon--filter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/filter/icon--x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/forms/input.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | @import "../../css/colors.css"; 3 | 4 | .input-form { 5 | height: 2rem; 6 | padding: 0 0.75rem; 7 | 8 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | font-size: 0.625rem; 10 | font-weight: bold; 11 | color: $text-primary; 12 | 13 | border-width: 1px; 14 | border-style: solid; 15 | border-color: $ui-black-transparent; 16 | border-radius: 2rem; 17 | 18 | outline: none; 19 | cursor: text; 20 | transition: 0.25s ease-out; /* @todo: standardize with var */ 21 | box-shadow: none; 22 | 23 | /* 24 | For truncating overflowing text gracefully 25 | Min-width is for a bug: https://css-tricks.com/flexbox-truncated-text 26 | @todo: move this out into a mixin or a helper component 27 | */ 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | white-space: nowrap; 31 | min-width: 0; 32 | } 33 | 34 | .input-form:hover { 35 | border-color: $motion-primary; 36 | } 37 | 38 | .input-form:focus { 39 | border-color: $motion-primary; 40 | box-shadow: 0 0 0 0.25rem $motion-transparent; 41 | } 42 | 43 | .input-small { 44 | width: 3rem; 45 | padding: 0 0.5rem; 46 | text-overflow: clip; 47 | text-align: center; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/forms/input.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | 5 | import styles from './input.css'; 6 | 7 | const Input = props => { 8 | const {small, ...componentProps} = props; 9 | return ( 10 | 20 | ); 21 | }; 22 | 23 | Input.propTypes = { 24 | className: PropTypes.string, 25 | small: PropTypes.bool 26 | }; 27 | 28 | Input.defaultProps = { 29 | small: false 30 | }; 31 | 32 | export default Input; 33 | -------------------------------------------------------------------------------- /src/components/forms/label.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | @import "../../css/colors.css"; 3 | 4 | .input-group { 5 | display: inline-flex; 6 | flex-direction: row; 7 | align-items: center; 8 | } 9 | 10 | .input-group-column { 11 | display: inline-flex; 12 | flex-direction: column; 13 | align-items: flex-start; 14 | } 15 | 16 | .input-group-column span { 17 | margin-bottom: .25rem; 18 | } 19 | 20 | .input-label, .input-label-secondary { 21 | font-size: 0.625rem; 22 | user-select: none; 23 | cursor: default; 24 | 25 | white-space: nowrap; 26 | } 27 | 28 | [dir="ltr"] .input-label, [dir="ltr"] .input-label-secondary { 29 | margin-right: .5rem; 30 | } 31 | 32 | [dir="rtl"] .input-label, [dir="rtl"] .input-label-secondary { 33 | margin-left: .5rem; 34 | } 35 | 36 | .input-label { 37 | font-weight: bold; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/forms/label.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import styles from './label.css'; 5 | 6 | const Label = props => ( 7 | 13 | ); 14 | 15 | Label.propTypes = { 16 | above: PropTypes.bool, 17 | children: PropTypes.node, 18 | secondary: PropTypes.bool, 19 | text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired 20 | }; 21 | 22 | Label.defaultProps = { 23 | above: false, 24 | secondary: false 25 | }; 26 | 27 | export default Label; 28 | -------------------------------------------------------------------------------- /src/components/green-flag/green-flag.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .green-flag { 4 | width: 2rem; 5 | height: 2rem; 6 | padding: 0.375rem; 7 | border-radius: 0.25rem; 8 | user-select: none; 9 | user-drag: none; 10 | cursor: pointer; 11 | } 12 | 13 | .green-flag:hover { 14 | background-color: $motion-light-transparent; 15 | } 16 | 17 | .green-flag.is-active { 18 | background-color: $motion-transparent; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/green-flag/green-flag.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import greenFlagIcon from './icon--green-flag.svg'; 6 | import styles from './green-flag.css'; 7 | 8 | const GreenFlagComponent = function (props) { 9 | const { 10 | active, 11 | className, 12 | onClick, 13 | title, 14 | ...componentProps 15 | } = props; 16 | return ( 17 | 31 | ); 32 | }; 33 | GreenFlagComponent.propTypes = { 34 | active: PropTypes.bool, 35 | className: PropTypes.string, 36 | onClick: PropTypes.func.isRequired, 37 | title: PropTypes.string 38 | }; 39 | GreenFlagComponent.defaultProps = { 40 | active: false, 41 | title: 'Go' 42 | }; 43 | export default GreenFlagComponent; 44 | -------------------------------------------------------------------------------- /src/components/green-flag/icon--green-flag.svg: -------------------------------------------------------------------------------- 1 | icon--green-flag -------------------------------------------------------------------------------- /src/components/icon-button/icon-button.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .container { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | cursor: pointer; 8 | font-size: 0.75rem; 9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | color: $motion-primary; 11 | } 12 | 13 | .container + .container { 14 | margin-top: 1.25rem; 15 | } 16 | 17 | .title { 18 | margin-top: 0.5rem; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/icon-button/icon-button.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import classNames from 'classnames'; 4 | import styles from './icon-button.css'; 5 | 6 | const IconButton = ({ 7 | img, 8 | className, 9 | title, 10 | onClick 11 | }) => ( 12 |
17 | 22 |
23 | {title} 24 |
25 |
26 | ); 27 | 28 | IconButton.propTypes = { 29 | className: PropTypes.string, 30 | img: PropTypes.string, 31 | onClick: PropTypes.func.isRequired, 32 | title: PropTypes.node.isRequired 33 | }; 34 | 35 | export default IconButton; 36 | -------------------------------------------------------------------------------- /src/components/language-selector/language-selector.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | 4 | /* Position the language select over the language icon, and make it transparent */ 5 | .language-select { 6 | position: absolute; 7 | width: $language-selector-width; 8 | height: $menu-bar-height; 9 | opacity: 0; 10 | user-select: none; 11 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 12 | font-size: .875rem; 13 | color: $text-primary; 14 | background: $ui-white; 15 | } 16 | 17 | [dir="ltr"] .language-select { 18 | right: 0; 19 | } 20 | 21 | [dir="rtl"] .language-select { 22 | left: 0; 23 | } 24 | 25 | .language-select option { 26 | opacity: 1; 27 | } 28 | 29 | .language-select:focus { 30 | border: 1px solid white; 31 | } 32 | -------------------------------------------------------------------------------- /src/components/language-selector/language-selector.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import locales from 'scratch-l10n'; 5 | import styles from './language-selector.css'; 6 | 7 | // supported languages to exclude from the menu, but allow as a URL option 8 | const ignore = []; 9 | 10 | const LanguageSelector = ({currentLocale, label, onChange}) => ( 11 | 30 | ); 31 | 32 | LanguageSelector.propTypes = { 33 | currentLocale: PropTypes.string, 34 | label: PropTypes.string, 35 | onChange: PropTypes.func 36 | }; 37 | 38 | export default LanguageSelector; 39 | -------------------------------------------------------------------------------- /src/components/library/library.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | @import "../../css/colors.css"; 3 | 4 | .library-scroll-grid { 5 | display: flex; 6 | justify-content: flex-start; 7 | align-content: flex-start; 8 | align-items: flex-start; 9 | background: $ui-secondary; 10 | flex-grow: 1; 11 | flex-wrap: wrap; 12 | overflow-y: auto; 13 | height: auto; 14 | padding: 0.5rem; 15 | } 16 | 17 | .library-scroll-grid.withFilterBar { 18 | height: calc(100% - $library-header-height - $library-filter-bar-height - 2rem); 19 | } 20 | 21 | .filter-bar { 22 | display: flex; 23 | flex-direction: row; 24 | justify-content: flex-start; 25 | align-items: center; 26 | height: calc($library-filter-bar-height + 2rem); /* padding */ 27 | background-color: $motion-transparent; 28 | padding: 0 1rem; 29 | font-size: .875rem; 30 | } 31 | 32 | .filter-bar-item { 33 | margin-right: .75rem; 34 | } 35 | 36 | .filter { 37 | flex-grow: 0; 38 | } 39 | 40 | .filter-input { 41 | width: 11.5rem; 42 | transition: .2s; 43 | } 44 | 45 | .filter-input:focus, 46 | .filter-input:not([value=""]) { 47 | width: 18.75rem; 48 | } 49 | 50 | .divider { 51 | transform: scaleY(1.39); 52 | height: $library-filter-bar-height; 53 | } 54 | 55 | .tag-wrapper { 56 | display: flex; 57 | flex-direction: row; 58 | flex-wrap: wrap; 59 | height: $library-filter-bar-height; 60 | overflow: hidden; 61 | } 62 | -------------------------------------------------------------------------------- /src/components/loupe/loupe.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .color-picker { 4 | position: absolute; 5 | border-radius: 100%; 6 | border: 4px solid $ui-black-transparent; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/menu-bar/account-nav.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | 4 | .user-info { 5 | display: inline-flex; 6 | flex-wrap: nowrap; 7 | justify-content: center; 8 | align-items: center; 9 | align-content: center; 10 | padding: 0 0.95rem; 11 | max-width: 260px; 12 | height: $menu-bar-height; 13 | overflow: hidden; 14 | text-decoration: none; 15 | text-overflow: ellipsis; 16 | white-space: nowrap; 17 | color: $ui-white; 18 | font-size: .75rem; 19 | font-weight: normal; 20 | } 21 | 22 | [dir="ltr"] .user-info .avatar { 23 | margin-right: calc($space * .8125); 24 | } 25 | 26 | [dir="rtl"] .user-info .avatar { 27 | margin-left: calc($space * .8125); 28 | } 29 | 30 | .user-info .avatar { 31 | width: 2rem; 32 | height: 2rem; 33 | } 34 | 35 | [dir="ltr"] .user-info .dropdown-caret-position { 36 | margin-left: calc($space * .8125); 37 | } 38 | 39 | [dir="rtl"] .user-info .dropdown-caret-position { 40 | margin-right: calc($space * .8125); 41 | } 42 | 43 | .user-info .dropdown-caret-position { 44 | display: inline-block; 45 | padding-bottom: .0625rem; 46 | vertical-align: middle; 47 | } 48 | 49 | .user-info .profile-name { 50 | font-size: .75rem; 51 | line-height: .9375rem; 52 | font-weight: bold; 53 | } 54 | -------------------------------------------------------------------------------- /src/components/menu-bar/author-info.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | 4 | .author-info { 5 | color: $ui-white; 6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 7 | display: flex; 8 | justify-content: start; 9 | align-items: center; 10 | cursor: default; 11 | } 12 | 13 | .avatar { 14 | margin-right: .5625rem; 15 | } 16 | 17 | .project-title { 18 | max-width: $menu-bar-item-max-width; 19 | display: block; 20 | overflow: hidden; 21 | font-size: $menu-bar-large-font-size; 22 | font-weight: bold; 23 | text-overflow: ellipsis; 24 | white-space: nowrap; 25 | } 26 | 27 | .username-line { 28 | max-width: $menu-bar-item-max-width; 29 | font-size: $menu-bar-standard-font-size; 30 | display: block; 31 | overflow: hidden; 32 | font-weight: normal; 33 | text-overflow: ellipsis; 34 | white-space: nowrap; 35 | } 36 | 37 | .username { 38 | font-weight: bold; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/menu-bar/community-button.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .community-button { 4 | box-shadow: 0 0 0 1px $ui-black-transparent; 5 | } 6 | 7 | .community-button-icon { 8 | height: 1.25rem; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/menu-bar/community-button.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import {FormattedMessage} from 'react-intl'; 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | import Button from '../button/button.jsx'; 6 | 7 | import communityIcon from './icon--see-community.svg'; 8 | import styles from './community-button.css'; 9 | 10 | const CommunityButton = ({ 11 | className, 12 | onClick 13 | }) => ( 14 | 29 | ); 30 | 31 | CommunityButton.propTypes = { 32 | className: PropTypes.string, 33 | onClick: PropTypes.func 34 | }; 35 | 36 | CommunityButton.defaultProps = { 37 | onClick: () => {} 38 | }; 39 | 40 | export default CommunityButton; 41 | -------------------------------------------------------------------------------- /src/components/menu-bar/dropdown-caret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dropdown-caret 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/menu-bar/icon--mystuff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/menu-bar/icon--mystuff.png -------------------------------------------------------------------------------- /src/components/menu-bar/icon--profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/menu-bar/icon--profile.png -------------------------------------------------------------------------------- /src/components/menu-bar/login-dropdown.css: -------------------------------------------------------------------------------- 1 | 2 | .login { 3 | padding: .625rem; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/menu-bar/menu-bar-menu.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import Menu from '../../containers/menu.jsx'; 4 | 5 | const MenuBarMenu = ({ 6 | children, 7 | className, 8 | onRequestClose, 9 | open, 10 | place = 'right' 11 | }) => ( 12 |
13 | 18 | {children} 19 | 20 |
21 | ); 22 | 23 | MenuBarMenu.propTypes = { 24 | children: PropTypes.node, 25 | className: PropTypes.string, 26 | onRequestClose: PropTypes.func, 27 | open: PropTypes.bool, 28 | place: PropTypes.oneOf(['left', 'right']) 29 | }; 30 | 31 | export default MenuBarMenu; 32 | -------------------------------------------------------------------------------- /src/components/menu-bar/project-title-input.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | @import "../../css/z-index.css"; 4 | 5 | /* 6 | If project-title-input.jsx is part of a menu bar say menu-bar.jsx, it can have additional css classes that 7 | can set a width for example or what it should do in a flex box (eg. grow). 8 | */ 9 | 10 | .title-field { 11 | border: 1px dashed $ui-black-transparent; 12 | border-radius: $form-radius; 13 | -webkit-border-radius: $form-radius; 14 | -moz-border-radius: $form-radius; 15 | background-color: $ui-white-transparent; 16 | background-clip: padding-box; 17 | -webkit-background-clip: padding-box; 18 | height: auto; 19 | padding: .5rem; 20 | } 21 | 22 | .title-field { 23 | color: $ui-white; 24 | font-weight: bold; 25 | font-size: .8rem; 26 | } 27 | 28 | .title-field::placeholder { 29 | color: $ui-white; 30 | font-weight: normal; 31 | font-size: .8rem; 32 | font-style: italic; 33 | } 34 | 35 | .title-field:hover { 36 | background-color: hsla(0, 100%, 100%, 0.5); 37 | } 38 | 39 | .title-field:focus { 40 | outline:none; 41 | border: 1px solid $ui-transparent; 42 | -webkit-box-shadow: 0 0 0 calc($space * .5) $ui-white-transparent; 43 | box-shadow: 0 0 0 calc($space * .5) $ui-white-transparent; 44 | background-color: $ui-white; 45 | color: $text-primary; 46 | } 47 | -------------------------------------------------------------------------------- /src/components/menu-bar/save-status.css: -------------------------------------------------------------------------------- 1 | .save-now { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | cursor: pointer; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/menu-bar/share-button.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .share-button { 4 | background: $data-primary; 5 | } 6 | 7 | .share-button-is-shared { 8 | background: $ui-black-transparent; 9 | cursor: default; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/menu-bar/share-button.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import {FormattedMessage} from 'react-intl'; 3 | import PropTypes from 'prop-types'; 4 | import React from 'react'; 5 | import Button from '../button/button.jsx'; 6 | 7 | import styles from './share-button.css'; 8 | 9 | const ShareButton = ({ 10 | className, 11 | isShared, 12 | onClick 13 | }) => ( 14 | 36 | ); 37 | 38 | ShareButton.propTypes = { 39 | className: PropTypes.string, 40 | isShared: PropTypes.bool, 41 | onClick: PropTypes.func 42 | }; 43 | 44 | ShareButton.defaultProps = { 45 | onClick: () => {} 46 | }; 47 | 48 | export default ShareButton; 49 | -------------------------------------------------------------------------------- /src/components/menu-bar/user-avatar.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | 4 | .user-thumbnail { 5 | width: $menu-bar-button-size; 6 | height: $menu-bar-button-size; 7 | border-radius: $form-radius; 8 | vertical-align: middle; 9 | box-shadow: 0 0 0 1px $ui-black-transparent; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/menu-bar/user-avatar.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import styles from './user-avatar.css'; 6 | 7 | const UserAvatar = ({ 8 | className, 9 | imageUrl 10 | }) => ( 11 | 18 | ); 19 | 20 | UserAvatar.propTypes = { 21 | className: PropTypes.string, 22 | imageUrl: PropTypes.string 23 | }; 24 | 25 | export default UserAvatar; 26 | -------------------------------------------------------------------------------- /src/components/menu/menu.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .menu { 4 | position: absolute; 5 | border: 1px solid $ui-black-transparent; 6 | border-radius: 0 0 8px 8px; 7 | background-color: $motion-primary; 8 | padding: 0; 9 | margin: 0; 10 | min-width: 186px; 11 | max-width: 260px; 12 | overflow: visible; 13 | color: $ui-white; 14 | box-shadow: 0 8px 8px 0 $ui-black-transparent; 15 | } 16 | 17 | .menu.left { 18 | right: 0; 19 | } 20 | 21 | .menu.right { 22 | left: 0; 23 | } 24 | 25 | .menu-item { 26 | display: block; 27 | line-height: 34px; 28 | white-space: nowrap; 29 | padding: 0 10px; 30 | font-size: .75rem; 31 | margin: 0; 32 | font-weight: bold; 33 | } 34 | 35 | .menu-item.active, 36 | .menu-item:hover { 37 | background-color: $ui-black-transparent; 38 | } 39 | 40 | .menu-section { 41 | border-top: 1px solid $ui-black-transparent; 42 | } 43 | -------------------------------------------------------------------------------- /src/components/meter/meter.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .green { 4 | fill: rgb(171, 220, 170); 5 | stroke: rgb(174, 211, 168); 6 | } 7 | 8 | .yellow { 9 | fill: rgb(251, 219, 130); 10 | stroke: rgb(239, 212, 134); 11 | } 12 | 13 | .red { 14 | fill: rgb(251, 194, 142); 15 | stroke: rgb(235, 189, 142); 16 | } 17 | 18 | .mask-container { 19 | position: relative; 20 | } 21 | 22 | .mask { 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | height: 100%; 27 | width: 100%; 28 | transform-origin: top; 29 | will-change: transform; 30 | background: $ui-primary; 31 | opacity: 0.75; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/mic-indicator/mic-indicator.css: -------------------------------------------------------------------------------- 1 | @keyframes popIn { 2 | from {transform: scale(0.5)} 3 | to {transform: scale(1)} 4 | } 5 | 6 | .mic-img { 7 | margin: 10px; 8 | transform-origin: center; 9 | animation: popIn 0.1s ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /src/components/mic-indicator/mic-indicator.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './mic-indicator.css'; 4 | import micIcon from './mic-indicator.svg'; 5 | import {stageSizeToTransform} from '../../lib/screen-utils'; 6 | 7 | const MicIndicatorComponent = props => ( 8 |
12 | 16 |
17 | ); 18 | 19 | MicIndicatorComponent.propTypes = { 20 | className: PropTypes.string, 21 | stageSize: PropTypes.shape({ 22 | width: PropTypes.number, 23 | height: PropTypes.number, 24 | widthDefault: PropTypes.number, 25 | heightDefault: PropTypes.number 26 | }).isRequired 27 | }; 28 | 29 | export default MicIndicatorComponent; 30 | -------------------------------------------------------------------------------- /src/components/monitor-list/monitor-list.css: -------------------------------------------------------------------------------- 1 | .monitor-list { 2 | /* Width/height are set by the component, margin: auto centers in fullscreen */ 3 | margin: auto; 4 | pointer-events: none; 5 | overflow: hidden; 6 | } 7 | 8 | .monitor-list-scaler { 9 | /* Scaling for monitors should happen from the top left */ 10 | transform-origin: left top; 11 | } 12 | 13 | ::-ms-clear { display: none; } 14 | -------------------------------------------------------------------------------- /src/components/monitor/default-monitor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './monitor.css'; 4 | 5 | const DefaultMonitor = ({categoryColor, label, value}) => ( 6 |
7 |
8 |
9 | {label} 10 |
11 |
15 | {value} 16 |
17 |
18 |
19 | ); 20 | 21 | DefaultMonitor.propTypes = { 22 | categoryColor: PropTypes.string.isRequired, 23 | label: PropTypes.string.isRequired, 24 | value: PropTypes.oneOfType([ 25 | PropTypes.string, 26 | PropTypes.number 27 | ]) 28 | }; 29 | 30 | export default DefaultMonitor; 31 | -------------------------------------------------------------------------------- /src/components/monitor/large-monitor.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './monitor.css'; 4 | 5 | const LargeMonitor = ({categoryColor, value}) => ( 6 |
7 |
11 | {value} 12 |
13 |
14 | ); 15 | 16 | LargeMonitor.propTypes = { 17 | categoryColor: PropTypes.string, 18 | value: PropTypes.oneOfType([ 19 | PropTypes.string, 20 | PropTypes.number 21 | ]) 22 | }; 23 | 24 | export default LargeMonitor; 25 | -------------------------------------------------------------------------------- /src/components/prompt/icon--dropdown-caret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | dropdown-caret-gray 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/question/icon--enter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | General/Check 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/sound-editor/icon--echo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon--echo 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/sound-editor/icon--faster.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon--faster 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/sound-editor/icon--slower.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon--slower 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/spinner/spinner.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import styles from './spinner.css'; 6 | 7 | const SpinnerComponent = function (props) { 8 | const { 9 | className, 10 | level, 11 | small 12 | } = props; 13 | return ( 14 |
22 | ); 23 | }; 24 | SpinnerComponent.propTypes = { 25 | className: PropTypes.string, 26 | level: PropTypes.string, 27 | small: PropTypes.bool 28 | }; 29 | SpinnerComponent.defaultProps = { 30 | className: '', 31 | level: 'info' 32 | }; 33 | export default SpinnerComponent; 34 | -------------------------------------------------------------------------------- /src/components/sprite-info/icon--draggable-off.svg: -------------------------------------------------------------------------------- 1 | draggable-off -------------------------------------------------------------------------------- /src/components/sprite-info/icon--draggable-on.svg: -------------------------------------------------------------------------------- 1 | icon--draggable-on -------------------------------------------------------------------------------- /src/components/sprite-info/icon--show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | show-icon-active 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/sprite-info/icon--x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | x-icon_V2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/sprite-info/icon--y.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | y icon V2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/stage-header/icon--large-stage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Large Stage (active) 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/stage-header/icon--small-stage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Small Stage (inactive) 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/stage-wrapper/stage-wrapper.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | @import "../../css/colors.css"; 3 | 4 | .stage-wrapper * { 5 | box-sizing: border-box; 6 | } 7 | 8 | .stage-canvas-wrapper { 9 | /* Hides negative space between edge of rounded corners + container, when selected */ 10 | user-select: none; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/stop-all/icon--stop-all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/stop-all/stop-all.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .stop-all { 4 | width: 2rem; 5 | height: 2rem; 6 | padding: 0.375rem; 7 | border-radius: 0.25rem; 8 | user-select: none; 9 | cursor: pointer; 10 | } 11 | 12 | .stop-all:hover { 13 | background-color: $motion-light-transparent; 14 | } 15 | 16 | .stop-all { 17 | opacity: 0.5; 18 | } 19 | 20 | .stop-all.is-active { 21 | opacity: 1; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/stop-all/stop-all.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import stopAllIcon from './icon--stop-all.svg'; 6 | import styles from './stop-all.css'; 7 | 8 | const StopAllComponent = function (props) { 9 | const { 10 | active, 11 | className, 12 | onClick, 13 | title, 14 | ...componentProps 15 | } = props; 16 | return ( 17 | 31 | ); 32 | }; 33 | 34 | StopAllComponent.propTypes = { 35 | active: PropTypes.bool, 36 | className: PropTypes.string, 37 | onClick: PropTypes.func.isRequired, 38 | title: PropTypes.string 39 | }; 40 | 41 | StopAllComponent.defaultProps = { 42 | active: false, 43 | title: 'Stop' 44 | }; 45 | 46 | export default StopAllComponent; 47 | -------------------------------------------------------------------------------- /src/components/tag-button/tag-button.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | @import "../../css/units.css"; 3 | 4 | .tag-button { 5 | padding: .625rem 1rem; 6 | background: $motion-primary; 7 | border-radius: 1.375rem; 8 | color: $ui-white; 9 | height: $library-filter-bar-height; 10 | } 11 | 12 | .tag-button-icon { 13 | max-width: 1rem; 14 | max-height: 1rem; 15 | } 16 | 17 | .active { 18 | background: $data-primary; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/tag-button/tag-button.jsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | import {FormattedMessage} from 'react-intl'; 5 | 6 | import Button from '../button/button.jsx'; 7 | 8 | import styles from './tag-button.css'; 9 | 10 | const TagButtonComponent = ({ 11 | active, 12 | iconClassName, 13 | className, 14 | tag, // eslint-disable-line no-unused-vars 15 | intlLabel, 16 | ...props 17 | }) => ( 18 | 33 | ); 34 | 35 | TagButtonComponent.propTypes = { 36 | ...Button.propTypes, 37 | active: PropTypes.bool, 38 | intlLabel: PropTypes.shape({ 39 | defaultMessage: PropTypes.string, 40 | description: PropTypes.string, 41 | id: PropTypes.string 42 | }).isRequired, 43 | tag: PropTypes.string.isRequired 44 | }; 45 | 46 | TagButtonComponent.defaultProps = { 47 | active: false 48 | }; 49 | 50 | export default TagButtonComponent; 51 | -------------------------------------------------------------------------------- /src/components/target-pane/target-pane.css: -------------------------------------------------------------------------------- 1 | @import "../../css/units.css"; 2 | 3 | .target-pane { 4 | /* Makes columns for the sprite library selector + and the stage selector */ 5 | display: flex; 6 | flex-direction: row; 7 | flex-grow: 1; 8 | } 9 | 10 | .stage-selector-wrapper { 11 | display: flex; 12 | flex-basis: 72px; 13 | flex-shrink: 0; 14 | } 15 | 16 | [dir="ltr"] .stage-selector-wrapper { 17 | margin-left: calc($space / 2); 18 | } 19 | 20 | [dir="rtl"] .stage-selector-wrapper { 21 | margin-right: calc($space / 2); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/telemetry-modal/telemetry-modal-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/telemetry-modal/telemetry-modal-header.png -------------------------------------------------------------------------------- /src/components/turbo-mode/icon--turbo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | turbo-bolt-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/turbo-mode/turbo-mode.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .turbo-container { 4 | display: flex; 5 | align-items: center; 6 | padding: 0.25rem; 7 | user-select: none; 8 | } 9 | 10 | .turbo-icon { 11 | margin: 0.25rem; 12 | } 13 | 14 | .turbo-label { 15 | font-size: 0.625rem; 16 | font-weight: bold; 17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 18 | color: $control-primary; 19 | white-space: nowrap; 20 | } 21 | -------------------------------------------------------------------------------- /src/components/turbo-mode/turbo-mode.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {FormattedMessage} from 'react-intl'; 3 | 4 | import turboIcon from './icon--turbo.svg'; 5 | 6 | import styles from './turbo-mode.css'; 7 | 8 | const TurboMode = () => ( 9 |
10 | 14 |
15 | 20 |
21 |
22 | ); 23 | 24 | export default TurboMode; 25 | -------------------------------------------------------------------------------- /src/components/watermark/watermark.css: -------------------------------------------------------------------------------- 1 | 2 | .sprite-image { 3 | margin: auto; 4 | user-select: none; 5 | max-width: 48px; 6 | max-height: 48px; 7 | opacity: 0.35; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/watermark/watermark.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import styles from './watermark.css'; 5 | 6 | const Watermark = props => ( 7 | 11 | ); 12 | 13 | Watermark.propTypes = { 14 | costumeURL: PropTypes.string 15 | }; 16 | 17 | export default Watermark; 18 | -------------------------------------------------------------------------------- /src/components/waveform/waveform.css: -------------------------------------------------------------------------------- 1 | @import "../../css/colors.css"; 2 | 3 | .container { 4 | width: 100%; 5 | } 6 | .waveform-path { 7 | /* 8 | This color is lighter than sound-primary, but 9 | cannot use alpha because of overlapping elements. 10 | */ 11 | fill: hsl(300, 54%, 72%); 12 | stroke: $sound-tertiary; 13 | } 14 | .baseline { 15 | stroke: $sound-tertiary; 16 | } 17 | -------------------------------------------------------------------------------- /src/components/webgl-modal/unsupported.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/components/webgl-modal/unsupported.png -------------------------------------------------------------------------------- /src/containers/alerts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | 5 | import { 6 | closeAlert, 7 | filterPopupAlerts 8 | } from '../reducers/alerts'; 9 | 10 | import AlertsComponent from '../components/alerts/alerts.jsx'; 11 | 12 | const Alerts = ({ 13 | alertsList, 14 | className, 15 | onCloseAlert 16 | }) => ( 17 | 23 | ); 24 | 25 | Alerts.propTypes = { 26 | alertsList: PropTypes.arrayOf(PropTypes.object), 27 | className: PropTypes.string, 28 | onCloseAlert: PropTypes.func 29 | }; 30 | 31 | const mapStateToProps = state => ({ 32 | alertsList: state.scratchGui.alerts.alertsList 33 | }); 34 | 35 | const mapDispatchToProps = dispatch => ({ 36 | onCloseAlert: index => dispatch(closeAlert(index)) 37 | }); 38 | 39 | export default connect( 40 | mapStateToProps, 41 | mapDispatchToProps 42 | )(Alerts); 43 | -------------------------------------------------------------------------------- /src/containers/cards.jsx: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | 3 | import { 4 | activateDeck, 5 | closeCards, 6 | nextStep, 7 | prevStep, 8 | dragCard, 9 | startDrag, 10 | endDrag 11 | } from '../reducers/cards'; 12 | 13 | import { 14 | openTipsLibrary 15 | } from '../reducers/modals'; 16 | 17 | import CardsComponent from '../components/cards/cards.jsx'; 18 | 19 | const mapStateToProps = state => ({ 20 | visible: state.scratchGui.cards.visible, 21 | content: state.scratchGui.cards.content, 22 | activeDeckId: state.scratchGui.cards.activeDeckId, 23 | step: state.scratchGui.cards.step, 24 | x: state.scratchGui.cards.x, 25 | y: state.scratchGui.cards.y, 26 | isRtl: state.locales.isRtl, 27 | locale: state.locales.locale, 28 | dragging: state.scratchGui.cards.dragging 29 | }); 30 | 31 | const mapDispatchToProps = dispatch => ({ 32 | onActivateDeckFactory: id => () => dispatch(activateDeck(id)), 33 | onShowAll: () => { 34 | dispatch(openTipsLibrary()); 35 | dispatch(closeCards()); 36 | }, 37 | onCloseCards: () => dispatch(closeCards()), 38 | onNextStep: () => dispatch(nextStep()), 39 | onPrevStep: () => dispatch(prevStep()), 40 | onDrag: (e_, data) => dispatch(dragCard(data.x, data.y)), 41 | onStartDrag: () => dispatch(startDrag()), 42 | onEndDrag: () => dispatch(endDrag()) 43 | }); 44 | 45 | export default connect( 46 | mapStateToProps, 47 | mapDispatchToProps 48 | )(CardsComponent); 49 | -------------------------------------------------------------------------------- /src/containers/drag-layer.jsx: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | import DragLayer from '../components/drag-layer/drag-layer.jsx'; 3 | 4 | const mapStateToProps = state => ({ 5 | dragging: state.scratchGui.assetDrag.dragging, 6 | currentOffset: state.scratchGui.assetDrag.currentOffset, 7 | img: state.scratchGui.assetDrag.img 8 | }); 9 | 10 | export default connect(mapStateToProps)(DragLayer); 11 | -------------------------------------------------------------------------------- /src/containers/inline-messages.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {connect} from 'react-redux'; 4 | 5 | import { 6 | filterInlineAlerts 7 | } from '../reducers/alerts'; 8 | 9 | import InlineMessageComponent from '../components/alerts/inline-message.jsx'; 10 | 11 | const InlineMessages = ({ 12 | alertsList, 13 | className 14 | }) => { 15 | if (!alertsList) { 16 | return null; 17 | } 18 | // only display inline alerts here 19 | const inlineAlerts = filterInlineAlerts(alertsList); 20 | if (!inlineAlerts || !inlineAlerts.length) { 21 | return null; 22 | } 23 | 24 | // get first alert 25 | const firstInlineAlert = inlineAlerts[0]; 26 | const { 27 | content, 28 | iconSpinner, 29 | level 30 | } = firstInlineAlert; 31 | 32 | return ( 33 | 39 | ); 40 | }; 41 | 42 | InlineMessages.propTypes = { 43 | alertsList: PropTypes.arrayOf(PropTypes.object), 44 | className: PropTypes.string 45 | }; 46 | 47 | const mapStateToProps = state => ({ 48 | alertsList: state.scratchGui.alerts.alertsList 49 | }); 50 | 51 | const mapDispatchToProps = () => ({}); 52 | 53 | export default connect( 54 | mapStateToProps, 55 | mapDispatchToProps 56 | )(InlineMessages); 57 | -------------------------------------------------------------------------------- /src/containers/menu-item.jsx: -------------------------------------------------------------------------------- 1 | import bindAll from 'lodash.bindall'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import {MenuItem as MenuItemComponent} from '../components/menu/menu.jsx'; 6 | 7 | class MenuItem extends React.Component { 8 | constructor (props) { 9 | super(props); 10 | bindAll(this, [ 11 | 'navigateToHref' 12 | ]); 13 | } 14 | navigateToHref () { 15 | if (this.props.href) window.location.href = this.props.href; 16 | } 17 | render () { 18 | const { 19 | children, 20 | className, 21 | onClick 22 | } = this.props; 23 | const clickAction = onClick ? onClick : this.navigateToHref; 24 | return ( 25 | 29 | {children} 30 | 31 | ); 32 | } 33 | } 34 | 35 | MenuItem.propTypes = { 36 | children: PropTypes.node, 37 | className: PropTypes.string, 38 | // can take an onClick prop, or take an href and build an onClick handler 39 | href: PropTypes.string, 40 | onClick: PropTypes.func 41 | }; 42 | 43 | export default MenuItem; 44 | -------------------------------------------------------------------------------- /src/containers/question.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import bindAll from 'lodash.bindall'; 4 | import QuestionComponent from '../components/question/question.jsx'; 5 | 6 | class Question extends React.Component { 7 | constructor (props) { 8 | super(props); 9 | bindAll(this, [ 10 | 'handleChange', 11 | 'handleKeyPress', 12 | 'handleSubmit' 13 | ]); 14 | this.state = { 15 | answer: '' 16 | }; 17 | } 18 | handleChange (e) { 19 | this.setState({answer: e.target.value}); 20 | } 21 | handleKeyPress (event) { 22 | if (event.key === 'Enter') this.handleSubmit(); 23 | } 24 | handleSubmit () { 25 | this.props.onQuestionAnswered(this.state.answer); 26 | } 27 | render () { 28 | return ( 29 | 36 | ); 37 | } 38 | } 39 | 40 | Question.propTypes = { 41 | onQuestionAnswered: PropTypes.func.isRequired, 42 | question: PropTypes.string 43 | }; 44 | 45 | export default Question; 46 | -------------------------------------------------------------------------------- /src/containers/stage-wrapper.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import VM from 'scratch-vm'; 4 | import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants.js'; 5 | import StageWrapperComponent from '../components/stage-wrapper/stage-wrapper.jsx'; 6 | 7 | const StageWrapper = props => ; 8 | 9 | StageWrapper.propTypes = { 10 | isRendererSupported: PropTypes.bool.isRequired, 11 | stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired, 12 | vm: PropTypes.instanceOf(VM).isRequired 13 | }; 14 | 15 | export default StageWrapper; 16 | -------------------------------------------------------------------------------- /src/containers/tag-button.jsx: -------------------------------------------------------------------------------- 1 | import bindAll from 'lodash.bindall'; 2 | import PropTypes from 'prop-types'; 3 | import React from 'react'; 4 | 5 | import TagButtonComponent from '../components/tag-button/tag-button.jsx'; 6 | 7 | class TagButton extends React.Component { 8 | constructor (props) { 9 | super(props); 10 | bindAll(this, [ 11 | 'handleClick' 12 | ]); 13 | } 14 | handleClick () { 15 | this.props.onClick(this.props.tag); 16 | } 17 | render () { 18 | return ( 19 | 23 | ); 24 | } 25 | } 26 | 27 | TagButton.propTypes = { 28 | ...TagButtonComponent.propTypes, 29 | onClick: PropTypes.func 30 | }; 31 | 32 | export default TagButton; 33 | -------------------------------------------------------------------------------- /src/containers/webgl-modal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import WebGlModalComponent from '../components/webgl-modal/webgl-modal.jsx'; 5 | 6 | class WebGlModal extends React.Component { 7 | handleCancel () { 8 | window.history.back(); 9 | } 10 | render () { 11 | return ( 12 | 16 | ); 17 | } 18 | } 19 | 20 | WebGlModal.propTypes = { 21 | isRtl: PropTypes.bool 22 | }; 23 | 24 | export default WebGlModal; 25 | -------------------------------------------------------------------------------- /src/css/typography.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h2 { 6 | font-size: 1.5rem; 7 | font-weight: bold; 8 | } 9 | 10 | p { 11 | font-size: 1rem; 12 | line-height: 1.5em; 13 | } 14 | -------------------------------------------------------------------------------- /src/css/units.css: -------------------------------------------------------------------------------- 1 | /* make sure to keep these in sync with other constants, 2 | e.g. STAGE_DIMENSION_DEFAULTS in lib/screen-utils.js */ 3 | 4 | $space: 0.5rem; 5 | 6 | $sprites-per-row: 5; 7 | 8 | $menu-bar-height: 3rem; 9 | $language-selector-width: 3rem; 10 | $sprite-info-height: 6rem; 11 | $stage-menu-height: 2.75rem; 12 | 13 | $library-header-height: 3.125rem; 14 | $library-filter-bar-height: 2.5rem; 15 | 16 | $stage-standard-border-width: 0.0625rem; 17 | $stage-full-screen-border-width: 0.1875rem; 18 | $stage-full-screen-stage-padding: 0.1875rem; 19 | 20 | $form-radius: calc($space / 2); 21 | 22 | /* layout contants from `layout-constants.js` */ 23 | $full-size: 1095px; 24 | $full-size-paint: 1249px; 25 | 26 | $menu-bar-standard-font-size: 0.75rem; 27 | $menu-bar-large-font-size: 0.875rem; 28 | $menu-bar-button-size: 2rem; 29 | 30 | $menu-bar-item-max-width: 12rem; 31 | -------------------------------------------------------------------------------- /src/css/z-index.css: -------------------------------------------------------------------------------- 1 | /* 2 | Contains constants for the z-index values of elements that are part of the global stack context. 3 | In other words, z-index values that are "inside" a component are not added here. 4 | This prevents conflicts between identical z-index values in different components. 5 | */ 6 | 7 | /* Toolbox z-index: 40; set in scratch-blocks */ 8 | $z-index-extension-button: 42; 9 | $z-index-stage-indicator: 45; 10 | $z-index-add-button: 46; 11 | $z-index-tooltip: 47; /* tooltips should go over add buttons if they overlap */ 12 | $z-index-monitor: 48; /* monitors go over add buttons */ 13 | $z-index-stage-question: 49; /* "ask" block text input goes above monitors */ 14 | 15 | $z-index-card: 480; 16 | $z-index-alerts: 490; 17 | $z-index-menu-bar: 491; /* menu-bar should go over monitors, alerts and tutorials */ 18 | $z-index-loader: 500; 19 | $z-index-modal: 510; 20 | 21 | $z-index-drag-layer: 1000; 22 | /* Block drag z-index: 1000; default 50 is overriden in blocks.css */ 23 | $z-index-monitor-dragging: 1010; 24 | $z-index-dragging-sprite: 1020; /* so it is draggable into other panes */ 25 | 26 | $z-index-stage-color-picker-background: 2000; 27 | $z-index-stage-with-color-picker: 2010; 28 | $z-index-stage-header: 5000; 29 | $z-index-stage-wrapper-overlay: 5000; 30 | 31 | /* in most interfaces, the context menu is always on top */ 32 | $z-index-context-menu: 10000; 33 | -------------------------------------------------------------------------------- /src/examples/extensions/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['scratch'], // no ES6 3 | env: { 4 | worker: true 5 | }, 6 | globals: { 7 | Scratch: true 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import GUI from './containers/gui.jsx'; 2 | import AppStateHOC from './lib/app-state-hoc.jsx'; 3 | import GuiReducer, {guiInitialState, guiMiddleware, initEmbedded, initFullScreen, initPlayer} from './reducers/gui'; 4 | import LocalesReducer, {localesInitialState, initLocale} from './reducers/locales'; 5 | import {ScratchPaintReducer} from 'scratch-paint'; 6 | import {setFullScreen, setPlayer} from './reducers/mode'; 7 | import {remixProject} from './reducers/project-state'; 8 | import {setAppElement} from 'react-modal'; 9 | 10 | const guiReducers = { 11 | locales: LocalesReducer, 12 | scratchGui: GuiReducer, 13 | scratchPaint: ScratchPaintReducer 14 | }; 15 | 16 | export { 17 | GUI as default, 18 | AppStateHOC, 19 | setAppElement, 20 | guiReducers, 21 | guiInitialState, 22 | guiMiddleware, 23 | initEmbedded, 24 | initPlayer, 25 | initFullScreen, 26 | initLocale, 27 | localesInitialState, 28 | remixProject, 29 | setFullScreen, 30 | setPlayer 31 | }; 32 | -------------------------------------------------------------------------------- /src/lib/analytics.js: -------------------------------------------------------------------------------- 1 | import GoogleAnalytics from 'react-ga'; 2 | 3 | GoogleAnalytics.initialize(process.env.GA_ID || window.GA_ID, { 4 | debug: (process.env.NODE_ENV !== 'production'), 5 | titleCase: true, 6 | sampleRate: (process.env.NODE_ENV === 'production') ? 100 : 0, 7 | forceSSL: true 8 | }); 9 | 10 | export default GoogleAnalytics; 11 | -------------------------------------------------------------------------------- /src/lib/assets/icon--back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | back-icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/lib/assets/icon--success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Alerts/Check 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/assets/placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/lib/audio/audio-util.js: -------------------------------------------------------------------------------- 1 | const SOUND_BYTE_LIMIT = 10 * 1000 * 1000; // 10mb 2 | 3 | const computeRMS = function (samples, scaling = 0.55) { 4 | if (samples.length === 0) return 0; 5 | // Calculate RMS, adapted from https://github.com/Tonejs/Tone.js/blob/master/Tone/component/Meter.js#L88 6 | let sum = 0; 7 | for (let i = 0; i < samples.length; i++) { 8 | const sample = samples[i]; 9 | sum += Math.pow(sample, 2); 10 | } 11 | const rms = Math.sqrt(sum / samples.length); 12 | const val = rms / scaling; 13 | return Math.sqrt(val); 14 | }; 15 | 16 | const computeChunkedRMS = function (samples, chunkSize = 1024) { 17 | const sampleCount = samples.length; 18 | const chunkLevels = []; 19 | for (let i = 0; i < sampleCount; i += chunkSize) { 20 | const maxIndex = Math.min(sampleCount, i + chunkSize); 21 | chunkLevels.push(computeRMS(samples.slice(i, maxIndex))); 22 | } 23 | return chunkLevels; 24 | }; 25 | 26 | export { 27 | computeRMS, 28 | computeChunkedRMS, 29 | SOUND_BYTE_LIMIT 30 | }; 31 | -------------------------------------------------------------------------------- /src/lib/audio/effects/echo-effect.js: -------------------------------------------------------------------------------- 1 | class EchoEffect { 2 | constructor (audioContext, delayTime) { 3 | this.audioContext = audioContext; 4 | this.delayTime = delayTime; 5 | this.input = this.audioContext.createGain(); 6 | this.output = this.audioContext.createGain(); 7 | 8 | this.effectInput = this.audioContext.createGain(); 9 | this.effectInput.gain.value = 0.75; 10 | 11 | this.delay = this.audioContext.createDelay(1); 12 | this.delay.delayTime.value = delayTime; 13 | this.decay = this.audioContext.createGain(); 14 | this.decay.gain.value = 0.3; 15 | 16 | this.compressor = this.audioContext.createDynamicsCompressor(); 17 | this.compressor.threshold.value = -5; 18 | this.compressor.knee.value = 15; 19 | this.compressor.ratio.value = 12; 20 | this.compressor.attack.value = 0; 21 | this.compressor.release.value = 0.25; 22 | 23 | this.input.connect(this.effectInput); 24 | this.effectInput.connect(this.delay); 25 | this.delay.connect(this.compressor); 26 | this.input.connect(this.compressor); 27 | this.delay.connect(this.decay); 28 | this.decay.connect(this.delay); 29 | this.compressor.connect(this.output); 30 | } 31 | } 32 | 33 | export default EchoEffect; 34 | -------------------------------------------------------------------------------- /src/lib/audio/effects/volume-effect.js: -------------------------------------------------------------------------------- 1 | class VolumeEffect { 2 | constructor (audioContext, volume) { 3 | this.audioContext = audioContext; 4 | 5 | this.input = this.audioContext.createGain(); 6 | this.output = this.audioContext.createGain(); 7 | 8 | this.gain = this.audioContext.createGain(); 9 | this.gain.gain.value = volume; 10 | 11 | this.input.connect(this.gain); 12 | this.gain.connect(this.output); 13 | } 14 | } 15 | 16 | export default VolumeEffect; 17 | -------------------------------------------------------------------------------- /src/lib/audio/shared-audio-context.js: -------------------------------------------------------------------------------- 1 | import StartAudioContext from 'startaudiocontext'; 2 | import bowser from 'bowser'; 3 | 4 | let AUDIO_CONTEXT; 5 | if (!bowser.msie) { 6 | AUDIO_CONTEXT = new (window.AudioContext || window.webkitAudioContext)(); 7 | 8 | StartAudioContext(AUDIO_CONTEXT); 9 | } 10 | 11 | /** 12 | * Wrap browser AudioContext because we shouldn't create more than one 13 | * @return {AudioContext} The singleton AudioContext 14 | */ 15 | export default function () { 16 | return AUDIO_CONTEXT; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/backpack/code-payload.js: -------------------------------------------------------------------------------- 1 | import blockToImage from './block-to-image'; 2 | import jpegThumbnail from './jpeg-thumbnail'; 3 | import {Base64} from 'js-base64'; 4 | 5 | const codePayload = ({blockObjects, topBlockId}) => { 6 | const payload = { 7 | type: 'script', // Needs to match backpack-server type name 8 | name: 'code', // All code currently gets the same name 9 | mime: 'application/json', 10 | // Backpack expects a base64 encoded string to store. Cannot use btoa because 11 | // the code can contain characters outside the 0-255 code-point range supported by btoa 12 | body: Base64.encode(JSON.stringify(blockObjects)) // Base64 encode the json 13 | }; 14 | 15 | return blockToImage(topBlockId) 16 | .then(jpegThumbnail) 17 | .then(thumbnail => { 18 | payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', ''); 19 | return payload; 20 | }); 21 | }; 22 | 23 | export default codePayload; 24 | -------------------------------------------------------------------------------- /src/lib/backpack/jpeg-thumbnail.js: -------------------------------------------------------------------------------- 1 | const jpegThumbnail = dataUrl => new Promise((resolve, reject) => { 2 | const image = new Image(); 3 | image.onload = () => { 4 | const canvas = document.createElement('canvas'); 5 | const ctx = canvas.getContext('2d'); 6 | // TODO we may want to draw to a smaller, fixed size to optimize file size 7 | canvas.width = image.width; 8 | canvas.height = image.height; 9 | 10 | ctx.fillStyle = 'white'; // Create white background, since jpeg doesn't have transparency 11 | ctx.fillRect(0, 0, canvas.width, canvas.height); 12 | 13 | ctx.drawImage(image, 0, 0); 14 | // TODO we can play with the `quality` option here to optimize file size 15 | resolve(canvas.toDataURL('image/jpeg', 0.92 /* quality */)); // Default quality is 0.92 16 | }; 17 | image.onerror = err => { 18 | reject(err); 19 | }; 20 | image.src = dataUrl; 21 | }); 22 | 23 | export default jpegThumbnail; 24 | -------------------------------------------------------------------------------- /src/lib/backpack/sound-payload.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-unresolved 2 | import soundThumbnail from '!base64-loader!./sound-thumbnail.jpg'; 3 | 4 | const soundPayload = sound => { 5 | const assetDataUrl = sound.asset.encodeDataURI(); 6 | const assetDataFormat = sound.dataFormat; 7 | const payload = { 8 | type: 'sound', 9 | name: sound.name, 10 | thumbnail: soundThumbnail, 11 | // Params to be filled in below 12 | mime: '', 13 | body: '' 14 | }; 15 | 16 | switch (assetDataFormat) { 17 | case 'wav': 18 | payload.mime = 'audio/x-wav'; 19 | payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', ''); 20 | break; 21 | case 'mp3': 22 | payload.mime = 'audio/mp3'; 23 | // TODO scratch-storage should be fixed so that encodeDataURI does not 24 | // always prepend the wave format header; Once that is fixed, the following 25 | // line will have to change. 26 | payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', ''); 27 | break; 28 | default: 29 | alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line 30 | } 31 | 32 | // Return a promise to make it consistent with other payload constructors like costume-payload 33 | return new Promise(resolve => resolve(payload)); 34 | }; 35 | 36 | export default soundPayload; 37 | -------------------------------------------------------------------------------- /src/lib/backpack/sound-thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/backpack/sound-thumbnail.jpg -------------------------------------------------------------------------------- /src/lib/backpack/sprite-payload.js: -------------------------------------------------------------------------------- 1 | import jpegThumbnail from './jpeg-thumbnail'; 2 | 3 | const spritePayload = (id, vm) => { 4 | const target = vm.runtime.getTargetById(id); 5 | if (!target) return null; 6 | 7 | return vm.exportSprite( 8 | id, 9 | 'base64' 10 | ).then(zippedSprite => { 11 | const payload = { 12 | type: 'sprite', 13 | name: target.sprite.name, 14 | mime: 'application/zip', 15 | body: zippedSprite, 16 | // Filled in below 17 | thumbnail: '' 18 | }; 19 | 20 | const costumeDataUrl = target.sprite.costumes[target.currentCostume].asset.encodeDataURI(); 21 | 22 | return jpegThumbnail(costumeDataUrl).then(thumbnail => { 23 | payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', ''); 24 | return payload; 25 | }); 26 | }); 27 | }; 28 | 29 | export default spritePayload; 30 | -------------------------------------------------------------------------------- /src/lib/connected-intl-provider.jsx: -------------------------------------------------------------------------------- 1 | import {IntlProvider as ReactIntlProvider} from 'react-intl'; 2 | import {connect} from 'react-redux'; 3 | 4 | const mapStateToProps = state => ({ 5 | key: state.locales.locale, 6 | locale: state.locales.locale, 7 | messages: state.locales.messages 8 | }); 9 | 10 | export default connect(mapStateToProps)(ReactIntlProvider); 11 | -------------------------------------------------------------------------------- /src/lib/data-uri-to-blob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility to convert data URIs to blobs 3 | * Adapted from https://stackoverflow.com/questions/12168909/blob-from-dataurl 4 | * @param {string} dataURI the data uri to blobify 5 | * @return {Blob} a blob representing the data uri 6 | */ 7 | export default function dataURItoBlob (dataURI) { 8 | const byteString = atob(dataURI.split(',')[1]); 9 | const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 10 | const arrayBuffer = new ArrayBuffer(byteString.length); 11 | const uintArray = new Uint8Array(arrayBuffer); 12 | for (let i = 0; i < byteString.length; i++) { 13 | uintArray[i] = byteString.charCodeAt(i); 14 | } 15 | const blob = new Blob([arrayBuffer], {type: mimeString}); 16 | return blob; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/default-project/83a9787d4cb6f3b7632b4ddfebf74367.wav -------------------------------------------------------------------------------- /src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/default-project/83c36d806dc92327b9e7049a565c6bff.wav -------------------------------------------------------------------------------- /src/lib/default-project/cd21514d0531fdffb22204e0ec5ed84a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/lib/download-blob.js: -------------------------------------------------------------------------------- 1 | export default (filename, blob) => { 2 | const downloadLink = document.createElement('a'); 3 | document.body.appendChild(downloadLink); 4 | 5 | // Use special ms version if available to get it working on Edge. 6 | if (navigator.msSaveOrOpenBlob) { 7 | navigator.msSaveOrOpenBlob(blob, filename); 8 | return; 9 | } 10 | 11 | const url = window.URL.createObjectURL(blob); 12 | downloadLink.href = url; 13 | downloadLink.download = filename; 14 | downloadLink.click(); 15 | window.URL.revokeObjectURL(url); 16 | document.body.removeChild(downloadLink); 17 | }; 18 | -------------------------------------------------------------------------------- /src/lib/drag-constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SOUND: 'SOUND', 3 | COSTUME: 'COSTUME', 4 | SPRITE: 'SPRITE', 5 | CODE: 'CODE', 6 | 7 | BACKPACK_SOUND: 'BACKPACK_SOUND', 8 | BACKPACK_COSTUME: 'BACKPACK_COSTUME', 9 | BACKPACK_SPRITE: 'BACKPACK_SPRITE', 10 | BACKPACK_CODE: 'BACKPACK_CODE' 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/error-boundary-hoc.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorBoundary from '../containers/error-boundary.jsx'; 3 | 4 | /* 5 | * Higher Order Component to provide error boundary for wrapped component. 6 | * A curried function, call like errorHOC()(). 7 | * @param {string} action - Label for GA tracking of errors. 8 | * @returns {function} a function that accepts a component to wrap. 9 | */ 10 | const ErrorBoundaryHOC = function (action){ 11 | /** 12 | * The function to be called with a React component to wrap it. 13 | * @param {React.Component} WrappedComponent - Component to wrap with an error boundary. 14 | * @returns {React.Component} the component wrapped with an error boundary. 15 | */ 16 | return function (WrappedComponent) { 17 | const ErrorBoundaryWrapper = props => ( 18 | 19 | 20 | 21 | ); 22 | return ErrorBoundaryWrapper; 23 | }; 24 | }; 25 | 26 | export default ErrorBoundaryHOC; 27 | -------------------------------------------------------------------------------- /src/lib/get-costume-url.js: -------------------------------------------------------------------------------- 1 | import storage from './storage'; 2 | import {inlineSvgFonts} from 'scratch-svg-renderer'; 3 | 4 | // Contains 'font-family', but doesn't only contain 'font-family="none"' 5 | const HAS_FONT_REGEXP = 'font-family(?!="none")'; 6 | 7 | const getCostumeUrl = (function () { 8 | let cachedAssetId; 9 | let cachedUrl; 10 | 11 | return function (asset) { 12 | 13 | if (cachedAssetId === asset.assetId) { 14 | return cachedUrl; 15 | } 16 | 17 | cachedAssetId = asset.assetId; 18 | 19 | // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag. 20 | // Avoid parsing the SVG when possible, since it's expensive. 21 | if (asset.assetType === storage.AssetType.ImageVector) { 22 | const svgString = asset.decodeText(); 23 | if (svgString.match(HAS_FONT_REGEXP)) { 24 | const svgText = inlineSvgFonts(svgString); 25 | cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`; 26 | } else { 27 | cachedUrl = asset.encodeDataURI(); 28 | } 29 | } else { 30 | cachedUrl = asset.encodeDataURI(); 31 | } 32 | 33 | return cachedUrl; 34 | }; 35 | }()); 36 | 37 | export { 38 | getCostumeUrl as default, 39 | HAS_FONT_REGEXP 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/import-csv.js: -------------------------------------------------------------------------------- 1 | import Papa from 'papaparse'; 2 | 3 | export default () => new Promise((resolve, reject) => { 4 | const fileInput = document.createElement('input'); 5 | fileInput.setAttribute('type', 'file'); 6 | fileInput.setAttribute('accept', '.csv, .tsv, .txt'); // parser auto-detects delimiter 7 | fileInput.onchange = e => { 8 | const file = e.target.files[0]; 9 | Papa.parse(file, { 10 | header: false, 11 | complete: results => { 12 | document.body.removeChild(fileInput); 13 | resolve(results.data); 14 | }, 15 | error: err => { 16 | document.body.removeChild(fileInput); 17 | reject(err); 18 | } 19 | }); 20 | }; 21 | document.body.appendChild(fileInput); 22 | fileInput.click(); 23 | }); 24 | -------------------------------------------------------------------------------- /src/lib/isScratchDesktop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal stored state. Not valid until after at least one call to `setIsScratchDesktop()`. 3 | * @type {boolean} 4 | */ 5 | let _isScratchDesktop; // undefined = not ready yet 6 | 7 | /** 8 | * Tell the `isScratchDesktop()` whether or not the GUI is running under Scratch Desktop. 9 | * @param {boolean} value - the new value which `isScratchDesktop()` should return in the future. 10 | */ 11 | const setIsScratchDesktop = function (value) { 12 | _isScratchDesktop = value; 13 | }; 14 | 15 | /** 16 | * @returns {boolean} - true if it seems like the GUI is running under Scratch Desktop; false otherwise. 17 | * If `setIsScratchDesktop()` has not yet been called, this can return `undefined`. 18 | */ 19 | const isScratchDesktop = function () { 20 | return _isScratchDesktop; 21 | }; 22 | 23 | /** 24 | * @returns {boolean} - false if it seems like the GUI is running under Scratch Desktop; true otherwise. 25 | */ 26 | const notScratchDesktop = function () { 27 | return !isScratchDesktop(); 28 | }; 29 | 30 | export default isScratchDesktop; 31 | export { 32 | isScratchDesktop, 33 | notScratchDesktop, 34 | setIsScratchDesktop 35 | }; 36 | -------------------------------------------------------------------------------- /src/lib/layout-constants.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'keymirror'; 2 | 3 | /** 4 | * Names for each state of the stage size toggle 5 | * @enum {string} 6 | */ 7 | const STAGE_SIZE_MODES = keyMirror({ 8 | /** 9 | * The "large stage" button is pressed; the user would like a large stage. 10 | */ 11 | large: null, 12 | 13 | /** 14 | * The "small stage" button is pressed; the user would like a small stage. 15 | */ 16 | small: null 17 | }); 18 | 19 | /** 20 | * Names for each stage render size 21 | * @enum {string} 22 | */ 23 | const STAGE_DISPLAY_SIZES = keyMirror({ 24 | /** 25 | * Large stage with wide browser 26 | */ 27 | large: null, 28 | 29 | /** 30 | * Large stage with narrow browser 31 | */ 32 | largeConstrained: null, 33 | 34 | /** 35 | * Small stage (ignores browser width) 36 | */ 37 | small: null 38 | }); 39 | 40 | const STAGE_DISPLAY_SCALES = {}; 41 | STAGE_DISPLAY_SCALES[STAGE_DISPLAY_SIZES.large] = 1; // large mode, wide browser (standard) 42 | STAGE_DISPLAY_SCALES[STAGE_DISPLAY_SIZES.largeConstrained] = 0.85; // large mode but narrow browser 43 | STAGE_DISPLAY_SCALES[STAGE_DISPLAY_SIZES.small] = 0.5; // small mode, regardless of browser size 44 | 45 | export default { 46 | standardStageWidth: 480, 47 | standardStageHeight: 360, 48 | fullSizeMinWidth: 1096, 49 | fullSizePaintMinWidth: 1250 50 | }; 51 | 52 | export { 53 | STAGE_DISPLAY_SCALES, 54 | STAGE_DISPLAY_SIZES, 55 | STAGE_SIZE_MODES 56 | }; 57 | -------------------------------------------------------------------------------- /src/lib/libraries/backdrop-tags.js: -------------------------------------------------------------------------------- 1 | import messages from './tag-messages.js'; 2 | export default [ 3 | {tag: 'fantasy', intlLabel: messages.fantasy}, 4 | {tag: 'music', intlLabel: messages.music}, 5 | {tag: 'sports', intlLabel: messages.sports}, 6 | {tag: 'outdoors', intlLabel: messages.outdoors}, 7 | {tag: 'indoors', intlLabel: messages.indoors}, 8 | {tag: 'space', intlLabel: messages.space}, 9 | {tag: 'underwater', intlLabel: messages.underwater}, 10 | {tag: 'patterns', intlLabel: messages.patterns} 11 | ]; 12 | -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-add-sound.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-add-sound.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-change-color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-change-color.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-jump.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-jump.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-move.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-move.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-pick-another-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-pick-another-sprite.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-pick-backdrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-pick-backdrop.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-pick-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-pick-sprite.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-say-something.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-say-something.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/animate-char-talk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/animate-char-talk.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate-char/lib_Animate_a_Character.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate-char/lib_Animate_a_Character.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/animate-name-change-color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/animate-name-change-color.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/animate-name-grow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/animate-name-grow.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/animate-name-pick-a-letter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/animate-name-pick-a-letter.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/animate-name-pick-a-letter2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/animate-name-pick-a-letter2.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/animate-name-play-sound.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/animate-name-play-sound.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/animate-name-spin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/animate-name-spin.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/animate/lib_animate-a-name.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/animate/lib_animate-a-name.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/1-cn-hideshow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/1-cn-hideshow.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/2-cn-say.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/2-cn-say.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/3-cn-glide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/3-cn-glide.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/4-cn-pick-gem.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/4-cn-pick-gem.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/5-cn-collect.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/5-cn-collect.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/6-cn-variable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/6-cn-variable.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/7-cn-score.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/7-cn-score.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/8-cn-change-backdrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/8-cn-change-backdrop.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/cartoonnetwork/lib_CartoonNetwork.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/cartoonnetwork/lib_CartoonNetwork.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-add-backdrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-add-backdrop.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-add-sprite1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-add-sprite1.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-add-sprite2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-add-sprite2.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-add-variable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-add-variable.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-change-score.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-change-score.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-move-randomly.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-move-randomly.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-move-rightleft.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-move-rightleft.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-move-updown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-move-updown.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-touching-score.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-touching-score.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/chase-game-touching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/chase-game-touching.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/chase-game/lib-chasegame.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/chase-game/lib-chasegame.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-add-score.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-add-score.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-change-color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-change-color.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-change-score.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-change-score.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-pick-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-pick-sprite.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-play-sound.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-play-sound.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-random-position.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-random-position.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/game-reset-score.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/game-reset-score.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/game/lib-pop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/game/lib-pop.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/intro/intro-coverimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/intro/intro-coverimage.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/intro/intro1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/intro/intro1.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/intro/intro2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/intro/intro2.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/intro/lib-getting-started.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/intro/lib-getting-started.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/make-music/lib-make-music.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/make-music/lib-make-music.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/make-music/make-music-beatbox.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/make-music/make-music-beatbox.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/make-music/make-music-make-beat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/make-music/make-music-make-beat.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/make-music/make-music-make-song.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/make-music/make-music-make-song.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/make-music/make-music-pick-instrument.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/make-music/make-music-pick-instrument.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/make-music/make-music-play-sound.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/make-music/make-music-play-sound.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/sprite/cover-add-sprite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/sprite/cover-add-sprite.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/sprite/intro-choose-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/sprite/intro-choose-sprite.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/lib-tell-a-story.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/lib-tell-a-story.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-add-background-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-add-background-2.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-conversation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-conversation.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-flip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-flip.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-hide-character.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-hide-character.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-pick-another-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-pick-another-sprite.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-pick-backdrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-pick-backdrop.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-pick-sprite.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-pick-sprite.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-say-something.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-say-something.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-show-character.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-show-character.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/story/story-switch-backdrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/story/story-switch-backdrop.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/01_hoc-add-extensions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/01_hoc-add-extensions.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/02_hoc-say-something.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/02_hoc-say-something.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/03_hoc-set-voice.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/03_hoc-set-voice.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/04_hoc-move-around.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/04_hoc-move-around.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/05_hoc-add-backdrop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/05_hoc-add-backdrop.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/06_hoc-add-character.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/06_hoc-add-character.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/07_hoc-perform-song.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/07_hoc-perform-song.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/08_hoc-color-click.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/08_hoc-color-click.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/09_hoc-spin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/09_hoc-spin.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/10_hoc-grow-shrink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/10_hoc-grow-shrink.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/txt/lib_txt-to-speech.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/txt/lib_txt-to-speech.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/add-backdrop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/add-backdrop.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/add-effects.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/add-effects.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/animate-sprite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/animate-sprite.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/change-size.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/change-size.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/glide-around.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/glide-around.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/hide-and-show.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/hide-and-show.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/move-arrow-keys.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/move-arrow-keys.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/record-a-sound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/record-a-sound.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videos/spin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videos/spin.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videosensing/animate-char-jump.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videosensing/animate-char-jump.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/videosensing/lib_video_sensing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videosensing/lib_video_sensing.jpg -------------------------------------------------------------------------------- /src/lib/libraries/decks/videosensing/videosens-add-extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videosensing/videosens-add-extension.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/videosensing/videosens-animate.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videosensing/videosens-animate.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/videosensing/videosens-pet-cat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videosensing/videosens-pet-cat.gif -------------------------------------------------------------------------------- /src/lib/libraries/decks/videosensing/videosens-pop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/decks/videosensing/videosens-pop.gif -------------------------------------------------------------------------------- /src/lib/libraries/extensions/boost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/boost.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/ev3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/ev3.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/makeymakey-small.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/libraries/extensions/makeymakey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/makeymakey.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/microbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/microbit.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/music.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/pen-small.svg: -------------------------------------------------------------------------------- 1 | pen-icon -------------------------------------------------------------------------------- /src/lib/libraries/extensions/pen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/pen.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/physics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/physics.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/physicsIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | ]> 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/lib/libraries/extensions/speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/speech.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/text2speech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/text2speech.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/translate-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/translate-small.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/translate.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/video-sensing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/video-sensing.png -------------------------------------------------------------------------------- /src/lib/libraries/extensions/wedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/lib/libraries/extensions/wedo.png -------------------------------------------------------------------------------- /src/lib/libraries/sound-tags.js: -------------------------------------------------------------------------------- 1 | import messages from './tag-messages.js'; 2 | export default [ 3 | {tag: 'animals', intlLabel: messages.animals}, 4 | {tag: 'effects', intlLabel: messages.effects}, 5 | {tag: 'loops', intlLabel: messages.loops}, 6 | {tag: 'notes', intlLabel: messages.notes}, 7 | {tag: 'percussion', intlLabel: messages.percussion}, 8 | {tag: 'space', intlLabel: messages.space}, 9 | {tag: 'sports', intlLabel: messages.sports}, 10 | {tag: 'voice', intlLabel: messages.voice}, 11 | {tag: 'wacky', intlLabel: messages.wacky} 12 | ]; 13 | -------------------------------------------------------------------------------- /src/lib/libraries/sprite-tags.js: -------------------------------------------------------------------------------- 1 | import messages from './tag-messages.js'; 2 | export default [ 3 | {tag: 'animals', intlLabel: messages.animals}, 4 | {tag: 'people', intlLabel: messages.people}, 5 | {tag: 'fantasy', intlLabel: messages.fantasy}, 6 | {tag: 'dance', intlLabel: messages.dance}, 7 | {tag: 'music', intlLabel: messages.music}, 8 | {tag: 'sports', intlLabel: messages.sports}, 9 | {tag: 'food', intlLabel: messages.food}, 10 | {tag: 'fashion', intlLabel: messages.fashion}, 11 | {tag: 'letters', intlLabel: messages.letters} 12 | ]; 13 | -------------------------------------------------------------------------------- /src/lib/libraries/tutorial-tags.js: -------------------------------------------------------------------------------- 1 | import messages from './tag-messages.js'; 2 | export default [ 3 | {tag: 'animation', intlLabel: messages.animation}, 4 | {tag: 'art', intlLabel: messages.art}, 5 | {tag: 'music', intlLabel: messages.music}, 6 | {tag: 'games', intlLabel: messages.games}, 7 | {tag: 'stories', intlLabel: messages.stories} 8 | ]; 9 | -------------------------------------------------------------------------------- /src/lib/locale-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview 3 | * Utility functions related to localization specific to the GUI 4 | */ 5 | 6 | const wideLocales = [ 7 | 'ab', 8 | 'ca', 9 | 'de', 10 | 'el', 11 | 'it', 12 | 'ja', 13 | 'ja-Hira', 14 | 'ko', 15 | 'hu', 16 | 'ru', 17 | 'vi' 18 | ]; 19 | 20 | /** 21 | * Identify the languages where translations are too long to fit in fixed width parts of the gui. 22 | * @param {string} locale The current locale. 23 | * @return {bool} true if translations in this language are too long 24 | */ 25 | 26 | const isWideLocale = locale => ( 27 | wideLocales.indexOf(locale) !== -1 28 | ); 29 | 30 | export { 31 | wideLocales, 32 | isWideLocale 33 | }; 34 | -------------------------------------------------------------------------------- /src/lib/log.js: -------------------------------------------------------------------------------- 1 | import minilog from 'minilog'; 2 | minilog.enable(); 3 | 4 | export default minilog('gui'); 5 | -------------------------------------------------------------------------------- /src/lib/randomize-sprite-position.js: -------------------------------------------------------------------------------- 1 | const randomizeSpritePosition = spriteObject => { 2 | // https://github.com/LLK/scratch-flash/blob/689f3c79a7e8b2e98f5be80056d877f303a8d8ba/src/Scratch.as#L1385 3 | const randomX = Math.floor((200 * Math.random()) - 100); 4 | const randomY = Math.floor((100 * Math.random()) - 50); 5 | if (spriteObject.hasOwnProperty('json')) { 6 | // Library sprite object 7 | spriteObject.json.scratchX = randomX; 8 | spriteObject.json.scratchY = randomY; 9 | } else if (spriteObject.hasOwnProperty('x') && spriteObject.hasOwnProperty('y')) { 10 | // Scratch 3 sprite object 11 | spriteObject.x = randomX; 12 | spriteObject.y = randomY; 13 | } 14 | }; 15 | 16 | export default randomizeSpritePosition; 17 | -------------------------------------------------------------------------------- /src/lib/shared-messages.js: -------------------------------------------------------------------------------- 1 | import {defineMessages} from 'react-intl'; 2 | 3 | export default defineMessages({ 4 | backdrop: { 5 | defaultMessage: 'backdrop{index}', 6 | description: 'Default name for a new backdrop, scratch will automatically adjust the number if necessary', 7 | id: 'gui.sharedMessages.backdrop' 8 | }, 9 | costume: { 10 | defaultMessage: 'costume{index}', 11 | description: 'Default name for a new costume, scratch will automatically adjust the number if necessary', 12 | id: 'gui.sharedMessages.costume' 13 | }, 14 | sprite: { 15 | defaultMessage: 'Sprite{index}', 16 | description: 'Default name for a new sprite, scratch will automatically adjust the number if necessary', 17 | id: 'gui.sharedMessages.sprite' 18 | }, 19 | pop: { 20 | defaultMessage: 'pop', 21 | description: 'Name of the pop sound, the default sound added to a sprite', 22 | id: 'gui.sharedMessages.pop' 23 | }, 24 | replaceProjectWarning: { 25 | id: 'gui.sharedMessages.replaceProjectWarning', 26 | defaultMessage: 'Replace contents of the current project?', 27 | description: 'Confirmation that user wants to overwrite the current project contents' 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/lib/supported-browser.js: -------------------------------------------------------------------------------- 1 | import bowser from 'bowser'; 2 | 3 | const minVersions = { 4 | chrome: '63', 5 | msedge: '15', 6 | firefox: '57', 7 | safari: '11' 8 | }; 9 | 10 | /** 11 | * Helper function to determine if the browser is supported at all. 12 | * @returns {boolean} False if the platform is definitely not supported. 13 | */ 14 | const supportedBrowser = () => { 15 | if (bowser.msie) { 16 | return false; 17 | } 18 | return true; 19 | }; 20 | 21 | /** 22 | * Helper function to determine if the browser meets the minimum recommended version 23 | * @returns {boolean} False if the browser isn't a recommended browser, or doesn't 24 | * meet the minimum version for recommended browsers. 25 | * NOTE: uses strict_mode==true so that any browser not listed in the minVersions 26 | * always returns false 27 | */ 28 | 29 | const recommendedBrowser = () => !bowser.isUnsupportedBrowser(minVersions, true); 30 | 31 | export { 32 | supportedBrowser as default, 33 | recommendedBrowser 34 | }; 35 | -------------------------------------------------------------------------------- /src/lib/tablet-full-screen.js: -------------------------------------------------------------------------------- 1 | import bowser from 'bowser'; 2 | 3 | /** 4 | * Helper method to request full screen in the browser when on a tablet. 5 | */ 6 | export default function () { 7 | if (bowser.tablet) { 8 | if ((bowser.webkit || bowser.blink) && document.documentElement.webkitRequestFullScreen) { 9 | document.documentElement.webkitRequestFullScreen(); 10 | } 11 | if (bowser.gecko && document.documentElement.mozRequestFullScreen) { 12 | document.documentElement.mozRequestFullScreen(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/titled-hoc.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import bindAll from 'lodash.bindall'; 3 | 4 | /* Higher Order Component to get and set the project title 5 | * @param {React.Component} WrappedComponent component to receive project title related props 6 | * @returns {React.Component} component with project loading behavior 7 | */ 8 | const TitledHOC = function (WrappedComponent) { 9 | class TitledComponent extends React.Component { 10 | constructor (props) { 11 | super(props); 12 | bindAll(this, [ 13 | 'handleUpdateProjectTitle' 14 | ]); 15 | this.state = { 16 | projectTitle: null 17 | }; 18 | } 19 | handleUpdateProjectTitle (newTitle) { 20 | this.setState({projectTitle: newTitle}); 21 | } 22 | render () { 23 | return ( 24 | 30 | ); 31 | } 32 | } 33 | 34 | return TitledComponent; 35 | }; 36 | 37 | export { 38 | TitledHOC as default 39 | }; 40 | -------------------------------------------------------------------------------- /src/lib/touch-utils.js: -------------------------------------------------------------------------------- 1 | const getEventXY = e => { 2 | if (e.touches && e.touches[0]) { 3 | return {x: e.touches[0].clientX, y: e.touches[0].clientY}; 4 | } else if (e.changedTouches && e.changedTouches[0]) { 5 | return {x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY}; 6 | } 7 | return {x: e.clientX, y: e.clientY}; 8 | }; 9 | 10 | export { 11 | getEventXY 12 | }; 13 | -------------------------------------------------------------------------------- /src/lib/variable-utils.js: -------------------------------------------------------------------------------- 1 | // Utility functions for updating variables in the VM 2 | // TODO (VM#1145) these should be moved to top-level VM API 3 | const getVariable = (vm, targetId, variableId) => { 4 | const target = targetId ? 5 | vm.runtime.getTargetById(targetId) : 6 | vm.runtime.getTargetForStage(); 7 | return target.variables[variableId]; 8 | }; 9 | 10 | const getVariableValue = (vm, targetId, variableId) => { 11 | const variable = getVariable(vm, targetId, variableId); 12 | // If array, return a new copy for mutating, ensuring that updates stay immutable. 13 | if (variable.value instanceof Array) return variable.value.slice(); 14 | return variable.value; 15 | }; 16 | 17 | const setVariableValue = (vm, targetId, variableId, value) => { 18 | getVariable(vm, targetId, variableId).value = value; 19 | }; 20 | 21 | export { 22 | getVariable, 23 | getVariableValue, 24 | setVariableValue 25 | }; 26 | -------------------------------------------------------------------------------- /src/lib/video/camera.js: -------------------------------------------------------------------------------- 1 | import getUserMedia from 'get-user-media-promise'; 2 | 3 | // Single Setup For All Video Streams used by the GUI 4 | // While VideoProvider uses a private _singleSetup 5 | // property to ensure that each instance of a VideoProvider 6 | // use the same setup, this ensures that all instances 7 | // of VideoProviders use a single stream. This way, closing a camera modal 8 | // does not affect the video on the stage, and a program running and disabling 9 | // video on the stage will not affect the camera modal's video. 10 | const requestStack = []; 11 | const requestVideoStream = videoDesc => { 12 | let streamPromise; 13 | if (requestStack.length === 0) { 14 | streamPromise = getUserMedia({ 15 | audio: false, 16 | video: videoDesc 17 | }); 18 | requestStack.push(streamPromise); 19 | } else if (requestStack.length > 0) { 20 | streamPromise = requestStack[0]; 21 | requestStack.push(true); 22 | } 23 | return streamPromise; 24 | }; 25 | 26 | const requestDisableVideo = () => { 27 | requestStack.pop(); 28 | if (requestStack.length > 0) return false; 29 | return true; 30 | }; 31 | 32 | export { 33 | requestVideoStream, 34 | requestDisableVideo 35 | }; 36 | -------------------------------------------------------------------------------- /src/playground/blocks-only.css: -------------------------------------------------------------------------------- 1 | .controls { 2 | position: absolute; 3 | z-index: 2; 4 | top: 10px; 5 | right: 15px; 6 | } 7 | -------------------------------------------------------------------------------- /src/playground/blocks-only.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {connect} from 'react-redux'; 4 | 5 | import Controls from '../containers/controls.jsx'; 6 | import Blocks from '../containers/blocks.jsx'; 7 | import GUI from '../containers/gui.jsx'; 8 | import HashParserHOC from '../lib/hash-parser-hoc.jsx'; 9 | import AppStateHOC from '../lib/app-state-hoc.jsx'; 10 | 11 | import styles from './blocks-only.css'; 12 | 13 | const mapStateToProps = state => ({vm: state.scratchGui.vm}); 14 | 15 | const VMBlocks = connect(mapStateToProps)(Blocks); 16 | const VMControls = connect(mapStateToProps)(Controls); 17 | 18 | const BlocksOnly = props => ( 19 | 20 | 26 | 27 | 28 | ); 29 | 30 | const App = AppStateHOC(HashParserHOC(BlocksOnly)); 31 | 32 | const appTarget = document.createElement('div'); 33 | document.body.appendChild(appTarget); 34 | 35 | ReactDOM.render(, appTarget); 36 | -------------------------------------------------------------------------------- /src/playground/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | .app { 4 | /* probably unecessary, transitional until layout is refactored */ 5 | width: 100%; 6 | height: 100%; 7 | margin: 0; 8 | 9 | /* Setting min height/width makes the UI scroll below those sizes */ 10 | min-width: 1024px; 11 | min-height: 640px; /* Min height to fit sprite/backdrop button */ 12 | } 13 | 14 | /* @todo: move globally? Safe / side FX, for blocks particularly? */ 15 | * { box-sizing: border-box; } 16 | -------------------------------------------------------------------------------- /src/playground/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | <% if (htmlWebpackPlugin.options.sentryConfig) { %> 10 | 11 | 12 | 15 | 16 | <% } %> 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/playground/index.jsx: -------------------------------------------------------------------------------- 1 | // Polyfills 2 | import 'es6-object-assign/auto'; 3 | import 'core-js/fn/array/includes'; 4 | import 'core-js/fn/promise/finally'; 5 | import 'intl'; // For Safari 9 6 | 7 | import React from 'react'; 8 | import ReactDOM from 'react-dom'; 9 | 10 | import analytics from '../lib/analytics'; 11 | import AppStateHOC from '../lib/app-state-hoc.jsx'; 12 | import BrowserModalComponent from '../components/browser-modal/browser-modal.jsx'; 13 | import supportedBrowser from '../lib/supported-browser'; 14 | 15 | import styles from './index.css'; 16 | 17 | // Register "base" page view 18 | analytics.pageview('/'); 19 | 20 | const appTarget = document.createElement('div'); 21 | appTarget.className = styles.app; 22 | document.body.appendChild(appTarget); 23 | 24 | if (supportedBrowser()) { 25 | // require needed here to avoid importing unsupported browser-crashing code 26 | // at the top level 27 | require('./render-gui.jsx').default(appTarget); 28 | 29 | } else { 30 | BrowserModalComponent.setAppElement(appTarget); 31 | const WrappedBrowserModalComponent = AppStateHOC(BrowserModalComponent, true /* localesOnly */); 32 | const handleBack = () => {}; 33 | // eslint-disable-next-line react/jsx-no-bind 34 | ReactDOM.render(, appTarget); 35 | } 36 | -------------------------------------------------------------------------------- /src/playground/player.css: -------------------------------------------------------------------------------- 1 | .stage-only { 2 | width: calc(480px + 1rem); 3 | } 4 | 5 | .editor { 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | height: 100%; 10 | width: 100%; 11 | } 12 | 13 | .stage-only * { 14 | box-sizing: border-box; 15 | } 16 | -------------------------------------------------------------------------------- /src/reducers/asset-drag.js: -------------------------------------------------------------------------------- 1 | const DRAG_UPDATE = 'scratch-gui/asset-drag/DRAG_UPDATE'; 2 | 3 | const initialState = { 4 | dragging: false, 5 | currentOffset: null, 6 | img: null 7 | }; 8 | 9 | const reducer = function (state, action) { 10 | if (typeof state === 'undefined') state = initialState; 11 | 12 | switch (action.type) { 13 | case DRAG_UPDATE: 14 | return Object.assign({}, state, action.state); 15 | default: 16 | return state; 17 | } 18 | }; 19 | 20 | const updateAssetDrag = function (state) { 21 | return { 22 | type: DRAG_UPDATE, 23 | state: state 24 | }; 25 | }; 26 | 27 | export { 28 | reducer as default, 29 | initialState as assetDragInitialState, 30 | updateAssetDrag 31 | }; 32 | -------------------------------------------------------------------------------- /src/reducers/block-drag.js: -------------------------------------------------------------------------------- 1 | const BLOCK_DRAG_UPDATE = 'scratch-gui/block-drag/BLOCK_DRAG_UPDATE'; 2 | 3 | const initialState = false; 4 | 5 | const reducer = function (state, action) { 6 | if (typeof state === 'undefined') state = initialState; 7 | switch (action.type) { 8 | case BLOCK_DRAG_UPDATE: 9 | return action.areBlocksOverGui; 10 | default: 11 | return state; 12 | } 13 | }; 14 | 15 | const updateBlockDrag = function (areBlocksOverGui) { 16 | return { 17 | type: BLOCK_DRAG_UPDATE, 18 | areBlocksOverGui: areBlocksOverGui, 19 | meta: { 20 | throttle: 30 21 | } 22 | }; 23 | }; 24 | 25 | export { 26 | reducer as default, 27 | initialState as blockDragInitialState, 28 | updateBlockDrag 29 | }; 30 | -------------------------------------------------------------------------------- /src/reducers/connection-modal.js: -------------------------------------------------------------------------------- 1 | const SET_ID = 'scratch-gui/connection-modal/setId'; 2 | 3 | const initialState = { 4 | extensionId: null 5 | }; 6 | 7 | const reducer = function (state, action) { 8 | if (typeof state === 'undefined') state = initialState; 9 | switch (action.type) { 10 | case SET_ID: 11 | return Object.assign({}, state, { 12 | extensionId: action.extensionId 13 | }); 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | const setConnectionModalExtensionId = function (extensionId) { 20 | return { 21 | type: SET_ID, 22 | extensionId: extensionId 23 | }; 24 | }; 25 | 26 | export { 27 | reducer as default, 28 | initialState as connectionModalInitialState, 29 | setConnectionModalExtensionId 30 | }; 31 | -------------------------------------------------------------------------------- /src/reducers/editor-tab.js: -------------------------------------------------------------------------------- 1 | const ACTIVATE_TAB = 'scratch-gui/navigation/ACTIVATE_TAB'; 2 | 3 | // Constants use numbers to make it easier to work with react-tabs 4 | const BLOCKS_TAB_INDEX = 0; 5 | const COSTUMES_TAB_INDEX = 1; 6 | const SOUNDS_TAB_INDEX = 2; 7 | 8 | const initialState = { 9 | activeTabIndex: BLOCKS_TAB_INDEX 10 | }; 11 | 12 | const reducer = function (state, action) { 13 | if (typeof state === 'undefined') state = initialState; 14 | switch (action.type) { 15 | case ACTIVATE_TAB: 16 | return Object.assign({}, state, { 17 | activeTabIndex: action.activeTabIndex 18 | }); 19 | default: 20 | return state; 21 | } 22 | }; 23 | 24 | const activateTab = function (tab) { 25 | return { 26 | type: ACTIVATE_TAB, 27 | activeTabIndex: tab 28 | }; 29 | }; 30 | 31 | export { 32 | reducer as default, 33 | initialState as editorTabInitialState, 34 | activateTab, 35 | BLOCKS_TAB_INDEX, 36 | COSTUMES_TAB_INDEX, 37 | SOUNDS_TAB_INDEX 38 | }; 39 | -------------------------------------------------------------------------------- /src/reducers/fonts-loaded.js: -------------------------------------------------------------------------------- 1 | const SET_FONTS_LOADED = 'fontsLoaded/SET_FONTS_LOADED'; 2 | 3 | const initialState = false; 4 | 5 | const reducer = function (state, action) { 6 | if (typeof state === 'undefined') state = initialState; 7 | switch (action.type) { 8 | case SET_FONTS_LOADED: 9 | return action.loaded; 10 | default: 11 | return state; 12 | } 13 | }; 14 | const setFontsLoaded = () => ({ 15 | type: SET_FONTS_LOADED, 16 | loaded: true 17 | }); 18 | 19 | export { 20 | reducer as default, 21 | initialState as fontsLoadedInitialState, 22 | setFontsLoaded 23 | }; 24 | -------------------------------------------------------------------------------- /src/reducers/hovered-target.js: -------------------------------------------------------------------------------- 1 | const SET_HOVERED_SPRITE = 'scratch-gui/hovered-target/SET_HOVERED_SPRITE'; 2 | const SET_RECEIVED_BLOCKS = 'scratch-gui/hovered-target/SET_RECEIVED_BLOCKS'; 3 | 4 | const initialState = { 5 | sprite: null, 6 | receivedBlocks: false 7 | }; 8 | 9 | const reducer = function (state, action) { 10 | if (typeof state === 'undefined') state = initialState; 11 | switch (action.type) { 12 | case SET_HOVERED_SPRITE: 13 | return { 14 | sprite: action.spriteId, 15 | receivedBlocks: false 16 | }; 17 | case SET_RECEIVED_BLOCKS: 18 | return { 19 | sprite: state.sprite, 20 | receivedBlocks: action.receivedBlocks 21 | }; 22 | default: 23 | return state; 24 | } 25 | }; 26 | 27 | const setHoveredSprite = function (spriteId) { 28 | return { 29 | type: SET_HOVERED_SPRITE, 30 | spriteId: spriteId, 31 | meta: { 32 | throttle: 30 33 | } 34 | }; 35 | }; 36 | 37 | const setReceivedBlocks = function (receivedBlocks) { 38 | return { 39 | type: SET_RECEIVED_BLOCKS, 40 | receivedBlocks: receivedBlocks 41 | }; 42 | }; 43 | 44 | export { 45 | reducer as default, 46 | initialState as hoveredTargetInitialState, 47 | setHoveredSprite, 48 | setReceivedBlocks 49 | }; 50 | -------------------------------------------------------------------------------- /src/reducers/mic-indicator.js: -------------------------------------------------------------------------------- 1 | const UPDATE = 'scratch-gui/mic-indicator/UPDATE'; 2 | 3 | const initialState = false; 4 | 5 | const reducer = function (state, action) { 6 | if (typeof state === 'undefined') state = initialState; 7 | switch (action.type) { 8 | case UPDATE: 9 | return action.visible; 10 | default: 11 | return state; 12 | } 13 | }; 14 | 15 | const updateMicIndicator = function (visible) { 16 | return { 17 | type: UPDATE, 18 | visible: visible 19 | }; 20 | }; 21 | 22 | export { 23 | reducer as default, 24 | initialState as micIndicatorInitialState, 25 | updateMicIndicator 26 | }; 27 | -------------------------------------------------------------------------------- /src/reducers/mode.js: -------------------------------------------------------------------------------- 1 | const SET_FULL_SCREEN = 'scratch-gui/mode/SET_FULL_SCREEN'; 2 | const SET_PLAYER = 'scratch-gui/mode/SET_PLAYER'; 3 | 4 | const initialState = { 5 | showBranding: false, 6 | isFullScreen: false, 7 | isPlayerOnly: false, 8 | hasEverEnteredEditor: true 9 | }; 10 | 11 | const reducer = function (state, action) { 12 | if (typeof state === 'undefined') state = initialState; 13 | switch (action.type) { 14 | case SET_FULL_SCREEN: 15 | return Object.assign({}, state, { 16 | isFullScreen: action.isFullScreen 17 | }); 18 | case SET_PLAYER: 19 | return Object.assign({}, state, { 20 | isPlayerOnly: action.isPlayerOnly, 21 | hasEverEnteredEditor: state.hasEverEnteredEditor || !action.isPlayerOnly 22 | }); 23 | default: 24 | return state; 25 | } 26 | }; 27 | 28 | const setFullScreen = function (isFullScreen) { 29 | return { 30 | type: SET_FULL_SCREEN, 31 | isFullScreen: isFullScreen 32 | }; 33 | }; 34 | const setPlayer = function (isPlayerOnly) { 35 | return { 36 | type: SET_PLAYER, 37 | isPlayerOnly: isPlayerOnly 38 | }; 39 | }; 40 | 41 | export { 42 | reducer as default, 43 | initialState as modeInitialState, 44 | setFullScreen, 45 | setPlayer 46 | }; 47 | -------------------------------------------------------------------------------- /src/reducers/monitors.js: -------------------------------------------------------------------------------- 1 | const UPDATE_MONITORS = 'scratch-gui/monitors/UPDATE_MONITORS'; 2 | import {OrderedMap} from 'immutable'; 3 | 4 | const initialState = OrderedMap(); 5 | 6 | const reducer = function (state, action) { 7 | if (typeof state === 'undefined') state = initialState; 8 | switch (action.type) { 9 | case UPDATE_MONITORS: 10 | return action.monitors; 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | const updateMonitors = function (monitors) { 17 | return { 18 | type: UPDATE_MONITORS, 19 | monitors: monitors, 20 | meta: { 21 | throttle: 30 22 | } 23 | }; 24 | }; 25 | 26 | export { 27 | reducer as default, 28 | initialState as monitorsInitialState, 29 | updateMonitors 30 | }; 31 | -------------------------------------------------------------------------------- /src/reducers/project-changed.js: -------------------------------------------------------------------------------- 1 | const SET_PROJECT_CHANGED = 'scratch-gui/project-changed/SET_PROJECT_CHANGED'; 2 | 3 | const initialState = false; 4 | 5 | const reducer = function (state, action) { 6 | if (typeof state === 'undefined') state = initialState; 7 | switch (action.type) { 8 | case SET_PROJECT_CHANGED: 9 | return action.changed; 10 | default: 11 | return state; 12 | } 13 | }; 14 | const setProjectChanged = () => ({ 15 | type: SET_PROJECT_CHANGED, 16 | changed: true 17 | }); 18 | const setProjectUnchanged = () => ({ 19 | type: SET_PROJECT_CHANGED, 20 | changed: false 21 | }); 22 | 23 | export { 24 | reducer as default, 25 | initialState as projectChangedInitialState, 26 | setProjectChanged, 27 | setProjectUnchanged 28 | }; 29 | -------------------------------------------------------------------------------- /src/reducers/project-title.js: -------------------------------------------------------------------------------- 1 | const SET_PROJECT_TITLE = 'projectTitle/SET_PROJECT_TITLE'; 2 | 3 | // we are initializing to a blank string instead of an actual title, 4 | // because it would be hard to localize here 5 | const initialState = ''; 6 | 7 | const reducer = function (state, action) { 8 | if (typeof state === 'undefined') state = initialState; 9 | switch (action.type) { 10 | case SET_PROJECT_TITLE: 11 | return action.title; 12 | default: 13 | return state; 14 | } 15 | }; 16 | const setProjectTitle = title => ({ 17 | type: SET_PROJECT_TITLE, 18 | title: title 19 | }); 20 | 21 | export { 22 | reducer as default, 23 | initialState as projectTitleInitialState, 24 | setProjectTitle 25 | }; 26 | -------------------------------------------------------------------------------- /src/reducers/restore-deletion.js: -------------------------------------------------------------------------------- 1 | const RESTORE_UPDATE = 'scratch-gui/restore-deletion/RESTORE_UPDATE'; 2 | 3 | const initialState = { 4 | restoreFun: null, 5 | deletedItem: '' 6 | }; 7 | 8 | const reducer = function (state, action) { 9 | if (typeof state === 'undefined') state = initialState; 10 | 11 | switch (action.type) { 12 | case RESTORE_UPDATE: 13 | return Object.assign({}, state, action.state); 14 | default: 15 | return state; 16 | } 17 | }; 18 | 19 | const setRestore = function (state) { 20 | return { 21 | type: RESTORE_UPDATE, 22 | state: { 23 | restoreFun: state.restoreFun, 24 | deletedItem: state.deletedItem 25 | } 26 | }; 27 | }; 28 | 29 | export { 30 | reducer as default, 31 | initialState as restoreDeletionInitialState, 32 | setRestore 33 | }; 34 | -------------------------------------------------------------------------------- /src/reducers/stage-size.js: -------------------------------------------------------------------------------- 1 | import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants.js'; 2 | 3 | const SET_STAGE_SIZE = 'scratch-gui/StageSize/SET_STAGE_SIZE'; 4 | 5 | const initialState = { 6 | stageSize: STAGE_DISPLAY_SIZES.large 7 | }; 8 | 9 | const reducer = function (state, action) { 10 | if (typeof state === 'undefined') state = initialState; 11 | switch (action.type) { 12 | case SET_STAGE_SIZE: 13 | return { 14 | stageSize: action.stageSize 15 | }; 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | const setStageSize = function (stageSize) { 22 | return { 23 | type: SET_STAGE_SIZE, 24 | stageSize: stageSize 25 | }; 26 | }; 27 | 28 | export { 29 | reducer as default, 30 | initialState as stageSizeInitialState, 31 | setStageSize 32 | }; 33 | -------------------------------------------------------------------------------- /src/reducers/timeout.js: -------------------------------------------------------------------------------- 1 | const SET_AUTOSAVE_TIMEOUT_ID = 'timeout/SET_AUTOSAVE_TIMEOUT_ID'; 2 | 3 | const initialState = { 4 | autoSaveTimeoutId: null 5 | }; 6 | 7 | const reducer = function (state, action) { 8 | if (typeof state === 'undefined') state = initialState; 9 | switch (action.type) { 10 | case SET_AUTOSAVE_TIMEOUT_ID: 11 | return Object.assign({}, state, { 12 | autoSaveTimeoutId: action.id 13 | }); 14 | default: 15 | return state; 16 | } 17 | }; 18 | const setAutoSaveTimeoutId = id => ({ 19 | type: SET_AUTOSAVE_TIMEOUT_ID, 20 | id 21 | }); 22 | 23 | export { 24 | reducer as default, 25 | initialState as timeoutInitialState, 26 | setAutoSaveTimeoutId 27 | }; 28 | -------------------------------------------------------------------------------- /src/reducers/toolbox.js: -------------------------------------------------------------------------------- 1 | const UPDATE_TOOLBOX = 'scratch-gui/toolbox/UPDATE_TOOLBOX'; 2 | import makeToolboxXML from '../lib/make-toolbox-xml'; 3 | 4 | const initialState = { 5 | toolboxXML: makeToolboxXML(true) 6 | }; 7 | 8 | const reducer = function (state, action) { 9 | if (typeof state === 'undefined') state = initialState; 10 | switch (action.type) { 11 | case UPDATE_TOOLBOX: 12 | return Object.assign({}, state, { 13 | toolboxXML: action.toolboxXML 14 | }); 15 | default: 16 | return state; 17 | } 18 | }; 19 | 20 | const updateToolbox = function (toolboxXML) { 21 | return { 22 | type: UPDATE_TOOLBOX, 23 | toolboxXML: toolboxXML 24 | }; 25 | }; 26 | 27 | export { 28 | reducer as default, 29 | initialState as toolboxInitialState, 30 | updateToolbox 31 | }; 32 | -------------------------------------------------------------------------------- /src/reducers/vm.js: -------------------------------------------------------------------------------- 1 | import VM from 'scratch-vm'; 2 | import storage from '../lib/storage'; 3 | 4 | const SET_VM = 'scratch-gui/vm/SET_VM'; 5 | const defaultVM = new VM(); 6 | defaultVM.attachStorage(storage); 7 | const initialState = defaultVM; 8 | 9 | const reducer = function (state, action) { 10 | if (typeof state === 'undefined') state = initialState; 11 | switch (action.type) { 12 | case SET_VM: 13 | return action.vm; 14 | default: 15 | return state; 16 | } 17 | }; 18 | const setVM = function (vm) { 19 | return { 20 | type: SET_VM, 21 | vm: vm 22 | }; 23 | }; 24 | 25 | export { 26 | reducer as default, 27 | initialState as vmInitialState, 28 | setVM 29 | }; 30 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/src/test.js -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/static/favicon.ico -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['scratch/react', 'scratch/es6'], 3 | env: { 4 | browser: true, 5 | jest: true 6 | }, 7 | rules: { 8 | 'react/prop-types': 0 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /test/__mocks__/audio-buffer-player.js: -------------------------------------------------------------------------------- 1 | export default class MockAudioBufferPlayer { 2 | constructor (samples, sampleRate) { 3 | this.samples = samples; 4 | this.sampleRate = sampleRate; 5 | this.buffer = { 6 | getChannelData: jest.fn(() => samples), 7 | sampleRate: sampleRate 8 | }; 9 | this.play = jest.fn((trimStart, trimEnd, onUpdate) => { 10 | this.onUpdate = onUpdate; 11 | }); 12 | this.stop = jest.fn(); 13 | MockAudioBufferPlayer.instance = this; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/__mocks__/audio-effects.js: -------------------------------------------------------------------------------- 1 | export default class MockAudioEffects { 2 | static get effectTypes () { // @todo can this be imported from the real file? 3 | return { 4 | ROBOT: 'robot', 5 | REVERSE: 'reverse', 6 | LOUDER: 'higher', 7 | SOFTER: 'lower', 8 | FASTER: 'faster', 9 | SLOWER: 'slower', 10 | ECHO: 'echo' 11 | }; 12 | } 13 | constructor (buffer, name) { 14 | this.buffer = buffer; 15 | this.name = name; 16 | this.process = jest.fn(done => { 17 | this._finishProcessing = renderedBuffer => done({renderedBuffer}); 18 | }); 19 | MockAudioEffects.instance = this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | // __mocks__/fileMock.js 2 | 3 | module.exports = 'test-file-stub'; 4 | -------------------------------------------------------------------------------- /test/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | // __mocks__/styleMock.js 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /test/fixtures/gh-3582-png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/test/fixtures/gh-3582-png.png -------------------------------------------------------------------------------- /test/fixtures/movie.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/test/fixtures/movie.wav -------------------------------------------------------------------------------- /test/fixtures/paddleball.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/test/fixtures/paddleball.gif -------------------------------------------------------------------------------- /test/fixtures/project1.sb3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/test/fixtures/project1.sb3 -------------------------------------------------------------------------------- /test/fixtures/sneaker.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/griffpatch/scratch-gui/2b0feb66321c073b5765e8960c7d15c5c99254bc/test/fixtures/sneaker.wav -------------------------------------------------------------------------------- /test/helpers/enzyme-setup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | Enzyme.configure({adapter: new Adapter()}); 5 | -------------------------------------------------------------------------------- /test/helpers/intl-helpers.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Helpers for using enzyme and react-test-renderer with react-intl 3 | * Directly from https://github.com/yahoo/react-intl/wiki/Testing-with-React-Intl 4 | */ 5 | import React from 'react'; 6 | import renderer from 'react-test-renderer'; 7 | import {IntlProvider, intlShape} from 'react-intl'; 8 | import {mount, shallow} from 'enzyme'; 9 | 10 | const intlProvider = new IntlProvider({locale: 'en'}, {}); 11 | const {intl} = intlProvider.getChildContext(); 12 | 13 | const nodeWithIntlProp = node => React.cloneElement(node, {intl}); 14 | 15 | const shallowWithIntl = (node, {context} = {}) => shallow( 16 | nodeWithIntlProp(node), 17 | { 18 | context: Object.assign({}, context, {intl}) 19 | } 20 | ); 21 | 22 | const mountWithIntl = (node, {context, childContextTypes} = {}) => mount( 23 | nodeWithIntlProp(node), 24 | { 25 | context: Object.assign({}, context, {intl}), 26 | childContextTypes: Object.assign({}, {intl: intlShape}, childContextTypes) 27 | } 28 | ); 29 | 30 | // react-test-renderer component for use with snapshot testing 31 | const componentWithIntl = (children, props = {locale: 'en'}) => renderer.create( 32 | {children} 33 | ); 34 | 35 | export { 36 | componentWithIntl, 37 | shallowWithIntl, 38 | mountWithIntl 39 | }; 40 | -------------------------------------------------------------------------------- /test/integration/how-tos.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import SeleniumHelper from '../helpers/selenium-helper'; 3 | 4 | const { 5 | clickText, 6 | clickXpath, 7 | findByXpath, 8 | getDriver, 9 | getLogs, 10 | loadUri 11 | } = new SeleniumHelper(); 12 | 13 | const uri = path.resolve(__dirname, '../../build/index.html'); 14 | 15 | let driver; 16 | 17 | describe('Working with the how-to library', () => { 18 | beforeAll(() => { 19 | driver = getDriver(); 20 | }); 21 | 22 | afterAll(async () => { 23 | await driver.quit(); 24 | }); 25 | 26 | test('Choosing a how-to', async () => { 27 | await loadUri(uri); 28 | await clickText('Costumes'); 29 | await clickXpath('//*[@aria-label="Tutorials"]'); 30 | await clickText('Getting Started'); // Modal should close 31 | // Make sure YouTube video on first card appears 32 | await findByXpath('//div[contains(@class, "step-video")]'); 33 | const logs = await getLogs(); 34 | await expect(logs).toEqual([]); 35 | }); 36 | 37 | // @todo navigating cards, etc. 38 | }); 39 | -------------------------------------------------------------------------------- /test/integration/stage-size.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import SeleniumHelper from '../helpers/selenium-helper'; 3 | 4 | const { 5 | clickText, 6 | clickXpath, 7 | rightClickText, 8 | getDriver, 9 | getLogs, 10 | loadUri, 11 | scope 12 | } = new SeleniumHelper(); 13 | 14 | const uri = path.resolve(__dirname, '../../build/index.html'); 15 | 16 | let driver; 17 | 18 | describe('Loading scratch gui', () => { 19 | beforeAll(() => { 20 | driver = getDriver(); 21 | }); 22 | 23 | afterAll(async () => { 24 | await driver.quit(); 25 | }); 26 | 27 | test('Switching small/large stage after highlighting and deleting sprite', async () => { 28 | await loadUri(uri); 29 | 30 | // Highlight the sprite 31 | await clickText('Sprite1', scope.spriteTile); 32 | 33 | // Delete it 34 | await rightClickText('Sprite1', scope.spriteTile); 35 | await clickText('delete', scope.spriteTile); 36 | 37 | // Go to small stage mode 38 | await clickXpath('//img[@alt="Switch to small stage"]'); 39 | 40 | // Confirm app still working 41 | await clickXpath('//img[@alt="Switch to large stage"]'); 42 | 43 | const logs = await getLogs(); 44 | await expect(logs).toEqual([]); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/integration/tutorials-shortcut.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import SeleniumHelper from '../helpers/selenium-helper'; 3 | 4 | const { 5 | clickText, 6 | findByXpath, 7 | getDriver, 8 | loadUri 9 | } = new SeleniumHelper(); 10 | 11 | const uri = path.resolve(__dirname, '../../build/index.html?tutorial=all'); 12 | const uriPrefix = path.resolve(__dirname, '../../build/index.html?tutorial='); 13 | 14 | let driver; 15 | 16 | describe('Working with shortcut to Tutorials library', () => { 17 | beforeAll(() => { 18 | driver = getDriver(); 19 | }); 20 | 21 | afterAll(async () => { 22 | await driver.quit(); 23 | }); 24 | 25 | test('opens with the Tutorial Library showing', async () => { 26 | await loadUri(uri); 27 | // make sure there is a tutorial visible that doesn't have a shortcut 28 | await clickText('Make It Spin'); 29 | await findByXpath('//div[contains(@class, "step-video")]'); 30 | }); 31 | 32 | test('can open hidden tutorials', async () => { 33 | await loadUri(`${uriPrefix}whatsnew`); 34 | // should open the tutorial video immediately 35 | await findByXpath('//div[contains(@class, "step-video")]'); 36 | }); 37 | // @todo navigating cards, etc. 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/components/__snapshots__/button.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ButtonComponent matches snapshot 1`] = ` 4 | 9 |
12 | 13 | `; 14 | -------------------------------------------------------------------------------- /test/unit/components/__snapshots__/icon-button.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`IconButtonComponent matches snapshot 1`] = ` 4 |
9 | 14 |
17 |
18 | Text 19 |
20 |
21 |
22 | `; 23 | -------------------------------------------------------------------------------- /test/unit/components/button.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {shallow} from 'enzyme'; 3 | import ButtonComponent from '../../../src/components/button/button'; 4 | import renderer from 'react-test-renderer'; 5 | 6 | describe('ButtonComponent', () => { 7 | test('matches snapshot', () => { 8 | const onClick = jest.fn(); 9 | const component = renderer.create( 10 | 11 | ); 12 | expect(component.toJSON()).toMatchSnapshot(); 13 | }); 14 | 15 | test('triggers callback when clicked', () => { 16 | const onClick = jest.fn(); 17 | const componentShallowWrapper = shallow( 18 | 19 | ); 20 | componentShallowWrapper.simulate('click'); 21 | expect(onClick).toHaveBeenCalled(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/components/icon-button.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {shallow} from 'enzyme'; 3 | import IconButton from '../../../src/components/icon-button/icon-button'; 4 | import renderer from 'react-test-renderer'; 5 | 6 | describe('IconButtonComponent', () => { 7 | test('matches snapshot', () => { 8 | const onClick = jest.fn(); 9 | const title =
Text
; 10 | const imgSrc = 'imgSrc'; 11 | const className = 'custom-class-name'; 12 | const component = renderer.create( 13 | 19 | ); 20 | expect(component.toJSON()).toMatchSnapshot(); 21 | }); 22 | 23 | test('triggers callback when clicked', () => { 24 | const onClick = jest.fn(); 25 | const title =
Text
; 26 | const imgSrc = 'imgSrc'; 27 | const componentShallowWrapper = shallow( 28 | 33 | ); 34 | componentShallowWrapper.simulate('click'); 35 | expect(onClick).toHaveBeenCalled(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/unit/util/code-payload.test.js: -------------------------------------------------------------------------------- 1 | jest.mock('../../../src/lib/backpack/block-to-image', () => () => Promise.resolve('block-image')); 2 | jest.mock('../../../src/lib/backpack/jpeg-thumbnail', () => () => Promise.resolve('thumbnail')); 3 | 4 | import codePayload from '../../../src/lib/backpack/code-payload'; 5 | import {Base64} from 'js-base64'; 6 | 7 | describe('codePayload', () => { 8 | test('base64 encodes the blocks as json', () => { 9 | const blocks = '☁︎❤️🐻'; 10 | const payload = codePayload({ 11 | blockObjects: blocks 12 | }); 13 | return payload.then(p => { 14 | expect( 15 | JSON.parse(Base64.decode(p.body)) 16 | ).toEqual(blocks); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /test/unit/util/get-costume-url.test.js: -------------------------------------------------------------------------------- 1 | import {HAS_FONT_REGEXP} from '../../../src/lib/get-costume-url'; 2 | 3 | describe('SVG Font Parsing', () => { 4 | test('Has font regexp works', () => { 5 | expect('font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy(); 6 | expect('font-family="none" font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy(); 7 | expect('font-family = "Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy(); 8 | 9 | expect('font-family="none"'.match(HAS_FONT_REGEXP)).toBeFalsy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/unit/util/opcode-labels.test.js: -------------------------------------------------------------------------------- 1 | import opcodeLabels from '../../../src/lib/opcode-labels'; 2 | 3 | describe('Opcode Labels', () => { 4 | test('day of week label', () => { 5 | const labelFun = opcodeLabels.getLabel('sensing_current').labelFn; 6 | expect(labelFun({CURRENTMENU: 'dayofweek'})).toBe('day of week'); 7 | expect(labelFun({CURRENTMENU: 'DAYOFWEEK'})).toBe('day of week'); 8 | }); 9 | 10 | test('unspecified opcodes default to extension category and opcode as label', () => { 11 | const labelInfo = opcodeLabels.getLabel('music_getTempo'); 12 | expect(labelInfo.label).toBe('music_getTempo'); 13 | expect(labelInfo.category).toBe('extension'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/unit/util/translate-video.test.js: -------------------------------------------------------------------------------- 1 | import {translateVideo} from '../../../src/lib/libraries/decks/translate-video.js'; 2 | 3 | describe('translateVideo', () => { 4 | test('returns the id if it is not found', () => { 5 | expect(translateVideo('not-a-key', 'en')).toEqual('not-a-key'); 6 | }); 7 | 8 | test('returns the expected id for Japanese', () => { 9 | expect(translateVideo('intro-move-sayhello', 'ja')).toEqual('v2c2f3y2sc'); 10 | }); 11 | 12 | test('returns the expected id for English', () => { 13 | expect(translateVideo('intro-move-sayhello', 'en')).toEqual('rpjvs3v9gj'); 14 | }); 15 | 16 | test('returns the English id for non-existent locales', () => { 17 | expect(translateVideo('intro-move-sayhello', 'yum')).toEqual('rpjvs3v9gj'); 18 | }); 19 | }); 20 | --------------------------------------------------------------------------------