├── .babelrc
├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bugreport.md
│ └── feature.md
└── workflows
│ ├── codeql-analysis.yml
│ └── publish.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── Contributor License Agreement
├── LICENSE
├── README.md
├── TRADEMARK
├── docs
├── project_state_diagram.svg
└── project_state_example.png
├── gen-meta.js
├── package.json
├── prune-gh-pages.sh
├── renovate.json5
├── src
├── .eslintrc.js
├── components
│ ├── about-modal
│ │ ├── about-modal.css
│ │ ├── about-modal.jsx
│ │ ├── clipcc3_logo.svg
│ │ ├── discord.svg
│ │ ├── github.svg
│ │ ├── qq.svg
│ │ └── telegram.svg
│ ├── action-menu
│ │ ├── action-menu.css
│ │ ├── action-menu.jsx
│ │ ├── icon--backdrop.svg
│ │ ├── icon--file-upload.svg
│ │ ├── icon--paint.svg
│ │ ├── icon--search.svg
│ │ ├── icon--sprite.svg
│ │ └── icon--surprise.svg
│ ├── alerts
│ │ ├── alert.css
│ │ ├── alert.jsx
│ │ ├── alerts.css
│ │ ├── alerts.jsx
│ │ ├── inline-message.css
│ │ └── inline-message.jsx
│ ├── asset-panel
│ │ ├── asset-panel.css
│ │ ├── asset-panel.jsx
│ │ ├── icon--add-backdrop-lib.svg
│ │ ├── icon--add-blank-costume.svg
│ │ ├── icon--add-costume-lib.svg
│ │ ├── icon--add-sound-lib.svg
│ │ ├── icon--add-sound-record.svg
│ │ ├── icon--sound-rtl.svg
│ │ ├── icon--sound.svg
│ │ ├── selector.css
│ │ ├── selector.jsx
│ │ └── sortable-asset.jsx
│ ├── audio-trimmer
│ │ ├── audio-selector.jsx
│ │ ├── audio-trimmer.css
│ │ ├── audio-trimmer.jsx
│ │ ├── icon--handle.svg
│ │ ├── playhead.jsx
│ │ └── selection-handle.jsx
│ ├── backpack
│ │ ├── backpack.css
│ │ └── backpack.jsx
│ ├── blocks
│ │ ├── blocks.css
│ │ └── blocks.jsx
│ ├── box
│ │ ├── box.css
│ │ └── box.jsx
│ ├── browser-modal
│ │ ├── browser-modal.css
│ │ ├── browser-modal.jsx
│ │ └── unsupported-browser.svg
│ ├── button
│ │ ├── button.css
│ │ └── button.jsx
│ ├── cards
│ │ ├── card.css
│ │ ├── cards.jsx
│ │ ├── icon--close.svg
│ │ ├── icon--expand.svg
│ │ ├── icon--next.svg
│ │ ├── icon--prev.svg
│ │ └── icon--shrink.svg
│ ├── checkbox
│ │ ├── check.svg
│ │ ├── checkbox.css
│ │ └── checkbox.jsx
│ ├── close-button
│ │ ├── close-button.css
│ │ ├── close-button.jsx
│ │ ├── icon--close-orange.svg
│ │ └── icon--close.svg
│ ├── coming-soon
│ │ ├── aww-cat.png
│ │ ├── coming-soon.css
│ │ ├── coming-soon.jsx
│ │ └── cool-cat.png
│ ├── connection-modal
│ │ ├── auto-scanning-step.jsx
│ │ ├── connected-step.jsx
│ │ ├── connecting-step.jsx
│ │ ├── connection-modal.css
│ │ ├── connection-modal.jsx
│ │ ├── dots.jsx
│ │ ├── error-step.jsx
│ │ ├── icons
│ │ │ ├── back.svg
│ │ │ ├── bluetooth-white.svg
│ │ │ ├── bluetooth.svg
│ │ │ ├── cancel.svg
│ │ │ ├── close.svg
│ │ │ ├── help.svg
│ │ │ ├── refresh.svg
│ │ │ ├── scratchlink.svg
│ │ │ └── searching.png
│ │ ├── peripheral-tile.jsx
│ │ ├── scanning-step.jsx
│ │ └── unavailable-step.jsx
│ ├── context-menu
│ │ ├── context-menu.css
│ │ └── context-menu.jsx
│ ├── contributor-modal
│ │ ├── contributor-list.js
│ │ ├── contributor-modal.css
│ │ └── contributor-modal.jsx
│ ├── controls
│ │ ├── controls.css
│ │ └── controls.jsx
│ ├── crash-message
│ │ ├── crash-message.css
│ │ ├── crash-message.jsx
│ │ └── reload.svg
│ ├── custom-procedures
│ │ ├── custom-procedures.css
│ │ ├── custom-procedures.jsx
│ │ ├── icon--boolean-input.svg
│ │ ├── icon--label.svg
│ │ └── icon--text-input.svg
│ ├── delete-button
│ │ ├── delete-button.css
│ │ ├── delete-button.jsx
│ │ └── icon--delete.svg
│ ├── direction-picker
│ │ ├── dial.css
│ │ ├── dial.jsx
│ │ ├── direction-picker.css
│ │ ├── direction-picker.jsx
│ │ ├── icon--all-around.svg
│ │ ├── icon--dial.svg
│ │ ├── icon--dont-rotate.svg
│ │ ├── icon--handle.svg
│ │ └── icon--left-right.svg
│ ├── divider
│ │ ├── divider.css
│ │ └── divider.jsx
│ ├── drag-layer
│ │ ├── drag-layer.css
│ │ └── drag-layer.jsx
│ ├── filter
│ │ ├── filter.css
│ │ ├── filter.jsx
│ │ ├── icon--filter.svg
│ │ └── icon--x.svg
│ ├── forms
│ │ ├── buffered-input-hoc.jsx
│ │ ├── input.css
│ │ ├── input.jsx
│ │ ├── label.css
│ │ └── label.jsx
│ ├── green-flag
│ │ ├── green-flag.css
│ │ ├── green-flag.jsx
│ │ └── icon--green-flag.svg
│ ├── gui
│ │ ├── gui.css
│ │ ├── gui.jsx
│ │ ├── icon--code.svg
│ │ ├── icon--costumes.svg
│ │ ├── icon--extensions.svg
│ │ └── icon--sounds.svg
│ ├── icon-button
│ │ ├── icon-button.css
│ │ └── icon-button.jsx
│ ├── language-selector
│ │ ├── language-icon.svg
│ │ ├── language-selector.css
│ │ └── language-selector.jsx
│ ├── library-item
│ │ ├── bluetooth.svg
│ │ ├── internet-connection.svg
│ │ ├── lib-icon--sound-rtl.svg
│ │ ├── lib-icon--sound.svg
│ │ ├── library-item.css
│ │ └── library-item.jsx
│ ├── library
│ │ ├── library.css
│ │ └── library.jsx
│ ├── load-error-modal
│ │ ├── load-error-modal.css
│ │ └── load-error-modal.jsx
│ ├── loader
│ │ ├── blocks-loader.jsx
│ │ ├── bottom-block.svg
│ │ ├── loader.css
│ │ ├── loader.jsx
│ │ ├── middle-block.svg
│ │ └── top-block.svg
│ ├── loupe
│ │ ├── loupe.css
│ │ └── loupe.jsx
│ ├── menu-bar
│ │ ├── account-nav.css
│ │ ├── account-nav.jsx
│ │ ├── author-info.css
│ │ ├── author-info.jsx
│ │ ├── clipcc3_logo_white.svg
│ │ ├── community-button.css
│ │ ├── community-button.jsx
│ │ ├── dropdown-caret.svg
│ │ ├── icon--about.svg
│ │ ├── icon--mystuff.png
│ │ ├── icon--profile.png
│ │ ├── icon--remix.svg
│ │ ├── icon--see-community.svg
│ │ ├── login-dropdown.css
│ │ ├── login-dropdown.jsx
│ │ ├── menu-bar-menu.jsx
│ │ ├── menu-bar.css
│ │ ├── menu-bar.jsx
│ │ ├── project-title-input.css
│ │ ├── project-title-input.jsx
│ │ ├── save-status.css
│ │ ├── save-status.jsx
│ │ ├── scratch-logo.svg
│ │ ├── share-button.css
│ │ ├── share-button.jsx
│ │ ├── user-avatar.css
│ │ └── user-avatar.jsx
│ ├── menu
│ │ ├── menu.css
│ │ └── menu.jsx
│ ├── message-box-modal
│ │ ├── message-box-modal.css
│ │ └── message-box-modal.jsx
│ ├── meter
│ │ ├── meter.css
│ │ └── meter.jsx
│ ├── mic-indicator
│ │ ├── mic-indicator.css
│ │ ├── mic-indicator.jsx
│ │ └── mic-indicator.svg
│ ├── modal
│ │ ├── modal.css
│ │ └── modal.jsx
│ ├── monitor-list
│ │ ├── monitor-list.css
│ │ └── monitor-list.jsx
│ ├── monitor
│ │ ├── default-monitor.jsx
│ │ ├── large-monitor.jsx
│ │ ├── list-monitor-scroller.jsx
│ │ ├── list-monitor.jsx
│ │ ├── monitor.css
│ │ ├── monitor.jsx
│ │ └── slider-monitor.jsx
│ ├── play-button
│ │ ├── icon--play.svg
│ │ ├── icon--stop.svg
│ │ ├── play-button.css
│ │ └── play-button.jsx
│ ├── prompt
│ │ ├── icon--dropdown-caret.svg
│ │ ├── prompt.css
│ │ └── prompt.jsx
│ ├── question
│ │ ├── icon--enter.svg
│ │ ├── question.css
│ │ └── question.jsx
│ ├── record-modal
│ │ ├── icon--back.svg
│ │ ├── icon--play.svg
│ │ ├── icon--stop-playback.svg
│ │ ├── icon--stop-recording.svg
│ │ ├── playback-step.jsx
│ │ ├── record-modal.css
│ │ ├── record-modal.jsx
│ │ └── recording-step.jsx
│ ├── select
│ │ ├── chevron-down.svg
│ │ ├── select.css
│ │ └── select.jsx
│ ├── settings-modal
│ │ ├── settings-modal.css
│ │ └── settings-modal.jsx
│ ├── slider-prompt
│ │ ├── slider-prompt.css
│ │ └── slider-prompt.jsx
│ ├── sound-editor
│ │ ├── icon--copy-to-new.svg
│ │ ├── icon--copy.svg
│ │ ├── icon--delete.svg
│ │ ├── icon--echo.svg
│ │ ├── icon--fade-in.svg
│ │ ├── icon--fade-out.svg
│ │ ├── icon--faster.svg
│ │ ├── icon--louder.svg
│ │ ├── icon--mute.svg
│ │ ├── icon--paste.svg
│ │ ├── icon--play.svg
│ │ ├── icon--redo.svg
│ │ ├── icon--reverse.svg
│ │ ├── icon--robot.svg
│ │ ├── icon--slower.svg
│ │ ├── icon--softer.svg
│ │ ├── icon--stop.svg
│ │ ├── icon--trim-confirm.svg
│ │ ├── icon--trim.svg
│ │ ├── icon--undo.svg
│ │ ├── sound-editor.css
│ │ └── sound-editor.jsx
│ ├── spinner
│ │ ├── spinner.css
│ │ └── spinner.jsx
│ ├── sprite-info
│ │ ├── icon--draggable-off.svg
│ │ ├── icon--draggable-on.svg
│ │ ├── icon--hide.svg
│ │ ├── icon--show.svg
│ │ ├── icon--x.svg
│ │ ├── icon--y.svg
│ │ ├── sprite-info.css
│ │ └── sprite-info.jsx
│ ├── sprite-selector-item
│ │ ├── sprite-selector-item.css
│ │ └── sprite-selector-item.jsx
│ ├── sprite-selector
│ │ ├── sprite-list.jsx
│ │ ├── sprite-selector.css
│ │ └── sprite-selector.jsx
│ ├── stage-header
│ │ ├── icon--fullscreen.svg
│ │ ├── icon--large-stage.svg
│ │ ├── icon--small-stage.svg
│ │ ├── icon--unfullscreen.svg
│ │ ├── stage-header.css
│ │ └── stage-header.jsx
│ ├── stage-selector
│ │ ├── stage-selector.css
│ │ └── stage-selector.jsx
│ ├── stage-wrapper
│ │ ├── stage-wrapper.css
│ │ └── stage-wrapper.jsx
│ ├── stage
│ │ ├── stage.css
│ │ └── stage.jsx
│ ├── stop-all
│ │ ├── icon--stop-all.svg
│ │ ├── stop-all.css
│ │ └── stop-all.jsx
│ ├── switch
│ │ ├── switch.css
│ │ └── switch.jsx
│ ├── tag-button
│ │ ├── tag-button.css
│ │ └── tag-button.jsx
│ ├── target-pane
│ │ ├── target-pane.css
│ │ └── target-pane.jsx
│ ├── telemetry-modal
│ │ ├── telemetry-modal-header.png
│ │ ├── telemetry-modal.css
│ │ └── telemetry-modal.jsx
│ ├── text-switch
│ │ ├── text-switch.css
│ │ └── text-switch.jsx
│ ├── turbo-mode
│ │ ├── icon--turbo.svg
│ │ ├── turbo-mode.css
│ │ └── turbo-mode.jsx
│ ├── watermark
│ │ ├── watermark.css
│ │ └── watermark.jsx
│ ├── waveform
│ │ ├── waveform.css
│ │ └── waveform.jsx
│ └── webgl-modal
│ │ ├── unsupported.png
│ │ ├── webgl-modal.css
│ │ └── webgl-modal.jsx
├── containers
│ ├── account-nav.jsx
│ ├── alert.jsx
│ ├── alerts.jsx
│ ├── audio-selector.jsx
│ ├── audio-trimmer.jsx
│ ├── auto-scanning-step.jsx
│ ├── backdrop-library.jsx
│ ├── backpack.jsx
│ ├── blocks.jsx
│ ├── cards.jsx
│ ├── connection-modal.jsx
│ ├── controls.jsx
│ ├── costume-library.jsx
│ ├── costume-tab.jsx
│ ├── custom-procedures.jsx
│ ├── deletion-restorer.jsx
│ ├── direction-picker.jsx
│ ├── dom-element-renderer.jsx
│ ├── drag-layer.jsx
│ ├── error-boundary.jsx
│ ├── extension-library.jsx
│ ├── green-flag-overlay.jsx
│ ├── gui.jsx
│ ├── inline-messages.jsx
│ ├── language-selector.jsx
│ ├── library-item.jsx
│ ├── list-monitor.jsx
│ ├── load-error-modal.jsx
│ ├── menu-bar-hoc.jsx
│ ├── menu-item.jsx
│ ├── menu.jsx
│ ├── modal.jsx
│ ├── monitor-list.jsx
│ ├── monitor.jsx
│ ├── paint-editor-wrapper.jsx
│ ├── play-button.jsx
│ ├── playback-step.jsx
│ ├── project-watcher.jsx
│ ├── prompt.jsx
│ ├── question.jsx
│ ├── record-modal.jsx
│ ├── recording-step.jsx
│ ├── sb-file-uploader.jsx
│ ├── sb3-downloader.jsx
│ ├── scanning-step.jsx
│ ├── settings-modal.jsx
│ ├── slider-monitor.jsx
│ ├── slider-prompt.jsx
│ ├── sound-editor.jsx
│ ├── sound-library.jsx
│ ├── sound-tab.jsx
│ ├── sprite-info.jsx
│ ├── sprite-library.jsx
│ ├── sprite-selector-item.jsx
│ ├── stage-header.jsx
│ ├── stage-selector.jsx
│ ├── stage-wrapper.jsx
│ ├── stage.jsx
│ ├── tag-button.jsx
│ ├── target-highlight.jsx
│ ├── target-pane.jsx
│ ├── tips-library.jsx
│ ├── turbo-mode.jsx
│ ├── watermark.jsx
│ └── webgl-modal.jsx
├── css
│ ├── colors.css
│ ├── scrollbar.css
│ ├── typography.css
│ ├── units.css
│ └── z-index.css
├── examples
│ └── extensions
│ │ ├── .eslintrc.js
│ │ └── example-extension.js
├── index.js
├── lib
│ ├── alerts
│ │ └── index.jsx
│ ├── analytics.js
│ ├── app-state-hoc.jsx
│ ├── assets
│ │ ├── icon--back.svg
│ │ ├── icon--help.svg
│ │ ├── icon--success.svg
│ │ ├── icon--tutorials.svg
│ │ └── placeholder.svg
│ ├── audio
│ │ ├── audio-buffer-player.js
│ │ ├── audio-effects.js
│ │ ├── audio-recorder.js
│ │ ├── audio-util.js
│ │ ├── effects
│ │ │ ├── echo-effect.js
│ │ │ ├── fade-effect.js
│ │ │ ├── mute-effect.js
│ │ │ ├── robot-effect.js
│ │ │ └── volume-effect.js
│ │ └── shared-audio-context.js
│ ├── backpack-api.js
│ ├── backpack
│ │ ├── block-to-image.js
│ │ ├── code-payload.js
│ │ ├── costume-payload.js
│ │ ├── jpeg-thumbnail.js
│ │ ├── sound-payload.js
│ │ ├── sound-thumbnail.jpg
│ │ └── sprite-payload.js
│ ├── blocks.js
│ ├── bmp-converter.js
│ ├── cloud-manager-hoc.jsx
│ ├── cloud-provider.js
│ ├── collect-metadata.js
│ ├── connected-intl-provider.jsx
│ ├── data-uri-to-blob.js
│ ├── default-project
│ │ ├── 8503e5b283cf0a746478e000a67c7e6f.svg
│ │ ├── ade71c65863ef2b939bf573ab9cb0049.svg
│ │ ├── cd21514d0531fdffb22204e0ec5ed84a.svg
│ │ ├── fd8543abeeba255072da239223d2d342.wav
│ │ ├── index.js
│ │ └── project-data.js
│ ├── define-dynamic-block.js
│ ├── detect-locale.js
│ ├── download-blob.js
│ ├── drag-constants.js
│ ├── drag-recognizer.js
│ ├── drag-utils.js
│ ├── drop-area-hoc.jsx
│ ├── empty-assets.js
│ ├── error-boundary-hoc.jsx
│ ├── extension-api.js
│ ├── extension-manager.js
│ ├── file-uploader.js
│ ├── font-loader-hoc.jsx
│ ├── get-costume-url.js
│ ├── gif-decoder.js
│ ├── hash-parser-hoc.jsx
│ ├── import-csv.js
│ ├── isScratchDesktop.js
│ ├── l10n.js
│ ├── layout-constants.js
│ ├── lazy-blocks-hoc.jsx
│ ├── lazy-blocks.js
│ ├── libraries
│ │ ├── .eslintrc.js
│ │ ├── async-load-libraries.js
│ │ ├── backdrop-tags.js
│ │ ├── backdrops.json
│ │ ├── costumes.json
│ │ ├── decks
│ │ │ ├── en-steps.js
│ │ │ ├── index.jsx
│ │ │ ├── thumbnails
│ │ │ │ └── getting-started-asl.png
│ │ │ ├── translate-image.js
│ │ │ └── translate-video.js
│ │ ├── extensions
│ │ │ ├── HTTPIO
│ │ │ │ ├── HTTPIO.png
│ │ │ │ ├── HTTPIO_icon.svg
│ │ │ │ └── clipcc.httpio-small.svg
│ │ │ ├── JSON
│ │ │ │ ├── JSON.png
│ │ │ │ ├── JSON_icon.svg
│ │ │ │ └── ccjson-small.svg
│ │ │ ├── boost
│ │ │ │ ├── boost-button-illustration.svg
│ │ │ │ ├── boost-illustration.svg
│ │ │ │ ├── boost-small.svg
│ │ │ │ └── boost.png
│ │ │ ├── clipcc
│ │ │ │ ├── CCUnknownExtension.jpg
│ │ │ │ └── CCUnknownExtension.svg
│ │ │ ├── ev3
│ │ │ │ ├── ev3-hub-illustration.svg
│ │ │ │ ├── ev3-small.svg
│ │ │ │ └── ev3.png
│ │ │ ├── gdxfor
│ │ │ │ ├── gdxfor-illustration.svg
│ │ │ │ ├── gdxfor-small.svg
│ │ │ │ └── gdxfor.png
│ │ │ ├── index.jsx
│ │ │ ├── libra
│ │ │ │ ├── Libra-small.svg
│ │ │ │ └── Libra.png
│ │ │ ├── makeymakey
│ │ │ │ ├── makeymakey-small.svg
│ │ │ │ └── makeymakey.png
│ │ │ ├── microbit
│ │ │ │ ├── microbit-illustration.svg
│ │ │ │ ├── microbit-small.svg
│ │ │ │ └── microbit.png
│ │ │ ├── music
│ │ │ │ ├── music-small.svg
│ │ │ │ └── music.png
│ │ │ ├── pen
│ │ │ │ ├── pen-old.png
│ │ │ │ ├── pen-small.svg
│ │ │ │ └── pen.png
│ │ │ ├── speech2text
│ │ │ │ └── speech.png
│ │ │ ├── text2speech
│ │ │ │ ├── text2speech-old.png
│ │ │ │ ├── text2speech-small.svg
│ │ │ │ └── text2speech.png
│ │ │ ├── translate
│ │ │ │ ├── translate-small.png
│ │ │ │ └── translate.png
│ │ │ ├── upload
│ │ │ │ └── upload.png
│ │ │ ├── videoSensing
│ │ │ │ ├── video-sensing-small.svg
│ │ │ │ └── video-sensing.png
│ │ │ └── wedo2
│ │ │ │ ├── wedo-button-illustration.svg
│ │ │ │ ├── wedo-illustration.svg
│ │ │ │ ├── wedo-small.svg
│ │ │ │ └── wedo.png
│ │ ├── sound-tags.js
│ │ ├── sounds.json
│ │ ├── sprite-tags.js
│ │ ├── sprites.json
│ │ ├── tag-messages.js
│ │ └── tutorial-tags.js
│ ├── locale-utils.js
│ ├── localization-hoc.jsx
│ ├── log.js
│ ├── make-toolbox-xml.js
│ ├── math.js
│ ├── monitor-adapter.js
│ ├── opcode-labels.js
│ ├── project-fetcher-hoc.jsx
│ ├── project-saver-hoc.jsx
│ ├── query-parser-hoc.jsx
│ ├── randomize-sprite-position.js
│ ├── save-project-to-server.js
│ ├── sb-file-uploader-hoc.jsx
│ ├── screen-utils.js
│ ├── shared-messages.js
│ ├── sortable-hoc.jsx
│ ├── storage.js
│ ├── supported-browser.js
│ ├── tablet-full-screen.js
│ ├── throttled-property-hoc.jsx
│ ├── titled-hoc.jsx
│ ├── touch-utils.js
│ ├── tutorial-from-url.js
│ ├── variable-utils.js
│ ├── video
│ │ ├── camera.js
│ │ └── video-provider.js
│ ├── vm-listener-hoc.jsx
│ └── vm-manager-hoc.jsx
├── playground
│ ├── blocks-only.css
│ ├── blocks-only.jsx
│ ├── compatibility-testing.jsx
│ ├── index.css
│ ├── index.ejs
│ ├── index.jsx
│ ├── player.css
│ ├── player.jsx
│ └── render-gui.jsx
├── reducers
│ ├── alerts.js
│ ├── asset-drag.js
│ ├── block-drag.js
│ ├── cards.js
│ ├── color-picker.js
│ ├── connection-modal.js
│ ├── custom-procedures.js
│ ├── editor-tab.js
│ ├── extension-settings.js
│ ├── extension.js
│ ├── fonts-loaded.js
│ ├── gui.js
│ ├── hovered-target.js
│ ├── load-error.js
│ ├── locales.js
│ ├── menus.js
│ ├── mic-indicator.js
│ ├── modals.js
│ ├── mode.js
│ ├── monitor-layout.js
│ ├── monitors.js
│ ├── project-changed.js
│ ├── project-state.js
│ ├── project-title.js
│ ├── restore-deletion.js
│ ├── settings.js
│ ├── stage-size.js
│ ├── targets.js
│ ├── timeout.js
│ ├── toolbox.js
│ ├── vm-status.js
│ ├── vm.js
│ └── workspace-metrics.js
└── test.js
├── static
├── bottom-block.svg
├── clipcc_logo144.png
├── clipcc_logo48.png
├── clipcc_logo72.png
├── clipcc_logo96.png
├── favicon.ico
├── manifest.webmanifest
├── middle-block.svg
├── social.jpg
├── sw.js
└── top-block.svg
├── test
├── .eslintrc.js
├── __mocks__
│ ├── audio-buffer-player.js
│ ├── audio-effects.js
│ ├── editor-msgs-mock.js
│ ├── fileMock.js
│ └── styleMock.js
├── fixtures
│ ├── 100-100.svg
│ ├── bmpfile.bmp
│ ├── corrupt-bmp.sb3
│ ├── corrupt-bmp.sprite3
│ ├── corrupt-from-scratch3.svg
│ ├── corrupt-svg.sb2
│ ├── corrupt-svg.sb3
│ ├── corrupt-svg.sprite3
│ ├── corrupted-svg.sprite2
│ ├── gh-3582-png.png
│ ├── missing-bmp.sb3
│ ├── missing-bmp.sprite3
│ ├── missing-sprite-svg.sb3
│ ├── missing-svg.sb2
│ ├── missing-svg.sprite2
│ ├── missing-svg.sprite3
│ ├── movie.wav
│ ├── paddleball.gif
│ ├── project1.sb3
│ ├── scratch2-corrupted.svg
│ └── sneaker.wav
├── helpers
│ ├── enzyme-setup.js
│ ├── intl-helpers.jsx
│ └── selenium-helper.js
├── integration
│ ├── backdrops.test.js
│ ├── backpack.test.js
│ ├── blocks.test.js
│ ├── connection-modal.test.js
│ ├── costumes.test.js
│ ├── examples.test.js
│ ├── how-tos.test.js
│ ├── localization.test.js
│ ├── menu-bar.test.js
│ ├── project-loading.test.js
│ ├── project-state.test.js
│ ├── sb-file-uploader-hoc.test.js
│ ├── sounds.test.js
│ ├── sprites.test.js
│ ├── stage-size.test.js
│ └── tutorials-shortcut.test.js
├── smoke
│ └── browser.test.js
└── unit
│ ├── components
│ ├── __snapshots__
│ │ ├── button.test.jsx.snap
│ │ ├── icon-button.test.jsx.snap
│ │ ├── sound-editor.test.jsx.snap
│ │ └── sprite-selector-item.test.jsx.snap
│ ├── button.test.jsx
│ ├── cards.test.jsx
│ ├── controls.test.jsx
│ ├── icon-button.test.jsx
│ ├── menu-bar.test.jsx
│ ├── monitor-list.test.jsx
│ ├── sound-editor.test.jsx
│ └── sprite-selector-item.test.jsx
│ ├── containers
│ ├── menu-bar-hoc.test.jsx
│ ├── save-status.test.jsx
│ ├── slider-prompt.test.jsx
│ ├── sound-editor.test.jsx
│ └── sprite-selector-item.test.jsx
│ ├── reducers
│ ├── alerts-reducer.test.js
│ ├── mode-reducer.test.js
│ ├── monitor-layout-reducer.test.js
│ ├── project-state-reducer.test.js
│ └── workspace-metrics-reducer.test.js
│ └── util
│ ├── audio-context.test.js
│ ├── audio-effects.test.js
│ ├── audio-util.test.js
│ ├── cloud-manager-hoc.test.jsx
│ ├── cloud-provider.test.js
│ ├── code-payload.test.js
│ ├── default-project.test.js
│ ├── define-dynamic-block.test.js
│ ├── detect-locale.test.js
│ ├── drag-recognizer.test.js
│ ├── drag-utils.test.js
│ ├── get-costume-url.test.js
│ ├── hash-project-loader-hoc.test.jsx
│ ├── opcode-labels.test.js
│ ├── project-fetcher-hoc.test.jsx
│ ├── project-saver-hoc.test.jsx
│ ├── sb-file-uploader-hoc.test.jsx
│ ├── throttled-property-hoc.test.jsx
│ ├── translate-video.test.js
│ ├── tutorial-from-url.test.js
│ ├── vm-listener-hoc.test.jsx
│ └── vm-manager-hoc.test.jsx
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "@babel/plugin-syntax-dynamic-import",
4 | "@babel/plugin-transform-async-to-generator",
5 | "@babel/plugin-proposal-object-rest-spread",
6 | ["react-intl", {
7 | "messagesDir": "./translations/messages/"
8 | }]],
9 | "presets": [
10 | ["@babel/preset-env"],
11 | "@babel/preset-react"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | Safari >= 12
2 | iOS >= 12
3 | chrome >= 72
4 | firefox >= 70
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | charset = utf-8
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 |
10 | [*.{js,html}]
11 | indent_style = space
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | build/*
3 | dist/*
4 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['scratch', 'scratch/node']
3 | };
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly specify line endings for as many files as possible.
5 | # People who (for example) rsync between Windows and Linux need this.
6 |
7 | # File types which we know are binary
8 |
9 | # Treat SVG files as binary so that their contents don't change due to line
10 | # endings. The contents of SVGs must not change from the way they're stored
11 | # on assets.scratch.mit.edu so that MD5 calculations don't change.
12 | *.svg binary
13 |
14 | # Prefer LF for most file types
15 | *.css text eol=lf
16 | *.frag text eol=lf
17 | *.htm text eol=lf
18 | *.html text eol=lf
19 | *.iml text eol=lf
20 | *.js text eol=lf
21 | *.js.map text eol=lf
22 | *.json text eol=lf
23 | *.json5 text eol=lf
24 | *.jsx text eol=lf
25 | *.md text eol=lf
26 | *.vert text eol=lf
27 | *.xml text eol=lf
28 | *.yml text eol=lf
29 |
30 | # Prefer LF for these files
31 | .editorconfig text eol=lf
32 | .eslintrc text eol=lf
33 | .gitattributes text eol=lf
34 | .gitignore text eol=lf
35 | .gitmodules text eol=lf
36 | LICENSE text eol=lf
37 | Makefile text eol=lf
38 | README text eol=lf
39 | TRADEMARK text eol=lf
40 |
41 | # Use CRLF for Windows-specific file types
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bugreport.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 漏洞反馈
3 | about: 反馈在使用cc过程中遇到的问题
4 | ---
5 |
6 |
7 |
8 |
9 | ### 描述一下bug
10 |
11 | ## 请叙述bug触发步骤
12 |
13 | ## 预期的行为
14 |
15 | ## 截图或视频
16 |
17 | ## ClipCC版本
18 |
19 | ## 附加内容
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 新功能
3 | about: 让你cc有更多你想要的功能
4 | ---
5 |
6 |
7 |
8 |
9 | ### 描述一下你想要的功能
10 |
11 | ### 你为什么想要实现它?(可选)
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Publish Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 | workflow_dispatch:
10 |
11 | jobs:
12 | build:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-node@v2
17 | with:
18 | node-version: 14
19 | - run: yarn
20 | - run: yarn run build:dist
21 | env:
22 | NODE_ENV: production
23 | - name: Upload results
24 | uses: actions/upload-artifact@v2
25 | with:
26 | name: dist
27 | path: dist
28 |
29 | publish-npm:
30 | needs: build
31 | runs-on: ubuntu-latest
32 | steps:
33 | - uses: actions/checkout@v2
34 | - uses: actions/setup-node@v2
35 | with:
36 | node-version: 14
37 | registry-url: https://registry.npmjs.org/
38 | always-auth: true
39 | - name: Download results
40 | uses: actions/download-artifact@v2
41 | with:
42 | name: dist
43 | path: dist
44 | - run: yarn publish
45 | env:
46 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac OS
2 | .DS_Store
3 |
4 | # NPM
5 | /node_modules
6 | npm-*
7 | yarn-error.log
8 |
9 | # Testing
10 | /.nyc_output
11 | /coverage
12 |
13 | # Build
14 | /build
15 | /dist
16 | /.opt-in
17 |
18 | # Generated translation files
19 | /translations
20 | /locale
21 |
22 | /src/lib/app-info.js
23 |
24 | .vscode
25 | static/sw.build.js
26 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Mac OS
2 | .DS_Store
3 |
4 | # NPM
5 | /node_modules
6 | npm-*
7 |
8 | # Double copies of all the static assets and tutorial gifs
9 | /src
10 |
11 | # Testing
12 | /.nyc_output
13 | /coverage
14 | /test
15 |
16 | # Build
17 | /.opt-in
18 | /build
19 |
20 | # generated translation files
21 | /translations
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '10'
4 |
5 | before_install:
6 | - npm install yarn -g
7 |
8 | install:
9 | - yarn install
10 |
11 | script:
12 | - yarn run build
13 |
--------------------------------------------------------------------------------
/TRADEMARK:
--------------------------------------------------------------------------------
1 | The Scratch trademarks, including the Scratch name, logo, the Scratch Cat, Gobo, Pico, Nano, Tera and Giga graphics (the "Marks"), are property of the Massachusetts Institute of Technology (MIT). Marks may not be used to endorse or promote products derived from this software without specific prior written permission.
2 |
--------------------------------------------------------------------------------
/docs/project_state_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/docs/project_state_example.png
--------------------------------------------------------------------------------
/renovate.json5:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 |
4 | "extends": [
5 | "github>LLK/scratch-renovate-config:conservative"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/src/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | module.exports = {
3 | root: true,
4 | extends: ['scratch', 'scratch/es6', 'scratch/react', 'plugin:import/errors'],
5 | env: {
6 | browser: true
7 | },
8 | globals: {
9 | process: true
10 | },
11 | rules: {
12 | 'import/no-mutable-exports': 'error',
13 | 'import/no-commonjs': 'error',
14 | 'import/no-amd': 'error',
15 | 'import/no-nodejs-modules': 'error',
16 | 'react/jsx-no-literals': 'error',
17 | 'no-confusing-arrow': ['error', {
18 | allowParens: true
19 | }],
20 | 'camelcase': [2, {
21 | properties: 'never', // This is from the base `scratch` config
22 | allow: ['^UNSAFE_'] // Allow until migrated to new lifecycle methods
23 | }]
24 | },
25 | settings: {
26 | 'react': {
27 | version: 'detect'
28 | },
29 | 'import/resolver': {
30 | webpack: {
31 | config: path.resolve(__dirname, '../webpack.config.js')
32 | }
33 | }
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/about-modal/about-modal.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .modal-content {
5 | width: 450px;
6 | }
7 |
8 | .body {
9 | background: $ui-white;
10 | padding: 1.5rem 2.25rem;
11 | }
12 |
13 | [theme='dark'] .body {
14 | background: $ui-primary-dark;
15 | color: $text-primary-dark;
16 | }
17 |
18 | .logo {
19 | display: block;
20 | margin: 0px auto;
21 | width: 100%;
22 | max-width: 200px;
23 | }
24 |
25 | .contact {
26 | display: flex;
27 | justify-content: space-around;
28 | padding: 0 5rem;
29 | }
30 |
31 | .contact > a > img {
32 | width: 2rem;
33 | }
34 |
35 | [theme='dark'] .logo {
36 | filter: invert(100%) brightness(10000%);
37 | }
--------------------------------------------------------------------------------
/src/components/about-modal/github.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/alerts/alerts.css:
--------------------------------------------------------------------------------
1 | .alerts-inner-container {
2 | min-width: 200px;
3 | max-width: 548px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/alerts/inline-message.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .inline-message {
5 | color: $ui-white;
6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
7 | display: flex;
8 | justify-content: flex-end;
9 | align-items: center;
10 | font-size: .8125rem;
11 | }
12 |
13 | .success {
14 | color: $ui-white-dim;
15 | }
16 |
17 | .info {
18 | color: $ui-white;
19 | }
20 |
21 | .warn {
22 | color: $error-light;
23 | }
24 |
25 | .spinner {
26 | margin-right: $space;
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/alerts/inline-message.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import Spinner from '../spinner/spinner.jsx';
6 | import {AlertLevels} from '../../lib/alerts/index.jsx';
7 |
8 | import styles from './inline-message.css';
9 |
10 | const InlineMessageComponent = ({
11 | content,
12 | iconSpinner,
13 | level
14 | }) => (
15 |
18 | {/* TODO: implement Rtl handling */}
19 | {iconSpinner && (
20 |
25 | )}
26 | {content}
27 |
28 | );
29 |
30 | InlineMessageComponent.propTypes = {
31 | content: PropTypes.element,
32 | iconSpinner: PropTypes.bool,
33 | level: PropTypes.string
34 | };
35 |
36 | InlineMessageComponent.defaultProps = {
37 | level: AlertLevels.INFO
38 | };
39 |
40 | export default InlineMessageComponent;
41 |
--------------------------------------------------------------------------------
/src/components/asset-panel/asset-panel.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 | @import "../../css/colors.css";
3 |
4 | .wrapper {
5 | display: flex;
6 | flex-grow: 1;
7 | border: 1px solid $ui-black-transparent;
8 | background: white;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | font-size: 0.85rem;
11 | }
12 |
13 | [theme='dark'] .wrapper {
14 | background: $motion-primary-dark;
15 | color: $text-primary-dark;
16 | }
17 |
18 | [dir="ltr"] .wrapper {
19 | border-top-right-radius: $space;
20 | border-bottom-right-radius: $space;
21 | }
22 |
23 | [dir="rtl"] .wrapper {
24 | border-top-left-radius: $space;
25 | border-bottom-left-radius: $space;
26 | }
27 |
28 | .detail-area {
29 | display: flex;
30 | flex-grow: 1;
31 | flex-shrink: 1;
32 | overflow: visible;
33 | }
34 |
35 | [dir="ltr"] .detail-area {
36 | border-left: 1px solid $ui-black-transparent;
37 | }
38 |
39 | [dir="rtl"] .detail-area {
40 | border-right: 1px solid $ui-black-transparent;
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/asset-panel/asset-panel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Box from '../box/box.jsx';
4 | import Selector from './selector.jsx';
5 | import styles from './asset-panel.css';
6 |
7 | const AssetPanel = props => (
8 |
9 |
13 |
14 | {props.children}
15 |
16 |
17 | );
18 |
19 | AssetPanel.propTypes = {
20 | ...Selector.propTypes
21 | };
22 |
23 | export default AssetPanel;
24 |
--------------------------------------------------------------------------------
/src/components/asset-panel/sortable-asset.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import bindAll from 'lodash.bindall';
5 |
6 | class SortableAsset extends React.Component {
7 | constructor (props) {
8 | super(props);
9 | bindAll(this, [
10 | 'setRef'
11 | ]);
12 | this.ref = React.createRef();
13 | }
14 | componentDidMount () {
15 | this.props.onAddSortable(this.ref);
16 | }
17 | componentWillUnmount () {
18 | this.props.onRemoveSortable(this.ref);
19 | }
20 | setRef (ref) {
21 | this.ref = ref;
22 | }
23 | render () {
24 | return (
25 |
32 | {this.props.children}
33 |
34 | );
35 | }
36 | }
37 |
38 | SortableAsset.propTypes = {
39 | children: PropTypes.node.isRequired,
40 | className: PropTypes.string,
41 | index: PropTypes.number.isRequired,
42 | onAddSortable: PropTypes.func.isRequired,
43 | onRemoveSortable: PropTypes.func.isRequired
44 | };
45 |
46 | export default SortableAsset;
47 |
--------------------------------------------------------------------------------
/src/components/audio-trimmer/icon--handle.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/src/components/audio-trimmer/playhead.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import styles from './audio-trimmer.css';
5 |
6 | const Playhead = props => (
7 |
15 | );
16 |
17 | Playhead.propTypes = {
18 | playbackPosition: PropTypes.number
19 | };
20 |
21 | export default Playhead;
22 |
--------------------------------------------------------------------------------
/src/components/audio-trimmer/selection-handle.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import Box from '../box/box.jsx';
5 | import styles from './audio-trimmer.css';
6 | import handleIcon from './icon--handle.svg';
7 |
8 | const SelectionHandle = props => (
9 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | SelectionHandle.propTypes = {
24 | handleStyle: PropTypes.string,
25 | onMouseDown: PropTypes.func
26 | };
27 |
28 | export default SelectionHandle;
29 |
--------------------------------------------------------------------------------
/src/components/blocks/blocks.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import classNames from 'classnames';
3 | import React from 'react';
4 | import Box from '../box/box.jsx';
5 | import styles from './blocks.css';
6 |
7 | const BlocksComponent = props => {
8 | const {
9 | containerRef,
10 | dragOver,
11 | ...componentProps
12 | } = props;
13 | return (
14 |
21 | );
22 | };
23 | BlocksComponent.propTypes = {
24 | containerRef: PropTypes.func,
25 | dragOver: PropTypes.bool
26 | };
27 | export default BlocksComponent;
28 |
--------------------------------------------------------------------------------
/src/components/box/box.css:
--------------------------------------------------------------------------------
1 | @import "../../css/scrollbar.css";
2 |
3 | .box {
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/button/button.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 |
3 | .outlined-button {
4 | cursor: pointer;
5 | border-radius: $form-radius;
6 | font-weight: bold;
7 | display: flex;
8 | flex-direction: row;
9 | align-items: center;
10 | padding-left: .75rem;
11 | padding-right: .75rem;
12 | user-select: none;
13 | }
14 |
15 | .icon {
16 | height: 1.5rem;
17 | }
18 |
19 | [dir="ltr"] .icon {
20 | margin-right: .5rem;
21 | }
22 |
23 | [dir="rtl"] .icon {
24 | margin-left: .5rem;
25 | }
26 |
27 | .content {
28 | white-space: nowrap;
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/button/button.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import styles from './button.css';
6 |
7 | const ButtonComponent = ({
8 | className,
9 | disabled,
10 | iconClassName,
11 | iconSrc,
12 | onClick,
13 | children,
14 | ...props
15 | }) => {
16 |
17 | if (disabled) {
18 | onClick = function () {};
19 | }
20 |
21 | const icon = iconSrc && (
22 |
27 | );
28 |
29 | return (
30 |
39 | {icon}
40 | {children}
41 |
42 | );
43 | };
44 |
45 | ButtonComponent.propTypes = {
46 | children: PropTypes.node,
47 | className: PropTypes.string,
48 | disabled: PropTypes.bool,
49 | iconClassName: PropTypes.string,
50 | iconSrc: PropTypes.string,
51 | onClick: PropTypes.func
52 | };
53 |
54 | export default ButtonComponent;
55 |
--------------------------------------------------------------------------------
/src/components/cards/icon--next.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/cards/icon--prev.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/checkbox/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/checkbox/checkbox.css:
--------------------------------------------------------------------------------
1 | @import '../../css/colors.css';
2 |
3 | .label {
4 | padding: 0.5rem 1rem;
5 | display: flex;
6 | align-items: center;
7 | border-radius: 0.3rem;
8 | }
9 |
10 | .label span {
11 | line-height: 32px;
12 | }
13 |
14 | .disabled {
15 | color: darkgrey;
16 | }
17 |
18 | [theme='dark'] .disabled {
19 | color: rgb(213 213 213)!important;
20 | }
21 |
22 | .checkbox {
23 | margin-right: 0.5rem;
24 | background-color: $ui-white;
25 | background-size: 100%;
26 | background-origin: content-box;
27 | background-repeat: no-repeat;
28 | appearance: none;
29 | width: 18px;
30 | height: 18px;
31 | border: 1px solid rgb(92, 92, 92);
32 | border-radius: 4px;
33 | outline: none;
34 | padding: 2px;
35 | transition-duration: .2s;
36 | }
37 |
38 | [theme='dark'] .label{
39 | color: text-primary-dark;
40 | }
41 |
42 | .checkbox:checked {
43 | background-image: url('./check.svg');
44 | background-color: $motion-primary;
45 | border-color: $motion-primary;
46 | }
47 |
48 | .checkbox:disabled {
49 | border-color: darkgrey;
50 | }
51 |
52 | [theme='dark'] .checkbox:disabled {
53 | background-color: darkgrey;
54 | }
55 |
56 | .checkbox:disabled:checked {
57 | background-color: darkgrey;
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/checkbox/checkbox.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 |
5 | import styles from './checkbox.css';
6 |
7 | const CheckboxComponent = ({
8 | disabled,
9 | checked,
10 | onChange,
11 | children,
12 | ...props
13 | }) => (
14 |
27 | );
28 |
29 | CheckboxComponent.propTypes = {
30 | children: PropTypes.node,
31 | className: PropTypes.string,
32 | disabled: PropTypes.bool,
33 | checked: PropTypes.bool,
34 | onChange: PropTypes.func
35 | };
36 |
37 | export default CheckboxComponent;
38 |
--------------------------------------------------------------------------------
/src/components/close-button/icon--close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/coming-soon/aww-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/coming-soon/aww-cat.png
--------------------------------------------------------------------------------
/src/components/coming-soon/cool-cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/coming-soon/cool-cat.png
--------------------------------------------------------------------------------
/src/components/connection-modal/icons/back.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/connection-modal/icons/cancel.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/connection-modal/icons/close.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/connection-modal/icons/searching.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/connection-modal/icons/searching.png
--------------------------------------------------------------------------------
/src/components/context-menu/context-menu.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 | @import "../../css/z-index.css";
4 |
5 | .context-menu {
6 | min-width: 130px;
7 | padding: 5px 0; /* The white strip at the top and bottom of the menu */
8 | margin: 2px 0 0; /* To keep the menu below the cursor comfortably */
9 | font-size: 0.85rem;
10 | text-align: left;
11 | background-color: $ui-white;
12 | border: 1px solid $ui-black-transparent;
13 | border-radius: calc($space / 2);
14 | box-shadow: 0px 0px 5px 1px $ui-black-transparent;
15 | pointer-events: none;
16 | transition: opacity 0.2s ease;
17 | z-index: $z-index-context-menu;
18 | }
19 |
20 | [theme='dark'] .context-menu {
21 | background-color: $ui-primary-dark;
22 | color: $text-primary-dark;
23 | }
24 |
25 | .menu-item {
26 | padding: 8px 12px;
27 | white-space: nowrap;
28 | cursor: pointer;
29 | transition: 0.1s ease;
30 | }
31 |
32 | .menu-item:hover {
33 | background: $motion-primary;
34 | color: white;
35 | }
36 |
37 | .menu-item-bordered {
38 | border-top: 1px solid $ui-black-transparent;
39 | }
40 |
41 | .menu-item-danger:hover {
42 | background: $error-primary;
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/context-menu/context-menu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {ContextMenu, MenuItem} from 'react-contextmenu';
3 | import classNames from 'classnames';
4 |
5 | import styles from './context-menu.css';
6 |
7 | const StyledContextMenu = props => (
8 |
12 | );
13 |
14 | const StyledMenuItem = props => (
15 |
19 | );
20 |
21 | const BorderedMenuItem = props => (
22 |
26 | );
27 |
28 | const DangerousMenuItem = props => (
29 |
33 | );
34 |
35 |
36 | export {
37 | BorderedMenuItem,
38 | DangerousMenuItem,
39 | StyledContextMenu as ContextMenu,
40 | StyledMenuItem as MenuItem
41 | };
42 |
--------------------------------------------------------------------------------
/src/components/contributor-modal/contributor-list.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | export const contributorList = {
3 | alexcui: 'AlexCui',
4 | sinangentoo: 'SinanGentoo',
5 | frank782: 'frank_782',
6 | hydrostic: 'Hydrostic',
7 | solstice23: 'solstice23',
8 | e4361: 'e4361',
9 | sparrowhe: 'SparrowHe',
10 | stevexmh: 'SteveXMH',
11 | waterblock79: 'waterblock79',
12 | jasonxu: 'JasonXu',
13 | zerlight: 'Zerlight',
14 | soilzhu: 'SoilZhu',
15 | bleshi: 'BleShi',
16 | xiaoji4093: 'xiaoji4093',
17 | hotover: 'Hotover',
18 | lmlanmei: 'LanmeiCN',
19 | yuan3old: 'Yuan3old',
20 | someoneyoung: 'Someone_Young',
21 | jasonjia: 'JasonJia',
22 | unknown: 'Unknown contributor',
23 | lyricepic: 'LyricEpic',
24 | cyarice: 'Cya_Rice',
25 | afadian: 'Afadian_c5qE',
26 | mbzzw: 'mbzzw',
27 | editmr: 'Edit Mr.'
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/contributor-modal/contributor-modal.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .modal-content {
5 | width: 600px;
6 | }
7 |
8 | .body {
9 | background: $ui-white;
10 | padding: 1.5rem 2.25rem;
11 | overflow-y: auto;
12 | height: 70vh;
13 | }
14 |
15 | [theme='dark'] .body {
16 | background: $ui-primary-dark;
17 | color: text-primary-dark;
18 | }
19 |
20 | .logo {
21 | display: block;
22 | margin: 0px auto;
23 | width: 100%;
24 | max-width: 200px;
25 | }
26 |
27 | [theme='dark'] .logo {
28 | filter: invert(100%) brightness(10000%);
29 | }
30 |
--------------------------------------------------------------------------------
/src/components/controls/controls.css:
--------------------------------------------------------------------------------
1 | .controls-container {
2 | display: flex;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/crash-message/crash-message.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 | @import "../../css/typography.css";
4 |
5 | .crash-wrapper {
6 | background-color: $motion-primary;
7 | width: 100%;
8 | height: 100%;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | }
13 | .body {
14 | width: 35%;
15 | color: white;
16 | text-align: center;
17 | }
18 |
19 | .reloadButton {
20 | border: 1px solid $motion-primary;
21 | border-radius: 0.25rem;
22 | padding: 0.5rem 2rem;
23 | background: white;
24 | color: $motion-primary;
25 | font-weight: bold;
26 | font-size: 0.875rem;
27 | cursor: pointer;
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/custom-procedures/icon--boolean-input.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/src/components/custom-procedures/icon--text-input.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/src/components/delete-button/delete-button.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | /* wrapper to allow for touch slop if we decide to add it */
5 | .delete-button {
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | user-select: none;
10 | cursor: pointer;
11 | transition: all 0.15s ease-out;
12 | }
13 |
14 | .delete-button-visible {
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | overflow: hidden; /* Mask the icon animation */
19 | width: 1.75rem;
20 | height: 1.75rem;
21 | box-shadow: 0px 0px 0px 2px $motion-transparent;
22 | background-color: $motion-primary;
23 | color: $ui-white;
24 | border-radius: 50%;
25 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
26 | user-select: none;
27 | cursor: pointer;
28 | transition: all 0.15s ease-out;
29 | }
30 |
31 | .delete-icon {
32 | position: relative;
33 | margin: 0.25rem;
34 | user-select: none;
35 | transform-origin: 50%;
36 | }
37 |
--------------------------------------------------------------------------------
/src/components/delete-button/delete-button.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 |
5 | import styles from './delete-button.css';
6 | import deleteIcon from './icon--delete.svg';
7 |
8 | const DeleteButton = props => (
9 |
19 |
20 |

24 |
25 |
26 |
27 | );
28 |
29 | DeleteButton.propTypes = {
30 | className: PropTypes.string,
31 | onClick: PropTypes.func.isRequired,
32 | tabIndex: PropTypes.number
33 | };
34 |
35 | DeleteButton.defaultProps = {
36 | tabIndex: 0
37 | };
38 |
39 | export default DeleteButton;
40 |
--------------------------------------------------------------------------------
/src/components/direction-picker/dial.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .container {
4 | padding: 1rem;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | user-select: none;
9 | }
10 |
11 | .dial-container {
12 | position: relative;
13 | }
14 |
15 | .dial-face, .dial-handle, .gauge {
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | overflow: visible;
20 | }
21 |
22 | .dial-face {
23 | width: 100%;
24 | }
25 |
26 | $dial-size: 40px;
27 |
28 | .dial-handle {
29 | cursor: pointer;
30 | width: $dial-size;
31 | height: $dial-size;
32 | /* Use margin to make positioning via top/left easier */
33 | margin-left: calc($dial-size / -2);
34 | margin-top: calc($dial-size / -2);
35 | }
36 |
37 | .gauge-path {
38 | fill: $motion-transparent;
39 | stroke: $motion-primary;
40 | stroke-width: 1px;
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/direction-picker/direction-picker.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/z-index.css";
3 |
4 | .popover {
5 | z-index: $z-index-direction-picker;
6 | }
7 |
8 | .button-row {
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: center;
12 |
13 | }
14 |
15 | .icon-button {
16 | margin: 0.25rem;
17 | border: none;
18 | background: none;
19 | outline: none;
20 | cursor: pointer;
21 | user-select: none;
22 | }
23 |
24 | .icon-button:active > img {
25 | width: 20px;
26 | height: 20px;
27 | transform: scale(1.15);
28 | }
29 |
30 | .icon-button > img {
31 | transition: transform 0.1s;
32 | filter: grayscale(100%);
33 | }
34 |
35 | .icon-button.active > img {
36 | filter: none;
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/direction-picker/icon--all-around.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/direction-picker/icon--handle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/divider/divider.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .divider {
4 | border-right: 1px dashed $ui-black-transparent;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/divider/divider.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import styles from './divider.css';
6 |
7 | const Divider = ({className}) => (
8 |
9 | );
10 |
11 | Divider.propTypes = {
12 | className: PropTypes.string
13 | };
14 |
15 | export default Divider;
16 |
--------------------------------------------------------------------------------
/src/components/drag-layer/drag-layer.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 | @import "../../css/colors.css";
3 | @import "../../css/z-index.css";
4 |
5 | .drag-layer {
6 | position: fixed;
7 | pointer-events: none;
8 | z-index: $z-index-drag-layer;
9 | left: 0;
10 | top: 0;
11 | width: 100%;
12 | height: 100%;
13 | direction: ltr;
14 | }
15 |
16 | .image-wrapper {
17 | /* Absolute allows wrapper to snuggly fit image */
18 | position: absolute;
19 | }
20 |
21 | .image {
22 | max-width: 80px;
23 | max-height: 80px;
24 | min-width: 50px;
25 | min-height: 50px;
26 |
27 | /* Center the dragging image on the given position */
28 | margin-left: -50%;
29 | margin-top: -50%;
30 |
31 | padding: 0.25rem;
32 | border: 2px solid $motion-primary;
33 | background: $ui-white;
34 | border-radius: 0.5rem;
35 |
36 | /* Use the same drop shadow as stage dragging */
37 | box-shadow: 5px 5px 5px $ui-black-transparent;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/drag-layer/drag-layer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './drag-layer.css';
4 |
5 | /* eslint no-confusing-arrow: ["error", {"allowParens": true}] */
6 | const DragLayer = ({dragging, img, currentOffset}) => (dragging ? (
7 |
8 |
14 |

18 |
19 |
20 | ) : null);
21 |
22 | DragLayer.propTypes = {
23 | currentOffset: PropTypes.shape({
24 | x: PropTypes.number.isRequired,
25 | y: PropTypes.number.isRequired
26 | }),
27 | dragging: PropTypes.bool.isRequired,
28 | img: PropTypes.string
29 | };
30 |
31 | export default DragLayer;
32 |
--------------------------------------------------------------------------------
/src/components/filter/icon--filter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/filter/icon--x.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/forms/input.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 | @import "../../css/colors.css";
3 |
4 | .input-form {
5 | height: 2rem;
6 | padding: 0 0.75rem;
7 |
8 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | font-size: 0.625rem;
10 | font-weight: bold;
11 | color: $text-primary;
12 |
13 | border-width: 1px;
14 | border-style: solid;
15 | border-color: $ui-black-transparent;
16 | border-radius: 0.75rem;
17 |
18 | outline: none;
19 | cursor: text;
20 | transition: 0.25s ease-out; /* @todo: standardize with var */
21 | box-shadow: none;
22 |
23 | /*
24 | For truncating overflowing text gracefully
25 | Min-width is for a bug: https://css-tricks.com/flexbox-truncated-text
26 | @todo: move this out into a mixin or a helper component
27 | */
28 | overflow: hidden;
29 | text-overflow: ellipsis;
30 | white-space: nowrap;
31 | min-width: 0;
32 | }
33 |
34 | [theme='dark'] .input-form {
35 | background-color: $ui-tertiary-dark;
36 | color: white;
37 | }
38 |
39 | .input-form:hover {
40 | border-color: $motion-primary;
41 | }
42 |
43 | .input-form:focus {
44 | border-color: $motion-primary;
45 | box-shadow: 0 0 0 0.25rem $motion-transparent;
46 | }
47 |
48 | .input-small {
49 | width: 3rem;
50 | padding: 0 0.5rem;
51 | text-overflow: clip;
52 | text-align: center;
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/forms/input.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 |
5 | import styles from './input.css';
6 |
7 |
8 | const Input = props => {
9 | const {small, ...componentProps} = props;
10 | return (
11 |
21 | );
22 | };
23 |
24 | Input.propTypes = {
25 | className: PropTypes.string,
26 | small: PropTypes.bool
27 | };
28 |
29 | Input.defaultProps = {
30 | small: false
31 | };
32 |
33 | export default Input;
34 |
--------------------------------------------------------------------------------
/src/components/forms/label.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 | @import "../../css/colors.css";
3 |
4 | .input-group {
5 | display: inline-flex;
6 | flex-direction: row;
7 | align-items: center;
8 | }
9 |
10 | .input-group-column {
11 | display: inline-flex;
12 | flex-direction: column;
13 | align-items: flex-start;
14 | }
15 |
16 | .input-group-column span {
17 | margin-bottom: .25rem;
18 | }
19 |
20 | .input-label, .input-label-secondary {
21 | font-size: 0.625rem;
22 | user-select: none;
23 | cursor: default;
24 |
25 | white-space: nowrap;
26 | }
27 |
28 | [dir="ltr"] .input-label, [dir="ltr"] .input-label-secondary {
29 | margin-right: .5rem;
30 | }
31 |
32 | [dir="rtl"] .input-label, [dir="rtl"] .input-label-secondary {
33 | margin-left: .5rem;
34 | }
35 |
36 | .input-label {
37 | font-weight: bold;
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/forms/label.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import styles from './label.css';
5 |
6 | const Label = props => (
7 |
13 | );
14 |
15 | Label.propTypes = {
16 | above: PropTypes.bool,
17 | children: PropTypes.node,
18 | secondary: PropTypes.bool,
19 | text: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired
20 | };
21 |
22 | Label.defaultProps = {
23 | above: false,
24 | secondary: false
25 | };
26 |
27 | export default Label;
28 |
--------------------------------------------------------------------------------
/src/components/green-flag/green-flag.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .green-flag {
4 | width: 2rem;
5 | height: 2rem;
6 | padding: 0.375rem;
7 | border-radius: 0.25rem;
8 | user-select: none;
9 | user-drag: none;
10 | cursor: pointer;
11 | }
12 |
13 | .green-flag:hover {
14 | background-color: $motion-light-transparent;
15 | }
16 |
17 | .green-flag.is-active {
18 | background-color: $motion-transparent;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/green-flag/green-flag.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import greenFlagIcon from './icon--green-flag.svg';
6 | import styles from './green-flag.css';
7 |
8 | const GreenFlagComponent = function (props) {
9 | const {
10 | active,
11 | className,
12 | onClick,
13 | title,
14 | ...componentProps
15 | } = props;
16 | return (
17 |
31 | );
32 | };
33 | GreenFlagComponent.propTypes = {
34 | active: PropTypes.bool,
35 | className: PropTypes.string,
36 | onClick: PropTypes.func.isRequired,
37 | title: PropTypes.string
38 | };
39 | GreenFlagComponent.defaultProps = {
40 | active: false,
41 | title: 'Go'
42 | };
43 | export default GreenFlagComponent;
44 |
--------------------------------------------------------------------------------
/src/components/green-flag/icon--green-flag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/icon-button/icon-button.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .container {
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | cursor: pointer;
8 | font-size: 0.75rem;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | color: $motion-primary;
11 | border-radius: 0.5rem;
12 | }
13 |
14 | .container + .container {
15 | margin-top: 1.25rem;
16 | }
17 |
18 | .title {
19 | margin-top: 0.5rem;
20 | text-align: center;
21 | }
22 |
23 | .disabled {
24 | opacity: 0.5;
25 | pointer-events: none;
26 | }
27 |
28 | .container:active {
29 | background-color: $motion-light-transparent;
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/icon-button/icon-button.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import styles from './icon-button.css';
5 |
6 | const IconButton = ({
7 | img,
8 | disabled,
9 | className,
10 | title,
11 | onClick
12 | }) => (
13 |
22 |

27 |
28 | {title}
29 |
30 |
31 | );
32 |
33 | IconButton.propTypes = {
34 | className: PropTypes.string,
35 | disabled: PropTypes.bool,
36 | img: PropTypes.string,
37 | onClick: PropTypes.func.isRequired,
38 | title: PropTypes.node.isRequired
39 | };
40 |
41 | export default IconButton;
42 |
--------------------------------------------------------------------------------
/src/components/language-selector/language-selector.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | /* Position the language select over the language icon, and make it transparent */
5 | .language-select {
6 | position: absolute;
7 | width: $language-selector-width;
8 | height: $menu-bar-height;
9 | opacity: 0;
10 | user-select: none;
11 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
12 | font-size: .875rem;
13 | color: $text-primary;
14 | background: $ui-white;
15 | }
16 |
17 | [dir="ltr"] .language-select {
18 | right: 0;
19 | }
20 |
21 | [dir="rtl"] .language-select {
22 | left: 0;
23 | }
24 |
25 | .language-select option {
26 | opacity: 1;
27 | }
28 |
29 | .language-select:focus {
30 | border: 1px solid white;
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/language-selector/language-selector.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import locales from 'clipcc-l10n';
5 | import styles from './language-selector.css';
6 |
7 | // supported languages to exclude from the menu, but allow as a URL option
8 | const ignore = [];
9 |
10 | const LanguageSelector = ({currentLocale, label, onChange}) => (
11 |
30 | );
31 |
32 | LanguageSelector.propTypes = {
33 | currentLocale: PropTypes.string,
34 | label: PropTypes.string,
35 | onChange: PropTypes.func
36 | };
37 |
38 | export default LanguageSelector;
39 |
--------------------------------------------------------------------------------
/src/components/loader/blocks-loader.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable require-jsdoc */
2 | import React from 'react';
3 | import classNames from 'classnames';
4 | import styles from './loader.css';
5 |
6 | import topBlock from './top-block.svg';
7 | import middleBlock from './middle-block.svg';
8 | import bottomBlock from './bottom-block.svg';
9 |
10 | export default function BlocksLoaderComponent () {
11 | return (
12 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/loupe/loupe.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .color-picker {
4 | position: absolute;
5 | border-radius: 100%;
6 | border: 4px solid $ui-black-transparent;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/menu-bar/account-nav.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .user-info {
5 | display: inline-flex;
6 | flex-wrap: nowrap;
7 | justify-content: center;
8 | align-items: center;
9 | align-content: center;
10 | padding: 0 0.95rem;
11 | max-width: 260px;
12 | height: $menu-bar-height;
13 | overflow: hidden;
14 | text-decoration: none;
15 | text-overflow: ellipsis;
16 | white-space: nowrap;
17 | color: $ui-white;
18 | font-size: .75rem;
19 | font-weight: normal;
20 | }
21 |
22 | [dir="ltr"] .user-info .avatar {
23 | margin-right: calc($space * .8125);
24 | }
25 |
26 | [dir="rtl"] .user-info .avatar {
27 | margin-left: calc($space * .8125);
28 | }
29 |
30 | .user-info .avatar {
31 | width: 2rem;
32 | height: 2rem;
33 | }
34 |
35 | [dir="ltr"] .user-info .dropdown-caret-position {
36 | margin-left: calc($space * .8125);
37 | }
38 |
39 | [dir="rtl"] .user-info .dropdown-caret-position {
40 | margin-right: calc($space * .8125);
41 | }
42 |
43 | .user-info .dropdown-caret-position {
44 | display: inline-block;
45 | padding-bottom: .0625rem;
46 | vertical-align: middle;
47 | }
48 |
49 | .user-info .profile-name {
50 | font-size: .75rem;
51 | line-height: .9375rem;
52 | font-weight: bold;
53 | }
54 |
--------------------------------------------------------------------------------
/src/components/menu-bar/author-info.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .author-info {
5 | color: $ui-white;
6 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
7 | display: flex;
8 | justify-content: flex-start;
9 | align-items: center;
10 | cursor: default;
11 | }
12 |
13 | .avatar {
14 | margin-right: .5625rem;
15 | }
16 |
17 | .project-title {
18 | max-width: $menu-bar-item-max-width;
19 | display: block;
20 | overflow: hidden;
21 | font-size: $menu-bar-large-font-size;
22 | font-weight: bold;
23 | text-overflow: ellipsis;
24 | white-space: nowrap;
25 | }
26 |
27 | .username-line {
28 | max-width: $menu-bar-item-max-width;
29 | font-size: $menu-bar-standard-font-size;
30 | display: block;
31 | overflow: hidden;
32 | font-weight: normal;
33 | text-overflow: ellipsis;
34 | white-space: nowrap;
35 | }
36 |
37 | .username {
38 | font-weight: bold;
39 | }
40 |
--------------------------------------------------------------------------------
/src/components/menu-bar/community-button.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .community-button {
4 | box-shadow: 0 0 0 1px $ui-black-transparent;
5 | }
6 |
7 | .community-button-icon {
8 | height: 1.25rem;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/menu-bar/community-button.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import {FormattedMessage} from 'react-intl';
3 | import PropTypes from 'prop-types';
4 | import React from 'react';
5 | import Button from '../button/button.jsx';
6 |
7 | import communityIcon from './icon--see-community.svg';
8 | import styles from './community-button.css';
9 |
10 | const CommunityButton = ({
11 | className,
12 | onClick
13 | }) => (
14 |
29 | );
30 |
31 | CommunityButton.propTypes = {
32 | className: PropTypes.string,
33 | onClick: PropTypes.func
34 | };
35 |
36 | CommunityButton.defaultProps = {
37 | onClick: () => {}
38 | };
39 |
40 | export default CommunityButton;
41 |
--------------------------------------------------------------------------------
/src/components/menu-bar/dropdown-caret.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/menu-bar/icon--about.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/menu-bar/icon--mystuff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/menu-bar/icon--mystuff.png
--------------------------------------------------------------------------------
/src/components/menu-bar/icon--profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/menu-bar/icon--profile.png
--------------------------------------------------------------------------------
/src/components/menu-bar/login-dropdown.css:
--------------------------------------------------------------------------------
1 |
2 | .login {
3 | padding: .625rem;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/menu-bar/menu-bar-menu.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import Menu from '../../containers/menu.jsx';
4 |
5 | const MenuBarMenu = ({
6 | children,
7 | className,
8 | onRequestClose,
9 | open,
10 | place = 'right'
11 | }) => (
12 |
13 |
20 |
21 | );
22 |
23 | MenuBarMenu.propTypes = {
24 | children: PropTypes.node,
25 | className: PropTypes.string,
26 | onRequestClose: PropTypes.func,
27 | open: PropTypes.bool,
28 | place: PropTypes.oneOf(['left', 'right'])
29 | };
30 |
31 | export default MenuBarMenu;
32 |
--------------------------------------------------------------------------------
/src/components/menu-bar/project-title-input.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 | @import "../../css/z-index.css";
4 |
5 | /*
6 | If project-title-input.jsx is part of a menu bar say menu-bar.jsx, it can have additional css classes that
7 | can set a width for example or what it should do in a flex box (eg. grow).
8 | */
9 |
10 | .title-field {
11 | border: 1px dashed $ui-black-transparent;
12 | border-radius: $form-radius;
13 | -webkit-border-radius: $form-radius;
14 | -moz-border-radius: $form-radius;
15 | background-color: $ui-white-transparent;
16 | background-clip: padding-box;
17 | -webkit-background-clip: padding-box;
18 | height: auto;
19 | padding: .5rem;
20 | }
21 |
22 | .title-field {
23 | color: $ui-white;
24 | font-weight: bold;
25 | font-size: .8rem;
26 | }
27 |
28 | .title-field::placeholder {
29 | color: $ui-white;
30 | font-weight: normal;
31 | font-size: .8rem;
32 | font-style: italic;
33 | }
34 |
35 | .title-field:hover {
36 | background-color: hsla(0, 100%, 100%, 0.5);
37 | }
38 |
39 | .title-field:focus {
40 | outline:none;
41 | border: 1px solid $ui-transparent;
42 | -webkit-box-shadow: 0 0 0 calc($space * .5) $ui-white-transparent;
43 | box-shadow: 0 0 0 calc($space * .5) $ui-white-transparent;
44 | background-color: $ui-white;
45 | color: $text-primary;
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/menu-bar/save-status.css:
--------------------------------------------------------------------------------
1 | .save-now {
2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
3 | cursor: pointer;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/menu-bar/share-button.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .share-button {
4 | background: $data-primary;
5 | }
6 |
7 | .share-button-is-shared {
8 | background: $ui-black-transparent;
9 | cursor: default;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/menu-bar/share-button.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import {FormattedMessage} from 'react-intl';
3 | import PropTypes from 'prop-types';
4 | import React from 'react';
5 | import Button from '../button/button.jsx';
6 |
7 | import styles from './share-button.css';
8 |
9 | const ShareButton = ({
10 | className,
11 | isShared,
12 | onClick
13 | }) => (
14 |
36 | );
37 |
38 | ShareButton.propTypes = {
39 | className: PropTypes.string,
40 | isShared: PropTypes.bool,
41 | onClick: PropTypes.func
42 | };
43 |
44 | ShareButton.defaultProps = {
45 | onClick: () => {}
46 | };
47 |
48 | export default ShareButton;
49 |
--------------------------------------------------------------------------------
/src/components/menu-bar/user-avatar.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .user-thumbnail {
5 | width: $menu-bar-button-size;
6 | height: $menu-bar-button-size;
7 | border-radius: $form-radius;
8 | vertical-align: middle;
9 | box-shadow: 0 0 0 1px $ui-black-transparent;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/menu-bar/user-avatar.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import styles from './user-avatar.css';
6 |
7 | const UserAvatar = ({
8 | className,
9 | imageUrl
10 | }) => (
11 |
18 | );
19 |
20 | UserAvatar.propTypes = {
21 | className: PropTypes.string,
22 | imageUrl: PropTypes.string
23 | };
24 |
25 | export default UserAvatar;
26 |
--------------------------------------------------------------------------------
/src/components/menu/menu.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .menu {
4 | position: absolute;
5 | border: 1px solid $ui-black-transparent;
6 | border-radius: 0 0 8px 8px;
7 | background-color: $motion-primary;
8 | padding: 0;
9 | margin: 0;
10 | min-width: 186px;
11 | max-width: 260px;
12 | overflow: visible;
13 | color: $ui-white;
14 | box-shadow: 0 8px 8px 0 $ui-black-transparent;
15 | }
16 |
17 | [theme='dark'] .menu {
18 | background-color: $motion-primary-dark;
19 | }
20 |
21 | .menu.left {
22 | right: 0;
23 | }
24 |
25 | .menu.right {
26 | left: 0;
27 | }
28 |
29 | .menu-item {
30 | display: block;
31 | line-height: 34px;
32 | white-space: nowrap;
33 | padding: 0 10px;
34 | font-size: .75rem;
35 | margin: 0;
36 | font-weight: bold;
37 | transition: 0.25s ease-out; /* @todo: standardize with var */
38 | }
39 |
40 | .menu-item.active,
41 | .menu-item:hover {
42 | background-color: $ui-black-transparent;
43 | }
44 |
45 | [theme='dark'] .menu-item.active,
46 | [theme='dark'] .menu-item:hover {
47 | background-color: $ui-white-transparent;
48 | }
49 |
50 | .menu-item.hoverable {
51 | cursor: pointer;
52 | }
53 |
54 | .menu-section {
55 | border-top: 1px solid $ui-black-transparent;
56 | }
57 |
--------------------------------------------------------------------------------
/src/components/meter/meter.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .green {
4 | fill: rgb(171, 220, 170);
5 | stroke: rgb(174, 211, 168);
6 | }
7 |
8 | .yellow {
9 | fill: rgb(251, 219, 130);
10 | stroke: rgb(239, 212, 134);
11 | }
12 |
13 | .red {
14 | fill: rgb(251, 194, 142);
15 | stroke: rgb(235, 189, 142);
16 | }
17 |
18 | .mask-container {
19 | position: relative;
20 | }
21 |
22 | .mask {
23 | position: absolute;
24 | top: 0;
25 | left: 0;
26 | height: 100%;
27 | width: 100%;
28 | transform-origin: top;
29 | will-change: transform;
30 | background: $ui-primary;
31 | opacity: 0.75;
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/mic-indicator/mic-indicator.css:
--------------------------------------------------------------------------------
1 | @keyframes popIn {
2 | from {transform: scale(0.5)}
3 | to {transform: scale(1)}
4 | }
5 |
6 | .mic-img {
7 | margin: 10px;
8 | transform-origin: center;
9 | animation: popIn 0.1s ease-in-out;
10 | }
11 |
--------------------------------------------------------------------------------
/src/components/mic-indicator/mic-indicator.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './mic-indicator.css';
4 | import micIcon from './mic-indicator.svg';
5 | import {stageSizeToTransform} from '../../lib/screen-utils';
6 |
7 | const MicIndicatorComponent = props => (
8 |
12 |

16 |
17 | );
18 |
19 | MicIndicatorComponent.propTypes = {
20 | className: PropTypes.string,
21 | stageSize: PropTypes.shape({
22 | width: PropTypes.number,
23 | height: PropTypes.number,
24 | widthDefault: PropTypes.number,
25 | heightDefault: PropTypes.number
26 | }).isRequired
27 | };
28 |
29 | export default MicIndicatorComponent;
30 |
--------------------------------------------------------------------------------
/src/components/monitor-list/monitor-list.css:
--------------------------------------------------------------------------------
1 | .monitor-list {
2 | /* Width/height are set by the component, margin: auto centers in fullscreen */
3 | margin: auto;
4 | pointer-events: none;
5 | overflow: hidden;
6 | }
7 |
8 | .monitor-list-scaler {
9 | /* Scaling for monitors should happen from the top left */
10 | transform-origin: left top;
11 | }
12 |
13 | ::-ms-clear { display: none; }
14 |
--------------------------------------------------------------------------------
/src/components/monitor/default-monitor.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './monitor.css';
4 |
5 | const DefaultMonitor = ({categoryColor, label, value}) => (
6 |
7 |
8 |
9 | {label}
10 |
11 |
15 | {value}
16 |
17 |
18 |
19 | );
20 |
21 | DefaultMonitor.propTypes = {
22 | categoryColor: PropTypes.string.isRequired,
23 | label: PropTypes.string.isRequired,
24 | value: PropTypes.oneOfType([
25 | PropTypes.string,
26 | PropTypes.number
27 | ])
28 | };
29 |
30 | export default DefaultMonitor;
31 |
--------------------------------------------------------------------------------
/src/components/monitor/large-monitor.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './monitor.css';
4 |
5 | const LargeMonitor = ({categoryColor, value}) => (
6 |
7 |
11 | {value}
12 |
13 |
14 | );
15 |
16 | LargeMonitor.propTypes = {
17 | categoryColor: PropTypes.string,
18 | value: PropTypes.oneOfType([
19 | PropTypes.string,
20 | PropTypes.number
21 | ])
22 | };
23 |
24 | export default LargeMonitor;
25 |
--------------------------------------------------------------------------------
/src/components/play-button/play-button.css:
--------------------------------------------------------------------------------
1 |
2 | @import "../../css/colors.css";
3 | @import "../../css/units.css";
4 |
5 | .play-button {
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 |
10 | overflow: hidden; /* Mask the icon animation */
11 | width: 2.5rem;
12 | height: 2.5rem;
13 | background-color: $sound-primary;
14 | color: $ui-white;
15 | border-radius: 50%;
16 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
17 | user-select: none;
18 | cursor: pointer;
19 | transition: all 0.15s ease-out;
20 | }
21 |
22 | .play-button {
23 | position: absolute;
24 | top: .5rem;
25 | z-index: auto;
26 | }
27 |
28 | .play-button:focus {
29 | outline: none;
30 | }
31 |
32 | .play-icon {
33 | width: 50%;
34 | }
35 |
36 | [dir="ltr"] .play-button {
37 | right: .5rem;
38 | }
39 |
40 | [dir="rtl"] .play-button {
41 | left: .5rem;
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/prompt/icon--dropdown-caret.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/question/icon--enter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/select/chevron-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/sound-editor/icon--faster.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/sound-editor/icon--slower.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/sound-editor/icon--stop.svg:
--------------------------------------------------------------------------------
1 |
2 |
22 |
--------------------------------------------------------------------------------
/src/components/spinner/spinner.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import styles from './spinner.css';
6 |
7 | const SpinnerComponent = function (props) {
8 | const {
9 | className,
10 | level,
11 | small,
12 | large
13 | } = props;
14 | return (
15 |
26 | );
27 | };
28 | SpinnerComponent.propTypes = {
29 | className: PropTypes.string,
30 | large: PropTypes.bool,
31 | level: PropTypes.string,
32 | small: PropTypes.bool
33 | };
34 | SpinnerComponent.defaultProps = {
35 | className: '',
36 | large: false,
37 | level: 'info',
38 | small: false
39 | };
40 | export default SpinnerComponent;
41 |
--------------------------------------------------------------------------------
/src/components/sprite-info/icon--draggable-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/sprite-info/icon--draggable-on.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/sprite-info/icon--x.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/sprite-info/icon--y.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/stage-header/icon--large-stage.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/stage-header/icon--small-stage.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/stage-wrapper/stage-wrapper.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 | @import "../../css/colors.css";
3 | @import "../../css/z-index.css";
4 |
5 | .stage-wrapper * {
6 | box-sizing: border-box;
7 | }
8 |
9 | .stage-canvas-wrapper {
10 | /* Hides negative space between edge of rounded corners + container, when selected */
11 | user-select: none;
12 | }
13 |
14 | .stage-wrapper.full-screen {
15 | position: fixed;
16 | top: $stage-menu-height;
17 | left: 0;
18 | right: 0;
19 | bottom: 0;
20 | z-index: $z-index-stage-wrapper-overlay;
21 | background-color: $ui-white;
22 | /* spacing between stage and control bar (on the top), or between
23 | stage and window edges (on left/right/bottom) */
24 | padding: $stage-full-screen-stage-padding;
25 |
26 | /* this centers the stage */
27 | display: flex;
28 | flex-direction: column;
29 | align-items: center;
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/stop-all/icon--stop-all.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/src/components/stop-all/stop-all.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .stop-all {
4 | width: 2rem;
5 | height: 2rem;
6 | padding: 0.375rem;
7 | border-radius: 0.25rem;
8 | user-select: none;
9 | cursor: pointer;
10 | }
11 |
12 | .stop-all:hover {
13 | background-color: $motion-light-transparent;
14 | }
15 |
16 | .stop-all {
17 | opacity: 0.5;
18 | }
19 |
20 | .stop-all.is-active {
21 | opacity: 1;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/stop-all/stop-all.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import stopAllIcon from './icon--stop-all.svg';
6 | import styles from './stop-all.css';
7 |
8 | const StopAllComponent = function (props) {
9 | const {
10 | active,
11 | className,
12 | onClick,
13 | title,
14 | ...componentProps
15 | } = props;
16 | return (
17 |
31 | );
32 | };
33 |
34 | StopAllComponent.propTypes = {
35 | active: PropTypes.bool,
36 | className: PropTypes.string,
37 | onClick: PropTypes.func.isRequired,
38 | title: PropTypes.string
39 | };
40 |
41 | StopAllComponent.defaultProps = {
42 | active: false,
43 | title: 'Stop'
44 | };
45 |
46 | export default StopAllComponent;
47 |
--------------------------------------------------------------------------------
/src/components/tag-button/tag-button.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .tag-button {
5 | padding: .625rem 1rem;
6 | background: $motion-primary;
7 | border-radius: 1.375rem;
8 | color: $ui-white;
9 | height: $library-filter-bar-height;
10 | transition: 0.25s ease-out; /* @todo: standardize with var */
11 | }
12 |
13 | .tag-button-icon {
14 | max-width: 1rem;
15 | max-height: 1rem;
16 | }
17 |
18 | .active {
19 | background: $data-primary;
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/tag-button/tag-button.jsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 | import {FormattedMessage} from 'react-intl';
5 |
6 | import Button from '../button/button.jsx';
7 |
8 | import styles from './tag-button.css';
9 |
10 | const TagButtonComponent = ({
11 | active,
12 | iconClassName,
13 | className,
14 | tag, // eslint-disable-line no-unused-vars
15 | intlLabel,
16 | ...props
17 | }) => (
18 |
33 | );
34 |
35 | TagButtonComponent.propTypes = {
36 | ...Button.propTypes,
37 | active: PropTypes.bool,
38 | intlLabel: PropTypes.shape({
39 | defaultMessage: PropTypes.string,
40 | description: PropTypes.string,
41 | id: PropTypes.string
42 | }).isRequired,
43 | tag: PropTypes.string.isRequired
44 | };
45 |
46 | TagButtonComponent.defaultProps = {
47 | active: false
48 | };
49 |
50 | export default TagButtonComponent;
51 |
--------------------------------------------------------------------------------
/src/components/target-pane/target-pane.css:
--------------------------------------------------------------------------------
1 | @import "../../css/units.css";
2 |
3 | .target-pane.scratch2 {
4 | flex-direction: row-reverse;
5 | }
6 |
7 | .target-pane {
8 | /* Makes columns for the sprite library selector + and the stage selector */
9 | display: flex;
10 | flex-direction: row;
11 | flex-grow: 1;
12 | }
13 |
14 | [dir="ltr"] .stage-selector-wrapper.scratch2 {
15 | margin-left: 0;
16 | margin-right: $space;
17 | }
18 |
19 | .stage-selector-wrapper {
20 | display: flex;
21 | flex-basis: 72px;
22 | flex-shrink: 0;
23 | }
24 |
25 | [dir="ltr"] .stage-selector-wrapper {
26 | margin-left: calc($space / 2);
27 | }
28 |
29 | [dir="rtl"] .stage-selector-wrapper {
30 | margin-right: calc($space / 2);
31 | }
32 |
--------------------------------------------------------------------------------
/src/components/telemetry-modal/telemetry-modal-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/telemetry-modal/telemetry-modal-header.png
--------------------------------------------------------------------------------
/src/components/text-switch/text-switch.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 | @import "../../css/units.css";
3 |
4 | .text-switch {
5 | display: flex;
6 | align-content: center;
7 | align-items: center;
8 | }
9 |
10 | .switch {
11 | cursor: pointer;
12 | border-color: $motion-tertiary;
13 | padding: $space;
14 | border-top-style: solid;
15 | border-bottom-style: solid;
16 | border-width: 1px;
17 | border-color: $motion-tertiary;
18 | border-right-style: solid;
19 | background-color: $ui-white;
20 | color: $motion-primary;
21 | text-align: center;
22 | transition: 0.25s ease-out; /* @todo: standardize with var */
23 | }
24 |
25 | [theme='dark'] .switch {
26 | background-color: $ui-primary-dark;
27 | color: $text-primary-dark;
28 | }
29 |
30 | .switch:first-child {
31 | border-color: $motion-tertiary;
32 | border-style: solid;
33 | border-radius: $form-radius 0 0 $form-radius;
34 | }
35 |
36 | .switch:last-child {
37 | border-right-style: solid;
38 | border-radius: 0 $form-radius $form-radius 0;
39 | }
40 |
41 | .switch.active {
42 | background-color: $motion-primary;
43 | color: $ui-white;
44 | }
45 |
46 | .switch img {
47 | image-rendering: crisp-edges;
48 | filter: invert(50%) sepia(41%) saturate(1318%) hue-rotate(188deg) brightness(103%) contrast(101%);
49 | }
50 |
51 | .switch.active img {
52 | filter: none;
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/components/text-switch/text-switch.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 |
5 | import styles from './text-switch.css';
6 |
7 | const TextSwitch = props => (
8 |
9 | {props.options.map(option => (
10 | props.onChange(option.id)}
19 | >
20 | {option.text}
21 |
22 | ))}
23 |
24 | );
25 |
26 | TextSwitch.propTypes = {
27 | options: PropTypes.arrayOf(PropTypes.shape({
28 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
29 | text: PropTypes.string
30 | })).isRequired,
31 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
32 | onChange: PropTypes.func.isRequired
33 | };
34 |
35 | export default TextSwitch;
36 |
--------------------------------------------------------------------------------
/src/components/turbo-mode/icon--turbo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/turbo-mode/turbo-mode.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .turbo-container {
4 | display: flex;
5 | align-items: center;
6 | padding: 0.25rem;
7 | user-select: none;
8 | }
9 |
10 | .turbo-icon {
11 | margin: 0.25rem;
12 | }
13 |
14 | .turbo-label {
15 | font-size: 0.625rem;
16 | font-weight: bold;
17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
18 | color: $control-primary;
19 | white-space: nowrap;
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/turbo-mode/turbo-mode.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {FormattedMessage} from 'react-intl';
3 |
4 | import turboIcon from './icon--turbo.svg';
5 |
6 | import styles from './turbo-mode.css';
7 |
8 | const TurboMode = () => (
9 |
10 |

14 |
15 |
20 |
21 |
22 | );
23 |
24 | export default TurboMode;
25 |
--------------------------------------------------------------------------------
/src/components/watermark/watermark.css:
--------------------------------------------------------------------------------
1 |
2 | .sprite-image {
3 | margin: auto;
4 | user-select: none;
5 | max-width: 48px;
6 | max-height: 48px;
7 | opacity: 0.35;
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/watermark/watermark.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import styles from './watermark.css';
5 |
6 | const Watermark = props => (
7 |
11 | );
12 |
13 | Watermark.propTypes = {
14 | costumeURL: PropTypes.string
15 | };
16 |
17 | export default Watermark;
18 |
--------------------------------------------------------------------------------
/src/components/waveform/waveform.css:
--------------------------------------------------------------------------------
1 | @import "../../css/colors.css";
2 |
3 | .container {
4 | width: 100%;
5 | }
6 | .waveform-path {
7 | /*
8 | This color is lighter than sound-primary, but
9 | cannot use alpha because of overlapping elements.
10 | */
11 | fill: hsl(300, 54%, 72%);
12 | stroke: $sound-tertiary;
13 | }
14 | .baseline {
15 | stroke: $sound-tertiary;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/webgl-modal/unsupported.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/components/webgl-modal/unsupported.png
--------------------------------------------------------------------------------
/src/containers/alerts.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {connect} from 'react-redux';
4 |
5 | import {
6 | closeAlert,
7 | filterPopupAlerts
8 | } from '../reducers/alerts';
9 |
10 | import AlertsComponent from '../components/alerts/alerts.jsx';
11 |
12 | const Alerts = ({
13 | alertsList,
14 | className,
15 | onCloseAlert
16 | }) => (
17 |
23 | );
24 |
25 | Alerts.propTypes = {
26 | alertsList: PropTypes.arrayOf(PropTypes.object),
27 | className: PropTypes.string,
28 | onCloseAlert: PropTypes.func
29 | };
30 |
31 | const mapStateToProps = state => ({
32 | alertsList: state.scratchGui.alerts.alertsList
33 | });
34 |
35 | const mapDispatchToProps = dispatch => ({
36 | onCloseAlert: index => dispatch(closeAlert(index))
37 | });
38 |
39 | export default connect(
40 | mapStateToProps,
41 | mapDispatchToProps
42 | )(Alerts);
43 |
--------------------------------------------------------------------------------
/src/containers/drag-layer.jsx:
--------------------------------------------------------------------------------
1 | import {connect} from 'react-redux';
2 | import DragLayer from '../components/drag-layer/drag-layer.jsx';
3 |
4 | const mapStateToProps = state => ({
5 | dragging: state.scratchGui.assetDrag.dragging,
6 | currentOffset: state.scratchGui.assetDrag.currentOffset,
7 | img: state.scratchGui.assetDrag.img
8 | });
9 |
10 | export default connect(mapStateToProps)(DragLayer);
11 |
--------------------------------------------------------------------------------
/src/containers/load-error-modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {connect} from 'react-redux';
4 |
5 | import LoadErrorModalComponent from '../components/load-error-modal/load-error-modal.jsx';
6 |
7 | const LoadErrorModal = ({
8 | data,
9 | onRequestClose
10 | }) => (
11 |
15 | );
16 |
17 | LoadErrorModal.propTypes = {
18 | onRequestClose: PropTypes.func,
19 | data: PropTypes.shape({
20 | errorId: PropTypes.string,
21 | detail: PropTypes.string,
22 | missingExtensions: PropTypes.arrayOf(PropTypes.shape({
23 | id: PropTypes.string,
24 | version: PropTypes.string
25 | }))
26 | })
27 | };
28 |
29 | const mapStateToProps = state => ({
30 | data: state.scratchGui.loadError
31 | });
32 |
33 | export default connect(
34 | mapStateToProps
35 | )(LoadErrorModal);
36 |
--------------------------------------------------------------------------------
/src/containers/menu-item.jsx:
--------------------------------------------------------------------------------
1 | import bindAll from 'lodash.bindall';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import {MenuItem as MenuItemComponent} from '../components/menu/menu.jsx';
6 |
7 | class MenuItem extends React.Component {
8 | constructor (props) {
9 | super(props);
10 | bindAll(this, [
11 | 'navigateToHref'
12 | ]);
13 | }
14 | navigateToHref () {
15 | if (this.props.href) window.location.href = this.props.href;
16 | }
17 | render () {
18 | const {
19 | children,
20 | className,
21 | onClick
22 | } = this.props;
23 | const clickAction = onClick ? onClick : this.navigateToHref;
24 | return (
25 |
29 | {children}
30 |
31 | );
32 | }
33 | }
34 |
35 | MenuItem.propTypes = {
36 | children: PropTypes.node,
37 | className: PropTypes.string,
38 | // can take an onClick prop, or take an href and build an onClick handler
39 | href: PropTypes.string,
40 | onClick: PropTypes.func
41 | };
42 |
43 | export default MenuItem;
44 |
--------------------------------------------------------------------------------
/src/containers/question.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import bindAll from 'lodash.bindall';
4 | import QuestionComponent from '../components/question/question.jsx';
5 |
6 | class Question extends React.Component {
7 | constructor (props) {
8 | super(props);
9 | bindAll(this, [
10 | 'handleChange',
11 | 'handleKeyPress',
12 | 'handleSubmit'
13 | ]);
14 | this.state = {
15 | answer: ''
16 | };
17 | }
18 | handleChange (e) {
19 | this.setState({answer: e.target.value});
20 | }
21 | handleKeyPress (event) {
22 | if (event.key === 'Enter') this.handleSubmit();
23 | }
24 | handleSubmit () {
25 | this.props.onQuestionAnswered(this.state.answer);
26 | }
27 | render () {
28 | return (
29 |
36 | );
37 | }
38 | }
39 |
40 | Question.propTypes = {
41 | onQuestionAnswered: PropTypes.func.isRequired,
42 | question: PropTypes.string
43 | };
44 |
45 | export default Question;
46 |
--------------------------------------------------------------------------------
/src/containers/stage-wrapper.jsx:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import VM from 'clipcc-vm';
4 | import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants.js';
5 | import StageWrapperComponent from '../components/stage-wrapper/stage-wrapper.jsx';
6 |
7 | const StageWrapper = props => ;
8 |
9 | StageWrapper.propTypes = {
10 | isRendererSupported: PropTypes.bool.isRequired,
11 | layoutStyle: PropTypes.string,
12 | stageSize: PropTypes.oneOf(Object.keys(STAGE_DISPLAY_SIZES)).isRequired,
13 | vm: PropTypes.instanceOf(VM).isRequired
14 | };
15 |
16 | export default StageWrapper;
17 |
--------------------------------------------------------------------------------
/src/containers/tag-button.jsx:
--------------------------------------------------------------------------------
1 | import bindAll from 'lodash.bindall';
2 | import PropTypes from 'prop-types';
3 | import React from 'react';
4 |
5 | import TagButtonComponent from '../components/tag-button/tag-button.jsx';
6 |
7 | class TagButton extends React.Component {
8 | constructor (props) {
9 | super(props);
10 | bindAll(this, [
11 | 'handleClick'
12 | ]);
13 | }
14 | handleClick () {
15 | this.props.onClick(this.props.tag);
16 | }
17 | render () {
18 | return (
19 |
23 | );
24 | }
25 | }
26 |
27 | TagButton.propTypes = {
28 | ...TagButtonComponent.propTypes,
29 | onClick: PropTypes.func
30 | };
31 |
32 | export default TagButton;
33 |
--------------------------------------------------------------------------------
/src/containers/webgl-modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import WebGlModalComponent from '../components/webgl-modal/webgl-modal.jsx';
5 |
6 | class WebGlModal extends React.Component {
7 | handleCancel () {
8 | window.history.back();
9 | }
10 | render () {
11 | return (
12 |
16 | );
17 | }
18 | }
19 |
20 | WebGlModal.propTypes = {
21 | isRtl: PropTypes.bool
22 | };
23 |
24 | export default WebGlModal;
25 |
--------------------------------------------------------------------------------
/src/css/scrollbar.css:
--------------------------------------------------------------------------------
1 | @import "colors.css";
2 |
3 | $scrollbar-thickness: 11px;
4 | $scrollbar-radius: 6px;
5 | $scrollbar-padding: 2.5px;
6 | $scrollbar-color: #bbb;
7 | $scrollbar-color-dark: #333;
8 | $scrollbar-color-hover: #aaa;
9 |
10 | /* for webkit */
11 | .scrollbar::-webkit-scrollbar {
12 | width: $scrollbar-thickness;
13 | height: $scrollbar-thickness;
14 | }
15 |
16 | .scrollbar::-webkit-scrollbar-thumb {
17 | border-radius: $scrollbar-radius;
18 | background-color: $scrollbar-color;
19 | border: solid white $scrollbar-padding;
20 | }
21 |
22 | [theme='dark'] .scrollbar::-webkit-scrollbar-thumb {
23 | border-color: $scrollbar-color-dark;
24 | }
25 |
26 | .scrollbar::-webkit-scrollbar-thumb:hover {
27 | background-color: $scrollbar-color-hover;
28 | }
29 |
30 | /* for firefox */
31 | .scrollbar {
32 | scrollbar-color: $scrollbar-color;
33 | scrollbar-width: thin;
34 | }
35 |
36 | [theme='dark'] .scrollbar {
37 | scrollbar-color: $scrollbar-color-dark;
38 | }
39 |
--------------------------------------------------------------------------------
/src/css/typography.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
3 | }
4 |
5 | h2 {
6 | font-size: 1.5rem;
7 | font-weight: bold;
8 | }
9 |
10 | p {
11 | font-size: 1rem;
12 | line-height: 1.5em;
13 | }
14 |
--------------------------------------------------------------------------------
/src/css/units.css:
--------------------------------------------------------------------------------
1 | /* make sure to keep these in sync with other constants,
2 | e.g. STAGE_DIMENSION_DEFAULTS in lib/screen-utils.js */
3 |
4 | $space: 0.5rem;
5 |
6 | $sprites-per-row: 5;
7 |
8 | $menu-bar-height: 3rem;
9 | $language-selector-width: 3rem;
10 | $sprite-info-height: 6rem;
11 | $stage-menu-height: 2.75rem;
12 |
13 | $library-header-height: 3.125rem;
14 | $library-filter-bar-height: 2.5rem;
15 |
16 | $stage-standard-border-width: 0.0625rem;
17 | $stage-full-screen-border-width: 0.1875rem;
18 | $stage-full-screen-stage-padding: 0.1875rem;
19 |
20 | $form-radius: calc($space / 2);
21 |
22 | /* layout contants from `layout-constants.js` */
23 | $full-size: 1095px;
24 | $full-size-paint: 1249px;
25 |
26 | $menu-bar-standard-font-size: 0.75rem;
27 | $menu-bar-large-font-size: 0.875rem;
28 | $menu-bar-button-size: 2rem;
29 |
30 | $menu-bar-item-max-width: 12rem;
31 |
--------------------------------------------------------------------------------
/src/examples/extensions/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['scratch'], // no ES6
3 | env: {
4 | worker: true
5 | },
6 | globals: {
7 | Scratch: true
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import GUI from './containers/gui.jsx';
2 | import AppStateHOC from './lib/app-state-hoc.jsx';
3 | import TitledHOC from './lib/titled-hoc.jsx';
4 | import GuiReducer, {guiInitialState, guiMiddleware, initEmbedded, initFullScreen, initPlayer} from './reducers/gui';
5 | import LocalesReducer, {localesInitialState, initLocale} from './reducers/locales';
6 | import {ScratchPaintReducer} from 'clipcc-paint';
7 | import {setFullScreen, setPlayer} from './reducers/mode';
8 | import {remixProject} from './reducers/project-state';
9 | import {loadExtensionFromFile} from './lib/extension-manager.js';
10 | import {setAppElement} from 'react-modal';
11 | import totallyNormalStrings from './lib/l10n.js';
12 |
13 | const guiReducers = {
14 | locales: LocalesReducer,
15 | scratchGui: GuiReducer,
16 | scratchPaint: ScratchPaintReducer
17 | };
18 |
19 | export {
20 | GUI as default,
21 | AppStateHOC,
22 | TitledHOC,
23 | setAppElement,
24 | guiReducers,
25 | guiInitialState,
26 | guiMiddleware,
27 | initEmbedded,
28 | initPlayer,
29 | initFullScreen,
30 | initLocale,
31 | localesInitialState,
32 | remixProject,
33 | setFullScreen,
34 | setPlayer,
35 | loadExtensionFromFile,
36 | totallyNormalStrings
37 | };
38 |
--------------------------------------------------------------------------------
/src/lib/analytics.js:
--------------------------------------------------------------------------------
1 | import GoogleAnalytics from 'react-ga';
2 |
3 | import log from './log';
4 |
5 | const GA_ID = false;
6 | if (GA_ID) {
7 | GoogleAnalytics.initialize(GA_ID, {
8 | debug: (process.env.NODE_ENV !== 'production'),
9 | titleCase: true,
10 | sampleRate: (process.env.NODE_ENV === 'production') ? 100 : 0,
11 | forceSSL: true
12 | });
13 | } else {
14 | log.info('Disabling GA because GA_ID is not set.');
15 | window.ga = () => {
16 | // The `react-ga` module calls this function to implement all Google Analytics calls. Providing an empty
17 | // function effectively disables `react-ga`. This is similar to the `testModeAPI` feature of `react-ga` except
18 | // that `testModeAPI` logs the arguments of every call into an array. That's nice for testing purposes but
19 | // would look like a memory leak in a live program.
20 | };
21 | }
22 |
23 | export default GoogleAnalytics;
24 |
--------------------------------------------------------------------------------
/src/lib/assets/icon--back.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/lib/assets/icon--success.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/lib/assets/placeholder.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/lib/audio/effects/fade-effect.js:
--------------------------------------------------------------------------------
1 | class FadeEffect {
2 | constructor (audioContext, fadeIn, startSeconds, endSeconds) {
3 | this.audioContext = audioContext;
4 |
5 | this.input = this.audioContext.createGain();
6 | this.output = this.audioContext.createGain();
7 |
8 | this.gain = this.audioContext.createGain();
9 |
10 | this.gain.gain.setValueAtTime(1, 0);
11 |
12 | if (fadeIn) {
13 | this.gain.gain.setValueAtTime(0, startSeconds);
14 | this.gain.gain.linearRampToValueAtTime(1, endSeconds);
15 | } else {
16 | this.gain.gain.setValueAtTime(1, startSeconds);
17 | this.gain.gain.linearRampToValueAtTime(0, endSeconds);
18 | }
19 |
20 | this.gain.gain.setValueAtTime(1, endSeconds);
21 |
22 | this.input.connect(this.gain);
23 | this.gain.connect(this.output);
24 | }
25 | }
26 |
27 | export default FadeEffect;
28 |
--------------------------------------------------------------------------------
/src/lib/audio/effects/mute-effect.js:
--------------------------------------------------------------------------------
1 | class MuteEffect {
2 | constructor (audioContext, startSeconds, endSeconds) {
3 | this.audioContext = audioContext;
4 |
5 | this.input = this.audioContext.createGain();
6 | this.output = this.audioContext.createGain();
7 |
8 | this.gain = this.audioContext.createGain();
9 |
10 | // Smoothly ramp the gain down before the start time, and up after the end time.
11 | this.rampLength = 0.001;
12 | this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength));
13 | this.gain.gain.linearRampToValueAtTime(0, startSeconds);
14 | this.gain.gain.setValueAtTime(0, endSeconds);
15 | this.gain.gain.linearRampToValueAtTime(1.0, endSeconds + this.rampLength);
16 |
17 | this.input.connect(this.gain);
18 | this.gain.connect(this.output);
19 | }
20 | }
21 |
22 | export default MuteEffect;
23 |
--------------------------------------------------------------------------------
/src/lib/audio/effects/volume-effect.js:
--------------------------------------------------------------------------------
1 | class VolumeEffect {
2 | constructor (audioContext, volume, startSeconds, endSeconds) {
3 | this.audioContext = audioContext;
4 |
5 | this.input = this.audioContext.createGain();
6 | this.output = this.audioContext.createGain();
7 |
8 | this.gain = this.audioContext.createGain();
9 |
10 | // Smoothly ramp the gain up before the start time, and down after the end time.
11 | this.rampLength = 0.01;
12 | this.gain.gain.setValueAtTime(1.0, Math.max(0, startSeconds - this.rampLength));
13 | this.gain.gain.exponentialRampToValueAtTime(volume, startSeconds);
14 | this.gain.gain.setValueAtTime(volume, endSeconds);
15 | this.gain.gain.exponentialRampToValueAtTime(1.0, endSeconds + this.rampLength);
16 |
17 | this.input.connect(this.gain);
18 | this.gain.connect(this.output);
19 | }
20 | }
21 |
22 | export default VolumeEffect;
23 |
--------------------------------------------------------------------------------
/src/lib/audio/shared-audio-context.js:
--------------------------------------------------------------------------------
1 | import StartAudioContext from 'startaudiocontext';
2 | import bowser from 'bowser';
3 |
4 | let AUDIO_CONTEXT;
5 |
6 | if (!bowser.msie) {
7 | /**
8 | * AudioContext can be initialized only when user interaction event happens
9 | */
10 | const event =
11 | typeof document.ontouchstart === 'undefined' ?
12 | 'mousedown' :
13 | 'touchstart';
14 | const initAudioContext = () => {
15 | document.removeEventListener(event, initAudioContext);
16 | AUDIO_CONTEXT = new (window.AudioContext ||
17 | window.webkitAudioContext)();
18 | StartAudioContext(AUDIO_CONTEXT);
19 | };
20 | document.addEventListener(event, initAudioContext, { capture: true });
21 | }
22 |
23 | /**
24 | * Wrap browser AudioContext because we shouldn't create more than one
25 | * @return {AudioContext} The singleton AudioContext
26 | */
27 | export default function () {
28 | return AUDIO_CONTEXT;
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/backpack/code-payload.js:
--------------------------------------------------------------------------------
1 | import blockToImage from './block-to-image';
2 | import jpegThumbnail from './jpeg-thumbnail';
3 | import {Base64} from 'js-base64';
4 |
5 | const codePayload = ({blockObjects, topBlockId}) => {
6 | const payload = {
7 | type: 'script', // Needs to match backpack-server type name
8 | name: 'code', // All code currently gets the same name
9 | mime: 'application/json',
10 | // Backpack expects a base64 encoded string to store. Cannot use btoa because
11 | // the code can contain characters outside the 0-255 code-point range supported by btoa
12 | body: Base64.encode(JSON.stringify(blockObjects)) // Base64 encode the json
13 | };
14 |
15 | return blockToImage(topBlockId)
16 | .then(jpegThumbnail)
17 | .then(thumbnail => {
18 | payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
19 | return payload;
20 | });
21 | };
22 |
23 | export default codePayload;
24 |
--------------------------------------------------------------------------------
/src/lib/backpack/sound-payload.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-unresolved
2 | import soundThumbnail from '!base64-loader!./sound-thumbnail.jpg';
3 |
4 | const soundPayload = sound => {
5 | const assetDataUrl = sound.asset.encodeDataURI();
6 | const assetDataFormat = sound.dataFormat;
7 | const payload = {
8 | type: 'sound',
9 | name: sound.name,
10 | thumbnail: soundThumbnail,
11 | // Params to be filled in below
12 | mime: '',
13 | body: ''
14 | };
15 |
16 | switch (assetDataFormat) {
17 | case 'wav':
18 | payload.mime = 'audio/x-wav';
19 | payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', '');
20 | break;
21 | case 'mp3':
22 | payload.mime = 'audio/mp3';
23 | // TODO scratch-storage should be fixed so that encodeDataURI does not
24 | // always prepend the wave format header; Once that is fixed, the following
25 | // line will have to change.
26 | payload.body = assetDataUrl.replace('data:audio/x-wav;base64,', '');
27 | break;
28 | default:
29 | alert(`Cannot serialize for format: ${assetDataFormat}`); // eslint-disable-line
30 | }
31 |
32 | // Return a promise to make it consistent with other payload constructors like costume-payload
33 | return new Promise(resolve => resolve(payload));
34 | };
35 |
36 | export default soundPayload;
37 |
--------------------------------------------------------------------------------
/src/lib/backpack/sound-thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/backpack/sound-thumbnail.jpg
--------------------------------------------------------------------------------
/src/lib/backpack/sprite-payload.js:
--------------------------------------------------------------------------------
1 | import jpegThumbnail from './jpeg-thumbnail';
2 |
3 | const spritePayload = (id, vm) => {
4 | const target = vm.runtime.getTargetById(id);
5 | if (!target) return null;
6 |
7 | return vm.exportSprite(
8 | id,
9 | 'base64'
10 | ).then(zippedSprite => {
11 | const payload = {
12 | type: 'sprite',
13 | name: target.sprite.name,
14 | mime: 'application/zip',
15 | body: zippedSprite,
16 | // Filled in below
17 | thumbnail: ''
18 | };
19 |
20 | const costumeDataUrl = target.sprite.costumes[target.currentCostume].asset.encodeDataURI();
21 |
22 | return jpegThumbnail(costumeDataUrl).then(thumbnail => {
23 | payload.thumbnail = thumbnail.replace('data:image/jpeg;base64,', '');
24 | return payload;
25 | });
26 | });
27 | };
28 |
29 | export default spritePayload;
30 |
--------------------------------------------------------------------------------
/src/lib/bmp-converter.js:
--------------------------------------------------------------------------------
1 | export default bmpImage => new Promise(resolve => {
2 | // If the input is an ArrayBuffer, we need to convert it to a `Blob` and give it a URL so we can use it as an
3 | // `src`. If it's a data URI, we can use it as-is.
4 | const imageUrl = bmpImage instanceof String ?
5 | bmpImage :
6 | window.URL.createObjectURL(new Blob([bmpImage], {type: 'image/bmp'}));
7 |
8 | const canvas = document.createElement('canvas');
9 | const ctx = canvas.getContext('2d');
10 |
11 | const image = document.createElement('img');
12 |
13 | image.addEventListener('load', () => {
14 | canvas.width = image.naturalWidth;
15 | canvas.height = image.naturalHeight;
16 | ctx.drawImage(image, 0, 0);
17 |
18 | const dataUrl = canvas.toDataURL('image/png');
19 |
20 | // Revoke URL. If a blob URL was generated earlier, this allows the blob to be GC'd and prevents a memory leak.
21 | window.URL.revokeObjectURL(imageUrl);
22 |
23 | resolve(dataUrl);
24 | });
25 |
26 | image.setAttribute('src', imageUrl);
27 | });
28 |
--------------------------------------------------------------------------------
/src/lib/connected-intl-provider.jsx:
--------------------------------------------------------------------------------
1 | import {IntlProvider as ReactIntlProvider} from 'react-intl';
2 | import {connect} from 'react-redux';
3 |
4 | const mapStateToProps = state => ({
5 | key: state.locales.locale,
6 | locale: state.locales.locale,
7 | messages: state.locales.messages
8 | });
9 |
10 | export default connect(mapStateToProps)(ReactIntlProvider);
11 |
--------------------------------------------------------------------------------
/src/lib/data-uri-to-blob.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Utility to convert data URIs to blobs
3 | * Adapted from https://stackoverflow.com/questions/12168909/blob-from-dataurl
4 | * @param {string} dataURI the data uri to blobify
5 | * @return {Blob} a blob representing the data uri
6 | */
7 | export default function dataURItoBlob (dataURI) {
8 | const byteString = atob(dataURI.split(',')[1]);
9 | const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
10 | const arrayBuffer = new ArrayBuffer(byteString.length);
11 | const uintArray = new Uint8Array(arrayBuffer);
12 | for (let i = 0; i < byteString.length; i++) {
13 | uintArray[i] = byteString.charCodeAt(i);
14 | }
15 | const blob = new Blob([arrayBuffer], {type: mimeString});
16 | return blob;
17 | }
18 |
--------------------------------------------------------------------------------
/src/lib/default-project/cd21514d0531fdffb22204e0ec5ed84a.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/default-project/fd8543abeeba255072da239223d2d342.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/default-project/fd8543abeeba255072da239223d2d342.wav
--------------------------------------------------------------------------------
/src/lib/download-blob.js:
--------------------------------------------------------------------------------
1 | export default (filename, blob) => {
2 | const downloadLink = document.createElement('a');
3 | document.body.appendChild(downloadLink);
4 |
5 | // Use special ms version if available to get it working on Edge.
6 | if (navigator.msSaveOrOpenBlob) {
7 | navigator.msSaveOrOpenBlob(blob, filename);
8 | return;
9 | }
10 |
11 | if ('download' in HTMLAnchorElement.prototype) {
12 | const url = window.URL.createObjectURL(blob);
13 | downloadLink.href = url;
14 | downloadLink.download = filename;
15 | downloadLink.type = blob.type;
16 | downloadLink.click();
17 | // remove the link after a timeout to prevent a crash on iOS 13 Safari
18 | window.setTimeout(() => {
19 | document.body.removeChild(downloadLink);
20 | window.URL.revokeObjectURL(url);
21 | }, 1000);
22 | } else {
23 | // iOS 12 Safari, open a new page and set href to data-uri
24 | let popup = window.open('', '_blank');
25 | const reader = new FileReader();
26 | reader.onloadend = function () {
27 | popup.location.href = reader.result;
28 | popup = null;
29 | };
30 | reader.readAsDataURL(blob);
31 | }
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/src/lib/drag-constants.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SOUND: 'SOUND',
3 | COSTUME: 'COSTUME',
4 | SPRITE: 'SPRITE',
5 | CODE: 'CODE',
6 |
7 | BACKPACK_SOUND: 'BACKPACK_SOUND',
8 | BACKPACK_COSTUME: 'BACKPACK_COSTUME',
9 | BACKPACK_SPRITE: 'BACKPACK_SPRITE',
10 | BACKPACK_CODE: 'BACKPACK_CODE'
11 | };
12 |
--------------------------------------------------------------------------------
/src/lib/error-boundary-hoc.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ErrorBoundary from '../containers/error-boundary.jsx';
3 |
4 | /*
5 | * Higher Order Component to provide error boundary for wrapped component.
6 | * A curried function, call like errorHOC()().
7 | * @param {string} action - Label for GA tracking of errors.
8 | * @returns {function} a function that accepts a component to wrap.
9 | */
10 | const ErrorBoundaryHOC = function (action){
11 | /**
12 | * The function to be called with a React component to wrap it.
13 | * @param {React.Component} WrappedComponent - Component to wrap with an error boundary.
14 | * @returns {React.Component} the component wrapped with an error boundary.
15 | */
16 | return function (WrappedComponent) {
17 | const ErrorBoundaryWrapper = props => (
18 |
19 |
20 |
21 | );
22 | return ErrorBoundaryWrapper;
23 | };
24 | };
25 |
26 | export default ErrorBoundaryHOC;
27 |
--------------------------------------------------------------------------------
/src/lib/extension-api.js:
--------------------------------------------------------------------------------
1 | class ExtensionAPI {
2 | constructor (gui) {
3 | this.gui = gui;
4 | }
5 |
6 | isDesktop () {
7 | return this.gui.props.isScratchDesktop;
8 | }
9 |
10 | isEditorLoading () {
11 | return this.gui.props.isLoading;
12 | }
13 |
14 | getSettings (id) {
15 | return this.gui.props.settings[id];
16 | }
17 | }
18 |
19 | export default ExtensionAPI;
20 |
--------------------------------------------------------------------------------
/src/lib/get-costume-url.js:
--------------------------------------------------------------------------------
1 | import storage from './storage';
2 | import {inlineSvgFonts} from 'scratch-svg-renderer';
3 |
4 | // Contains 'font-family', but doesn't only contain 'font-family="none"'
5 | const HAS_FONT_REGEXP = 'font-family(?!="none")';
6 |
7 | const getCostumeUrl = (function () {
8 | let cachedAssetId;
9 | let cachedUrl;
10 |
11 | return function (asset) {
12 |
13 | if (cachedAssetId === asset.assetId) {
14 | return cachedUrl;
15 | }
16 |
17 | cachedAssetId = asset.assetId;
18 |
19 | // If the SVG refers to fonts, they must be inlined in order to display correctly in the img tag.
20 | // Avoid parsing the SVG when possible, since it's expensive.
21 | if (asset.assetType === storage.AssetType.ImageVector) {
22 | const svgString = asset.decodeText();
23 | if (svgString.match(HAS_FONT_REGEXP)) {
24 | const svgText = inlineSvgFonts(svgString);
25 | cachedUrl = `data:image/svg+xml;utf8,${encodeURIComponent(svgText)}`;
26 | } else {
27 | cachedUrl = asset.encodeDataURI();
28 | }
29 | } else {
30 | cachedUrl = asset.encodeDataURI();
31 | }
32 |
33 | return cachedUrl;
34 | };
35 | }());
36 |
37 | export {
38 | getCostumeUrl as default,
39 | HAS_FONT_REGEXP
40 | };
41 |
--------------------------------------------------------------------------------
/src/lib/import-csv.js:
--------------------------------------------------------------------------------
1 | import Papa from 'papaparse';
2 |
3 | export default () => new Promise((resolve, reject) => {
4 | const fileInput = document.createElement('input');
5 | fileInput.setAttribute('type', 'file');
6 | fileInput.setAttribute('accept', '.csv, .tsv, .txt'); // parser auto-detects delimiter
7 | fileInput.onchange = e => {
8 | const file = e.target.files[0];
9 | Papa.parse(file, {
10 | header: false,
11 | complete: results => {
12 | document.body.removeChild(fileInput);
13 | resolve(results.data);
14 | },
15 | error: err => {
16 | document.body.removeChild(fileInput);
17 | reject(err);
18 | }
19 | });
20 | };
21 | document.body.appendChild(fileInput);
22 | fileInput.click();
23 | });
24 |
--------------------------------------------------------------------------------
/src/lib/isScratchDesktop.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Internal stored state. Not valid until after at least one call to `setIsScratchDesktop()`.
3 | * @type {boolean}
4 | */
5 | let _isScratchDesktop; // undefined = not ready yet
6 |
7 | /**
8 | * Tell the `isScratchDesktop()` whether or not the GUI is running under Scratch Desktop.
9 | * @param {boolean} value - the new value which `isScratchDesktop()` should return in the future.
10 | */
11 | const setIsScratchDesktop = function (value) {
12 | _isScratchDesktop = value;
13 | };
14 |
15 | /**
16 | * @returns {boolean} - true if it seems like the GUI is running under Scratch Desktop; false otherwise.
17 | * If `setIsScratchDesktop()` has not yet been called, this can return `undefined`.
18 | */
19 | const isScratchDesktop = function () {
20 | return _isScratchDesktop;
21 | };
22 |
23 | /**
24 | * @returns {boolean} - false if it seems like the GUI is running under Scratch Desktop; true otherwise.
25 | */
26 | const notScratchDesktop = function () {
27 | return !isScratchDesktop();
28 | };
29 |
30 | export default isScratchDesktop;
31 | export {
32 | isScratchDesktop,
33 | notScratchDesktop,
34 | setIsScratchDesktop
35 | };
36 |
--------------------------------------------------------------------------------
/src/lib/l10n.js:
--------------------------------------------------------------------------------
1 | import {defineMessages} from 'react-intl';
2 |
3 | export default defineMessages({
4 | 'mode': {
5 | defaultMessage: 'Mode',
6 | description: 'Mode menu item in the menu bar',
7 | id: 'gui.menuBar.modeMenu'
8 | },
9 | 'normal': {
10 | defaultMessage: 'Normal mode',
11 | description: 'April fools: resets editor to not have any pranks',
12 | id: 'gui.menuBar.normalMode'
13 | },
14 | 'cat': {
15 | defaultMessage: 'Caturday mode',
16 | description: 'April fools: Cat blocks mode',
17 | id: 'gui.menuBar.caturdayMode'
18 | },
19 | '90s': {
20 | defaultMessage: '90s mode',
21 | description: 'April fools: Makes the editor look like the 1990s',
22 | id: 'gui.menuBar.1990sMode'
23 | },
24 | 'old': {
25 | defaultMessage: 'Old timey mode',
26 | description: 'April fools: Makes the editor look like an old movie projector', // eslint-disable-line max-len
27 | id: 'gui.menuBar.oldTimeyMode'
28 | },
29 | 'prehistoric': {
30 | defaultMessage: 'Prehistoric mode',
31 | description: 'April fools: Makes the editor look like inside a cave',
32 | id: 'gui.menuBar.prehistoricMode'
33 | }
34 | });
35 |
--------------------------------------------------------------------------------
/src/lib/lazy-blocks.js:
--------------------------------------------------------------------------------
1 | import blocks from './blocks.js';
2 | /* eslint-disable linebreak-style */
3 | // 异步加载 clipcc-block
4 | let BlocksComponent = null;
5 |
6 | const loaded = () => !!BlocksComponent;
7 |
8 | const get = () => {
9 | if (!loaded()) return Error('blocks not loaded');
10 | return BlocksComponent;
11 | };
12 |
13 | const load = (vm, callback) => {
14 | if (BlocksComponent) return Promise.resolve(BlocksComponent);
15 | return import(/* webpackChunkName: "ccblocks" */'clipcc-block').then(data => {
16 | BlocksComponent = data.default;
17 | callback(blocks(vm));
18 | return BlocksComponent;
19 | });
20 | };
21 |
22 | export default {
23 | get,
24 | load,
25 | loaded
26 | };
27 |
--------------------------------------------------------------------------------
/src/lib/libraries/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | rules: {
3 | // These manifest files use duplicate imports to make things easier to follow
4 | // by providing clear parallel structure. Turn the error off for this folder.
5 | 'no-duplicate-imports': 0
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/src/lib/libraries/async-load-libraries.js:
--------------------------------------------------------------------------------
1 | const asyncLibrary = callback => {
2 | let data = null;
3 | return () => {
4 | if (data) return data;
5 | return callback()
6 | .then(mod => (data = mod.default));
7 | };
8 | };
9 |
10 | export const getBackdropLibrary = asyncLibrary(
11 | () => import('./backdrops.json')
12 | );
13 | export const getCostumeLibrary = asyncLibrary(
14 | () => import('./costumes.json')
15 | );
16 | export const getSoundLibrary = asyncLibrary(
17 | () => import('./sounds.json')
18 | );
19 | export const getSpriteLibrary = asyncLibrary(
20 | () => import('./sprites.json')
21 | );
22 |
--------------------------------------------------------------------------------
/src/lib/libraries/backdrop-tags.js:
--------------------------------------------------------------------------------
1 | import messages from './tag-messages.js';
2 | export default [
3 | {tag: 'fantasy', intlLabel: messages.fantasy},
4 | {tag: 'music', intlLabel: messages.music},
5 | {tag: 'sports', intlLabel: messages.sports},
6 | {tag: 'outdoors', intlLabel: messages.outdoors},
7 | {tag: 'indoors', intlLabel: messages.indoors},
8 | {tag: 'space', intlLabel: messages.space},
9 | {tag: 'underwater', intlLabel: messages.underwater},
10 | {tag: 'patterns', intlLabel: messages.patterns}
11 | ];
12 |
--------------------------------------------------------------------------------
/src/lib/libraries/decks/en-steps.js:
--------------------------------------------------------------------------------
1 | const enImages = {};
2 | export {enImages};
3 |
--------------------------------------------------------------------------------
/src/lib/libraries/decks/index.jsx:
--------------------------------------------------------------------------------
1 | export default {};
--------------------------------------------------------------------------------
/src/lib/libraries/decks/thumbnails/getting-started-asl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/decks/thumbnails/getting-started-asl.png
--------------------------------------------------------------------------------
/src/lib/libraries/decks/translate-image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview
3 | * Utility functions for handling tutorial images in multiple languages
4 | */
5 |
6 | import {enImages as defaultImages} from './en-steps.js';
7 |
8 | let savedImages = {};
9 | let savedLocale = '';
10 |
11 | const translations = {};
12 |
13 | const loadImageData = locale => {
14 | if (translations.hasOwnProperty(locale)) {
15 | translations[locale]()
16 | .then(newImages => {
17 | savedImages = newImages;
18 | savedLocale = locale;
19 | });
20 | }
21 | };
22 |
23 | /**
24 | * Return image data for tutorials based on locale (default: en)
25 | * @param {string} imageId key in the images object, or id string.
26 | * @param {string} locale requested locale
27 | * @return {string} image
28 | */
29 | const translateImage = (imageId, locale) => {
30 | if (locale !== savedLocale || !savedImages.hasOwnProperty(imageId)) {
31 | return defaultImages[imageId];
32 | }
33 | return savedImages[imageId];
34 | };
35 |
36 | export {
37 | loadImageData,
38 | translateImage
39 | };
40 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/HTTPIO/HTTPIO.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/HTTPIO/HTTPIO.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/HTTPIO/HTTPIO_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/HTTPIO/clipcc.httpio-small.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/JSON/JSON.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/JSON/JSON.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/JSON/JSON_icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/JSON/ccjson-small.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/boost/boost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/boost/boost.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/clipcc/CCUnknownExtension.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/clipcc/CCUnknownExtension.jpg
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/ev3/ev3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/ev3/ev3.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/gdxfor/gdxfor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/gdxfor/gdxfor.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/libra/Libra.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/libra/Libra.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/makeymakey/makeymakey-small.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/makeymakey/makeymakey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/makeymakey/makeymakey.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/microbit/microbit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/microbit/microbit.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/music/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/music/music.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/pen/pen-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/pen/pen-old.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/pen/pen-small.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/pen/pen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/pen/pen.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/speech2text/speech.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/speech2text/speech.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/text2speech/text2speech-old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/text2speech/text2speech-old.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/text2speech/text2speech.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/text2speech/text2speech.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/translate/translate-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/translate/translate-small.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/translate/translate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/translate/translate.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/upload/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/upload/upload.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/videoSensing/video-sensing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/videoSensing/video-sensing.png
--------------------------------------------------------------------------------
/src/lib/libraries/extensions/wedo2/wedo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/lib/libraries/extensions/wedo2/wedo.png
--------------------------------------------------------------------------------
/src/lib/libraries/sound-tags.js:
--------------------------------------------------------------------------------
1 | import messages from './tag-messages.js';
2 | export default [
3 | {tag: 'animals', intlLabel: messages.animals},
4 | {tag: 'effects', intlLabel: messages.effects},
5 | {tag: 'loops', intlLabel: messages.loops},
6 | {tag: 'notes', intlLabel: messages.notes},
7 | {tag: 'percussion', intlLabel: messages.percussion},
8 | {tag: 'space', intlLabel: messages.space},
9 | {tag: 'sports', intlLabel: messages.sports},
10 | {tag: 'voice', intlLabel: messages.voice},
11 | {tag: 'wacky', intlLabel: messages.wacky}
12 | ];
13 |
--------------------------------------------------------------------------------
/src/lib/libraries/sprite-tags.js:
--------------------------------------------------------------------------------
1 | import messages from './tag-messages.js';
2 | export default [
3 | {tag: 'animals', intlLabel: messages.animals},
4 | {tag: 'people', intlLabel: messages.people},
5 | {tag: 'fantasy', intlLabel: messages.fantasy},
6 | {tag: 'dance', intlLabel: messages.dance},
7 | {tag: 'music', intlLabel: messages.music},
8 | {tag: 'sports', intlLabel: messages.sports},
9 | {tag: 'food', intlLabel: messages.food},
10 | {tag: 'fashion', intlLabel: messages.fashion},
11 | {tag: 'letters', intlLabel: messages.letters}
12 | ];
13 |
--------------------------------------------------------------------------------
/src/lib/libraries/tutorial-tags.js:
--------------------------------------------------------------------------------
1 | import messages from './tag-messages.js';
2 | export default [
3 | {tag: 'animation', intlLabel: messages.animation},
4 | {tag: 'art', intlLabel: messages.art},
5 | {tag: 'music', intlLabel: messages.music},
6 | {tag: 'games', intlLabel: messages.games},
7 | {tag: 'stories', intlLabel: messages.stories}
8 | ];
9 |
--------------------------------------------------------------------------------
/src/lib/locale-utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview
3 | * Utility functions related to localization specific to the GUI
4 | */
5 |
6 | const wideLocales = [
7 | 'ab',
8 | 'ca',
9 | 'de',
10 | 'el',
11 | 'it',
12 | 'ja',
13 | 'ja-Hira',
14 | 'ko',
15 | 'hu',
16 | 'ru',
17 | 'vi'
18 | ];
19 |
20 | /**
21 | * Identify the languages where translations are too long to fit in fixed width parts of the gui.
22 | * @param {string} locale The current locale.
23 | * @return {bool} true if translations in this language are too long
24 | */
25 |
26 | const isWideLocale = locale => (
27 | wideLocales.indexOf(locale) !== -1
28 | );
29 |
30 | export {
31 | wideLocales,
32 | isWideLocale
33 | };
34 |
--------------------------------------------------------------------------------
/src/lib/log.js:
--------------------------------------------------------------------------------
1 | import minilog from 'minilog';
2 | minilog.enable();
3 |
4 | export default minilog('gui');
5 |
--------------------------------------------------------------------------------
/src/lib/math.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Adjust a number to specific precision.
3 | * @param {function} func Adjustment function.
4 | * @param {number} value The number.
5 | * @param {number} precision The count of numbers after point.
6 | * @returns {number} The adjusted value.
7 | */
8 | const adjustNumber = (func, value, precision) => {
9 | if (typeof precision === 'undefined' || +precision === 0) {
10 | return func(value);
11 | }
12 | value = +value;
13 | precision = +precision;
14 | if (isNaN(value) || !(typeof precision === 'number' && precision % 1 === 0)) {
15 | return NaN;
16 | }
17 | value = value.toString().split('e');
18 | value = func(+(`${value[0]}e${value[1] ? (+value[1] + precision) : +precision}`));
19 | value = value.toString().split('e');
20 | return +(`${value[0]}e${value[1] ? (+value[1] - precision) : -precision}`);
21 | };
22 |
23 | const round10 = (value, precision) => adjustNumber(Math.round, value, precision);
24 | const floor10 = (value, precision) => adjustNumber(Math.floor, value, precision);
25 | const ceil10 = (value, precision) => adjustNumber(Math.ceil, value, precision);
26 |
27 | export {
28 | round10, floor10, ceil10
29 | };
30 |
--------------------------------------------------------------------------------
/src/lib/randomize-sprite-position.js:
--------------------------------------------------------------------------------
1 | const randomizeSpritePosition = spriteObject => {
2 | // https://github.com/LLK/scratch-flash/blob/689f3c79a7e8b2e98f5be80056d877f303a8d8ba/src/Scratch.as#L1385
3 | spriteObject.x = Math.floor((200 * Math.random()) - 100);
4 | spriteObject.y = Math.floor((100 * Math.random()) - 50);
5 | };
6 |
7 | export default randomizeSpritePosition;
8 |
--------------------------------------------------------------------------------
/src/lib/supported-browser.js:
--------------------------------------------------------------------------------
1 | import bowser from 'bowser';
2 |
3 | const minVersions = {
4 | chrome: '72',
5 | msedge: '19',
6 | firefox: '70',
7 | safari: '12'
8 | };
9 |
10 | /**
11 | * Helper function to determine if the browser is supported at all.
12 | * @returns {boolean} False if the platform is definitely not supported.
13 | */
14 | const supportedBrowser = () => {
15 | if (bowser.msie) {
16 | return false;
17 | }
18 | return true;
19 | };
20 |
21 | /**
22 | * Helper function to determine if the browser meets the minimum recommended version
23 | * @returns {boolean} False if the browser isn't a recommended browser, or doesn't
24 | * meet the minimum version for recommended browsers.
25 | * NOTE: uses strict_mode==true so that any browser not listed in the minVersions
26 | * always returns false
27 | */
28 |
29 | const recommendedBrowser = () =>
30 | !bowser.isUnsupportedBrowser(minVersions, true) ||
31 | window.navigator.userAgent.toLowerCase().indexOf('googlebot') !== -1;
32 |
33 | export {
34 | supportedBrowser as default,
35 | recommendedBrowser
36 | };
37 |
--------------------------------------------------------------------------------
/src/lib/tablet-full-screen.js:
--------------------------------------------------------------------------------
1 | import bowser from 'bowser';
2 |
3 | /**
4 | * Helper method to request full screen in the browser when on a tablet.
5 | */
6 | export default function () {
7 | if (bowser.tablet) {
8 | if ((bowser.webkit || bowser.blink) && document.documentElement.webkitRequestFullScreen) {
9 | document.documentElement.webkitRequestFullScreen();
10 | }
11 | if (bowser.gecko && document.documentElement.mozRequestFullScreen) {
12 | document.documentElement.mozRequestFullScreen();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/touch-utils.js:
--------------------------------------------------------------------------------
1 | const getEventXY = e => {
2 | if (e.touches && e.touches[0]) {
3 | return {x: e.touches[0].clientX, y: e.touches[0].clientY};
4 | } else if (e.changedTouches && e.changedTouches[0]) {
5 | return {x: e.changedTouches[0].clientX, y: e.changedTouches[0].clientY};
6 | }
7 | return {x: e.clientX, y: e.clientY};
8 | };
9 |
10 | export {
11 | getEventXY
12 | };
13 |
--------------------------------------------------------------------------------
/src/lib/variable-utils.js:
--------------------------------------------------------------------------------
1 | // Utility functions for updating variables in the VM
2 | // TODO (VM#1145) these should be moved to top-level VM API
3 | const getVariable = (vm, targetId, variableId) => {
4 | const target = targetId ?
5 | vm.runtime.getTargetById(targetId) :
6 | vm.runtime.getTargetForStage();
7 | return target.variables[variableId];
8 | };
9 |
10 | const getVariableValue = (vm, targetId, variableId) => {
11 | const variable = getVariable(vm, targetId, variableId);
12 | // If array, return a new copy for mutating, ensuring that updates stay immutable.
13 | if (variable.value instanceof Array) return variable.value.slice();
14 | return variable.value;
15 | };
16 |
17 | const setVariableValue = (vm, targetId, variableId, value) => {
18 | getVariable(vm, targetId, variableId).value = value;
19 | };
20 |
21 | export {
22 | getVariable,
23 | getVariableValue,
24 | setVariableValue
25 | };
26 |
--------------------------------------------------------------------------------
/src/lib/video/camera.js:
--------------------------------------------------------------------------------
1 | import getUserMedia from 'get-user-media-promise';
2 |
3 | // Single Setup For All Video Streams used by the GUI
4 | // While VideoProvider uses a private _singleSetup
5 | // property to ensure that each instance of a VideoProvider
6 | // use the same setup, this ensures that all instances
7 | // of VideoProviders use a single stream. This way, closing a camera modal
8 | // does not affect the video on the stage, and a program running and disabling
9 | // video on the stage will not affect the camera modal's video.
10 | const requestStack = [];
11 | const requestVideoStream = videoDesc => {
12 | let streamPromise;
13 | if (requestStack.length === 0) {
14 | streamPromise = getUserMedia({
15 | audio: false,
16 | video: videoDesc
17 | });
18 | requestStack.push(streamPromise);
19 | } else if (requestStack.length > 0) {
20 | streamPromise = requestStack[0];
21 | requestStack.push(true);
22 | }
23 | return streamPromise;
24 | };
25 |
26 | const requestDisableVideo = () => {
27 | requestStack.pop();
28 | if (requestStack.length > 0) return false;
29 | return true;
30 | };
31 |
32 | export {
33 | requestVideoStream,
34 | requestDisableVideo
35 | };
36 |
--------------------------------------------------------------------------------
/src/playground/blocks-only.css:
--------------------------------------------------------------------------------
1 | .controls {
2 | position: absolute;
3 | z-index: 2;
4 | top: 10px;
5 | right: 15px;
6 | }
7 |
--------------------------------------------------------------------------------
/src/playground/blocks-only.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {connect} from 'react-redux';
4 |
5 | import Controls from '../containers/controls.jsx';
6 | import Blocks from '../containers/blocks.jsx';
7 | import GUI from '../containers/gui.jsx';
8 | import HashParserHOC from '../lib/hash-parser-hoc.jsx';
9 | import AppStateHOC from '../lib/app-state-hoc.jsx';
10 |
11 | import styles from './blocks-only.css';
12 |
13 | const mapStateToProps = state => ({vm: state.scratchGui.vm});
14 |
15 | const VMBlocks = connect(mapStateToProps)(Blocks);
16 | const VMControls = connect(mapStateToProps)(Controls);
17 |
18 | const BlocksOnly = props => (
19 |
20 |
26 |
27 |
28 | );
29 |
30 | const App = AppStateHOC(HashParserHOC(BlocksOnly));
31 |
32 | const appTarget = document.createElement('div');
33 | document.body.appendChild(appTarget);
34 |
35 | ReactDOM.render(, appTarget);
36 |
--------------------------------------------------------------------------------
/src/playground/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | .app {
4 | /* probably unecessary, transitional until layout is refactored */
5 | width: 100%;
6 | height: 100%;
7 | margin: 0;
8 |
9 | /* Setting min height/width makes the UI scroll below those sizes */
10 | min-width: 1024px;
11 | min-height: 640px; /* Min height to fit sprite/backdrop button */
12 | }
13 |
14 | /* @todo: move globally? Safe / side FX, for blocks particularly? */
15 | * { box-sizing: border-box; }
16 |
--------------------------------------------------------------------------------
/src/playground/player.css:
--------------------------------------------------------------------------------
1 | .stage-only {
2 | width: calc(480px + 1rem);
3 | }
4 |
5 | .editor {
6 | position: absolute;
7 | top: 0;
8 | left: 0;
9 | height: 100%;
10 | width: 100%;
11 | }
12 |
13 | .stage-only * {
14 | box-sizing: border-box;
15 | }
16 |
--------------------------------------------------------------------------------
/src/reducers/asset-drag.js:
--------------------------------------------------------------------------------
1 | const DRAG_UPDATE = 'clipcc-gui/asset-drag/DRAG_UPDATE';
2 |
3 | const initialState = {
4 | dragging: false,
5 | currentOffset: null,
6 | img: null
7 | };
8 |
9 | const reducer = function (state, action) {
10 | if (typeof state === 'undefined') state = initialState;
11 |
12 | switch (action.type) {
13 | case DRAG_UPDATE:
14 | return Object.assign({}, state, action.state);
15 | default:
16 | return state;
17 | }
18 | };
19 |
20 | const updateAssetDrag = function (state) {
21 | return {
22 | type: DRAG_UPDATE,
23 | state: state
24 | };
25 | };
26 |
27 | export {
28 | reducer as default,
29 | initialState as assetDragInitialState,
30 | updateAssetDrag
31 | };
32 |
--------------------------------------------------------------------------------
/src/reducers/block-drag.js:
--------------------------------------------------------------------------------
1 | const BLOCK_DRAG_UPDATE = 'clipcc-gui/block-drag/BLOCK_DRAG_UPDATE';
2 |
3 | const initialState = false;
4 |
5 | const reducer = function (state, action) {
6 | if (typeof state === 'undefined') state = initialState;
7 | switch (action.type) {
8 | case BLOCK_DRAG_UPDATE:
9 | return action.areBlocksOverGui;
10 | default:
11 | return state;
12 | }
13 | };
14 |
15 | const updateBlockDrag = function (areBlocksOverGui) {
16 | return {
17 | type: BLOCK_DRAG_UPDATE,
18 | areBlocksOverGui: areBlocksOverGui,
19 | meta: {
20 | throttle: 30
21 | }
22 | };
23 | };
24 |
25 | export {
26 | reducer as default,
27 | initialState as blockDragInitialState,
28 | updateBlockDrag
29 | };
30 |
--------------------------------------------------------------------------------
/src/reducers/connection-modal.js:
--------------------------------------------------------------------------------
1 | const SET_ID = 'clipcc-gui/connection-modal/setId';
2 |
3 | const initialState = {
4 | extensionId: null
5 | };
6 |
7 | const reducer = function (state, action) {
8 | if (typeof state === 'undefined') state = initialState;
9 | switch (action.type) {
10 | case SET_ID:
11 | return Object.assign({}, state, {
12 | extensionId: action.extensionId
13 | });
14 | default:
15 | return state;
16 | }
17 | };
18 |
19 | const setConnectionModalExtensionId = function (extensionId) {
20 | return {
21 | type: SET_ID,
22 | extensionId: extensionId
23 | };
24 | };
25 |
26 | export {
27 | reducer as default,
28 | initialState as connectionModalInitialState,
29 | setConnectionModalExtensionId
30 | };
31 |
--------------------------------------------------------------------------------
/src/reducers/editor-tab.js:
--------------------------------------------------------------------------------
1 | const ACTIVATE_TAB = 'clipcc-gui/navigation/ACTIVATE_TAB';
2 |
3 | // Constants use numbers to make it easier to work with react-tabs
4 | const BLOCKS_TAB_INDEX = 0;
5 | const COSTUMES_TAB_INDEX = 1;
6 | const SOUNDS_TAB_INDEX = 2;
7 |
8 | const initialState = {
9 | activeTabIndex: BLOCKS_TAB_INDEX
10 | };
11 |
12 | const reducer = function (state, action) {
13 | if (typeof state === 'undefined') state = initialState;
14 | switch (action.type) {
15 | case ACTIVATE_TAB:
16 | return Object.assign({}, state, {
17 | activeTabIndex: action.activeTabIndex
18 | });
19 | default:
20 | return state;
21 | }
22 | };
23 |
24 | const activateTab = function (tab) {
25 | return {
26 | type: ACTIVATE_TAB,
27 | activeTabIndex: tab
28 | };
29 | };
30 |
31 | export {
32 | reducer as default,
33 | initialState as editorTabInitialState,
34 | activateTab,
35 | BLOCKS_TAB_INDEX,
36 | COSTUMES_TAB_INDEX,
37 | SOUNDS_TAB_INDEX
38 | };
39 |
--------------------------------------------------------------------------------
/src/reducers/extension-settings.js:
--------------------------------------------------------------------------------
1 | const NEW_EXTENSION = 'clipcc-gui/extension-settings/NEW_EXTENSION';
2 |
3 | const initialState = {};
4 |
5 | const reducer = function (state, action) {
6 | if (typeof state === 'undefined') state = initialState;
7 | switch (action.type) {
8 | case NEW_EXTENSION:
9 | state[action.id] = action.options;
10 | return Object.assign({}, state);
11 | default:
12 | return state;
13 | }
14 | };
15 |
16 | const newExtensionSettings = (id, options) => ({
17 | type: NEW_EXTENSION,
18 | id,
19 | options
20 | });
21 |
22 | export {
23 | reducer as default,
24 | initialState as extensionSettingsInitialState,
25 | newExtensionSettings
26 | };
27 |
--------------------------------------------------------------------------------
/src/reducers/fonts-loaded.js:
--------------------------------------------------------------------------------
1 | const SET_FONTS_LOADED = 'fontsLoaded/SET_FONTS_LOADED';
2 |
3 | const initialState = false;
4 |
5 | const reducer = function (state, action) {
6 | if (typeof state === 'undefined') state = initialState;
7 | switch (action.type) {
8 | case SET_FONTS_LOADED:
9 | return action.loaded;
10 | default:
11 | return state;
12 | }
13 | };
14 | const setFontsLoaded = () => ({
15 | type: SET_FONTS_LOADED,
16 | loaded: true
17 | });
18 |
19 | export {
20 | reducer as default,
21 | initialState as fontsLoadedInitialState,
22 | setFontsLoaded
23 | };
24 |
--------------------------------------------------------------------------------
/src/reducers/hovered-target.js:
--------------------------------------------------------------------------------
1 | const SET_HOVERED_SPRITE = 'clipcc-gui/hovered-target/SET_HOVERED_SPRITE';
2 | const SET_RECEIVED_BLOCKS = 'clipcc-gui/hovered-target/SET_RECEIVED_BLOCKS';
3 |
4 | const initialState = {
5 | sprite: null,
6 | receivedBlocks: false
7 | };
8 |
9 | const reducer = function (state, action) {
10 | if (typeof state === 'undefined') state = initialState;
11 | switch (action.type) {
12 | case SET_HOVERED_SPRITE:
13 | return {
14 | sprite: action.spriteId,
15 | receivedBlocks: false
16 | };
17 | case SET_RECEIVED_BLOCKS:
18 | return {
19 | sprite: state.sprite,
20 | receivedBlocks: action.receivedBlocks
21 | };
22 | default:
23 | return state;
24 | }
25 | };
26 |
27 | const setHoveredSprite = function (spriteId) {
28 | return {
29 | type: SET_HOVERED_SPRITE,
30 | spriteId: spriteId,
31 | meta: {
32 | throttle: 30
33 | }
34 | };
35 | };
36 |
37 | const setReceivedBlocks = function (receivedBlocks) {
38 | return {
39 | type: SET_RECEIVED_BLOCKS,
40 | receivedBlocks: receivedBlocks
41 | };
42 | };
43 |
44 | export {
45 | reducer as default,
46 | initialState as hoveredTargetInitialState,
47 | setHoveredSprite,
48 | setReceivedBlocks
49 | };
50 |
--------------------------------------------------------------------------------
/src/reducers/load-error.js:
--------------------------------------------------------------------------------
1 | const SET_LOAD_ERROR = 'clipcc-gui/load-error/SET_LOAD_ERROR';
2 |
3 | const initialState = {
4 | errorId: '',
5 | detail: '',
6 | missingExtensions: []
7 | };
8 |
9 | const reducer = function (state, action) {
10 | if (typeof state === 'undefined') state = initialState;
11 | if (action.type === SET_LOAD_ERROR) {
12 | return Object.assign({}, initialState, action.data);
13 | }
14 | return state;
15 | };
16 |
17 | const setLoadError = function (data) {
18 | return {
19 | type: SET_LOAD_ERROR,
20 | data: data
21 | };
22 | };
23 |
24 | export {
25 | reducer as default,
26 | initialState as loadErrorInitialState,
27 | setLoadError
28 | };
29 |
--------------------------------------------------------------------------------
/src/reducers/mic-indicator.js:
--------------------------------------------------------------------------------
1 | const UPDATE = 'clipcc-gui/mic-indicator/UPDATE';
2 |
3 | const initialState = false;
4 |
5 | const reducer = function (state, action) {
6 | if (typeof state === 'undefined') state = initialState;
7 | switch (action.type) {
8 | case UPDATE:
9 | return action.visible;
10 | default:
11 | return state;
12 | }
13 | };
14 |
15 | const updateMicIndicator = function (visible) {
16 | return {
17 | type: UPDATE,
18 | visible: visible
19 | };
20 | };
21 |
22 | export {
23 | reducer as default,
24 | initialState as micIndicatorInitialState,
25 | updateMicIndicator
26 | };
27 |
--------------------------------------------------------------------------------
/src/reducers/monitors.js:
--------------------------------------------------------------------------------
1 | const UPDATE_MONITORS = 'clipcc-gui/monitors/UPDATE_MONITORS';
2 | import {OrderedMap} from 'immutable';
3 |
4 | const initialState = OrderedMap();
5 |
6 | const reducer = function (state, action) {
7 | if (typeof state === 'undefined') state = initialState;
8 | switch (action.type) {
9 | case UPDATE_MONITORS:
10 | return action.monitors;
11 | default:
12 | return state;
13 | }
14 | };
15 |
16 | const updateMonitors = function (monitors) {
17 | return {
18 | type: UPDATE_MONITORS,
19 | monitors: monitors,
20 | meta: {
21 | throttle: 30
22 | }
23 | };
24 | };
25 |
26 | export {
27 | reducer as default,
28 | initialState as monitorsInitialState,
29 | updateMonitors
30 | };
31 |
--------------------------------------------------------------------------------
/src/reducers/project-changed.js:
--------------------------------------------------------------------------------
1 | const SET_PROJECT_CHANGED = 'clipcc-gui/project-changed/SET_PROJECT_CHANGED';
2 |
3 | const initialState = false;
4 |
5 | const reducer = function (state, action) {
6 | if (typeof state === 'undefined') state = initialState;
7 | switch (action.type) {
8 | case SET_PROJECT_CHANGED:
9 | return action.changed;
10 | default:
11 | return state;
12 | }
13 | };
14 | const setProjectChanged = () => ({
15 | type: SET_PROJECT_CHANGED,
16 | changed: true
17 | });
18 | const setProjectUnchanged = () => ({
19 | type: SET_PROJECT_CHANGED,
20 | changed: false
21 | });
22 |
23 | export {
24 | reducer as default,
25 | initialState as projectChangedInitialState,
26 | setProjectChanged,
27 | setProjectUnchanged
28 | };
29 |
--------------------------------------------------------------------------------
/src/reducers/project-title.js:
--------------------------------------------------------------------------------
1 | const SET_PROJECT_TITLE = 'projectTitle/SET_PROJECT_TITLE';
2 |
3 | // we are initializing to a blank string instead of an actual title,
4 | // because it would be hard to localize here
5 | const initialState = '';
6 |
7 | const reducer = function (state, action) {
8 | if (typeof state === 'undefined') state = initialState;
9 | switch (action.type) {
10 | case SET_PROJECT_TITLE:
11 | return action.title;
12 | default:
13 | return state;
14 | }
15 | };
16 | const setProjectTitle = title => ({
17 | type: SET_PROJECT_TITLE,
18 | title: title
19 | });
20 |
21 | export {
22 | reducer as default,
23 | initialState as projectTitleInitialState,
24 | setProjectTitle
25 | };
26 |
--------------------------------------------------------------------------------
/src/reducers/restore-deletion.js:
--------------------------------------------------------------------------------
1 | const RESTORE_UPDATE = 'clipcc-gui/restore-deletion/RESTORE_UPDATE';
2 |
3 | const initialState = {
4 | restoreFun: null,
5 | deletedItem: ''
6 | };
7 |
8 | const reducer = function (state, action) {
9 | if (typeof state === 'undefined') state = initialState;
10 |
11 | switch (action.type) {
12 | case RESTORE_UPDATE:
13 | return Object.assign({}, state, action.state);
14 | default:
15 | return state;
16 | }
17 | };
18 |
19 | const setRestore = function (state) {
20 | return {
21 | type: RESTORE_UPDATE,
22 | state: {
23 | restoreFun: state.restoreFun,
24 | deletedItem: state.deletedItem
25 | }
26 | };
27 | };
28 |
29 | export {
30 | reducer as default,
31 | initialState as restoreDeletionInitialState,
32 | setRestore
33 | };
34 |
--------------------------------------------------------------------------------
/src/reducers/stage-size.js:
--------------------------------------------------------------------------------
1 | import {STAGE_DISPLAY_SIZES} from '../lib/layout-constants.js';
2 |
3 | const SET_STAGE_SIZE = 'clipcc-gui/StageSize/SET_STAGE_SIZE';
4 |
5 | const initialState = {
6 | stageSize: STAGE_DISPLAY_SIZES.large
7 | };
8 |
9 | const reducer = function (state, action) {
10 | if (typeof state === 'undefined') state = initialState;
11 | switch (action.type) {
12 | case SET_STAGE_SIZE:
13 | return {
14 | stageSize: action.stageSize
15 | };
16 | default:
17 | return state;
18 | }
19 | };
20 |
21 | const setStageSize = function (stageSize) {
22 | return {
23 | type: SET_STAGE_SIZE,
24 | stageSize: stageSize
25 | };
26 | };
27 |
28 | export {
29 | reducer as default,
30 | initialState as stageSizeInitialState,
31 | setStageSize
32 | };
33 |
--------------------------------------------------------------------------------
/src/reducers/timeout.js:
--------------------------------------------------------------------------------
1 | const SET_AUTOSAVE_TIMEOUT_ID = 'timeout/SET_AUTOSAVE_TIMEOUT_ID';
2 |
3 | const initialState = {
4 | autoSaveTimeoutId: null
5 | };
6 |
7 | const reducer = function (state, action) {
8 | if (typeof state === 'undefined') state = initialState;
9 | switch (action.type) {
10 | case SET_AUTOSAVE_TIMEOUT_ID:
11 | return Object.assign({}, state, {
12 | autoSaveTimeoutId: action.id
13 | });
14 | default:
15 | return state;
16 | }
17 | };
18 | const setAutoSaveTimeoutId = id => ({
19 | type: SET_AUTOSAVE_TIMEOUT_ID,
20 | id
21 | });
22 |
23 | export {
24 | reducer as default,
25 | initialState as timeoutInitialState,
26 | setAutoSaveTimeoutId
27 | };
28 |
--------------------------------------------------------------------------------
/src/reducers/toolbox.js:
--------------------------------------------------------------------------------
1 | const UPDATE_TOOLBOX = 'clipcc-gui/toolbox/UPDATE_TOOLBOX';
2 | import makeToolboxXML from '../lib/make-toolbox-xml';
3 |
4 | const initialState = {
5 | toolboxXML: makeToolboxXML(true)
6 | };
7 |
8 | const reducer = function (state, action) {
9 | if (typeof state === 'undefined') state = initialState;
10 | switch (action.type) {
11 | case UPDATE_TOOLBOX:
12 | return Object.assign({}, state, {
13 | toolboxXML: action.toolboxXML
14 | });
15 | default:
16 | return state;
17 | }
18 | };
19 |
20 | const updateToolbox = function (toolboxXML) {
21 | return {
22 | type: UPDATE_TOOLBOX,
23 | toolboxXML: toolboxXML
24 | };
25 | };
26 |
27 | export {
28 | reducer as default,
29 | initialState as toolboxInitialState,
30 | updateToolbox
31 | };
32 |
--------------------------------------------------------------------------------
/src/reducers/vm.js:
--------------------------------------------------------------------------------
1 | import VM from 'clipcc-vm';
2 | import storage from '../lib/storage';
3 | import {extensionManager} from 'clipcc-extension';
4 | import {appVersion} from '../lib/app-info';
5 |
6 | const SET_VM = 'clipcc-gui/vm/SET_VM';
7 | const defaultVM = new VM({appVersion, extensionManager});
8 | defaultVM.attachStorage(storage);
9 | console.log(`%cClipCC ${appVersion}`, 'font-size:32px;');
10 | const initialState = defaultVM;
11 |
12 | const reducer = function (state, action) {
13 | if (typeof state === 'undefined') state = initialState;
14 | switch (action.type) {
15 | case SET_VM:
16 | return action.vm;
17 | default:
18 | return state;
19 | }
20 | };
21 | const setVM = function (vm) {
22 | return {
23 | type: SET_VM,
24 | vm: vm
25 | };
26 | };
27 |
28 | export {
29 | reducer as default,
30 | initialState as vmInitialState,
31 | setVM
32 | };
33 |
--------------------------------------------------------------------------------
/src/reducers/workspace-metrics.js:
--------------------------------------------------------------------------------
1 | const UPDATE_METRICS = 'scratch-gui/workspace-metrics/UPDATE_METRICS';
2 |
3 | const initialState = {
4 | targets: {}
5 | };
6 |
7 | const reducer = function (state, action) {
8 | if (typeof state === 'undefined') state = initialState;
9 |
10 | switch (action.type) {
11 | case UPDATE_METRICS:
12 | return Object.assign({}, state, {
13 | targets: Object.assign({}, state.targets, {
14 | [action.targetID]: {
15 | scrollX: action.scrollX,
16 | scrollY: action.scrollY,
17 | scale: action.scale
18 | }
19 | })
20 | });
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 | const updateMetrics = function (metrics) {
27 | return {
28 | type: UPDATE_METRICS,
29 | ...metrics
30 | };
31 | };
32 |
33 | export {
34 | reducer as default,
35 | initialState as workspaceMetricsInitialState,
36 | updateMetrics
37 | };
38 |
--------------------------------------------------------------------------------
/src/test.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/src/test.js
--------------------------------------------------------------------------------
/static/clipcc_logo144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/static/clipcc_logo144.png
--------------------------------------------------------------------------------
/static/clipcc_logo48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/static/clipcc_logo48.png
--------------------------------------------------------------------------------
/static/clipcc_logo72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/static/clipcc_logo72.png
--------------------------------------------------------------------------------
/static/clipcc_logo96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/static/clipcc_logo96.png
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/static/favicon.ico
--------------------------------------------------------------------------------
/static/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ClipCC",
3 | "short_name": "ClipCC",
4 | "id": "com.codingclip.clipcc3",
5 | "start_url": ".",
6 | "display": "standalone",
7 | "background_color": "#4D97FF",
8 | "description": "A powerful modified Scratch editor.",
9 | "icons": [
10 | {
11 | "src": "static/clipcc_logo48.png",
12 | "sizes": "48x48",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "static/clipcc_logo72.png",
17 | "sizes": "72x72",
18 | "type": "image/png"
19 | },
20 | {
21 | "src": "static/clipcc_logo96.png",
22 | "sizes": "96x96",
23 | "type": "image/png"
24 | },
25 | {
26 | "src": "static/clipcc_logo144.png",
27 | "sizes": "144x144",
28 | "type": "image/png"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/static/social.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/static/social.jpg
--------------------------------------------------------------------------------
/static/sw.js:
--------------------------------------------------------------------------------
1 | const appId = 'com.codingclip.clipcc3';
2 | const appVer = 'gen@appVer'; // Don't modify, generated by gen-meta.js
3 |
4 | const cacheName = appId + '@' + appVer;
5 | const cacheList = global.serviceWorkerOption.assets;
6 |
7 | self.addEventListener('install', event => {
8 | console.log('[Service Worker] Event: install');
9 | event.waitUntil(
10 | caches.open(cacheName).then(cache => {
11 | return cache.addAll(cacheList);
12 | })
13 | );
14 | });
15 |
16 | self.addEventListener('fetch', event => {
17 | console.log('[Service Worker] Event: fetch');
18 | event.respondWith(
19 | caches.match(event.request).then(response => {
20 | if (response) {
21 | console.log('[Service Workder] Debug: ', response);
22 | return response;
23 | }
24 | return fetch(event.request);
25 | })
26 | );
27 | });
28 |
29 | self.addEventListener('activate', event => {
30 | console.log('[Service Workder] Event: activate');
31 | const cacheWhitelist = [cacheName];
32 | event.waitUntil(
33 | caches.keys().then(cacheNames => Promise.all(
34 | cacheNames.map(cacheName => {
35 | if (cacheWhitelist.indexOf(cacheName) === -1) {
36 | return caches.delete(cacheName);
37 | }
38 | return '';
39 | })
40 | ))
41 | );
42 | });
43 |
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['scratch/react', 'scratch/es6', 'plugin:jest/recommended'],
3 | env: {
4 | browser: true,
5 | jest: true
6 | },
7 | plugins: ['jest'],
8 | rules: {
9 | 'react/prop-types': 0
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/test/__mocks__/audio-buffer-player.js:
--------------------------------------------------------------------------------
1 | export default class MockAudioBufferPlayer {
2 | constructor (samples, sampleRate) {
3 | this.samples = samples;
4 | this.sampleRate = sampleRate;
5 | this.buffer = {
6 | getChannelData: jest.fn(() => samples),
7 | sampleRate: sampleRate
8 | };
9 | this.play = jest.fn((trimStart, trimEnd, onUpdate) => {
10 | this.onUpdate = onUpdate;
11 | });
12 | this.stop = jest.fn();
13 | MockAudioBufferPlayer.instance = this;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/__mocks__/audio-effects.js:
--------------------------------------------------------------------------------
1 | export default class MockAudioEffects {
2 | static get effectTypes () { // @todo can this be imported from the real file?
3 | return {
4 | ROBOT: 'robot',
5 | REVERSE: 'reverse',
6 | LOUDER: 'higher',
7 | SOFTER: 'lower',
8 | FASTER: 'faster',
9 | SLOWER: 'slower',
10 | ECHO: 'echo'
11 | };
12 | }
13 | constructor (buffer, name) {
14 | this.buffer = buffer;
15 | this.name = name;
16 | this.process = jest.fn(done => {
17 | this._finishProcessing = renderedBuffer => {
18 | done(renderedBuffer, 0, 1);
19 | return new Promise(resolve => setTimeout(resolve));
20 | };
21 | });
22 | MockAudioEffects.instance = this;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/__mocks__/editor-msgs-mock.js:
--------------------------------------------------------------------------------
1 | export default {
2 | en: {}
3 | };
4 |
--------------------------------------------------------------------------------
/test/__mocks__/fileMock.js:
--------------------------------------------------------------------------------
1 | // __mocks__/fileMock.js
2 |
3 | module.exports = 'test-file-stub';
4 |
--------------------------------------------------------------------------------
/test/__mocks__/styleMock.js:
--------------------------------------------------------------------------------
1 | // __mocks__/styleMock.js
2 |
3 | module.exports = {};
4 |
--------------------------------------------------------------------------------
/test/fixtures/bmpfile.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/bmpfile.bmp
--------------------------------------------------------------------------------
/test/fixtures/corrupt-bmp.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/corrupt-bmp.sb3
--------------------------------------------------------------------------------
/test/fixtures/corrupt-bmp.sprite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/corrupt-bmp.sprite3
--------------------------------------------------------------------------------
/test/fixtures/corrupt-svg.sb2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/corrupt-svg.sb2
--------------------------------------------------------------------------------
/test/fixtures/corrupt-svg.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/corrupt-svg.sb3
--------------------------------------------------------------------------------
/test/fixtures/corrupt-svg.sprite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/corrupt-svg.sprite3
--------------------------------------------------------------------------------
/test/fixtures/corrupted-svg.sprite2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/corrupted-svg.sprite2
--------------------------------------------------------------------------------
/test/fixtures/gh-3582-png.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/gh-3582-png.png
--------------------------------------------------------------------------------
/test/fixtures/missing-bmp.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/missing-bmp.sb3
--------------------------------------------------------------------------------
/test/fixtures/missing-bmp.sprite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/missing-bmp.sprite3
--------------------------------------------------------------------------------
/test/fixtures/missing-sprite-svg.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/missing-sprite-svg.sb3
--------------------------------------------------------------------------------
/test/fixtures/missing-svg.sb2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/missing-svg.sb2
--------------------------------------------------------------------------------
/test/fixtures/missing-svg.sprite2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/missing-svg.sprite2
--------------------------------------------------------------------------------
/test/fixtures/missing-svg.sprite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/missing-svg.sprite3
--------------------------------------------------------------------------------
/test/fixtures/movie.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/movie.wav
--------------------------------------------------------------------------------
/test/fixtures/paddleball.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/paddleball.gif
--------------------------------------------------------------------------------
/test/fixtures/project1.sb3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/project1.sb3
--------------------------------------------------------------------------------
/test/fixtures/sneaker.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Clipteam/clipcc-gui/675ef136990992515588f3d8e37b00090c698968/test/fixtures/sneaker.wav
--------------------------------------------------------------------------------
/test/helpers/enzyme-setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | Enzyme.configure({adapter: new Adapter()});
5 |
--------------------------------------------------------------------------------
/test/helpers/intl-helpers.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Helpers for using enzyme and react-test-renderer with react-intl
3 | * Directly from https://github.com/yahoo/react-intl/wiki/Testing-with-React-Intl
4 | */
5 | import React from 'react';
6 | import renderer from 'react-test-renderer';
7 | import {IntlProvider, intlShape} from 'react-intl';
8 | import {mount, shallow} from 'enzyme';
9 |
10 | const intlProvider = new IntlProvider({locale: 'en'}, {});
11 | const {intl} = intlProvider.getChildContext();
12 |
13 | const nodeWithIntlProp = node => React.cloneElement(node, {intl});
14 |
15 | const shallowWithIntl = (node, {context} = {}) => shallow(
16 | nodeWithIntlProp(node),
17 | {
18 | context: Object.assign({}, context, {intl})
19 | }
20 | );
21 |
22 | const mountWithIntl = (node, {context, childContextTypes} = {}) => mount(
23 | nodeWithIntlProp(node),
24 | {
25 | context: Object.assign({}, context, {intl}),
26 | childContextTypes: Object.assign({}, {intl: intlShape}, childContextTypes)
27 | }
28 | );
29 |
30 | // react-test-renderer component for use with snapshot testing
31 | const componentWithIntl = (children, props = {locale: 'en'}) => renderer.create(
32 | {children}
33 | );
34 |
35 | export {
36 | componentWithIntl,
37 | shallowWithIntl,
38 | mountWithIntl
39 | };
40 |
--------------------------------------------------------------------------------
/test/integration/how-tos.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import SeleniumHelper from '../helpers/selenium-helper';
3 |
4 | const {
5 | clickText,
6 | clickXpath,
7 | findByXpath,
8 | getDriver,
9 | getLogs,
10 | loadUri
11 | } = new SeleniumHelper();
12 |
13 | const uri = path.resolve(__dirname, '../../build/index.html');
14 |
15 | let driver;
16 |
17 | describe('Working with the how-to library', () => {
18 | beforeAll(() => {
19 | driver = getDriver();
20 | });
21 |
22 | afterAll(async () => {
23 | await driver.quit();
24 | });
25 |
26 | test('Choosing a how-to', async () => {
27 | await loadUri(uri);
28 | await clickText('Costumes');
29 | await clickXpath('//*[@aria-label="Tutorials"]');
30 | await clickText('Getting Started'); // Modal should close
31 | // Make sure YouTube video on first card appears
32 | await findByXpath('//div[contains(@class, "step-video")]');
33 | const logs = await getLogs();
34 | await expect(logs).toEqual([]);
35 | });
36 |
37 | // @todo navigating cards, etc.
38 | });
39 |
--------------------------------------------------------------------------------
/test/integration/stage-size.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import SeleniumHelper from '../helpers/selenium-helper';
3 |
4 | const {
5 | clickText,
6 | clickXpath,
7 | rightClickText,
8 | getDriver,
9 | getLogs,
10 | loadUri,
11 | scope
12 | } = new SeleniumHelper();
13 |
14 | const uri = path.resolve(__dirname, '../../build/index.html');
15 |
16 | let driver;
17 |
18 | describe('Loading scratch gui', () => {
19 | beforeAll(() => {
20 | driver = getDriver();
21 | });
22 |
23 | afterAll(async () => {
24 | await driver.quit();
25 | });
26 |
27 | test('Switching small/large stage after highlighting and deleting sprite', async () => {
28 | await loadUri(uri);
29 |
30 | // Highlight the sprite
31 | await clickText('Sprite1', scope.spriteTile);
32 |
33 | // Delete it
34 | await rightClickText('Sprite1', scope.spriteTile);
35 | await clickText('delete', scope.spriteTile);
36 |
37 | // Go to small stage mode
38 | await clickXpath('//img[@alt="Switch to small stage"]');
39 |
40 | // Confirm app still working
41 | await clickXpath('//img[@alt="Switch to large stage"]');
42 |
43 | const logs = await getLogs();
44 | await expect(logs).toEqual([]);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/integration/tutorials-shortcut.test.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import SeleniumHelper from '../helpers/selenium-helper';
3 |
4 | const {
5 | clickText,
6 | findByXpath,
7 | getDriver,
8 | loadUri
9 | } = new SeleniumHelper();
10 |
11 | const uri = path.resolve(__dirname, '../../build/index.html?tutorial=all');
12 | const uriPrefix = path.resolve(__dirname, '../../build/index.html?tutorial=');
13 |
14 | let driver;
15 |
16 | describe('Working with shortcut to Tutorials library', () => {
17 | beforeAll(() => {
18 | driver = getDriver();
19 | });
20 |
21 | afterAll(async () => {
22 | await driver.quit();
23 | });
24 |
25 | test('opens with the Tutorial Library showing', async () => {
26 | await loadUri(uri);
27 | // make sure there is a tutorial visible that doesn't have a shortcut
28 | await clickText('Make It Spin');
29 | await findByXpath('//div[contains(@class, "step-video")]');
30 | });
31 |
32 | test('can open hidden tutorials', async () => {
33 | await loadUri(`${uriPrefix}whatsnew`);
34 | // should open the tutorial video immediately
35 | await findByXpath('//div[contains(@class, "step-video")]');
36 | });
37 | // @todo navigating cards, etc.
38 | });
39 |
--------------------------------------------------------------------------------
/test/unit/components/__snapshots__/button.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ButtonComponent matches snapshot 1`] = `
4 |
9 |
12 |
13 | `;
14 |
--------------------------------------------------------------------------------
/test/unit/components/__snapshots__/icon-button.test.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`IconButtonComponent matches snapshot 1`] = `
4 |
9 |

14 |
21 |
22 | `;
23 |
--------------------------------------------------------------------------------
/test/unit/components/button.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {shallow} from 'enzyme';
3 | import ButtonComponent from '../../../src/components/button/button';
4 | import renderer from 'react-test-renderer';
5 |
6 | describe('ButtonComponent', () => {
7 | test('matches snapshot', () => {
8 | const onClick = jest.fn();
9 | const component = renderer.create(
10 |
11 | );
12 | expect(component.toJSON()).toMatchSnapshot();
13 | });
14 |
15 | test('triggers callback when clicked', () => {
16 | const onClick = jest.fn();
17 | const componentShallowWrapper = shallow(
18 |
19 | );
20 | componentShallowWrapper.simulate('click');
21 | expect(onClick).toHaveBeenCalled();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/test/unit/components/icon-button.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {shallow} from 'enzyme';
3 | import IconButton from '../../../src/components/icon-button/icon-button';
4 | import renderer from 'react-test-renderer';
5 |
6 | describe('IconButtonComponent', () => {
7 | test('matches snapshot', () => {
8 | const onClick = jest.fn();
9 | const title = Text
;
10 | const imgSrc = 'imgSrc';
11 | const className = 'custom-class-name';
12 | const component = renderer.create(
13 |
19 | );
20 | expect(component.toJSON()).toMatchSnapshot();
21 | });
22 |
23 | test('triggers callback when clicked', () => {
24 | const onClick = jest.fn();
25 | const title = Text
;
26 | const imgSrc = 'imgSrc';
27 | const componentShallowWrapper = shallow(
28 |
33 | );
34 | componentShallowWrapper.simulate('click');
35 | expect(onClick).toHaveBeenCalled();
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/unit/reducers/workspace-metrics-reducer.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-env jest */
2 | import workspaceMetricsReducer, {updateMetrics} from '../../../src/reducers/workspace-metrics';
3 |
4 | test('initialState', () => {
5 | let defaultState;
6 | /* workspaceMetricsReducer(state, action) */
7 | expect(workspaceMetricsReducer(defaultState, {type: 'anything'})).toBeDefined();
8 | expect(workspaceMetricsReducer(defaultState, {type: 'anything'})).toEqual({targets: {}});
9 | });
10 |
11 | test('updateMetrics action creator', () => {
12 | let defaultState;
13 | const action = updateMetrics({
14 | targetID: 'abcde',
15 | scrollX: 225,
16 | scrollY: 315,
17 | scale: 1.25
18 | });
19 | const resultState = workspaceMetricsReducer(defaultState, action);
20 | expect(Object.keys(resultState.targets).length).toBe(1);
21 | expect(resultState.targets.abcde).toBeDefined();
22 | expect(resultState.targets.abcde.scrollX).toBe(225);
23 | expect(resultState.targets.abcde.scrollY).toBe(315);
24 | expect(resultState.targets.abcde.scale).toBe(1.25);
25 | });
26 |
--------------------------------------------------------------------------------
/test/unit/util/audio-context.test.js:
--------------------------------------------------------------------------------
1 | import 'web-audio-test-api';
2 | import SharedAudioContext from '../../../src/lib/audio/shared-audio-context';
3 |
4 | describe('Shared Audio Context', () => {
5 | const audioContext = new AudioContext();
6 |
7 | test('returns empty object without user gesture', () => {
8 | const sharedAudioContext = new SharedAudioContext();
9 | expect(sharedAudioContext).toMatchObject({});
10 | });
11 |
12 | test('returns AudioContext when mousedown is triggered', () => {
13 | const sharedAudioContext = new SharedAudioContext();
14 | const event = new Event('mousedown');
15 | document.dispatchEvent(event);
16 | expect(sharedAudioContext).toMatchObject(audioContext);
17 | });
18 |
19 | test('returns AudioContext when touchstart is triggered', () => {
20 | const sharedAudioContext = new SharedAudioContext();
21 | const event = new Event('touchstart');
22 | document.dispatchEvent(event);
23 | expect(sharedAudioContext).toMatchObject(audioContext);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/test/unit/util/code-payload.test.js:
--------------------------------------------------------------------------------
1 | jest.mock('../../../src/lib/backpack/block-to-image', () => () => Promise.resolve('block-image'));
2 | jest.mock('../../../src/lib/backpack/jpeg-thumbnail', () => () => Promise.resolve('thumbnail'));
3 |
4 | import codePayload from '../../../src/lib/backpack/code-payload';
5 | import {Base64} from 'js-base64';
6 |
7 | describe('codePayload', () => {
8 | test('base64 encodes the blocks as json', () => {
9 | const blocks = '☁︎❤️🐻';
10 | const payload = codePayload({
11 | blockObjects: blocks
12 | });
13 | return payload.then(p => {
14 | expect(
15 | JSON.parse(Base64.decode(p.body))
16 | ).toEqual(blocks);
17 | });
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/test/unit/util/default-project.test.js:
--------------------------------------------------------------------------------
1 | import defaultProjectGenerator from '../../../src/lib/default-project/index.js';
2 |
3 | describe('defaultProject', () => {
4 | // This test ensures that the assets referenced in the default project JSON
5 | // do not get out of sync with the raw assets that are included alongside.
6 | // see https://github.com/LLK/scratch-gui/issues/4844
7 | test('assets referenced by the project are included', () => {
8 | const translatorFn = () => '';
9 | const defaultProject = defaultProjectGenerator(translatorFn);
10 | const includedAssetIds = defaultProject.map(obj => obj.id);
11 | const projectData = JSON.parse(defaultProject[0].data);
12 | projectData.targets.forEach(target => {
13 | target.costumes.forEach(costume => {
14 | expect(includedAssetIds.includes(costume.assetId)).toBe(true);
15 | });
16 | target.sounds.forEach(sound => {
17 | expect(includedAssetIds.includes(sound.assetId)).toBe(true);
18 | });
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/test/unit/util/get-costume-url.test.js:
--------------------------------------------------------------------------------
1 | import {HAS_FONT_REGEXP} from '../../../src/lib/get-costume-url';
2 |
3 | describe('SVG Font Parsing', () => {
4 | test('Has font regexp works', () => {
5 | expect('font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
6 | expect('font-family="none" font-family="Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
7 | expect('font-family = "Sans Serif"'.match(HAS_FONT_REGEXP)).toBeTruthy();
8 |
9 | expect('font-family="none"'.match(HAS_FONT_REGEXP)).toBeFalsy();
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/test/unit/util/opcode-labels.test.js:
--------------------------------------------------------------------------------
1 | import opcodeLabels from '../../../src/lib/opcode-labels';
2 |
3 | describe('Opcode Labels', () => {
4 | test('day of week label', () => {
5 | const labelFun = opcodeLabels.getLabel('sensing_current').labelFn;
6 | expect(labelFun({CURRENTMENU: 'dayofweek'})).toBe('day of week');
7 | expect(labelFun({CURRENTMENU: 'DAYOFWEEK'})).toBe('day of week');
8 | });
9 |
10 | test('unspecified opcodes default to extension category and opcode as label', () => {
11 | const labelInfo = opcodeLabels.getLabel('music_getTempo');
12 | expect(labelInfo.label).toBe('music_getTempo');
13 | expect(labelInfo.category).toBe('extension');
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/test/unit/util/translate-video.test.js:
--------------------------------------------------------------------------------
1 | import {translateVideo} from '../../../src/lib/libraries/decks/translate-video.js';
2 |
3 | describe('translateVideo', () => {
4 | test('returns the id if it is not found', () => {
5 | expect(translateVideo('not-a-key', 'en')).toEqual('not-a-key');
6 | });
7 |
8 | test('returns the expected id for Japanese', () => {
9 | expect(translateVideo('intro-move-sayhello', 'ja')).toEqual('v2c2f3y2sc');
10 | });
11 |
12 | test('returns the expected id for English', () => {
13 | expect(translateVideo('intro-move-sayhello', 'en')).toEqual('rpjvs3v9gj');
14 | });
15 |
16 | test('returns the English id for non-existent locales', () => {
17 | expect(translateVideo('intro-move-sayhello', 'yum')).toEqual('rpjvs3v9gj');
18 | });
19 | });
20 |
--------------------------------------------------------------------------------