easy-scratch项目源码
13
.babelrc
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-transform-async-to-generator",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
["react-intl", {
|
||||
"messagesDir": "./translations/messages/"
|
||||
}]],
|
||||
"presets": [
|
||||
["@babel/preset-env", {"targets": {"browsers": ["last 3 versions", "Safari >= 8", "iOS >= 8"]}}],
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
3
.browserslistrc
Normal file
@ -0,0 +1,3 @@
|
||||
last 3 versions
|
||||
Safari >= 8
|
||||
iOS >= 8
|
11
.editorconfig
Normal file
@ -0,0 +1,11 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,html}]
|
||||
indent_style = space
|
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
node_modules/*
|
||||
build/*
|
||||
dist/*
|
3
.eslintrc.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: ['scratch', 'scratch/node']
|
||||
};
|
39
.gitattributes
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly specify line endings for as many files as possible.
|
||||
# People who (for example) rsync between Windows and Linux need this.
|
||||
|
||||
# File types which we know are binary
|
||||
|
||||
# Treat SVG files as binary so that their contents don't change due to line
|
||||
# endings. The contents of SVGs must not change from the way they're stored
|
||||
# on assets.scratch.mit.edu so that MD5 calculations don't change.
|
||||
*.svg binary
|
||||
|
||||
# Prefer LF for most file types
|
||||
*.frag text eol=lf
|
||||
*.htm text eol=lf
|
||||
*.html text eol=lf
|
||||
*.iml text eol=lf
|
||||
*.js text eol=lf
|
||||
*.js.map text eol=lf
|
||||
*.json text eol=lf
|
||||
*.jsx text eol=lf
|
||||
*.md text eol=lf
|
||||
*.vert text eol=lf
|
||||
*.xml text eol=lf
|
||||
*.yml text eol=lf
|
||||
|
||||
# Prefer LF for these files
|
||||
.editorconfig text eol=lf
|
||||
.eslintrc text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.gitmodules text eol=lf
|
||||
LICENSE text eol=lf
|
||||
Makefile text eol=lf
|
||||
README text eol=lf
|
||||
TRADEMARK text eol=lf
|
||||
|
||||
# Use CRLF for Windows-specific file types
|
20
.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
# Mac OS
|
||||
.DS_Store
|
||||
|
||||
# NPM
|
||||
/node_modules
|
||||
npm-*
|
||||
|
||||
# Testing
|
||||
/.nyc_output
|
||||
/coverage
|
||||
|
||||
# Build
|
||||
/build
|
||||
/dist
|
||||
/.opt-in
|
||||
|
||||
# generated translation files
|
||||
/translations
|
||||
/locale
|
||||
node_modules
|
21
.npmignore
Normal file
@ -0,0 +1,21 @@
|
||||
# Mac OS
|
||||
.DS_Store
|
||||
|
||||
# NPM
|
||||
/node_modules
|
||||
npm-*
|
||||
|
||||
# Double copies of all the static assets and tutorial gifs
|
||||
/src
|
||||
|
||||
# Testing
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/test
|
||||
|
||||
# Build
|
||||
/.opt-in
|
||||
/build
|
||||
|
||||
# generated translation files
|
||||
/translations
|
66
.travis.yml
Normal file
@ -0,0 +1,66 @@
|
||||
language: node_js
|
||||
sudo: required
|
||||
dist: xenial
|
||||
addons:
|
||||
chrome: stable
|
||||
node_js:
|
||||
- 8
|
||||
env:
|
||||
global:
|
||||
- CHROMEDRIVER_VERSION=LATEST
|
||||
- NODE_ENV=production
|
||||
- NODE_OPTIONS=--max-old-space-size=7250
|
||||
- NPM_TAG=latest
|
||||
- RELEASE_VERSION="0.1.0-prerelease.$(date +'%Y%m%d%H%M%S')"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
install:
|
||||
- npm --production=false install
|
||||
script:
|
||||
- npm test
|
||||
before_deploy:
|
||||
- >
|
||||
if [ -z "$BEFORE_DEPLOY_RAN" ]; then
|
||||
npm --no-git-tag-version version $RELEASE_VERSION
|
||||
if [ "$TRAVIS_BRANCH" == "master" ]; then export NPM_TAG=stable; fi
|
||||
if [[ "$TRAVIS_BRANCH" == hotfix/* ]]; then export NPM_TAG=hotfix; fi # double brackets are important for matching the wildcard
|
||||
git config --global user.email $(git log --pretty=format:"%ae" -n1)
|
||||
git config --global user.name $(git log --pretty=format:"%an" -n1)
|
||||
export BEFORE_DEPLOY_RAN=true
|
||||
fi
|
||||
deploy:
|
||||
- provider: npm
|
||||
on:
|
||||
branch:
|
||||
- master
|
||||
- develop
|
||||
- hotfix/*
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
skip_cleanup: true
|
||||
email: $NPM_EMAIL
|
||||
api_key: $NPM_TOKEN
|
||||
tag: $NPM_TAG
|
||||
- provider: script
|
||||
on:
|
||||
branch:
|
||||
- master
|
||||
- develop
|
||||
- smoke
|
||||
- hotfix/*
|
||||
condition: $TRAVIS_EVENT_TYPE != cron
|
||||
skip_cleanup: true
|
||||
script: if npm info scratch-gui | grep -q $RELEASE_VERSION; then git tag $RELEASE_VERSION && git push https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git $RELEASE_VERSION; fi
|
||||
- provider: script
|
||||
on:
|
||||
all_branches: true
|
||||
condition: $TRAVIS_EVENT_TYPE != cron && ! $TRAVIS_BRANCH =~ ^greenkeeper/
|
||||
tags: false # Don't push tags to gh-pages
|
||||
skip_cleanup: true
|
||||
script: npm run deploy -- -x -e $TRAVIS_BRANCH -r https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git
|
||||
- provider: script
|
||||
on:
|
||||
branch: develop
|
||||
condition: $TRAVIS_EVENT_TYPE == cron
|
||||
skip_cleanup: true
|
||||
script: npm run i18n:src && npm run i18n:push
|
1
INIT
Normal file
@ -0,0 +1 @@
|
||||
|
12
LICENSE
Normal file
@ -0,0 +1,12 @@
|
||||
Copyright (c) 2016, Massachusetts Institute of Technology
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
444
README-EN.md
Normal file
@ -0,0 +1,444 @@
|
||||
English | [简体中文](./README.md)
|
||||
|
||||
# Introduction
|
||||
|
||||
You don't need to focus on implementation of Scratch3.0 functionality. Only use simple JavaScript language to call Scratch functions.
|
||||
|
||||
|
||||
## That can be implemented
|
||||
|
||||
- load project
|
||||
- upload project
|
||||
- change style
|
||||
- mobile scratch player
|
||||
- virtual keyboard
|
||||
- change block size
|
||||
- hidden block category or specific block
|
||||
- custom assets library
|
||||
- backpack API
|
||||
- and more……
|
||||
|
||||
### The case
|
||||
|
||||
The Teaching system is embed this project.
|
||||
|
||||
website:https://teaching.vip
|
||||
|
||||
source:http://github.com/open-scratch/teaching-open
|
||||
|
||||
### Secondary development
|
||||
|
||||
- install dependencies
|
||||
npm install
|
||||
|
||||
- debug
|
||||
npm start
|
||||
|
||||
- build for productioin
|
||||
npm run build:prod
|
||||
|
||||
|
||||
# Quickly to use
|
||||
|
||||
## use DEMO
|
||||
|
||||
After build, see `index.html` and `player.html`.
|
||||
|
||||
## Inject to page
|
||||
|
||||
|
||||
import `lib.min.js` and `chunks/gui.js`, scratch will render into`<div id="scratch"></div>`。
|
||||
|
||||
Do not forget to edit `window.scratchConfig`
|
||||
|
||||
|
||||
# Document
|
||||
|
||||
## initial config
|
||||
|
||||
Notice: The window.scratchConfig object need created before import `lib.min.js`
|
||||
|
||||
This is a complete configuration example
|
||||
|
||||
```js
|
||||
window.scratchConfig = {
|
||||
session: {
|
||||
token: "", // user token
|
||||
username: "Username" // user name
|
||||
},
|
||||
backpack:{
|
||||
enable: true, // enable backpack
|
||||
api: "/api/teaching/scratch/backpack", //backpack API
|
||||
},
|
||||
cloudData:{
|
||||
enable: true, //enable cloud data
|
||||
id: "create", //default cloud data ID
|
||||
api: "127.0.0.1:1234/api/websocket/scratch/cloudData" //cloud data API
|
||||
},
|
||||
projectInfo: {//project info
|
||||
projectName: "",
|
||||
authorUsername: "admin",
|
||||
authorAvatar: './static/avatar.png',
|
||||
},
|
||||
logo: {
|
||||
show: true, //is visible
|
||||
url: "./static/logo.png", //logo url, support base64 images
|
||||
handleClickLogo: () => { //handle logo click event
|
||||
}
|
||||
},
|
||||
menuBar: {
|
||||
//menu bar style
|
||||
style: {
|
||||
background: 'hsla(215, 100%, 65%, 1)',
|
||||
},
|
||||
//new project button
|
||||
newButton:{
|
||||
show: true, //is visible
|
||||
handleBefore(){
|
||||
//handle before click button event, return 'true' to continue
|
||||
return true
|
||||
}
|
||||
},
|
||||
//load project from computer button
|
||||
loadFileButton:{
|
||||
show: true, //is visible
|
||||
handleBefore(){
|
||||
//handle before click button event, return 'true' to continue
|
||||
return true
|
||||
}
|
||||
},
|
||||
//save project to computer button
|
||||
saveFileButton:{
|
||||
show: true, //is visible
|
||||
handleBefore(){
|
||||
//handle before click button event, return 'true' to continue
|
||||
return true
|
||||
}
|
||||
},
|
||||
//turbo mode button
|
||||
turboModeButton:{
|
||||
show: true //is visible
|
||||
},
|
||||
//help button
|
||||
helpButton:{
|
||||
show: true, //is visible
|
||||
handleBefore:()=>{
|
||||
//handle before click button event, return 'true' to continue
|
||||
return true
|
||||
}
|
||||
},
|
||||
//my stuff button
|
||||
myStuff:{
|
||||
show: true, //is visible
|
||||
url: '/myProject' //my project href
|
||||
},
|
||||
//user avatar button
|
||||
userAvatar:{
|
||||
show: true, //is visible
|
||||
username: '未登录', //user name
|
||||
avatar: './static/avatar.png', //avatar image url
|
||||
handleClick(){
|
||||
//handle click avatar button
|
||||
}
|
||||
},
|
||||
//custom buttons
|
||||
customButtons: [
|
||||
{
|
||||
show: true, //is visible
|
||||
buttonName: 'Share', //button label name
|
||||
style:{ //button style
|
||||
color: 'white',
|
||||
background: 'hsla(30, 100%, 55%, 1)',
|
||||
},
|
||||
handleClick:()=>{ //button click event
|
||||
window.scratch.getProjectCover(cover => {
|
||||
//TODO got project cover
|
||||
})
|
||||
window.scratch.getProjectFile(file => {
|
||||
//TODO got project file
|
||||
})
|
||||
// got project name
|
||||
var projectName = window.scratch.getProjectName()
|
||||
console.log(projectName);
|
||||
}
|
||||
},
|
||||
//You can add more buttons
|
||||
]
|
||||
},
|
||||
blocks:{
|
||||
// block scale
|
||||
scale: 0.8,
|
||||
// hide block catagorys (catagory code see appendix)
|
||||
// window.vm.emitWorkspaceUpdate()
|
||||
hideCatagorys:[],
|
||||
//hide some blocks (block code see appendix)
|
||||
hideBlocks:[],
|
||||
},
|
||||
stageArea:{ //stage setting
|
||||
fullscreenButton:{ //fullscreen button
|
||||
show: true,
|
||||
handleBeforeSetStageUnFull(){ //拦截退出全屏,返回true继续执行
|
||||
return true
|
||||
},
|
||||
handleBeforeSetStageFull(){ //拦截全屏,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
startButton:{ //stat button
|
||||
show: true,
|
||||
handleBeforeStart(){
|
||||
//handle before click button event, return 'true' to continue
|
||||
return true
|
||||
}
|
||||
},
|
||||
stopButton:{ // stop button
|
||||
show: true,
|
||||
handleBeforeStop(){
|
||||
//handle before click button event, return 'true' to continue
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
//scratch vm initialized
|
||||
handleVmInitialized: (vm) => {
|
||||
window.vm = vm
|
||||
},
|
||||
//when create project or loaded project will call this function
|
||||
handleProjectLoaded:() => {
|
||||
},
|
||||
//when default project loaded will call this function
|
||||
handleDefaultProjectLoaded:() => {
|
||||
},
|
||||
//default project url
|
||||
defaultProjectURL: "./static/project.sb3",
|
||||
//assets library setting
|
||||
assets:{
|
||||
//asset host
|
||||
assetHost: './static',
|
||||
//asset index url
|
||||
defaultIndex:{
|
||||
sprites: "./static/json_index/sprites.json",
|
||||
costumes: "./static/json_index/costumes.json",
|
||||
backdrops: "./static/json_index/backdrops.json",
|
||||
sounds: "./static/json_index/sounds.json"
|
||||
},
|
||||
//handle before sprites library open
|
||||
handleBeforeSpriteLibraryOpen(){
|
||||
// append assets
|
||||
// window.scratch.pushSpriteLibrary(Arrays)
|
||||
return true;
|
||||
},
|
||||
//handle before costumes library open
|
||||
handleBeforeCostumesLibraryOpen(){
|
||||
return true;
|
||||
},
|
||||
//handle before backdrops library open
|
||||
handleBeforeBackdropsLibraryOpen(){
|
||||
return true;
|
||||
},
|
||||
//handle before sounds library open
|
||||
handleBeforeSoundLibraryOpen(){
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### ScratchVm Object
|
||||
|
||||
It's scratch-vm instance object, You can call scratch virtual machine API from outside.
|
||||
|
||||
scratch-vm APIs:
|
||||
|
||||
- vm.saveProjectSb3()
|
||||
- vm.loadProject(file)
|
||||
- vm.greenFlag()
|
||||
- vm.stopAll()
|
||||
- vm.emitWorkspaceUpdate()
|
||||
- ……
|
||||
|
||||
[Scratch-vm document](./doc/scratch-vm/index.html)
|
||||
|
||||
### load project
|
||||
|
||||
`window.scratch.loadProject(url, callback)`
|
||||
|
||||
You can also use vm.loadProject
|
||||
|
||||
example
|
||||
```
|
||||
window.scratch.loadProject(url, ()=>{
|
||||
console.log("load project finished")
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### get project file
|
||||
|
||||
`window.scratch.getProjectFile(callback)`
|
||||
|
||||
You can also use vm.saveProjectSb3
|
||||
|
||||
example
|
||||
```
|
||||
window.scratch.getProjectFile((file)=>{
|
||||
console.log(file)
|
||||
//upload this file to server
|
||||
})
|
||||
```
|
||||
|
||||
### get project cover
|
||||
|
||||
`window.scratch.getProjectCover(callback)`
|
||||
|
||||
example
|
||||
```
|
||||
window.scratch.getProjectCover((file)=>{
|
||||
console.log(file)
|
||||
//upload this file to server
|
||||
})
|
||||
```
|
||||
|
||||
### get project name
|
||||
|
||||
`window.scratch.getProjectName()`
|
||||
|
||||
### set project name
|
||||
|
||||
`window.scratch.setProjectName(projectName)`
|
||||
|
||||
### set player only mode
|
||||
|
||||
example
|
||||
```js
|
||||
window.scratch.setPlayerOnly(true)
|
||||
```
|
||||
|
||||
### fullscreen mode
|
||||
|
||||
example
|
||||
```js
|
||||
window.scratch.setFullScreen(true)
|
||||
```
|
||||
|
||||
### append assets library
|
||||
|
||||
You can dynamically add the asset index to the library. You need to await user to open the asset library and then call this method.
|
||||
|
||||
The parameter is an array of assets indexes. See the files in 'josn_index' folder.
|
||||
|
||||
|
||||
#### append sprite assets
|
||||
`window.scratch.pushSpritesLibrary(Arrays)`
|
||||
|
||||
#### append costumes assets
|
||||
`window.scratch.pushCostumesLibrary(Arrays)`
|
||||
|
||||
#### append backdrops assets
|
||||
`window.scratch.pushBackdropsLibrary(Arrays)`
|
||||
|
||||
#### append sounds assets
|
||||
`window.scratch.pushSoundLibrary(Arrays)`
|
||||
|
||||
example
|
||||
```js
|
||||
window.scratch.pushSoundLibrary(
|
||||
[{
|
||||
"name": "My Custom Sound",
|
||||
"tags": [
|
||||
"music",
|
||||
],
|
||||
"assetId": "5cb46ddd903fc2c9976ff881df9273c9",
|
||||
"dataFormat": "",
|
||||
"md5ext": "5cb46ddd903fc2c9976ff881df9273c9.wav",
|
||||
"sampleCount": 11840,
|
||||
"rate": 44100
|
||||
}]
|
||||
)
|
||||
```
|
||||
|
||||
#### set cloud data ID
|
||||
`window.scratch.setCloudId(id)`
|
||||
|
||||
# Appendix
|
||||
|
||||
## block catagory code
|
||||
|
||||
motion looks sound events control sensing operators variables myBlocks
|
||||
|
||||
## block code
|
||||
|
||||
### motion
|
||||
|
||||
motion_movesteps motion_turnright motion_turnleft motion_goto motion_gotoxy motion_glideto
|
||||
motion_glidesecstoxy motion_pointindirection motion_pointtowards motion_changexby motion_setx
|
||||
motion_changeyby motion_sety motion_ifonedgebounce motion_setrotationstyle motion_xposition motion_yposition motion_direction
|
||||
|
||||
### looks
|
||||
|
||||
looks_sayforsecs looks_say looks_thinkforsecs looks_think looks_switchbackdropto looks_switchbackdroptoandwait looks_nextbackdrop
|
||||
looks_switchcostumeto looks_nextcostume looks_switchbackdropto looks_nextbackdrop looks_changesizeby looks_setsizeto looks_changeeffectby
|
||||
looks_seteffectto looks_cleargraphiceffects looks_show looks_hide looks_gotofrontback looks_goforwardbackwardlayers looks_backdropnumbername
|
||||
looks_costumenumbername looks_backdropnumbername looks_size
|
||||
|
||||
### sound
|
||||
|
||||
sound_playuntildone sound_play sound_stopallsounds sound_changeeffectby sound_seteffectto sound_cleareffects
|
||||
sound_changevolumeby sound_setvolumeto sound_volume
|
||||
|
||||
### event
|
||||
|
||||
event_whenflagclicked event_whenkeypressed event_whenstageclicked event_whenthisspriteclicked event_whenbackdropswitchesto
|
||||
event_whengreaterthan event_whenbroadcastreceived event_broadcast event_broadcastandwait
|
||||
|
||||
### control
|
||||
|
||||
control_wait control_repeat control_forever control_if control_if_else control_wait_until control_repeat_until control_stop
|
||||
control_create_clone_of control_start_as_clone control_create_clone_of control_delete_this_clone
|
||||
|
||||
### sensing
|
||||
|
||||
sensing_touchingobject sensing_touchingcolor sensing_coloristouchingcolor sensing_distanceto sensing_askandwait sensing_answer
|
||||
sensing_keypressed sensing_mousedown sensing_mousex sensing_mousey sensing_setdragmode sensing_loudness sensing_timer sensing_resettimer
|
||||
sensing_of sensing_current sensing_dayssince2000 sensing_username
|
||||
|
||||
### operator
|
||||
|
||||
operator_add operator_subtract operator_multiply operator_divide operator_random operator_gt operator_lt operator_equals operator_and
|
||||
operator_or operator_not operator_join operator_letter_of operator_length operator_contains operator_mod operator_round operator_mathop
|
||||
|
||||
|
||||
## player virtual keyboard example
|
||||
|
||||
first import jQuery
|
||||
|
||||
```js
|
||||
function regKeyEvent(selector, key, keyCode) {
|
||||
console.log("reg key event:" + key)
|
||||
$(selector).on("touchstart", function(event) {
|
||||
vm.postIOData("keyboard", {
|
||||
keyCode: keyCode,
|
||||
key: key,
|
||||
isDown: true,
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
$(selector).on("touchend", function() {
|
||||
vm.postIOData("keyboard", {
|
||||
keyCode: keyCode,
|
||||
key: key,
|
||||
isDown: false,
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
regKeyEvent(".button_space", " ", 32)
|
||||
regKeyEvent(".button_down", "ArrowDown", 40)
|
||||
regKeyEvent(".button_up", "ArrowUp", 38)
|
||||
regKeyEvent(".button_left", "ArrowLeft", 37)
|
||||
regKeyEvent(".button_right", "ArrowRight", 39)
|
||||
|
||||
```
|
198
README-RAW.md
Normal file
@ -0,0 +1,198 @@
|
||||
# scratch-gui
|
||||
#### Scratch GUI is a set of React components that comprise the interface for creating and running Scratch 3.0 projects
|
||||
|
||||
[](https://travis-ci.com/LLK/scratch-gui)
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
## Installation
|
||||
This requires you to have Git and Node.js installed.
|
||||
|
||||
In your own node environment/application:
|
||||
```bash
|
||||
npm install https://github.com/LLK/scratch-gui.git
|
||||
```
|
||||
If you want to edit/play yourself:
|
||||
```bash
|
||||
git clone https://github.com/LLK/scratch-gui.git
|
||||
cd scratch-gui
|
||||
npm install
|
||||
```
|
||||
|
||||
## Getting started
|
||||
Running the project requires Node.js to be installed.
|
||||
|
||||
## Running
|
||||
Open a Command Prompt or Terminal in the repository and run:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
Then go to [http://localhost:8601/](http://localhost:8601/) - the playground outputs the default GUI component
|
||||
|
||||
## Developing alongside other Scratch repositories
|
||||
|
||||
### Getting another repo to point to this code
|
||||
|
||||
|
||||
If you wish to develop `scratch-gui` alongside other scratch repositories that depend on it, you may wish
|
||||
to have the other repositories use your local `scratch-gui` build instead of fetching the current production
|
||||
version of the scratch-gui that is found by default using `npm install`.
|
||||
|
||||
Here's how to link your local `scratch-gui` code to another project's `node_modules/scratch-gui`.
|
||||
|
||||
#### Configuration
|
||||
|
||||
1. In your local `scratch-gui` repository's top level:
|
||||
1. Make sure you have run `npm install`
|
||||
2. Build the `dist` directory by running `BUILD_MODE=dist npm run build`
|
||||
3. Establish a link to this repository by running `npm link`
|
||||
|
||||
2. From the top level of each repository (such as `scratch-www`) that depends on `scratch-gui`:
|
||||
1. Make sure you have run `npm install`
|
||||
2. Run `npm link scratch-gui`
|
||||
3. Build or run the repositoriy
|
||||
|
||||
#### Using `npm run watch`
|
||||
|
||||
Instead of `BUILD_MODE=dist npm run build`, you can use `BUILD_MODE=dist npm run watch` instead. This will watch for changes to your `scratch-gui` code, and automatically rebuild when there are changes. Sometimes this has been unreliable; if you are having problems, try going back to `BUILD_MODE=dist npm run build` until you resolve them.
|
||||
|
||||
#### Oh no! It didn't work!
|
||||
|
||||
If you can't get linking to work right, try:
|
||||
* Follow the recipe above step by step and don't change the order. It is especially important to run `npm install` _before_ `npm link`, because installing after the linking will reset the linking.
|
||||
* Make sure the repositories are siblings on your machine's file tree, like `.../.../MY_SCRATCH_DEV_DIRECTORY/scratch-gui/` and `.../.../MY_SCRATCH_DEV_DIRECTORY/scratch-www/`.
|
||||
* Consistent node.js version: If you have multiple Terminal tabs or windows open for the different Scratch repositories, make sure to use the same node version in all of them.
|
||||
* If nothing else works, unlink the repositories by running `npm unlink` in both, and start over.
|
||||
|
||||
## Testing
|
||||
### Documentation
|
||||
|
||||
You may want to review the documentation for [Jest](https://facebook.github.io/jest/docs/en/api.html) and [Enzyme](http://airbnb.io/enzyme/docs/api/) as you write your tests.
|
||||
|
||||
See [jest cli docs](https://facebook.github.io/jest/docs/en/cli.html#content) for more options.
|
||||
|
||||
### Running tests
|
||||
|
||||
*NOTE: If you're a windows user, please run these scripts in Windows `cmd.exe` instead of Git Bash/MINGW64.*
|
||||
|
||||
Before running any test, make sure you have run `npm install` from this (scratch-gui) repository's top level.
|
||||
|
||||
#### Main testing command
|
||||
|
||||
To run linter, unit tests, build, and integration tests, all at once:
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
#### Running unit tests
|
||||
|
||||
To run unit tests in isolation:
|
||||
```bash
|
||||
npm run test:unit
|
||||
```
|
||||
|
||||
To run unit tests in watch mode (watches for code changes and continuously runs tests):
|
||||
```bash
|
||||
npm run test:unit -- --watch
|
||||
```
|
||||
|
||||
You can run a single file of integration tests (in this example, the `button` tests):
|
||||
|
||||
```bash
|
||||
$(npm bin)/jest --runInBand test/unit/components/button.test.jsx
|
||||
```
|
||||
|
||||
#### Running integration tests
|
||||
|
||||
Integration tests use a headless browser to manipulate the actual html and javascript that the repo
|
||||
produces. You will not see this activity (though you can hear it when sounds are played!).
|
||||
|
||||
Note that integration tests require you to first create a build that can be loaded in a browser:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Then, you can run all integration tests:
|
||||
|
||||
```bash
|
||||
npm run test:integration
|
||||
```
|
||||
|
||||
Or, you can run a single file of integration tests (in this example, the `backpack` tests):
|
||||
|
||||
```bash
|
||||
$(npm bin)/jest --runInBand test/integration/backpack.test.js
|
||||
```
|
||||
|
||||
If you want to watch the browser as it runs the test, rather than running headless, use:
|
||||
|
||||
```bash
|
||||
USE_HEADLESS=no $(npm bin)/jest --runInBand test/integration/backpack.test.js
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ignoring optional dependencies
|
||||
|
||||
When running `npm install`, you can get warnings about optionsl dependencies:
|
||||
|
||||
```
|
||||
npm WARN optional Skipping failed optional dependency /chokidar/fsevents:
|
||||
npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.2.7
|
||||
```
|
||||
|
||||
You can suppress them by adding the `no-optional` switch:
|
||||
|
||||
```
|
||||
npm install --no-optional
|
||||
```
|
||||
|
||||
Further reading: [Stack Overflow](https://stackoverflow.com/questions/36725181/not-compatible-with-your-operating-system-or-architecture-fsevents1-0-11)
|
||||
|
||||
### Resolving dependencies
|
||||
|
||||
When installing for the first time, you can get warnings which need to be resolved:
|
||||
|
||||
```
|
||||
npm WARN eslint-config-scratch@5.0.0 requires a peer of babel-eslint@^8.0.1 but none was installed.
|
||||
npm WARN eslint-config-scratch@5.0.0 requires a peer of eslint@^4.0 but none was installed.
|
||||
npm WARN scratch-paint@0.2.0-prerelease.20190318170811 requires a peer of react-intl-redux@^0.7 but none was installed.
|
||||
npm WARN scratch-paint@0.2.0-prerelease.20190318170811 requires a peer of react-responsive@^4 but none was installed.
|
||||
```
|
||||
|
||||
You can check which versions are available:
|
||||
|
||||
```
|
||||
npm view react-intl-redux@0.* version
|
||||
```
|
||||
|
||||
You will neet do install the required version:
|
||||
|
||||
```
|
||||
npm install --no-optional --save-dev react-intl-redux@^0.7
|
||||
```
|
||||
|
||||
The dependency itself might have more missing dependencies, which will show up like this:
|
||||
|
||||
```
|
||||
user@machine:~/sources/scratch/scratch-gui (491-translatable-library-objects)$ npm install --no-optional --save-dev react-intl-redux@^0.7
|
||||
scratch-gui@0.1.0 /media/cuideigin/Linux/sources/scratch/scratch-gui
|
||||
├── react-intl-redux@0.7.0
|
||||
└── UNMET PEER DEPENDENCY react-responsive@5.0.0
|
||||
```
|
||||
|
||||
You will need to install those as well:
|
||||
|
||||
```
|
||||
npm install --no-optional --save-dev react-responsive@^5.0.0
|
||||
```
|
||||
|
||||
Further reading: [Stack Overflow](https://stackoverflow.com/questions/46602286/npm-requires-a-peer-of-but-all-peers-are-in-package-json-and-node-modules)
|
||||
|
||||
|
||||
## Publishing to GitHub Pages
|
||||
You can publish the GUI to github.io so that others on the Internet can view it.
|
||||
[Read the wiki for a step-by-step guide.](https://github.com/LLK/scratch-gui/wiki/Publishing-to-GitHub-Pages)
|
||||
|
||||
## Donate
|
||||
We provide [Scratch](https://scratch.mit.edu) free of charge, and want to keep it that way! Please consider making a [donation](https://secure.donationpay.org/scratchfoundation/) to support our continued engineering, design, community, and resource development efforts. Donations of any size are appreciated. Thank you!
|
532
README.md
Normal file
@ -0,0 +1,532 @@
|
||||
[English](./README-EN.md) | 简体中文
|
||||
|
||||
# 项目介绍
|
||||
|
||||
使用本项目,不需要关注Scratch3.0功能的具体实现,只需要简单的js基础即可调用Scratch的相关功能,助力项目快速开发。
|
||||
|
||||
## 可以实现的功能
|
||||
- 加载项目
|
||||
- 上传项目
|
||||
- 修改外观
|
||||
- 移动端播放器
|
||||
- 修改积木大小
|
||||
- 显示隐藏积木
|
||||
- 自定义素材库
|
||||
- 对接背包API
|
||||
- And more……
|
||||
|
||||
### 使用案例
|
||||
|
||||
开源Teaching在线教学系统便是使用的本项目,可以参考具体示例
|
||||
|
||||
官网:http://teaching.vip
|
||||
|
||||
开源地址:http://github.com/open-scratch/teaching-open
|
||||
|
||||
### 二次开发
|
||||
|
||||
- 安装依赖
|
||||
npm install
|
||||
|
||||
- 调试
|
||||
npm start
|
||||
|
||||
- 编译正式版
|
||||
npm run build:prod
|
||||
|
||||
建议在Linux环境下编译开发,若windows下编译遇到问题可参见:
|
||||
|
||||
https://www.213.name/archives/1739
|
||||
|
||||
|
||||
### 参与本项目
|
||||
欢迎提交Issues和PR!
|
||||
|
||||
|
||||
# 快速使用
|
||||
|
||||
## 直接使用本DEMO
|
||||
|
||||
编译后直接修改index.html和player.html中的配置即可使用
|
||||
|
||||
## 引入到自己的页面
|
||||
|
||||
不使用DEMO,引入到自己的页面
|
||||
|
||||
页面引入`lib.min.js`和`chunks/gui.js`后,scratch将自动渲染至`<div id="scratch"></div>`内。
|
||||
|
||||
别忘了配置 `window.scratchConfig`
|
||||
|
||||
|
||||
# 项目文档
|
||||
|
||||
## 初始化配置
|
||||
|
||||
初始化配置均通过`window.scratchConfig`对象完成
|
||||
|
||||
需要注意的是,需要在引入lib.min.js之前就加入该代码,即Scratch主程序加载前就需要定义该配置
|
||||
|
||||
以下是完整示例:
|
||||
|
||||
```js
|
||||
window.scratchConfig = {
|
||||
session: {
|
||||
token: "", // 用户Token
|
||||
username: "Username" //用户名
|
||||
},
|
||||
backpack:{
|
||||
enable: true, // 是否启用背包
|
||||
api: "/api/teaching/scratch/backpack", //背包API接口
|
||||
},
|
||||
cloudData:{
|
||||
enable: true, //是否开启云变量功能
|
||||
id: "create", //默认云变量ID,可使用window.scratch.setCloudId更换ID
|
||||
api: "127.0.0.1:1234/api/websocket/scratch/cloudData" //云变量API地址
|
||||
},
|
||||
projectInfo: {//作品信息
|
||||
projectName: "",
|
||||
authorUsername: "admin",
|
||||
authorAvatar: './static/avatar.png',
|
||||
},
|
||||
logo: {
|
||||
show: true, //是否显示
|
||||
url: "./static/logo.png", //logo地址,支持base64图片
|
||||
handleClickLogo: () => { //处理LOGO点击事件
|
||||
}
|
||||
},
|
||||
menuBar: {
|
||||
//菜单栏样式
|
||||
style: {
|
||||
background: 'hsla(215, 100%, 65%, 1)',
|
||||
},
|
||||
//切换语言按钮
|
||||
languageButton:{
|
||||
show: true, //是否显示
|
||||
defaultLanguage: 'zh-cn' //默认语言 en zh-cn zh-tw
|
||||
},
|
||||
//新建按钮
|
||||
newButton:{
|
||||
show: true, //是否显示
|
||||
handleBefore(){
|
||||
//拦截点击事件,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
//从计算机加载按钮
|
||||
loadFileButton:{
|
||||
show: true, //是否显示
|
||||
handleBefore(){
|
||||
//拦截点击事件,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
//保存到计算机按钮
|
||||
saveFileButton:{
|
||||
show: true, //是否显示
|
||||
handleBefore(){
|
||||
//拦截点击事件,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
//加速模式按钮
|
||||
turboModeButton:{
|
||||
show: true //是否显示
|
||||
},
|
||||
//教程按钮
|
||||
helpButton:{
|
||||
show: true, //是否显示
|
||||
handleBefore:()=>{
|
||||
//拦截点击事件,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
//我的物品按钮
|
||||
myStuff:{
|
||||
show: true, //是否显示
|
||||
url: '/myProject' //跳转的连接
|
||||
},
|
||||
//用户头像按钮
|
||||
userAvatar:{
|
||||
show: true, //是否显示
|
||||
username: '未登录', //用户名
|
||||
avatar: './static/avatar.png', //用户头像
|
||||
handleClick(){
|
||||
//点击头像,可以弹出登录框等操作
|
||||
}
|
||||
},
|
||||
//自定义按钮
|
||||
customButtons: [
|
||||
{
|
||||
show: true, //是否显示
|
||||
buttonName: '分享', //按钮名
|
||||
style:{ //按钮样式
|
||||
color: 'white',
|
||||
background: 'hsla(30, 100%, 55%, 1)',
|
||||
},
|
||||
handleClick:()=>{ //按钮事件
|
||||
console.log("自定义按钮1");
|
||||
console.log('分享按钮')
|
||||
window.scratch.getProjectCover(cover => {
|
||||
//TODO 获取到作品截图
|
||||
})
|
||||
window.scratch.getProjectFile(file => {
|
||||
//TODO 获取到项目文件
|
||||
})
|
||||
// 获取到项目名
|
||||
var projectName = window.scratch.getProjectName()
|
||||
console.log(projectName);
|
||||
}
|
||||
},
|
||||
//可继续新增按钮
|
||||
]
|
||||
},
|
||||
blocks:{
|
||||
// 积木缩放比例
|
||||
scale: 0.8,
|
||||
// 隐藏分类 费雷见README附录:
|
||||
// 如需动态隐藏显示分类或积木,修改此配置后需手动执行 window.vm.emitWorkspaceUpdate()
|
||||
hideCatagorys:[],
|
||||
//隐藏积木 积木代码见README附录:
|
||||
hideBlocks:[],
|
||||
},
|
||||
stageArea:{ //舞台设置
|
||||
fullscreenButton:{ //全屏按钮
|
||||
show: true,
|
||||
handleBeforeSetStageUnFull(){ //拦截退出全屏,返回true继续执行
|
||||
return true
|
||||
},
|
||||
handleBeforeSetStageFull(){ //拦截全屏,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
startButton:{ //开始按钮
|
||||
show: true,
|
||||
handleBeforeStart(){ //拦截开始按钮,返回true继续执行
|
||||
return true
|
||||
}
|
||||
},
|
||||
stopButton:{ // 停止按钮
|
||||
show: true,
|
||||
handleBeforeStop(){ //拦截停止按钮,,返回true继续执行
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
//scratch vm初始化完毕
|
||||
handleVmInitialized: (vm) => {
|
||||
window.vm = vm
|
||||
},
|
||||
//作品加载完毕
|
||||
handleProjectLoaded:() => {
|
||||
},
|
||||
//默认作品加载完毕
|
||||
handleDefaultProjectLoaded:() => {
|
||||
},
|
||||
//默认项目地址,不需要修请删除本配置项
|
||||
defaultProjectURL: "./static/project.sb3",
|
||||
//素材库配置
|
||||
assets:{
|
||||
//附:Scratch素材库采集和处理工具 https://github.com/open-scratch/scratch-asset-utils
|
||||
//素材库地址,默认为/static下的素材库
|
||||
assetHost: './static',
|
||||
//素材库索引地址
|
||||
defaultIndex:{
|
||||
sprites: "./static/json_index/sprites.json",
|
||||
costumes: "./static/json_index/costumes.json",
|
||||
backdrops: "./static/json_index/backdrops.json",
|
||||
sounds: "./static/json_index/sounds.json"
|
||||
},
|
||||
//拦截角色库打开
|
||||
handleBeforeSpriteLibraryOpen(){
|
||||
console.log("角色库打开")
|
||||
//追加素材库
|
||||
// window.scratch.pushSpriteLibrary(Arrays)
|
||||
return true;
|
||||
},
|
||||
//拦截造型库打开
|
||||
handleBeforeCostumesLibraryOpen(){
|
||||
return true;
|
||||
},
|
||||
//拦截背景库打开
|
||||
handleBeforeBackdropsLibraryOpen(){
|
||||
return true;
|
||||
},
|
||||
//拦截声音库打开
|
||||
handleBeforeSoundLibraryOpen(){
|
||||
return true;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### ScratchVm对象
|
||||
|
||||
scratch-vm实例化的对象,可以从外部直接操作部分scratch-vm虚拟机功能,
|
||||
|
||||
该对象常用API列表:
|
||||
|
||||
- vm.saveProjectSb3() //获取SB3格式项目
|
||||
- vm.loadProject(file) //加载SB3项目
|
||||
- vm.greenFlag() //点击小绿旗
|
||||
- vm.stopAll() //停止运行项目
|
||||
- vm.emitWorkspaceUpdate() //刷新工作区
|
||||
- ……
|
||||
|
||||
[Scratch-vm介绍](./doc/scratch-vm.md)
|
||||
|
||||
[Scratch-vm官方文档](./doc/scratch-vm/index.html)
|
||||
|
||||
### 加载项目
|
||||
|
||||
`window.scratch.loadProject(url, callback)`
|
||||
|
||||
也可以使用vm对象的loadProject方法载入scratch项目
|
||||
|
||||
示例
|
||||
```
|
||||
window.scratch.loadProject(url, ()=>{
|
||||
//加载文件完成后的操作
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 获取项目文件
|
||||
|
||||
`window.scratch.getProjectFile(callback)`
|
||||
|
||||
也可以使用vm对象的saveProjectSb3方法
|
||||
|
||||
示例
|
||||
```
|
||||
window.scratch.getProjectFile((file)=>{
|
||||
console.log(file)
|
||||
//上传file文件
|
||||
})
|
||||
```
|
||||
|
||||
### 获取项目截图
|
||||
|
||||
`window.scratch.getProjectCover(callback)`
|
||||
|
||||
示例
|
||||
```
|
||||
window.scratch.getProjectCover((file)=>{
|
||||
console.log(file)
|
||||
//上传截图文件
|
||||
})
|
||||
```
|
||||
|
||||
### 获取项目名称
|
||||
|
||||
`window.scratch.getProjectName()`
|
||||
|
||||
### 设置项目名称
|
||||
|
||||
`window.scratch.setProjectName(projectName)`
|
||||
|
||||
### 设置为仅播放模式
|
||||
|
||||
示例
|
||||
```js
|
||||
window.scratch.setPlayerOnly(true)
|
||||
```
|
||||
|
||||
### 设置为全屏
|
||||
|
||||
示例
|
||||
```js
|
||||
window.scratch.setFullScreen(true)
|
||||
```
|
||||
|
||||
### 追加素材库
|
||||
|
||||
可以动态新增素材库索引,需要等待用户打开素材库后再调用本方法,否则无法追加成功。
|
||||
增加索引后还需要assetHost中增加对应的素材文件。
|
||||
|
||||
参数为素材索引的数组,具体格式参见json_index下文件的内容。
|
||||
|
||||
#### 追加角色库
|
||||
`window.scratch.pushSpritesLibrary(Arrays)`
|
||||
|
||||
#### 追加造型库
|
||||
`window.scratch.pushCostumesLibrary(Arrays)`
|
||||
|
||||
#### 追加背景库
|
||||
`window.scratch.pushBackdropsLibrary(Arrays)`
|
||||
|
||||
#### 追加声音库
|
||||
`window.scratch.pushSoundsLibrary(Arrays)`
|
||||
|
||||
示例
|
||||
```js
|
||||
window.scratch.pushSoundsLibrary(
|
||||
[{
|
||||
"name": "自定义声音",
|
||||
"tags": [
|
||||
"music",
|
||||
],
|
||||
"assetId": "5cb46ddd903fc2c9976ff881df9273c9",
|
||||
"dataFormat": "",
|
||||
"md5ext": "5cb46ddd903fc2c9976ff881df9273c9.wav",
|
||||
"sampleCount": 11840,
|
||||
"rate": 44100
|
||||
}]
|
||||
)
|
||||
```
|
||||
|
||||
#### 设置云变量ID
|
||||
`window.scratch.setCloudId(id)`
|
||||
|
||||
# 附录
|
||||
|
||||
## Scratch项目结构
|
||||
|
||||
### 各个模块
|
||||
|
||||
#### scratch-vm 虚拟机
|
||||
|
||||
解析加载序列化项目文件、扩展功能实现、根据相应事件渲染舞台
|
||||
|
||||
#### scratch-audio 声音引擎
|
||||
|
||||
解析、播放声音
|
||||
|
||||
#### scratch-blocks 代码积木块
|
||||
|
||||
创建积木操作块区域和每个积木对应的代码
|
||||
|
||||
#### scratch-l10n 国际化
|
||||
|
||||
多语言支持
|
||||
|
||||
#### scratch-paint 画图引擎
|
||||
|
||||
图片编辑器
|
||||
|
||||
#### scratch-render:
|
||||
|
||||
舞台渲染
|
||||
|
||||
#### scratch-storage 存储引擎
|
||||
|
||||
项目和对应素材的存储加载
|
||||
|
||||
#### scratch-svg-renderer
|
||||
|
||||
svg文件处理
|
||||
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
├── build # 编译后的文件夹
|
||||
│ ├── static # 静态资源
|
||||
│ ├── chunks # scratch核心加载器
|
||||
│ ├── index.html # scratch编辑器
|
||||
│ ├── player.html # scratch播放器
|
||||
│ ├── lib.min.js # scratch核心
|
||||
├── src
|
||||
│ ├── components # UI组件
|
||||
│ ├── containers # 容器组件,承载容器组件业务逻辑
|
||||
│ ├── css # 全局通用css
|
||||
│ ├── examples # 集成测试用例
|
||||
│ ├── extensions # 拓展案例
|
||||
│ ├── lib # 插件及高阶组件
|
||||
│ ├── audio # 声音插件
|
||||
│ ├── backpack # 背包插件
|
||||
│ ├── default-project # 默认项目
|
||||
│ ├── libraries # 素材库相关
|
||||
│ ├── video # 视频模块
|
||||
│ ├── playground # 编译后页面的模版
|
||||
│ ├── reducers # 全局状态控制
|
||||
├── test # 测试用例
|
||||
├── translations # 翻译库
|
||||
├── README.md
|
||||
├── README-RAW.md #
|
||||
└── package.json
|
||||
└── webpack.config.js
|
||||
└── webpack.prod.js
|
||||
```
|
||||
|
||||
## 积木分类代码
|
||||
|
||||
motion looks sound events control sensing operators variables myBlocks
|
||||
|
||||
## 积木代码
|
||||
|
||||
### 运动分类
|
||||
|
||||
motion_movesteps motion_turnright motion_turnleft motion_goto motion_gotoxy motion_glideto
|
||||
motion_glidesecstoxy motion_pointindirection motion_pointtowards motion_changexby motion_setx
|
||||
motion_changeyby motion_sety motion_ifonedgebounce motion_setrotationstyle motion_xposition motion_yposition motion_direction
|
||||
|
||||
### 外观分类
|
||||
|
||||
looks_sayforsecs looks_say looks_thinkforsecs looks_think looks_switchbackdropto looks_switchbackdroptoandwait looks_nextbackdrop
|
||||
looks_switchcostumeto looks_nextcostume looks_switchbackdropto looks_nextbackdrop looks_changesizeby looks_setsizeto looks_changeeffectby
|
||||
looks_seteffectto looks_cleargraphiceffects looks_show looks_hide looks_gotofrontback looks_goforwardbackwardlayers looks_backdropnumbername
|
||||
looks_costumenumbername looks_backdropnumbername looks_size
|
||||
|
||||
### 声音分类
|
||||
|
||||
sound_playuntildone sound_play sound_stopallsounds sound_changeeffectby sound_seteffectto sound_cleareffects
|
||||
sound_changevolumeby sound_setvolumeto sound_volume
|
||||
|
||||
### 事件分类
|
||||
|
||||
event_whenflagclicked event_whenkeypressed event_whenstageclicked event_whenthisspriteclicked event_whenbackdropswitchesto
|
||||
event_whengreaterthan event_whenbroadcastreceived event_broadcast event_broadcastandwait
|
||||
|
||||
### 控制分类
|
||||
|
||||
control_wait control_repeat control_forever control_if control_if_else control_wait_until control_repeat_until control_stop
|
||||
control_create_clone_of control_start_as_clone control_create_clone_of control_delete_this_clone
|
||||
|
||||
### 侦测分类
|
||||
|
||||
sensing_touchingobject sensing_touchingcolor sensing_coloristouchingcolor sensing_distanceto sensing_askandwait sensing_answer
|
||||
sensing_keypressed sensing_mousedown sensing_mousex sensing_mousey sensing_setdragmode sensing_loudness sensing_timer sensing_resettimer
|
||||
sensing_of sensing_current sensing_dayssince2000 sensing_username
|
||||
|
||||
### 运算分类
|
||||
|
||||
operator_add operator_subtract operator_multiply operator_divide operator_random operator_gt operator_lt operator_equals operator_and
|
||||
operator_or operator_not operator_join operator_letter_of operator_length operator_contains operator_mod operator_round operator_mathop
|
||||
|
||||
|
||||
## 手机端虚拟按键实现案例
|
||||
|
||||
绑定某个dom为移动端的虚拟键盘,需要先引入jQuery
|
||||
|
||||
```js
|
||||
function regKeyEvent(selector, key, keyCode) {
|
||||
console.log("注册按键事件:" + key)
|
||||
$(selector).on("touchstart", function(event) {
|
||||
vm.postIOData("keyboard", {
|
||||
keyCode: keyCode,
|
||||
key: key,
|
||||
isDown: true,
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
$(selector).on("touchend", function() {
|
||||
vm.postIOData("keyboard", {
|
||||
keyCode: keyCode,
|
||||
key: key,
|
||||
isDown: false,
|
||||
});
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
// 配置上下左右空格键
|
||||
regKeyEvent(".button_space", " ", 32)
|
||||
regKeyEvent(".button_down", "ArrowDown", 40)
|
||||
regKeyEvent(".button_up", "ArrowUp", 38)
|
||||
regKeyEvent(".button_left", "ArrowLeft", 37)
|
||||
regKeyEvent(".button_right", "ArrowRight", 39)
|
||||
|
||||
```
|
1
TRADEMARK
Normal file
@ -0,0 +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.
|
581
doc/scratch-blocks-tree.json
Normal file
@ -0,0 +1,581 @@
|
||||
[
|
||||
{
|
||||
"title": "动作",
|
||||
"value": "motion",
|
||||
"key": "motion",
|
||||
"children": [
|
||||
{
|
||||
"title": "移动 %1 步",
|
||||
"value": "motion_movesteps",
|
||||
"key": "motion_movesteps"
|
||||
},
|
||||
{
|
||||
"title": "右转 %1 度",
|
||||
"value": "motion_turnright",
|
||||
"key": "motion_turnright"
|
||||
},
|
||||
{
|
||||
"title": "左转 %1 度",
|
||||
"value": "motion_turnleft",
|
||||
"key": "motion_turnleft"
|
||||
},
|
||||
{
|
||||
"title": "移到 %1",
|
||||
"value": "motion_goto",
|
||||
"key": "motion_goto"
|
||||
},
|
||||
{
|
||||
"title": "移到 x: %1 y: %2",
|
||||
"value": "motion_gotoxy",
|
||||
"key": "motion_gotoxy"
|
||||
},
|
||||
{
|
||||
"title": "在 %1 秒内滑行到 %2",
|
||||
"value": "motion_glideto",
|
||||
"key": "motion_glideto"
|
||||
},
|
||||
{
|
||||
"title": "在 %1 秒内滑行到 x: %2 y: %3",
|
||||
"value": "motion_glidesecstoxy",
|
||||
"key": "motion_glidesecstoxy"
|
||||
},
|
||||
{
|
||||
"title": "面向 %1 方向",
|
||||
"value": "motion_pointindirection",
|
||||
"key": "motion_pointindirection"
|
||||
},
|
||||
{
|
||||
"title": "面向 %1",
|
||||
"value": "motion_pointtowards",
|
||||
"key": "motion_pointtowards"
|
||||
},
|
||||
{
|
||||
"title": "将x坐标增加 %1",
|
||||
"value": "motion_changexby",
|
||||
"key": "motion_changexby"
|
||||
},
|
||||
{
|
||||
"title": "将x坐标设为 %1",
|
||||
"value": "motion_setx",
|
||||
"key": "motion_setx"
|
||||
},
|
||||
{
|
||||
"title": "将y坐标增加 %1",
|
||||
"value": "motion_changeyby",
|
||||
"key": "motion_changeyby"
|
||||
},
|
||||
{
|
||||
"title": "将y坐标设为 %1",
|
||||
"value": "motion_sety",
|
||||
"key": "motion_sety"
|
||||
},
|
||||
{
|
||||
"title": "碰到边缘就反弹",
|
||||
"value": "motion_ifonedgebounce",
|
||||
"key": "motion_ifonedgebounce"
|
||||
},
|
||||
{
|
||||
"title": "将旋转方式设为 %1",
|
||||
"value": "motion_setrotationstyle",
|
||||
"key": "motion_setrotationstyle"
|
||||
},
|
||||
{
|
||||
"title": "x 坐标",
|
||||
"value": "motion_xposition",
|
||||
"key": "motion_xposition"
|
||||
},
|
||||
{
|
||||
"title": "y 坐标",
|
||||
"value": "motion_yposition",
|
||||
"key": "motion_yposition"
|
||||
},
|
||||
{
|
||||
"title": "方向",
|
||||
"value": "motion_direction",
|
||||
"key": "motion_direction"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "外观",
|
||||
"value": "looks",
|
||||
"key": "looks",
|
||||
"children": [
|
||||
{
|
||||
"title": "说 %1 %2 秒",
|
||||
"value": "looks_sayforsecs",
|
||||
"key": "looks_sayforsecs"
|
||||
},
|
||||
{
|
||||
"title": "说 %1",
|
||||
"value": "looks_say",
|
||||
"key": "looks_say"
|
||||
},
|
||||
{
|
||||
"title": "思考 %1 %2 秒",
|
||||
"value": "looks_thinkforsecs",
|
||||
"key": "looks_thinkforsecs"
|
||||
},
|
||||
{
|
||||
"title": "思考 %1",
|
||||
"value": "looks_think",
|
||||
"key": "looks_think"
|
||||
},
|
||||
{
|
||||
"title": "换成 %1 背景",
|
||||
"value": "looks_switchbackdropto",
|
||||
"key": "looks_switchbackdropto"
|
||||
},
|
||||
{
|
||||
"title": "换成 %1 背景并等待",
|
||||
"value": "looks_switchbackdroptoandwait",
|
||||
"key": "looks_switchbackdroptoandwait"
|
||||
},
|
||||
{
|
||||
"title": "下一个背景",
|
||||
"value": "looks_nextbackdrop",
|
||||
"key": "looks_nextbackdrop"
|
||||
},
|
||||
{
|
||||
"title": "换成 %1 造型",
|
||||
"value": "looks_switchcostumeto",
|
||||
"key": "looks_switchcostumeto"
|
||||
},
|
||||
{
|
||||
"title": "下一个造型",
|
||||
"value": "looks_nextcostume",
|
||||
"key": "looks_nextcostume"
|
||||
},
|
||||
{
|
||||
"title": "将大小增加 %1",
|
||||
"value": "looks_changesizeby",
|
||||
"key": "looks_changesizeby"
|
||||
},
|
||||
{
|
||||
"title": "将大小设为 %1",
|
||||
"value": "looks_setsizeto",
|
||||
"key": "looks_setsizeto"
|
||||
},
|
||||
{
|
||||
"title": "将 %1 特效增加 %2",
|
||||
"value": "looks_changeeffectby",
|
||||
"key": "looks_changeeffectby"
|
||||
},
|
||||
{
|
||||
"title": "将 %1 特效设定为 %2",
|
||||
"value": "looks_seteffectto",
|
||||
"key": "looks_seteffectto"
|
||||
},
|
||||
{
|
||||
"title": "清除图形特效",
|
||||
"value": "looks_cleargraphiceffects",
|
||||
"key": "looks_cleargraphiceffects"
|
||||
},
|
||||
{
|
||||
"title": "显示",
|
||||
"value": "looks_show",
|
||||
"key": "looks_show"
|
||||
},
|
||||
{
|
||||
"title": "隐藏",
|
||||
"value": "looks_hide",
|
||||
"key": "looks_hide"
|
||||
},
|
||||
{
|
||||
"title": "移到最 前面|后面",
|
||||
"value": "looks_gotofrontback",
|
||||
"key": "looks_gotofrontback"
|
||||
},
|
||||
{
|
||||
"title": "前移|后移 %1 层",
|
||||
"value": "looks_goforwardbackwardlayers",
|
||||
"key": "looks_goforwardbackwardlayers"
|
||||
},
|
||||
{
|
||||
"title": "造型 编号|名称",
|
||||
"value": "looks_costumenumbername",
|
||||
"key": "looks_costumenumbername"
|
||||
},
|
||||
{
|
||||
"title": "背景 编号|名称",
|
||||
"value": "looks_backdropnumbername",
|
||||
"key": "looks_backdropnumbername"
|
||||
},
|
||||
{
|
||||
"title": "大小",
|
||||
"value": "looks_size",
|
||||
"key": "looks_size"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "声音",
|
||||
"value": "sound",
|
||||
"key": "sound",
|
||||
"children": [
|
||||
{
|
||||
"title": "播放声音 %1 等待播完",
|
||||
"value": "sound_playuntildone",
|
||||
"key": "sound_playuntildone"
|
||||
},
|
||||
{
|
||||
"title": "播放声音 %1",
|
||||
"value": "sound_play",
|
||||
"key": "sound_play"
|
||||
},
|
||||
{
|
||||
"title": "停止所有声音",
|
||||
"value": "sound_stopallsounds",
|
||||
"key": "sound_stopallsounds"
|
||||
},
|
||||
{
|
||||
"title": "将 %1 音效增加 %2",
|
||||
"value": "sound_changeeffectby",
|
||||
"key": "sound_changeeffectby"
|
||||
},
|
||||
{
|
||||
"title": "将 %1 音效设为 %2",
|
||||
"value": "sound_seteffectto",
|
||||
"key": "sound_seteffectto"
|
||||
},
|
||||
{
|
||||
"title": "清除音效",
|
||||
"value": "sound_cleareffects",
|
||||
"key": "sound_cleareffects"
|
||||
},
|
||||
{
|
||||
"title": "将音量增加 %1",
|
||||
"value": "sound_changevolumeby",
|
||||
"key": "sound_changevolumeby"
|
||||
},
|
||||
{
|
||||
"title": "将音量设为 %1",
|
||||
"value": "sound_setvolumeto",
|
||||
"key": "sound_setvolumeto"
|
||||
},
|
||||
{
|
||||
"title": "音量",
|
||||
"value": "sound_volume",
|
||||
"key": "sound_volume"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "事件",
|
||||
"value": "events",
|
||||
"key": "events",
|
||||
"children": [
|
||||
{
|
||||
"title": "当小绿旗被点击",
|
||||
"value": "event_whenflagclicked",
|
||||
"key": "event_whenflagclicked"
|
||||
},
|
||||
{
|
||||
"title": "当按下 %1 键",
|
||||
"value": "event_whenkeypressed",
|
||||
"key": "event_whenkeypressed"
|
||||
},
|
||||
{
|
||||
"title": "当舞台被点击",
|
||||
"value": "event_whenstageclicked",
|
||||
"key": "event_whenstageclicked"
|
||||
},
|
||||
{
|
||||
"title": "当角色被点击",
|
||||
"value": "event_whenthisspriteclicked",
|
||||
"key": "event_whenthisspriteclicked"
|
||||
},
|
||||
{
|
||||
"title": "当背景换成 %1",
|
||||
"value": "event_whenbackdropswitchesto",
|
||||
"key": "event_whenbackdropswitchesto"
|
||||
},
|
||||
{
|
||||
"title": "当 %1 > %2",
|
||||
"value": "event_whengreaterthan",
|
||||
"key": "event_whengreaterthan"
|
||||
},
|
||||
{
|
||||
"title": "当接收到 %1",
|
||||
"value": "event_whenbroadcastreceived",
|
||||
"key": "event_whenbroadcastreceived"
|
||||
},
|
||||
{
|
||||
"title": "广播 %1",
|
||||
"value": "event_broadcast",
|
||||
"key": "event_broadcast"
|
||||
},
|
||||
{
|
||||
"title": "广播 %1 并等待",
|
||||
"value": "event_broadcastandwait",
|
||||
"key": "event_broadcastandwait"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "控制",
|
||||
"value": "control",
|
||||
"key": "control",
|
||||
"children": [
|
||||
{
|
||||
"title": "等待 %1 秒",
|
||||
"value": "control_wait",
|
||||
"key": "control_wait"
|
||||
},
|
||||
{
|
||||
"title": "重复执行 %1 次",
|
||||
"value": "control_repeat",
|
||||
"key": "control_repeat"
|
||||
},
|
||||
{
|
||||
"title": "重复执行",
|
||||
"value": "control_forever",
|
||||
"key": "control_forever"
|
||||
},
|
||||
{
|
||||
"title": "如果 %1 那么",
|
||||
"value": "control_if",
|
||||
"key": "control_if"
|
||||
},
|
||||
{
|
||||
"title": "如果 %1 那么 否则",
|
||||
"value": "control_if_else",
|
||||
"key": "control_if_else"
|
||||
},
|
||||
{
|
||||
"title": "等待 %1",
|
||||
"value": "control_wait_until",
|
||||
"key": "control_wait_until"
|
||||
},
|
||||
{
|
||||
"title": "重复执行直到 %1",
|
||||
"value": "control_repeat_until",
|
||||
"key": "control_repeat_until"
|
||||
},
|
||||
{
|
||||
"title": "停止 1%",
|
||||
"value": "control_stop",
|
||||
"key": "control_stop"
|
||||
},
|
||||
{
|
||||
"title": "克隆 %1",
|
||||
"value": "control_create_clone_of",
|
||||
"key": "control_create_clone_of"
|
||||
},
|
||||
{
|
||||
"title": "删除此克隆体",
|
||||
"value": "control_delete_this_clone",
|
||||
"key": "control_delete_this_clone"
|
||||
},
|
||||
{
|
||||
"title": "当作为克隆体启动时",
|
||||
"value": "control_start_as_clone",
|
||||
"key": "control_start_as_clone"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "侦测",
|
||||
"value": "sensing",
|
||||
"key": "sensing",
|
||||
"children": [
|
||||
{
|
||||
"title": "碰到 %1 ?",
|
||||
"value": "sensing_touchingobject",
|
||||
"key": "sensing_touchingobject"
|
||||
},
|
||||
{
|
||||
"title": "碰到颜色 %1 ?",
|
||||
"value": "sensing_touchingcolor",
|
||||
"key": "sensing_touchingcolor"
|
||||
},
|
||||
{
|
||||
"title": "颜色 %1 碰到 %2 ?",
|
||||
"value": "sensing_coloristouchingcolor",
|
||||
"key": "sensing_coloristouchingcolor"
|
||||
},
|
||||
{
|
||||
"title": "到 %1 的距离",
|
||||
"value": "sensing_distanceto",
|
||||
"key": "sensing_distanceto"
|
||||
},
|
||||
{
|
||||
"title": "询问 %1 并等待",
|
||||
"value": "sensing_askandwait",
|
||||
"key": "sensing_askandwait"
|
||||
},
|
||||
{
|
||||
"title": "回答",
|
||||
"value": "sensing_answer",
|
||||
"key": "sensing_answer"
|
||||
},
|
||||
{
|
||||
"title": "按下 %1 键?",
|
||||
"value": "sensing_keypressed",
|
||||
"key": "sensing_keypressed"
|
||||
},
|
||||
{
|
||||
"title": "按下鼠标?",
|
||||
"value": "sensing_mousedown",
|
||||
"key": "sensing_mousedown"
|
||||
},
|
||||
{
|
||||
"title": "鼠标的x坐标",
|
||||
"value": "sensing_mousex",
|
||||
"key": "sensing_mousex"
|
||||
},
|
||||
{
|
||||
"title": "鼠标的y坐标",
|
||||
"value": "sensing_mousey",
|
||||
"key": "sensing_mousey"
|
||||
},
|
||||
{
|
||||
"title": "将拖动模式设为 %1",
|
||||
"value": "sensing_setdragmode",
|
||||
"key": "sensing_setdragmode"
|
||||
},
|
||||
{
|
||||
"title": "响度",
|
||||
"value": "sensing_loudness",
|
||||
"key": "sensing_loudness"
|
||||
},
|
||||
{
|
||||
"title": "计时器",
|
||||
"value": "sensing_timer",
|
||||
"key": "sensing_timer"
|
||||
},
|
||||
{
|
||||
"title": "计时器归零",
|
||||
"value": "sensing_resettimer",
|
||||
"key": "sensing_resettimer"
|
||||
},
|
||||
{
|
||||
"title": "%2 的 %1",
|
||||
"value": "sensing_of",
|
||||
"key": "sensing_of"
|
||||
},
|
||||
{
|
||||
"title": "当前时间的 %1",
|
||||
"value": "sensing_current",
|
||||
"key": "sensing_current"
|
||||
},
|
||||
{
|
||||
"title": "2000年至今的天数",
|
||||
"value": "sensing_dayssince2000",
|
||||
"key": "sensing_dayssince2000"
|
||||
},
|
||||
{
|
||||
"title": "用户名",
|
||||
"value": "sensing_username",
|
||||
"key": "sensing_username"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "运算",
|
||||
"value": "operators",
|
||||
"key": "operators",
|
||||
"children": [
|
||||
{
|
||||
"title": "+",
|
||||
"value": "operator_add",
|
||||
"key": "operator_add"
|
||||
},
|
||||
{
|
||||
"title": "-",
|
||||
"value": "operator_subtract",
|
||||
"key": "operator_subtract"
|
||||
},
|
||||
{
|
||||
"title": "*",
|
||||
"value": "operator_multiply",
|
||||
"key": "operator_multiply"
|
||||
},
|
||||
{
|
||||
"title": "/",
|
||||
"value": "operator_divide",
|
||||
"key": "operator_divide"
|
||||
},
|
||||
{
|
||||
"title": "在 %1 和 %2 之间取随机数",
|
||||
"value": "operator_random",
|
||||
"key": "operator_random"
|
||||
},
|
||||
{
|
||||
"title": ">",
|
||||
"value": "operator_gt",
|
||||
"key": "operator_gt"
|
||||
},
|
||||
{
|
||||
"title": "<",
|
||||
"value": "operator_lt",
|
||||
"key": "operator_lt"
|
||||
},
|
||||
{
|
||||
"title": "=",
|
||||
"value": "operator_equals",
|
||||
"key": "operator_equals"
|
||||
},
|
||||
{
|
||||
"title": "与",
|
||||
"value": "operator_and",
|
||||
"key": "operator_and"
|
||||
},
|
||||
{
|
||||
"title": "或",
|
||||
"value": "operator_or",
|
||||
"key": "operator_or"
|
||||
},
|
||||
{
|
||||
"title": "%1 不成立",
|
||||
"value": "operator_not",
|
||||
"key": "operator_not"
|
||||
},
|
||||
{
|
||||
"title": "连接 %1 和 %2",
|
||||
"value": "operator_join",
|
||||
"key": "operator_join"
|
||||
},
|
||||
{
|
||||
"title": "%2 的第 %1 个字符",
|
||||
"value": "operator_letter_of",
|
||||
"key": "operator_letter_of"
|
||||
},
|
||||
{
|
||||
"title": "%1 的字符数",
|
||||
"value": "operator_length",
|
||||
"key": "operator_length"
|
||||
},
|
||||
{
|
||||
"title": "%1 包含 %2 ?",
|
||||
"value": "operator_contains",
|
||||
"key": "operator_contains"
|
||||
},
|
||||
{
|
||||
"title": "%1 除以 %2 的余数",
|
||||
"value": "operator_mod",
|
||||
"key": "operator_mod"
|
||||
},
|
||||
{
|
||||
"title": "四舍五入 %1",
|
||||
"value": "operator_round",
|
||||
"key": "operator_round"
|
||||
},
|
||||
{
|
||||
"title": "科学计算",
|
||||
"value": "operator_mathop",
|
||||
"key": "operator_mathop"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "变量",
|
||||
"value": "variables",
|
||||
"key": "variables"
|
||||
},
|
||||
{
|
||||
"title": "自定义积木",
|
||||
"value": "myBlocks",
|
||||
"key": "myBlocks"
|
||||
}
|
||||
]
|
125
doc/scratch-vm.md
Normal file
@ -0,0 +1,125 @@
|
||||
|
||||
[scratch-vm](https://github.com/LLK/scratch-vm)
|
||||
|
||||
[官方文档搬运](./scratch-vm/index.html)
|
||||
|
||||
## 主要功能
|
||||
|
||||
+ 侦听scratch-blocks发出的事件来构造和维护[抽象语法树](https://en.wikipedia.org/wiki/Abstract_syntax_tree)的状态。
|
||||
+ 加载解析项目:解析sb2和sb3项目文件,生成积木和角色等
|
||||
+ 导出项目:将当前项目资源打包压缩成 .sb3 文件或者生成json格式
|
||||
+ 运行项目并将结果渲染至舞台
|
||||
+ 扩展管理
|
||||
|
||||
## 编译
|
||||
|
||||
yarn install
|
||||
|
||||
yarn run build
|
||||
|
||||
在国内可能会因网络问题编译失败,如果有条件可以开全局代理,或者干脆在国外的服务器上编译
|
||||
|
||||
## 项目解析
|
||||
|
||||
```
|
||||
├── dist # 编译后的文件夹
|
||||
│ ├── node # 用在node.js
|
||||
│ ├── web # 用在web
|
||||
├── playground # 几个vm使用案例和文档
|
||||
├── docs # 拓展帮助文档
|
||||
├── src
|
||||
│ ├── blocks # 对几种积木功能的定义
|
||||
│ ├── extension-support # 拓展支持
|
||||
│ ├── extensions # 内置拓展
|
||||
│ ├── utils # 常用工具类
|
||||
│ ├── dispatch # 消息调度
|
||||
│ ├── playground # 几个vm使用案例
|
||||
│ ├── engine # vm核心引擎
|
||||
│ ├── import # 载入素材
|
||||
│ ├── io # 处理IO事件
|
||||
│ ├── serialization # 解析sb2和sb3文件
|
||||
│ ├── sprites # 角色操作
|
||||
├── test # 测试用例
|
||||
├── README.md
|
||||
└── package.json
|
||||
└── webpack.consig.js
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 常用方法
|
||||
|
||||
|
||||
|
||||
#### toJSON 获取JSON格式的项目
|
||||
|
||||
返回:{String} JSON格式的项目
|
||||
|
||||
#### saveProjectSb3 获取SB3格式项目
|
||||
|
||||
返回:{Promise} zip格式的项目
|
||||
|
||||
#### loadProject 加载SB3项目
|
||||
|
||||
参数:{string | object}
|
||||
|
||||
返回:{Promise}
|
||||
|
||||
##### greenFlag
|
||||
|
||||
点击小绿旗
|
||||
|
||||
#### stopAll 停止项目运行
|
||||
|
||||
#### setTurboMode 设置为加速模式
|
||||
|
||||
参数:{Boolean}
|
||||
|
||||
#### clear 清除当前项目数据
|
||||
|
||||
#### addSprite 添加角色
|
||||
|
||||
参数:{String|Object}
|
||||
|
||||
返回:{Promise}
|
||||
|
||||
#### postIODate 向vm发送I/O数据
|
||||
|
||||
类型:Clock、Cloud、Keyboard、Mouse、MouseWheel、UserData、Video
|
||||
|
||||
相关定义在src/io包下
|
||||
|
||||
例:
|
||||
|
||||
```js
|
||||
//按下按键
|
||||
vm.postIOData("keyboard", {
|
||||
keyCode: keyCode,
|
||||
key: key,
|
||||
isDown: true,
|
||||
});
|
||||
//松开按键
|
||||
vm.postIOData("keyboard", {
|
||||
keyCode: keyCode,
|
||||
key: key,
|
||||
isDown: false,
|
||||
});
|
||||
//鼠标移动
|
||||
vm.postIOData('mouseWheel', {
|
||||
deltaX: e.deltaX,
|
||||
deltaY: e.deltaY
|
||||
})
|
||||
//向vm发送用户信息,向vm推送云变量
|
||||
vm.postIOData('userData', {username: this.props.username});
|
||||
```
|
||||
|
||||
#### addListener
|
||||
|
||||
监听事件,如:
|
||||
|
||||
- workspaceUpdate
|
||||
- EXTENSION_ADDED
|
||||
|
||||
#### removeListener
|
||||
|
||||
解绑事件
|
1343
doc/scratch-vm/-_StackFrame.html
Normal file
1757
doc/scratch-vm/BLE.html
Normal file
1083
doc/scratch-vm/BT.html
Normal file
1569
doc/scratch-vm/BlockCached.html
Normal file
5794
doc/scratch-vm/Blocks.html
Normal file
3512
doc/scratch-vm/Boost.html
Normal file
2641
doc/scratch-vm/BoostMotor.html
Normal file
510
doc/scratch-vm/CanvasPool.html
Normal file
1451
doc/scratch-vm/CentralDispatch.html
Normal file
344
doc/scratch-vm/Clone.html
Normal file
1510
doc/scratch-vm/Cloud.html
Normal file
369
doc/scratch-vm/Comment.html
Normal file
1939
doc/scratch-vm/EV3Motor.html
Normal file
1365
doc/scratch-vm/GdxFor.html
Normal file
2422
doc/scratch-vm/MicroBit.html
Normal file
2295
doc/scratch-vm/Profiler.html
Normal file
653
doc/scratch-vm/ProfilerFrame.html
Normal file
624
doc/scratch-vm/RateLimiter.html
Normal file
8617
doc/scratch-vm/RenderedTarget.html
Normal file
18499
doc/scratch-vm/Runtime.html
Normal file
3607
doc/scratch-vm/Scratch3BoostBlocks.html
Normal file
468
doc/scratch-vm/Scratch3CoreExample.html
Normal file
796
doc/scratch-vm/Scratch3Ev3Blocks.html
Normal file
720
doc/scratch-vm/Scratch3GdxForBlocks.html
Normal file
513
doc/scratch-vm/Scratch3MakeyMakeyBlocks.html
Normal file
2809
doc/scratch-vm/Scratch3MicroBitBlocks.html
Normal file
5962
doc/scratch-vm/Scratch3MusicBlocks.html
Normal file
5173
doc/scratch-vm/Scratch3PenBlocks.html
Normal file
2763
doc/scratch-vm/Scratch3Text2SpeechBlocks.html
Normal file
1433
doc/scratch-vm/Scratch3TranslateBlocks.html
Normal file
2734
doc/scratch-vm/Scratch3VideoSensingBlocks.html
Normal file
3627
doc/scratch-vm/Scratch3WeDo2Blocks.html
Normal file
186
doc/scratch-vm/ScratchLinkDeviceAdapter.html
Normal file
197
doc/scratch-vm/ScratchLinkWebSocket.html
Normal file
2382
doc/scratch-vm/SharedDispatch.html
Normal file
1279
doc/scratch-vm/Sprite.html
Normal file
5092
doc/scratch-vm/Target.html
Normal file
1364
doc/scratch-vm/TaskQueue.html
Normal file
2856
doc/scratch-vm/Thread.html
Normal file
510
doc/scratch-vm/Variable.html
Normal file
1266
doc/scratch-vm/VideoMotion.html
Normal file
1491
doc/scratch-vm/VideoMotionView.html
Normal file
11268
doc/scratch-vm/VirtualMachine.html
Normal file
3666
doc/scratch-vm/WeDo2.html
Normal file
208
doc/scratch-vm/WeDo2Motor.BRAKE_TIME_MS.html
Normal file
1882
doc/scratch-vm/WeDo2Motor.html
Normal file
932
doc/scratch-vm/WorkerDispatch.html
Normal file
37477
doc/scratch-vm/global.html
Normal file
472
doc/scratch-vm/global.html#RuntimeScriptCache
Normal file
1720
doc/scratch-vm/index.html
Normal file
20
doc/scratch-vm/scripts/collapse.js
Normal file
@ -0,0 +1,20 @@
|
||||
function hideAllButCurrent(){
|
||||
//by default all submenut items are hidden
|
||||
//but we need to rehide them for search
|
||||
document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) {
|
||||
parent.style.display = "none";
|
||||
});
|
||||
|
||||
//only current page (if it exists) should be opened
|
||||
var file = window.location.pathname.split("/").pop().replace(/\.html/, '');
|
||||
document.querySelectorAll("nav > ul > li > a").forEach(function(parent) {
|
||||
var href = parent.attributes.href.value.replace(/\.html/, '');
|
||||
if (file === href) {
|
||||
parent.parentNode.querySelectorAll("ul li").forEach(function(elem) {
|
||||
elem.style.display = "block";
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hideAllButCurrent();
|
25
doc/scratch-vm/scripts/linenumber.js
Normal file
@ -0,0 +1,25 @@
|
||||
/*global document */
|
||||
(function() {
|
||||
var source = document.getElementsByClassName('prettyprint source linenums');
|
||||
var i = 0;
|
||||
var lineNumber = 0;
|
||||
var lineId;
|
||||
var lines;
|
||||
var totalLines;
|
||||
var anchorHash;
|
||||
|
||||
if (source && source[0]) {
|
||||
anchorHash = document.location.hash.substring(1);
|
||||
lines = source[0].getElementsByTagName('li');
|
||||
totalLines = lines.length;
|
||||
|
||||
for (; i < totalLines; i++) {
|
||||
lineNumber++;
|
||||
lineId = 'line' + lineNumber;
|
||||
lines[i].id = lineId;
|
||||
if (lineId === anchorHash) {
|
||||
lines[i].className += ' selected';
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
12
doc/scratch-vm/scripts/nav.js
Normal file
@ -0,0 +1,12 @@
|
||||
function scrollToNavItem() {
|
||||
var path = window.location.href.split('/').pop().replace(/\.html/, '');
|
||||
document.querySelectorAll('nav a').forEach(function(link) {
|
||||
var href = link.attributes.href.value.replace(/\.html/, '');
|
||||
if (path === href) {
|
||||
link.scrollIntoView({block: 'center'});
|
||||
return;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
scrollToNavItem();
|
4
doc/scratch-vm/scripts/polyfill.js
Normal file
@ -0,0 +1,4 @@
|
||||
//IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/
|
||||
if (typeof(NodeList.prototype.forEach)!==typeof(alert)){
|
||||
NodeList.prototype.forEach=Array.prototype.forEach;
|
||||
}
|
202
doc/scratch-vm/scripts/prettify/Apache-License-2.0.txt
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
2
doc/scratch-vm/scripts/prettify/lang-css.js
Normal file
@ -0,0 +1,2 @@
|
||||
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
|
||||
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
|
28
doc/scratch-vm/scripts/prettify/prettify.js
Normal file
@ -0,0 +1,28 @@
|
||||
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
|
||||
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
|
||||
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
|
||||
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
|
||||
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
|
||||
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
|
||||
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
|
||||
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
|
||||
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
|
||||
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
|
||||
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
|
||||
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
|
||||
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
|
||||
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
|
||||
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
|
||||
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
|
||||
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
|
||||
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
|
||||
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
|
||||
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
|
||||
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
|
||||
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
|
||||
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
|
||||
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
|
||||
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
|
||||
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
|
||||
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
|
||||
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
|
83
doc/scratch-vm/scripts/search.js
Normal file
@ -0,0 +1,83 @@
|
||||
|
||||
var searchAttr = 'data-search-mode';
|
||||
function contains(a,m){
|
||||
return (a.textContent || a.innerText || "").toUpperCase().indexOf(m) !== -1;
|
||||
};
|
||||
|
||||
//on search
|
||||
document.getElementById("nav-search").addEventListener("keyup", function(event) {
|
||||
var search = this.value.toUpperCase();
|
||||
|
||||
if (!search) {
|
||||
//no search, show all results
|
||||
document.documentElement.removeAttribute(searchAttr);
|
||||
|
||||
document.querySelectorAll("nav > ul > li:not(.level-hide)").forEach(function(elem) {
|
||||
elem.style.display = "block";
|
||||
});
|
||||
|
||||
if (typeof hideAllButCurrent === "function"){
|
||||
//let's do what ever collapse wants to do
|
||||
hideAllButCurrent();
|
||||
} else {
|
||||
//menu by default should be opened
|
||||
document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) {
|
||||
elem.style.display = "block";
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//we are searching
|
||||
document.documentElement.setAttribute(searchAttr, '');
|
||||
|
||||
//show all parents
|
||||
document.querySelectorAll("nav > ul > li").forEach(function(elem) {
|
||||
elem.style.display = "block";
|
||||
});
|
||||
//hide all results
|
||||
document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) {
|
||||
elem.style.display = "none";
|
||||
});
|
||||
//show results matching filter
|
||||
document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) {
|
||||
if (!contains(elem.parentNode, search)) {
|
||||
return;
|
||||
}
|
||||
elem.parentNode.style.display = "block";
|
||||
});
|
||||
//hide parents without children
|
||||
document.querySelectorAll("nav > ul > li").forEach(function(parent) {
|
||||
var countSearchA = 0;
|
||||
parent.querySelectorAll("a").forEach(function(elem) {
|
||||
if (contains(elem, search)) {
|
||||
countSearchA++;
|
||||
}
|
||||
});
|
||||
|
||||
var countUl = 0;
|
||||
var countUlVisible = 0;
|
||||
parent.querySelectorAll("ul").forEach(function(ulP) {
|
||||
// count all elements that match the search
|
||||
if (contains(ulP, search)) {
|
||||
countUl++;
|
||||
}
|
||||
|
||||
// count all visible elements
|
||||
var children = ulP.children
|
||||
for (i=0; i<children.length; i++) {
|
||||
var elem = children[i];
|
||||
if (elem.style.display != "none") {
|
||||
countUlVisible++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (countSearchA == 0 && countUl === 0){
|
||||
//has no child at all and does not contain text
|
||||
parent.style.display = "none";
|
||||
} else if(countSearchA == 0 && countUlVisible == 0){
|
||||
//has no visible child and does not contain text
|
||||
parent.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
671
doc/scratch-vm/styles/jsdoc.css
Normal file
@ -0,0 +1,671 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Montserrat:400,700);
|
||||
|
||||
* {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #4d4e53;
|
||||
background-color: white;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
a,
|
||||
a:active {
|
||||
color: #606;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
article a {
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
article a:hover, article a:active {
|
||||
border-bottom-color: #222;
|
||||
}
|
||||
|
||||
article .description a {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
p, ul, ol, blockquote {
|
||||
margin-bottom: 1em;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #000;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 300;
|
||||
font-size: 48px;
|
||||
margin: 1em 0 .5em;
|
||||
}
|
||||
|
||||
h1.page-title {
|
||||
font-size: 48px;
|
||||
margin: 1em 30px;
|
||||
line-height: 100%;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
margin: 1.5em 0 .3em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 24px;
|
||||
margin: 1.2em 0 .3em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 18px;
|
||||
margin: 1em 0 .2em;
|
||||
color: #4d4e53;
|
||||
}
|
||||
|
||||
h4.name {
|
||||
color: #fff;
|
||||
background: #6d426d;
|
||||
box-shadow: 0 .25em .5em #d3d3d3;
|
||||
border-top: 1px solid #d3d3d3;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
margin: 1.5em 0 0.5em;
|
||||
padding: .75em 0 .75em 10px;
|
||||
}
|
||||
|
||||
h4.name a {
|
||||
color: #fc83ff;
|
||||
}
|
||||
|
||||
h4.name a:hover {
|
||||
border-bottom-color: #fc83ff;
|
||||
}
|
||||
|
||||
h5, .container-overview .subsection-title {
|
||||
font-size: 120%;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 8px 0 3px 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 100%;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 6px 0 3px 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
tt, code, kbd, samp {
|
||||
font-family: Consolas, Monaco, 'Andale Mono', monospace;
|
||||
background: #f4f4f4;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
.class-description {
|
||||
font-size: 130%;
|
||||
line-height: 140%;
|
||||
margin-bottom: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.class-description:empty {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#main {
|
||||
float: right;
|
||||
min-width: 360px;
|
||||
width: calc(100% - 240px);
|
||||
}
|
||||
|
||||
header {
|
||||
display: block
|
||||
}
|
||||
|
||||
section {
|
||||
display: block;
|
||||
background-color: #fff;
|
||||
padding: 0 0 0 30px;
|
||||
}
|
||||
|
||||
.variation {
|
||||
display: none
|
||||
}
|
||||
|
||||
.signature-attributes {
|
||||
font-size: 60%;
|
||||
color: #eee;
|
||||
font-style: italic;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
nav {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 250px;
|
||||
background: #fff;
|
||||
overflow: auto;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
nav #nav-search{
|
||||
width: 210px;
|
||||
height: 30px;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
border-radius: 3px;
|
||||
margin-right: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
nav.wrap a{
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
nav h3 {
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin: 15px 0 10px;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
nav ul {
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 17px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
nav ul a,
|
||||
nav ul a:active {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
line-height: 18px;
|
||||
padding: 0;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
nav a:hover,
|
||||
nav a:active {
|
||||
color: #606;
|
||||
}
|
||||
|
||||
nav > ul {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
nav > ul > li > a {
|
||||
color: #606;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
nav ul ul a {
|
||||
color: hsl(207, 1%, 60%);
|
||||
border-left: 1px solid hsl(207, 10%, 86%);
|
||||
}
|
||||
|
||||
nav ul ul a,
|
||||
nav ul ul a:active {
|
||||
padding-left: 20px
|
||||
}
|
||||
|
||||
nav h2 {
|
||||
font-size: 13px;
|
||||
margin: 10px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
nav > h2 > a {
|
||||
margin: 10px 0 -10px;
|
||||
color: #606 !important;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: hsl(0, 0%, 28%);
|
||||
margin-left: 250px;
|
||||
display: block;
|
||||
padding: 15px;
|
||||
font-style: italic;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.ancestors {
|
||||
color: #999
|
||||
}
|
||||
|
||||
.ancestors a {
|
||||
color: #999 !important;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both
|
||||
}
|
||||
|
||||
.important {
|
||||
font-weight: bold;
|
||||
color: #950B02;
|
||||
}
|
||||
|
||||
.yes-def {
|
||||
text-indent: -1000px
|
||||
}
|
||||
|
||||
.type-signature {
|
||||
color: #CA79CA
|
||||
}
|
||||
|
||||
.type-signature:last-child {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.name, .signature {
|
||||
font-family: Consolas, Monaco, 'Andale Mono', monospace
|
||||
}
|
||||
|
||||
.signature {
|
||||
color: #fc83ff;
|
||||
}
|
||||
|
||||
.details {
|
||||
margin-top: 6px;
|
||||
border-left: 2px solid #DDD;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.details dt {
|
||||
width: auto;
|
||||
float: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.details dd {
|
||||
margin-left: 70px;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.details ul {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.details ul {
|
||||
list-style-type: none
|
||||
}
|
||||
|
||||
.details pre.prettyprint {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.details .object-value {
|
||||
padding-top: 0
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-bottom: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.code-caption {
|
||||
font-style: italic;
|
||||
font-size: 107%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.prettyprint {
|
||||
font-size: 14px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.prettyprint.source {
|
||||
width: inherit;
|
||||
line-height: 18px;
|
||||
display: block;
|
||||
background-color: #0d152a;
|
||||
color: #aeaeae;
|
||||
}
|
||||
|
||||
.prettyprint code {
|
||||
line-height: 18px;
|
||||
display: block;
|
||||
background-color: #0d152a;
|
||||
color: #4D4E53;
|
||||
}
|
||||
|
||||
.prettyprint > code {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.prettyprint .linenums code {
|
||||
padding: 0 15px
|
||||
}
|
||||
|
||||
.prettyprint .linenums li:first-of-type code {
|
||||
padding-top: 15px
|
||||
}
|
||||
|
||||
.prettyprint code span.line {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.prettyprint.linenums {
|
||||
padding-left: 70px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.prettyprint.linenums ol {
|
||||
padding-left: 0
|
||||
}
|
||||
|
||||
.prettyprint.linenums li {
|
||||
border-left: 3px #34446B solid;
|
||||
}
|
||||
|
||||
.prettyprint.linenums li.selected, .prettyprint.linenums li.selected * {
|
||||
background-color: #34446B;
|
||||
}
|
||||
|
||||
.prettyprint.linenums li * {
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
border: 1px solid #ddd;
|
||||
border-collapse: collapse;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
td, th {
|
||||
margin: 0px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
padding: 10px;
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
thead tr, thead tr {
|
||||
background-color: #fff;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.params .type {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.params code {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.params td, .params .name, .props .name, .name code {
|
||||
color: #4D4E53;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', monospace;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.params td {
|
||||
border-top: 1px solid #eee
|
||||
}
|
||||
|
||||
.params td.description > p:first-child, .props td.description > p:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.params td.description > p:last-child, .props td.description > p:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
span.param-type, .params td .param-type, .param-type dd {
|
||||
color: #606;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', monospace
|
||||
}
|
||||
|
||||
.param-type dt, .param-type dd {
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.param-type {
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: #454545
|
||||
}
|
||||
|
||||
/* navicon button */
|
||||
.navicon-button {
|
||||
display: none;
|
||||
position: relative;
|
||||
padding: 2.0625rem 1.5rem;
|
||||
transition: 0.25s;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
opacity: .8;
|
||||
}
|
||||
.navicon-button .navicon:before, .navicon-button .navicon:after {
|
||||
transition: 0.25s;
|
||||
}
|
||||
.navicon-button:hover {
|
||||
transition: 0.5s;
|
||||
opacity: 1;
|
||||
}
|
||||
.navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after {
|
||||
transition: 0.25s;
|
||||
}
|
||||
.navicon-button:hover .navicon:before {
|
||||
top: .825rem;
|
||||
}
|
||||
.navicon-button:hover .navicon:after {
|
||||
top: -.825rem;
|
||||
}
|
||||
|
||||
/* navicon */
|
||||
.navicon {
|
||||
position: relative;
|
||||
width: 2.5em;
|
||||
height: .3125rem;
|
||||
background: #000;
|
||||
transition: 0.3s;
|
||||
border-radius: 2.5rem;
|
||||
}
|
||||
.navicon:before, .navicon:after {
|
||||
display: block;
|
||||
content: "";
|
||||
height: .3125rem;
|
||||
width: 2.5rem;
|
||||
background: #000;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
transition: 0.3s 0.25s;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.navicon:before {
|
||||
top: .625rem;
|
||||
}
|
||||
.navicon:after {
|
||||
top: -.625rem;
|
||||
}
|
||||
|
||||
/* open */
|
||||
.nav-trigger:checked + label:not(.steps) .navicon:before,
|
||||
.nav-trigger:checked + label:not(.steps) .navicon:after {
|
||||
top: 0 !important;
|
||||
}
|
||||
|
||||
.nav-trigger:checked + label .navicon:before,
|
||||
.nav-trigger:checked + label .navicon:after {
|
||||
transition: 0.5s;
|
||||
}
|
||||
|
||||
/* Minus */
|
||||
.nav-trigger:checked + label {
|
||||
-webkit-transform: scale(0.75);
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
/* × and + */
|
||||
.nav-trigger:checked + label.plus .navicon,
|
||||
.nav-trigger:checked + label.x .navicon {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-trigger:checked + label.plus .navicon:before,
|
||||
.nav-trigger:checked + label.x .navicon:before {
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
.nav-trigger:checked + label.plus .navicon:after,
|
||||
.nav-trigger:checked + label.x .navicon:after {
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
.nav-trigger:checked + label.plus {
|
||||
-webkit-transform: scale(0.75) rotate(45deg);
|
||||
transform: scale(0.75) rotate(45deg);
|
||||
}
|
||||
|
||||
.nav-trigger:checked ~ nav {
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
.nav-trigger:checked ~ .overlay {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-trigger {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: hsla(0, 0%, 0%, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* nav level */
|
||||
.level-hide {
|
||||
display: none;
|
||||
}
|
||||
html[data-search-mode] .level-hide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (min-width: 320px) and (max-width: 680px) {
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
nav {
|
||||
background: #FFF;
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: -250px;
|
||||
z-index: 3;
|
||||
padding: 0 10px;
|
||||
transition: left 0.2s;
|
||||
}
|
||||
|
||||
.navicon-button {
|
||||
display: inline-block;
|
||||
position: fixed;
|
||||
top: 1.5em;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#main {
|
||||
width: 100%;
|
||||
min-width: 360px;
|
||||
}
|
||||
|
||||
#main h1.page-title {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
#main section {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a '#' to static members */
|
||||
[data-type="member"] a::before {
|
||||
content: '#';
|
||||
display: inline-block;
|
||||
margin-left: -14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#disqus_thread{
|
||||
margin-left: 30px;
|
||||
}
|
79
doc/scratch-vm/styles/prettify.css
Normal file
@ -0,0 +1,79 @@
|
||||
.pln {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* string content */
|
||||
.str {
|
||||
color: #61ce3c;
|
||||
}
|
||||
|
||||
/* a keyword */
|
||||
.kwd {
|
||||
color: #fbde2d;
|
||||
}
|
||||
|
||||
/* a comment */
|
||||
.com {
|
||||
color: #aeaeae;
|
||||
}
|
||||
|
||||
/* a type name */
|
||||
.typ {
|
||||
color: #8da6ce;
|
||||
}
|
||||
|
||||
/* a literal value */
|
||||
.lit {
|
||||
color: #fbde2d;
|
||||
}
|
||||
|
||||
/* punctuation */
|
||||
.pun {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* lisp open bracket */
|
||||
.opn {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* lisp close bracket */
|
||||
.clo {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* a markup tag name */
|
||||
.tag {
|
||||
color: #8da6ce;
|
||||
}
|
||||
|
||||
/* a markup attribute name */
|
||||
.atn {
|
||||
color: #fbde2d;
|
||||
}
|
||||
|
||||
/* a markup attribute value */
|
||||
.atv {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
/* a declaration */
|
||||
.dec {
|
||||
color: #EF5050;
|
||||
}
|
||||
|
||||
/* a variable name */
|
||||
.var {
|
||||
color: #c82829;
|
||||
}
|
||||
|
||||
/* a function name */
|
||||
.fun {
|
||||
color: #4271ae;
|
||||
}
|
||||
|
||||
/* Specify class=linenums on a pre to get line numbering */
|
||||
ol.linenums {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
BIN
docs/project_state_diagram.svg
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
docs/project_state_example.png
Normal file
After Width: | Height: | Size: 27 KiB |
36270
package-lock.json
generated
Normal file
146
package.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"name": "easy-scratch3",
|
||||
"version": "0.1.0",
|
||||
"description": "Easy Scratch 3.0",
|
||||
"main": "./dist/scratch-gui.js",
|
||||
"scripts": {
|
||||
"build": "npm run clean && webpack --progress --colors --bail",
|
||||
"build:prod": "npm run clean && webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors --bail --config webpack.prod.js",
|
||||
"clean": "rimraf ./build && mkdirp build && rimraf ./dist && mkdirp dist",
|
||||
"deploy": "touch build/.nojekyll && gh-pages -t -d build -m \"Build for $(git log --pretty=format:%H -n1) [skip ci]\"",
|
||||
"prune": "./prune-gh-pages.sh",
|
||||
"i18n:push": "tx-push-src scratch-editor interface translations/en.json",
|
||||
"i18n:src": "rimraf ./translations/messages/src && babel src > tmp.js && rimraf tmp.js && build-i18n-src ./translations/messages/src ./translations/ && npm run i18n:push",
|
||||
"start": "webpack-dev-server",
|
||||
"test": "npm run test:lint && npm run test:unit && npm run build && npm run test:integration",
|
||||
"test:integration": "jest --runInBand test[\\\\/]integration",
|
||||
"test:lint": "eslint . --ext .js,.jsx",
|
||||
"test:unit": "jest test[\\\\/]unit",
|
||||
"test:smoke": "jest --runInBand test[\\\\/]smoke",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
},
|
||||
"author": "Massachusetts Institute of Technology",
|
||||
"license": "BSD-3-Clause",
|
||||
"homepage": "https://github.com/open-scratch/easy-scratch3#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/open-scratch/easy-scratch3.git"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.1.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"arraybuffer-loader": "^1.0.6",
|
||||
"autoprefixer": "^9.0.1",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"base64-loader": "1.0.0",
|
||||
"bowser": "1.9.4",
|
||||
"chromedriver": "80.0.0",
|
||||
"classnames": "2.2.6",
|
||||
"computed-style-to-inline-style": "3.0.0",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"core-js": "2.5.7",
|
||||
"css-loader": "^1.0.0",
|
||||
"enzyme": "^3.5.0",
|
||||
"enzyme-adapter-react-16": "1.3.0",
|
||||
"es6-object-assign": "1.1.0",
|
||||
"eslint": "^5.0.1",
|
||||
"eslint-config-scratch": "^5.0.0",
|
||||
"eslint-import-resolver-webpack": "^0.11.1",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-jest": "^22.14.1",
|
||||
"eslint-plugin-react": "^7.12.4",
|
||||
"file-loader": "2.0.0",
|
||||
"get-float-time-domain-data": "0.1.0",
|
||||
"get-user-media-promise": "1.1.4",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"immutable": "3.8.2",
|
||||
"intl": "1.2.5",
|
||||
"jest": "^21.0.0",
|
||||
"jest-junit": "^7.0.0",
|
||||
"js-base64": "2.4.9",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.bindall": "4.4.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.defaultsdeep": "4.6.0",
|
||||
"lodash.isequal": "4.5.0",
|
||||
"lodash.omit": "4.5.0",
|
||||
"lodash.pick": "4.4.0",
|
||||
"lodash.throttle": "4.0.1",
|
||||
"minilog": "3.1.0",
|
||||
"mkdirp": "^1.0.3",
|
||||
"omggif": "1.0.9",
|
||||
"papaparse": "5.1.1",
|
||||
"postcss-import": "^12.0.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-simple-vars": "^5.0.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"query-string": "^5.1.1",
|
||||
"raf": "^3.4.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "16.2.0",
|
||||
"react-contextmenu": "2.9.4",
|
||||
"react-dom": "16.2.0",
|
||||
"react-draggable": "3.0.5",
|
||||
"react-ga": "2.5.3",
|
||||
"react-intl": "2.9.0",
|
||||
"react-modal": "3.9.1",
|
||||
"react-popover": "0.5.10",
|
||||
"react-redux": "5.0.7",
|
||||
"react-responsive": "4.0.0",
|
||||
"react-style-proptype": "3.2.2",
|
||||
"react-tabs": "2.3.0",
|
||||
"react-test-renderer": "16.2.0",
|
||||
"react-tooltip": "3.8.0",
|
||||
"react-virtualized": "9.20.1",
|
||||
"redux": "3.7.2",
|
||||
"redux-mock-store": "^1.2.3",
|
||||
"redux-throttle": "0.1.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20200528195344",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210829081240",
|
||||
"scratch-l10n":"https://github.com/open-scratch/easy-scratch-l10n.git",
|
||||
"scratch-paint": "0.2.0-prerelease.20210907080144",
|
||||
"scratch-render": "0.1.0-prerelease.20210819221425",
|
||||
"scratch-render-fonts": "1.0.0-prerelease.20210401210003",
|
||||
"scratch-storage": "1.3.5",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20210727023023",
|
||||
"scratch-vm": "0.2.0-prerelease.20210907050649",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
"svg-to-image": "1.1.3",
|
||||
"text-encoding": "0.7.0",
|
||||
"to-style": "1.3.3",
|
||||
"uglifyjs-webpack-plugin": "^1.2.5",
|
||||
"wav-encoder": "1.3.0",
|
||||
"web-audio-test-api": "^0.5.2",
|
||||
"webpack": "^4.6.0",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"webpack-dev-server": "^3.1.3",
|
||||
"xhr": "2.5.0"
|
||||
},
|
||||
"jest": {
|
||||
"setupFiles": [
|
||||
"raf/polyfill",
|
||||
"<rootDir>/test/helpers/enzyme-setup.js"
|
||||
],
|
||||
"testPathIgnorePatterns": [
|
||||
"src/test.js"
|
||||
],
|
||||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",
|
||||
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
|
||||
}
|
||||
}
|
||||
}
|
64
prune-gh-pages.sh
Normal file
@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# gh-pages cleanup script: Switches to gh-pages branch, and removes all
|
||||
# directories that aren't listed as remote branches
|
||||
|
||||
function deslash () {
|
||||
# Recursively build a string of a directory's parents. E.g.,
|
||||
# deslashed "feature/test/branch" returns feature/test feature
|
||||
deslashed=$(dirname $1)
|
||||
if [[ $deslashed =~ .*/.* ]]
|
||||
then
|
||||
echo $deslashed $(deslash $deslashed)
|
||||
else
|
||||
echo $deslashed
|
||||
fi
|
||||
}
|
||||
|
||||
repository=origin
|
||||
|
||||
if [[ $1 != "" ]]
|
||||
then
|
||||
repository=$1
|
||||
fi
|
||||
|
||||
# Cache current branch
|
||||
current=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# Checkout most recent gh-pages
|
||||
git fetch --force $repository gh-pages:gh-pages
|
||||
git checkout gh-pages
|
||||
git clean -fdx
|
||||
|
||||
# Make an array of directories to not delete, from the list of remote branches
|
||||
branches=$(git ls-remote --refs --quiet $repository | awk '{print $2}' | sed -e 's/refs\/heads\///')
|
||||
|
||||
# Add parent directories of branches to the exclusion list (e.g. greenkeeper/)
|
||||
for branch in $branches; do
|
||||
if [[ $branch =~ .*/.* ]]; then
|
||||
branches+=" $(deslash $branch)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Dedupe all the greenkeepers (or other duplicate parent directories)
|
||||
branches=$(echo "${branches[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')
|
||||
|
||||
# Remove all directories that don't have corresponding branches
|
||||
# It would be nice if we could exclude everything in .gitignore, but we're
|
||||
# not on the branch with the .gitignore anymore... so we can't.
|
||||
find . -type d \
|
||||
\( \
|
||||
-path ./.git -o \
|
||||
-path ./node_modules \
|
||||
$(printf " -o -path ./%s" $branches) \
|
||||
\) -prune \
|
||||
-o -mindepth 1 -type d \
|
||||
-exec rm -rfv {} \;
|
||||
|
||||
# Push
|
||||
git add -u
|
||||
git commit -m "Remove stale directories"
|
||||
git push $repository gh-pages
|
||||
|
||||
# Return to where we were
|
||||
git checkout -f $current
|
||||
exit
|
31
src/.eslintrc.js
Normal file
@ -0,0 +1,31 @@
|
||||
const path = require('path');
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['scratch', 'scratch/es6', 'scratch/react', 'plugin:import/errors'],
|
||||
env: {
|
||||
browser: true
|
||||
},
|
||||
globals: {
|
||||
process: true
|
||||
},
|
||||
rules: {
|
||||
'import/no-mutable-exports': 'error',
|
||||
'import/no-commonjs': 'error',
|
||||
'import/no-amd': 'error',
|
||||
'import/no-nodejs-modules': 'error',
|
||||
'react/jsx-no-literals': 'error',
|
||||
'no-confusing-arrow': ['error', {
|
||||
'allowParens': true
|
||||
}]
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: '16.2' // Prevent 16.3 lifecycle method errors
|
||||
},
|
||||
'import/resolver': {
|
||||
webpack: {
|
||||
config: path.resolve(__dirname, '../webpack.config.js')
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
200
src/components/action-menu/action-menu.css
Normal file
@ -0,0 +1,200 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
@import "../../css/z-index.css";
|
||||
|
||||
$main-button-size: 2.75rem;
|
||||
$more-button-size: 2.25rem;
|
||||
|
||||
.menu-container {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
transition: 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
background: $motion-primary;
|
||||
outline: none;
|
||||
border: none;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: $extensions-primary;
|
||||
}
|
||||
|
||||
.button:active {
|
||||
padding: inherit;
|
||||
}
|
||||
|
||||
.button.coming-soon:hover {
|
||||
background: $data-primary;
|
||||
}
|
||||
|
||||
.main-button {
|
||||
border-radius: 100%;
|
||||
width: $main-button-size;
|
||||
height: $main-button-size;
|
||||
box-shadow: 0 0 0 4px $motion-transparent;
|
||||
z-index: $z-index-add-button;
|
||||
transition: transform, box-shadow 0.5s;
|
||||
}
|
||||
|
||||
.main-button:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 0 0 6px $motion-transparent;
|
||||
}
|
||||
|
||||
.main-icon {
|
||||
width: calc($main-button-size - 1rem);
|
||||
height: calc($main-button-size - 1rem);
|
||||
}
|
||||
|
||||
[dir="rtl"] .main-icon {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.more-buttons-outer {
|
||||
/*
|
||||
Need to use two divs to set different overflow x/y
|
||||
which is needed to get animation to look right while
|
||||
allowing the tooltips to be visible.
|
||||
*/
|
||||
overflow-y: hidden;
|
||||
|
||||
background: hsla(47, 100%, 61%, 1);
|
||||
border-top-left-radius: $more-button-size;
|
||||
border-top-right-radius: $more-button-size;
|
||||
width: $more-button-size;
|
||||
margin-left: calc(($main-button-size - $more-button-size) / 2);
|
||||
margin-right: calc(($main-button-size - $more-button-size) / 2);
|
||||
|
||||
position: absolute;
|
||||
bottom: calc($main-button-size);
|
||||
|
||||
margin-bottom: calc($main-button-size / -2);
|
||||
padding-bottom: calc($main-button-size / 2);
|
||||
}
|
||||
|
||||
.more-buttons {
|
||||
max-height: 0;
|
||||
transition: max-height 1s;
|
||||
overflow-x: visible;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 10; /* @todo justify */
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.expanded .more-buttons {
|
||||
max-height: 1000px; /* Arbitrary, needs to be a value in order for animation to run */
|
||||
}
|
||||
|
||||
.force-hidden .more-buttons {
|
||||
display: none; /* This property does not animate */
|
||||
}
|
||||
|
||||
.more-buttons:first-child { /* Round off top button */
|
||||
border-top-right-radius: $more-button-size;
|
||||
border-top-left-radius: $more-button-size;
|
||||
}
|
||||
|
||||
.more-button {
|
||||
width: $more-button-size;
|
||||
height: $more-button-size;
|
||||
background: hsla(47, 100%, 61%, 1);
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
width: calc($more-button-size - 1rem);
|
||||
height: calc($more-button-size - 1rem);
|
||||
}
|
||||
|
||||
.coming-soon .more-icon {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*
|
||||
@todo needs to be refactored with coming soon tooltip overrides.
|
||||
The "!important"s are for the same reason as with coming soon, the library
|
||||
is not very easy to style.
|
||||
*/
|
||||
.tooltip {
|
||||
background-color: $extensions-primary !important;
|
||||
opacity: 1 !important;
|
||||
border: 1px solid hsla(0, 0%, 0%, .1) !important;
|
||||
box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
||||
}
|
||||
|
||||
.tooltip:after {
|
||||
background-color: $extensions-primary;
|
||||
}
|
||||
|
||||
.coming-soon-tooltip {
|
||||
background-color: $data-primary !important;
|
||||
}
|
||||
|
||||
.coming-soon-tooltip:after {
|
||||
background-color: $data-primary !important;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
border: 1px solid hsla(0, 0%, 0%, .1) !important;
|
||||
border-radius: $form-radius !important;
|
||||
box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
|
||||
z-index: $z-index-tooltip !important;
|
||||
}
|
||||
|
||||
$arrow-size: 0.5rem;
|
||||
$arrow-inset: -0.25rem;
|
||||
$arrow-rounding: 0.125rem;
|
||||
|
||||
.tooltip:after {
|
||||
content: "";
|
||||
border-top: 1px solid hsla(0, 0%, 0%, .1) !important;
|
||||
border-left: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
border-right: 1px solid hsla(0, 0%, 0%, .1) !important;
|
||||
border-radius: $arrow-rounding;
|
||||
height: $arrow-size !important;
|
||||
width: $arrow-size !important;
|
||||
}
|
||||
|
||||
.tooltip:global(.place-left):after {
|
||||
margin-top: $arrow-inset !important;
|
||||
right: $arrow-inset !important;
|
||||
transform: rotate(45deg) !important;
|
||||
}
|
||||
|
||||
.tooltip:global(.place-right):after {
|
||||
margin-top: $arrow-inset !important;
|
||||
left: $arrow-inset !important;
|
||||
transform: rotate(-135deg) !important;
|
||||
}
|
||||
|
||||
.tooltip:global(.place-top):after {
|
||||
margin-right: $arrow-inset !important;
|
||||
bottom: $arrow-inset !important;
|
||||
transform: rotate(135deg) !important;
|
||||
}
|
||||
|
||||
.tooltip:global(.place-bottom):after {
|
||||
margin-left: $arrow-inset !important;
|
||||
top: $arrow-inset !important;
|
||||
transform: rotate(-45deg) !important;
|
||||
}
|
210
src/components/action-menu/action-menu.jsx
Normal file
@ -0,0 +1,210 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
|
||||
import styles from './action-menu.css';
|
||||
|
||||
const CLOSE_DELAY = 300; // ms
|
||||
|
||||
class ActionMenu extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'clickDelayer',
|
||||
'handleClosePopover',
|
||||
'handleToggleOpenState',
|
||||
'handleTouchStart',
|
||||
'handleTouchOutside',
|
||||
'setButtonRef',
|
||||
'setContainerRef'
|
||||
]);
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
forceHide: false
|
||||
};
|
||||
this.mainTooltipId = `tooltip-${Math.random()}`;
|
||||
}
|
||||
componentDidMount () {
|
||||
// Touch start on the main button is caught to trigger open and not click
|
||||
this.buttonRef.addEventListener('touchstart', this.handleTouchStart);
|
||||
// Touch start on document is used to trigger close if it is outside
|
||||
document.addEventListener('touchstart', this.handleTouchOutside);
|
||||
}
|
||||
shouldComponentUpdate (newProps, newState) {
|
||||
// This check prevents re-rendering while the project is updating.
|
||||
// @todo check only the state and the title because it is enough to know
|
||||
// if anything substantial has changed
|
||||
// This is needed because of the sloppy way the props are passed as a new object,
|
||||
// which should be refactored.
|
||||
return newState.isOpen !== this.state.isOpen ||
|
||||
newState.forceHide !== this.state.forceHide ||
|
||||
newProps.title !== this.props.title;
|
||||
}
|
||||
componentWillUnmount () {
|
||||
this.buttonRef.removeEventListener('touchstart', this.handleTouchStart);
|
||||
document.removeEventListener('touchstart', this.handleTouchOutside);
|
||||
}
|
||||
handleClosePopover () {
|
||||
this.closeTimeoutId = setTimeout(() => {
|
||||
this.setState({isOpen: false});
|
||||
this.closeTimeoutId = null;
|
||||
}, CLOSE_DELAY);
|
||||
}
|
||||
handleToggleOpenState () {
|
||||
// Mouse enter back in after timeout was started prevents it from closing.
|
||||
if (this.closeTimeoutId) {
|
||||
clearTimeout(this.closeTimeoutId);
|
||||
this.closeTimeoutId = null;
|
||||
} else if (!this.state.isOpen) {
|
||||
this.setState({
|
||||
isOpen: true,
|
||||
forceHide: false
|
||||
});
|
||||
}
|
||||
}
|
||||
handleTouchOutside (e) {
|
||||
if (this.state.isOpen && !this.containerRef.contains(e.target)) {
|
||||
this.setState({isOpen: false});
|
||||
ReactTooltip.hide();
|
||||
}
|
||||
}
|
||||
clickDelayer (fn) {
|
||||
// Return a wrapped action that manages the menu closing.
|
||||
// @todo we may be able to use react-transition for this in the future
|
||||
// for now all this work is to ensure the menu closes BEFORE the
|
||||
// (possibly slow) action is started.
|
||||
return event => {
|
||||
ReactTooltip.hide();
|
||||
if (fn) fn(event);
|
||||
// Blur the button so it does not keep focus after being clicked
|
||||
// This prevents keyboard events from triggering the button
|
||||
this.buttonRef.blur();
|
||||
this.setState({forceHide: true, isOpen: false}, () => {
|
||||
setTimeout(() => this.setState({forceHide: false}));
|
||||
});
|
||||
};
|
||||
}
|
||||
handleTouchStart (e) {
|
||||
// Prevent this touch from becoming a click if menu is closed
|
||||
if (!this.state.isOpen) {
|
||||
e.preventDefault();
|
||||
this.handleToggleOpenState();
|
||||
}
|
||||
}
|
||||
setButtonRef (ref) {
|
||||
this.buttonRef = ref;
|
||||
}
|
||||
setContainerRef (ref) {
|
||||
this.containerRef = ref;
|
||||
}
|
||||
render () {
|
||||
const {
|
||||
className,
|
||||
img: mainImg,
|
||||
title: mainTitle,
|
||||
moreButtons,
|
||||
tooltipPlace,
|
||||
onClick
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.menuContainer, className, {
|
||||
[styles.expanded]: this.state.isOpen,
|
||||
[styles.forceHidden]: this.state.forceHide
|
||||
})}
|
||||
ref={this.setContainerRef}
|
||||
onMouseEnter={this.handleToggleOpenState}
|
||||
onMouseLeave={this.handleClosePopover}
|
||||
>
|
||||
<button
|
||||
aria-label={mainTitle}
|
||||
className={classNames(styles.button, styles.mainButton)}
|
||||
data-for={this.mainTooltipId}
|
||||
data-tip={mainTitle}
|
||||
ref={this.setButtonRef}
|
||||
onClick={this.clickDelayer(onClick)}
|
||||
>
|
||||
<img
|
||||
className={styles.mainIcon}
|
||||
draggable={false}
|
||||
src={mainImg}
|
||||
/>
|
||||
</button>
|
||||
<ReactTooltip
|
||||
className={styles.tooltip}
|
||||
effect="solid"
|
||||
id={this.mainTooltipId}
|
||||
place={tooltipPlace || 'left'}
|
||||
/>
|
||||
<div className={styles.moreButtonsOuter}>
|
||||
<div className={styles.moreButtons}>
|
||||
{(moreButtons || []).map(({img, title, onClick: handleClick,
|
||||
fileAccept, fileChange, fileInput, fileMultiple}, keyId) => {
|
||||
const isComingSoon = !handleClick;
|
||||
const hasFileInput = fileInput;
|
||||
const tooltipId = `${this.mainTooltipId}-${title}`;
|
||||
return (
|
||||
<div key={`${tooltipId}-${keyId}`}>
|
||||
<button
|
||||
aria-label={title}
|
||||
className={classNames(styles.button, styles.moreButton, {
|
||||
[styles.comingSoon]: isComingSoon
|
||||
})}
|
||||
data-for={tooltipId}
|
||||
data-tip={title}
|
||||
onClick={hasFileInput ? handleClick : this.clickDelayer(handleClick)}
|
||||
>
|
||||
<img
|
||||
className={styles.moreIcon}
|
||||
draggable={false}
|
||||
src={img}
|
||||
/>
|
||||
{hasFileInput ? (
|
||||
<input
|
||||
accept={fileAccept}
|
||||
className={styles.fileInput}
|
||||
multiple={fileMultiple}
|
||||
ref={fileInput}
|
||||
type="file"
|
||||
onChange={fileChange}
|
||||
/>) : null}
|
||||
</button>
|
||||
<ReactTooltip
|
||||
className={classNames(styles.tooltip, {
|
||||
[styles.comingSoonTooltip]: isComingSoon
|
||||
})}
|
||||
effect="solid"
|
||||
id={tooltipId}
|
||||
place={tooltipPlace || 'left'}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ActionMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
img: PropTypes.string,
|
||||
moreButtons: PropTypes.arrayOf(PropTypes.shape({
|
||||
img: PropTypes.string,
|
||||
title: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func, // Optional, "coming soon" if no callback provided
|
||||
fileAccept: PropTypes.string, // Optional, only for file upload
|
||||
fileChange: PropTypes.func, // Optional, only for file upload
|
||||
fileInput: PropTypes.func, // Optional, only for file upload
|
||||
fileMultiple: PropTypes.bool // Optional, only for file upload
|
||||
})),
|
||||
onClick: PropTypes.func.isRequired,
|
||||
title: PropTypes.node.isRequired,
|
||||
tooltipPlace: PropTypes.string
|
||||
};
|
||||
|
||||
export default ActionMenu;
|
BIN
src/components/action-menu/icon--backdrop.svg
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/components/action-menu/icon--camera.svg
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
src/components/action-menu/icon--file-upload.svg
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/components/action-menu/icon--paint.svg
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/components/action-menu/icon--search.svg
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/components/action-menu/icon--sprite.svg
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
src/components/action-menu/icon--surprise.svg
Normal file
After Width: | Height: | Size: 3.0 KiB |
104
src/components/alerts/alert.css
Normal file
@ -0,0 +1,104 @@
|
||||
@import "../../css/units.css";
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/z-index.css";
|
||||
|
||||
.alert {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
justify-content: flex-start;
|
||||
border-radius: $space;
|
||||
padding-top: .875rem;
|
||||
padding-bottom: .875rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
margin-bottom: 7px;
|
||||
min-height: 1.5rem;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.alert.warn {
|
||||
background: #FFF0DF;
|
||||
border: 1px solid #FF8C1A;
|
||||
box-shadow: 0px 0px 0px 2px rgba(255, 140, 26, 0.25);
|
||||
}
|
||||
|
||||
.alert.success {
|
||||
background: $extensions-light;
|
||||
border: 1px solid $extensions-tertiary;
|
||||
box-shadow: 0px 0px 0px 2px $extensions-light;
|
||||
}
|
||||
|
||||
.alert-spinner {
|
||||
self-align: center;
|
||||
}
|
||||
|
||||
.icon-section {
|
||||
min-width: 1.25rem;
|
||||
min-height: 1.25rem;
|
||||
display: flex;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
color: #555;
|
||||
font-weight: bold;
|
||||
font-size: .8125rem;
|
||||
line-height: .875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: .5rem;
|
||||
}
|
||||
|
||||
.alert-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.alert-close-button {
|
||||
outline-style:none;
|
||||
}
|
||||
|
||||
.alert-close-button-container {
|
||||
outline-style: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.alert-connection-button {
|
||||
min-height: 2rem;
|
||||
width: 6.5rem;
|
||||
padding: 0.55rem 0.9rem;
|
||||
border-radius: 0.35rem;
|
||||
background: #FF8C1A;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
font-size: 0.77rem;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
outline-style:none;
|
||||
}
|
||||
|
||||
[dir="ltr"] .alert-connection-button {
|
||||
margin-right: 13px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .alert-connection-button {
|
||||
margin-left: 13px;
|
||||
}
|
||||
|
||||
/* prevent last button in list from too much margin to edge of alert */
|
||||
.alert-buttons > :last-child {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
140
src/components/alerts/alert.jsx
Normal file
@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import CloseButton from '../close-button/close-button.jsx';
|
||||
import Spinner from '../spinner/spinner.jsx';
|
||||
import {AlertLevels} from '../../lib/alerts/index.jsx';
|
||||
|
||||
import styles from './alert.css';
|
||||
|
||||
const closeButtonColors = {
|
||||
[AlertLevels.SUCCESS]: CloseButton.COLOR_GREEN,
|
||||
[AlertLevels.WARN]: CloseButton.COLOR_ORANGE
|
||||
};
|
||||
|
||||
const AlertComponent = ({
|
||||
content,
|
||||
closeButton,
|
||||
extensionName,
|
||||
iconSpinner,
|
||||
iconURL,
|
||||
level,
|
||||
showDownload,
|
||||
showSaveNow,
|
||||
onCloseAlert,
|
||||
onDownload,
|
||||
onSaveNow,
|
||||
onReconnect,
|
||||
showReconnect
|
||||
}) => (
|
||||
<Box
|
||||
className={classNames(styles.alert, styles[level])}
|
||||
>
|
||||
{/* TODO: implement Rtl handling */}
|
||||
{(iconSpinner || iconURL) && (
|
||||
<div className={styles.iconSection}>
|
||||
{iconSpinner && (
|
||||
<Spinner
|
||||
className={styles.alertSpinner}
|
||||
level={level}
|
||||
/>
|
||||
)}
|
||||
{iconURL && (
|
||||
<img
|
||||
className={styles.alertIcon}
|
||||
src={iconURL}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.alertMessage}>
|
||||
{extensionName ? (
|
||||
<FormattedMessage
|
||||
defaultMessage="Scratch lost connection to {extensionName}."
|
||||
description="Message indicating that an extension peripheral has been disconnected"
|
||||
id="gui.alerts.lostPeripheralConnection"
|
||||
values={{
|
||||
extensionName: (
|
||||
`${extensionName}`
|
||||
)
|
||||
}}
|
||||
/>
|
||||
) : content}
|
||||
</div>
|
||||
<div className={styles.alertButtons}>
|
||||
{showSaveNow && (
|
||||
<button
|
||||
className={styles.alertConnectionButton}
|
||||
onClick={onSaveNow}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Try Again"
|
||||
description="Button to try saving again"
|
||||
id="gui.alerts.tryAgain"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{showDownload && (
|
||||
<button
|
||||
className={styles.alertConnectionButton}
|
||||
onClick={onDownload}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Download"
|
||||
description="Button to download project locally"
|
||||
id="gui.alerts.download"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{showReconnect && (
|
||||
<button
|
||||
className={styles.alertConnectionButton}
|
||||
onClick={onReconnect}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Reconnect"
|
||||
description="Button to reconnect the device"
|
||||
id="gui.connection.reconnect"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{closeButton && (
|
||||
<Box
|
||||
className={styles.alertCloseButtonContainer}
|
||||
>
|
||||
<CloseButton
|
||||
className={classNames(styles.alertCloseButton)}
|
||||
color={closeButtonColors[level]}
|
||||
size={CloseButton.SIZE_LARGE}
|
||||
onClick={onCloseAlert}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
|
||||
AlertComponent.propTypes = {
|
||||
closeButton: PropTypes.bool,
|
||||
content: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
|
||||
extensionName: PropTypes.string,
|
||||
iconSpinner: PropTypes.bool,
|
||||
iconURL: PropTypes.string,
|
||||
level: PropTypes.string,
|
||||
onCloseAlert: PropTypes.func.isRequired,
|
||||
onDownload: PropTypes.func,
|
||||
onReconnect: PropTypes.func,
|
||||
onSaveNow: PropTypes.func,
|
||||
showDownload: PropTypes.func,
|
||||
showReconnect: PropTypes.bool,
|
||||
showSaveNow: PropTypes.bool
|
||||
};
|
||||
|
||||
AlertComponent.defaultProps = {
|
||||
level: AlertLevels.WARN
|
||||
};
|
||||
|
||||
export default AlertComponent;
|
4
src/components/alerts/alerts.css
Normal file
@ -0,0 +1,4 @@
|
||||
.alerts-inner-container {
|
||||
min-width: 200px;
|
||||
max-width: 548px;
|
||||
}
|
47
src/components/alerts/alerts.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import Alert from '../../containers/alert.jsx';
|
||||
|
||||
import styles from './alerts.css';
|
||||
|
||||
const AlertsComponent = ({
|
||||
alertsList,
|
||||
className,
|
||||
onCloseAlert
|
||||
}) => (
|
||||
<Box
|
||||
bounds="parent"
|
||||
className={className}
|
||||
>
|
||||
<Box className={styles.alertsInnerContainer} >
|
||||
{alertsList.map((a, index) => (
|
||||
<Alert
|
||||
closeButton={a.closeButton}
|
||||
content={a.content}
|
||||
extensionId={a.extensionId}
|
||||
extensionName={a.extensionName}
|
||||
iconSpinner={a.iconSpinner}
|
||||
iconURL={a.iconURL}
|
||||
index={index}
|
||||
key={index}
|
||||
level={a.level}
|
||||
message={a.message}
|
||||
showDownload={a.showDownload}
|
||||
showReconnect={a.showReconnect}
|
||||
showSaveNow={a.showSaveNow}
|
||||
onCloseAlert={onCloseAlert}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
AlertsComponent.propTypes = {
|
||||
alertsList: PropTypes.arrayOf(PropTypes.object),
|
||||
className: PropTypes.string,
|
||||
onCloseAlert: PropTypes.func
|
||||
};
|
||||
|
||||
export default AlertsComponent;
|
27
src/components/alerts/inline-message.css
Normal file
@ -0,0 +1,27 @@
|
||||
@import "../../css/colors.css";
|
||||
@import "../../css/units.css";
|
||||
|
||||
.inline-message {
|
||||
color: $ui-white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
font-size: .8125rem;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: $ui-white-dim;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: $ui-white;
|
||||
}
|
||||
|
||||
.warn {
|
||||
color: $error-light;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin-right: $space;
|
||||
}
|
40
src/components/alerts/inline-message.jsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Spinner from '../spinner/spinner.jsx';
|
||||
import {AlertLevels} from '../../lib/alerts/index.jsx';
|
||||
|
||||
import styles from './inline-message.css';
|
||||
|
||||
const InlineMessageComponent = ({
|
||||
content,
|
||||
iconSpinner,
|
||||
level
|
||||
}) => (
|
||||
<div
|
||||
className={classNames(styles.inlineMessage, styles[level])}
|
||||
>
|
||||
{/* TODO: implement Rtl handling */}
|
||||
{iconSpinner && (
|
||||
<Spinner
|
||||
small
|
||||
className={styles.spinner}
|
||||
level={'info'}
|
||||
/>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
|
||||
InlineMessageComponent.propTypes = {
|
||||
content: PropTypes.element,
|
||||
iconSpinner: PropTypes.bool,
|
||||
level: PropTypes.string
|
||||
};
|
||||
|
||||
InlineMessageComponent.defaultProps = {
|
||||
level: AlertLevels.INFO
|
||||
};
|
||||
|
||||
export default InlineMessageComponent;
|
36
src/components/asset-panel/asset-panel.css
Normal file
@ -0,0 +1,36 @@
|
||||
@import "../../css/units.css";
|
||||
@import "../../css/colors.css";
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
border: 1px solid $ui-black-transparent;
|
||||
background: white;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
[dir="ltr"] .wrapper {
|
||||
border-top-right-radius: $space;
|
||||
border-bottom-right-radius: $space;
|
||||
}
|
||||
|
||||
[dir="rtl"] .wrapper {
|
||||
border-top-left-radius: $space;
|
||||
border-bottom-left-radius: $space;
|
||||
}
|
||||
|
||||
.detail-area {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
[dir="ltr"] .detail-area {
|
||||
border-left: 1px solid $ui-black-transparent;
|
||||
}
|
||||
|
||||
[dir="rtl"] .detail-area {
|
||||
border-right: 1px solid $ui-black-transparent;
|
||||
}
|
23
src/components/asset-panel/asset-panel.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
|
||||
import Box from '../box/box.jsx';
|
||||
import Selector from './selector.jsx';
|
||||
import styles from './asset-panel.css';
|
||||
|
||||
const AssetPanel = props => (
|
||||
<Box className={styles.wrapper}>
|
||||
<Selector
|
||||
className={styles.selector}
|
||||
{...props}
|
||||
/>
|
||||
<Box className={styles.detailArea}>
|
||||
{props.children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
AssetPanel.propTypes = {
|
||||
...Selector.propTypes
|
||||
};
|
||||
|
||||
export default AssetPanel;
|