Compare commits

..
Author SHA1 Message Date
Eric Rosenbaum 19e392d061 Merge pull request #5263 from LLK/hotfix/hoc-2-tutorials-2019
[hotfix] Add 2 HoC tutorials
2019-10-24 15:24:32 -04:00
Eric Rosenbaum f19ac7459a Don’t publish duplicate assets 2019-10-22 14:57:09 -04:00
Eric Rosenbaum 3fe729c025 Remove duplicate 2019-10-22 14:26:29 -04:00
Zoë Bentley 428b5da5b6 Reduced file size of GIFs 2019-10-22 14:26:29 -04:00
Zoë Bentley 78d831d6af Linter
# Conflicts:
#	src/lib/libraries/decks/en-steps.js
#	src/lib/libraries/decks/index.jsx
2019-10-22 14:26:29 -04:00
Zoë Bentley 8f7a9d96f1 Edited one GIF to be slower
# Conflicts:
#	src/lib/libraries/decks/en-steps.js
#	src/lib/libraries/decks/index.jsx
#	src/lib/libraries/decks/translate-video.js
2019-10-22 14:26:29 -04:00
Zoë Bentley c23678ef3d Made Travis happy (shortened length of one GIF name) 2019-10-22 14:26:29 -04:00
Zoë Bentley 6f2a0b057a Code a Cartoon 10/21 2019-10-22 14:26:29 -04:00
Zoë Bentley e73c5ce91c Changed order of tutorials
# Conflicts:
#	package-lock.json
#	package.json
2019-10-22 14:26:29 -04:00
Zoë Bentley be8873a6e0 Changed order of tutorials actually 2019-10-22 14:26:29 -04:00
Zoë Bentley 43f5f0aebc Reverted the tips-library.jsx file to its correct state 2019-10-22 14:26:29 -04:00
Zoë Bentley 84b6c33813 Optimized images and made small tweaks to GIFs 2019-10-22 14:26:29 -04:00
Zoë Bentley deff6dd385 Added first draft of Code a Cartoon video and made some other small changes 2019-10-22 14:26:29 -04:00
Zoë Bentley ca0c47f742 Draft of Code a Cartoon tutorial for Oct 1st 2019-10-22 14:26:29 -04:00
Ray Schamp 5253554fff Merge pull request #4854 from towerofnix/list-whitespace
Always display whitespace in list monitors
2019-10-22 09:46:54 -04:00
Florrie 81c22f369c Always display whitespace in all monitors 2019-10-21 20:57:09 -03:00
Karishma Chadha fde5b56e65 Merge pull request #5259 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20191021180653
Update scratch-render to the latest version 🚀
2019-10-21 16:28:09 -04:00
greenkeeper[bot] c7b4e1b832 chore(package): update lockfile package-lock.json 2019-10-21 18:53:21 +00:00
greenkeeper[bot] a37868f921 chore(package): update scratch-render to version 0.1.0-prerelease.20191021180653 2019-10-21 18:53:14 +00:00
Benjamin Wheeler 395b55868b Merge pull request #5168 from benjiwheeler/clear-title-on-default-project
Refactor title handling
2019-10-21 10:52:02 -04:00
Ben Wheeler e3fa196ade revised name of updateReduxProjectTitle, updateReduxProjectTitleWithDefault 2019-10-18 17:53:28 -04:00
Ben Wheeler f4a9bfac9f in titledhoc, consolidate function calls 2019-10-18 13:41:20 -04:00
Ben Wheeler b6ff1f40b5 better names for prop functions 2019-10-18 13:40:58 -04:00
Paul Kaplan 758b0ef1ff Merge pull request #5253 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20191017151616
Update scratch-vm to the latest version 🚀
2019-10-17 14:24:55 -04:00
Ben Wheeler 68780ae6cf added back missing canEditTitle 2019-10-17 13:26:34 -04:00
Ben Wheeler 22e2c17ff4 don’t pass onUpdateProjectTitle below titledHIC 2019-10-17 13:26:33 -04:00
Ben Wheeler 9c33bbb2ee remove TitledHOC from playground, and explicitly pass canEditTitle 2019-10-17 13:26:33 -04:00
Ben Wheeler b63ac51fb1 move project title management into titled hoc 2019-10-17 13:26:33 -04:00
Ben Wheeler 91e2fc92f3 on showing project without id, tell owner of GUI to set title to default 2019-10-17 13:26:33 -04:00
Paul Kaplan a3f0652e7d Update scratch-vm to pull in video fixes 2019-10-17 11:25:32 -04:00
greenkeeper[bot] 7193c50bbe chore(package): update lockfile package-lock.json 2019-10-17 15:19:16 +00:00
greenkeeper[bot] be0259f543 chore(package): update scratch-vm to version 0.2.0-prerelease.20191017151616 2019-10-17 15:19:12 +00:00
Benjamin Wheeler 5fed732e7e Merge pull request #5241 from benjiwheeler/extension-line-height
Fix extension modal tiles' description line height
2019-10-16 17:42:51 -04:00
Paul Kaplan 33d7181432 Merge pull request #5248 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20191015002244
Update scratch-vm to the latest version 🚀
2019-10-14 22:13:23 -04:00
greenkeeper[bot] f6fa9e3e25 chore(package): update lockfile package-lock.json 2019-10-15 00:25:41 +00:00
greenkeeper[bot] dee9323f5c chore(package): update scratch-vm to version 0.2.0-prerelease.20191015002244 2019-10-15 00:25:37 +00:00
Paul Kaplan 3513a680a6 Merge pull request #5197 from paulkaplan/fix-backpack-thumbnailing
Draw backpack thumbnails smaller
2019-10-14 12:04:47 -04:00
Ben Wheeler b8833199e1 Fix extension line height 2019-10-10 22:25:24 -04:00
chrisgarrity 38aa76fae8 Merge pull request #5239 from chrisgarrity/update-blocks
Update blocks for swahili
2019-10-10 14:48:35 -04:00
Chris Garrity 18c7b58a16 Update blocks for swahili 2019-10-10 14:29:56 -04:00
DD Liu d7d38109a8 Merge pull request #5237 from fsih/updatePaint
Update paint to add swahili to overflow languages
2019-10-10 12:58:46 -04:00
DD Liu 2317758df8 update paint to add swahili to overflow languages 2019-10-10 12:15:29 -04:00
DD Liu 25c8253138 Merge pull request #5234 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20191001200908
Greenkeeper/scratch render 0.1.0 prerelease.20191001200908
2019-10-09 15:32:57 -04:00
chrisgarrity 75f9da9f22 Merge pull request #5233 from chrisgarrity/add-swahili
change l10n version to get swahili
2019-10-09 10:05:19 -04:00
Chris Garrity 8c3ab1df78 change l10n version to get swahili 2019-10-09 09:50:39 -04:00
chrisgarrity e04c290587 Merge pull request #5232 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1570622534
Update scratch-blocks to the latest version 🚀
2019-10-09 08:36:46 -04:00
greenkeeper[bot] 527f8dd3e7 chore(package): update lockfile package-lock.json 2019-10-09 12:17:56 +00:00
greenkeeper[bot] 251db0108d chore(package): update scratch-blocks to version 0.1.0-prerelease.1570622534 2019-10-09 12:17:51 +00:00
chrisgarrity 5100a1bd8a Merge pull request #5231 from LLK/greenkeeper/scratch-l10n-3.6.20191008224547
Greenkeeper/scratch l10n 3.6.20191008224547
2019-10-09 08:17:43 -04:00
greenkeeper[bot] ee5e2324a8 chore(package): update lockfile package-lock.json 2019-10-09 01:00:21 +00:00
greenkeeper[bot] 624bf7c965 chore(package): update scratch-l10n to version 3.6.20191008224547
Closes #5230
2019-10-09 01:00:17 +00:00
zoebentley 3089503ab1 Merge pull request #5228 from zoebentley/imagine-a-world
Changed title of Imagine If tutorial to Imagine a World
2019-10-08 08:44:07 -05:00
Zoë Bentley 8cac1b1bb7 Changed title of Imagine If tutorial to Imagine a World 2019-10-07 09:49:52 -04:00
Eric Rosenbaum 67fe5185fb Merge pull request #5218 from ericrosenbaum/feature/limit-loud-rms-no-waveshaper
Limit louder effect with a max RMS (w/o waveshaper)
2019-10-02 12:13:57 -04:00
greenkeeper[bot] 9122b26d1a chore(package): update lockfile package-lock.json 2019-10-01 20:12:23 +00:00
greenkeeper[bot] efcd2c9ebc chore(package): update scratch-render to version 0.1.0-prerelease.20191001200908
Closes #5217
2019-10-01 20:12:19 +00:00
Karishma Chadha 181c7ea93f Merge pull request #5222 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20191001145300
Greenkeeper/scratch vm 0.2.0 prerelease.20191001145300
2019-10-01 10:54:32 -05:00
greenkeeper[bot] a8a0bc437a chore(package): update lockfile package-lock.json 2019-10-01 14:55:57 +00:00
greenkeeper[bot] c6bc1c3450 chore(package): update scratch-vm to version 0.2.0-prerelease.20191001145300
Closes #5191
2019-10-01 14:55:53 +00:00
Ray Schamp 1ad0f43d23 Merge pull request #5220 from rschamp/ci/circle
Add config for CircleCI
2019-09-30 15:04:18 -04:00
rschamp a3400b3b0f Disable publishing
While CircleCI is running in parallel with Travis, let Travis continue to handle the deployments.
2019-09-30 14:22:58 -04:00
Eric Rosenbaum 523667dfaf Remove waveshaper from volume effect
the waveshaper node is implemented incorrectly on Mac Safari (https://bugs.webkit.org/show_bug.cgi?id=129333) and causes extreme distortion of the sound
2019-09-27 14:26:14 -04:00
Eric Rosenbaum 5599626405 Revert "Revert "Limit louder effect with a max RMS""
This reverts commit 45cb96f2d4.
2019-09-27 14:20:34 -04:00
Paul Kaplan d31d42cf3c Merge pull request #5216 from LLK/revert-5212-greenkeeper/scratch-render-0.1.0-prerelease.20190925202615
Revert "Update scratch-render to the latest version 🚀"
2019-09-26 10:05:56 -04:00
Paul Kaplan 11646d4877 Revert "Update scratch-render to the latest version 🚀" 2019-09-26 10:03:54 -04:00
Paul Kaplan e53c8ddd85 Merge pull request #5214 from LLK/revert-5149-feature/limit-louder-rms
Revert "Limit louder effect with a max RMS"
2019-09-26 09:49:33 -04:00
Paul Kaplan 45cb96f2d4 Revert "Limit louder effect with a max RMS" 2019-09-26 09:48:15 -04:00
Chris Willis-Ford ab0d58ed6c Merge pull request #5212 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190925202615
Update scratch-render to the latest version 🚀
2019-09-25 13:59:22 -07:00
greenkeeper[bot] bdcddf5655 chore(package): update lockfile package-lock.json 2019-09-25 20:33:27 +00:00
greenkeeper[bot] 0f0219a4b1 chore(package): update scratch-render to version 0.1.0-prerelease.20190925202615 2019-09-25 20:32:57 +00:00
Eric Rosenbaum aa2890a2ca Merge pull request #5211 from LLK/greenkeeper/scratch-audio-0.1.0-prerelease.20190925183642
Update scratch-audio to the latest version 🚀
2019-09-25 15:49:01 -04:00
greenkeeper[bot] 43bddbba14 chore(package): update lockfile package-lock.json 2019-09-25 18:38:19 +00:00
greenkeeper[bot] e4af27cd40 chore(package): update scratch-audio to version 0.1.0-prerelease.20190925183642 2019-09-25 18:38:15 +00:00
chrisgarrity d390e67542 Merge pull request #5210 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1569422127
Update scratch-blocks to the latest version 🚀
2019-09-25 15:54:21 +01:00
greenkeeper[bot] a8b7be6197 chore(package): update lockfile package-lock.json 2019-09-25 14:38:29 +00:00
greenkeeper[bot] 35da6ace36 chore(package): update scratch-blocks to version 0.1.0-prerelease.1569422127 2019-09-25 14:38:25 +00:00
chrisgarrity 8590ceb4ce Merge pull request #5209 from LLK/greenkeeper/scratch-l10n-3.5.20190924223720
Update scratch-l10n to the latest version 🚀
2019-09-25 15:06:51 +01:00
greenkeeper[bot] 96e637cfc0 chore(package): update lockfile package-lock.json 2019-09-25 00:52:57 +00:00
greenkeeper[bot] f1470feb0a chore(package): update scratch-l10n to version 3.5.20190924223720 2019-09-25 00:52:53 +00:00
rschamp 7070b19562 Fix npm cache order in setup 2019-09-24 09:44:19 -04:00
rschamp 931a17462a Add build to build deploy step 2019-09-23 22:19:22 -04:00
rschamp 859e636d1a Moar parallel 2019-09-23 21:55:05 -04:00
rschamp 7ccf9f5bef FIx cache ordering 2019-09-23 21:35:26 -04:00
rschamp 0f589004a1 Use npm install to get better cache benefits 2019-09-23 21:31:42 -04:00
rschamp 2df4df0791 Use granular caching 2019-09-23 21:26:06 -04:00
rschamp 41296a9624 Optimize 2019-09-23 17:31:40 -04:00
rschamp 3dbfebe923 Parallelize 2019-09-23 16:27:30 -04:00
rschamp 5e0fc58f27 De-alias workflow
The requirements for npm publishing and gh-pages are different anyway.
2019-09-23 15:29:33 -04:00
rschamp 5eb8438782 Aliasify
The equivalent config for Travis is only 87 lines long, and easy to read. Can CircleCI be similarly easy to read? Is this easy? Are you still reading this?
2019-09-23 14:43:06 -04:00
rschamp 4f0bf3c5aa Add config for gh-pages deploy 2019-09-23 13:59:22 -04:00
DD Liu abe79b30ff Merge pull request #5203 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190923154429
Greenkeeper/scratch paint 0.2.0 prerelease.20190923154429
2019-09-23 12:38:24 -04:00
greenkeeper[bot] 5d1b1fb4c0 chore(package): update lockfile package-lock.json 2019-09-23 15:57:57 +00:00
greenkeeper[bot] 9265ee6323 chore(package): update scratch-paint to version 0.2.0-prerelease.20190923154429
Closes #5201
2019-09-23 15:57:51 +00:00
Ray Schamp 519256c50c Merge pull request #5194 from rschamp/dont-build-tags
Don’t push tags to gh-pages
2019-09-23 11:14:50 -04:00
Eric Rosenbaum 17324e5340 Merge pull request #5149 from ericrosenbaum/feature/limit-louder-rms
Limit louder effect with a max RMS
2019-09-23 10:52:17 -04:00
Ray Schamp 7d01844371 Merge pull request #5196 from rschamp/max-old-space-size
Allow Node to use more memory for old space
2019-09-20 14:35:40 -04:00
rschamp 745fa360c3 Actually use the tag for npm publish 2019-09-20 14:31:55 -04:00
rschamp 51fbae655e Use circle prefix in default NPM dist channel 2019-09-20 13:59:45 -04:00
rschamp c3119aae2c Set max-old-space-size for NPM publishing 2019-09-20 13:58:54 -04:00
rschamp bc1b787974 Allow Node to use more memory for old space
This flag essentially configures how much memory Node can use. If it’s too low, then Node processes may hang as Node spends time garbage collecting. Increasing it allows more memory to be used before garbage collection is necessary.  Travis VMs have 7.5GB of memory, so set the limit to just below that to avoid running out of memory completely.
2019-09-20 13:41:34 -04:00
Paul Kaplan fb4b492f1c Draw backpack thumbnails smaller 2019-09-20 13:37:02 -04:00
rschamp e3cc730cd7 Make npm publishing do the thing 2019-09-20 13:12:12 -04:00
rschamp 46743c9bf8 Make git tagging do the thing 2019-09-20 13:12:12 -04:00
rschamp d573e34501 Add NPM tagging configuration 2019-09-20 13:12:12 -04:00
rschamp fac69fbcf4 Tag after publishing 2019-09-20 13:12:12 -04:00
rschamp 99f2ac586d Try out yaml inheritance in preparation for parallelization 2019-09-20 13:12:12 -04:00
rschamp 33dc1f7095 Remove circle branch from deploy list 2019-09-20 13:12:12 -04:00
rschamp 0707527800 Cache builds by commit not build number 2019-09-20 13:12:12 -04:00
rschamp f71e117e3a Cache git checkouts 2019-09-20 13:12:12 -04:00
rschamp d011aca301 Add NPM publish step 2019-09-20 13:12:12 -04:00
rschamp 2c3f3349b5 Add coverage report 2019-09-20 13:12:12 -04:00
rschamp 10eef7234f Add build artifact 2019-09-20 13:12:12 -04:00
rschamp 17154cefec Add back jest and build steps with reporting 2019-09-20 13:12:12 -04:00
rschamp e3d4d6e9d7 Circle doesn’t know about eslint warnings
They show up as errors
2019-09-20 13:12:12 -04:00
rschamp 89918a304f Try uploading lint results 2019-09-20 13:12:12 -04:00
rschamp 74b795ada8 Reformat 2019-09-20 13:12:12 -04:00
rschamp 40a3b857da Keep config-dependent environment vars in the config
NODE_ENV isn’t one we’d want to change dynamically, so keep it checked in to the config
2019-09-20 13:12:12 -04:00
rschamp 615927f3f2 Add initial CircleCI config 2019-09-20 13:12:12 -04:00
rschamp 3d1ad740dd Don’t push tags to gh-pages
These builds make our gh-pages branch too large for GitHub to handle.
2019-09-20 10:18:15 -04:00
DD Liu 4c76f654b7 Merge pull request #5189 from fsih/reupdateAssets
Reupdate watermelon a and basketball background with asset exported from sketch
2019-09-19 16:31:57 -04:00
DD Liu bd835adcd4 Reupdate watermelon a and basketball background with asset exported from sketch 2019-09-19 15:21:17 -04:00
Karishma Chadha 78e4cfce68 Merge pull request #5183 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190918022946
Update scratch-vm to the latest version 🚀
2019-09-18 09:34:28 -07:00
Eric Rosenbaum deb5889a2c Merge pull request #5182 from LLK/greenkeeper/scratch-l10n-3.5.20190917223712
Update scratch-l10n to the latest version 🚀
2019-09-18 11:10:26 -04:00
greenkeeper[bot] 2030ad54bc chore(package): update lockfile package-lock.json 2019-09-18 02:32:41 +00:00
greenkeeper[bot] a95604008d chore(package): update scratch-vm to version 0.2.0-prerelease.20190918022946 2019-09-18 02:32:37 +00:00
greenkeeper[bot] bb21288cff chore(package): update lockfile package-lock.json 2019-09-17 22:39:32 +00:00
greenkeeper[bot] f31977489f chore(package): update scratch-l10n to version 3.5.20190917223712 2019-09-17 22:39:28 +00:00
Paul Kaplan f179587c2e Merge pull request #5180 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190917163837
Update scratch-vm to the latest version 🚀
2019-09-17 14:07:04 -04:00
greenkeeper[bot] fa3d9c9e1e chore(package): update lockfile package-lock.json 2019-09-17 16:41:32 +00:00
greenkeeper[bot] 459eb2b767 chore(package): update scratch-vm to version 0.2.0-prerelease.20190917163837 2019-09-17 16:41:29 +00:00
Paul Kaplan 4e1c6afe02 Merge pull request #5178 from paulkaplan/skip-flakey
Skip flakey test
2019-09-17 12:35:56 -04:00
Paul Kaplan dc69ccea64 Skip flakey test 2019-09-17 12:18:03 -04:00
Paul Kaplan 03d9f8f2c6 Merge pull request #5169 from LLK/recording-glitch
Use a larger buffer size for recording to prevent glitching
2019-09-17 11:10:20 -04:00
Paul Kaplan 5d006cf677 Merge pull request #5175 from LLK/greenkeeper/chromedriver-77.0.0
Update chromedriver to the latest version 🚀
2019-09-17 10:54:50 -04:00
DD Liu 6c9657e453 Merge pull request #5172 from fsih/fixAndroidAssets
Fix assets that aren't working correctly in Android libraries
2019-09-17 10:24:24 -04:00
greenkeeper[bot] e82ec50e7b chore(package): update lockfile package-lock.json 2019-09-17 00:00:43 +00:00
greenkeeper[bot] 09ccbb4c01 chore(package): update chromedriver to version 77.0.0 2019-09-17 00:00:38 +00:00
Karishma Chadha e3b2ab22a6 Merge pull request #5170 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190916185023
Update scratch-vm to the latest version 🚀
2019-09-16 18:56:29 -04:00
DD Liu a2bc04e5d4 Fix assets that aren't working correctly in Android libraries 2019-09-16 16:27:55 -04:00
greenkeeper[bot] a4c81e55c0 chore(package): update lockfile package-lock.json 2019-09-16 18:57:52 +00:00
greenkeeper[bot] 2b571d9d83 chore(package): update scratch-vm to version 0.2.0-prerelease.20190916185023 2019-09-16 18:57:47 +00:00
Ray Schamp 6caaaf6f36 Merge pull request #5166 from rschamp/debug-chromedriver
Use not broken Travis VM distribution
2019-09-16 10:53:12 -04:00
Paul Kaplan 2b9550f3d9 Use single setState call instead of two 2019-09-13 14:05:07 -04:00
Paul Kaplan 94bb677fd8 Force meter and waveform to separate paint layer 2019-09-13 14:04:54 -04:00
Paul Kaplan 5b6f33e1a4 Use a larger buffer size for recording to prevent glitching 2019-09-13 12:46:25 -04:00
Paul Kaplan 32723a8876 Merge pull request #5114 from LLK/default-last-asset
Default blocks to last asset instead of first
2019-09-13 09:51:54 -04:00
rschamp a1fc068dd1 Un-hack 2019-09-12 14:50:32 -04:00
rschamp 225fc772f6 Bump travis dist version 2019-09-12 14:42:20 -04:00
Karishma Chadha 2a167ba775 Merge pull request #5165 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190912174749
Update scratch-render to the latest version 🚀
2019-09-12 13:59:54 -04:00
greenkeeper[bot] 193717a1a9 chore(package): update lockfile package-lock.json 2019-09-12 17:49:51 +00:00
greenkeeper[bot] 4fc140ec2c chore(package): update scratch-render to version 0.1.0-prerelease.20190912174749 2019-09-12 17:49:47 +00:00
Ray Schamp 2ffac96579 Merge pull request #5163 from rschamp/bump-chromedriver
Ugly hack to keep being able to publish
2019-09-12 11:50:41 -04:00
rschamp bc57adc12c Ugly hack to keep being able to publish 2019-09-12 11:32:02 -04:00
Karishma Chadha dc64aa92fd Merge pull request #5151 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190904154449
Update scratch-vm to the latest version 🚀
2019-09-11 12:41:30 -04:00
chrisgarrity 8b5ff14ac9 Merge pull request #5161 from LLK/greenkeeper/scratch-l10n-3.5.20190910223701
Greenkeeper/scratch l10n 3.5.20190910223701
2019-09-11 11:56:30 -04:00
Karishma Chadha 6641c4696b Merge pull request #5154 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190905011937
Update scratch-render to the latest version 🚀
2019-09-11 10:16:16 -04:00
Paul Kaplan a680d30930 Merge pull request #5143 from towerofnix/selector-padding
Use list-area:after to pad scroll area
2019-09-11 09:22:10 -04:00
Paul Kaplan 1dce71e787 Merge pull request #5115 from paulkaplan/fix-bluetooth-keyboards
Fix bluetooth keyboards with iOS keypress blocks
2019-09-11 09:12:40 -04:00
greenkeeper[bot] 1451416e6b chore(package): update lockfile package-lock.json 2019-09-10 22:39:16 +00:00
greenkeeper[bot] f4f20203f4 chore(package): update scratch-l10n to version 3.5.20190910223701
Closes #5150
2019-09-10 22:39:12 +00:00
Eric Rosenbaum 2df0c80b30 Simplify chunk counting logic 2019-09-10 10:35:19 -04:00
picklesrus 7aa036a18c Merge pull request #5157 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1568071968
Update scratch-blocks to the latest version 🚀
2019-09-09 19:57:28 -04:00
greenkeeper[bot] 2f9a2ec305 chore(package): update lockfile package-lock.json 2019-09-09 23:36:02 +00:00
greenkeeper[bot] 75ccdbad51 chore(package): update scratch-blocks to version 0.1.0-prerelease.1568071968 2019-09-09 23:35:58 +00:00
Eric Rosenbaum 6414985461 Calc tooLoud with chunkLevels, disable louder btn 2019-09-05 13:49:22 -04:00
greenkeeper[bot] 230debb911 chore(package): update lockfile package-lock.json 2019-09-05 01:21:30 +00:00
greenkeeper[bot] b42291d10b chore(package): update scratch-render to version 0.1.0-prerelease.20190905011937 2019-09-05 01:21:26 +00:00
DD Liu 79616ba067 Merge pull request #5153 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190904212449
Update scratch-render to the latest version 🚀
2019-09-04 18:42:44 -04:00
greenkeeper[bot] 35c1a3e5e2 chore(package): update lockfile package-lock.json 2019-09-04 21:46:08 +00:00
greenkeeper[bot] ac0e81dc00 chore(package): update scratch-render to version 0.1.0-prerelease.20190904212449 2019-09-04 21:46:03 +00:00
greenkeeper[bot] f9ea1088e8 chore(package): update lockfile package-lock.json 2019-09-04 15:56:55 +00:00
greenkeeper[bot] bb837f57e2 chore(package): update scratch-vm to version 0.2.0-prerelease.20190904154449 2019-09-04 15:56:47 +00:00
Eric Rosenbaum 059d3f709b Use a waveshaper to hard limit samples 2019-09-04 11:01:10 -04:00
Eric Rosenbaum 26ca5da725 Limit louder effect with a max RMS 2019-09-03 18:13:45 -04:00
Karishma Chadha ce8b0ff593 Merge pull request #5146 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190830194018
Greenkeeper/scratch render 0.1.0 prerelease.20190830194018
2019-09-03 10:46:28 -04:00
Florrie b74f19e6e6 Use list-area:after to pad scroll area 2019-09-02 23:26:46 -03:00
greenkeeper[bot] 3566251676 chore(package): update lockfile package-lock.json 2019-08-30 19:43:11 +00:00
greenkeeper[bot] 4f02dbd807 chore(package): update scratch-render to version 0.1.0-prerelease.20190830194018
Closes #5139
2019-08-30 19:43:07 +00:00
DD Liu eed0aab4e5 Merge pull request #5137 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190830153254
Update scratch-render to the latest version 🚀
2019-08-30 12:03:36 -04:00
greenkeeper[bot] 498ab2f547 chore(package): update lockfile package-lock.json 2019-08-30 15:34:51 +00:00
greenkeeper[bot] 9c1c6c6e4c chore(package): update scratch-render to version 0.1.0-prerelease.20190830153254 2019-08-30 15:34:47 +00:00
Paul Kaplan bd29738d52 Merge pull request #5134 from zoebentley/portuguese-brazilian
Fixed Portuguese (Brazilian) locale for tutorial videos
2019-08-29 16:14:22 -04:00
Zoë Bentley 2f08acae45 Fixed Portuguese (Brazilian) locale 2019-08-29 15:41:06 -04:00
Eric Rosenbaum ff78f0bcb3 Merge pull request #5129 from LLK/hotfix/add-marian-sprite
[hotfix] Add Marian sprite and costumes
2019-08-28 13:38:51 -04:00
Eric Rosenbaum 8ba5ecbec0 Add Marian sprite and costumes 2019-08-28 10:14:28 -04:00
chrisgarrity a8c3eb5831 Merge pull request #5128 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1566984014
Update scratch-blocks to the latest version 🚀
2019-08-28 11:28:44 +01:00
chrisgarrity d3ecb7b725 Merge branch 'develop' into greenkeeper/scratch-blocks-0.1.0-prerelease.1566984014 2019-08-28 11:16:26 +01:00
chrisgarrity c2321f8fe9 Merge pull request #5127 from LLK/greenkeeper/scratch-l10n-3.5.20190827223456
Greenkeeper/scratch l10n 3.5.20190827223456
2019-08-28 11:14:40 +01:00
greenkeeper[bot] 018d451431 chore(package): update lockfile package-lock.json 2019-08-28 09:46:20 +00:00
greenkeeper[bot] 986d8360e2 chore(package): update scratch-blocks to version 0.1.0-prerelease.1566984014 2019-08-28 09:46:14 +00:00
greenkeeper[bot] 087b73540c chore(package): update lockfile package-lock.json 2019-08-27 22:43:52 +00:00
greenkeeper[bot] 02a3221a09 chore(package): update scratch-l10n to version 3.5.20190827223456
Closes #5112
2019-08-27 22:43:45 +00:00
DD Liu 16960b5941 Merge pull request #5119 from fsih/updateSvgRend
Update svg renderer
2019-08-23 11:55:36 -04:00
DD Liu 11ffda10e6 fix svg rend number 2019-08-23 11:33:55 -04:00
DD Liu a07b234745 update svg renderer 2019-08-22 16:55:51 -04:00
DD Liu 276bcb0b28 Merge pull request #5118 from fsih/updateVm
Update vm
2019-08-22 16:46:32 -04:00
DD Liu 4e1842134a Update vm 2019-08-22 16:33:56 -04:00
Paul Kaplan 7b9aed73f3 Fix multi-word matching in the blocks 2019-08-22 09:44:22 -04:00
Paul Kaplan 280f139ba2 Fix costume test 2019-08-21 16:05:29 -04:00
Paul Kaplan def5cdc450 Send keyCode if the key information is gone 2019-08-21 15:28:04 -04:00
Paul Kaplan 79fe377f19 Update integration tests 2019-08-21 15:17:34 -04:00
Paul Kaplan b2dd3ed421 Default blocks to last asset instead of first 2019-08-21 14:56:46 -04:00
Paul Kaplan a76dcfa1f9 Merge pull request #5111 from LLK/greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190820171249
Update scratch-svg-renderer to the latest version 🚀
2019-08-20 15:14:26 -04:00
Paul Kaplan f2e4bc518e Merge pull request #5108 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190819154958
Update scratch-render to the latest version 🚀
2019-08-20 15:14:07 -04:00
greenkeeper[bot] 770b9db129 chore(package): update lockfile package-lock.json 2019-08-20 17:25:08 +00:00
greenkeeper[bot] 13c502d76d chore(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190820171249 2019-08-20 17:25:02 +00:00
greenkeeper[bot] c2785f6429 chore(package): update lockfile package-lock.json 2019-08-19 16:23:41 +00:00
greenkeeper[bot] 0e8b6d7290 chore(package): update scratch-render to version 0.1.0-prerelease.20190819154958 2019-08-19 16:23:36 +00:00
Paul Kaplan 5c1b806f2d Merge pull request #5106 from fsih/revertRender
Revert render to last good version
2019-08-19 11:00:20 -04:00
DD 146c5d19ad Revert render to last good version 2019-08-16 17:34:16 -04:00
Paul Kaplan 25611283c4 Merge pull request #5103 from zoebentley/imagine-if-video
Added the Imagine If video to Imagine If tutorial, and changed last step
2019-08-16 11:09:39 -04:00
Paul Kaplan ebc91adfb5 Merge pull request #5104 from zoebentley/portuguese-and-italian
Portuguese and italian
2019-08-16 11:05:01 -04:00
Zoë Bentley 4a19260911 Added Imagine If Wistia link to translate-video.js 2019-08-15 18:14:18 -04:00
Zoë Bentley e9880c4d17 Removed Imagine If video (supposed to be part of a different PR) 2019-08-15 18:06:45 -04:00
Zoë Bentley b2bf8605ae Added video links for Portuguese and Italian tutorials 2019-08-15 17:56:07 -04:00
Zoë Bentley 3f5e5fbc0e Reduced image size 2019-08-15 17:51:03 -04:00
Zoë Bentley ad1d7e2751 Added the Imagine If video to the Imagine If tutorial, and changed the last card 2019-08-15 17:43:55 -04:00
Benjamin Wheeler 98eee04712 Merge pull request #5097 from LLK/greenkeeper/scratch-l10n-3.5.20190813223429
Update scratch-l10n to the latest version 🚀
2019-08-14 17:54:21 -04:00
Paul Kaplan 593c3448eb Merge pull request #5092 from paulkaplan/fix-import-hang
Close importing after failing to import
2019-08-14 11:15:06 -04:00
greenkeeper[bot] 4e8e0d09b0 chore(package): update lockfile package-lock.json 2019-08-13 22:52:50 +00:00
greenkeeper[bot] 88210fb5cc chore(package): update scratch-l10n to version 3.5.20190813223429 2019-08-13 22:52:46 +00:00
chrisgarrity 0913c1134d Merge pull request #5094 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190813192748
Update scratch-vm to the latest version 🚀
2019-08-13 18:10:38 -04:00
picklesrus 0d4808748d Merge pull request #5096 from LLK/revert-5078-fix-duplicate-run
Revert "Fix duplicate running blocks by removing pointer-events: none"
2019-08-13 17:35:36 -04:00
picklesrus 907059be00 Revert "Fix duplicate running blocks by removing pointer-events: none" 2019-08-13 17:34:02 -04:00
Paul Kaplan 7ffe6d650f Merge pull request #5095 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1565724757
Update scratch-blocks to the latest version 🚀
2019-08-13 16:25:05 -04:00
greenkeeper[bot] 179b850837 chore(package): update lockfile package-lock.json 2019-08-13 19:46:02 +00:00
greenkeeper[bot] 72617e8e6d chore(package): update scratch-blocks to version 0.1.0-prerelease.1565724757 2019-08-13 19:45:49 +00:00
greenkeeper[bot] 149e50595b chore(package): update lockfile package-lock.json 2019-08-13 19:32:12 +00:00
greenkeeper[bot] c8c33755de chore(package): update scratch-vm to version 0.2.0-prerelease.20190813192748 2019-08-13 19:32:07 +00:00
chrisgarrity e9e07052f8 Merge pull request #5088 from chrisgarrity/export-custom-mimetype
Get the mime-type for the file from the blob
2019-08-13 15:22:58 -04:00
Karishma Chadha 3252a33a87 Merge pull request #5093 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190813154945
Greenkeeper/scratch render 0.1.0 prerelease.20190813154945
2019-08-13 13:19:35 -04:00
greenkeeper[bot] 00f0c009c7 chore(package): update lockfile package-lock.json 2019-08-13 15:52:42 +00:00
greenkeeper[bot] 0648950207 chore(package): update scratch-render to version 0.1.0-prerelease.20190813154945
Closes #4992
2019-08-13 15:52:37 +00:00
Paul Kaplan ab0ee830d8 Close importing after failing to import 2019-08-13 11:33:32 -04:00
Chris Garrity 8c94fef24c Get the mime-type for the file from the blob
Depends on the VM change that sets the mime-type
2019-08-12 15:32:30 -04:00
Paul Kaplan ec386be590 Merge pull request #5078 from LLK/fix-duplicate-run
Fix duplicate running blocks by removing pointer-events: none
2019-08-12 08:42:42 -04:00
DD Liu 0adf321455 Merge pull request #5071 from fsih/webpackResolver
Add webpack resolver to eslintrc so linter doesn't give false positive noUnresolved errors
2019-08-08 16:08:04 -04:00
Paul Kaplan d4d6dede94 Fix duplicate running blocks by removing pointer-events: none; 2019-08-08 15:16:55 -04:00
Ray Schamp 95abe8207d Merge pull request #5076 from LLK/revert-5017-fix-up-delete-button
Revert "Make delete button accessible"
2019-08-08 10:31:38 -04:00
Ray Schamp 445874cb9c Revert "Make delete button accessible" 2019-08-08 10:30:58 -04:00
DD Liu 829435d5e6 Resolve package lock conflict 2019-08-08 00:36:59 -04:00
DD Liu 6658158972 Merge branch 'develop' into webpackResolver 2019-08-08 00:35:46 -04:00
DD Liu 127aa38f58 use path.resolve for config 2019-08-08 00:26:51 -04:00
Colby Gutierrez-Kraybill 770f381f6c Merge pull request #5064 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190805150531
Update scratch-vm to the latest version 🚀
2019-08-07 23:13:15 -04:00
Paul Kaplan 90b71ce5e5 Merge pull request #5039 from paulkaplan/fix-context-menu-after-drag
Fix issue where context menu wouldn't come back after dragging
2019-08-07 15:59:28 -04:00
Paul Kaplan 6db27c7fd9 Merge pull request #5072 from LLK/greenkeeper/scratch-l10n-3.5.20190807144510
Update scratch-l10n to the latest version 🚀
2019-08-07 15:38:57 -04:00
DD Liu dfdb491c7b Merge pull request #5069 from fsih/updatePaint
Update paint
2019-08-07 12:00:52 -04:00
DD Liu 212a0a5a59 Update paint 2019-08-07 11:48:51 -04:00
greenkeeper[bot] 442a949e97 chore(package): update lockfile package-lock.json 2019-08-07 15:44:30 +00:00
greenkeeper[bot] bdcb54f8b1 chore(package): update scratch-l10n to version 3.5.20190807144510 2019-08-07 15:44:25 +00:00
DD Liu eb989d7b2e Add webpack resolver to eslintrc so linter doesnt give false positive noUnresolved errors 2019-08-06 16:05:06 -04:00
DD Liu f30eb26b6d Switch to rolled back dep 2019-08-06 15:03:13 -04:00
Paul Kaplan 468da6fcbf Merge pull request #5059 from paulkaplan/fix-save-after-recording
Fix saving after recording a sound
2019-08-06 12:21:53 -04:00
greenkeeper[bot] ae5068a411 chore(package): update lockfile package-lock.json 2019-08-05 15:38:06 +00:00
greenkeeper[bot] e9b4b61c5f chore(package): update scratch-vm to version 0.2.0-prerelease.20190805150531 2019-08-05 15:38:02 +00:00
Paul Kaplan 043cbec994 Merge pull request #5061 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1564769819
Update scratch-blocks to the latest version 🚀
2019-08-02 14:54:11 -04:00
greenkeeper[bot] 4e34d5ab55 chore(package): update lockfile package-lock.json 2019-08-02 18:19:49 +00:00
greenkeeper[bot] baf7427453 chore(package): update scratch-blocks to version 0.1.0-prerelease.1564769819 2019-08-02 18:19:45 +00:00
Paul Kaplan b07c645124 Fix saving after recording a sound 2019-08-02 09:31:53 -04:00
chrisgarrity 69b7eef768 Merge pull request #4926 from LLK/sound-lib-touch
Add play buttons to sound library tiles for touch
2019-08-02 08:39:48 -04:00
chrisgarrity 3feff9fea8 Merge pull request #5017 from chrisgarrity/fix-up-delete-button
Make delete button accessible
2019-08-02 08:39:27 -04:00
Ray Schamp e23ac95c2c Merge pull request #5057 from rschamp/lint-tests
Add Jest-specific linting rules
2019-08-01 13:03:12 -04:00
rschamp 3c6bfe0607 Add Jest-specific linting rules
Adding this caught a couple of important things:
- The sound integration tests have a `.only` in them
- The project-saver-hoc tests had two tests with the same description, causing possible confusion if that test broke
2019-08-01 11:31:01 -04:00
Chris Garrity 3701a4fb44 Refactor to separate container/component
Also noticed that Delete aria label was not localized.
2019-07-31 16:20:34 -04:00
Chris Garrity e2c2bd3927 remove callbacks 2019-07-31 15:38:30 -04:00
Karishma Chadha d7e338607a Merge pull request #5054 from LLK/greenkeeper/scratch-l10n-3.5.20190731161310
Update scratch-l10n to the latest version 🚀
2019-07-31 15:25:41 -04:00
greenkeeper[bot] 285c06df9c chore(package): update lockfile package-lock.json 2019-07-31 16:15:25 +00:00
greenkeeper[bot] 928fd2afab chore(package): update scratch-l10n to version 3.5.20190731161310 2019-07-31 16:15:21 +00:00
Chris Garrity ab1537dab4 Use classNames instead of ternary
Use the classNames feature to include/exclude a class based on a boolean instead of ternary operator
2019-07-31 12:05:16 -04:00
Chris Garrity 043e31a472 Refactor
* split play-button into container and component
* revised sound library tile:
  * play button in upper right
  * smaller sound icon (make the tile less loaded)
2019-07-31 12:05:16 -04:00
Chris Garrity 91f5c4910f refactor to create play-button for sound items
Play button doesn’t have a ‘touch-outside’ listener to stop sounds. That seems to be ok as other events cause the sound to stop.

Both handleClick and handleMouseDown are needed. Must use handleMouseDown for `preventDefault` to skip setting focus on the item. handleClick is needed to prevent the Click from propagating to the item and selecting it.
2019-07-31 12:05:16 -04:00
Chris Garrity 41cc11c32e Handle sound ending
Update UI play/stop button when the sound ends
2019-07-31 12:05:16 -04:00
Chris Garrity 6216e7dfa7 improve touch interactions
Play button is larger (easier to touch). Can both start and stop sounds.
Sound doesn’t restart on mouseEnter if the sound is already playing.
Sound stops if you move off play button

Missing: stop button does not revert to play button when the sound finishes.
2019-07-31 12:05:16 -04:00
Chris Garrity 8ff41a3986 revise styles
Checked with Carl and updated the style of the play button.
2019-07-31 12:05:16 -04:00
Chris Garrity f3d1a91533 Add play buttons to sound library tiles for touch
Add prop to library items to show a play button. If a play button is present the ‘play on hover’ only applies over the icon rather than the full tile.
2019-07-31 12:05:16 -04:00
Paul Kaplan a72a420bd3 Merge pull request #5053 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190731151656
Update scratch-vm to the latest version 🚀
2019-07-31 11:40:41 -04:00
greenkeeper[bot] 6df151d244 chore(package): update lockfile package-lock.json 2019-07-31 15:30:16 +00:00
greenkeeper[bot] 14fa71d7f7 chore(package): update scratch-vm to version 0.2.0-prerelease.20190731151656 2019-07-31 15:30:12 +00:00
Paul Kaplan d73f912944 Merge pull request #5043 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190730131442
Update scratch-vm to the latest version 🚀
2019-07-31 11:17:26 -04:00
Eric Rosenbaum 593d919d48 Merge pull request #5049 from ericrosenbaum/bugfix/remove-test-only
Remove test.only
2019-07-31 11:10:18 -04:00
Paul Kaplan d452607c7a Merge pull request #5045 from LLK/greenkeeper/chromedriver-76.0.0
Update chromedriver to the latest version 🚀
2019-07-31 11:05:23 -04:00
Paul Kaplan 5987562bcb Merge branch 'develop' into greenkeeper/chromedriver-76.0.0 2019-07-31 11:04:59 -04:00
Paul Kaplan 3fae69e035 Merge pull request #5051 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1564581595
Greenkeeper/scratch blocks 0.1.0 prerelease.1564581595
2019-07-31 11:03:32 -04:00
Paul Kaplan 6222c79bee Merge pull request #5048 from ericrosenbaum/bugfix/update-fade-icons
Update fade icons
2019-07-31 10:50:03 -04:00
Paul Kaplan e4ebd53e69 Merge pull request #5050 from paulkaplan/fix-recording-drag-firefox
Prevent default when dragging trim handles to prevent image drag
2019-07-31 10:49:20 -04:00
Paul Kaplan c855152e86 Prevent default when dragging trim handles to prevent image drag 2019-07-31 10:48:06 -04:00
Eric Rosenbaum fb22e42b59 Remove test.only 2019-07-31 10:47:37 -04:00
Eric Rosenbaum dce65e33a5 Update fade icons 2019-07-31 10:43:54 -04:00
Eric Rosenbaum 1259457ee4 Merge pull request #5047 from paulkaplan/fix-zero-trim-update
Do not update trimmers only if both have not changed
2019-07-31 10:41:03 -04:00
Paul Kaplan 4757bb73e4 Do not update trimmers only if both have not changed 2019-07-31 10:27:15 -04:00
greenkeeper[bot] 6453345577 chore(package): update lockfile package-lock.json 2019-07-31 14:13:06 +00:00
greenkeeper[bot] a931f0e7be chore(package): update scratch-blocks to version 0.1.0-prerelease.1564581595
Closes #5034
2019-07-31 14:13:02 +00:00
greenkeeper[bot] 5ec2c42e55 chore(package): update lockfile package-lock.json 2019-07-31 00:11:24 +00:00
greenkeeper[bot] f664b37b4b chore(package): update chromedriver to version 76.0.0 2019-07-31 00:11:20 +00:00
Eric Rosenbaum 7d1dfb67b7 Merge pull request #4974 from ericrosenbaum/feature/sound-editor-selection4
Sound editor update: selection-based editing
2019-07-30 15:27:04 -04:00
Eric Rosenbaum 6bbff14aa4 Update chromedriver 2019-07-30 15:09:27 -04:00
Eric Rosenbaum 58499cec73 Add integration test for sound editor keyboard shortcuts 2019-07-30 14:57:25 -04:00
Eric Rosenbaum 2890609053 Add unit test for audio effect adjustedTrim times 2019-07-30 11:38:52 -04:00
greenkeeper[bot] 416284afad chore(package): update lockfile package-lock.json 2019-07-30 13:17:38 +00:00
greenkeeper[bot] 73480840db chore(package): update scratch-vm to version 0.2.0-prerelease.20190730131442 2019-07-30 13:17:34 +00:00
Paul Kaplan 227d747c72 Fix gestureInProgress after dragEnd to return the correct value
Fixes #5031, which is happening because the gestureInProgress call is still returning true after a drag ends.
2019-07-29 15:52:15 -04:00
Eric Rosenbaum 6100e0f199 Add integration tests for copy/paste 2019-07-29 14:00:04 -04:00
DD Liu 5b1670f520 Merge pull request #5036 from fsih/svgRendTest
Roll forward scratch-svg-renderer
2019-07-29 12:22:13 -04:00
DD Liu f62f170226 update package-lock 2019-07-29 11:54:59 -04:00
DD Liu 7e38d9dd0c update paint 2019-07-29 11:45:29 -04:00
DD Liu 5cd13c3039 Update svg renderer 2019-07-29 11:45:05 -04:00
Chris Garrity 1b4b50b09a Make space and enter actually work if focused
If the delete button is the current active element (e.g. focused by tab) make hitting ‘space’ or ‘Enter’ do the actual delete.
2019-07-26 09:37:39 -04:00
Chris Garrity cd7b5d93ee fix focus styling
Don’t use default focus style it’s ugly.

Don’t allow the image to be dragged.
2019-07-26 09:36:16 -04:00
Eric Rosenbaum 1563f7892d Add a test for the copy to new button 2019-07-25 16:32:53 -04:00
Eric Rosenbaum 5472f0a3e9 Enable the accidentally disabled sound integration tests 2019-07-25 16:32:27 -04:00
Eric Rosenbaum 032cd5aa02 Use DragRecognizer in AudioTrimmer 2019-07-25 15:53:07 -04:00
Eric Rosenbaum b0dbe9e323 Flip copy to new button in RTL, and use a class for flipping buttons 2019-07-25 15:29:10 -04:00
Eric Rosenbaum 8f23093617 Update snapshot 2019-07-25 15:12:04 -04:00
Eric Rosenbaum afccf65157 Improve accuracy of waveform rendering 2019-07-25 15:10:13 -04:00
Eric Rosenbaum c215104522 Only deselect-outside if there’s a selection 2019-07-25 14:33:49 -04:00
Ray Schamp 68389c8e6f Merge pull request #5027 from chrisgarrity/remove-delete-slop
Remove touch slop
2019-07-25 11:52:02 -04:00
Chris Garrity d4201075d0 Remove touch slop 2019-07-25 11:47:21 -04:00
Ray Schamp 1f95ff0fb1 Merge pull request #5026 from chrisgarrity/fix-file-menu
Check for null child before cloning menu item
2019-07-25 11:26:35 -04:00
Chris Garrity dd0c3d7468 Check for null child before cloning menu item 2019-07-25 11:20:18 -04:00
Eric Rosenbaum 0b9969c412 Update snapshot 2019-07-24 17:11:36 -04:00
Eric Rosenbaum d8df13a12c Prevent selection from moving when handles cross over 2019-07-24 17:02:54 -04:00
Eric Rosenbaum f28b6d8e00 Make the waveform rendering more precisely positioned 2019-07-24 17:01:56 -04:00
Eric Rosenbaum c23e4c4611 Improve effect button active state padding 2019-07-24 15:03:40 -04:00
Eric Rosenbaum bc3e69d047 Prevent dragging img in trimmer handles on firefox 2019-07-24 14:08:36 -04:00
Eric Rosenbaum aaa9c355e9 Use svgs w/o masks to prevent blurriness on safari 2019-07-24 14:02:20 -04:00
Ray Schamp c1dcbb0997 Merge pull request #5023 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1563990552
Update scratch-blocks to version 0.1.0-prerelease.1563990552
2019-07-24 13:53:46 -04:00
greenkeeper[bot] bc501cede2 chore(package): update lockfile package-lock.json 2019-07-24 17:51:57 +00:00
greenkeeper[bot] c243fa9c23 chore(package): update scratch-blocks to version 0.1.0-prerelease.1563990552
Closes #5008
2019-07-24 17:51:54 +00:00
Ray Schamp 765a5d3b95 Merge pull request #5020 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190724154115
Update scratch-paint to the latest version 🚀
2019-07-24 13:49:35 -04:00
chrisgarrity 351c0ba9b1 Merge pull request #5022 from LLK/greenkeeper/scratch-l10n-3.5.20190724160319
Greenkeeper/scratch l10n 3.5.20190724160319
2019-07-24 12:42:02 -04:00
greenkeeper[bot] ac30e17222 chore(package): update lockfile package-lock.json 2019-07-24 16:05:41 +00:00
greenkeeper[bot] bcdb2d5f3c chore(package): update scratch-l10n to version 3.5.20190724160319
Closes #5019
2019-07-24 16:05:37 +00:00
Eric Rosenbaum 6e5f378633 Prevent buffer detach issues with copy/paste on firefox 2019-07-24 12:05:27 -04:00
greenkeeper[bot] e70a99e52d chore(package): update lockfile package-lock.json 2019-07-24 15:43:19 +00:00
greenkeeper[bot] c6230ffc66 chore(package): update scratch-paint to version 0.2.0-prerelease.20190724154115 2019-07-24 15:43:15 +00:00
Karishma Chadha edaf9badcf Merge pull request #5013 from LLK/hotfix/revert-vm-raise-params-pr
Hotfix/revert vm raise params pr
2019-07-24 11:35:02 -04:00
Eric Rosenbaum 24d2e344e5 Prevent default on keyboard shortcuts 2019-07-24 11:31:20 -04:00
Eric Rosenbaum 79a819989f Prevent img drag and select issues 2019-07-24 11:22:25 -04:00
Eric Rosenbaum d882d180a5 Use separate play and stop icons for sound editor 2019-07-24 11:07:10 -04:00
Eric Rosenbaum 6be4e5e122 Fix mirroring of effect icons for RTL 2019-07-23 18:00:38 -04:00
Eric Rosenbaum 75dbed61fa Fix bug where play after paste used old selection state 2019-07-23 17:51:46 -04:00
chrisgarrity c4122ec03f Merge pull request #4960 from chrisgarrity/customize-menubar
Customize menubar
2019-07-23 16:32:35 -04:00
Eric Rosenbaum 0f17157a3d Update tests without echo effect 2019-07-23 16:14:56 -04:00
Eric Rosenbaum 5d2287b0b5 Use DragRecognizer to fix draggging on touch 2019-07-23 16:05:29 -04:00
Eric Rosenbaum c9ec59dc8a Remove unused icon 2019-07-23 15:37:00 -04:00
Eric Rosenbaum 3ab1b8254b Fix tool button styles 2019-07-23 15:29:03 -04:00
Eric Rosenbaum bf5b05e63f Re-order effects buttons and remove echo 2019-07-23 15:00:35 -04:00
Eric Rosenbaum 0eacc4b718 Prevent undo button layout from breaking 2019-07-23 15:00:22 -04:00
Eric Rosenbaum e1100f975c Fix handle div position 2019-07-23 14:48:22 -04:00
Eric Rosenbaum da4b492e08 Show trim handles over the edge of the asset panel 2019-07-23 14:21:18 -04:00
chrisgarrity 288aa721b6 Merge pull request #4915 from chrisgarrity/4911-delete-icon
Add larger delete icon to make it easier to do on touch devices
2019-07-23 11:40:56 -04:00
Eric Rosenbaum 324f3a6687 styles for trim handles and play button 2019-07-23 11:08:05 -04:00
Eric Rosenbaum d9409f0f0c Cleanup test 2019-07-22 17:48:25 -04:00
Eric Rosenbaum 40dc8487f4 Fix off-by-one error in reverse effect, add a test 2019-07-22 17:47:44 -04:00
Eric Rosenbaum bbe72e3485 Remove extra spaces 2019-07-22 16:10:31 -04:00
Eric Rosenbaum d6f2b0a48a Clean up AudioEffects constructor (needs a refactor) 2019-07-22 16:05:08 -04:00
Eric Rosenbaum e74255cf10 Use static getters for echo params 2019-07-22 15:07:34 -04:00
Karishma Chadha dd2841f349 More package-lock updates 2019-07-22 14:15:59 -04:00
Eric Rosenbaum 3823dd0629 Remove unused prop 2019-07-22 13:56:00 -04:00
Eric Rosenbaum 056da9ab94 Use offline context to create offline nodes 2019-07-22 13:55:49 -04:00
Karishma Chadha c9808e0eee Update vm to pull in PR reversion causing custom procedures regression 2019-07-22 13:54:56 -04:00
Chris Garrity 6bf9fac393 addressing comments
* add scratchLogo as default for logo prop
* prefer `{featureAllowed && (<Component />)} over ternary operator with a null ‘else’
* renamed `handleSaveToComputer` as `getSaveToComputerHandler` to more clearly represent what it’s doing (returning a function to be the handler)
* renamed the parameter that is the function to handle clicking on the load project menu item (also updated the comments in `SBFileUploader`)
2019-07-22 12:25:43 -04:00
Chris Garrity 6d482fdd5e Allow customized logo
Show alternate logo if GUI is initialized with a different file.
2019-07-22 12:05:04 -04:00
Chris Garrity 48c50692fc Add flags for language and file menus
Show or hide the language and file menus.
2019-07-22 12:05:04 -04:00
Chris Garrity 4e47cc875a Cleanup
* removed commented out cruft
* added tabIndex prop to delete button (with default=0)
* the last change also required a change to the sprite selector test snapshot
2019-07-22 12:02:14 -04:00
Chris Garrity 415ecee8ba Update tests 2019-07-22 12:02:14 -04:00
Chris Garrity d1e6960cd4 Modify tile delete button for touch
Prototype of larger delete button for sprites, costumes and sounds to better support touch devices.

I split it out from `CloseButton` as it made the changes simpler, and we may end up going in a different direction (open context menu).
2019-07-22 12:02:14 -04:00
chrisgarrity f2ac39fe0e Merge pull request #5010 from chrisgarrity/fix-package-lock
fix package-lock (revert svg-renderer) version
2019-07-22 11:34:04 -04:00
Chris Garrity 8e67cd413a fix package-lock (revert svg-renderer) version
*
2019-07-22 11:30:28 -04:00
Ray Schamp 4418231892 Merge pull request #4983 from rschamp/fix-browserslist-warning
Move browsers options to .browserslistrc
2019-07-22 10:14:44 -04:00
DD Liu 8eee001c12 Merge pull request #5003 from fsih/updatePaint
Update paint to revert svg-renderer
2019-07-18 14:11:55 -04:00
DD Liu a290fb0227 Update paint to revert svg-renderer 2019-07-18 14:01:50 -04:00
DD Liu 42ddb2387a Merge pull request #5002 from fsih/revertSvgRend
Revert to last good scratch-svg-renderer
2019-07-18 13:38:37 -04:00
DD Liu 1e51907e74 Revert to last good scratch-svg-renderer 2019-07-18 13:36:57 -04:00
picklesrus 568a75480f Merge pull request #4996 from LLK/greenkeeper/scratch-l10n-3.3.20190717144934
Update scratch-l10n to the latest version 🚀
2019-07-17 16:32:44 -04:00
picklesrus 460a0f1969 Merge pull request #4997 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190717201758
Update scratch-vm to the latest version 🚀
2019-07-17 16:32:09 -04:00
greenkeeper[bot] 8d4f86dc3b chore(package): update lockfile package-lock.json 2019-07-17 20:20:51 +00:00
greenkeeper[bot] 56f9f02798 chore(package): update scratch-vm to version 0.2.0-prerelease.20190717201758 2019-07-17 20:20:48 +00:00
greenkeeper[bot] abf1b731bd chore(package): update scratch-l10n to version 3.3.20190717144934 2019-07-17 14:51:21 +00:00
Eric Rosenbaum 5062518d16 Fix playhead CSS 2019-07-16 16:41:36 -04:00
Eric Rosenbaum df7e77c865 Localize button titles 2019-07-16 16:14:28 -04:00
Eric Rosenbaum f0a18d3fd3 Extract selection handle and playhead components from trimmer and selector 2019-07-16 16:00:43 -04:00
Benjamin Wheeler 8dade6d387 Merge pull request #4985 from LLK/greenkeeper/react-modal-3.9.1
Update react-modal to the latest version 🚀
2019-07-16 15:45:21 -04:00
DD Liu 73eb44c0f6 Merge pull request #4989 from LLK/greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190715153806
Greenkeeper/scratch svg renderer 0.2.0 prerelease.20190715153806
2019-07-15 15:39:38 -04:00
DD Liu b190d29d46 Merge branch 'develop' into greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190715153806 2019-07-15 15:05:25 -04:00
DD Liu f858590358 Merge pull request #4991 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190715183326
Update scratch-paint to the latest version 🚀
2019-07-15 15:01:32 -04:00
greenkeeper[bot] 0795c511de chore(package): update lockfile package-lock.json 2019-07-15 18:35:08 +00:00
greenkeeper[bot] de678d8663 chore(package): update scratch-paint to version 0.2.0-prerelease.20190715183326 2019-07-15 18:35:05 +00:00
DD Liu 5c60052775 Merge branch 'develop' into greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190715153806 2019-07-15 14:30:04 -04:00
Andrew Sliwinski a0edb49c0a Merge pull request #4990 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190715155430
Greenkeeper/scratch paint 0.2.0 prerelease.20190715155430
2019-07-15 12:27:33 -04:00
Andrew Sliwinski 1282f659f9 Merge pull request #4986 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190715144356
Update scratch-vm to the latest version 🚀
2019-07-15 12:27:21 -04:00
greenkeeper[bot] f7b91a79e6 chore(package): update lockfile package-lock.json 2019-07-15 16:11:52 +00:00
greenkeeper[bot] 426546c36e chore(package): update scratch-paint to version 0.2.0-prerelease.20190715155430
Closes #4988
2019-07-15 16:11:48 +00:00
greenkeeper[bot] 404d9ed65f chore(package): update lockfile package-lock.json 2019-07-15 16:11:22 +00:00
greenkeeper[bot] 753aebdba7 chore(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190715153806
Closes #4987
2019-07-15 16:11:18 +00:00
Eric Rosenbaum 2659490a70 Update snapshot 2019-07-15 12:00:27 -04:00
Eric Rosenbaum cff0b17881 Combine CSS for trimmer and selector into single file 2019-07-15 11:40:10 -04:00
greenkeeper[bot] 9d4218531b chore(package): update lockfile package-lock.json 2019-07-15 15:04:03 +00:00
greenkeeper[bot] b56020be3e chore(package): update scratch-vm to version 0.2.0-prerelease.20190715144356 2019-07-15 15:03:59 +00:00
greenkeeper[bot] b5c5e28b59 chore(package): update lockfile package-lock.json 2019-07-14 16:53:10 +00:00
greenkeeper[bot] 8a2bcb3f53 chore(package): update react-modal to version 3.9.1 2019-07-14 16:53:07 +00:00
Ray Schamp e5ae5b7035 Merge pull request #4981 from LLK/greenkeeper/initial
Update dependencies to enable Greenkeeper 🌴
2019-07-11 14:08:32 -04:00
Ray Schamp 413df6ed65 Move browsers options to .browserslistrc
Apparently this is the recommendation for loaders that look at the `browsers` option.
2019-07-11 14:00:24 -04:00
Ray Schamp 0ec46056f0 Add package-lock to repo 2019-07-11 13:53:31 -04:00
Ray Schamp 9dcb1f8126 Revert "chore(package): update dependencies"
This reverts commit 4e9c8aea01.
2019-07-11 13:48:28 -04:00
greenkeeper[bot] 4e9c8aea01 chore(package): update dependencies 2019-07-11 14:44:45 +00:00
DD Liu 7d748ce59d Merge pull request #4980 from fsih/updatel10n
Update l10n
2019-07-10 21:11:01 -04:00
DD Liu 198cf00498 update l10n 2019-07-10 21:10:07 -04:00
DD Liu 145592b2e1 Merge pull request #4979 from fsih/unupdateScratchDeps
Revert to last good render
2019-07-10 20:31:15 -04:00
DD Liu 6fe8f7b2ec revert to last good render 2019-07-10 20:11:03 -04:00
DD Liu 3cbfa7cfc3 Merge pull request #4978 from fsih/updateScratchDeps
Update deps
2019-07-10 19:57:33 -04:00
DD Liu 30a35d7651 Update deps 2019-07-10 19:53:57 -04:00
Eric Rosenbaum c46069759a Cleanup and lint fixes 2019-07-09 17:45:25 -04:00
Eric Rosenbaum 68a4284e8c Add util for encodeAndAddSoundToVM 2019-07-09 17:33:33 -04:00
Eric Rosenbaum c11624ab06 Fix tests and remove unused icons 2019-07-09 17:23:20 -04:00
Eric Rosenbaum 91c81131d8 Add keyboard shortcuts 2019-07-09 15:32:30 -04:00
Eric Rosenbaum ee44e225f5 Icon button styling 2019-07-09 15:32:29 -04:00
Eric Rosenbaum 0a489ea2ea Add copy, paste, copy to new and delete buttons 2019-07-09 15:32:29 -04:00
Eric Rosenbaum 1e147e4397 Fix tests 2019-07-09 15:32:29 -04:00
Eric Rosenbaum c11b4a34ef Protect effects from error due to very short sound 2019-07-09 15:32:29 -04:00
Eric Rosenbaum e5a517a325 Include selection state in undo/redo stack 2019-07-09 15:32:29 -04:00
Eric Rosenbaum 7bbe5f0589 Add fade in, fade out and mute effects 2019-07-09 15:32:29 -04:00
Eric Rosenbaum 8f949f532f Apply effects to selection 2019-07-09 15:32:29 -04:00
Eric Rosenbaum f71a481d0e Remove icons not yet in use 2019-07-09 15:32:29 -04:00
Eric Rosenbaum fdd4b61d5d Revert and fix tests
This reverts commit 42ff50300cff7558fd03cdebb757eca8caf85199.
2019-07-09 15:32:29 -04:00
Eric Rosenbaum b34e1c69f0 Cleanup and connect trim button to delete 2019-07-09 15:32:29 -04:00
Eric Rosenbaum 64562a8a05 Selection and delete 2019-07-09 15:32:29 -04:00
Eric Rosenbaum 0d88c7755b Update tests 2019-07-09 15:32:28 -04:00
Eric Rosenbaum 7704bafb00 Add icons 2019-07-09 15:32:28 -04:00
Paul Kaplan ab31d36251 Merge pull request #4962 from LLK/hotfix/2019-07-02
Correctly fail on non-200 responses when saving
2019-07-08 13:05:14 -04:00
Paul Kaplan 7362bd999c Respond to non-200 responses when saving 2019-07-02 12:32:27 -04:00
Paul Kaplan b4a0879584 Merge pull request #4953 from LLK/paulkaplan-patch-1
Bump l10n version
2019-06-26 13:59:33 -04:00
Paul Kaplan 8274a8c6c7 Bump l10n version 2019-06-26 13:59:19 -04:00
Paul Kaplan cdffd567b6 Merge pull request #4952 from LLK/paulkaplan-patch-1
Update blocks manually
2019-06-26 12:59:22 -04:00
Paul Kaplan c7003b4765 Update blocks manually
Seems like greenkeeper isn't keeping up?
2019-06-26 12:57:58 -04:00
Paul Kaplan 4e3f2160ae Merge pull request #4938 from LLK/touch-share
Allow sharing blocks/costumes/sounds on touch devices
2019-06-26 12:55:33 -04:00
Paul Kaplan 095fea939d Merge pull request #4941 from paulkaplan/proper-keys
Fix library images lagging behind filter changes
2019-06-26 12:53:44 -04:00
Paul Kaplan 26e4bc2ad2 Fix merge conflicts 2019-06-26 10:56:54 -04:00
Paul Kaplan 18241a5cbd Merge branch 'develop' into touch-share 2019-06-26 10:35:13 -04:00
Paul Kaplan d8cd3e38c7 Merge pull request #4931 from paulkaplan/fix-wistia-crash
Prevent crash when wistia API takes a while to load
2019-06-26 10:28:32 -04:00
Paul Kaplan 0bbb11012d Merge pull request #4935 from LLK/drag-angle
Allow vertical scrolling of sprite tile pane on touch devices
2019-06-24 14:05:32 -04:00
Paul Kaplan 212c998980 Merge pull request #4947 from LLK/update-paint
Bump scratch-paint version
2019-06-24 10:30:36 -04:00
Paul Kaplan e359efd97e Merge pull request #4948 from LLK/paulkaplan-patch-1
Fix PropType on Loupe component
2019-06-24 10:30:19 -04:00
Paul Kaplan 547640bfcc Add sprite prop to proptypes 2019-06-24 10:11:17 -04:00
Paul Kaplan d7a2264c0d Fix proptypes on Loupe component 2019-06-24 09:57:17 -04:00
Paul Kaplan 0bb91add74 Bump scratch-paint version 2019-06-24 09:42:09 -04:00
Paul Kaplan cea2dd9ab9 Fix spacing 2019-06-21 09:45:23 -04:00
Paul Kaplan c424f25da1 Fix proptypes 2019-06-21 09:33:26 -04:00
Paul Kaplan 7c1931749c Use name or url for library keys 2019-06-21 09:19:43 -04:00
Paul Kaplan 4708ccc7dc Be defensive about using the Wistia API 2019-06-21 09:19:18 -04:00
Paul Kaplan 9e0a239ec8 Merge pull request #4932 from paulkaplan/fix-tutorial-image-sync
Prevent previous tutorial images from hanging around on next/prev
2019-06-21 09:05:28 -04:00
Paul Kaplan 8a2c001531 Merge pull request #4921 from paulkaplan/on-vm-init
Add VM init callback
2019-06-21 09:05:10 -04:00
Paul Kaplan 044b958e08 Add vm init callback 2019-06-21 08:48:53 -04:00
Paul Kaplan a2bbed65d5 Force img node change to prevent previous tutorial images from hanging around 2019-06-21 08:41:01 -04:00
picklesrus cf62045e26 Merge pull request #4942 from LLK/revert-4940-revert-4790
Revert "Revert "Merge pull request #4790 from LLK/e16n""
2019-06-20 17:53:23 -04:00
chrisgarrity 164ddbe4f4 Revert "Revert "Merge pull request #4790 from LLK/e16n"" 2019-06-20 17:12:59 -04:00
picklesrus 6b5eb032ad Merge pull request #4940 from picklesrus/revert-4790
Revert "Merge pull request #4790 from LLK/e16n"
2019-06-20 11:40:30 -04:00
picklesrus 8d3a85860e Revert "Merge pull request #4790 from LLK/e16n"
This reverts commit c061f2e2f0, reversing
changes made to 6e007b1e4a.
2019-06-20 11:21:56 -04:00
Benjamin Wheeler cbb265e9f0 Merge pull request #4934 from benjiwheeler/remove-example-test
skip example change categories test
2019-06-19 18:13:05 -04:00
Eric Rosenbaum 948fb2f747 Merge pull request #4937 from zoebentley/tutorial-updates
Make Updates to Tutorials Library
2019-06-19 16:54:05 -04:00
Zoë Bentley b0ca6dfcaa Lint 2019-06-19 16:22:34 -04:00
Zoë Bentley 61eb31ea66 Optimize .png images 2019-06-19 15:53:48 -04:00
Paul Kaplan eed4c36b7b Emit hover on touch end to make sharing to sprites work on touch 2019-06-19 15:41:59 -04:00
Zoë Bentley b5a3a53780 Added 3 new tutorials, added pngs to tutorials that only had videos, reordered tutorials in library 2019-06-19 15:22:50 -04:00
Eric Rosenbaum a6302ecc14 Merge pull request #4936 from LLK/ericrosenbaum-patch-1
Update l10n
2019-06-19 15:04:01 -04:00
Eric Rosenbaum 2a9d74adb4 Update l10n 2019-06-19 14:45:01 -04:00
Eric Rosenbaum e5772a5902 Merge pull request #4922 from ericrosenbaum/feature/acw-assets
Updates to sprite and costume libraries
2019-06-19 11:36:51 -04:00
Paul Kaplan abdeb8dbdb Fix context menu coming up when dragging 2019-06-19 11:27:00 -04:00
Paul Kaplan 8611c9ff1a Refactor gesture recognizer with tests 2019-06-19 11:26:42 -04:00
Ben Wheeler f7244e6c89 skip example change categories test 2019-06-19 11:25:07 -04:00
Paul Kaplan 4b7f72c00c Only start tile drag for certain angles 2019-06-19 09:22:05 -04:00
Christopher Willis-Ford 1476c88a54 update VM for non-droppable extension menu support 2019-06-18 21:27:42 -07:00
Chris Willis-Ford c061f2e2f0 Merge pull request #4790 from LLK/e16n
Supporting GUI changes for extensionification
2019-06-18 16:22:25 -07:00
Chris Willis-Ford dc49c0ebe4 Merge branch 'develop' into e16n 2019-06-18 16:06:46 -07:00
Christopher Willis-Ford 11d4273982 update VM to an e16n-compatible version 2019-06-18 16:02:28 -07:00
Christopher Willis-Ford 7755f76215 simplify MockBlock construction 2019-06-18 14:15:43 -07:00
Christopher Willis-Ford 6e44bfea95 fix lint errors in define-dynamic-block.test.js 2019-06-18 11:37:17 -07:00
Christopher Willis-Ford fa8ceacbcf add comments suggested in code review 2019-06-18 11:31:53 -07:00
Christopher Willis-Ford d19c468336 allow per-block colors 2019-06-18 11:24:29 -07:00
Chris Willis-FordandKarishma Chadha 86edd829c0 Apply suggestions from code review
Comments slightly tweaked from kchadha's suggestions

Co-Authored-By: Karishma Chadha <kchadha@media.mit.edu>
2019-06-18 11:21:03 -07:00
Eric Rosenbaum bdc9c1e7de Update sprite and costume libraries 2019-06-17 17:56:28 -04:00
chrisgarrity 6e007b1e4a Merge pull request #4917 from chrisgarrity/update-l10n
Update l10n to the latest version
2019-06-12 12:05:32 -04:00
Chris Garrity 356c1d9811 Update l10n to the latest version 2019-06-12 11:47:38 -04:00
picklesrus 723a2bf117 Merge pull request #4913 from LLK/picklesrus-patch-1
Update scratch-vm
2019-06-10 13:22:50 -04:00
picklesrus 7b7e5e6e72 Update scratch-vm
Greenkeeper didn't do this for me... not sure why.
2019-06-10 11:59:41 -04:00
Paul Kaplan 3fcbf74c71 Merge pull request #4909 from LLK/progressive-libraries
Show libraries before loading their content
2019-06-10 09:29:56 -04:00
Paul Kaplan 8f4a6ce255 Fix proptype sorting 2019-06-07 09:19:26 -04:00
Paul Kaplan 9e0853aa9a Merge branch 'develop' of github.com:LLK/scratch-gui into progressive-libraries 2019-06-07 09:06:39 -04:00
Paul Kaplan c4480b9ade Show spinner before loading library content 2019-06-07 09:05:56 -04:00
Paul Kaplan 1a588880bf Merge pull request #4901 from paulkaplan/no-videos-on-desktop
Use a "title card" instead of video if using Scratch Desktop
2019-06-05 16:10:16 -04:00
Paul Kaplan e2c2392a70 Only show items in the library that would have image steps for offline
Because the video steps get replaced by an image step if offline, we should not show items that would result in having no content
2019-06-05 15:09:28 -04:00
Paul Kaplan 5160b5ae22 Make the cards images all the same size, and shrink to fit centered 2019-06-05 15:08:32 -04:00
Karishma Chadha 8be847b68a Merge pull request #4904 from LLK/kchadha-update-render
Update scratch-render
2019-06-05 14:51:29 -04:00
Karishma Chadha e7ed24f1ef Update scratch-render 2019-06-05 14:50:57 -04:00
Paul Kaplan 13e324b52d Use title card instead of video if using scratchDesktop flag 2019-06-05 12:53:32 -04:00
Karishma Chadha 5c7b221799 Merge pull request #4900 from LLK/kchadha-update-deps
Update scratch-vm and scratch-l10n.
2019-06-05 11:51:25 -04:00
Karishma Chadha 7398a30223 Update scratch-vm and scratch-l10n. 2019-06-05 11:34:33 -04:00
Paul Kaplan 637b9fd225 Merge pull request #4832 from paulkaplan/do-not-select-stage
Make surprise and library backdrop additions NOT select the stage
2019-06-05 10:45:34 -04:00
Benjamin Wheeler 88442708c2 Merge pull request #4883 from benjiwheeler/fix-project-loading-tests
fixed project loading tests
2019-06-05 10:35:26 -04:00
Paul Kaplan d7759317c4 Merge pull request #4896 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190604153446
chore(package): update scratch-vm to version 0.2.0-prerelease.2019060…
2019-06-04 14:10:38 -04:00
Ben Wheeler fd3045eb9d also turn off examples tests that load remote project 2019-06-04 11:40:03 -04:00
greenkeeper[bot] 09ae22f329 chore(package): update scratch-vm to version 0.2.0-prerelease.20190604153446
Closes #4894
2019-06-04 15:37:41 +00:00
Benjamin Wheeler 365e3b3bce Merge pull request #4881 from LLK/revert-4877-revert-4835-bugfix/4766
Revert "Revert "Pause video on tutorial window shrink""
2019-06-03 16:16:01 -04:00
Christopher Willis-Ford a6fe8585fb add unit tests for defineDynamicBlock 2019-06-03 13:04:15 -07:00
Christopher Willis-Ford 930b2ec05c setOutput takes a Boolean, not a type 2019-06-03 13:04:15 -07:00
Christopher Willis-Ford c9d1535967 move defineDynamicBlock into its own file 2019-06-03 13:04:15 -07:00
Evelyn Eastmond 550849c93f Reset video if a new one has been chosen. Added some comments. 2019-05-30 22:03:56 -04:00
Ben Wheeler 8128902cd6 fixed project loading test 2019-05-30 18:52:33 -04:00
Evelyn Eastmond b350049ca7 Testing a commit onto upstream branch for revert PR. 2019-05-30 17:17:01 -04:00
Evelyn Eastmond e0e07929e8 Revert "Revert "Pause video on tutorial window shrink"" 2019-05-30 17:10:58 -04:00
Benjamin Wheeler 128b196220 Merge pull request #4878 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190530160621
Update scratch-render to the latest version 🚀
2019-05-30 12:23:54 -04:00
greenkeeper[bot] 5b806dff27 chore(package): update scratch-render to version 0.1.0-prerelease.20190530160621 2019-05-30 16:13:40 +00:00
Benjamin Wheeler 9c5d358c3e Merge pull request #4877 from LLK/revert-4835-bugfix/4766
Revert "Pause video on tutorial window shrink"
2019-05-30 12:01:22 -04:00
Benjamin Wheeler ab89ee1a4f Revert "Pause video on tutorial window shrink" 2019-05-30 11:59:45 -04:00
Paul Kaplan dc82e5f653 Merge pull request #4873 from LLK/revert-4800-delay-gui
Revert "Delay loading and rendering gui"
2019-05-30 11:35:57 -04:00
Paul Kaplan 7fb3a81a66 Merge pull request #4874 from LLK/revert-4677-update-toolbox
Revert "render only new toolbox xmls"
2019-05-30 11:31:30 -04:00
Paul Kaplan d958b14b8d Revert "render only new toolbox xmls" 2019-05-30 10:50:41 -04:00
Paul Kaplan 0a6c75eb93 Revert "Delay loading and rendering gui" 2019-05-30 09:41:05 -04:00
Colby Gutierrez-Kraybill 7c5a4fc682 Merge pull request #4870 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190524155459
chore(package): update scratch-vm to version 0.2.0-prerelease.2019052…
2019-05-29 15:46:39 -04:00
Bryce Taylor ca8caa8504 Merge pull request #4871 from BryceLTaylor/Stabilize-Integration-Tests
Stabilize integration tests
2019-05-29 15:40:02 -04:00
Benjamin Wheeler 339dfe0b08 Merge pull request #4846 from paulkaplan/update-builtin-assets
Update the built-in SVG assets for the default project
2019-05-29 15:11:15 -04:00
BryceLTaylor b9cefd325b Remove unused reference to findByText in integration tests 2019-05-29 14:45:57 -04:00
BryceLTaylor fa974c68bd Remove WaitUntilGone function from integration tests
These were causing the tests to fail and should be fixed by the update to FindByXpath function.
2019-05-29 14:23:50 -04:00
BryceLTaylor 3c47e0e040 Make Find by Xpath ensure element is visible in integration tests 2019-05-29 14:23:50 -04:00
Colby Gutierrez-Kraybill 34a9a97201 Merge pull request #4868 from LLK/greenkeeper/scratch-l10n-3.3.20190529144051
Update scratch-l10n to the latest version 🚀
2019-05-29 13:23:10 -04:00
Colby Gutierrez-Kraybill f6847e3aea Merge branch 'develop' into greenkeeper/scratch-l10n-3.3.20190529144051 2019-05-29 13:08:47 -04:00
Evelyn Eastmond e27021cbf4 Merge pull request #4835 from evhan55/bugfix/4766
Pause video on tutorial window shrink
2019-05-29 12:39:50 -04:00
Colby Gutierrez-Kraybill 3dc6413ea4 Merge pull request #4867 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1559136797
Update scratch-blocks to the latest version 🚀
2019-05-29 11:38:53 -04:00
greenkeeper[bot] 53c04a1ad9 chore(package): update scratch-l10n to version 3.3.20190529144051 2019-05-29 14:45:06 +00:00
Evelyn Eastmond b2486380fb Disable dragging on video to allow for scrobbling. 2019-05-29 09:46:08 -04:00
greenkeeper[bot] 9f41ddaaa2 chore(package): update scratch-blocks to version 0.1.0-prerelease.1559136797 2019-05-29 13:35:43 +00:00
Paul Kaplan ef4d8c6e8d Merge pull request #4852 from LLK/greenkeeper/scratch-blocks-0.1.0-prerelease.1558719162
Update scratch-blocks to the latest version 🚀
2019-05-29 08:34:07 -04:00
Paul Kaplan a6245b87f6 Merge pull request #4866 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190528231524
chore(package): update scratch-render to version 0.1.0-prerelease.201…
2019-05-29 08:33:43 -04:00
Evelyn Eastmond fa4541e85c Adding dynamic removal of dynamically loaded scripts. 2019-05-29 08:27:20 -04:00
Evelyn Eastmond 376f5626a4 Remove auto-play on expansion. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond e17dc90983 Fix Wistia access to use window. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond a63385343a Remove useless props constructor. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond 3c3206d8e2 Remove bindAll import. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond f6c8feae6e Video play/pause test on video step expand/shrink. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond 98a007a17e Pause video on component update if not expanded. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond 3e0d5b9ede Clean up and get handle on video on load. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond 555082bb1d Change Wistia embed so video API is accessible from current window. 2019-05-28 20:34:14 -04:00
Evelyn Eastmond 5c95c352c3 Trying VideoStep component. 2019-05-28 20:34:14 -04:00
greenkeeper[bot] 3b4758ca5c chore(package): update scratch-render to version 0.1.0-prerelease.20190528231524
Closes #4864
2019-05-28 23:16:58 +00:00
Christopher Willis-Ford 38455a7108 enable monitors for dynamic extension blocks 2019-05-28 11:45:57 -07:00
Karishma Chadha ebe310bc65 Add support for arguments on dynamic extension blocks. 2019-05-28 11:45:57 -07:00
Christopher Willis-Ford 5bc3198263 remove unused require 2019-05-28 11:45:57 -07:00
Christopher Willis-Ford 5d6669db6d add "scratch_extension" only if a block has an icon 2019-05-28 11:45:57 -07:00
Christopher Willis-Ford 519ffe3b91 weave extension categories into the output of makeToolboxXML 2019-05-28 11:45:57 -07:00
Christopher Willis-Ford 717c773800 add block type check in defineDynamicBlock 2019-05-28 11:45:57 -07:00
Christopher Willis-Ford 935a4520da WIP dynamic block support 2019-05-28 11:45:56 -07:00
Christopher Willis-Ford 7685dad40a update extension event handling for categoryInfo 2019-05-28 11:45:56 -07:00
Chris Willis-Ford 699671bde5 Merge pull request #4841 from cwillisf/test-failure-feedback
add timeouts & messages to all driver.wait calls
2019-05-28 09:43:39 -07:00
Florrie 639fc6a994 Always display whitespace in list monitors 2019-05-26 11:18:25 -03:00
Paul Kaplan 7facbf35b2 Merge pull request #4851 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190524133325
Update scratch-paint to the latest version 🚀
2019-05-24 13:38:31 -04:00
greenkeeper[bot] b54fe54b4a chore(package): update scratch-blocks to version 0.1.0-prerelease.1558719162 2019-05-24 17:35:05 +00:00
Christopher Willis-Ford 796339dcfe simplify SeleniumHelper timeout options 2019-05-24 09:35:44 -07:00
greenkeeper[bot] 1721a322c2 chore(package): update scratch-vm to version 0.2.0-prerelease.20190524155459
Closes #4840
2019-05-24 15:57:43 +00:00
greenkeeper[bot] 3125097b83 chore(package): update scratch-paint to version 0.2.0-prerelease.20190524133325 2019-05-24 13:34:51 +00:00
Paul Kaplan aa0c40fca1 Merge pull request #4850 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190524125950
Update scratch-render to the latest version 🚀
2019-05-24 09:26:53 -04:00
Paul Kaplan 2d61027bbe Merge pull request #4848 from LLK/greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190523193400
Update scratch-svg-renderer to the latest version 🚀
2019-05-24 09:26:33 -04:00
greenkeeper[bot] 2dac3cae81 chore(package): update scratch-render to version 0.1.0-prerelease.20190524125950 2019-05-24 13:02:23 +00:00
greenkeeper[bot] 43d3df2ffd chore(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190523193400 2019-05-23 19:38:15 +00:00
Paul Kaplan 44ffaeee2c Merge pull request #4847 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190523183140
Update scratch-paint to the latest version 🚀
2019-05-23 15:33:33 -04:00
greenkeeper[bot] 0bf4e2c781 chore(package): update scratch-paint to version 0.2.0-prerelease.20190523183140 2019-05-23 18:56:29 +00:00
Paul Kaplan d342b82219 Merge pull request #4800 from mzgoddard/delay-gui
Delay loading and rendering gui
2019-05-23 14:14:37 -04:00
Paul Kaplan 227581ebcf Merge pull request #4677 from mzgoddard/update-toolbox
render only new toolbox xmls
2019-05-23 14:14:09 -04:00
Paul Kaplan eb74d6c033 Include a unit test to ensure default project assets are included 2019-05-23 11:22:36 -04:00
Paul Kaplan a2035a68e4 Update the built-in SVG assets for the default project 2019-05-23 09:59:41 -04:00
Christopher Willis-Ford 1b055af545 add timeouts & messages to all driver.wait calls 2019-05-22 18:25:45 -07:00
Karishma Chadha 5b79947178 Merge pull request #4842 from LLK/greenkeeper/scratch-paint-0.2.0-prerelease.20190522213620
Update scratch-paint to the latest version 🚀
2019-05-22 17:40:57 -04:00
Karishma Chadha 9e329b999e Merge branch 'develop' into greenkeeper/scratch-paint-0.2.0-prerelease.20190522213620 2019-05-22 17:40:44 -04:00
Karishma Chadha e5e0c3dd28 Merge pull request #4843 from LLK/greenkeeper/scratch-render-0.1.0-prerelease.20190522213558
Update scratch-render to the latest version 🚀
2019-05-22 17:39:51 -04:00
greenkeeper[bot] e810422429 chore(package): update scratch-render to version 0.1.0-prerelease.20190522213558 2019-05-22 21:38:44 +00:00
greenkeeper[bot] 8b2c590309 chore(package): update scratch-paint to version 0.2.0-prerelease.20190522213620 2019-05-22 21:38:30 +00:00
Benjamin Wheeler d92ef0c807 Merge pull request #4833 from LLK/greenkeeper/scratch-storage-1.3.2
Update scratch-storage to the latest version 🚀
2019-05-22 14:42:44 -04:00
Benjamin Wheeler faa14b7add Merge branch 'develop' into greenkeeper/scratch-storage-1.3.2 2019-05-22 14:42:11 -04:00
Benjamin Wheeler abbc8ddad0 Merge pull request #4837 from LLK/greenkeeper/scratch-l10n-3.3.20190522144128
Update scratch-l10n to the latest version 🚀
2019-05-22 14:41:02 -04:00
Benjamin Wheeler 551e03b256 Merge pull request #4822 from LLK/greenkeeper/react-intl-2.9.0
Update react-intl to the latest version 🚀
2019-05-22 14:30:36 -04:00
Paul Kaplan f65f80160f Merge pull request #4834 from LLK/greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190521170426
Update scratch-svg-renderer to the latest version 🚀
2019-05-22 14:05:17 -04:00
Paul Kaplan 5438c14bdb Merge branch 'develop' into greenkeeper/scratch-svg-renderer-0.2.0-prerelease.20190521170426 2019-05-22 14:04:51 -04:00
Paul Kaplan 8f3d84ec60 Merge pull request #4830 from LLK/greenkeeper/scratch-vm-0.2.0-prerelease.20190520172914
Update scratch-vm to the latest version 🚀
2019-05-22 13:55:03 -04:00
greenkeeper[bot] c3df930bea chore(package): update scratch-l10n to version 3.3.20190522144128 2019-05-22 14:43:31 +00:00
greenkeeper[bot] 4513a45306 chore(package): update scratch-svg-renderer to version 0.2.0-prerelease.20190521170426 2019-05-21 17:06:21 +00:00
greenkeeper[bot] a94a83dc4f chore(package): update scratch-storage to version 1.3.2 2019-05-21 16:06:05 +00:00
Eric Rosenbaum e1e5c8352c Merge pull request #4806 from ericrosenbaum/bugfix/no-surprise-letters
Surprise sprite button does not give you letters
2019-05-21 11:55:00 -04:00
Paul Kaplan 98ceb00bb4 Fix lint 2019-05-21 11:01:48 -04:00
Paul Kaplan 77974839b0 Make surprise and library backdrop additions NOT select the stage 2019-05-21 10:46:05 -04:00
greenkeeper[bot] 706341011a chore(package): update scratch-vm to version 0.2.0-prerelease.20190520172914 2019-05-20 17:32:00 +00:00
greenkeeper[bot] fdea0a1620 chore(package): update react-intl to version 2.9.0 2019-05-15 18:01:27 +00:00
Eric Rosenbaum a11fcfe373 Fix typo in variable name 2019-05-09 15:12:13 -04:00
Eric Rosenbaum 52a3600819 Surprise sprites do not include letters and numbers 2019-05-09 15:06:26 -04:00
Michael "Z" Goddard bec6223e6e delay loading and rendering gui
- Create the scratch renderer earlier in the GUI container/HOC stack
- Delay loading and rendering GUI component until after project assets
  have been fetched
2019-05-08 16:58:07 -04:00
Michael "Z" Goddard d83ec5a0b5 replace blocks timeout with a promise
The timeout as a promise lets us update the toolbox during the same
render as the workspace so that the screen updates all together. This
also means the toolbox will update before the loading screen is
dismissed after loading, letting us avoid a second flash for the
toolbox to update.
2019-04-30 16:36:52 -04:00
Michael "Z" Goddard 866af2f766 render toolboxes once the workspace has rendered 2019-03-26 17:33:42 -04:00
Michael "Z" Goddard 656e8c2c77 render only new toolbox xmls 2019-03-21 14:57:22 -04:00
219 changed files with 20625 additions and 1907 deletions

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
last 3 versions
Safari >= 8
iOS >= 8
+207
View File
@@ -0,0 +1,207 @@
version: 2.1
aliases:
- &save_git_cache
save_cache:
paths:
- .git
key: v1-git-{{ .Revision }}
- &restore_git_cache
restore_cache:
keys:
- v1-git-{{ .Revision }}
- v1-git-
- &save_build_cache
save_cache:
paths:
- build
key: v1-build-{{ .Revision }}
- &restore_build_cache
restore_cache:
keys:
- v1-build-{{ .Revision }}
- &save_dist_cache
save_cache:
paths:
- dist
key: v1-dist-{{ .Revision }}
- &restore_dist_cache
restore_cache:
keys:
- v1-dist-{{ .Revision }}
- &save_npm_cache
save_cache:
paths:
- node_modules
key: v1-npm-{{ checksum "package-lock.json" }}
- &restore_npm_cache
restore_cache:
keys:
- v1-npm-{{ checksum "package-lock.json" }}
- v1-npm-
- &defaults
docker:
- image: circleci/node:8.16-browsers
working_directory: ~/repo
jobs:
setup:
<<: *defaults
steps:
- *restore_git_cache
- checkout
- *restore_npm_cache
- run: npm install
- *save_git_cache
- *save_npm_cache
lint:
<<: *defaults
steps:
- *restore_git_cache
- checkout
- *restore_npm_cache
- run:
name: Lint
command: npm run test:lint -- --quiet --output-file test-results/eslint/results.xml --format junit
- store_test_results:
path: test-results
unit:
<<: *defaults
environment:
JEST_JUNIT_OUTPUT_NAME: results.xml
steps:
- *restore_git_cache
- checkout
- *restore_npm_cache
- run:
name: Unit
environment:
JEST_JUNIT_OUTPUT_DIR: test-results/unit
command: npm run test:unit -- --reporters="default" --reporters="jest-junit" --coverage --coverageReporters=text --coverageReporters=lcov
- store_artifacts:
path: coverage
- store_test_results:
path: test-results
build:
<<: *defaults
environment:
NODE_ENV: production
NODE_OPTIONS: --max-old-space-size=4000
steps:
- *restore_git_cache
- checkout
- *restore_npm_cache
- run:
name: Build
command: npm run build
- *save_build_cache
- *save_dist_cache
store_build:
<<: *defaults
steps:
- *restore_build_cache
- store_artifacts:
path: build
store_dist:
<<: *defaults
steps:
- *restore_dist_cache
- store_artifacts:
path: dist
integration:
<<: *defaults
parallelism: 2
environment:
JEST_JUNIT_OUTPUT_NAME: results.txt
steps:
- *restore_git_cache
- checkout
- *restore_npm_cache
- *restore_build_cache
- run:
name: Integration
environment:
JEST_JUNIT_OUTPUT_DIR: test-results/integration
command: |
export TESTFILES=$(circleci tests glob "test/integration/*.test.js" | circleci tests split --split-by=timings)
$(npm bin)/jest ${TESTFILES} --reporters="default" --reporters="jest-junit"
- store_test_results:
path: test-results
deploy-npm:
<<: *defaults
environment:
NODE_OPTIONS: --max-old-space-size=4000
steps:
- *restore_git_cache
- *restore_dist_cache
- checkout
- run: |
echo export RELEASE_VERSION="0.1.0-prerelease.$(date +'%Y%m%d%H%M%S')" >> $BASH_ENV
echo export NPM_TAG=circlelatest >> $BASH_ENV
if [ "$CIRCLE_BRANCH" == "master" ]
then echo export NPM_TAG=circlestable >> $BASH_ENV
fi
if [[ "$CIRCLE_BRANCH" == hotfix/* ]] # double brackets are important for matching the wildcard
then echo export NPM_TAG=circlehotfix >> $BASH_ENV
fi
- run: npm version --no-git-tag-version $RELEASE_VERSION
- run: |
npm set //registry.npmjs.org/:_authToken=$NPM_TOKEN
npm publish --tag $NPM_TAG
- run: git tag $RELEASE_VERSION
- run: git push $CIRCLE_REPOSITORY_URL $RELEASE_VERSION
deploy-gh-pages:
<<: *defaults
steps:
- *restore_git_cache
- checkout
- *restore_npm_cache
- *restore_build_cache
- run: |
git config --global user.email $(git log --pretty=format:"%ae" -n1)
git config --global user.name $(git log --pretty=format:"%an" -n1)
- run: npm run deploy -- -e $CIRCLE_BRANCH
workflows:
version: 2
build-test-deploy:
jobs:
- setup
- lint:
requires:
- setup
- unit:
requires:
- setup
- build:
requires:
- setup
- integration:
requires:
- build
- store_build:
requires:
- build
- store_dist:
requires:
- build
# Disable deployment while running in parallel with Travis
# - deploy-npm:
# requires:
# - lint
# - unit
# - integration
# - build
# filters:
# branches:
# only:
# - master
# - develop
# - /^hotfix\/.*/
# - deploy-gh-pages:
# requires:
# - lint
# - unit
# - integration
# - build
-1
View File
@@ -4,7 +4,6 @@
# NPM
/node_modules
npm-*
/package-lock.json
# Testing
/.nyc_output
+3
View File
@@ -5,6 +5,9 @@
/node_modules
npm-*
# Double copies of all the static assets and tutorial gifs
/src
# Testing
/.nyc_output
/coverage
+11 -2
View File
@@ -1,25 +1,33 @@
language: node_js
sudo: required
dist: trusty
dist: xenial
addons:
chrome: stable
node_js:
- 8
env:
global:
- CHROMEDRIVER_VERSION=LATEST
- NODE_ENV=production
- SMOKE_URL=https://llk.github.io/scratch-gui/$TRAVIS_PULL_REQUEST_BRANCH
- NODE_OPTIONS=--max-old-space-size=7250
- NPM_TAG=latest
- RELEASE_VERSION="0.1.0-prerelease.$(date +'%Y%m%d%H%M%S')"
- SMOKE_URL=https://llk.github.io/scratch-gui/$TRAVIS_PULL_REQUEST_BRANCH
cache:
directories:
- node_modules
before_install:
# package-lock.json was introduced in npm@5
- '[[ $(node -v) =~ ^v9.*$ ]] || npm install -g npm@latest' # skipped when using node 9
- npm install -g greenkeeper-lockfile
install:
- npm --production=false install
- npm --production=false update
before_script: greenkeeper-lockfile-update
script:
- npm test
- if [ "$TRAVIS_EVENT_TYPE" == "pull_request" ] && [ "$TRAVIS_BRANCH" == "master" ]; then npm run test:smoke; fi
after_script: greenkeeper-lockfile-upload
before_deploy:
- >
if [ -z "$BEFORE_DEPLOY_RAN" ]; then
@@ -68,6 +76,7 @@ deploy:
on:
all_branches: true
condition: $TRAVIS_EVENT_TYPE != cron && ! $TRAVIS_BRANCH =~ ^greenkeeper/
tags: false # Don't push tags to gh-pages
skip_cleanup: true
script: npm run deploy -- -x -e $TRAVIS_BRANCH -r https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git
- provider: script
+14845
View File
File diff suppressed because it is too large. Load diff
+15 -13
View File
@@ -44,7 +44,7 @@
"babel-loader": "^8.0.4",
"base64-loader": "1.0.0",
"bowser": "1.9.4",
"chromedriver": "74.0.0",
"chromedriver": "77.0.0",
"classnames": "2.2.6",
"computed-style-to-inline-style": "3.0.0",
"copy-webpack-plugin": "^4.5.1",
@@ -55,7 +55,9 @@
"es6-object-assign": "1.1.0",
"eslint": "^5.0.1",
"eslint-config-scratch": "^5.0.0",
"eslint-plugin-import": "^2.8.0",
"eslint-import-resolver-webpack": "^0.11.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^22.14.1",
"eslint-plugin-react": "^7.12.4",
"file-loader": "2.0.0",
"get-float-time-domain-data": "0.1.0",
@@ -65,6 +67,7 @@
"immutable": "3.8.2",
"intl": "1.2.5",
"jest": "^21.0.0",
"jest-junit": "^7.0.0",
"js-base64": "2.4.9",
"keymirror": "0.1.1",
"lodash.bindall": "4.4.0",
@@ -90,8 +93,8 @@
"react-dom": "16.2.0",
"react-draggable": "3.0.5",
"react-ga": "2.5.3",
"react-intl": "2.8.0",
"react-modal": "3.6.1",
"react-intl": "2.9.0",
"react-modal": "3.9.1",
"react-popover": "0.5.10",
"react-redux": "5.0.7",
"react-responsive": "5.0.0",
@@ -100,19 +103,18 @@
"react-test-renderer": "16.2.0",
"react-tooltip": "3.8.0",
"react-virtualized": "9.20.1",
"react-visibility-sensor": "5.0.2",
"redux": "3.7.2",
"redux-mock-store": "^1.2.3",
"redux-throttle": "0.1.1",
"rimraf": "^2.6.1",
"scratch-audio": "0.1.0-prerelease.20190114210212",
"scratch-blocks": "0.1.0-prerelease.1557852594",
"scratch-l10n": "3.3.20190516144548",
"scratch-paint": "0.2.0-prerelease.20190516171147",
"scratch-render": "0.1.0-prerelease.20190419202157",
"scratch-storage": "1.3.1",
"scratch-svg-renderer": "0.2.0-prerelease.20190419183947",
"scratch-vm": "0.2.0-prerelease.20190515153227",
"scratch-audio": "0.1.0-prerelease.20190925183642",
"scratch-l10n": "3.6.20191008224547",
"scratch-blocks": "0.1.0-prerelease.1570730963",
"scratch-paint": "0.2.0-prerelease.20191010161144",
"scratch-render": "0.1.0-prerelease.20191021180653",
"scratch-storage": "1.3.2",
"scratch-svg-renderer": "0.2.0-prerelease.20190822202608",
"scratch-vm": "0.2.0-prerelease.20191017151616",
"selenium-webdriver": "3.6.0",
"startaudiocontext": "1.2.1",
"style-loader": "^0.23.0",
+6
View File
@@ -1,3 +1,4 @@
const path = require('path');
module.exports = {
root: true,
extends: ['scratch', 'scratch/es6', 'scratch/react', 'plugin:import/errors'],
@@ -20,6 +21,11 @@ module.exports = {
settings: {
react: {
version: '16.2' // Prevent 16.3 lifecycle method errors
},
'import/resolver': {
webpack: {
config: path.resolve(__dirname, '../webpack.config.js')
}
}
}
};
+1 -1
View File
@@ -24,7 +24,7 @@
display: flex;
flex-grow: 1;
flex-shrink: 1;
overflow-y: auto;
overflow: visible;
}
[dir="ltr"] .detail-area {
+9 -1
View File
@@ -50,8 +50,16 @@ $fade-out-distance: 100px;
overflow-y: scroll;
display: flex;
flex-direction: column;
}
.list-area:after {
/* Make sure there is room to scroll beyond the last tile */
padding-bottom: 70px;
content: '';
display: block;
height: 70px;
width: 100%;
flex-shrink: 0;
order: 99999999;
}
.list-item {
@@ -0,0 +1,53 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import SelectionHandle from './selection-handle.jsx';
import Playhead from './playhead.jsx';
const AudioSelector = props => (
<div
className={classNames(styles.absolute, styles.selector)}
ref={props.containerRef}
onMouseDown={props.onNewSelectionMouseDown}
onTouchStart={props.onNewSelectionMouseDown}
>
{props.trimStart === null ? null : (
<Box
className={classNames(styles.absolute)}
style={{
left: `${props.trimStart * 100}%`,
width: `${100 * (props.trimEnd - props.trimStart)}%`
}}
>
<Box className={classNames(styles.absolute, styles.selectionBackground)} />
<SelectionHandle
handleStyle={styles.leftHandle}
onMouseDown={props.onTrimStartMouseDown}
/>
<SelectionHandle
handleStyle={styles.rightHandle}
onMouseDown={props.onTrimEndMouseDown}
/>
</Box>
)}
{props.playhead ? (
<Playhead
playbackPosition={props.playhead}
/>
) : null}
</div>
);
AudioSelector.propTypes = {
containerRef: PropTypes.func,
onNewSelectionMouseDown: PropTypes.func.isRequired,
onTrimEndMouseDown: PropTypes.func.isRequired,
onTrimStartMouseDown: PropTypes.func.isRequired,
playhead: PropTypes.number,
trimEnd: PropTypes.number,
trimStart: PropTypes.number
};
export default AudioSelector;
+44 -20
View File
@@ -1,10 +1,11 @@
@import "../../css/colors.css";
$border-radius: 4px;
$trim-handle-width: 12px;
$trim-handle-height: 14px;
$trim-handle-width: 30px;
$trim-handle-height: 30px;
$trim-handle-border: 3px;
$stripe-size: 10px;
$hover-scale: 2;
$hover-scale: 1.25;
.absolute {
position: absolute;
@@ -17,6 +18,10 @@ $hover-scale: 2;
transform: translateZ(0);
}
.selector {
cursor: pointer;
}
.trim-background {
cursor: pointer;
touch-action: none;
@@ -35,6 +40,11 @@ $hover-scale: 2;
);
}
.selection-background {
background: $motion-primary;
opacity: 0.5;
}
.start-trim-background .trim-background-mask {
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
@@ -53,6 +63,10 @@ $hover-scale: 2;
border: 1px solid $red-tertiary;
}
.selector .trim-line {
border: 1px solid $motion-tertiary;
}
.playhead-container {
position: absolute;
top: 0;
@@ -68,31 +82,50 @@ $hover-scale: 2;
so that we can use transform: translateX() using percentages.
*/
width: 100%;
height: 100%;
border-left: 1px solid $motion-primary;
border-top: none;
border-bottom: none;
border-right: none;
}
.start-trim-line {
right: 0;
.right-handle {
transform: scaleX(-1);
}
.end-trim-line {
left: 0;
.selector .left-handle {
left: -1px
}
.selector .right-handle {
right: -1px
}
.trimmer .left-handle {
right: -1px
}
.trimmer .right-handle {
left: -1px
}
.trim-handle {
position: absolute;
left: calc(-$trim-handle-width / 2);
width: $trim-handle-width;
height: $trim-handle-height;
right: 0;
user-select: none;
}
.trimmer .trim-handle {
filter: hue-rotate(150deg);
}
.trim-handle img {
position: absolute;
width: $trim-handle-width;
height: $trim-handle-height;
left: calc($trim-handle-border * 1.5);
/* Make sure image dragging isn't triggered */
user-select: none;
@@ -103,22 +136,13 @@ $hover-scale: 2;
}
.top-trim-handle {
top: -$trim-handle-height;
top: calc(-$trim-handle-height + $trim-handle-border);
}
.bottom-trim-handle {
bottom: -$trim-handle-height;
bottom: calc(-$trim-handle-height + $trim-handle-border);
}
.top-trim-handle img {
transform: rotate(180deg);
}
/* Increase handle size when anywhere on draggable area is hovered */
.trim-background:hover img {
transform: scale($hover-scale);
}
.trim-background:hover .top-trim-handle img {
transform: rotate(180deg) scale($hover-scale);
transform: scaleY(-1);
}
+12 -28
View File
@@ -3,11 +3,12 @@ import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import handleIcon from './icon--handle.svg';
import SelectionHandle from './selection-handle.jsx';
import Playhead from './playhead.jsx';
const AudioTrimmer = props => (
<div
className={styles.absolute}
className={classNames(styles.absolute, styles.trimmer)}
ref={props.containerRef}
>
{props.trimStart === null ? null : (
@@ -20,28 +21,16 @@ const AudioTrimmer = props => (
onTouchStart={props.onTrimStartMouseDown}
>
<Box className={classNames(styles.absolute, styles.trimBackgroundMask)} />
<Box className={classNames(styles.trimLine, styles.startTrimLine)}>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle, styles.startTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle, styles.startTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
<SelectionHandle
handleStyle={styles.leftHandle}
/>
</Box>
)}
{props.playhead ? (
<div className={styles.playheadContainer}>
<div
className={classNames(styles.trimLine, styles.playhead)}
style={{
transform: `translateX(${100 * props.playhead}%)`
}}
/>
</div>
<Playhead
playbackPosition={props.playhead}
/>
) : null}
{props.trimEnd === null ? null : (
<Box
className={classNames(styles.absolute, styles.trimBackground, styles.endTrimBackground)}
@@ -53,14 +42,9 @@ const AudioTrimmer = props => (
onTouchStart={props.onTrimEndMouseDown}
>
<Box className={classNames(styles.absolute, styles.trimBackgroundMask)} />
<Box className={classNames(styles.trimLine, styles.endTrimLine)}>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle, styles.endTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle, styles.endTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
<SelectionHandle
handleStyle={styles.rightHandle}
/>
</Box>
)}
</div>
+12 -14
View File
@@ -1,18 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="8px" height="10px" viewBox="0 0 8 10" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>handle</title>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="34px" height="34px" viewBox="1 1 33 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
<title>Bottom Left Handle</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Desktop---1280x720" transform="translate(-461.000000, -329.000000)" stroke="#E64D00" fill="#FF661A">
<g id="Step-5---Confirming-Trim" transform="translate(0.000000, 42.000000)">
<g id="Sound-Sketches" transform="translate(427.000000, 167.000000)">
<g id="start-playhead" transform="translate(35.000000, 0.000000)">
<path d="M0,122.213854 C0,121.658366 0.45097518,121.208054 0.990777969,121.208054 L5.00922203,121.208054 C5.55641359,121.208054 6,121.659838 6,122.213854 L6,126.298963 L3,129.208054 L0,126.298963 L0,122.213854 Z" id="handle" transform="translate(3.000000, 125.208054) scale(1, -1) translate(-3.000000, -125.208054) "></path>
</g>
</g>
</g>
<defs>
<path d="M17,5 C23.627417,5 29,10.372583 29,17 L29,29 L17,29 C10.372583,29 5,23.627417 5,17 C5,10.372583 10.372583,5 17,5 Z" id="path-1"></path>
</defs>
<g id="Bottom-Left-Handle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Bottom-Left" transform="translate(17.000000, 17.000000) scale(1, -1) translate(-17.000000, -17.000000) ">
<use stroke="#4D97FF33" stroke-width="8" xlink:href="#path-1"></use>
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use stroke="#3D73CC" stroke-width="1" fill="#4D97FF" fill-rule="evenodd" xlink:href="#path-1"></use>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+21
View File
@@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import styles from './audio-trimmer.css';
const Playhead = props => (
<div className={styles.playheadContainer}>
<div
className={classNames(styles.playhead)}
style={{
transform: `translateX(${100 * props.playbackPosition}%)`
}}
/>
</div>
);
Playhead.propTypes = {
playbackPosition: PropTypes.number
};
export default Playhead;
@@ -0,0 +1,28 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import Box from '../box/box.jsx';
import styles from './audio-trimmer.css';
import handleIcon from './icon--handle.svg';
const SelectionHandle = props => (
<Box
className={classNames(styles.trimLine, props.handleStyle)}
onMouseDown={props.onMouseDown}
onTouchStart={props.onMouseDown}
>
<Box className={classNames(styles.trimHandle, styles.topTrimHandle)}>
<img src={handleIcon} />
</Box>
<Box className={classNames(styles.trimHandle, styles.bottomTrimHandle)}>
<img src={handleIcon} />
</Box>
</Box>
);
SelectionHandle.propTypes = {
handleStyle: PropTypes.string,
onMouseDown: PropTypes.func
};
export default SelectionHandle;
+6 -8
View File
@@ -162,6 +162,7 @@
}
.step-body {
width: 100%;
background: $ui-white;
display: flex;
flex-direction: column;
@@ -169,6 +170,9 @@
align-items: center;
position: relative;
text-align: center;
/* Min height prevents layout changing when images change */
min-height: 256px;
}
.step-video {
@@ -177,6 +181,8 @@
.step-image {
max-width: 450px;
max-height: 200px;
object-fit: contain;
background: #F9F9F9;
border: 1px solid #ddd;
border-radius: 0.5rem;
@@ -275,14 +281,6 @@
margin-right: 0.5rem;
}
.video-cover {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.steps-list {
display: flex;
flex-direction: row;
+85 -29
View File
@@ -85,32 +85,67 @@ const CardHeader = ({onCloseCards, onShrinkExpandCards, onShowAll, totalSteps, s
</div>
);
// Video step needs to know if the card is being dragged to cover the video
// so that the mouseup is not swallowed by the iframe.
const VideoStep = ({video, dragging}) => (
<div className={styles.stepVideo}>
{dragging ? (
<div className={styles.videoCover} />
) : null}
<iframe
allowFullScreen
allowTransparency="true"
frameBorder="0"
height="257"
scrolling="no"
src={`https://fast.wistia.net/embed/iframe/${video}?seo=false&videoFoam=true`}
title="📹"
width="466"
/>
<script
async
src="https://fast.wistia.net/assets/external/E-v1.js"
/>
</div>
);
class VideoStep extends React.Component {
componentDidMount () {
const script = document.createElement('script');
script.src = `https://fast.wistia.com/embed/medias/${this.props.video}.jsonp`;
script.async = true;
script.setAttribute('id', 'wistia-video-content');
document.body.appendChild(script);
const script2 = document.createElement('script');
script2.src = 'https://fast.wistia.com/assets/external/E-v1.js';
script2.async = true;
script2.setAttribute('id', 'wistia-video-api');
document.body.appendChild(script2);
}
// We use the Wistia API here to update or pause the video dynamically:
// https://wistia.com/support/developers/player-api
componentDidUpdate (prevProps) {
// Ensure the wistia API is loaded and available
if (!(window.Wistia && window.Wistia.api)) return;
// Get a handle on the currently loaded video
const video = window.Wistia.api(prevProps.video);
// Reset the video source if a new video has been chosen from the library
if (prevProps.video !== this.props.video) {
video.replaceWith(this.props.video);
}
// Pause the video if the modal is being shrunken
if (!this.props.expanded) {
video.pause();
}
}
componentWillUnmount () {
const script = document.getElementById('wistia-video-content');
script.parentNode.removeChild(script);
const script2 = document.getElementById('wistia-video-api');
script2.parentNode.removeChild(script2);
}
render () {
return (
<div className={styles.stepVideo}>
<div
className={`wistia_embed wistia_async_${this.props.video}`}
id="video-div"
style={{height: `257px`, width: `466px`}}
>
&nbsp;
</div>
</div>
);
}
}
VideoStep.propTypes = {
dragging: PropTypes.bool.isRequired,
expanded: PropTypes.bool.isRequired,
video: PropTypes.string.isRequired
};
@@ -123,6 +158,7 @@ const ImageStep = ({title, image}) => (
<img
className={styles.stepImage}
draggable={false}
key={image} /* Use src as key to prevent hanging around on slow connections */
src={image}
/>
</div>
@@ -256,6 +292,7 @@ const Cards = props => {
onShowAll,
onNextStep,
onPrevStep,
showVideos,
step,
expanded,
...posProps
@@ -298,6 +335,7 @@ const Cards = props => {
>
<Draggable
bounds="parent"
cancel="#video-div" // disable dragging on video div
position={{x: x, y: y}}
onDrag={onDrag}
onStart={onStartDrag}
@@ -323,10 +361,18 @@ const Cards = props => {
/>
) : (
steps[step].video ? (
<VideoStep
dragging={dragging}
video={translateVideo(steps[step].video, locale)}
/>
showVideos ? (
<VideoStep
dragging={dragging}
expanded={expanded}
video={translateVideo(steps[step].video, locale)}
/>
) : ( // Else show the deck image and title
<ImageStep
image={content[activeDeckId].img}
title={content[activeDeckId].name}
/>
)
) : (
<ImageStep
image={translateImage(steps[step].image, locale)}
@@ -376,9 +422,19 @@ Cards.propTypes = {
onShowAll: PropTypes.func,
onShrinkExpandCards: PropTypes.func.isRequired,
onStartDrag: PropTypes.func,
showVideos: PropTypes.bool,
step: PropTypes.number.isRequired,
x: PropTypes.number,
y: PropTypes.number
};
export default Cards;
Cards.defaultProps = {
showVideos: true
};
export {
Cards as default,
// Others exported for testability
ImageStep,
VideoStep
};
@@ -0,0 +1,36 @@
@import "../../css/colors.css";
@import "../../css/units.css";
/* wrapper to allow for touch slop if we decide to add it */
.delete-button {
display: flex;
align-items: center;
justify-content: center;
user-select: none;
cursor: pointer;
transition: all 0.15s ease-out;
}
.delete-button-visible {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden; /* Mask the icon animation */
width: 1.75rem;
height: 1.75rem;
box-shadow: 0px 0px 0px 2px $motion-transparent;
background-color: $motion-primary;
color: $ui-white;
border-radius: 50%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
user-select: none;
cursor: pointer;
transition: all 0.15s ease-out;
}
.delete-icon {
position: relative;
margin: 0.25rem;
user-select: none;
transform-origin: 50%;
}
@@ -0,0 +1,39 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import styles from './delete-button.css';
import deleteIcon from './icon--delete.svg';
const DeleteButton = props => (
<div
aria-label="Delete"
className={classNames(
styles.deleteButton,
props.className
)}
role="button"
tabIndex={props.tabIndex}
onClick={props.onClick}
>
<div className={styles.deleteButtonVisible}>
<img
className={styles.deleteIcon}
src={deleteIcon}
/>
</div>
</div>
);
DeleteButton.propTypes = {
className: PropTypes.string,
onClick: PropTypes.func.isRequired,
tabIndex: PropTypes.number
};
DeleteButton.defaultProps = {
tabIndex: 0
};
export default DeleteButton;
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.1 (78136) - https://sketchapp.com -->
<title>Sound/General/Delete</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M4.54751641,6.99994966 L15.4523042,6.99994966 C15.7284466,6.99994966 15.9523042,7.22380729 15.9523042,7.49994966 C15.9523042,7.51506367 15.9516189,7.5301699 15.9502504,7.54522183 L15.1651793,16.1801783 C15.0715275,17.2102489 14.207924,17.9989808 13.1736049,17.9990897 L6.82662224,17.9997575 C5.79213514,17.9998663 4.92828345,17.2110677 4.83462539,16.180829 L4.04956981,7.54521753 C4.02456905,7.27020922 4.22724022,7.02700381 4.50224854,7.00200306 C4.51729904,7.00063483 4.53240384,6.99994966 4.54751641,6.99994966 Z M7.33333333,4 L7.88603796,2.34188612 C7.95409498,2.13771505 8.14516441,2 8.36037961,2 L11.6396204,2 C11.8548356,2 12.045905,2.13771505 12.113962,2.34188612 L12.6666667,4 L16.5,4 C16.7761424,4 17,4.22385763 17,4.5 L17,5.5 C17,5.77614237 16.7761424,6 16.5,6 L3.5,6 C3.22385763,6 3,5.77614237 3,5.5 L3,4.5 C3,4.22385763 3.22385763,4 3.5,4 L7.33333333,4 Z M8.38742589,4 L11.6125741,4 L11.2792408,3 L8.72075922,3 L8.38742589,4 Z M10,11.7204812 L11.5952436,10.1252376 C11.7905057,9.92997548 12.1070882,9.92997548 12.3023504,10.1252376 L12.3747624,10.1976496 C12.5700245,10.3929118 12.5700245,10.7094943 12.3747624,10.9047564 L10.7795188,12.5 L12.3747624,14.0952436 C12.5700245,14.2905057 12.5700245,14.6070882 12.3747624,14.8023504 L12.3023504,14.8747624 C12.1070882,15.0700245 11.7905057,15.0700245 11.5952436,14.8747624 L10,13.2795188 L8.40475641,14.8747624 C8.20949427,15.0700245 7.89291178,15.0700245 7.69764963,14.8747624 L7.62523762,14.8023504 C7.42997548,14.6070882 7.42997548,14.2905057 7.62523762,14.0952436 L9.22048121,12.5 L7.62523762,10.9047564 C7.42997548,10.7094943 7.42997548,10.3929118 7.62523762,10.1976496 L7.69764963,10.1252376 C7.89291178,9.92997548 8.20949427,9.92997548 8.40475641,10.1252376 L10,11.7204812 Z" id="path-1"></path>
</defs>
<g id="Sound/General/Delete" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<g id="Trash-Can"></g>
<g id="White" mask="url(#mask-2)" fill="#FFFFFF">
<rect id="Color" x="0" y="0" width="20" height="20"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

+11 -4
View File
@@ -66,8 +66,10 @@ const GUIComponent = props => {
backpackVisible,
blocksTabVisible,
cardsVisible,
canChangeLanguage,
canCreateNew,
canEditTitle,
canManageFiles,
canRemix,
canSave,
canCreateCopy,
@@ -85,13 +87,13 @@ const GUIComponent = props => {
isRtl,
isShared,
loading,
logo,
renderLogin,
onClickAccountNav,
onCloseAccountNav,
onLogOut,
onOpenRegistration,
onToggleLoginOpen,
onUpdateProjectTitle,
onActivateCostumesTab,
onActivateSoundsTab,
onActivateTab,
@@ -202,15 +204,18 @@ const GUIComponent = props => {
authorId={authorId}
authorThumbnailUrl={authorThumbnailUrl}
authorUsername={authorUsername}
canChangeLanguage={canChangeLanguage}
canCreateCopy={canCreateCopy}
canCreateNew={canCreateNew}
canEditTitle={canEditTitle}
canManageFiles={canManageFiles}
canRemix={canRemix}
canSave={canSave}
canShare={canShare}
className={styles.menuBarPosition}
enableCommunity={enableCommunity}
isShared={isShared}
logo={logo}
renderLogin={renderLogin}
showComingSoon={showComingSoon}
onClickAccountNav={onClickAccountNav}
@@ -222,7 +227,6 @@ const GUIComponent = props => {
onSeeCommunity={onSeeCommunity}
onShare={onShare}
onToggleLoginOpen={onToggleLoginOpen}
onUpdateProjectTitle={onUpdateProjectTitle}
/>
<Box className={styles.bodyWrapper}>
<Box className={styles.flexWrapper}>
@@ -359,9 +363,11 @@ GUIComponent.propTypes = {
backpackVisible: PropTypes.bool,
basePath: PropTypes.string,
blocksTabVisible: PropTypes.bool,
canChangeLanguage: PropTypes.bool,
canCreateCopy: PropTypes.bool,
canCreateNew: PropTypes.bool,
canEditTitle: PropTypes.bool,
canManageFiles: PropTypes.bool,
canRemix: PropTypes.bool,
canSave: PropTypes.bool,
canShare: PropTypes.bool,
@@ -378,6 +384,7 @@ GUIComponent.propTypes = {
isRtl: PropTypes.bool,
isShared: PropTypes.bool,
loading: PropTypes.bool,
logo: PropTypes.string,
onActivateCostumesTab: PropTypes.func,
onActivateSoundsTab: PropTypes.func,
onActivateTab: PropTypes.func,
@@ -397,7 +404,6 @@ GUIComponent.propTypes = {
onTelemetryModalOptIn: PropTypes.func,
onTelemetryModalOptOut: PropTypes.func,
onToggleLoginOpen: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
renderLogin: PropTypes.func,
showComingSoon: PropTypes.bool,
soundsTabVisible: PropTypes.bool,
@@ -411,8 +417,10 @@ GUIComponent.defaultProps = {
backpackHost: null,
backpackVisible: false,
basePath: './',
canChangeLanguage: true,
canCreateNew: false,
canEditTitle: false,
canManageFiles: true,
canRemix: false,
canSave: false,
canCreateCopy: false,
@@ -422,7 +430,6 @@ GUIComponent.defaultProps = {
isCreating: false,
isShared: false,
loading: false,
onUpdateProjectTitle: () => {},
showComingSoon: false,
stageSizeMode: STAGE_SIZE_MODES.large
};
@@ -8,6 +8,7 @@
font-size: 0.75rem;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: $motion-primary;
border-radius: 0.5rem;
}
.container + .container {
@@ -16,4 +17,14 @@
.title {
margin-top: 0.5rem;
text-align: center;
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
.container:active {
background-color: $motion-light-transparent;
}
+8 -2
View File
@@ -5,14 +5,19 @@ import styles from './icon-button.css';
const IconButton = ({
img,
disabled,
className,
title,
onClick
}) => (
<div
className={classNames(styles.container, className)}
className={classNames(
styles.container,
className,
disabled ? styles.disabled : null
)}
role="button"
onClick={onClick}
onClick={disabled ? null : onClick}
>
<img
className={styles.icon}
@@ -27,6 +32,7 @@ const IconButton = ({
IconButton.propTypes = {
className: PropTypes.string,
disabled: PropTypes.bool,
img: PropTypes.string,
onClick: PropTypes.func.isRequired,
title: PropTypes.node.isRequired
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
<title>Sound/General/Library Tile Icon/RTL</title>
<desc>Created with Sketch.</desc>
<g id="Sound/General/Library-Tile-Icon/RTL" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M40.7941213,60.3333334 C39.5757531,60.3333334 38.3858512,59.7627412 37.7254274,58.7262523 C34.4973213,53.700852 34.4973213,47.4609802 37.7254274,42.43558 C38.7160634,40.8860816 40.8909074,40.373072 42.5989001,41.2839258 C44.2898131,42.2052491 44.8534507,44.2101744 43.8514282,45.7701424 C41.9441696,48.7382694 41.9441696,52.423563 43.8514282,55.3969245 C44.8534507,56.951658 44.2898131,58.956583 42.5989001,59.8779064 C42.0295692,60.1867593 41.4033053,60.3333334 40.7941213,60.3333334 Z M30.014623,64.6666666 C28.9761967,64.6666666 27.9668853,64.1309203 27.3991478,63.1577291 C22.8669507,55.401691 22.8669507,45.7582521 27.3991478,38.002214 C28.2531806,36.5473425 30.1068197,36.0705772 31.5528528,36.9208906 C32.9940332,37.7859493 33.4744267,39.6733502 32.6203939,41.1331368 C29.2188198,46.957538 29.2188198,54.202405 32.6203939,60.0317213 C33.4744267,61.491508 32.9940332,63.3739938 31.5528528,64.2390524 C31.0724594,64.5290438 30.5338363,64.6666666 30.014623,64.6666666 Z M48.8695653,34.1786293 C48.8695653,30.5538239 53.3068341,28.6536838 56.0736627,31.1003021 L62.1037975,36.4397458 C64.2189991,38.3098044 66.9754844,39.3476118 69.8405742,39.3476118 L70.8283578,39.3476118 C73.6831043,39.3476118 76,41.5936876 76,44.3611739 L76,54.689112 C76,57.4616116 73.6831043,59.7026738 70.8283578,59.7026738 L69.7836861,59.7026738 C66.9185963,59.7026738 64.1621111,60.7404813 62.0469094,62.6105398 L56.0736627,67.899848 C53.3068341,70.3464662 48.8695653,68.4463264 48.8695653,64.8165072 L48.8695653,34.1786293 Z" id="Shape" fill="#575E75" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
<title>Sound/General/Library Tile Icon/LTR</title>
<desc>Created with Sketch.</desc>
<g id="Sound/General/Library-Tile-Icon/LTR" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M59.2058787,60.3333334 C58.5966947,60.3333334 57.9704308,60.1867593 57.4010999,59.8779064 C55.7101869,58.956583 55.1465493,56.951658 56.1485718,55.3969245 C58.0558304,52.423563 58.0558304,48.7382694 56.1485718,45.7701424 C55.1465493,44.2101744 55.7101869,42.2052491 57.4010999,41.2839258 C59.1090926,40.373072 61.2839366,40.8860816 62.2745726,42.43558 C65.5026787,47.4609802 65.5026787,53.700852 62.2745726,58.7262523 C61.6141488,59.7627412 60.4242469,60.3333334 59.2058787,60.3333334 Z M69.985377,64.6666666 C69.4661637,64.6666666 68.9275406,64.5290438 68.4471472,64.2390524 C67.0059668,63.3739938 66.5255733,61.491508 67.3796061,60.0317213 C70.7811802,54.202405 70.7811802,46.957538 67.3796061,41.1331368 C66.5255733,39.6733502 67.0059668,37.7859493 68.4471472,36.9208906 C69.8931803,36.0705772 71.7468194,36.5473425 72.6008522,38.002214 C77.1330493,45.7582521 77.1330493,55.401691 72.6008522,63.1577291 C72.0331147,64.1309203 71.0238033,64.6666666 69.985377,64.6666666 Z M51.1304347,34.1786293 L51.1304347,64.8165072 C51.1304347,68.4463264 46.6931659,70.3464662 43.9263373,67.899848 L37.9530906,62.6105398 C35.8378889,60.7404813 33.0814037,59.7026738 30.2163139,59.7026738 L29.1716422,59.7026738 C26.3168957,59.7026738 24,57.4616116 24,54.689112 L24,44.3611739 C24,41.5936876 26.3168957,39.3476118 29.1716422,39.3476118 L30.1594258,39.3476118 C33.0245156,39.3476118 35.7810009,38.3098044 37.8962025,36.4397458 L43.9263373,31.1003021 C46.6931659,28.6536838 51.1304347,30.5538239 51.1304347,34.1786293 Z" id="Combined-Shape" fill="#575E75" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+4 -1
View File
@@ -7,6 +7,7 @@
align-items: center;
justify-content: flex-start;
flex-basis: 160px;
position: relative;
height: 160px;
max-width: 160px;
margin: $space;
@@ -138,7 +139,9 @@
.featured-description {
display: block;
font-weight: normal;
line-height: 2;
line-height: 1.375rem;
padding-top: .3125rem;
padding-bottom: .25rem;
}
.featured-extension-metadata {
+32 -17
View File
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Box from '../box/box.jsx';
import ScratchImage from '../scratch-image/scratch-image.jsx';
import PlayButton from '../../containers/play-button.jsx';
import styles from './library-item.css';
import classNames from 'classnames';
@@ -36,12 +36,10 @@ class LibraryItemComponent extends React.PureComponent {
/>
</div>
) : null}
{this.props.iconSource ? (
<ScratchImage
className={styles.featuredImage}
imageSource={this.props.iconSource}
/>
) : null}
<img
className={styles.featuredImage}
src={this.props.iconURL}
/>
</div>
{this.props.insetIconURL ? (
<div className={styles.libraryItemInsetImageContainer}>
@@ -109,8 +107,9 @@ class LibraryItemComponent extends React.PureComponent {
) : (
<Box
className={classNames(
styles.libraryItem,
this.props.hidden ? styles.hidden : null
styles.libraryItem, {
[styles.hidden]: this.props.hidden
}
)}
role="button"
tabIndex="0"
@@ -118,19 +117,30 @@ class LibraryItemComponent extends React.PureComponent {
onClick={this.props.onClick}
onFocus={this.props.onFocus}
onKeyPress={this.props.onKeyPress}
onMouseEnter={this.props.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
onMouseEnter={this.props.showPlayButton ? null : this.props.onMouseEnter}
onMouseLeave={this.props.showPlayButton ? null : this.props.onMouseLeave}
>
{/* Layers of wrapping is to prevent layout thrashing on animation */}
<Box className={styles.libraryItemImageContainerWrapper}>
<Box className={styles.libraryItemImageContainer}>
<ScratchImage
<Box
className={styles.libraryItemImageContainer}
onMouseEnter={this.props.showPlayButton ? this.props.onMouseEnter : null}
onMouseLeave={this.props.showPlayButton ? this.props.onMouseLeave : null}
>
<img
className={styles.libraryItemImage}
imageSource={this.props.iconSource}
src={this.props.iconURL}
/>
</Box>
</Box>
<span className={styles.libraryItemName}>{this.props.name}</span>
{this.props.showPlayButton ? (
<PlayButton
isPlaying={this.props.isPlaying}
onPlay={this.props.onPlay}
onStop={this.props.onStop}
/>
) : null}
</Box>
);
}
@@ -149,9 +159,10 @@ LibraryItemComponent.propTypes = {
extensionId: PropTypes.string,
featured: PropTypes.bool,
hidden: PropTypes.bool,
iconSource: ScratchImage.ImageSourcePropType,
iconURL: PropTypes.string,
insetIconURL: PropTypes.string,
internetConnectionRequired: PropTypes.bool,
isPlaying: PropTypes.bool,
name: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
@@ -161,11 +172,15 @@ LibraryItemComponent.propTypes = {
onFocus: PropTypes.func.isRequired,
onKeyPress: PropTypes.func.isRequired,
onMouseEnter: PropTypes.func.isRequired,
onMouseLeave: PropTypes.func.isRequired
onMouseLeave: PropTypes.func.isRequired,
onPlay: PropTypes.func.isRequired,
onStop: PropTypes.func.isRequired,
showPlayButton: PropTypes.bool
};
LibraryItemComponent.defaultProps = {
disabled: false
disabled: false,
showPlayButton: false
};
export default LibraryItemComponent;
+9
View File
@@ -12,6 +12,7 @@
overflow-y: auto;
height: auto;
padding: 0.5rem;
height: calc(100% - $library-header-height);
}
.library-scroll-grid.withFilterBar {
@@ -59,3 +60,11 @@
height: $library-filter-bar-height;
overflow: hidden;
}
.spinner-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
+79 -64
View File
@@ -9,7 +9,7 @@ import Modal from '../../containers/modal.jsx';
import Divider from '../divider/divider.jsx';
import Filter from '../filter/filter.jsx';
import TagButton from '../../containers/tag-button.jsx';
import storage from '../../lib/storage';
import Spinner from '../spinner/spinner.jsx';
import styles from './library.css';
@@ -29,47 +29,6 @@ const messages = defineMessages({
const ALL_TAG = {tag: 'all', intlLabel: messages.allTag};
const tagListPrefix = [ALL_TAG];
/**
* Find the AssetType which corresponds to a particular file extension. For example, 'png' => AssetType.ImageBitmap.
* @param {string} fileExtension - the file extension to look up.
* @returns {AssetType} - the AssetType corresponding to the extension, if any.
*/
const getAssetTypeForFileExtension = function (fileExtension) {
const compareOptions = {
sensitivity: 'accent',
usage: 'search'
};
for (const assetTypeId in storage.AssetType) {
const assetType = storage.AssetType[assetTypeId];
if (fileExtension.localeCompare(assetType.runtimeFormat, compareOptions) === 0) {
return assetType;
}
}
};
/**
* Figure out an `imageSource` (URI or asset ID & type) for a library item's icon.
* @param {object} item - either a library item or one of a library item's costumes.
* @returns {object} - an `imageSource` ready to be passed to a `ScratchImage`.
*/
const getItemImageSource = function (item) {
if (item.rawURL) {
return {
uri: item.rawURL
};
}
// TODO: adjust libraries to be more storage-friendly; don't use split() here.
const md5 = item.md5 || item.baseLayerMD5;
if (md5) {
const [assetId, fileExtension] = md5.split('.');
return {
assetId: assetId,
assetType: getAssetTypeForFileExtension(fileExtension)
};
}
};
class LibraryComponent extends React.Component {
constructor (props) {
super(props);
@@ -79,16 +38,25 @@ class LibraryComponent extends React.Component {
'handleFilterClear',
'handleMouseEnter',
'handleMouseLeave',
'handlePlayingEnd',
'handleSelect',
'handleTagClick',
'setFilteredDataRef'
]);
this.state = {
selectedItem: null,
playingItem: null,
filterQuery: '',
selectedTag: ALL_TAG.tag
selectedTag: ALL_TAG.tag,
loaded: false
};
}
componentDidMount () {
// Allow the spinner to display before loading the content
setTimeout(() => {
this.setState({loaded: true});
});
if (this.props.setStopHandler) this.props.setStopHandler(this.handlePlayingEnd);
}
componentDidUpdate (prevProps, prevState) {
if (prevState.filterQuery !== this.state.filterQuery ||
prevState.selectedTag !== this.state.selectedTag) {
@@ -103,22 +71,58 @@ class LibraryComponent extends React.Component {
this.props.onRequestClose();
}
handleTagClick (tag) {
this.setState({
filterQuery: '',
selectedTag: tag.toLowerCase()
});
if (this.state.playingItem === null) {
this.setState({
filterQuery: '',
selectedTag: tag.toLowerCase()
});
} else {
this.props.onItemMouseLeave(this.getFilteredData()[[this.state.playingItem]]);
this.setState({
filterQuery: '',
playingItem: null,
selectedTag: tag.toLowerCase()
});
}
}
handleMouseEnter (id) {
if (this.props.onItemMouseEnter) this.props.onItemMouseEnter(this.getFilteredData()[id]);
// don't restart if mouse over already playing item
if (this.props.onItemMouseEnter && this.state.playingItem !== id) {
this.props.onItemMouseEnter(this.getFilteredData()[id]);
this.setState({
playingItem: id
});
}
}
handleMouseLeave (id) {
if (this.props.onItemMouseLeave) this.props.onItemMouseLeave(this.getFilteredData()[id]);
if (this.props.onItemMouseLeave) {
this.props.onItemMouseLeave(this.getFilteredData()[id]);
this.setState({
playingItem: null
});
}
}
handlePlayingEnd () {
if (this.state.playingItem !== null) {
this.setState({
playingItem: null
});
}
}
handleFilterChange (event) {
this.setState({
filterQuery: event.target.value,
selectedTag: ALL_TAG.tag
});
if (this.state.playingItem === null) {
this.setState({
filterQuery: event.target.value,
selectedTag: ALL_TAG.tag
});
} else {
this.props.onItemMouseLeave(this.getFilteredData()[[this.state.playingItem]]);
this.setState({
filterQuery: event.target.value,
playingItem: null,
selectedTag: ALL_TAG.tag
});
}
}
handleFilterClear () {
this.setState({filterQuery: ''});
@@ -204,10 +208,8 @@ class LibraryComponent extends React.Component {
})}
ref={this.setFilteredDataRef}
>
{this.getFilteredData().map((dataItem, index) => {
const iconSource = getItemImageSource(dataItem);
const icons = dataItem.json && dataItem.json.costumes.map(getItemImageSource);
return (<LibraryItem
{this.state.loaded ? this.getFilteredData().map((dataItem, index) => (
<LibraryItem
bluetoothRequired={dataItem.bluetoothRequired}
collaborator={dataItem.collaborator}
description={dataItem.description}
@@ -215,18 +217,28 @@ class LibraryComponent extends React.Component {
extensionId={dataItem.extensionId}
featured={dataItem.featured}
hidden={dataItem.hidden}
iconSource={iconSource}
icons={icons}
iconMd5={dataItem.md5}
iconRawURL={dataItem.rawURL}
icons={dataItem.json && dataItem.json.costumes}
id={index}
insetIconURL={dataItem.insetIconURL}
internetConnectionRequired={dataItem.internetConnectionRequired}
key={`item_${index}`}
isPlaying={this.state.playingItem === index}
key={typeof dataItem.name === 'string' ? dataItem.name : dataItem.rawURL}
name={dataItem.name}
showPlayButton={this.props.showPlayButton}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onSelect={this.handleSelect}
/>);
})}
/>
)) : (
<div className={styles.spinnerWrapper}>
<Spinner
large
level="primary"
/>
</div>
)}
</div>
</Modal>
);
@@ -255,12 +267,15 @@ LibraryComponent.propTypes = {
onItemMouseLeave: PropTypes.func,
onItemSelected: PropTypes.func,
onRequestClose: PropTypes.func,
setStopHandler: PropTypes.func,
showPlayButton: PropTypes.bool,
tags: PropTypes.arrayOf(PropTypes.shape(TagButton.propTypes)),
title: PropTypes.string.isRequired
};
LibraryComponent.defaultProps = {
filterable: true
filterable: true,
showPlayButton: false
};
export default injectIntl(LibraryComponent);
+2 -1
View File
@@ -91,7 +91,8 @@ LoupeComponent.propTypes = {
color: PropTypes.shape({
r: PropTypes.number,
g: PropTypes.number,
b: PropTypes.number
b: PropTypes.number,
a: PropTypes.number
}),
data: PropTypes.instanceOf(Uint8Array),
width: PropTypes.number,
+80 -75
View File
@@ -154,7 +154,7 @@ class MenuBar extends React.Component {
'handleKeyPress',
'handleLanguageMouseUp',
'handleRestoreOption',
'handleSaveToComputer',
'getSaveToComputerHandler',
'restoreOptionMessage'
]);
}
@@ -225,7 +225,7 @@ class MenuBar extends React.Component {
event.preventDefault();
}
}
handleSaveToComputer (downloadProjectCallback) {
getSaveToComputerHandler (downloadProjectCallback) {
return () => {
this.props.onRequestCloseFile();
downloadProjectCallback();
@@ -327,11 +327,11 @@ class MenuBar extends React.Component {
[styles.clickable]: typeof this.props.onClickLogo !== 'undefined'
})}
draggable={false}
src={scratchLogo}
src={this.props.logo}
onClick={this.props.onClickLogo}
/>
</div>
<div
{(this.props.canChangeLanguage) && (<div
className={classNames(styles.menuBarItem, styles.hoverable, styles.languageMenu)}
>
<div>
@@ -345,82 +345,85 @@ class MenuBar extends React.Component {
/>
</div>
<LanguageSelector label={this.props.intl.formatMessage(ariaMessages.language)} />
</div>
<div
className={classNames(styles.menuBarItem, styles.hoverable, {
[styles.active]: this.props.fileMenuOpen
})}
onMouseUp={this.props.onClickFile}
>
<FormattedMessage
defaultMessage="File"
description="Text for file dropdown menu"
id="gui.menuBar.file"
/>
<MenuBarMenu
className={classNames(styles.menuBarMenu)}
open={this.props.fileMenuOpen}
place={this.props.isRtl ? 'left' : 'right'}
onRequestClose={this.props.onRequestCloseFile}
</div>)}
{(this.props.canManageFiles) && (
<div
className={classNames(styles.menuBarItem, styles.hoverable, {
[styles.active]: this.props.fileMenuOpen
})}
onMouseUp={this.props.onClickFile}
>
<MenuSection>
<MenuItem
isRtl={this.props.isRtl}
onClick={this.handleClickNew}
>
{newProjectMessage}
</MenuItem>
</MenuSection>
{(this.props.canSave || this.props.canCreateCopy || this.props.canRemix) && (
<FormattedMessage
defaultMessage="File"
description="Text for file dropdown menu"
id="gui.menuBar.file"
/>
<MenuBarMenu
className={classNames(styles.menuBarMenu)}
open={this.props.fileMenuOpen}
place={this.props.isRtl ? 'left' : 'right'}
onRequestClose={this.props.onRequestCloseFile}
>
<MenuSection>
{this.props.canSave ? (
<MenuItem onClick={this.handleClickSave}>
{saveNowMessage}
</MenuItem>
) : []}
{this.props.canCreateCopy ? (
<MenuItem onClick={this.handleClickSaveAsCopy}>
{createCopyMessage}
</MenuItem>
) : []}
{this.props.canRemix ? (
<MenuItem onClick={this.handleClickRemix}>
{remixMessage}
</MenuItem>
) : []}
<MenuItem
isRtl={this.props.isRtl}
onClick={this.handleClickNew}
>
{newProjectMessage}
</MenuItem>
</MenuSection>
)}
<MenuSection>
<SBFileUploader
canSave={this.props.canSave}
userOwnsProject={this.props.userOwnsProject}
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
>
{(className, renderFileInput, loadProject) => (
{(this.props.canSave || this.props.canCreateCopy || this.props.canRemix) && (
<MenuSection>
{this.props.canSave && (
<MenuItem onClick={this.handleClickSave}>
{saveNowMessage}
</MenuItem>
)}
{this.props.canCreateCopy && (
<MenuItem onClick={this.handleClickSaveAsCopy}>
{createCopyMessage}
</MenuItem>
)}
{this.props.canRemix && (
<MenuItem onClick={this.handleClickRemix}>
{remixMessage}
</MenuItem>
)}
</MenuSection>
)}
<MenuSection>
<SBFileUploader
canSave={this.props.canSave}
userOwnsProject={this.props.userOwnsProject}
>
{(className, renderFileInput, handleLoadProject) => (
<MenuItem
className={className}
onClick={handleLoadProject}
>
{/* eslint-disable max-len */}
{this.props.intl.formatMessage(sharedMessages.loadFromComputerTitle)}
{/* eslint-enable max-len */}
{renderFileInput()}
</MenuItem>
)}
</SBFileUploader>
<SB3Downloader>{(className, downloadProjectCallback) => (
<MenuItem
className={className}
onClick={loadProject}
onClick={this.getSaveToComputerHandler(downloadProjectCallback)}
>
{this.props.intl.formatMessage(sharedMessages.loadFromComputerTitle)}
{renderFileInput()}
<FormattedMessage
defaultMessage="Save to your computer"
description="Menu bar item for downloading a project to your computer" // eslint-disable-line max-len
id="gui.menuBar.downloadToComputer"
/>
</MenuItem>
)}
</SBFileUploader>
<SB3Downloader>{(className, downloadProjectCallback) => (
<MenuItem
className={className}
onClick={this.handleSaveToComputer(downloadProjectCallback)}
>
<FormattedMessage
defaultMessage="Save to your computer"
description="Menu bar item for downloading a project to your computer"
id="gui.menuBar.downloadToComputer"
/>
</MenuItem>
)}</SB3Downloader>
</MenuSection>
</MenuBarMenu>
</div>
)}</SB3Downloader>
</MenuSection>
</MenuBarMenu>
</div>
)}
<div
className={classNames(styles.menuBarItem, styles.hoverable, {
[styles.active]: this.props.editMenuOpen
@@ -491,7 +494,6 @@ class MenuBar extends React.Component {
>
<ProjectTitleInput
className={classNames(styles.titleFieldGrowable)}
onUpdateProjectTitle={this.props.onUpdateProjectTitle}
/>
</MenuBarItemTooltip>
</div>
@@ -698,9 +700,11 @@ MenuBar.propTypes = {
authorThumbnailUrl: PropTypes.string,
authorUsername: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
autoUpdateProject: PropTypes.func,
canChangeLanguage: PropTypes.bool,
canCreateCopy: PropTypes.bool,
canCreateNew: PropTypes.bool,
canEditTitle: PropTypes.bool,
canManageFiles: PropTypes.bool,
canRemix: PropTypes.bool,
canSave: PropTypes.bool,
canShare: PropTypes.bool,
@@ -717,6 +721,7 @@ MenuBar.propTypes = {
languageMenuOpen: PropTypes.bool,
locale: PropTypes.string.isRequired,
loginMenuOpen: PropTypes.bool,
logo: PropTypes.string,
onClickAccount: PropTypes.func,
onClickEdit: PropTypes.func,
onClickFile: PropTypes.func,
@@ -739,7 +744,6 @@ MenuBar.propTypes = {
onSeeCommunity: PropTypes.func,
onShare: PropTypes.func,
onToggleLoginOpen: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
projectTitle: PropTypes.string,
renderLogin: PropTypes.func,
sessionExists: PropTypes.bool,
@@ -751,6 +755,7 @@ MenuBar.propTypes = {
};
MenuBar.defaultProps = {
logo: scratchLogo,
onShare: () => {}
};
+21 -31
View File
@@ -1,9 +1,9 @@
import classNames from 'classnames';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import React from 'react';
import {defineMessages, intlShape, injectIntl} from 'react-intl';
import {setProjectTitle} from '../../reducers/project-title';
import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
import Input from '../forms/input.jsx';
@@ -19,39 +19,27 @@ const messages = defineMessages({
}
});
class ProjectTitleInput extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleUpdateProjectTitle'
]);
}
// call onUpdateProjectTitle if it is defined (only defined when gui
// is used within scratch-www)
handleUpdateProjectTitle (newTitle) {
if (this.props.onUpdateProjectTitle) {
this.props.onUpdateProjectTitle(newTitle);
}
}
render () {
return (
<BufferedInput
className={classNames(styles.titleField, this.props.className)}
maxLength="100"
placeholder={this.props.intl.formatMessage(messages.projectTitlePlaceholder)}
tabIndex="0"
type="text"
value={this.props.projectTitle}
onSubmit={this.handleUpdateProjectTitle}
/>
);
}
}
const ProjectTitleInput = ({
className,
intl,
onSubmit,
projectTitle
}) => (
<BufferedInput
className={classNames(styles.titleField, className)}
maxLength="100"
placeholder={intl.formatMessage(messages.projectTitlePlaceholder)}
tabIndex="0"
type="text"
value={projectTitle}
onSubmit={onSubmit}
/>
);
ProjectTitleInput.propTypes = {
className: PropTypes.string,
intl: intlShape.isRequired,
onUpdateProjectTitle: PropTypes.func,
onSubmit: PropTypes.func,
projectTitle: PropTypes.string
};
@@ -59,7 +47,9 @@ const mapStateToProps = state => ({
projectTitle: state.scratchGui.projectTitle
});
const mapDispatchToProps = () => ({});
const mapDispatchToProps = dispatch => ({
onSubmit: title => dispatch(setProjectTitle(title))
});
export default injectIntl(connect(
mapStateToProps,
+1 -1
View File
@@ -58,7 +58,7 @@ MenuItem.propTypes = {
const addDividerClassToFirstChild = (child, id) => (
React.cloneElement(child, {
child && React.cloneElement(child, {
className: classNames(
child.className,
{[styles.menuSection]: id === 0}
+3 -1
View File
@@ -43,6 +43,7 @@
margin: 0 5px;
border-radius: calc($space / 2);
padding: 0 2px;
white-space: pre-wrap;
transform: translateZ(0); /* Fixes flickering in Safari */
}
@@ -53,6 +54,7 @@
text-align: center;
color: white;
font-size: 1rem;
white-space: pre-wrap;
transform: translateZ(0); /* Fixes flickering in Safari */
}
@@ -149,7 +151,7 @@
overflow: hidden; /* Don't let long values escape container */
text-overflow: ellipsis;
user-select: text; /* Allow selecting list values for 2.0 compatibility, only relevant in player */
white-space: nowrap;
white-space: pre;
}
.list-input {
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="49px" height="52px" viewBox="-5 0 49 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>play</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M17.2214651,8.34499443 C19.7766419,4.08636653 23.9234057,4.09303946 26.4745787,8.34499443 L40.1520974,31.140859 C42.7072741,35.3994869 40.7489672,38.8517869 35.7726637,38.8517869 L7.92338006,38.8517869 C2.9495089,38.8517869 0.992773429,35.392814 3.54394641,31.140859 L17.2214651,8.34499443 Z" id="path-1"></path>
<filter x="-16.7%" y="-19.3%" width="133.5%" height="138.6%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="2.5" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
</filter>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-623.000000, -347.000000)">
<g id="Step-3---Altering-Suggested-Trim" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
<g id="play" transform="translate(21.849131, 22.002656) rotate(90.000000) translate(-21.849131, -22.002656) ">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use stroke="#FFFFFF" stroke-width="1" fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="52px" height="52px" viewBox="0 0 52 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>stop-playback</title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="0" y="0" width="44" height="44" rx="4"></rect>
<filter x="-13.6%" y="-13.6%" width="127.3%" height="127.3%" filterUnits="objectBoundingBox" id="filter-2">
<feMorphology radius="2" operator="dilate" in="SourceAlpha" result="shadowSpreadOuter1"></feMorphology>
<feOffset dx="0" dy="0" in="shadowSpreadOuter1" result="shadowOffsetOuter1"></feOffset>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"></feComposite>
<feColorMatrix values="0 0 0 0 0.298039216 0 0 0 0 0.592156863 0 0 0 0 1 0 0 0 0.1 0" type="matrix" in="shadowOffsetOuter1"></feColorMatrix>
</filter>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-619.000000, -345.000000)">
<g id="Step-4---Playing-Trim" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
<g id="stop-playback">
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
<rect stroke="#FFFFFF" stroke-width="1" x="0.5" y="0.5" width="43" height="43" rx="4"></rect>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,42 @@
@import "../../css/colors.css";
@import "../../css/units.css";
.play-button {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden; /* Mask the icon animation */
width: 2.5rem;
height: 2.5rem;
background-color: $sound-primary;
color: $ui-white;
border-radius: 50%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
user-select: none;
cursor: pointer;
transition: all 0.15s ease-out;
}
.play-button {
position: absolute;
top: .5rem;
z-index: auto;
}
.play-button:focus {
outline: none;
}
.play-icon {
width: 50%;
}
[dir="ltr"] .play-button {
right: .5rem;
}
[dir="rtl"] .play-button {
left: .5rem;
}
@@ -0,0 +1,73 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import styles from './play-button.css';
import playIcon from './icon--play.svg';
import stopIcon from './icon--stop.svg';
const messages = defineMessages({
play: {
id: 'gui.playButton.play',
description: 'Title of the button to start playing the sound',
defaultMessage: 'Play'
},
stop: {
id: 'gui.playButton.stop',
description: 'Title of the button to stop the sound',
defaultMessage: 'Stop'
}
});
const PlayButtonComponent = ({
className,
intl,
isPlaying,
onClick,
onMouseDown,
onMouseEnter,
onMouseLeave,
setButtonRef,
...props
}) => {
const label = isPlaying ?
intl.formatMessage(messages.stop) :
intl.formatMessage(messages.play);
return (
<div
aria-label={label}
className={classNames(styles.playButton, className, {
[styles.playing]: isPlaying
})}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
ref={setButtonRef}
{...props}
>
<img
className={styles.playIcon}
draggable={false}
src={isPlaying ? stopIcon : playIcon}
/>
</div>
);
};
PlayButtonComponent.propTypes = {
className: PropTypes.string,
intl: intlShape,
isPlaying: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
onMouseDown: PropTypes.func.isRequired,
onMouseEnter: PropTypes.func.isRequired,
onMouseLeave: PropTypes.func.isRequired,
setButtonRef: PropTypes.func.isRequired
};
export default injectIntl(PlayButtonComponent);
+3 -1
View File
@@ -20,8 +20,10 @@
border: 1px solid $ui-black-transparent;
border-radius: 5px;
padding: 3px;
/* Force these to be on their own render layer because they update often */
transform: translateZ(0);
}
.meter-container {
margin-right: 5px;
height: 180px;
@@ -1,139 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import VisibilitySensor from 'react-visibility-sensor';
import storage from '../../lib/storage';
class ScratchImage extends React.PureComponent {
static init () {
this._maxParallelism = 6;
this._currentJobs = 0;
this._pendingImages = new Set();
}
static loadPendingImages () {
if (this._currentJobs >= this._maxParallelism) {
// already busy
return;
}
// Find the first visible image. If there aren't any, find the first non-visible image.
let nextImage;
for (const image of this._pendingImages) {
if (image.isVisible) {
nextImage = image;
break;
} else {
nextImage = nextImage || image;
}
}
// If we found an image to load:
// 1) Remove it from the queue
// 2) Load the image
// 3) Pump the queue again
if (nextImage) {
this._pendingImages.delete(nextImage);
const imageSource = nextImage.props.imageSource;
++this._currentJobs;
storage
.load(imageSource.assetType, imageSource.assetId)
.then(asset => {
if (!nextImage.wasUnmounted) {
const dataURI = asset.encodeDataURI();
nextImage.setState({
imageURI: dataURI
});
}
--this._currentJobs;
this.loadPendingImages();
});
}
}
constructor (props) {
super(props);
this.state = {};
Object.assign(this.state, this._loadImageSource(props.imageSource));
}
componentWillReceiveProps (nextProps) {
const newState = this._loadImageSource(nextProps.imageSource);
this.setState(newState);
}
componentWillUnmount () {
this.wasUnmounted = true;
ScratchImage._pendingImages.delete(this);
}
/**
* Calculate the state changes necessary to load the image specified in the provided source info. If the component
* is mounted, call setState() with the return value of this function. If the component has not yet mounted, use
* the return value of this function as initial state for the component.
*
* @param {object} imageSource - the new source for the image, including either assetId or URI
* @returns {object} - the new state values, if any.
*/
_loadImageSource (imageSource) {
if (imageSource) {
if (imageSource.uri) {
ScratchImage._pendingImages.delete(this);
return {
imageURI: imageSource.uri,
lastRequestedAsset: null
};
}
if (this.state.lastRequestedAsset !== imageSource.assetId) {
ScratchImage._pendingImages.add(this);
return {
lastRequestedAsset: imageSource.assetId
};
}
}
// Nothing to do - don't change any state.
return {};
}
render () {
const {
src: _src,
imageSource: _imageSource,
...imgProps
} = this.props;
return (
<VisibilitySensor
intervalCheck
scrollCheck
>
{
({isVisible}) => {
this.isVisible = isVisible;
ScratchImage.loadPendingImages();
return (
<img
src={this.state.imageURI}
{...imgProps}
/>
);
}
}
</VisibilitySensor>
);
}
}
ScratchImage.ImageSourcePropType = PropTypes.oneOfType([
PropTypes.shape({
assetId: PropTypes.string.isRequired,
assetType: PropTypes.oneOf(Object.values(storage.AssetType)).isRequired
}),
PropTypes.shape({
uri: PropTypes.string.isRequired
})
]);
ScratchImage.propTypes = {
imageSource: ScratchImage.ImageSourcePropType.isRequired
};
ScratchImage.init();
export default ScratchImage;
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.1 (81669) - https://sketch.com -->
<title>Sound/General/Copy to New</title>
<desc>Created with Sketch.</desc>
<g id="Sound/General/Copy-to-New" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M15,11.0967026 L15,4 C15,3.44771525 14.5522847,3 14,3 L4,3 C3.44771525,3 3,3.44771525 3,4 L3,12 L12.6616037,12 L11.5236347,13.0430331 C11.1882084,13.3580017 11,13.7827132 11,14.2302292 C11,14.6767472 11.187801,15.1005394 11.5128606,15.4074032 L12.1595777,16 L4,16 C2.8954305,16 2,15.1045695 2,14 L2,4 C2,2.8954305 2.8954305,2 4,2 L14,2 C15.1045695,2 16,2.8954305 16,4 L16,12.0333493 C15.8307357,11.617882 15.4895036,11.283542 15.0505738,11.115203 C15.0337809,11.1087625 15.0169209,11.1025961 15,11.0967026 Z M10.3153901,9.43844107 C10.240142,9.43844107 10.1627842,9.41684419 10.0924589,9.37133647 C9.88359282,9.23558464 9.81397079,8.94017013 9.93774329,8.71108891 C10.173333,8.27298072 10.173333,7.72997337 9.93774329,7.2926365 C9.81397079,7.06278396 9.88359282,6.76736945 10.0924589,6.63161762 C10.3034348,6.49740842 10.5720773,6.57299751 10.6944433,6.80130741 C11.0931877,7.54177197 11.0931877,8.46118212 10.6944433,9.20164668 C10.612866,9.35436749 10.4658862,9.43844107 10.3153901,9.43844107 Z M11.6283181,10.2119951 C11.5530701,10.2119951 11.475009,10.1903983 11.405387,10.1448905 C11.1965209,10.0091387 11.1268989,9.7137242 11.2506714,9.48464298 C11.7436516,8.56986072 11.7436516,7.4329391 11.2506714,6.51892817 C11.1268989,6.28984695 11.1965209,5.99366112 11.405387,5.85790929 C11.6149563,5.7244714 11.8835989,5.79928918 12.0073714,6.02759908 C12.6642095,7.2447377 12.6642095,8.75806213 12.0073714,9.97520074 C11.9250908,10.1279216 11.7788142,10.2119951 11.6283181,10.2119951 Z M9.1892643,5.64286604 L9.1892643,10.3563857 C9.1892643,10.9148194 8.58587338,11.2071486 8.20963312,10.8307458 L7.39737611,10.0170061 C7.10974571,9.72930481 6.73491195,9.56964214 6.34530989,9.56964214 L6.20325282,9.56964214 C5.81505726,9.56964214 5.5,9.22486333 5.5,8.79832489 L5.5,7.20941137 C5.5,6.78364425 5.81505726,6.43809412 6.20325282,6.43809412 L6.33757411,6.43809412 C6.72717617,6.43809412 7.10200993,6.27843145 7.38964033,5.99073012 L8.20963312,5.16927725 C8.58587338,4.79287444 9.1892643,5.08520367 9.1892643,5.64286604 Z M13.937674,12.1868928 C14.1369962,12.0038895 14.4343459,11.9498886 14.6924846,12.0488903 C14.9506233,12.1478921 15.1172698,12.3758962 15.1172698,12.6339008 L15.1172698,13.2759122 C15.2675784,13.3119129 15.44076,13.3599137 15.6237444,13.428915 C15.9439671,13.545917 16.2968655,13.7259203 16.6236234,13.9686246 C16.7053128,14.0319257 16.7837347,14.0949268 16.8621566,14.1639281 C16.9307757,14.2239291 17.0418734,14.325931 17.0908871,14.385932 C17.2215902,14.5209344 17.3555609,14.6949375 17.4699262,14.8569404 C17.6921215,15.1989465 17.8456977,15.5679531 17.9273871,15.9186594 C18.005809,16.2699656 18.0123442,16.6029716 17.9862036,16.8789765 C17.9567954,17.1579815 17.8979789,17.3709853 17.8424301,17.5389883 C17.8162895,17.6229898 17.7934164,17.6589904 17.7803461,17.6979911 L17.7574731,17.7519921 C17.6921215,17.9109949 17.5156723,18.0159968 17.3196176,17.9979965 C17.0876195,17.9769961 16.9177054,17.7849927 16.9405785,17.5719889 L16.9471136,17.5149878 C16.9503812,17.4819873 16.9634515,17.4159861 16.960184,17.3649852 C16.960184,17.2629833 16.9569164,17.0889802 16.920973,16.8999769 C16.8817621,16.7079734 16.8098753,16.4919696 16.6922425,16.2849659 C16.5746097,16.0809623 16.4144984,15.8889588 16.2282464,15.730256 C16.1269515,15.6519546 16.0387269,15.5859534 15.921094,15.5139521 C15.8492073,15.4686513 15.8328694,15.4719514 15.774053,15.4389508 C15.7217718,15.4149504 15.6694905,15.39365 15.6172092,15.3729496 C15.4374924,15.3069485 15.2675784,15.2679478 15.1172698,15.2529475 L15.1172698,15.8259577 C15.1172698,16.0839623 14.9506233,16.3119664 14.6924846,16.4109681 C14.4343459,16.5099699 14.1369962,16.4559689 13.937674,16.2729657 L12.1993223,14.6802373 C12.0718867,14.5599351 12,14.3979322 12,14.2302292 C12,14.0619262 12.0718867,13.8999234 12.1993223,13.7802212 L13.937674,12.1868928 Z" id="Copy-to-New" fill="#4D97FF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.1 (81669) - https://sketch.com -->
<title>Sound/General/Copy</title>
<desc>Created with Sketch.</desc>
<g id="Sound/General/Copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Secondary-Color" opacity="0.25" transform="translate(2.000000, 4.000000)" fill="#4D97FF">
<rect id="selection" x="0" y="0" width="16" height="12"></rect>
</g>
<g id="Primary-Color" transform="translate(0.000000, 2.000000)" fill="#4D97FF" fill-rule="nonzero">
<path d="M1.5,3.93699126 C0.637386394,3.71496986 0,2.93191971 0,2 C0,0.8954305 0.8954305,0 2,0 C3.1045695,0 4,0.8954305 4,2 C4,2.93191971 3.36261361,3.71496986 2.5,3.93699126 L2.5,12.0630087 C3.36261361,12.2850301 4,13.0680803 4,14 C4,15.1045695 3.1045695,16 2,16 C0.8954305,16 0,15.1045695 0,14 C0,13.0680803 0.637386394,12.2850301 1.5,12.0630087 L1.5,3.93699126 Z M17.5,12.0630087 L17.5,3.93699126 C16.6373864,3.71496986 16,2.93191971 16,2 C16,0.8954305 16.8954305,0 18,0 C19.1045695,0 20,0.8954305 20,2 C20,2.93191971 19.3626136,3.71496986 18.5,3.93699126 L18.5,12.0630087 C19.3626136,12.2850301 20,13.0680803 20,14 C20,15.1045695 19.1045695,16 18,16 C16.8954305,16 16,15.1045695 16,14 C16,13.0680803 16.6373864,12.2850301 17.5,12.0630087 Z M11,7 L12,7 C12.5522847,7 13,7.44771525 13,8 C13,8.55228475 12.5522847,9 12,9 L11,9 L11,10 C11,10.5522847 10.5522847,11 10,11 C9.44771525,11 9,10.5522847 9,10 L9,9 L8,9 C7.44771525,9 7,8.55228475 7,8 C7,7.44771525 7.44771525,7 8,7 L9,7 L9,6 C9,5.44771525 9.44771525,5 10,5 C10.5522847,5 11,5.44771525 11,6 L11,7 Z" id="handles-and-plus"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
<title>Sound/General/Trim</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M6.54210671,14.8773591 C6.54210671,15.6756813 5.89668548,16.3230878 5.11194905,16.3230878 C4.31632862,16.3230878 3.6709074,15.6756813 3.6709074,14.8773591 C3.6709074,14.086692 4.31632862,13.440379 5.11194905,13.440379 C5.89668548,13.440379 6.54210671,14.086692 6.54210671,14.8773591 L6.54210671,14.8773591 Z M8.63401159,13.5836396 C8.82992359,13.3878866 8.82992359,13.0685577 8.63401159,12.8706175 L7.10916313,11.338495 C6.91216272,11.1405548 6.59434991,11.1405548 6.3984379,11.338495 L5.87709428,11.861232 C5.63546947,11.7912421 5.37425346,11.7595279 5.11194905,11.7595279 C3.39010019,11.7595279 2.00021333,13.1571387 2.00021333,14.8872014 C2.00021333,16.8414505 3.794985,18.3779474 5.83246988,17.9230131 C7.36929074,17.5796252 8.34776238,16.1568618 8.19973997,14.5820892 C8.18450237,14.4213311 8.15620397,14.2638539 8.11266797,14.1074702 L8.63401159,13.5836396 Z M6.54210671,5.11617453 C6.54210671,5.91559037 5.89668548,6.5629969 5.11194905,6.5629969 C4.31632862,6.5629969 3.6709074,5.91559037 3.6709074,5.11617453 C3.6709074,4.32660102 4.31632862,3.68028808 5.11194905,3.68028808 C5.89668548,3.68028808 6.54210671,4.32660102 6.54210671,5.11617453 M18.0021973,16.1706411 L17.9368933,16.235163 C16.926858,17.2500165 15.2877276,17.2500165 14.2766039,16.235163 L7.87898848,9.80702856 C7.70919807,9.6353346 7.70919807,9.3586558 7.87898848,9.18805543 L9.15785853,7.90199109 C9.32764893,7.73139072 9.60301414,7.73139072 9.77280455,7.90199109 L18.0021973,16.1706411 Z M18.0021973,3.82934469 L12.4045559,9.45368895 L10.5107398,7.54865148 L14.2766039,3.76482275 C15.2877276,2.74996927 16.926858,2.74996927 17.9368933,3.76482275 L18.0021973,3.82934469 Z M8.63401159,6.41864274 C8.82992359,6.61548933 8.82992359,6.93591182 8.63401159,7.1327584 L7.10916313,8.66488096 C6.91216272,8.86282113 6.59434991,8.86282113 6.3984379,8.66488096 L5.87709428,8.14214392 C5.63546947,8.21213382 5.37425346,8.2427544 5.11194905,8.2427544 C3.39010019,8.2427544 2.00021333,6.84623726 2.00021333,5.11617453 C2.00021333,3.16192542 3.794985,1.62542849 5.83246988,2.0814564 C7.36929074,2.42375074 8.34776238,3.84651408 8.19973997,5.42128673 C8.18450237,5.58313837 8.15620397,5.73842845 8.11266797,5.89590571 L8.63401159,6.41864274 Z M10.3929749,10.0447755 C10.3929749,10.2744298 10.2079469,10.4614341 9.97829448,10.4614341 C9.74973047,10.4614341 9.56361406,10.2744298 9.56361406,10.0447755 C9.56361406,9.81402755 9.74973047,9.62811689 9.97829448,9.62811689 C10.2079469,9.62811689 10.3929749,9.81402755 10.3929749,10.0447755 Z" id="path-1"></path>
</defs>
<g id="Sound/General/Trim" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#575E75" xlink:href="#path-1"></use>
<g id="Blue/1_Blue" mask="url(#mask-2)" fill="#4D97FF">
<rect id="Primary-Blue" x="0" y="0" width="20" height="20"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
<title>Sound/Effects/Fade-in</title>
<desc>Created with Sketch.</desc>
<g id="Sound/Effects/Fade-in" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M2,10.25 C1.86192881,10.25 1.75,10.1380712 1.75,10 C1.75,9.86192881 1.86192881,9.75 2,9.75 L7,9.75 C7.35324851,9.75 7.58270077,9.50876428 7.90437981,8.88536107 L8.04612021,8.6082292 C8.35582235,8.01071273 8.58718112,7.75 9,7.75 C9.3879427,7.75 9.64783618,7.89521764 10.0567568,8.24762061 L10.1299543,8.31093199 C10.5105228,8.64016497 10.7058526,8.75 11,8.75 C11.1999644,8.75 11.402585,8.39321162 11.7655628,7.41317141 C12.2359183,6.14321162 12.4592237,5.75 13,5.75 C13.4020616,5.75 13.6314717,5.8897049 13.9133418,6.21140451 C13.9679067,6.27426342 13.9973758,6.30824799 14.0201857,6.33390904 C14.2852046,6.63205537 14.5123572,6.75 15,6.75 C15.3765436,6.75 15.6926018,6.13162529 16.2628292,4.42094306 C16.9426018,2.38162529 17.2654325,1.75 18,1.75 C18.1380712,1.75 18.25,1.86192881 18.25,2 L18.25,18 C18.25,18.1380712 18.1380712,18.25 18,18.25 C17.2654325,18.25 16.9426018,17.6183747 16.2628292,15.5790569 C15.6926018,13.8683747 15.3765436,13.25 15,13.25 C14.5123572,13.25 14.2852046,13.3679446 14.0201857,13.666091 C13.9973758,13.691752 13.9679067,13.7257366 13.9133418,13.7885955 C13.6314717,14.1102951 13.4020616,14.25 13,14.25 C12.4592237,14.25 12.2359183,13.8567884 11.7655628,12.5868286 C11.402585,11.6067884 11.1999644,11.25 11,11.25 C10.7058526,11.25 10.5105228,11.359835 10.1299543,11.689068 L10.0567568,11.7523794 C9.64783618,12.1047824 9.3879427,12.25 9,12.25 C8.58718112,12.25 8.35582235,11.9892873 8.04612021,11.3917708 L7.90437981,11.1146389 C7.58270078,10.4912357 7.35324851,10.25 7.00000001,10.25 L2,10.25 Z" id="Path" fill="#CF63CF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.2 (81672) - https://sketch.com -->
<title>Sound/Effects/Fade-out</title>
<desc>Created with Sketch.</desc>
<g id="Sound/Effects/Fade-out" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M13,10.25 C12.6467515,10.25 12.4172992,10.4912357 12.0956202,11.1146389 L11.9538798,11.3917708 C11.6441776,11.9892873 11.4128189,12.25 11,12.25 C10.6120573,12.25 10.3521638,12.1047824 9.94324322,11.7523794 L9.8700457,11.689068 C9.48947724,11.359835 9.29414743,11.25 9,11.25 C8.80003561,11.25 8.59741505,11.6067884 8.23443719,12.5868286 C7.76408171,13.8567884 7.54077635,14.25 7,14.25 C6.59793845,14.25 6.3685283,14.1102951 6.08665816,13.7885955 C6.03209329,13.7257366 6.00262416,13.691752 5.97981434,13.666091 C5.71479538,13.3679446 5.48764275,13.25 5,13.25 C4.62345642,13.25 4.30739824,13.8683747 3.73717082,15.5790569 C3.05739824,17.6183747 2.73456753,18.25 2,18.25 C1.86192881,18.25 1.75,18.1380712 1.75,18 L1.75,2 C1.75,1.86192881 1.86192881,1.75 2,1.75 C2.73456753,1.75 3.05739824,2.38162529 3.73717082,4.42094306 C4.30739824,6.13162529 4.62345642,6.75 5,6.75 C5.48764275,6.75 5.71479538,6.63205537 5.97981434,6.33390904 C6.00262416,6.30824799 6.03209329,6.27426342 6.08665816,6.21140451 C6.3685283,5.8897049 6.59793845,5.75 7,5.75 C7.54077635,5.75 7.76408171,6.14321162 8.23443719,7.41317141 C8.59741505,8.39321162 8.80003561,8.75 9,8.75 C9.29414743,8.75 9.48947724,8.64016497 9.8700457,8.31093199 L9.94324322,8.24762061 C10.3521638,7.89521764 10.6120573,7.75 11,7.75 C11.4128189,7.75 11.6441776,8.01071273 11.9538798,8.6082292 L12.0956202,8.88536107 C12.4172992,9.50876428 12.6467515,9.75 13,9.75 L18,9.75 C18.1380712,9.75 18.25,9.86192881 18.25,10 C18.25,10.1380712 18.1380712,10.25 18,10.25 L12.9999998,10.25 L13,10.25 Z" id="Path" fill="#CF63CF" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>icon--lounder</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="lounder">
<g id="louder" transform="translate(2.000000, 3.000000)">
<path d="M7.54011246,2.3873625 L7.54011246,11.8531125 C7.54011246,12.2457375 7.22294659,12.5629875 6.83155041,12.5629875 L5.50777655,12.5629875 C5.2018577,12.5629875 4.9296799,12.3661125 4.83408026,12.0736125 C4.29422346,10.4266125 2.75788098,9.3128625 1.02471572,9.3128625 L0.709799251,9.3128625 C0.319527773,9.3128625 0.0001125,8.9933625 0.0001125,8.6029875 L0.0001125,5.6374875 C0.0001125,5.2471125 0.319527773,4.9276125 0.709799251,4.9276125 L1.02471572,4.9276125 C2.75788098,4.9276125 4.29422346,3.8138625 4.83408026,2.1668625 C4.9296799,1.8743625 5.2018577,1.6774875 5.50777655,1.6774875 L6.83155041,1.6774875 C7.22294659,1.6774875 7.54011246,1.9947375 7.54011246,2.3873625" id="Fill-1" fill="#CF63CF"></path>
<path d="M9.25,5.8645125 C9.9385,6.5541375 9.9385,7.6858875 9.25,8.3755125" id="Stroke-3" stroke="#CF63CF" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M10.75,3.3541875 C12.829,5.4331875 12.829,8.8081875 10.75,10.8860625" id="Stroke-5" stroke="#CF63CF" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M12.5,0.8438625 C15.968375,4.3111125 15.968375,9.9293625 12.5,13.3966125" id="Stroke-7" stroke="#CF63CF" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
<title>Sound/Effects/Mute</title>
<desc>Created with Sketch.</desc>
<g id="Sound/Effects/Mute" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path fill="#CF63CF" d="M13.4541749,5.04815243 L15.6281168,2.87421054 C15.92101,2.58131732 16.3958837,2.58131732 16.688777,2.87421054 C16.9816702,3.16710376 16.9816702,3.64197749 16.688777,3.93487071 L4.53033009,16.0933176 C4.23743687,16.3862108 3.76256313,16.3862108 3.46966991,16.0933176 C3.1767767,15.8004244 3.1767767,15.3255506 3.46966991,15.0326574 L6.31214932,12.190178 C6.1241191,12.0621379 6,11.8463331 6,11.6029875 L6,8.6374875 C6,8.2471125 6.31941527,7.9276125 6.70968675,7.9276125 L7.02460322,7.9276125 C8.75776848,7.9276125 10.294111,6.8138625 10.8339678,5.1668625 C10.9295674,4.8743625 11.2017452,4.6774875 11.5076641,4.6774875 L12.8314379,4.6774875 C13.1002844,4.6774875 13.3341075,4.82717208 13.4541749,5.04815243 Z M13.54,9.08364771 L13.54,14.8531125 C13.54,15.2457375 13.2228341,15.5629875 12.8314379,15.5629875 L11.5076641,15.5629875 C11.2017452,15.5629875 10.9295674,15.3661125 10.8339678,15.0736125 C10.5773785,14.2908077 10.0956813,13.628465 9.47473531,13.1489124 L13.54,9.08364771 Z" id="path-1"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 56.1 (81669) - https://sketch.com -->
<title>Sound/General/Paste</title>
<desc>Created with Sketch.</desc>
<g id="Sound/General/Paste" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M2.39776184,8.69699508 C2.82037272,8.57348907 3.12697047,8.23068036 3.44607843,7.63235294 C3.50283588,7.52593273 3.70653958,7.12084014 3.75472041,7.03050109 C4.03665487,6.50187397 4.27845388,6.25 4.66666667,6.25 C5.08719776,6.25 5.31070056,6.3254322 5.67812087,6.53210612 C5.97736723,6.7004322 6.1242348,6.75 6.44444444,6.75 C6.611898,6.75 6.70455317,6.61444047 6.85968251,6.13575567 C6.89349476,6.03068757 6.91177802,5.97409653 6.92949584,5.92094306 C7.19192893,5.1336438 7.51873661,4.75 8.22222222,4.75 C8.89163675,4.75 9.21925695,5.04485818 9.76356813,5.86132495 C10.2192569,6.54485818 10.4471923,6.75 10.8888889,6.75 C11.3661624,6.75 11.5487705,6.64281697 11.7364319,6.36132495 C11.7692531,6.31129826 11.7692531,6.31129826 11.8038172,6.25761214 C12.0332099,5.90369189 12.2433073,5.75 12.6666667,5.75 C13.1851992,5.75 13.4061497,6.04828312 13.7840084,6.89846538 C14.0728163,7.54828312 14.2222362,7.75 14.4444444,7.75 C14.7687569,7.75 14.9402848,7.69488783 15.2489243,7.52961059 L15.2905031,7.50732887 C15.6361057,7.32238348 15.857036,7.25 16.2222222,7.25 C16.6365912,7.25 16.8489777,7.46586974 17.1158999,7.96477614 C17.1606386,8.04903257 17.1606386,8.04903257 17.2045543,8.13123692 C17.3511621,8.40280419 17.4762139,8.57093888 17.631381,8.6621791 C17.7227849,8.5624959 17.8540941,8.5 18,8.5 C18.2761424,8.5 18.5,8.72385763 18.5,9 C18.5,9.27614237 18.2761424,9.5 18,9.5 C17.8540941,9.5 17.7227849,9.4375041 17.631381,9.3378209 C17.4762139,9.42906112 17.3511621,9.59719581 17.2045543,9.86876308 C17.1606386,9.95096743 17.1606386,9.95096743 17.1158999,10.0352239 C16.8489777,10.5341303 16.6365912,10.75 16.2222222,10.75 C15.857036,10.75 15.6361057,10.6776165 15.2905031,10.4926711 L15.2489243,10.4703894 C14.9402848,10.3051122 14.7687569,10.25 14.4444444,10.25 C14.2222362,10.25 14.0728163,10.4517169 13.7840084,11.1015346 C13.4061497,11.9517169 13.1851992,12.25 12.6666667,12.25 C12.2433073,12.25 12.0332099,12.0963081 11.8038172,11.7423879 C11.7692531,11.6887017 11.7692531,11.6887017 11.7364319,11.638675 C11.5487705,11.357183 11.3661624,11.25 10.8888889,11.25 C10.4471923,11.25 10.2192569,11.4551418 9.76356813,12.138675 C9.21925695,12.9551418 8.89163675,13.25 8.22222222,13.25 C7.51873661,13.25 7.19192893,12.8663562 6.92949584,12.0790569 C6.91177802,12.0259035 6.89349476,11.9693124 6.85968251,11.8642443 C6.70455317,11.3855595 6.611898,11.25 6.44444444,11.25 C6.1242348,11.25 5.97736723,11.2995678 5.67812087,11.4678939 C5.31070056,11.6745678 5.08719776,11.75 4.66666667,11.75 C4.2576716,11.75 4.07414298,11.4976192 3.8822771,10.9321841 L3.78515608,10.639352 C3.74630783,10.5239258 3.71290663,10.4315854 3.67737933,10.3436452 C3.42795496,9.72624902 3.07265238,9.37546644 2.4155666,9.27813036 C2.32583908,9.41193093 2.17320436,9.5 2,9.5 C1.72385763,9.5 1.5,9.27614237 1.5,9 C1.5,8.72385763 1.72385763,8.5 2,8.5 C2.16222865,8.5 2.306412,8.5772611 2.39776184,8.69699508 Z M9,15 L9.7763932,13.4472136 C9.80058413,13.3988317 9.83981475,13.3596011 9.8881966,13.3354102 C10.0116912,13.2736629 10.1618595,13.323719 10.2236068,13.4472136 L11,15 L12,15 C12.2761424,15 12.5,15.2238576 12.5,15.5 C12.5,15.6081851 12.4649111,15.7134519 12.4,15.8 L10.4,18.4666667 C10.2343146,18.6875806 9.9209139,18.7323521 9.7,18.5666667 C9.66209717,18.5382395 9.62842712,18.5045695 9.6,18.4666667 L7.6,15.8 C7.43431458,15.5790861 7.4790861,15.2656854 7.7,15.1 C7.78654809,15.0350889 7.89181489,15 8,15 L9,15 L9,15 Z M2,3.5 C1.72385763,3.5 1.5,3.27614237 1.5,3 C1.5,2.72385763 1.72385763,2.5 2,2.5 C2.27614237,2.5 2.5,2.72385763 2.5,3 C2.5,3.27614237 2.27614237,3.5 2,3.5 Z M4,3.5 C3.72385763,3.5 3.5,3.27614237 3.5,3 C3.5,2.72385763 3.72385763,2.5 4,2.5 C4.27614237,2.5 4.5,2.72385763 4.5,3 C4.5,3.27614237 4.27614237,3.5 4,3.5 Z M6,3.5 C5.72385763,3.5 5.5,3.27614237 5.5,3 C5.5,2.72385763 5.72385763,2.5 6,2.5 C6.27614237,2.5 6.5,2.72385763 6.5,3 C6.5,3.27614237 6.27614237,3.5 6,3.5 Z M8,3.5 C7.72385763,3.5 7.5,3.27614237 7.5,3 C7.5,2.72385763 7.72385763,2.5 8,2.5 C8.27614237,2.5 8.5,2.72385763 8.5,3 C8.5,3.27614237 8.27614237,3.5 8,3.5 Z M10,3.5 C9.72385763,3.5 9.5,3.27614237 9.5,3 C9.5,2.72385763 9.72385763,2.5 10,2.5 C10.2761424,2.5 10.5,2.72385763 10.5,3 C10.5,3.27614237 10.2761424,3.5 10,3.5 Z M12,3.5 C11.7238576,3.5 11.5,3.27614237 11.5,3 C11.5,2.72385763 11.7238576,2.5 12,2.5 C12.2761424,2.5 12.5,2.72385763 12.5,3 C12.5,3.27614237 12.2761424,3.5 12,3.5 Z M14,3.5 C13.7238576,3.5 13.5,3.27614237 13.5,3 C13.5,2.72385763 13.7238576,2.5 14,2.5 C14.2761424,2.5 14.5,2.72385763 14.5,3 C14.5,3.27614237 14.2761424,3.5 14,3.5 Z M16,3.5 C15.7238576,3.5 15.5,3.27614237 15.5,3 C15.5,2.72385763 15.7238576,2.5 16,2.5 C16.2761424,2.5 16.5,2.72385763 16.5,3 C16.5,3.27614237 16.2761424,3.5 16,3.5 Z M18,3.5 C17.7238576,3.5 17.5,3.27614237 17.5,3 C17.5,2.72385763 17.7238576,2.5 18,2.5 C18.2761424,2.5 18.5,2.72385763 18.5,3 C18.5,3.27614237 18.2761424,3.5 18,3.5 Z M18,5.5 C17.7238576,5.5 17.5,5.27614237 17.5,5 C17.5,4.72385763 17.7238576,4.5 18,4.5 C18.2761424,4.5 18.5,4.72385763 18.5,5 C18.5,Line truncated
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.5 KiB

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="49px" height="52px" viewBox="-5 0 49 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>play</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M17.2214651,8.34499443 C19.7766419,4.08636653 23.9234057,4.09303946 26.4745787,8.34499443 L40.1520974,31.140859 C42.7072741,35.3994869 40.7489672,38.8517869 35.7726637,38.8517869 L7.92338006,38.8517869 C2.9495089,38.8517869 0.992773429,35.392814 3.54394641,31.140859 L17.2214651,8.34499443 Z" id="path-1"></path>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-623.000000, -347.000000)">
<g id="Step-3---Altering-Suggested-Trim" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
<g id="play" transform="translate(21.849131, 22.002656) rotate(90.000000) translate(-21.849131, -22.002656) ">
<use stroke="#FFFFFF" stroke-width="1" fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="52px" height="52px" viewBox="0 0 52 52" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
<title>stop-playback</title>
<desc>Created with Sketch.</desc>
<defs>
<rect id="path-1" x="0" y="0" width="44" height="44" rx="4"></rect>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Desktop---1280x720" transform="translate(-619.000000, -345.000000)">
<g id="Step-4---Playing-Trim" transform="translate(0.000000, 42.000000)">
<g id="Play-/-Record-/-Stop" transform="translate(623.000000, 307.000000)">
<g id="stop-playback">
<use fill="#FFFFFF" fill-rule="evenodd" xlink:href="#path-1"></use>
<rect stroke="#FFFFFF" stroke-width="1" x="0.5" y="0.5" width="43" height="43" rx="4"></rect>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+28 -8
View File
@@ -63,7 +63,9 @@
background: hsla(300, 53%, 60%, 0.15);
border: 1px solid $ui-black-transparent;
border-radius: 5px;
padding: 3px;
margin-top: 20px;
margin-bottom: 20px;
}
$border-radius: 0.25rem;
@@ -93,9 +95,9 @@ $border-radius: 0.25rem;
height: 3rem;
width: 3rem;
outline: none;
background: white;
background: $motion-primary;
border-radius: 100%;
border: 1px solid $ui-black-transparent;
border: 4px solid $ui-white-dim;
cursor: pointer;
padding: 0.75rem;
user-select: none;
@@ -118,6 +120,7 @@ $border-radius: 0.25rem;
color: $text-primary;
font-size: 0.625rem;
user-select: none;
}
[dir="ltr"] .trim-button {
@@ -130,7 +133,6 @@ $border-radius: 0.25rem;
.trim-button > img {
width: 1.25rem;
margin-bottom: -0.375rem;
}
.effect-button {
@@ -138,6 +140,7 @@ $border-radius: 0.25rem;
color: $text-primary;
font-size: 0.625rem;
user-select: none;
padding: 0.25rem 0;
}
.effect-button + .effect-button {
@@ -150,12 +153,25 @@ $border-radius: 0.25rem;
margin-bottom: -0.375rem;
}
/* mirror the louder/softer speaker icons when rtl */
[dir="rtl"] .effect-button:nth-of-type(6) img {
transform: scaleX(-1);
.tool-button {
flex-basis: 60px;
color: $text-primary;
font-size: 0.625rem;
user-select: none;
padding: 0.25rem 0;
}
[dir="rtl"] .effect-button:nth-of-type(7) img {
.tool-button + .tool-button {
margin: 0;
}
.tool-button img {
width: 4rem;
height: 1.5rem;
margin-bottom: -0.375rem;
}
[dir="rtl"] .flip-in-rtl img {
transform: scaleX(-1);
}
@@ -167,6 +183,10 @@ $border-radius: 0.25rem;
margin-right: 1rem;
}
.button-group {
display: flex;
}
.button-group .button {
border-radius: 0;
}
+119 -41
View File
@@ -8,24 +8,29 @@ import Label from '../forms/label.jsx';
import Input from '../forms/input.jsx';
import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
import AudioTrimmer from '../../containers/audio-trimmer.jsx';
import AudioSelector from '../../containers/audio-selector.jsx';
import IconButton from '../icon-button/icon-button.jsx';
import styles from './sound-editor.css';
import playIcon from '../record-modal/icon--play.svg';
import stopIcon from '../record-modal/icon--stop-playback.svg';
import trimIcon from './icon--trim.svg';
import trimConfirmIcon from './icon--trim-confirm.svg';
import playIcon from './icon--play.svg';
import stopIcon from './icon--stop.svg';
import redoIcon from './icon--redo.svg';
import undoIcon from './icon--undo.svg';
import echoIcon from './icon--echo.svg';
import fasterIcon from './icon--faster.svg';
import slowerIcon from './icon--slower.svg';
import louderIcon from './icon--louder.svg';
import softerIcon from './icon--softer.svg';
import robotIcon from './icon--robot.svg';
import reverseIcon from './icon--reverse.svg';
import fadeOutIcon from './icon--fade-out.svg';
import fadeInIcon from './icon--fade-in.svg';
import muteIcon from './icon--mute.svg';
import deleteIcon from './icon--delete.svg';
import copyIcon from './icon--copy.svg';
import pasteIcon from './icon--paste.svg';
import copyToNewIcon from './icon--copy-to-new.svg';
const BufferedInput = BufferedInputHOC(Input);
@@ -45,10 +50,25 @@ const messages = defineMessages({
description: 'Title of the button to stop the sound',
defaultMessage: 'Stop'
},
trim: {
id: 'gui.soundEditor.trim',
description: 'Title of the button to start trimminging the sound',
defaultMessage: 'Trim'
copy: {
id: 'gui.soundEditor.copy',
description: 'Title of the button to copy the sound',
defaultMessage: 'Copy'
},
paste: {
id: 'gui.soundEditor.paste',
description: 'Title of the button to paste the sound',
defaultMessage: 'Paste'
},
copyToNew: {
id: 'gui.soundEditor.copyToNew',
description: 'Title of the button to copy the selection into a new sound',
defaultMessage: 'Copy to New'
},
delete: {
id: 'gui.soundEditor.delete',
description: 'Title of the button to delete the sound',
defaultMessage: 'Delete'
},
save: {
id: 'gui.soundEditor.save',
@@ -99,11 +119,30 @@ const messages = defineMessages({
id: 'gui.soundEditor.reverse',
description: 'Title of the button to apply the reverse effect',
defaultMessage: 'Reverse'
},
fadeOut: {
id: 'gui.soundEditor.fadeOut',
description: 'Title of the button to apply the fade out effect',
defaultMessage: 'Fade out'
},
fadeIn: {
id: 'gui.soundEditor.fadeIn',
description: 'Title of the button to apply the fade in effect',
defaultMessage: 'Fade in'
},
mute: {
id: 'gui.soundEditor.mute',
description: 'Title of the button to apply the mute effect',
defaultMessage: 'Mute'
}
});
const SoundEditor = props => (
<div className={styles.editorContainer}>
<div
className={styles.editorContainer}
ref={props.setRef}
onMouseDown={props.onContainerClick}
>
<div className={styles.row}>
<div className={styles.inputGroup}>
<Label text={props.intl.formatMessage(messages.sound)}>
@@ -141,17 +180,33 @@ const SoundEditor = props => (
</button>
</div>
</div>
<div className={styles.inputGroup}>
<IconButton
className={styles.toolButton}
img={copyIcon}
title={props.intl.formatMessage(messages.copy)}
onClick={props.onCopy}
/>
<IconButton
className={styles.toolButton}
disabled={props.canPaste === false}
img={pasteIcon}
title={props.intl.formatMessage(messages.paste)}
onClick={props.onPaste}
/>
<IconButton
className={classNames(styles.toolButton, styles.flipInRtl)}
img={copyToNewIcon}
title={props.intl.formatMessage(messages.copyToNew)}
onClick={props.onCopyToNew}
/>
</div>
<IconButton
className={classNames(styles.trimButton, {
[styles.trimButtonActive]: props.trimStart !== null
})}
img={props.trimStart === null ? trimIcon : trimConfirmIcon}
title={props.trimStart === null ? (
<FormattedMessage {...messages.trim} />
) : (
<FormattedMessage {...messages.save} />
)}
onClick={props.onActivateTrim}
className={styles.toolButton}
disabled={props.trimStart === null}
img={deleteIcon}
title={props.intl.formatMessage(messages.delete)}
onClick={props.onDelete}
/>
</div>
<div className={styles.row}>
@@ -161,12 +216,13 @@ const SoundEditor = props => (
height={160}
width={600}
/>
<AudioTrimmer
<AudioSelector
playhead={props.playhead}
trimEnd={props.trimEnd}
trimStart={props.trimStart}
onSetTrimEnd={props.onSetTrimEnd}
onSetTrimStart={props.onSetTrimStart}
onPlay={props.onPlay}
onSetTrim={props.onSetTrim}
onStop={props.onStop}
/>
</div>
</div>
@@ -209,61 +265,83 @@ const SoundEditor = props => (
onClick={props.onSlower}
/>
<IconButton
className={styles.effectButton}
img={echoIcon}
title={<FormattedMessage {...messages.echo} />}
onClick={props.onEcho}
/>
<IconButton
className={styles.effectButton}
img={robotIcon}
title={<FormattedMessage {...messages.robot} />}
onClick={props.onRobot}
/>
<IconButton
className={styles.effectButton}
disabled={props.tooLoud}
className={classNames(styles.effectButton, styles.flipInRtl)}
img={louderIcon}
title={<FormattedMessage {...messages.louder} />}
onClick={props.onLouder}
/>
<IconButton
className={styles.effectButton}
className={classNames(styles.effectButton, styles.flipInRtl)}
img={softerIcon}
title={<FormattedMessage {...messages.softer} />}
onClick={props.onSofter}
/>
<IconButton
className={classNames(styles.effectButton, styles.flipInRtl)}
img={muteIcon}
title={<FormattedMessage {...messages.mute} />}
onClick={props.onMute}
/>
<IconButton
className={styles.effectButton}
img={fadeInIcon}
title={<FormattedMessage {...messages.fadeIn} />}
onClick={props.onFadeIn}
/>
<IconButton
className={styles.effectButton}
img={fadeOutIcon}
title={<FormattedMessage {...messages.fadeOut} />}
onClick={props.onFadeOut}
/>
<IconButton
className={styles.effectButton}
img={reverseIcon}
title={<FormattedMessage {...messages.reverse} />}
onClick={props.onReverse}
/>
<IconButton
className={styles.effectButton}
img={robotIcon}
title={<FormattedMessage {...messages.robot} />}
onClick={props.onRobot}
/>
</div>
</div>
);
SoundEditor.propTypes = {
canPaste: PropTypes.bool.isRequired,
canRedo: PropTypes.bool.isRequired,
canUndo: PropTypes.bool.isRequired,
chunkLevels: PropTypes.arrayOf(PropTypes.number).isRequired,
intl: intlShape,
name: PropTypes.string.isRequired,
onActivateTrim: PropTypes.func,
onChangeName: PropTypes.func.isRequired,
onContainerClick: PropTypes.func.isRequired,
onCopy: PropTypes.func.isRequired,
onCopyToNew: PropTypes.func.isRequired,
onDelete: PropTypes.func,
onEcho: PropTypes.func.isRequired,
onFadeIn: PropTypes.func.isRequired,
onFadeOut: PropTypes.func.isRequired,
onFaster: PropTypes.func.isRequired,
onLouder: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
onPaste: PropTypes.func.isRequired,
onPlay: PropTypes.func.isRequired,
onRedo: PropTypes.func.isRequired,
onReverse: PropTypes.func.isRequired,
onRobot: PropTypes.func.isRequired,
onSetTrimEnd: PropTypes.func,
onSetTrimStart: PropTypes.func,
onSetTrim: PropTypes.func,
onSlower: PropTypes.func.isRequired,
onSofter: PropTypes.func.isRequired,
onStop: PropTypes.func.isRequired,
onUndo: PropTypes.func.isRequired,
playhead: PropTypes.number,
setRef: PropTypes.func,
tooLoud: PropTypes.bool.isRequired,
trimEnd: PropTypes.number,
trimStart: PropTypes.number
};
+17
View File
@@ -39,6 +39,16 @@
height: .5rem;
}
.large {
width: 2.5rem;
height: 2.5rem;
}
.large::before, .large::after {
width: 2.5rem;
height: 2.5rem;
}
@keyframes spin {
0% {
transform: rotate(0deg);
@@ -71,3 +81,10 @@
.spinner.info::after {
border-top-color: $ui-white;
}
.spinner.primary {
border-color: $motion-transparent;
}
.spinner.primary::after {
border-top-color: $motion-primary;
}
+10 -3
View File
@@ -8,7 +8,8 @@ const SpinnerComponent = function (props) {
const {
className,
level,
small
small,
large
} = props;
return (
<div
@@ -16,18 +17,24 @@ const SpinnerComponent = function (props) {
className,
styles.spinner,
styles[level],
{[styles.small]: small}
{
[styles.small]: small,
[styles.large]: large
}
)}
/>
);
};
SpinnerComponent.propTypes = {
className: PropTypes.string,
large: PropTypes.bool,
level: PropTypes.string,
small: PropTypes.bool
};
SpinnerComponent.defaultProps = {
className: '',
level: 'info'
large: false,
level: 'info',
small: false
};
export default SpinnerComponent;
@@ -20,7 +20,6 @@
cursor: pointer;
user-select: none;
touch-action: none;
}
.sprite-selector-item.is-selected {
@@ -95,16 +94,16 @@
.delete-button {
position: absolute;
top: 0.125rem;
top: -.625rem;
z-index: auto;
}
[dir="ltr"] .delete-button {
right: 0.125rem;
right: -.625rem;
}
[dir="rtl"] .delete-button {
left: 0.125rem;
left: -.625rem;
}
.number {
@@ -2,7 +2,7 @@ import classNames from 'classnames';
import PropTypes from 'prop-types';
import React from 'react';
import CloseButton from '../close-button/close-button.jsx';
import DeleteButton from '../delete-button/delete-button.jsx';
import styles from './sprite-selector-item.css';
import {ContextMenuTrigger} from 'react-contextmenu';
import {DangerousMenuItem, ContextMenu, MenuItem} from '../context-menu/context-menu.jsx';
@@ -23,8 +23,9 @@ const SpriteSelectorItem = props => (
onMouseDown: props.onMouseDown,
onTouchStart: props.onMouseDown
}}
disable={props.dragging}
disable={props.preventContextMenu}
id={`${props.name}-${contextMenuId}`}
ref={props.componentRef}
>
{typeof props.number === 'undefined' ? null : (
<div className={styles.number}>{props.number}</div>
@@ -47,9 +48,8 @@ const SpriteSelectorItem = props => (
) : null}
</div>
{(props.selected && props.onDeleteButtonClick) ? (
<CloseButton
<DeleteButton
className={styles.deleteButton}
size={CloseButton.SIZE_SMALL}
onClick={props.onDeleteButtonClick}
/>
) : null }
@@ -89,9 +89,9 @@ const SpriteSelectorItem = props => (
SpriteSelectorItem.propTypes = {
className: PropTypes.string,
componentRef: PropTypes.func,
costumeURL: PropTypes.string,
details: PropTypes.string,
dragging: PropTypes.bool,
name: PropTypes.string.isRequired,
number: PropTypes.number,
onClick: PropTypes.func,
@@ -101,6 +101,7 @@ SpriteSelectorItem.propTypes = {
onMouseDown: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
preventContextMenu: PropTypes.bool,
selected: PropTypes.bool.isRequired
};
@@ -110,7 +110,8 @@ SpriteList.propTypes = {
editingTarget: PropTypes.string,
hoveredTarget: PropTypes.shape({
hoveredSprite: PropTypes.string,
receivedBlocks: PropTypes.bool
receivedBlocks: PropTypes.bool,
sprite: PropTypes.string
}),
items: PropTypes.arrayOf(PropTypes.shape({
costume: PropTypes.shape({
@@ -68,6 +68,8 @@
padding-left: calc($space / 2);
padding-right: calc($space / 2);
padding-bottom: $space;
overflow-x: hidden;
}
.add-button {
+11 -14
View File
@@ -19,17 +19,21 @@ class Waveform extends React.PureComponent {
// composite time when animating the playhead
const takeEveryN = Math.ceil(data.length / width);
const filteredData = takeEveryN === 1 ? data :
const filteredData = takeEveryN === 1 ? data.slice(0) :
data.filter((_, i) => i % takeEveryN === 0);
const cappedData = [0, ...filteredData, 0];
// Need at least two points to render waveform.
if (filteredData.length === 1) {
filteredData.push(filteredData[0]);
}
const maxIndex = filteredData.length - 1;
const points = [
...cappedData.map((v, i) =>
[width * i / cappedData.length, height * v / 2]
...filteredData.map((v, i) =>
[width * (i / maxIndex), height * v / 2]
),
...cappedData.reverse().map((v, i) =>
[width * (cappedData.length - i - 1) / cappedData.length, -height * v / 2]
...filteredData.reverse().map((v, i) =>
[width * (1 - (i / maxIndex)), -height * v / 2]
)
];
const pathComponents = points.map(([x, y], i) => {
@@ -40,15 +44,8 @@ class Waveform extends React.PureComponent {
return (
<svg
className={styles.container}
viewBox={`-1 0 ${width} ${height}`}
viewBox={`0 0 ${width} ${height}`}
>
<line
className={styles.baseline}
x1={-1}
x2={width}
y1={height / 2}
y2={height / 2}
/>
<g transform={`scale(1, -1) translate(0, -${height / 2})`}>
<path
className={styles.waveformPath}
+156
View File
@@ -0,0 +1,156 @@
import React from 'react';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import AudioSelectorComponent from '../components/audio-trimmer/audio-selector.jsx';
import {getEventXY} from '../lib/touch-utils';
import DragRecognizer from '../lib/drag-recognizer';
const MIN_LENGTH = 0.01;
const MIN_DURATION = 500;
class AudioSelector extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleNewSelectionMouseDown',
'handleTrimStartMouseDown',
'handleTrimEndMouseDown',
'handleTrimStartMouseMove',
'handleTrimEndMouseMove',
'handleTrimStartMouseUp',
'handleTrimEndMouseUp',
'storeRef'
]);
this.state = {
trimStart: props.trimStart,
trimEnd: props.trimEnd
};
this.clickStartTime = 0;
this.trimStartDragRecognizer = new DragRecognizer({
onDrag: this.handleTrimStartMouseMove,
onDragEnd: this.handleTrimStartMouseUp,
touchDragAngle: 90,
distanceThreshold: 0
});
this.trimEndDragRecognizer = new DragRecognizer({
onDrag: this.handleTrimEndMouseMove,
onDragEnd: this.handleTrimEndMouseUp,
touchDragAngle: 90,
distanceThreshold: 0
});
}
componentWillReceiveProps (newProps) {
const {trimStart, trimEnd} = this.props;
if (newProps.trimStart === trimStart && newProps.trimEnd === trimEnd) return;
this.setState({
trimStart: newProps.trimStart,
trimEnd: newProps.trimEnd
});
}
clearSelection () {
this.props.onSetTrim(null, null);
}
handleNewSelectionMouseDown (e) {
const {width, left} = this.containerElement.getBoundingClientRect();
this.initialTrimEnd = (getEventXY(e).x - left) / width;
this.initialTrimStart = this.initialTrimEnd;
this.props.onSetTrim(this.initialTrimStart, this.initialTrimEnd);
this.clickStartTime = Date.now();
this.containerSize = width;
this.trimEndDragRecognizer.start(e);
e.preventDefault();
}
handleTrimStartMouseMove (currentOffset, initialOffset) {
const dx = (currentOffset.x - initialOffset.x) / this.containerSize;
const newTrim = Math.max(0, Math.min(1, this.initialTrimStart + dx));
if (newTrim > this.initialTrimEnd) {
this.setState({
trimStart: this.initialTrimEnd,
trimEnd: newTrim
});
} else {
this.setState({
trimStart: newTrim,
trimEnd: this.initialTrimEnd
});
}
}
handleTrimEndMouseMove (currentOffset, initialOffset) {
const dx = (currentOffset.x - initialOffset.x) / this.containerSize;
const newTrim = Math.min(1, Math.max(0, this.initialTrimEnd + dx));
if (newTrim < this.initialTrimStart) {
this.setState({
trimStart: newTrim,
trimEnd: this.initialTrimStart
});
} else {
this.setState({
trimStart: this.initialTrimStart,
trimEnd: newTrim
});
}
}
handleTrimStartMouseUp () {
this.props.onSetTrim(this.state.trimStart, this.state.trimEnd);
}
handleTrimEndMouseUp () {
// If the selection was made quickly (tooFast) and is small (tooShort),
// deselect instead. This allows click-to-deselect even if you drag
// a little bit by accident. It also allows very quickly making a
// selection, as long as it is above a minimum length.
const tooFast = (Date.now() - this.clickStartTime) < MIN_DURATION;
const tooShort = (this.state.trimEnd - this.state.trimStart) < MIN_LENGTH;
if (tooFast && tooShort) {
this.clearSelection();
} else {
this.props.onSetTrim(this.state.trimStart, this.state.trimEnd);
}
}
handleTrimStartMouseDown (e) {
this.containerSize = this.containerElement.getBoundingClientRect().width;
this.trimStartDragRecognizer.start(e);
this.initialTrimStart = this.props.trimStart;
this.initialTrimEnd = this.props.trimEnd;
e.stopPropagation();
e.preventDefault();
}
handleTrimEndMouseDown (e) {
this.containerSize = this.containerElement.getBoundingClientRect().width;
this.trimEndDragRecognizer.start(e);
this.initialTrimEnd = this.props.trimEnd;
this.initialTrimStart = this.props.trimStart;
e.stopPropagation();
e.preventDefault();
}
storeRef (el) {
this.containerElement = el;
}
render () {
return (
<AudioSelectorComponent
containerRef={this.storeRef}
playhead={this.props.playhead}
trimEnd={this.state.trimEnd}
trimStart={this.state.trimStart}
onNewSelectionMouseDown={this.handleNewSelectionMouseDown}
onTrimEndMouseDown={this.handleTrimEndMouseDown}
onTrimStartMouseDown={this.handleTrimStartMouseDown}
/>
);
}
}
AudioSelector.propTypes = {
onSetTrim: PropTypes.func,
playhead: PropTypes.number,
trimEnd: PropTypes.number,
trimStart: PropTypes.number
};
export default AudioSelector;
+24 -33
View File
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import bindAll from 'lodash.bindall';
import AudioTrimmerComponent from '../components/audio-trimmer/audio-trimmer.jsx';
import {getEventXY} from '../lib/touch-utils';
import DragRecognizer from '../lib/drag-recognizer';
const MIN_LENGTH = 0.01; // Used to stop sounds being trimmed smaller than 1%
@@ -14,52 +14,43 @@ class AudioTrimmer extends React.Component {
'handleTrimEndMouseDown',
'handleTrimStartMouseMove',
'handleTrimEndMouseMove',
'handleTrimStartMouseUp',
'handleTrimEndMouseUp',
'storeRef'
]);
this.trimStartDragRecognizer = new DragRecognizer({
onDrag: this.handleTrimStartMouseMove,
touchDragAngle: 90,
distanceThreshold: 0
});
this.trimEndDragRecognizer = new DragRecognizer({
onDrag: this.handleTrimEndMouseMove,
touchDragAngle: 90,
distanceThreshold: 0
});
}
handleTrimStartMouseMove (e) {
const containerSize = this.containerElement.getBoundingClientRect().width;
const dx = (getEventXY(e).x - this.initialX) / containerSize;
handleTrimStartMouseMove (currentOffset, initialOffset) {
const dx = (currentOffset.x - initialOffset.x) / this.containerSize;
const newTrim = Math.max(0, Math.min(this.props.trimEnd - MIN_LENGTH, this.initialTrim + dx));
this.props.onSetTrimStart(newTrim);
e.preventDefault();
}
handleTrimEndMouseMove (e) {
const containerSize = this.containerElement.getBoundingClientRect().width;
const dx = (getEventXY(e).x - this.initialX) / containerSize;
handleTrimEndMouseMove (currentOffset, initialOffset) {
const dx = (currentOffset.x - initialOffset.x) / this.containerSize;
const newTrim = Math.min(1, Math.max(this.props.trimStart + MIN_LENGTH, this.initialTrim + dx));
this.props.onSetTrimEnd(newTrim);
e.preventDefault();
}
handleTrimStartMouseUp () {
window.removeEventListener('mousemove', this.handleTrimStartMouseMove);
window.removeEventListener('mouseup', this.handleTrimStartMouseUp);
window.removeEventListener('touchmove', this.handleTrimStartMouseMove);
window.removeEventListener('touchend', this.handleTrimStartMouseUp);
}
handleTrimEndMouseUp () {
window.removeEventListener('mousemove', this.handleTrimEndMouseMove);
window.removeEventListener('mouseup', this.handleTrimEndMouseUp);
window.removeEventListener('touchmove', this.handleTrimEndMouseMove);
window.removeEventListener('touchend', this.handleTrimEndMouseUp);
}
handleTrimStartMouseDown (e) {
this.initialX = getEventXY(e).x;
this.containerSize = this.containerElement.getBoundingClientRect().width;
this.trimStartDragRecognizer.start(e);
this.initialTrim = this.props.trimStart;
window.addEventListener('mousemove', this.handleTrimStartMouseMove);
window.addEventListener('mouseup', this.handleTrimStartMouseUp);
window.addEventListener('touchmove', this.handleTrimStartMouseMove);
window.addEventListener('touchend', this.handleTrimStartMouseUp);
e.stopPropagation();
e.preventDefault();
}
handleTrimEndMouseDown (e) {
this.initialX = getEventXY(e).x;
this.containerSize = this.containerElement.getBoundingClientRect().width;
this.trimEndDragRecognizer.start(e);
this.initialTrim = this.props.trimEnd;
window.addEventListener('mousemove', this.handleTrimEndMouseMove);
window.addEventListener('mouseup', this.handleTrimEndMouseUp);
window.addEventListener('touchmove', this.handleTrimEndMouseMove);
window.addEventListener('touchend', this.handleTrimEndMouseUp);
e.stopPropagation();
e.preventDefault();
}
storeRef (el) {
this.containerElement = el;
+2 -13
View File
@@ -2,7 +2,6 @@ import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import {connect} from 'react-redux';
import VM from 'scratch-vm';
import backdropLibraryContent from '../lib/libraries/backdrops.json';
@@ -33,7 +32,7 @@ class BackdropLibrary extends React.Component {
bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
skinId: null
};
this.props.vm.setEditingTarget(this.props.stageID);
// Do not switch to stage, just add the backdrop
this.props.vm.addBackdrop(item.md5, vmBackdrop);
}
render () {
@@ -53,17 +52,7 @@ class BackdropLibrary extends React.Component {
BackdropLibrary.propTypes = {
intl: intlShape.isRequired,
onRequestClose: PropTypes.func,
stageID: PropTypes.string.isRequired,
vm: PropTypes.instanceOf(VM).isRequired
};
const mapStateToProps = state => ({
stageID: state.scratchGui.targets.stage.id
});
const mapDispatchToProps = () => ({});
export default injectIntl(connect(
mapStateToProps,
mapDispatchToProps
)(BackdropLibrary));
export default injectIntl(BackdropLibrary);
+41 -10
View File
@@ -17,6 +17,7 @@ import errorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants';
import DropAreaHOC from '../lib/drop-area-hoc.jsx';
import DragConstants from '../lib/drag-constants';
import defineDynamicBlock from '../lib/define-dynamic-block';
import {connect} from 'react-redux';
import {updateToolbox} from '../reducers/toolbox';
@@ -339,9 +340,9 @@ class Blocks extends React.Component {
const targetSounds = target.getSounds();
const dynamicBlocksXML = this.props.vm.runtime.getBlocksXML();
return makeToolboxXML(target.isStage, target.id, dynamicBlocksXML,
targetCostumes[0].name,
stageCostumes[0].name,
targetSounds.length > 0 ? targetSounds[0].name : ''
targetCostumes[targetCostumes.length - 1].name,
stageCostumes[stageCostumes.length - 1].name,
targetSounds.length > 0 ? targetSounds[targetSounds.length - 1].name : ''
);
} catch {
return null;
@@ -393,20 +394,50 @@ class Blocks extends React.Component {
// workspace to be 'undone' here.
this.workspace.clearUndo();
}
handleExtensionAdded (blocksInfo) {
// select JSON from each block info object then reject the pseudo-blocks which don't have JSON, like separators
// this actually defines blocks and MUST run regardless of the UI state
this.ScratchBlocks.defineBlocksWithJsonArray(blocksInfo.map(blockInfo => blockInfo.json).filter(x => x));
handleExtensionAdded (categoryInfo) {
const defineBlocks = blockInfoArray => {
if (blockInfoArray && blockInfoArray.length > 0) {
const staticBlocksJson = [];
const dynamicBlocksInfo = [];
blockInfoArray.forEach(blockInfo => {
if (blockInfo.info && blockInfo.info.isDynamic) {
dynamicBlocksInfo.push(blockInfo);
} else if (blockInfo.json) {
staticBlocksJson.push(blockInfo.json);
}
// otherwise it's a non-block entry such as '---'
});
// Update the toolbox with new blocks
this.ScratchBlocks.defineBlocksWithJsonArray(staticBlocksJson);
dynamicBlocksInfo.forEach(blockInfo => {
// This is creating the block factory / constructor -- NOT a specific instance of the block.
// The factory should only know static info about the block: the category info and the opcode.
// Anything else will be picked up from the XML attached to the block instance.
const extendedOpcode = `${categoryInfo.id}_${blockInfo.info.opcode}`;
const blockDefinition =
defineDynamicBlock(this.ScratchBlocks, categoryInfo, blockInfo, extendedOpcode);
this.ScratchBlocks.Blocks[extendedOpcode] = blockDefinition;
});
}
};
// scratch-blocks implements a menu or custom field as a special kind of block ("shadow" block)
// these actually define blocks and MUST run regardless of the UI state
defineBlocks(
Object.getOwnPropertyNames(categoryInfo.customFieldTypes)
.map(fieldTypeName => categoryInfo.customFieldTypes[fieldTypeName].scratchBlocksDefinition));
defineBlocks(categoryInfo.menus);
defineBlocks(categoryInfo.blocks);
// Update the toolbox with new blocks if possible
const toolboxXML = this.getToolboxXML();
if (toolboxXML) {
this.props.updateToolboxState(toolboxXML);
}
}
handleBlocksInfoUpdate (blocksInfo) {
handleBlocksInfoUpdate (categoryInfo) {
// @todo Later we should replace this to avoid all the warnings from redefining blocks.
this.handleExtensionAdded(blocksInfo);
this.handleExtensionAdded(categoryInfo);
}
handleCategorySelected (categoryId) {
const extension = extensionData.find(ext => ext.extensionId === categoryId);
+3 -1
View File
@@ -19,6 +19,7 @@ import {
import CardsComponent from '../components/cards/cards.jsx';
import {loadImageData} from '../lib/libraries/decks/translate-image.js';
import {notScratchDesktop} from '../lib/isScratchDesktop';
class Cards extends React.Component {
componentDidMount () {
@@ -52,7 +53,8 @@ const mapStateToProps = state => ({
y: state.scratchGui.cards.y,
isRtl: state.locales.isRtl,
locale: state.locales.locale,
dragging: state.scratchGui.cards.dragging
dragging: state.scratchGui.cards.dragging,
showVideos: notScratchDesktop()
});
const mapDispatchToProps = dispatch => ({
+9 -31
View File
@@ -4,14 +4,13 @@ import {compose} from 'redux';
import {connect} from 'react-redux';
import ReactModal from 'react-modal';
import VM from 'scratch-vm';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import {injectIntl, intlShape} from 'react-intl';
import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx';
import {
getIsError,
getIsShowingProject
} from '../reducers/project-state';
import {setProjectTitle} from '../reducers/project-title';
import {
activateTab,
BLOCKS_TAB_INDEX,
@@ -29,6 +28,7 @@ import {
import FontLoaderHOC from '../lib/font-loader-hoc.jsx';
import LocalizationHOC from '../lib/localization-hoc.jsx';
import ProjectFetcherHOC from '../lib/project-fetcher-hoc.jsx';
import TitledHOC from '../lib/titled-hoc.jsx';
import ProjectSaverHOC from '../lib/project-saver-hoc.jsx';
import QueryParserHOC from '../lib/query-parser-hoc.jsx';
import storage from '../lib/storage';
@@ -39,42 +39,22 @@ import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx';
import GUIComponent from '../components/gui/gui.jsx';
import {setIsScratchDesktop} from '../lib/isScratchDesktop.js';
const messages = defineMessages({
defaultProjectTitle: {
id: 'gui.gui.defaultProjectTitle',
description: 'Default title for project',
defaultMessage: 'Scratch Project'
}
});
class GUI extends React.Component {
componentDidMount () {
setIsScratchDesktop(this.props.isScratchDesktop);
this.setReduxTitle(this.props.projectTitle);
this.props.onStorageInit(storage);
this.props.onVmInit(this.props.vm);
}
componentDidUpdate (prevProps) {
if (this.props.projectId !== prevProps.projectId && this.props.projectId !== null) {
this.props.onUpdateProjectId(this.props.projectId);
}
if (this.props.projectTitle !== prevProps.projectTitle) {
this.setReduxTitle(this.props.projectTitle);
}
if (this.props.isShowingProject && !prevProps.isShowingProject) {
// this only notifies container when a project changes from not yet loaded to loaded
// At this time the project view in www doesn't need to know when a project is unloaded
this.props.onProjectLoaded();
}
}
setReduxTitle (newTitle) {
if (newTitle === null || typeof newTitle === 'undefined') {
this.props.onUpdateReduxProjectTitle(
this.props.intl.formatMessage(messages.defaultProjectTitle)
);
} else {
this.props.onUpdateReduxProjectTitle(newTitle);
}
}
render () {
if (this.props.isError) {
throw new Error(
@@ -91,10 +71,9 @@ class GUI extends React.Component {
onProjectLoaded,
onStorageInit,
onUpdateProjectId,
onUpdateReduxProjectTitle,
onVmInit,
projectHost,
projectId,
projectTitle,
/* eslint-enable no-unused-vars */
children,
fetchingProject,
@@ -129,11 +108,9 @@ GUI.propTypes = {
onSeeCommunity: PropTypes.func,
onStorageInit: PropTypes.func,
onUpdateProjectId: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
onUpdateReduxProjectTitle: PropTypes.func,
onVmInit: PropTypes.func,
projectHost: PropTypes.string,
projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
projectTitle: PropTypes.string,
telemetryModalVisible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
@@ -142,7 +119,8 @@ GUI.defaultProps = {
isScratchDesktop: false,
onStorageInit: storageInstance => storageInstance.addOfficialScratchWebStores(),
onProjectLoaded: () => {},
onUpdateProjectId: () => {}
onUpdateProjectId: () => {},
onVmInit: (/* vm */) => {}
};
const mapStateToProps = state => {
@@ -182,8 +160,7 @@ const mapDispatchToProps = dispatch => ({
onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)),
onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()),
onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()),
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()),
onUpdateReduxProjectTitle: title => dispatch(setProjectTitle(title))
onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal())
});
const ConnectedGUI = injectIntl(connect(
@@ -200,6 +177,7 @@ const WrappedGui = compose(
FontLoaderHOC,
QueryParserHOC,
ProjectFetcherHOC,
TitledHOC,
ProjectSaverHOC,
vmListenerHOC,
vmManagerHOC,
+53 -21
View File
@@ -15,6 +15,8 @@ class LibraryItem extends React.PureComponent {
'handleKeyPress',
'handleMouseEnter',
'handleMouseLeave',
'handlePlay',
'handleStop',
'rotateIcon',
'startRotatingIcons',
'stopRotatingIcons'
@@ -37,7 +39,9 @@ class LibraryItem extends React.PureComponent {
e.preventDefault();
}
handleFocus (id) {
this.handleMouseEnter(id);
if (!this.props.showPlayButton) {
this.handleMouseEnter(id);
}
}
handleKeyPress (e) {
if (e.key === ' ' || e.key === 'Enter') {
@@ -46,22 +50,34 @@ class LibraryItem extends React.PureComponent {
}
}
handleMouseEnter () {
this.props.onMouseEnter(this.props.id);
if (this.props.icons && this.props.icons.length) {
this.stopRotatingIcons();
this.setState({
isRotatingIcon: true
}, this.startRotatingIcons);
// only show hover effects on the item if not showing a play button
if (!this.props.showPlayButton) {
this.props.onMouseEnter(this.props.id);
if (this.props.icons && this.props.icons.length) {
this.stopRotatingIcons();
this.setState({
isRotatingIcon: true
}, this.startRotatingIcons);
}
}
}
handleMouseLeave () {
this.props.onMouseLeave(this.props.id);
if (this.props.icons && this.props.icons.length) {
this.setState({
isRotatingIcon: false
}, this.stopRotatingIcons);
// only show hover effects on the item if not showing a play button
if (!this.props.showPlayButton) {
this.props.onMouseLeave(this.props.id);
if (this.props.icons && this.props.icons.length) {
this.setState({
isRotatingIcon: false
}, this.stopRotatingIcons);
}
}
}
handlePlay () {
this.props.onMouseEnter(this.props.id);
}
handleStop () {
this.props.onMouseLeave(this.props.id);
}
startRotatingIcons () {
this.rotateIcon();
this.intervalId = setInterval(this.rotateIcon, 300);
@@ -75,17 +91,21 @@ class LibraryItem extends React.PureComponent {
const nextIconIndex = (this.state.iconIndex + 1) % this.props.icons.length;
this.setState({iconIndex: nextIconIndex});
}
curIconSource () {
curIconMd5 () {
if (this.props.icons &&
this.state.isRotatingIcon &&
this.state.iconIndex < this.props.icons.length &&
this.props.icons[this.state.iconIndex]) {
return this.props.icons[this.state.iconIndex];
this.props.icons[this.state.iconIndex] &&
this.props.icons[this.state.iconIndex].baseLayerMD5) {
return this.props.icons[this.state.iconIndex].baseLayerMD5;
}
return this.props.iconSource;
return this.props.iconMd5;
}
render () {
const iconSource = this.curIconSource();
const iconMd5 = this.curIconMd5();
const iconURL = iconMd5 ?
`https://cdn.assets.scratch.mit.edu/internalapi/asset/${iconMd5}/get/` :
this.props.iconRawURL;
return (
<LibraryItemComponent
bluetoothRequired={this.props.bluetoothRequired}
@@ -95,17 +115,22 @@ class LibraryItem extends React.PureComponent {
extensionId={this.props.extensionId}
featured={this.props.featured}
hidden={this.props.hidden}
iconSource={iconSource}
iconURL={iconURL}
icons={this.props.icons}
id={this.props.id}
insetIconURL={this.props.insetIconURL}
internetConnectionRequired={this.props.internetConnectionRequired}
isPlaying={this.props.isPlaying}
name={this.props.name}
showPlayButton={this.props.showPlayButton}
onBlur={this.handleBlur}
onClick={this.handleClick}
onFocus={this.handleFocus}
onKeyPress={this.handleKeyPress}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onPlay={this.handlePlay}
onStop={this.handleStop}
/>
);
}
@@ -122,18 +147,25 @@ LibraryItem.propTypes = {
extensionId: PropTypes.string,
featured: PropTypes.bool,
hidden: PropTypes.bool,
iconSource: LibraryItemComponent.propTypes.iconSource, // single icon
icons: PropTypes.arrayOf(LibraryItemComponent.propTypes.iconSource), // rotating icons
iconMd5: PropTypes.string,
iconRawURL: PropTypes.string,
icons: PropTypes.arrayOf(
PropTypes.shape({
baseLayerMD5: PropTypes.string
})
),
id: PropTypes.number.isRequired,
insetIconURL: PropTypes.string,
internetConnectionRequired: PropTypes.bool,
isPlaying: PropTypes.bool,
name: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
]),
onMouseEnter: PropTypes.func.isRequired,
onMouseLeave: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired
onSelect: PropTypes.func.isRequired,
showPlayButton: PropTypes.bool
};
export default injectIntl(LibraryItem);
+115
View File
@@ -0,0 +1,115 @@
import PropTypes from 'prop-types';
import React from 'react';
import bindAll from 'lodash.bindall';
import PlayButtonComponent from '../components/play-button/play-button.jsx';
class PlayButton extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleClick',
'handleMouseDown',
'handleMouseEnter',
'handleMouseLeave',
'handleTouchStart',
'setButtonRef'
]);
this.state = {
touchStarted: false
};
}
getDerivedStateFromProps (props, state) {
// if touchStarted is true and it's not playing, the sound must have ended.
// reset the touchStarted state to allow the sound to be replayed
if (state.touchStarted && !props.isPlaying) {
return {
touchStarted: false
};
}
return null; // nothing changed
}
componentDidMount () {
// Touch start
this.buttonRef.addEventListener('touchstart', this.handleTouchStart);
}
componentWillUnmount () {
this.buttonRef.removeEventListener('touchstart', this.handleTouchStart);
}
handleClick (e) {
// stop the click from propagating out of the button
e.stopPropagation();
}
handleMouseDown (e) {
// prevent default (focus) on mouseDown
e.preventDefault();
if (this.props.isPlaying) {
// stop sound and reset touch state
this.props.onStop();
if (this.state.touchstarted) this.setState({touchStarted: false});
} else {
this.props.onPlay();
if (this.state.touchstarted) {
// started on touch, but now clicked mouse
this.setState({touchStarted: false});
}
}
}
handleTouchStart (e) {
if (this.props.isPlaying) {
// If playing, stop sound, and reset touch state
e.preventDefault();
this.setState({touchStarted: false});
this.props.onStop();
} else {
// otherwise start playing, and set touch state
e.preventDefault();
this.setState({touchStarted: true});
this.props.onPlay();
}
}
handleMouseEnter (e) {
// start the sound if it's not already playing
e.preventDefault();
if (!this.props.isPlaying) {
this.props.onPlay();
}
}
handleMouseLeave () {
// stop the sound unless it was started by touch
if (this.props.isPlaying && !this.state.touchstarted) {
this.props.onStop();
}
}
setButtonRef (ref) {
this.buttonRef = ref;
}
render () {
const {
className,
isPlaying,
onPlay, // eslint-disable-line no-unused-vars
onStop // eslint-disable-line no-unused-vars
} = this.props;
return (
<PlayButtonComponent
className={className}
isPlaying={isPlaying}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
setButtonRef={this.setButtonRef}
/>
);
}
}
PlayButton.propTypes = {
className: PropTypes.string,
isPlaying: PropTypes.bool.isRequired,
onPlay: PropTypes.func.isRequired,
onStop: PropTypes.func.isRequired
};
export default PlayButton;
+3 -30
View File
@@ -2,8 +2,8 @@ import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import VM from 'scratch-vm';
import WavEncoder from 'wav-encoder';
import {connect} from 'react-redux';
import {encodeAndAddSoundToVM} from '../lib/audio/audio-util.js';
import RecordModalComponent from '../components/record-modal/record-modal.jsx';
@@ -71,39 +71,12 @@ class RecordModal extends React.Component {
const startIndex = Math.floor(this.state.trimStart * sampleCount);
const endIndex = Math.floor(this.state.trimEnd * sampleCount);
const clippedSamples = this.state.samples.slice(startIndex, endIndex);
WavEncoder.encode({
sampleRate: this.state.sampleRate,
channelData: [clippedSamples]
}).then(wavBuffer => {
const vmSound = {
format: '',
dataFormat: 'wav',
rate: this.state.sampleRate,
sampleCount: clippedSamples.length
};
// Create an asset from the encoded .wav and get resulting md5
const storage = this.props.vm.runtime.storage;
vmSound.asset = storage.createAsset(
storage.AssetType.Sound,
storage.DataFormat.WAV,
new Uint8Array(wavBuffer),
null,
true // generate md5
);
vmSound.assetId = vmSound.asset.assetId;
// update vmSound object with md5 property
vmSound.md5 = `${vmSound.assetId}.${vmSound.dataFormat}`;
// The VM will update the sound name to a fresh name
// if the following is already taken
vmSound.name = 'recording1';
this.props.vm.addSound(vmSound).then(() => {
encodeAndAddSoundToVM(this.props.vm, clippedSamples, this.state.sampleRate, 'recording1',
() => {
this.props.onClose();
this.props.onNewSound();
});
});
});
}
handleCancel () {
+4 -4
View File
@@ -44,10 +44,10 @@ class RecordingStep extends React.Component {
alert(this.props.intl.formatMessage(messages.alertMsg)); // eslint-disable-line no-alert
}
handleLevelUpdate (level) {
this.setState({level});
if (this.props.recording) {
this.setState({levels: (this.state.levels || []).concat([level])});
}
this.setState({
level: level,
levels: this.props.recording ? (this.state.levels || []).concat([level]) : this.state.levels
});
}
handleRecord () {
this.audioRecorder.startRecording();
+9 -6
View File
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {connect} from 'react-redux';
import {defineMessages, injectIntl, intlShape} from 'react-intl';
import {setProjectTitle} from '../reducers/project-title';
import log from '../lib/log';
import sharedMessages from '../lib/shared-messages';
@@ -26,13 +27,14 @@ import {
/**
* SBFileUploader component passes a file input, load handler and props to its child.
* It expects this child to be a function with the signature
* function (renderFileInput, loadProject) {}
* function (renderFileInput, handleLoadProject) {}
* The component can then be used to attach project loading functionality
* to any other component:
*
* <SBFileUploader>{(renderFileInput, loadProject) => (
* <SBFileUploader>{(className, renderFileInput, handleLoadProject) => (
* <MyCoolComponent
* onClick={loadProject}
* className={className}
* onClick={handleLoadProject}
* >
* {renderFileInput()}
* </MyCoolComponent>
@@ -130,7 +132,7 @@ class SBFileUploader extends React.Component {
// This is necessary in case the user wants to reload a project
if (filename) {
const uploadedProjectTitle = this.getProjectTitleFromFilename(filename);
this.props.onUpdateProjectTitle(uploadedProjectTitle);
this.props.onReceivedProjectTitle(uploadedProjectTitle);
}
this.resetFileInput();
})
@@ -178,9 +180,9 @@ SBFileUploader.propTypes = {
loadingState: PropTypes.oneOf(LoadingStates),
onLoadingFinished: PropTypes.func,
onLoadingStarted: PropTypes.func,
onUpdateProjectTitle: PropTypes.func,
projectChanged: PropTypes.bool,
requestProjectUpload: PropTypes.func,
onReceivedProjectTitle: PropTypes.func,
userOwnsProject: PropTypes.bool,
vm: PropTypes.shape({
loadProject: PropTypes.func
@@ -208,7 +210,8 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
dispatch(closeFileMenu());
},
requestProjectUpload: loadingState => dispatch(requestProjectUpload(loadingState)),
onLoadingStarted: () => dispatch(openLoadingProject())
onLoadingStarted: () => dispatch(openLoadingProject()),
onReceivedProjectTitle: title => dispatch(setProjectTitle(title))
});
// Allow incoming props to override redux-provided props. Used to mock in tests.
+264 -45
View File
@@ -2,10 +2,11 @@ import bindAll from 'lodash.bindall';
import PropTypes from 'prop-types';
import React from 'react';
import WavEncoder from 'wav-encoder';
import VM from 'scratch-vm';
import {connect} from 'react-redux';
import {computeChunkedRMS, SOUND_BYTE_LIMIT} from '../lib/audio/audio-util.js';
import {computeChunkedRMS, encodeAndAddSoundToVM, SOUND_BYTE_LIMIT} from '../lib/audio/audio-util.js';
import AudioEffects from '../lib/audio/audio-effects.js';
import SoundEditorComponent from '../components/sound-editor/sound-editor.jsx';
import AudioBufferPlayer from '../lib/audio/audio-buffer-player.js';
@@ -13,25 +14,35 @@ import log from '../lib/log.js';
const UNDO_STACK_SIZE = 99;
const MAX_RMS = 1.2;
class SoundEditor extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'copy',
'copyCurrentBuffer',
'handleCopyToNew',
'handleStoppedPlaying',
'handleChangeName',
'handlePlay',
'handleStopPlaying',
'handleUpdatePlayhead',
'handleActivateTrim',
'handleUpdateTrimEnd',
'handleUpdateTrimStart',
'handleDelete',
'handleUpdateTrim',
'handleEffect',
'handleUndo',
'handleRedo',
'submitNewSamples'
'submitNewSamples',
'handleCopy',
'handlePaste',
'paste',
'handleKeyPress',
'handleContainerClick',
'setRef'
]);
this.state = {
copyBuffer: null,
chunkLevels: computeChunkedRMS(this.props.samples),
playhead: null, // null is not playing, [0 -> 1] is playing percent
trimStart: null,
@@ -40,28 +51,84 @@ class SoundEditor extends React.Component {
this.redoStack = [];
this.undoStack = [];
this.ref = null;
}
componentDidMount () {
this.audioBufferPlayer = new AudioBufferPlayer(this.props.samples, this.props.sampleRate);
document.addEventListener('keydown', this.handleKeyPress);
}
componentWillReceiveProps (newProps) {
if (newProps.soundId !== this.props.soundId) { // A different sound has been selected
this.redoStack = [];
this.undoStack = [];
this.resetState(newProps.samples, newProps.sampleRate);
this.setState({
trimStart: null,
trimEnd: null
});
}
}
componentWillUnmount () {
this.audioBufferPlayer.stop();
document.removeEventListener('keydown', this.handleKeyPress);
}
handleKeyPress (event) {
if (event.target instanceof HTMLInputElement) {
// Ignore keyboard shortcuts if a text input field is focused
return;
}
if (event.key === ' ') {
event.preventDefault();
if (this.state.playhead) {
this.handleStopPlaying();
} else {
this.handlePlay();
}
}
if (event.key === 'Delete' || event.key === 'Backspace') {
event.preventDefault();
if (event.shiftKey) {
this.handleDeleteInverse();
} else {
this.handleDelete();
}
}
if (event.key === 'Escape') {
event.preventDefault();
this.handleUpdateTrim(null, null);
}
if (event.metaKey || event.ctrlKey) {
if (event.shiftKey && event.key.toLowerCase() === 'z') {
event.preventDefault();
if (this.redoStack.length > 0) {
this.handleRedo();
}
} else if (event.key === 'z') {
if (this.undoStack.length > 0) {
event.preventDefault();
this.handleUndo();
}
} else if (event.key === 'c') {
event.preventDefault();
this.handleCopy();
} else if (event.key === 'v') {
event.preventDefault();
this.handlePaste();
} else if (event.key === 'a') {
event.preventDefault();
this.handleUpdateTrim(0, 1);
}
}
}
resetState (samples, sampleRate) {
this.audioBufferPlayer.stop();
this.audioBufferPlayer = new AudioBufferPlayer(samples, sampleRate);
this.setState({
chunkLevels: computeChunkedRMS(samples),
playhead: null,
trimStart: null,
trimEnd: null
playhead: null
});
}
submitNewSamples (samples, sampleRate, skipUndo) {
@@ -93,7 +160,7 @@ class SoundEditor extends React.Component {
if (this.undoStack.length >= UNDO_STACK_SIZE) {
this.undoStack.shift(); // Drop the first element off the array
}
this.undoStack.push(this.copyCurrentBuffer());
this.undoStack.push(this.getUndoItem());
}
this.resetState(samples, sampleRate);
this.props.vm.updateSoundBuffer(
@@ -106,6 +173,7 @@ class SoundEditor extends React.Component {
return false; // Update failed
}
handlePlay () {
this.audioBufferPlayer.stop();
this.audioBufferPlayer.play(
this.state.trimStart || 0,
this.state.trimEnd || 1,
@@ -125,31 +193,46 @@ class SoundEditor extends React.Component {
handleChangeName (name) {
this.props.vm.renameSound(this.props.soundIndex, name);
}
handleActivateTrim () {
if (this.state.trimStart === null && this.state.trimEnd === null) {
this.setState({trimEnd: 0.95, trimStart: 0.05});
handleDelete () {
const {samples, sampleRate} = this.copyCurrentBuffer();
const sampleCount = samples.length;
const startIndex = Math.floor(this.state.trimStart * sampleCount);
const endIndex = Math.floor(this.state.trimEnd * sampleCount);
const firstPart = samples.slice(0, startIndex);
const secondPart = samples.slice(endIndex, sampleCount);
const newLength = firstPart.length + secondPart.length;
let newSamples;
if (newLength === 0) {
newSamples = new Float32Array(1);
} else {
const {samples, sampleRate} = this.copyCurrentBuffer();
const sampleCount = samples.length;
const startIndex = Math.floor(this.state.trimStart * sampleCount);
const endIndex = Math.floor(this.state.trimEnd * sampleCount);
if (endIndex > startIndex) { // Strictly greater to prevent 0 sample sounds
const clippedSamples = samples.slice(startIndex, endIndex);
this.submitNewSamples(clippedSamples, sampleRate);
} else {
// Just clear the trim state, it cannot be completed
this.setState({
trimStart: null,
trimEnd: null
});
}
newSamples = new Float32Array(newLength);
newSamples.set(firstPart, 0);
newSamples.set(secondPart, firstPart.length);
}
this.submitNewSamples(newSamples, sampleRate);
this.setState({
trimStart: null,
trimEnd: null
});
}
handleUpdateTrimEnd (trimEnd) {
this.setState({trimEnd});
handleDeleteInverse () {
const {samples, sampleRate} = this.copyCurrentBuffer();
const sampleCount = samples.length;
const startIndex = Math.floor(this.state.trimStart * sampleCount);
const endIndex = Math.floor(this.state.trimEnd * sampleCount);
let clippedSamples = samples.slice(startIndex, endIndex);
if (clippedSamples.length === 0) {
clippedSamples = new Float32Array(1);
}
this.submitNewSamples(clippedSamples, sampleRate);
this.setState({
trimStart: null,
trimEnd: null
});
}
handleUpdateTrimStart (trimStart) {
this.setState({trimStart});
handleUpdateTrim (trimStart, trimEnd) {
this.setState({trimStart, trimEnd});
this.handleStopPlaying();
}
effectFactory (name) {
return () => this.handleEffect(name);
@@ -162,52 +245,191 @@ class SoundEditor extends React.Component {
};
}
handleEffect (name) {
const effects = new AudioEffects(this.audioBufferPlayer.buffer, name);
effects.process(({renderedBuffer}) => {
const trimStart = this.state.trimStart === null ? 0.0 : this.state.trimStart;
const trimEnd = this.state.trimEnd === null ? 1.0 : this.state.trimEnd;
// Offline audio context needs at least 2 samples
if (this.audioBufferPlayer.buffer.length < 2) {
return;
}
const effects = new AudioEffects(this.audioBufferPlayer.buffer, name, trimStart, trimEnd);
effects.process((renderedBuffer, adjustedTrimStart, adjustedTrimEnd) => {
const samples = renderedBuffer.getChannelData(0);
const sampleRate = renderedBuffer.sampleRate;
const success = this.submitNewSamples(samples, sampleRate);
if (success) this.handlePlay();
if (success) {
if (this.state.trimStart === null) {
this.handlePlay();
} else {
this.setState({trimStart: adjustedTrimStart, trimEnd: adjustedTrimEnd}, this.handlePlay);
}
}
});
}
tooLoud () {
const numChunks = this.state.chunkLevels.length;
const startIndex = this.state.trimStart === null ?
0 : Math.floor(this.state.trimStart * numChunks);
const endIndex = this.state.trimEnd === null ?
numChunks - 1 : Math.ceil(this.state.trimEnd * numChunks);
const trimChunks = this.state.chunkLevels.slice(startIndex, endIndex);
return Math.max(...trimChunks) > MAX_RMS;
}
getUndoItem () {
return {
...this.copyCurrentBuffer(),
trimStart: this.state.trimStart,
trimEnd: this.state.trimEnd
};
}
handleUndo () {
this.redoStack.push(this.copyCurrentBuffer());
const {samples, sampleRate} = this.undoStack.pop();
this.redoStack.push(this.getUndoItem());
const {samples, sampleRate, trimStart, trimEnd} = this.undoStack.pop();
if (samples) {
this.submitNewSamples(samples, sampleRate, true);
this.handlePlay();
this.setState({trimStart: trimStart, trimEnd: trimEnd}, this.handlePlay);
}
}
handleRedo () {
const {samples, sampleRate} = this.redoStack.pop();
const {samples, sampleRate, trimStart, trimEnd} = this.redoStack.pop();
if (samples) {
this.undoStack.push(this.copyCurrentBuffer());
this.undoStack.push(this.getUndoItem());
this.submitNewSamples(samples, sampleRate, true);
this.setState({trimStart: trimStart, trimEnd: trimEnd}, this.handlePlay);
}
}
handleCopy () {
this.copy();
}
copy (callback) {
const trimStart = this.state.trimStart === null ? 0.0 : this.state.trimStart;
const trimEnd = this.state.trimEnd === null ? 1.0 : this.state.trimEnd;
const newCopyBuffer = this.copyCurrentBuffer();
const trimStartSamples = trimStart * newCopyBuffer.samples.length;
const trimEndSamples = trimEnd * newCopyBuffer.samples.length;
newCopyBuffer.samples = newCopyBuffer.samples.slice(trimStartSamples, trimEndSamples);
this.setState({
copyBuffer: newCopyBuffer
}, callback);
}
handleCopyToNew () {
this.copy(() => {
encodeAndAddSoundToVM(this.props.vm, this.state.copyBuffer.samples,
this.state.copyBuffer.sampleRate, this.props.name);
});
}
resampleBufferToRate (buffer, newRate) {
return new Promise(resolve => {
if (window.OfflineAudioContext) {
const sampleRateRatio = newRate / buffer.sampleRate;
const newLength = sampleRateRatio * buffer.samples.length;
const offlineContext = new window.OfflineAudioContext(1, newLength, newRate);
const source = offlineContext.createBufferSource();
const audioBuffer = offlineContext.createBuffer(1, buffer.samples.length, buffer.sampleRate);
audioBuffer.getChannelData(0).set(buffer.samples);
source.buffer = audioBuffer;
source.connect(offlineContext.destination);
source.start();
offlineContext.startRendering();
offlineContext.oncomplete = ({renderedBuffer}) => {
resolve({
samples: renderedBuffer.getChannelData(0),
sampleRate: newRate
});
};
}
});
}
paste () {
// If there's no selection, paste at the end of the sound
const {samples} = this.copyCurrentBuffer();
if (this.state.trimStart === null) {
const newLength = samples.length + this.state.copyBuffer.samples.length;
const newSamples = new Float32Array(newLength);
newSamples.set(samples, 0);
newSamples.set(this.state.copyBuffer.samples, samples.length);
this.submitNewSamples(newSamples, this.props.sampleRate, false);
this.handlePlay();
} else {
// else replace the selection with the pasted sound
const trimStartSamples = this.state.trimStart * samples.length;
const trimEndSamples = this.state.trimEnd * samples.length;
const firstPart = samples.slice(0, trimStartSamples);
const lastPart = samples.slice(trimEndSamples);
const newLength = firstPart.length + this.state.copyBuffer.samples.length + lastPart.length;
const newSamples = new Float32Array(newLength);
newSamples.set(firstPart, 0);
newSamples.set(this.state.copyBuffer.samples, firstPart.length);
newSamples.set(lastPart, firstPart.length + this.state.copyBuffer.samples.length);
const trimStartSeconds = trimStartSamples / this.props.sampleRate;
const trimEndSeconds = trimStartSeconds +
(this.state.copyBuffer.samples.length / this.state.copyBuffer.sampleRate);
const newDurationSeconds = newSamples.length / this.state.copyBuffer.sampleRate;
const adjustedTrimStart = trimStartSeconds / newDurationSeconds;
const adjustedTrimEnd = trimEndSeconds / newDurationSeconds;
this.submitNewSamples(newSamples, this.props.sampleRate, false);
this.setState({
trimStart: adjustedTrimStart,
trimEnd: adjustedTrimEnd
}, this.handlePlay);
}
}
handlePaste () {
if (!this.state.copyBuffer) return;
if (this.state.copyBuffer.sampleRate === this.props.sampleRate) {
this.paste();
} else {
this.resampleBufferToRate(this.state.copyBuffer, this.props.sampleRate).then(buffer => {
this.setState({
copyBuffer: buffer
}, this.paste);
});
}
}
setRef (element) {
this.ref = element;
}
handleContainerClick (e) {
// If the click is on the sound editor's div (and not any other element), delesect
if (e.target === this.ref && this.state.trimStart !== null) {
this.handleUpdateTrim(null, null);
}
}
render () {
const {effectTypes} = AudioEffects;
return (
<SoundEditorComponent
canPaste={this.state.copyBuffer !== null}
canRedo={this.redoStack.length > 0}
canUndo={this.undoStack.length > 0}
chunkLevels={this.state.chunkLevels}
name={this.props.name}
playhead={this.state.playhead}
setRef={this.setRef}
tooLoud={this.tooLoud()}
trimEnd={this.state.trimEnd}
trimStart={this.state.trimStart}
onActivateTrim={this.handleActivateTrim}
onChangeName={this.handleChangeName}
onContainerClick={this.handleContainerClick}
onCopy={this.handleCopy}
onCopyToNew={this.handleCopyToNew}
onDelete={this.handleDelete}
onEcho={this.effectFactory(effectTypes.ECHO)}
onFadeIn={this.effectFactory(effectTypes.FADEIN)}
onFadeOut={this.effectFactory(effectTypes.FADEOUT)}
onFaster={this.effectFactory(effectTypes.FASTER)}
onLouder={this.effectFactory(effectTypes.LOUDER)}
onMute={this.effectFactory(effectTypes.MUTE)}
onPaste={this.handlePaste}
onPlay={this.handlePlay}
onRedo={this.handleRedo}
onReverse={this.effectFactory(effectTypes.REVERSE)}
onRobot={this.effectFactory(effectTypes.ROBOT)}
onSetTrimEnd={this.handleUpdateTrimEnd}
onSetTrimStart={this.handleUpdateTrimStart}
onSetTrim={this.handleUpdateTrim}
onSlower={this.effectFactory(effectTypes.SLOWER)}
onSofter={this.effectFactory(effectTypes.SOFTER)}
onStop={this.handleStopPlaying}
@@ -223,10 +445,7 @@ SoundEditor.propTypes = {
samples: PropTypes.instanceOf(Float32Array),
soundId: PropTypes.string,
soundIndex: PropTypes.number,
vm: PropTypes.shape({
updateSoundBuffer: PropTypes.func,
renameSound: PropTypes.func
})
vm: PropTypes.instanceOf(VM).isRequired
};
const mapStateToProps = (state, {soundIndex}) => {
+25 -3
View File
@@ -7,8 +7,8 @@ import AudioEngine from 'scratch-audio';
import LibraryComponent from '../components/library/library.jsx';
import soundIcon from '../components/asset-panel/icon--sound.svg';
import soundIconRtl from '../components/asset-panel/icon--sound-rtl.svg';
import soundIcon from '../components/library-item/lib-icon--sound.svg';
import soundIconRtl from '../components/library-item/lib-icon--sound-rtl.svg';
import soundLibraryContent from '../lib/libraries/sounds.json';
import soundTags from '../lib/libraries/sound-tags';
@@ -29,7 +29,9 @@ class SoundLibrary extends React.PureComponent {
bindAll(this, [
'handleItemSelected',
'handleItemMouseEnter',
'handleItemMouseLeave'
'handleItemMouseLeave',
'onStop',
'setStopHandler'
]);
/**
@@ -43,6 +45,11 @@ class SoundLibrary extends React.PureComponent {
* @type {Promise<SoundPlayer>}
*/
this.playingSoundPromise = null;
/**
* function to call when the sound ends
*/
this.handleStop = null;
}
componentDidMount () {
this.audioEngine = new AudioEngine();
@@ -51,10 +58,22 @@ class SoundLibrary extends React.PureComponent {
componentWillUnmount () {
this.stopPlayingSound();
}
onStop () {
if (this.playingSoundPromise !== null) {
this.playingSoundPromise.then(soundPlayer => soundPlayer.removeListener('stop', this.onStop));
if (this.handleStop) this.handleStop();
}
}
setStopHandler (func) {
this.handleStop = func;
}
stopPlayingSound () {
// Playback is queued, playing, or has played recently and finished
// normally.
if (this.playingSoundPromise !== null) {
// Forcing sound to stop, so stop listening for sound ending:
this.playingSoundPromise.then(soundPlayer => soundPlayer.removeListener('stop', this.onStop));
// Queued playback began playing before this method.
if (this.playingSoundPromise.isPlaying) {
// Fetch the player from the promise and stop playback soon.
@@ -102,6 +121,7 @@ class SoundLibrary extends React.PureComponent {
// Play the sound. Playing the sound will always come before a
// paired stop if the sound must stop early.
soundPlayer.play();
soundPlayer.addListener('stop', this.onStop);
// Set that the sound is playing. This affects the type of stop
// instruction given if the sound must stop early.
if (this.playingSoundPromise !== null) {
@@ -141,8 +161,10 @@ class SoundLibrary extends React.PureComponent {
return (
<LibraryComponent
showPlayButton
data={soundLibraryThumbnailData}
id="soundLibrary"
setStopHandler={this.setStopHandler}
tags={soundTags}
title={this.props.intl.formatMessage(messages.libraryTitle)}
onItemMouseEnter={this.handleItemMouseEnter}
+42 -31
View File
@@ -6,19 +6,19 @@ import {connect} from 'react-redux';
import {setHoveredSprite} from '../reducers/hovered-target';
import {updateAssetDrag} from '../reducers/asset-drag';
import storage from '../lib/storage';
import {getEventXY} from '../lib/touch-utils';
import VM from 'scratch-vm';
import getCostumeUrl from '../lib/get-costume-url';
import DragRecognizer from '../lib/drag-recognizer';
import {getEventXY} from '../lib/touch-utils';
import SpriteSelectorItemComponent from '../components/sprite-selector-item/sprite-selector-item.jsx';
const dragThreshold = 3; // Same as the block drag threshold
class SpriteSelectorItem extends React.PureComponent {
constructor (props) {
super(props);
bindAll(this, [
'getCostumeData',
'setRef',
'handleClick',
'handleDelete',
'handleDuplicate',
@@ -26,9 +26,22 @@ class SpriteSelectorItem extends React.PureComponent {
'handleMouseEnter',
'handleMouseLeave',
'handleMouseDown',
'handleMouseMove',
'handleMouseUp'
'handleDragEnd',
'handleDrag',
'handleTouchEnd'
]);
this.dragRecognizer = new DragRecognizer({
onDrag: this.handleDrag,
onDragEnd: this.handleDragEnd
});
}
componentDidMount () {
document.addEventListener('touchend', this.handleTouchEnd);
}
componentWillUnmount () {
document.removeEventListener('touchend', this.handleTouchEnd);
this.dragRecognizer.reset();
}
getCostumeData () {
if (this.props.costumeURL) return this.props.costumeURL;
@@ -36,12 +49,7 @@ class SpriteSelectorItem extends React.PureComponent {
return getCostumeUrl(this.props.asset);
}
handleMouseUp () {
this.initialOffset = null;
window.removeEventListener('mouseup', this.handleMouseUp);
window.removeEventListener('mousemove', this.handleMouseMove);
window.removeEventListener('touchend', this.handleMouseUp);
window.removeEventListener('touchmove', this.handleMouseMove);
handleDragEnd () {
if (this.props.dragging) {
this.props.onDrag({
img: null,
@@ -55,29 +63,26 @@ class SpriteSelectorItem extends React.PureComponent {
this.noClick = false;
});
}
handleMouseMove (e) {
const currentOffset = getEventXY(e);
const dx = currentOffset.x - this.initialOffset.x;
const dy = currentOffset.y - this.initialOffset.y;
if (Math.sqrt((dx * dx) + (dy * dy)) > dragThreshold) {
this.props.onDrag({
img: this.getCostumeData(),
currentOffset: currentOffset,
dragging: true,
dragType: this.props.dragType,
index: this.props.index,
payload: this.props.dragPayload
});
this.noClick = true;
handleDrag (currentOffset) {
this.props.onDrag({
img: this.getCostumeData(),
currentOffset: currentOffset,
dragging: true,
dragType: this.props.dragType,
index: this.props.index,
payload: this.props.dragPayload
});
this.noClick = true;
}
handleTouchEnd (e) {
const {x, y} = getEventXY(e);
const {top, left, bottom, right} = this.ref.getBoundingClientRect();
if (x >= left && x <= right && y >= top && y <= bottom) {
this.handleMouseEnter();
}
e.preventDefault();
}
handleMouseDown (e) {
this.initialOffset = getEventXY(e);
window.addEventListener('mouseup', this.handleMouseUp);
window.addEventListener('mousemove', this.handleMouseMove);
window.addEventListener('touchend', this.handleMouseUp);
window.addEventListener('touchmove', this.handleMouseMove);
this.dragRecognizer.start(e);
}
handleClick (e) {
e.preventDefault();
@@ -103,6 +108,10 @@ class SpriteSelectorItem extends React.PureComponent {
handleMouseEnter () {
this.props.dispatchSetHoveredSprite(this.props.id);
}
setRef (component) {
// Access the DOM node using .elem because it is going through ContextMenuTrigger
this.ref = component && component.elem;
}
render () {
const {
/* eslint-disable no-unused-vars */
@@ -122,7 +131,9 @@ class SpriteSelectorItem extends React.PureComponent {
} = this.props;
return (
<SpriteSelectorItemComponent
componentRef={this.setRef}
costumeURL={this.getCostumeData()}
preventContextMenu={this.dragRecognizer.gestureInProgress()}
onClick={this.handleClick}
onDeleteButtonClick={onDeleteButtonClick ? this.handleDelete : null}
onDuplicateButtonClick={onDuplicateButtonClick ? this.handleDuplicate : null}
+38 -11
View File
@@ -15,6 +15,7 @@ import ThrottledPropertyHOC from '../lib/throttled-property-hoc.jsx';
import {emptyCostume} from '../lib/empty-assets';
import sharedMessages from '../lib/shared-messages';
import {fetchCode} from '../lib/backpack-api';
import {getEventXY} from '../lib/touch-utils';
import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx';
@@ -46,11 +47,26 @@ class StageSelector extends React.Component {
'handleBackdropUpload',
'handleMouseEnter',
'handleMouseLeave',
'handleTouchEnd',
'handleDrop',
'setFileInput'
'setFileInput',
'setRef'
]);
}
addBackdropFromLibraryItem (item) {
componentDidMount () {
document.addEventListener('touchend', this.handleTouchEnd);
}
componentWillUnmount () {
document.removeEventListener('touchend', this.handleTouchEnd);
}
handleTouchEnd (e) {
const {x, y} = getEventXY(e);
const {top, left, bottom, right} = this.ref.getBoundingClientRect();
if (x >= left && x <= right && y >= top && y <= bottom) {
this.handleMouseEnter();
}
}
addBackdropFromLibraryItem (item, shouldActivateTab = true) {
const vmBackdrop = {
name: item.name,
md5: item.md5,
@@ -59,25 +75,30 @@ class StageSelector extends React.Component {
bitmapResolution: item.info.length > 2 ? item.info[2] : 1,
skinId: null
};
this.handleNewBackdrop(vmBackdrop);
this.handleNewBackdrop(vmBackdrop, shouldActivateTab);
}
handleClick () {
this.props.onSelect(this.props.id);
}
handleNewBackdrop (backdrops_) {
handleNewBackdrop (backdrops_, shouldActivateTab = true) {
const backdrops = Array.isArray(backdrops_) ? backdrops_ : [backdrops_];
return Promise.all(backdrops.map(backdrop =>
this.props.vm.addBackdrop(backdrop.md5, backdrop)
)).then(() =>
this.props.onActivateTab(COSTUMES_TAB_INDEX)
);
)).then(() => {
if (shouldActivateTab) {
return this.props.onActivateTab(COSTUMES_TAB_INDEX);
}
});
}
handleSurpriseBackdrop () {
handleSurpriseBackdrop (e) {
e.stopPropagation(); // Prevent click from falling through to selecting stage.
// @todo should this not add a backdrop you already have?
const item = backdropLibraryContent[Math.floor(Math.random() * backdropLibraryContent.length)];
this.addBackdropFromLibraryItem(item);
this.addBackdropFromLibraryItem(item, false);
}
handleEmptyBackdrop () {
handleEmptyBackdrop (e) {
e.stopPropagation(); // Prevent click from falling through to stage selector, select it manually below
this.props.vm.setEditingTarget(this.props.id);
this.handleNewBackdrop(emptyCostume(this.props.intl.formatMessage(sharedMessages.backdrop, {index: 1})));
}
handleBackdropUpload (e) {
@@ -85,6 +106,7 @@ class StageSelector extends React.Component {
this.props.onShowImporting();
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
costumeUpload(buffer, fileType, storage, vmCostumes => {
this.props.vm.setEditingTarget(this.props.id);
vmCostumes.forEach((costume, i) => {
costume.name = `${fileName}${i ? i + 1 : ''}`;
});
@@ -96,7 +118,8 @@ class StageSelector extends React.Component {
}, this.props.onCloseImporting);
}, this.props.onCloseImporting);
}
handleFileUploadClick () {
handleFileUploadClick (e) {
e.stopPropagation(); // Prevent click from selecting the stage, that is handled manually in backdrop upload
this.fileInput.click();
}
handleMouseEnter () {
@@ -130,12 +153,16 @@ class StageSelector extends React.Component {
setFileInput (input) {
this.fileInput = input;
}
setRef (ref) {
this.ref = ref;
}
render () {
const componentProps = omit(this.props, [
'asset', 'dispatchSetHoveredSprite', 'id', 'intl',
'onActivateTab', 'onSelect', 'onShowImporting', 'onCloseImporting']);
return (
<DroppableThrottledStage
componentRef={this.setRef}
fileInputRef={this.setFileInput}
onBackdropFileUpload={this.handleBackdropUpload}
onBackdropFileUploadClick={this.handleFileUploadClick}
+11 -6
View File
@@ -105,7 +105,10 @@ class TargetPane extends React.Component {
}
}
handleSurpriseSpriteClick () {
const item = spriteLibraryContent[Math.floor(Math.random() * spriteLibraryContent.length)];
const surpriseSprites = spriteLibraryContent.filter(sprite =>
(sprite.tags.indexOf('letters') === -1) && (sprite.tags.indexOf('numbers') === -1)
);
const item = surpriseSprites[Math.floor(Math.random() * surpriseSprites.length)];
randomizeSpritePosition(item);
this.props.vm.addSprite(JSON.stringify(item.json))
.then(this.handleActivateBlocksTab);
@@ -138,11 +141,13 @@ class TargetPane extends React.Component {
this.props.onShowImporting();
handleFileUpload(e.target, (buffer, fileType, fileName, fileIndex, fileCount) => {
spriteUpload(buffer, fileType, fileName, storage, newSprite => {
this.handleNewSprite(newSprite).then(() => {
if (fileIndex === fileCount - 1) {
this.props.onCloseImporting();
}
});
this.handleNewSprite(newSprite)
.then(() => {
if (fileIndex === fileCount - 1) {
this.props.onCloseImporting();
}
})
.catch(this.props.onCloseImporting);
}, this.props.onCloseImporting);
}, this.props.onCloseImporting);
}
+9 -3
View File
@@ -62,10 +62,16 @@ class TipsLibrary extends React.PureComponent {
}
render () {
const decksLibraryThumbnailData = Object.keys(decksLibraryContent)
.filter(id =>
.filter(id => {
if (notScratchDesktop()) return true; // Do not filter anything in online editor
const deck = decksLibraryContent[id];
// Scratch Desktop doesn't want tutorials with `requiredProjectId`
notScratchDesktop() || !decksLibraryContent[id].hasOwnProperty('requiredProjectId')
)
if (deck.hasOwnProperty('requiredProjectId')) return false;
// Scratch Desktop should not load tutorials that are _only_ videos
if (deck.steps.filter(s => s.title).length === 0) return false;
// Allow any other tutorials
return true;
})
.map(id => ({
rawURL: decksLibraryContent[id].img,
id: id,
+73 -15
View File
@@ -1,6 +1,8 @@
import EchoEffect from './effects/echo-effect.js';
import RobotEffect from './effects/robot-effect.js';
import VolumeEffect from './effects/volume-effect.js';
import FadeEffect from './effects/fade-effect.js';
import MuteEffect from './effects/mute-effect.js';
const effectTypes = {
ROBOT: 'robot',
@@ -9,32 +11,56 @@ const effectTypes = {
SOFTER: 'lower',
FASTER: 'faster',
SLOWER: 'slower',
ECHO: 'echo'
ECHO: 'echo',
FADEIN: 'fade in',
FADEOUT: 'fade out',
MUTE: 'mute'
};
class AudioEffects {
static get effectTypes () {
return effectTypes;
}
constructor (buffer, name) {
constructor (buffer, name, trimStart, trimEnd) {
this.trimStartSeconds = (trimStart * buffer.length) / buffer.sampleRate;
this.trimEndSeconds = (trimEnd * buffer.length) / buffer.sampleRate;
this.adjustedTrimStartSeconds = this.trimStartSeconds;
this.adjustedTrimEndSeconds = this.trimEndSeconds;
// Some effects will modify the playback rate and/or number of samples.
// Need to precompute those values to create the offline audio context.
const pitchRatio = Math.pow(2, 4 / 12); // A major third
let sampleCount = buffer.length;
let playbackRate = 1;
const affectedSampleCount = Math.floor((this.trimEndSeconds - this.trimStartSeconds) *
buffer.sampleRate);
let adjustedAffectedSampleCount = affectedSampleCount;
const unaffectedSampleCount = sampleCount - affectedSampleCount;
this.playbackRate = 1;
switch (name) {
case effectTypes.ECHO:
sampleCount = buffer.length + (0.25 * 3 * buffer.sampleRate);
sampleCount = Math.max(sampleCount,
Math.floor((this.trimEndSeconds + EchoEffect.TAIL_SECONDS) * buffer.sampleRate));
break;
case effectTypes.FASTER:
playbackRate = pitchRatio;
sampleCount = Math.floor(buffer.length / playbackRate);
this.playbackRate = pitchRatio;
adjustedAffectedSampleCount = Math.floor(affectedSampleCount / this.playbackRate);
sampleCount = unaffectedSampleCount + adjustedAffectedSampleCount;
break;
case effectTypes.SLOWER:
playbackRate = 1 / pitchRatio;
sampleCount = Math.floor(buffer.length / playbackRate);
this.playbackRate = 1 / pitchRatio;
adjustedAffectedSampleCount = Math.floor(affectedSampleCount / this.playbackRate);
sampleCount = unaffectedSampleCount + adjustedAffectedSampleCount;
break;
}
const durationSeconds = sampleCount / buffer.sampleRate;
this.adjustedTrimEndSeconds = this.trimStartSeconds +
(adjustedAffectedSampleCount / buffer.sampleRate);
this.adjustedTrimStart = this.adjustedTrimStartSeconds / durationSeconds;
this.adjustedTrimEnd = this.adjustedTrimEndSeconds / durationSeconds;
if (window.OfflineAudioContext) {
this.audioContext = new window.OfflineAudioContext(1, sampleCount, buffer.sampleRate);
} else {
@@ -52,8 +78,17 @@ class AudioEffects {
const newBuffer = this.audioContext.createBuffer(1, buffer.length, buffer.sampleRate);
const newBufferData = newBuffer.getChannelData(0);
const bufferLength = buffer.length;
const startSamples = Math.floor(this.trimStartSeconds * buffer.sampleRate);
const endSamples = Math.floor(this.trimEndSeconds * buffer.sampleRate);
let counter = 0;
for (let i = 0; i < bufferLength; i++) {
newBufferData[i] = originalBufferData[bufferLength - i - 1];
if (i >= startSamples && i < endSamples) {
newBufferData[i] = originalBufferData[endSamples - counter - 1];
counter++;
} else {
newBufferData[i] = originalBufferData[i];
}
}
this.buffer = newBuffer;
} else {
@@ -63,7 +98,6 @@ class AudioEffects {
this.source = this.audioContext.createBufferSource();
this.source.buffer = this.buffer;
this.source.playbackRate.value = playbackRate;
this.name = name;
}
process (done) {
@@ -71,17 +105,38 @@ class AudioEffects {
let input;
let output;
switch (this.name) {
case effectTypes.FASTER:
case effectTypes.SLOWER:
this.source.playbackRate.setValueAtTime(this.playbackRate, this.adjustedTrimStartSeconds);
this.source.playbackRate.setValueAtTime(1.0, this.adjustedTrimEndSeconds);
break;
case effectTypes.LOUDER:
({input, output} = new VolumeEffect(this.audioContext, 1.25));
({input, output} = new VolumeEffect(this.audioContext, 1.25,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.SOFTER:
({input, output} = new VolumeEffect(this.audioContext, 0.75));
({input, output} = new VolumeEffect(this.audioContext, 0.75,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.ECHO:
({input, output} = new EchoEffect(this.audioContext, 0.25));
({input, output} = new EchoEffect(this.audioContext,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.ROBOT:
({input, output} = new RobotEffect(this.audioContext, 0.25));
({input, output} = new RobotEffect(this.audioContext,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.FADEIN:
({input, output} = new FadeEffect(this.audioContext, true,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.FADEOUT:
({input, output} = new FadeEffect(this.audioContext, false,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
case effectTypes.MUTE:
({input, output} = new MuteEffect(this.audioContext,
this.adjustedTrimStartSeconds, this.adjustedTrimEndSeconds));
break;
}
@@ -96,7 +151,10 @@ class AudioEffects {
this.source.start();
this.audioContext.startRendering();
this.audioContext.oncomplete = done;
this.audioContext.oncomplete = ({renderedBuffer}) => {
done(renderedBuffer, this.adjustedTrimStart, this.adjustedTrimEnd);
};
}
}
+14 -14
View File
@@ -1,12 +1,12 @@
import 'get-float-time-domain-data';
import getUserMedia from 'get-user-media-promise';
import SharedAudioContext from './shared-audio-context.js';
import {computeRMS} from './audio-util.js';
import {computeRMS, computeChunkedRMS} from './audio-util.js';
class AudioRecorder {
constructor () {
this.audioContext = new SharedAudioContext();
this.bufferLength = 1024;
this.bufferLength = 8192;
this.userMediaStream = null;
this.mediaStreamSource = null;
@@ -51,7 +51,7 @@ class AudioRecorder {
this.userMediaStream = userMediaStream;
this.mediaStreamSource = this.audioContext.createMediaStreamSource(userMediaStream);
this.sourceNode = this.audioContext.createGain();
this.scriptProcessorNode = this.audioContext.createScriptProcessor(this.bufferLength, 2, 2);
this.scriptProcessorNode = this.audioContext.createScriptProcessor(this.bufferLength, 1, 1);
this.scriptProcessorNode.onaudioprocess = processEvent => {
if (this.recording && !this.disposed) {
@@ -68,9 +68,9 @@ class AudioRecorder {
const update = () => {
if (this.disposed) return;
requestAnimationFrame(update);
this.analyserNode.getFloatTimeDomainData(dataArray);
onUpdate(computeRMS(dataArray));
requestAnimationFrame(update);
};
requestAnimationFrame(update);
@@ -83,7 +83,16 @@ class AudioRecorder {
}
stop () {
const chunkLevels = this.buffers.map(buffer => computeRMS(buffer));
const buffer = new Float32Array(this.buffers.length * this.bufferLength);
let offset = 0;
for (let i = 0; i < this.buffers.length; i++) {
const bufferChunk = this.buffers[i];
buffer.set(bufferChunk, offset);
offset += bufferChunk.length;
}
const chunkLevels = computeChunkedRMS(buffer);
const maxRMS = Math.max.apply(null, chunkLevels);
const threshold = maxRMS / 8;
@@ -105,15 +114,6 @@ class AudioRecorder {
trimEnd = 1;
}
const buffer = new Float32Array(this.buffers.length * this.bufferLength);
let offset = 0;
for (let i = 0; i < this.buffers.length; i++) {
const bufferChunk = this.buffers[i];
buffer.set(bufferChunk, offset);
offset += bufferChunk.length;
}
return {
levels: chunkLevels,
samples: buffer,
+37
View File
@@ -1,3 +1,5 @@
import WavEncoder from 'wav-encoder';
const SOUND_BYTE_LIMIT = 10 * 1000 * 1000; // 10mb
const computeRMS = function (samples, scaling = 0.55) {
@@ -23,8 +25,43 @@ const computeChunkedRMS = function (samples, chunkSize = 1024) {
return chunkLevels;
};
const encodeAndAddSoundToVM = function (vm, samples, sampleRate, name, callback) {
WavEncoder.encode({
sampleRate: sampleRate,
channelData: [samples]
}).then(wavBuffer => {
const vmSound = {
format: '',
dataFormat: 'wav',
rate: sampleRate,
sampleCount: samples.length
};
// Create an asset from the encoded .wav and get resulting md5
const storage = vm.runtime.storage;
vmSound.asset = storage.createAsset(
storage.AssetType.Sound,
storage.DataFormat.WAV,
new Uint8Array(wavBuffer),
null,
true // generate md5
);
vmSound.assetId = vmSound.asset.assetId;
// update vmSound object with md5 property
vmSound.md5 = `${vmSound.assetId}.${vmSound.dataFormat}`;
// The VM will update the sound name to a fresh name
vmSound.name = name;
vm.addSound(vmSound).then(() => {
if (callback) callback();
});
});
};
export {
computeRMS,
computeChunkedRMS,
encodeAndAddSoundToVM,
SOUND_BYTE_LIMIT
};
+12 -4
View File
@@ -1,15 +1,23 @@
class EchoEffect {
constructor (audioContext, delayTime) {
static get DELAY_TIME () {
return 0.25;
}
static get TAIL_SECONDS () {
return 0.75;
}
constructor (audioContext, startTime, endTime) {
this.audioContext = audioContext;
this.delayTime = delayTime;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.effectInput = this.audioContext.createGain();
this.effectInput.gain.value = 0.75;
this.effectInput.gain.value = 0;
this.effectInput.gain.setValueAtTime(0.75, startTime);
this.effectInput.gain.setValueAtTime(0, endTime);
this.delay = this.audioContext.createDelay(1);
this.delay.delayTime.value = delayTime;
this.delay.delayTime.value = EchoEffect.DELAY_TIME;
this.decay = this.audioContext.createGain();
this.decay.gain.value = 0.3;
+27
View File
@@ -0,0 +1,27 @@
class FadeEffect {
constructor (audioContext, fadeIn, startSeconds, endSeconds) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.gain = this.audioContext.createGain();
this.gain.gain.setValueAtTime(1, 0);
if (fadeIn) {
this.gain.gain.setValueAtTime(0, startSeconds);
this.gain.gain.linearRampToValueAtTime(1, endSeconds);
} else {
this.gain.gain.setValueAtTime(1, startSeconds);
this.gain.gain.linearRampToValueAtTime(0, endSeconds);
}
this.gain.gain.setValueAtTime(1, endSeconds);
this.input.connect(this.gain);
this.gain.connect(this.output);
}
}
export default FadeEffect;
+22
View File
@@ -0,0 +1,22 @@
class MuteEffect {
constructor (audioContext, startSeconds, endSeconds) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.gain = this.audioContext.createGain();
// Smoothly ramp the gain down before the start time, and up after the end time.
this.rampLength = 0.001;
this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength));
this.gain.gain.linearRampToValueAtTime(0, startSeconds);
this.gain.gain.setValueAtTime(0, endSeconds);
this.gain.gain.linearRampToValueAtTime(1.0, endSeconds + this.rampLength);
this.input.connect(this.gain);
this.gain.connect(this.output);
}
}
export default MuteEffect;
+20 -4
View File
@@ -1,9 +1,20 @@
class RobotEffect {
constructor (audioContext) {
constructor (audioContext, startTime, endTime) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.passthrough = this.audioContext.createGain();
this.effectInput = this.audioContext.createGain();
this.passthrough.gain.value = 1;
this.effectInput.gain.value = 0;
this.passthrough.gain.setValueAtTime(0, startTime);
this.passthrough.gain.setValueAtTime(1, endTime);
this.effectInput.gain.setValueAtTime(1, startTime);
this.effectInput.gain.setValueAtTime(0, endTime);
// Ring modulator inspired by BBC Dalek voice
// http://recherche.ircam.fr/pub/dafx11/Papers/66_e.pdf
@@ -70,8 +81,13 @@ class RobotEffect {
biquadFilter.frequency.value = 1000;
biquadFilter.gain.value = 1.25;
this.input.connect(vcInverter1);
this.input.connect(vcDiode4);
this.input.connect(this.effectInput);
this.input.connect(this.passthrough);
this.passthrough.connect(this.output);
this.effectInput.connect(vcInverter1);
this.effectInput.connect(vcDiode4);
vcInverter1.connect(vcDiode3);
@@ -91,7 +107,7 @@ class RobotEffect {
vcDiode3.connect(compressor);
vcDiode4.connect(compressor);
this.input.connect(biquadFilter);
this.effectInput.connect(biquadFilter);
biquadFilter.connect(compressor);
compressor.connect(this.output);
+8 -2
View File
@@ -1,12 +1,18 @@
class VolumeEffect {
constructor (audioContext, volume) {
constructor (audioContext, volume, startSeconds, endSeconds) {
this.audioContext = audioContext;
this.input = this.audioContext.createGain();
this.output = this.audioContext.createGain();
this.gain = this.audioContext.createGain();
this.gain.gain.value = volume;
// Smoothly ramp the gain up before the start time, and down after the end time.
this.rampLength = 0.01;
this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength));
this.gain.gain.exponentialRampToValueAtTime(volume, startSeconds);
this.gain.gain.setValueAtTime(volume, endSeconds);
this.gain.gain.exponentialRampToValueAtTime(1.0, endSeconds + this.rampLength);
this.input.connect(this.gain);
this.gain.connect(this.output);
+11 -6
View File
@@ -3,15 +3,20 @@ const jpegThumbnail = dataUrl => new Promise((resolve, reject) => {
image.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// TODO we may want to draw to a smaller, fixed size to optimize file size
canvas.width = image.width;
canvas.height = image.height;
const maxDimension = 96; // 3x the maximum displayed size of 32px
if (image.height > image.width) {
canvas.height = maxDimension;
canvas.width = (maxDimension / image.height) * image.width;
} else {
canvas.width = maxDimension;
canvas.height = (maxDimension / image.width) * image.height;
}
ctx.fillStyle = 'white'; // Create white background, since jpeg doesn't have transparency
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0);
// TODO we can play with the `quality` option here to optimize file size
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL('image/jpeg', 0.92 /* quality */)); // Default quality is 0.92
};
image.onerror = err => {
@@ -1,42 +0,0 @@
<svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<g id="Layer_3">
<path fill="#FAA51D" stroke="#000000" d="M22.462,79.039c-2.415-0.451-5.304-1.309-7.742-3.503&#xD;&#xA;&#x9;&#x9;C9.268,70.629,7.526,62.535,3.672,64.622c-3.856,2.088-3.782,15.165,8.353,19.194c4.182,1.391,7.998,1.396,11.091,1.312&#xD;&#xA;&#x9;&#x9;c0.811-0.025,7.717-0.654,10.079-4.074c2.361-3.42,0.719-4.272-0.09-4.744C32.295,75.838,25.878,79.677,22.462,79.039z"/>
<path fill="#FFFFFF" d="M4.236,64.877c-1.989,0.613-3.075,4.998-2.076,8.484c0.998,3.49,2.634,5.022,3.863,6.398&#xD;&#xA;&#x9;&#x9;c1.528,1.038-0.72-2.402,1.361-4.15c2.075-1.744,5.733-0.914,5.733-0.914s-2.909-3.987-4.57-6.396&#xD;&#xA;&#x9;&#x9;C6.975,65.988,6.359,64.375,4.236,64.877z"/>
</g>
<g>
<path fill="#FAA51D" d="M38.217,86.756c0,0-8.832,6.2-17.071,8.412l0.086,0.215c1.247,1.824,5.87,7.497-0.334,9.496&#xD;&#xA;&#x9;&#x9;c-5.333,1.717-15.12-13.104-10.821-15.902c2.626-1.713,4.892-0.252,4.892-0.252s3.474-1.07,6.001-2.345&#xD;&#xA;&#x9;&#x9;c4.303-2.161,5.784-3.453,5.784-3.453s4.184-4.306,6.856-4.137C36.281,78.96,41.669,83.504,38.217,86.756z"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.232,95.383c1.247,1.824,5.87,7.497-0.334,9.496&#xD;&#xA;&#x9;&#x9;c-5.333,1.717-15.329-13.344-11.03-16.145c2.626-1.713,5.101-0.01,5.101-0.01s3.474-1.072,6.001-2.348&#xD;&#xA;&#x9;&#x9;c4.303-2.161,5.784-3.453,5.784-3.453"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M38.217,86.756c0,0-10.123,7.107-18.804,8.819"/>
</g>
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.169,74.885c0,0,1.235,0.165,4.744,3.676&#xD;&#xA;&#x9;c3.509,3.508,6.026,2.16,8.911,0.724c2.877-1.443,10.537-6.126,6.49-9.817c-4.049-3.688-6.207,1.146-9.715,2.405&#xD;&#xA;&#x9;c-3.512,1.26-5.061-2.487-6.858-4.287c-0.589-0.593-1.188-1.099-1.729-1.505c0,0-0.971-0.76-1.906,2.79&#xD;&#xA;&#x9;C51.172,72.412,50.162,73.415,52.169,74.885z"/>
<g id="Layer_2_1_">
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M46.753,82.012c1.188-0.912,2.397-2.402,3.951-4.713&#xD;&#xA;&#x9;&#x9;c1.296-1.927,2.7-5.578,2.7-5.578c0.875-2.521,1.934-6.576-1.902-7.296c-1.553-0.291-4.079-0.098-7.67-0.776&#xD;&#xA;&#x9;&#x9;c-3.593-0.681-6.798-2.522-9.517,2.233c-2.718,4.757-9.59,8.271-1.056,16.563c0,0,4.901,3.842,10.764,9.639&#xD;&#xA;&#x9;&#x9;c4.831,4.775,12.045,10.602,12.045,10.602s18.972,2.188,19.535-0.693c1.922-9.79-14.777-6.911-14.777-6.911&#xD;&#xA;&#x9;&#x9;s-4.605-3.933-6.725-5.794c-3.478-3.059-11.125-10.771-11.125-10.771"/>
<path fill="#FFFFFF" d="M51.253,75.434c0,0,2.47-2.66-2.469-5.317c-4.939-2.657-7.213-0.017-8.739,1.521&#xD;&#xA;&#x9;&#x9;c-2.644,2.655,3.443,6.611,3.443,6.611l3.176,3.204c0,0,1.738-1.647,2.499-2.979C50.036,77.26,51.253,75.434,51.253,75.434"/>
</g>
<g id="Layer_8"/>
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M29.926,73.218c0.749-0.571,2.889-2.202,4.854-3.657&#xD;&#xA;&#x9;c2.428-1.799,6.117-5.849,1.077-7.646c-5.04-1.801-7.507,1.604-11.519,4.946c-2.159,1.801-5.308,2.699-4.319,6.209&#xD;&#xA;&#x9;c0.993,3.511,4.862,13.408,11.789,10.17c6.929-3.239-1.799-9.18-3.06-11.157"/>
<g id="Layer_2">
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.709,14.156c-1.54-0.143-4.75-0.316-6.518-0.231&#xD;&#xA;&#x9;&#x9;c-4.728,0.225-9.224,1.928-9.224,1.928L23.949,7.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224&#xD;&#xA;&#x9;&#x9;c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.729,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823&#xD;&#xA;&#x9;&#x9;s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L62.822,5.647l-7.452,7.204L52.709,14.156z"/>
<path fill="#FFFFFF" d="M76.42,35.066l-2.482-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277&#xD;&#xA;&#x9;&#x9;L41.46,41.17c0,0-8.437,0.928-8.739,6.081C32.048,58.704,46.1,63.479,51.425,63.783c2.905,0.167,8.235-0.338,12.277-1.141&#xD;&#xA;&#x9;&#x9;c17.752-3.234,22.551-13.919,21.31-19.635c-1.242-5.714-7.978-7.196-7.978-7.196L76.42,35.066z"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M10.673,46.155c0,0,4.107,0.374,5.974,0.268&#xD;&#xA;&#x9;&#x9;c1.865-0.107,5.492-0.587,5.492-0.587"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.656,40.671c0,0,4.549-0.743,6.859-1.549&#xD;&#xA;&#x9;&#x9;c2.715-0.942,4.543-2.545,4.543-2.545"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M22.337,41.909c0,0-2.384-1.777-6.117-3.43&#xD;&#xA;&#x9;&#x9;c-4.134-1.831-6.405-2.303-6.405-2.303"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M82.117,46.622c0,0,2.726,1.104,5.533,1.385&#xD;&#xA;&#x9;&#x9;c2.77,0.276,4.646,0.11,4.646,0.11"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M52.35,14.212&#xD;&#xA;&#x9;&#x9;c2.84,0.7,3.887,1.469,3.887,1.469"/>
<line fill="none" stroke="#000000" x1="33.898" y1="13.684" x2="39.956" y2="18.042"/>
</g>
<g id="Layer_5">
<path fill="#FFFFFF" stroke="#231F20" d="M71.84,25.366c2.924,4.479,3.033,9.591,0.242,11.415&#xD;&#xA;&#x9;&#x9;c-2.793,1.825-7.426-0.332-10.354-4.813c-2.933-4.48-3.037-9.589-0.244-11.415C64.275,18.73,68.913,20.884,71.84,25.366z"/>
<path fill="#231F20" d="M71.089,32.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956&#xD;&#xA;&#x9;&#x9;c0-1.08,0.81-1.958,1.803-1.958C70.287,30.564,71.089,31.442,71.089,32.522"/>
</g>
<g id="Layer_7">
<path fill="#FFFFFF" stroke="#231F20" d="M47.867,28.619c2.926,4.48,2.619,9.862-0.681,12.015&#xD;&#xA;&#x9;&#x9;c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C39.891,22.253,44.938,24.137,47.867,28.619z"/>
<path fill="#231F20" d="M46.079,34.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957&#xD;&#xA;&#x9;&#x9;c0-1.08,0.811-1.957,1.803-1.957C45.274,32.55,46.079,33.427,46.079,34.507"/>
</g>
<path fill="#5E4A42" stroke="#000000" d="M59.766,37.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345&#xD;&#xA;&#x9;c-1.283,0.142-6.125-2.353-6.195-3.919C55.206,37.355,58.055,37.926,59.766,37.926z"/>
<g id="Layer_4">
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M46.774,45.235c0,0,10.347,3.054,14.217,3.897&#xD;&#xA;&#x9;&#x9;c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C45.176,55.863,45.345,49.975,46.774,45.235z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

@@ -1,37 +0,0 @@
<svg version="1.1" id="cat" x="0px" y="0px" width="95px" height="111px" viewBox="0 0 95 111" enable-background="new 0 0 95 111" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<g id="Layer_8">
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M23.754,82.506c-2.871-1.402-10.511-2.518-14.771-6.695&#xD;&#xA;&#x9;&#x9;c-9.431-9.246-3.824-21.732,0.704-21.797c3.611-0.061,3.363,5.482,4.622,11.145c0.875,3.939,1.673,6.988,6.407,9.674&#xD;&#xA;&#x9;&#x9;c4.735,2.688,6.918,2.916,6.918,2.916s3.258,0.791,1.596,2.562C27.568,82.076,23.754,82.506,23.754,82.506z"/>
<path fill="#FFFFFF" d="M14.291,66.369c-0.115-0.459-0.222-0.928-0.33-1.413c-1.172-5.271-0.941-10.432-4.303-10.377&#xD;&#xA;&#x9;&#x9;c-3.433,0.05-7.53,7.771-4.19,15.285c0.193-0.143,0.399-0.336,0.569-0.588c0.451-0.664,0.821-2.969,2.654-3.688&#xD;&#xA;&#x9;&#x9;C9.989,65.082,12.778,65.869,14.291,66.369z"/>
</g>
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M52.543,61.473c0,0,1.455,2.342,1.519,4.969&#xD;&#xA;&#x9;c0.065,2.723-0.351,6.984,1.804,8.158c3.381,1.842,9.959-0.463,10.174,5.191c0.211,5.652-10.152,3.623-13.299,2.547&#xD;&#xA;&#x9;c-2.695-0.926-4.559-1.758-5.18-5.182c0,0-3.764-13.969-0.196-15.59C50.933,59.945,52.543,61.473,52.543,61.473z"/>
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M14.61,89.584c0,0,1.504,10.391-5.922,8.387&#xD;&#xA;&#x9;c-5.586-1.51-5.705-20.68-0.406-20.691c3.237-0.006,4.425,2.859,4.425,2.859s4.385,4.812,6.95,4.312&#xD;&#xA;&#x9;c2.565-0.502,7.978-6.703,7.978-6.703s2.875-2.539,6.146,0.023c3.271,2.561,1.521,6.178,1.521,6.178s-6.07,7.072-11.014,8.072&#xD;&#xA;&#x9;c-4.945,1.002-9.375-2.568-11.58-3.451"/>
<g id="Layer_6">
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M42.435,76.238c0,0,4.866,4.062,8.166,7.52&#xD;&#xA;&#x9;&#x9;c4.384,4.592-3.339,15.039-3.339,15.039s11.234,6.467,2.998,11c-2.657,1.467-15.661-13.225-15.661-13.225s7.979-7.475,4.972-10.172&#xD;&#xA;&#x9;&#x9;c-4.964-4.449-10.771-4.959-11.6-9.824c-0.502-2.957,0.613-4.502,2.658-7.053c1.354-1.689,2.084-3.182,2.084-3.182&#xD;&#xA;&#x9;&#x9;s1.135-7.238,4.289-8.254c3.153-1.014,8.842,0.057,10.758,0.395c1.914,0.338,6.645,1.877,4.617,9.922c0,0-1.451,3.771-2.789,5.762&#xD;&#xA;&#x9;&#x9;c-1.604,2.389-2.854,3.924-4.08,4.867"/>
<path fill="#FFFFFF" d="M50.182,72.631c0,0,2.55-2.748-2.551-5.494c-5.102-2.746-7.449-0.016-9.024,1.572&#xD;&#xA;&#x9;&#x9;c-2.731,2.742,3.334,7.453,3.334,7.453l3.383,2.564c0,0,1.916-1.582,2.701-2.957C48.807,74.396,50.182,72.631,50.182,72.631"/>
</g>
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M24.428,65.395c0.939,0.057,3.625,0.213,6.064,0.391&#xD;&#xA;&#x9;c3.014,0.217,8.451-0.451,5.801-5.098c-2.649-4.65-6.74-3.674-11.961-3.754c-2.812-0.043-5.785-1.412-7.322,1.895&#xD;&#xA;&#x9;c-1.534,3.312-5.051,13.338,2.313,15.396c7.366,2.057,4.618-8.137,4.95-10.457"/>
<g id="Layer_2">
<path fill="#FAA51D" stroke="#231F20" stroke-width="1.2" d="M51.709,10.156c-1.54-0.143-4.75-0.316-6.518-0.231&#xD;&#xA;&#x9;&#x9;c-4.728,0.225-9.224,1.928-9.224,1.928L22.949,3.357l2.235,18.906c0.646-0.782-10.555,12.804-3.479,24.224&#xD;&#xA;&#x9;&#x9;c7.08,11.426,22.233,16.518,40.988,12.792c18.755-3.728,23.229-14.531,21.986-20.246c-1.242-5.714-8.322-7.823-8.322-7.823&#xD;&#xA;&#x9;&#x9;s-0.09-4.48-3.328-9.97c-1.926-3.268-8.348-8.041-8.348-8.041L61.822,1.647L54.37,8.851L51.709,10.156z"/>
<path fill="#FFFFFF" d="M75.42,31.066l-2.483-2.064l-9.115,2.661c0,0,0,3.419-4.367,4.367c-4.37,0.951-11.211-2.277-11.211-2.277&#xD;&#xA;&#x9;&#x9;l-7.784,3.417c0,0-8.437,0.928-8.739,6.081C31.048,54.704,45.1,59.478,50.425,59.783c2.905,0.167,8.235-0.337,12.277-1.14&#xD;&#xA;&#x9;&#x9;c17.752-3.235,22.551-13.92,21.309-19.635c-1.242-5.714-7.977-7.196-7.977-7.196L75.42,31.066z"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M9.673,42.155c0,0,4.107,0.374,5.974,0.268&#xD;&#xA;&#x9;&#x9;c1.865-0.107,5.492-0.587,5.492-0.587"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M80.656,36.671c0,0,4.549-0.743,6.859-1.549&#xD;&#xA;&#x9;&#x9;c2.715-0.942,4.543-2.545,4.543-2.545"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M21.337,37.909c0,0-2.384-1.777-6.117-3.43&#xD;&#xA;&#x9;&#x9;c-4.134-1.831-6.405-2.303-6.405-2.303"/>
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M81.117,42.622c0,0,2.726,1.104,5.533,1.385&#xD;&#xA;&#x9;&#x9;c2.77,0.276,4.647,0.11,4.647,0.11"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" d="M51.349,10.212&#xD;&#xA;&#x9;&#x9;c2.84,0.7,3.888,1.469,3.888,1.469"/>
<line fill="none" stroke="#000000" x1="32.898" y1="9.684" x2="38.956" y2="14.042"/>
</g>
<g id="Layer_5">
<path fill="#FFFFFF" stroke="#231F20" d="M70.84,21.366c2.924,4.479,3.033,9.591,0.242,11.415&#xD;&#xA;&#x9;&#x9;c-2.793,1.825-7.426-0.332-10.354-4.813c-2.932-4.48-3.037-9.589-0.244-11.415C63.275,14.73,67.913,16.884,70.84,21.366z"/>
<path fill="#231F20" d="M70.089,28.522c0,1.08-0.802,1.956-1.8,1.956c-0.993,0-1.803-0.877-1.803-1.956&#xD;&#xA;&#x9;&#x9;c0-1.08,0.81-1.958,1.803-1.958C69.287,26.564,70.089,27.442,70.089,28.522"/>
</g>
<g id="Layer_7">
<path fill="#FFFFFF" stroke="#231F20" d="M46.867,24.619c2.926,4.48,2.619,9.862-0.681,12.015&#xD;&#xA;&#x9;&#x9;c-3.302,2.159-8.351,0.272-11.276-4.208c-2.928-4.48-2.624-9.86,0.678-12.017C38.891,18.253,43.938,20.137,46.867,24.619z"/>
<path fill="#231F20" d="M45.079,30.507c0,1.081-0.803,1.957-1.801,1.957c-0.992,0-1.803-0.878-1.803-1.957&#xD;&#xA;&#x9;&#x9;c0-1.08,0.811-1.957,1.803-1.957C44.274,28.55,45.079,29.427,45.079,30.507"/>
</g>
<path fill="#5E4A42" stroke="#000000" d="M58.766,33.926c1.854,0,4.555-0.284,4.697,0.569c0.143,0.855-1.709,4.203-2.988,4.345&#xD;&#xA;&#x9;c-1.283,0.142-6.125-2.353-6.195-3.919C54.206,33.355,57.055,33.926,58.766,33.926z"/>
<g id="Layer_4">
<path fill="none" stroke="#231F20" stroke-width="1.2" d="M45.774,41.235c0,0,10.347,3.054,14.217,3.897&#xD;&#xA;&#x9;&#x9;c3.868,0.842,10.851,1.684,10.851,1.684s-7.99,10.245-17.328,7.644C44.176,51.863,44.345,45.975,45.774,41.235z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.1 KiB

@@ -0,0 +1,42 @@
<svg width="96px" height="101px" viewBox="0 0 96 101" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<g>
<title>costume1.1</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="costume1" transform="translate(-13.000000, -10.000000)">
<g id="costume1.1" transform="translate(13.000000, 10.000000)">
<g id="tail" transform="translate(0.000000, 59.000000)">
<path d="M21.9,14.8 C19.5,14.3 16.6,13.5 14.2,11.3 C8.7,6.4 7,-1.7 3.2,0.4 C-0.7,2.5 -0.6,15.6 11.6,19.6 C15.8,21 19.6,21 22.7,20.9 C23.5,20.9 30.4,20.2 32.8,16.8 C35.2,13.4 33.5,12.5 32.7,12.1 C31.8,11.6 25.3,15.4 21.9,14.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.8,0.6 C1.8,1.2 0.8,5.4 1.8,8.9 C2.8,12.4 4.4,14.2 5.7,15.5 C5.5,14.8 5.1,12.6 6.8,11.3 C8.9,9.6 12.6,10.5 12.6,10.5 C12.6,10.5 9.5,6.7 7.9,4 C6.3,1.7 5.8,0.2 3.8,0.6 Z" id="detail" fill="#FFFFFF"/>
</g>
<path d="M37.7,81.5 C35.9,82.7 29.7,87.1 21.8,89.6 L21.4,89.7 C21,89.8 20.8,90.3 21,90.7 C22.7,93.1 25.8,97.9 20.3,99.6 C15,101.3 5.1,87.2 9.3,83.5 C11.2,82.1 12.9,82.8 13.8,83.2 C14.3,83.4 14.8,83.4 15.3,83.3 C16.5,82.9 18.7,82.1 20.4,81.2 C24.7,79 25.7,78.1 27.7,76.6 C29.7,75.1 34.3,71.4 38,74.6 C41.2,77.3 39.4,80.3 37.7,81.5 Z" id="leg" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M53.6,60.7 C54.1,61.1 60.2,68.3 62.2,66.5 C64.6,64.4 67.9,60.3 71.5,63.6 C75.1,66.9 68.3,72.5 65.4,74 C58.5,77.1 52.9,71.2 51.7,69.6 C50.5,68 48.4,65.3 48.4,62.7 C48.5,59.9 51.9,59.2 53.6,60.7 Z" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<g id="body-and-leg" transform="translate(28.000000, 57.000000)">
<path d="M18.2,19.7 C19.4,18.8 20.6,17.3 22.2,15 C23.5,13.1 24.9,9.4 24.9,9.4 C25.8,6.9 26.4,2.1 23.1,2.2 C20.9,2.3 18.9,2 15.5,1.5 C9.5,0.3 8.4,-0.5 5.9,3.6 C3.2,8.4 -3.7,11.9 4.8,20.2 C4.8,20.2 9.7,24 15.6,29.8 C19.6,33.7 25.9,39.3 28.1,41.2 C28.6,41.6 29.2,41.8 29.8,41.9 C39.5,42.8 46.7,41.8 46.7,37.5 C46.7,30.3 32.4,32.8 32.4,32.8 C32.4,32.8 27.8,28.9 25.7,27 L18.2,19.7 Z" id="body" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22.6,13 C22.6,13 24.5,10.5 20.2,7.8 C15.7,4.9 14,8.1 12.2,10.5 C10.2,13.6 12.2,15.1 14.2,16.9 C15.8,18.4 17.3,19.6 17.3,19.6 C17.3,19.6 20.4,17.5 22.6,13 Z" id="tummy" fill="#FFFFFF"/>
</g>
<path d="M30.2,68.4 C32.4,71.2 35.8,74.7 31.5,77.6 C25.6,80.9 20.7,70.9 19.7,67.4 C18.8,64.3 21.4,62.3 23.6,60.6 C27.9,57.5 31.5,54.7 35.5,56.2 C40.5,58 36.9,62 34.4,63.8 C32.9,64.9 31.4,66.1 30.3,66.8 C30,67.3 29.9,67.9 30.2,68.4 Z" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<g id="head" transform="translate(14.000000, 0.000000)">
<path d="M39.1,9 C36.8,8.6 34.4,8.4 31.6,8.6 C26.9,8.8 22.4,10.5 22.4,10.5 L10.3,2.6 C9.9,2.4 9.4,2.7 9.5,3.1 L11.6,21 C12.2,20.2 1,33.8 8.1,45.2 C15.2,56.6 30.3,61.7 49.1,58 C67.9,54.3 72.3,43.5 71.1,37.8 C69.9,32.1 62.8,30 62.8,30 C62.8,30 62.7,25.5 59.5,20 C57.6,16.7 51.2,12 51.2,12 L48.6,1.3 C48.5,0.9 48,0.8 47.7,1 L39.1,9 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19"/>
<path d="M62.5,30.4 C62.5,30.4 69.4,32.2 70.6,37.9 C71.8,43.6 67,53.9 48.4,57.5 C24.2,62.5 12.7,48.1 19.4,37.5 C26.1,26.8 37.6,35.9 46,35.3 C53.2,34.8 54,28.5 62.5,30.4 Z" id="face" fill="#FFFFFF"/>
<path d="M31,41.1 C31,40.7 31.4,40.4 31.8,40.5 C33.7,41.2 39.1,42.8 45.1,43.2 C50.5,43.5 53.7,43.2 55.2,42.9 C55.7,42.8 56.1,43.3 55.9,43.8 C55,46.5 51.2,54 40.7,53.4 C31.6,52.4 30.7,46 31,41.1 Z" id="mouth" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M69,35.4 C69,35.4 76.2,35.3 80.9,31.5" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M69.4,41.3 C69.4,41.3 73.3,43.2 79.6,42.7" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M45.6,32.7 C47.7,32.7 49.9,32.9 50,33.6 C50.1,35 48.6,37.8 47,37.9 C45.2,38.1 41,35.6 41,34 C40.9,32.8 43.6,32.7 45.6,32.7 Z" id="nose" stroke="#001026" stroke-width="1.2" fill="#001026" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.6,31.2 C0.6,31.2 9.2,34 12.7,37.1" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.3,41.2 C1.3,41.2 8.7,42.3 13,40.6" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<g id="eye" transform="translate(44.000000, 15.000000)">
<path d="M13.4,6 C16.3,10.5 16.4,15.6 13.6,17.4 C10.8,19.2 6.2,17.1 3.2,12.6 C0.3,8.1 0.2,3 3,1.2 C5.8,-0.7 10.5,1.5 13.4,6 Z" id="pupil" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13,11.7 C13,12.8 12.2,13.7 11.2,13.7 C10.2,13.7 9.4,12.8 9.4,11.7 C9.4,10.6 10.2,9.7 11.2,9.7 C12.2,9.7 13,10.6 13,11.7" id="pupil" fill="#001026"/>
</g>
<g id="eye" transform="translate(19.000000, 18.000000)">
<path d="M13.6,5.8 C16.6,10.2 16.4,15.6 13.7,17.5 C10.4,19.4 6,18 3,13.6 C-0.1,9.2 -0.3,3.5 2.8,1.3 C5.9,-1 10.6,1.4 13.6,5.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13,11.6 C13,12.7 12.2,13.6 11.2,13.6 C10.2,13.6 9.4,12.7 9.4,11.6 C9.4,10.5 10.2,9.6 11.2,9.6 C12.2,9.7 13,10.5 13,11.6" id="pupil" fill="#001026"/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

@@ -0,0 +1,42 @@
<svg width="93px" height="106px" viewBox="0 0 93 106" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<g>
<title>costume2.1</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="cotsume2" transform="translate(-16.000000, -10.000000)">
<g id="costume2.1" transform="translate(16.250000, 9.500000)">
<g id="tail" transform="translate(0.000000, 53.000000)">
<path d="M12,14.8 C8.9,7 10.6,0 6.3,0.4 C1.9,0.8 -3.2,12.5 6.4,21.1 C15.3,29 24.4,26.1 28,24 C31.5,21.8 28.8,20.6 28.2,19.8 C27.6,19 16.4,26.1 12,14.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.8,0.9 C4.3,0.5 2.1,4.1 1.6,7.6 C1.4,11.2 2,13.3 2.7,15 C2.8,14.3 3.7,11.9 6.6,11.4 C9,11 10.6,13 10.6,13 C10.6,13 9.6,10 9.1,5.6 C8.7,2.9 8.8,1.3 6.8,0.9 Z" id="detail" fill="#FFFFFF"/>
</g>
<path d="M32.9,83.2 C32.2,84.1 30.2,87.6 25.4,90.6 C22.1,92.7 13.6,89.2 13.6,89.2 C14.5,98.4 7,98.2 5.6,95.2 C4.5,92.8 4.7,82.5 6.9,81.7 C10.3,80.5 14.9,82.5 19,82.5 C20.2,82.5 21.5,79.9 24.3,77.1 C26.3,75.1 28.9,71.4 33.1,74 C36.7,76.3 34.8,80.5 32.9,83.2 Z" id="leg" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M50.1,62 C50.4,64.2 50.6,72.6 53,73.9 C55.3,75.1 62.3,73.1 62.7,78 C63.1,82.8 56.6,82.9 51.2,81.4 C44.2,79.5 41,68.8 45,62.2 C46.5,59.9 49.8,59.7 50.1,62 Z" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<g id="body-and-leg" transform="translate(24.000000, 57.000000)">
<path d="M18.2,21.3 C19.4,20.4 21.2,18 22.8,15.6 C24.1,13.7 25.5,10 25.5,10 C26.4,7.5 27,2.7 23.7,2.8 C21.5,2.9 19.5,2.6 16.1,2.1 C10.1,0.9 8.9,-0.3 6.5,4.2 C2,12.5 -4,18.4 4.2,23.2 C6.2,24.4 9.4,26.4 12.2,28.4 C14.2,30.3 11.2,34.4 9.1,36.8 C8.2,37.9 8.2,39.5 9.3,40.6 C16.6,47.7 18.8,50.2 21.6,47.1 C25.4,42.9 19.4,38.8 19.4,38.8 C19.4,38.8 24.2,31.3 23.6,28 C22.9,24.8 18.2,21.3 18.2,21.3 Z" id="body" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M22.7284271,14.6028611 C22.7284271,14.6028611 24.6284271,12.1028611 20.3284271,9.40286109 C15.8284271,6.50286109 14.1284271,9.70286109 12.3284271,12.1028611 C10.3284271,15.2028611 12.3284271,16.7028611 14.3284271,18.5028611 C15.9284271,20.0028611 17.4284271,21.2028611 17.4284271,21.2028611 C17.4284271,21.2028611 20.5284271,19.1028611 22.7284271,14.6028611 Z" id="tummy" fill="#FFFFFF"/>
</g>
<path d="M32.3,65.2 C30.1,66.2 26,64.4 24.9,64.6 C24.6,64.7 22.8,69.9 21.7,73 C20.2,77.4 13.4,76.1 13,71.5 C12.7,67.2 16.6,58.9 17.9,57.4 C20.8,53.9 30.6591797,56.7082962 37.2463379,58.456543" id="arm" stroke="#001026" stroke-width="1.2" fill="#FFAB19" stroke-linecap="round" stroke-linejoin="round"/>
<g id="head" transform="translate(10.750000, 0.500000)">
<path d="M39.1,9 C36.8,8.6 34.4,8.4 31.6,8.6 C26.9,8.8 22.4,10.5 22.4,10.5 L10.3,2.6 C9.9,2.4 9.4,2.7 9.5,3.1 L11.6,21 C12.2,20.2 1,33.8 8.1,45.2 C15.2,56.6 30.3,61.7 49.1,58 C67.9,54.3 72.3,43.5 71.1,37.8 C69.9,32.1 62.8,30 62.8,30 C62.8,30 62.7,25.5 59.5,20 C57.6,16.7 51.2,12 51.2,12 L48.6,1.3 C48.5,0.9 48,0.8 47.7,1 L39.1,9 Z" stroke="#001026" stroke-width="1.2" fill="#FFAB19"/>
<path d="M62.5,30.4 C62.5,30.4 69.4,32.2 70.6,37.9 C71.8,43.6 67,53.9 48.4,57.5 C24.2,62.5 12.7,48.1 19.4,37.5 C26.1,26.8 37.6,35.9 46,35.3 C53.2,34.8 54,28.5 62.5,30.4 Z" id="face" fill="#FFFFFF"/>
<path d="M31,41.1 C31,40.7 31.4,40.4 31.8,40.5 C33.7,41.2 39.1,42.8 45.1,43.2 C50.5,43.5 53.7,43.2 55.2,42.9 C55.7,42.8 56.1,43.3 55.9,43.8 C55,46.5 51.2,54 40.7,53.4 C31.6,52.4 30.7,46 31,41.1 Z" id="mouth" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M69,35.4 C69,35.4 76.2,35.3 80.9,31.5" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M69.4,41.3 C69.4,41.3 73.3,43.2 79.6,42.7" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M45.6,32.7 C47.7,32.7 49.9,32.9 50,33.6 C50.1,35 48.6,37.8 47,37.9 C45.2,38.1 41,35.6 41,34 C40.9,32.8 43.6,32.7 45.6,32.7 Z" id="nose" stroke="#001026" stroke-width="1.2" fill="#001026" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.6,31.2 C0.6,31.2 9.2,34 12.7,37.1" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.3,41.2 C1.3,41.2 8.7,42.3 13,40.6" id="whisker" stroke="#001026" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<g id="eye" transform="translate(44.000000, 15.000000)">
<path d="M13.4,6 C16.3,10.5 16.4,15.6 13.6,17.4 C10.8,19.2 6.2,17.1 3.2,12.6 C0.3,8.1 0.2,3 3,1.2 C5.8,-0.7 10.5,1.5 13.4,6 Z" id="pupil" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13,11.7 C13,12.8 12.2,13.7 11.2,13.7 C10.2,13.7 9.4,12.8 9.4,11.7 C9.4,10.6 10.2,9.7 11.2,9.7 C12.2,9.7 13,10.6 13,11.7" id="pupil" fill="#001026"/>
</g>
<g id="eye" transform="translate(19.000000, 18.000000)">
<path d="M13.6,5.8 C16.6,10.2 16.4,15.6 13.7,17.5 C10.4,19.4 6,18 3,13.6 C-0.1,9.2 -0.3,3.5 2.8,1.3 C5.9,-1 10.6,1.4 13.6,5.8 Z" stroke="#001026" stroke-width="1.2" fill="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13,11.6 C13,12.7 12.2,13.6 11.2,13.6 C10.2,13.6 9.4,12.7 9.4,11.6 C9.4,10.5 10.2,9.6 11.2,9.6 C12.2,9.7 13,10.5 13,11.6" id="pupil" fill="#001026"/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

+4 -4
View File
@@ -4,8 +4,8 @@ import projectData from './project-data';
import popWav from '!arraybuffer-loader!./83a9787d4cb6f3b7632b4ddfebf74367.wav';
import meowWav from '!arraybuffer-loader!./83c36d806dc92327b9e7049a565c6bff.wav';
import backdrop from '!raw-loader!./cd21514d0531fdffb22204e0ec5ed84a.svg';
import costume1 from '!raw-loader!./09dc888b0b7df19f70d81588ae73420e.svg';
import costume2 from '!raw-loader!./3696356a03a8d938318876a593572843.svg';
import costume1 from '!raw-loader!./b7853f557e4426412e64bb3da6531a99.svg';
import costume2 from '!raw-loader!./e6ddc55a6ddd9cc9d84fe0b4c21e016f.svg';
/* eslint-enable import/no-unresolved */
const defaultProject = translator => {
@@ -40,12 +40,12 @@ const defaultProject = translator => {
dataFormat: 'SVG',
data: encoder.encode(backdrop)
}, {
id: '09dc888b0b7df19f70d81588ae73420e',
id: 'b7853f557e4426412e64bb3da6531a99',
assetType: 'ImageVector',
dataFormat: 'SVG',
data: encoder.encode(costume1)
}, {
id: '3696356a03a8d938318876a593572843',
id: 'e6ddc55a6ddd9cc9d84fe0b4c21e016f',
assetType: 'ImageVector',
dataFormat: 'SVG',
data: encoder.encode(costume2)
+108
View File
@@ -0,0 +1,108 @@
// TODO: access `BlockType` and `ArgumentType` without reaching into VM
// Should we move these into a new extension support module or something?
import ArgumentType from 'scratch-vm/src/extension-support/argument-type';
import BlockType from 'scratch-vm/src/extension-support/block-type';
/**
* Define a block using extension info which has the ability to dynamically determine (and update) its layout.
* This functionality is used for extension blocks which can change its properties based on different state
* information. For example, the `control_stop` block changes its shape based on which menu item is selected
* and a variable block changes its text to reflect the variable name without using an editable field.
* @param {object} ScratchBlocks - The ScratchBlocks name space.
* @param {object} categoryInfo - Information about this block's extension category, including any menus and icons.
* @param {object} staticBlockInfo - The base block information before any dynamic changes.
* @param {string} extendedOpcode - The opcode for the block (including the extension ID).
*/
// TODO: grow this until it can fully replace `_convertForScratchBlocks` in the VM runtime
const defineDynamicBlock = (ScratchBlocks, categoryInfo, staticBlockInfo, extendedOpcode) => ({
init: function () {
const blockJson = {
type: extendedOpcode,
inputsInline: true,
category: categoryInfo.name,
colour: categoryInfo.color1,
colourSecondary: categoryInfo.color2,
colourTertiary: categoryInfo.color3
};
// There is a scratch-blocks / Blockly extension called "scratch_extension" which adjusts the styling of
// blocks to allow for an icon, a feature of Scratch extension blocks. However, Scratch "core" extension
// blocks don't have icons and so they should not use 'scratch_extension'. Adding a scratch-blocks / Blockly
// extension after `jsonInit` isn't fully supported (?), so we decide now whether there will be an icon.
if (staticBlockInfo.blockIconURI || categoryInfo.blockIconURI) {
blockJson.extensions = ['scratch_extension'];
}
// initialize the basics of the block, to be overridden & extended later by `domToMutation`
this.jsonInit(blockJson);
// initialize the cached block info used to carry block info from `domToMutation` to `mutationToDom`
this.blockInfoText = '{}';
// we need a block info update (through `domToMutation`) before we have a completely initialized block
this.needsBlockInfoUpdate = true;
},
mutationToDom: function () {
const container = document.createElement('mutation');
container.setAttribute('blockInfo', this.blockInfoText);
return container;
},
domToMutation: function (xmlElement) {
const blockInfoText = xmlElement.getAttribute('blockInfo');
if (!blockInfoText) return;
if (!this.needsBlockInfoUpdate) {
throw new Error('Attempted to update block info twice');
}
delete this.needsBlockInfoUpdate;
this.blockInfoText = blockInfoText;
const blockInfo = JSON.parse(blockInfoText);
switch (blockInfo.blockType) {
case BlockType.COMMAND:
case BlockType.CONDITIONAL:
case BlockType.LOOP:
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
this.setPreviousStatement(true);
this.setNextStatement(!blockInfo.isTerminal);
break;
case BlockType.REPORTER:
this.setOutput(true);
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_ROUND);
if (!blockInfo.disableMonitor) {
this.setCheckboxInFlyout(true);
}
break;
case BlockType.BOOLEAN:
this.setOutput(true);
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_HEXAGONAL);
break;
case BlockType.HAT:
case BlockType.EVENT:
this.setOutputShape(ScratchBlocks.OUTPUT_SHAPE_SQUARE);
this.setNextStatement(true);
break;
}
if (blockInfo.color1 || blockInfo.color2 || blockInfo.color3) {
// `setColour` handles undefined parameters by adjusting defined colors
this.setColour(blockInfo.color1, blockInfo.color2, blockInfo.color3);
}
// Layout block arguments
// TODO handle E/C Blocks
const blockText = blockInfo.text;
const args = [];
let argCount = 0;
const scratchBlocksStyleText = blockText.replace(/\[(.+?)]/g, (match, argName) => {
const arg = blockInfo.arguments[argName];
switch (arg.type) {
case ArgumentType.STRING:
args.push({type: 'input_value', name: argName});
break;
case ArgumentType.BOOLEAN:
args.push({type: 'input_value', name: argName, check: 'Boolean'});
break;
}
return `%${++argCount}`;
});
this.interpolate_(scratchBlocksStyleText, args);
}
});
export default defineDynamicBlock;
+1
View File
@@ -11,6 +11,7 @@ export default (filename, blob) => {
const url = window.URL.createObjectURL(blob);
downloadLink.href = url;
downloadLink.download = filename;
downloadLink.type = blob.type;
downloadLink.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(downloadLink);
+127
View File
@@ -0,0 +1,127 @@
import bindAll from 'lodash.bindall';
import {getEventXY} from '../lib/touch-utils';
class DragRecognizer {
/* Gesture states */
static get STATE_UNIDENTIFIED () {
return 'unidentified';
}
static get STATE_SCROLL () {
return 'scroll';
}
static get STATE_DRAG () {
return 'drag';
}
constructor ({
onDrag = (() => {}),
onDragEnd = (() => {}),
touchDragAngle = 70, // Angle and distance thresholds are the same as scratch-blocks
distanceThreshold = 3
}) {
this._onDrag = onDrag;
this._onDragEnd = onDragEnd;
this._touchDragAngle = touchDragAngle;
this._distanceThreshold = distanceThreshold;
this._initialOffset = null;
this._gestureState = DragRecognizer.STATE_UNIDENTIFIED;
bindAll(this, [
'start',
'gestureInProgress',
'reset',
'_handleMove',
'_handleEnd'
]);
}
start (event) {
this._initialOffset = getEventXY(event);
this._bindListeners();
}
gestureInProgress () {
return this._gestureState !== DragRecognizer.STATE_UNIDENTIFIED;
}
reset () {
this._unbindListeners();
this._initialOffset = null;
this._gestureState = DragRecognizer.STATE_UNIDENTIFIED;
}
//
// Internal functions
//
_bindListeners () {
window.addEventListener('mouseup', this._handleEnd);
window.addEventListener('mousemove', this._handleMove);
window.addEventListener('touchend', this._handleEnd);
// touchmove must be marked as non-passive, or else it cannot prevent scrolling
window.addEventListener('touchmove', this._handleMove, {passive: false});
}
_unbindListeners () {
window.removeEventListener('mouseup', this._handleEnd);
window.removeEventListener('mousemove', this._handleMove);
window.removeEventListener('touchend', this._handleEnd);
window.removeEventListener('touchmove', this._handleMove, {passive: false});
}
_handleMove (event) {
// For gestures identified as vertical scrolls, do not process movement events
if (this._isScroll()) return;
const currentOffset = getEventXY(event);
// Try to identify this gesture if it hasn't been identified already
if (!this.gestureInProgress()) {
const dx = currentOffset.x - this._initialOffset.x;
const dy = currentOffset.y - this._initialOffset.y;
const dragDistance = Math.sqrt((dx * dx) + (dy * dy));
if (dragDistance < this._distanceThreshold) return;
// For touch moves, additionally check if the angle suggests drag vs. scroll
if (event.type === 'touchmove') {
// Direction goes from -180 to 180, with 0 toward the right.
let angle = Math.atan2(dy, dx) / Math.PI * 180;
// Fold over horizontal axis, range now 0 to 180
angle = Math.abs(angle);
// Fold over vertical axis, range now 0 to 90
if (angle > 90) angle = 180 - angle;
if (angle > this._touchDragAngle) {
this._gestureState = DragRecognizer.STATE_SCROLL;
} else {
this._gestureState = DragRecognizer.STATE_DRAG;
}
} else {
// Mouse moves are always considered drags
this._gestureState = DragRecognizer.STATE_DRAG;
}
}
if (this._isDrag()) {
this._onDrag(currentOffset, this._initialOffset);
event.preventDefault();
}
}
_handleEnd () {
this.reset();
// Call the callback after reset to make sure if gestureInProgress()
// is used in response, it get the correct value (i.e. no gesture in progress)
this._onDragEnd();
}
_isDrag () {
return this._gestureState === DragRecognizer.STATE_DRAG;
}
_isScroll () {
return this._gestureState === DragRecognizer.STATE_SCROLL;
}
}
export default DragRecognizer;
+1 -1
View File
@@ -52,7 +52,7 @@
},
{
"name": "Basketball 1",
"md5": "e494c4f44897d94e0541f7036a302449.svg",
"md5": "ae21eac3d1814aee1d37ae82ea287816.svg",
"type": "backdrop",
"tags": [
"sports",
+140 -56
View File
@@ -75,7 +75,7 @@
},
{
"name": "Andie-a",
"md5": "419ff5a76adbb4b076c072f68267fdeb.svg",
"md5": "d2c433e02481b10584e34ccdcf5a046a.svg",
"type": "costume",
"tags": [
"sports",
@@ -87,14 +87,33 @@
"alex eben meyer"
],
"info": [
86,
81,
80,
65,
1
]
},
{
"name": "Andie-b",
"md5": "a6a766912b4e43064ee3f389c72925b4.svg",
"md5": "045f92ad80d72521098ede6d7c80549a.svg",
"type": "costume",
"tags": [
"sports",
"basketball",
"people",
"wheelchair",
"handicap",
"handicapable",
"alex eben meyer"
],
"info": [
40,
91,
1
]
},
{
"name": "Andie-c",
"md5": "c9bad70ad371b8d6d97daed7ab9def98.svg",
"type": "costume",
"tags": [
"sports",
@@ -107,32 +126,13 @@
],
"info": [
46,
106,
1
]
},
{
"name": "Andie-c",
"md5": "fac1025857d2c28391cb31f39a80df54.svg",
"type": "costume",
"tags": [
"sports",
"basketball",
"people",
"wheelchair",
"handicap",
"handicapable",
"alex eben meyer"
],
"info": [
52,
65,
49,
1
]
},
{
"name": "Andie-d",
"md5": "7af50835e05d43e5e07cd2d641e7401e.svg",
"md5": "7c9db638cd671898ff07771112faff0f.svg",
"type": "costume",
"tags": [
"sports",
@@ -144,8 +144,8 @@
"alex eben meyer"
],
"info": [
87,
72,
71,
57,
1
]
},
@@ -1069,7 +1069,7 @@
},
{
"name": "Ben-a",
"md5": "4386d05d5abd6e1aa0a93b7b447d1b39.svg",
"md5": "322e13bd17d02acfdc048a5ef2167be8.svg",
"type": "costume",
"tags": [
"sports",
@@ -1079,14 +1079,14 @@
"alex eben meyer"
],
"info": [
53,
54,
69,
1
]
},
{
"name": "Ben-b",
"md5": "90db137cd0743204d02fdc9be1c8eb94.svg",
"md5": "59ba1e80ba75b1d06c37db4b9b4fde27.svg",
"type": "costume",
"tags": [
"sports",
@@ -1096,14 +1096,14 @@
"alex eben meyer"
],
"info": [
53,
67,
54,
69,
1
]
},
{
"name": "Ben-c",
"md5": "5f05d41ede773b3bb379286e86a5b869.svg",
"md5": "940ec2fa85d666dde95a51bcbccd266e.svg",
"type": "costume",
"tags": [
"sports",
@@ -1113,14 +1113,14 @@
"alex eben meyer"
],
"info": [
73,
71,
72,
1
]
},
{
"name": "Ben-d",
"md5": "6f6e246567c0639c758ed4f6930be76f.svg",
"md5": "3043a50a91b9d484a8cd32eb82f2d88f.svg",
"type": "costume",
"tags": [
"sports",
@@ -1130,8 +1130,8 @@
"alex eben meyer"
],
"info": [
42,
72,
44,
71,
1
]
},
@@ -2563,7 +2563,7 @@
},
{
"name": "Dinosaur1-c",
"md5": "a2bb28d5ba55ba997bec63772437cecf.svg",
"md5": "78f7e20f0e187fdca81195e4cc134175.svg",
"type": "costume",
"tags": [
"animals",
@@ -3628,16 +3628,17 @@
},
{
"name": "Earth",
"md5": "d9df2cf4f6eb2460d0de3e4f1e05f40b.svg",
"md5": "64220361b80d2812e9f27fc88ef39027.svg",
"type": "costume",
"tags": [
"space",
"planet",
"earth"
"earth",
"globe"
],
"info": [
41,
40,
55,
55,
1
]
},
@@ -6178,7 +6179,7 @@
},
{
"name": "Jordyn-a",
"md5": "8355df83cca2baa0fdf6269d1311f1ea.svg",
"md5": "2f59adebc2c0feb9ebfabd7ee9642f2b.svg",
"type": "costume",
"tags": [
"sports",
@@ -6189,13 +6190,13 @@
],
"info": [
51,
63,
62,
1
]
},
{
"name": "Jordyn-b",
"md5": "2e2c43bbd7b18524fb132f1426ca3134.svg",
"md5": "075f382883e60dba8094b3dc30918f60.svg",
"type": "costume",
"tags": [
"sports",
@@ -6212,7 +6213,7 @@
},
{
"name": "Jordyn-c",
"md5": "5c0d24c53805e16358a2f6f6cc4a456e.svg",
"md5": "fbe81c40d4a880c8799c458359758629.svg",
"type": "costume",
"tags": [
"sports",
@@ -6223,13 +6224,13 @@
],
"info": [
68,
61,
62,
1
]
},
{
"name": "Jordyn-d",
"md5": "ebe2b20b99c1e1b373fd2b02be48aab9.svg",
"md5": "68a784d4d0340475a6e6feff3b28b470.svg",
"type": "costume",
"tags": [
"sports",
@@ -6240,7 +6241,7 @@
],
"info": [
40,
61,
62,
1
]
},
@@ -6806,6 +6807,71 @@
1
]
},
{
"name": "Marian-a",
"md5": "e9577a1eb098905dd386135bb38c0398.png",
"type": "costume",
"tags": [
"people"
],
"info": [
217,
452,
2
]
},
{
"name": "Marian-b",
"md5": "3d2ecee35eab8c37d1c3eadfe50ce447.png",
"type": "costume",
"tags": [
"people"
],
"info": [
217,
452,
2
]
},
{
"name": "Marian-c",
"md5": "221e9999b20ecc21b37c68fcdf09ab02.png",
"type": "costume",
"tags": [
"people"
],
"info": [
141,
452,
2
]
},
{
"name": "Marian-d",
"md5": "64206b46c411e40926569cf3f5e587be.png",
"type": "costume",
"tags": [
"people"
],
"info": [
278,
438,
2
]
},
{
"name": "Marian-e",
"md5": "16893c6136292ae36e13dc72cc55719b.png",
"type": "costume",
"tags": [
"people"
],
"info": [
139,
423,
2
]
},
{
"name": "Max-a",
"md5": "5cc168bddf4561e6e7f1d30845d6efbf.svg",
@@ -9660,7 +9726,7 @@
},
{
"name": "Sun",
"md5": "55c931c65456822c0c56a2b30e3e550d.svg",
"md5": "14b0508da0dc30e83b2bede13c1be1e9.svg",
"type": "costume",
"tags": [
"space",
@@ -9671,8 +9737,8 @@
"nuclear"
],
"info": [
72,
72,
54,
54,
1
]
},
@@ -10427,7 +10493,7 @@
},
{
"name": "Watermelon-a",
"md5": "26fecef75cf3b6e0d98bff5c06475036.svg",
"md5": "8736ecc2524895733534c888cd91fa1f.svg",
"type": "costume",
"tags": [
"food",
@@ -10445,7 +10511,7 @@
},
{
"name": "Watermelon-b",
"md5": "fbdaf4d1d349edd3ddf3a1c4528aa9ec.svg",
"md5": "1ed1c8b78eae2ee7422074d7f883031d.svg",
"type": "costume",
"tags": [
"food",
@@ -10462,7 +10528,7 @@
},
{
"name": "Watermelon-c",
"md5": "5976c10412306fc093c1d1930fa05119.svg",
"md5": "677738282686d2dcce35d731c3ddc043.svg",
"type": "costume",
"tags": [
"food",
@@ -10582,6 +10648,24 @@
1
]
},
{
"name": "Wizard Girl",
"md5": "dd40d3a2565990bcc24d2b36cd446394.svg",
"type": "costume",
"tags": [
"people",
"female",
"girl",
"wizard",
"magic",
"fantasy"
],
"info": [
80,
91,
1
]
},
{
"name": "Wizard Hat",
"md5": "2da561a7e5cbfcda613f4750020d4aa5.svg",
@@ -12682,4 +12766,4 @@
1
]
}
]
]
+216 -1
View File
@@ -90,6 +90,114 @@ import videoPet from './steps/video-pet.en.gif';
import videoAnimate from './steps/video-animate.en.gif';
import videoPop from './steps/video-pop.en.gif';
// Make it Fly
import flyChooseBackdrop from './steps/fly-choose-backdrop.en.gif';
import flyChooseCharacter from './steps/fly-choose-character.en.gif';
import flySaySomething from './steps/fly-say-something.en.gif';
import flyMoveArrows from './steps/fly-make-interactive.en.gif';
import flyChooseObject from './steps/fly-object-to-collect.en.gif';
import flyFlyingObject from './steps/fly-flying-heart.en.gif';
import flySelectFlyingSprite from './steps/fly-select-flyer.en.jpg';
import flyAddScore from './steps/fly-add-score.en.gif';
import flyKeepScore from './steps/fly-keep-score.en.gif';
import flyAddScenery from './steps/fly-choose-scenery.en.gif';
import flyMoveScenery from './steps/fly-move-scenery.en.gif';
import flySwitchLooks from './steps/fly-switch-costume.en.gif';
// Pong
import pongAddBackdrop from './steps/pong-add-backdrop.en.png';
import pongAddBallSprite from './steps/pong-add-ball-sprite.en.png';
import pongBounceAround from './steps/pong-bounce-around.en.gif';
import pongAddPaddle from './steps/pong-add-a-paddle.en.gif';
import pongMoveThePaddle from './steps/pong-move-the-paddle.en.gif';
import pongSelectBallSprite from './steps/pong-select-ball.en.png';
import pongAddMoreCodeToBall from './steps/pong-add-code-to-ball.en.png';
import pongAddAScore from './steps/pong-add-score.en.gif';
import pongChooseScoreFromMenu from './steps/pong-choose-score.en.png';
import pongInsertChangeScoreBlock from './steps/pong-insert-change-score.en.png';
import pongResetScore from './steps/pong-reset-score.en.png';
import pongAddLineSprite from './steps/pong-add-line.en.gif';
import pongGameOver from './steps/pong-game-over.en.png';
// Imagine a World
import imagineTypeWhatYouWant from './steps/imagine-type-what-you-want.en.png';
import imagineClickGreenFlag from './steps/imagine-click-green-flag.en.png';
import imagineChooseBackdrop from './steps/imagine-choose-backdrop.en.png';
import imagineChooseSprite from './steps/imagine-choose-any-sprite.en.png';
import imagineFlyAround from './steps/imagine-fly-around.en.png';
import imagineChooseAnotherSprite from './steps/imagine-choose-another-sprite.en.png';
import imagineLeftRight from './steps/imagine-left-right.en.png';
import imagineUpDown from './steps/imagine-up-down.en.png';
import imagineChangeCostumes from './steps/imagine-change-costumes.en.png';
import imagineGlideToPoint from './steps/imagine-glide-to-point.en.png';
import imagineGrowShrink from './steps/imagine-grow-shrink.en.png';
import imagineChooseAnotherBackdrop from './steps/imagine-choose-another-backdrop.en.png';
import imagineSwitchBackdrops from './steps/imagine-switch-backdrops.en.gif';
import imagineRecordASound from './steps/imagine-record-a-sound.en.gif';
import imagineChooseSound from './steps/imagine-choose-sound.en.png';
// Add a Backdrop
import addBackdrop from './steps/add-backdrop.en.png';
// Add Effectsf
import addEffects from './steps/add-effects.en.png';
// Hide and Show
import hideAndShow from './steps/hide-show.en.png';
// Switch Costumes
import switchCostumes from './steps/switch-costumes.en.png';
// Change Size
import changeSize from './steps/change-size.en.png';
// Spin
import spinTurn from './steps/spin-turn.en.png';
import spinPointInDirection from './steps/spin-point-in-direction.en.png';
// Record a Sound
import recordASoundSoundsTab from './steps/record-a-sound-sounds-tab.en.png';
import recordASoundClickRecord from './steps/record-a-sound-click-record.en.png';
import recordASoundPressRecordButton from './steps/record-a-sound-press-record-button.en.png';
import recordASoundChooseSound from './steps/record-a-sound-choose-sound.en.png';
import recordASoundPlayYourSound from './steps/record-a-sound-play-your-sound.en.png';
// Use Arrow Keys
import moveArrowKeysLeftRight from './steps/move-arrow-keys-left-right.en.png';
import moveArrowKeysUpDown from './steps/move-arrow-keys-up-down.en.png';
// Glide Around
import glideAroundBackAndForth from './steps/glide-around-back-and-forth.en.png';
import glideAroundPoint from './steps/glide-around-point.en.png';
// Code a Cartoon
import codeCartoonSaySomething from './steps/code-cartoon-01-say-something.en.png';
import codeCartoonAnimate from './steps/code-cartoon-02-animate.en.gif';
import codeCartoonSelectDifferentCharacter from './steps/code-cartoon-03-select-different-character.en.gif';
import codeCartoonUseMinusSign from './steps/code-cartoon-04-use-minus-sign.en.gif';
import codeCartoonGrowShrink from './steps/code-cartoon-05-grow-shrink.en.gif';
import codeCartoonSelectDifferentCharacter2 from './steps/code-cartoon-06-select-another-different-character.en.gif';
import codeCartoonJump from './steps/code-cartoon-07-jump.en.gif';
import codeCartoonChangeScenes from './steps/code-cartoon-08-change-scenes.en.gif';
import codeCartoonGlideAround from './steps/code-cartoon-09-glide-around.en.gif';
import codeCartoonChangeCostumes from './steps/code-cartoon-10-change-costumes.en.gif';
import codeCartoonChooseMoreCharacters from './steps/code-cartoon-11-choose-more-characters.en.jpg';
// Talking Tales
import talesAddExtension from './steps/talking-1-add-extension.en.gif';
import talesChooseSprite from './steps/talking-2-choose-sprite.en.png';
import talesSaySomething from './steps/talking-3-say-something.en.gif';
import talesChooseBackdrop from './steps/talking-4-choose-backdrop.en.png';
import talesSwitchBackdrop from './steps/talking-5-switch-backdrop.en.gif';
import talesChooseAnotherSprite from './steps/talking-6-choose-another-sprite.en.png';
import talesMoveAround from './steps/talking-7-move-around.en.gif';
import talesChooseAnotherBackdrop from './steps/talking-8-choose-another-backdrop.en.png';
import talesAnimateTalking from './steps/talking-9-animate.en.gif';
import talesChooseThirdBackdrop from './steps/talking-10-choose-third-backdrop.en.png';
import talesChooseSound from './steps/talking-11-choose-sound.en.gif';
import talesDanceMoves from './steps/talking-12-dance-moves.en.gif';
import talesAskAnswer from './steps/talking-13-ask-and-answer.en.gif';
const enImages = {
// Intro
introMove: introMove,
@@ -181,8 +289,115 @@ const enImages = {
videoAddExtension: videoAddExtension,
videoPet: videoPet,
videoAnimate: videoAnimate,
videoPop: videoPop
videoPop: videoPop,
// Make it Fly
flyChooseBackdrop: flyChooseBackdrop,
flyChooseCharacter: flyChooseCharacter,
flySaySomething: flySaySomething,
flyMoveArrows: flyMoveArrows,
flyChooseObject: flyChooseObject,
flyFlyingObject: flyFlyingObject,
flySelectFlyingSprite: flySelectFlyingSprite,
flyAddScore: flyAddScore,
flyKeepScore: flyKeepScore,
flyAddScenery: flyAddScenery,
flyMoveScenery: flyMoveScenery,
flySwitchLooks: flySwitchLooks,
// Pong
pongAddBackdrop: pongAddBackdrop,
pongAddBallSprite: pongAddBallSprite,
pongBounceAround: pongBounceAround,
pongAddPaddle: pongAddPaddle,
pongMoveThePaddle: pongMoveThePaddle,
pongSelectBallSprite: pongSelectBallSprite,
pongAddMoreCodeToBall: pongAddMoreCodeToBall,
pongAddAScore: pongAddAScore,
pongChooseScoreFromMenu: pongChooseScoreFromMenu,
pongInsertChangeScoreBlock: pongInsertChangeScoreBlock,
pongResetScore: pongResetScore,
pongAddLineSprite: pongAddLineSprite,
pongGameOver: pongGameOver,
// Imagine a World
imagineTypeWhatYouWant: imagineTypeWhatYouWant,
imagineClickGreenFlag: imagineClickGreenFlag,
imagineChooseBackdrop: imagineChooseBackdrop,
imagineChooseSprite: imagineChooseSprite,
imagineFlyAround: imagineFlyAround,
imagineChooseAnotherSprite: imagineChooseAnotherSprite,
imagineLeftRight: imagineLeftRight,
imagineUpDown: imagineUpDown,
imagineChangeCostumes: imagineChangeCostumes,
imagineGlideToPoint: imagineGlideToPoint,
imagineGrowShrink: imagineGrowShrink,
imagineChooseAnotherBackdrop: imagineChooseAnotherBackdrop,
imagineSwitchBackdrops: imagineSwitchBackdrops,
imagineRecordASound: imagineRecordASound,
imagineChooseSound: imagineChooseSound,
// Add a Backdrop
addBackdrop: addBackdrop,
// Add Effects
addEffects: addEffects,
// Hide and Show
hideAndShow: hideAndShow,
// Switch Costumes
switchCostumes: switchCostumes,
// Change Size
changeSize: changeSize,
// Spin
spinTurn: spinTurn,
spinPointInDirection: spinPointInDirection,
// Record a Sound
recordASoundSoundsTab: recordASoundSoundsTab,
recordASoundClickRecord: recordASoundClickRecord,
recordASoundPressRecordButton: recordASoundPressRecordButton,
recordASoundChooseSound: recordASoundChooseSound,
recordASoundPlayYourSound: recordASoundPlayYourSound,
// Use Arrow Keys
moveArrowKeysLeftRight: moveArrowKeysLeftRight,
moveArrowKeysUpDown: moveArrowKeysUpDown,
// Glide Around
glideAroundBackAndForth: glideAroundBackAndForth,
glideAroundPoint: glideAroundPoint,
// Code a Cartoon
codeCartoonSaySomething: codeCartoonSaySomething,
codeCartoonAnimate: codeCartoonAnimate,
codeCartoonSelectDifferentCharacter: codeCartoonSelectDifferentCharacter,
codeCartoonUseMinusSign: codeCartoonUseMinusSign,
codeCartoonGrowShrink: codeCartoonGrowShrink,
codeCartoonSelectDifferentCharacter2: codeCartoonSelectDifferentCharacter2,
codeCartoonJump: codeCartoonJump,
codeCartoonChangeScenes: codeCartoonChangeScenes,
codeCartoonGlideAround: codeCartoonGlideAround,
codeCartoonChangeCostumes: codeCartoonChangeCostumes,
codeCartoonChooseMoreCharacters: codeCartoonChooseMoreCharacters,
// Talking Tales
talesAddExtension: talesAddExtension,
talesChooseSprite: talesChooseSprite,
talesSaySomething: talesSaySomething,
talesAskAnswer: talesAskAnswer,
talesChooseBackdrop: talesChooseBackdrop,
talesSwitchBackdrop: talesSwitchBackdrop,
talesChooseAnotherSprite: talesChooseAnotherSprite,
talesMoveAround: talesMoveAround,
talesChooseAnotherBackdrop: talesChooseAnotherBackdrop,
talesAnimateTalking: talesAnimateTalking,
talesChooseThirdBackdrop: talesChooseThirdBackdrop,
talesChooseSound: talesChooseSound,
talesDanceMoves: talesDanceMoves
};
export {enImages};
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Loaded 100 of 219 files, more files were not shown because too many files have changed in this diff. Show more