├── .gitignore
├── README.md
├── README_CN.md
├── config-overrides.js
├── package.json
├── public
├── favicon.ico
├── index.html
├── manifest.json
└── media
│ └── icons
│ ├── arrow.svg
│ ├── arrow_button.svg
│ ├── control_forever.svg
│ ├── control_repeat.svg
│ ├── control_stop.svg
│ ├── control_wait.svg
│ ├── event_broadcast_blue.svg
│ ├── event_broadcast_coral.svg
│ ├── event_broadcast_green.svg
│ ├── event_broadcast_magenta.svg
│ ├── event_broadcast_orange.svg
│ ├── event_broadcast_purple.svg
│ ├── event_when-broadcast-received_blue.svg
│ ├── event_when-broadcast-received_coral.svg
│ ├── event_when-broadcast-received_green.svg
│ ├── event_when-broadcast-received_magenta.svg
│ ├── event_when-broadcast-received_orange.svg
│ ├── event_when-broadcast-received_purple.svg
│ ├── event_whenflagclicked.svg
│ ├── remove.svg
│ ├── set-led_blue.svg
│ ├── set-led_coral.svg
│ ├── set-led_green.svg
│ ├── set-led_magenta.svg
│ ├── set-led_mystery.svg
│ ├── set-led_orange.svg
│ ├── set-led_purple.svg
│ ├── set-led_white.svg
│ ├── set-led_yellow.svg
│ ├── wedo_motor-clockwise.svg
│ ├── wedo_motor-counterclockwise.svg
│ ├── wedo_motor-speed_fast.svg
│ ├── wedo_motor-speed_med.svg
│ ├── wedo_motor-speed_slow.svg
│ ├── wedo_when-distance_close.svg
│ ├── wedo_when-tilt-backward.svg
│ ├── wedo_when-tilt-forward.svg
│ ├── wedo_when-tilt-left.svg
│ ├── wedo_when-tilt-right.svg
│ └── wedo_when-tilt.svg
└── src
├── App.css
├── App.js
├── App.test.js
├── BlockEditor.js
├── CodeBuilder.js
├── arduino.png
├── index.css
├── index.js
├── logo.svg
├── micropy.png
├── python.png
├── s3ext.png
└── serviceWorker.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Here is [Scratch3](https://scratch.mit.edu) extension generator based on [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | 
4 |
5 |
6 | ## The Scratch3 extension generator
7 |
8 | As scratch3 gets better and better, we find that many users want to implement their own plugins. But writing a plugin for scratch3 is not a simple matter, it requires a solid JavaScript development capability; for professional JavaScript programmers, writing a scratch3 plugin is just tedious and waste of time. The purpose of this webapp is to let you complete your scratch3 plugin framework code in 10 minutes. Hope you guys like it~
9 |
10 | [中文使用说明](./README_CN.md)
11 |
12 | ## Instructions
13 |
14 | ### Step1
15 |
16 | Open:[https://kittenbot.github.io/scratch3-extension/](https://kittenbot.github.io/scratch3-extension/)
17 |
18 | ### Step2
19 |
20 | Give your extension a name and ID. Note that the extension ID needs to be in ASCII characters and cannot contain spaces or special strings. The plugin ID should be unique in the runtime context of scratch3.
21 |
22 | You can also choose prefered color for your extension.
23 |
24 | 
25 |
26 | ### Step3
27 |
28 | Give your extension an icon. The icon is recommended to use less than 200x200 pixels square png or svg images.
29 |
30 | 
31 |
32 | ### Step4
33 |
34 | Then we will create a block, click on the `Add Function Block`. Then click on the `Add Text Variable` in the pop-up modal box and name the variable to `WORD`. Note that the variable name needs to be ASCII letters, and cannot contain special characters, and recommend all uppercase.
35 |
36 | Finally we have to change our block ID, the block ID needs to be unique in the current plugin. Here we will name the ID to `sayhello`.
37 |
38 | - Note that the **extension ID** also needs to be an ASCII string and cannot contain a special characters.
39 |
40 | 
41 |
42 | ### Step5
43 |
44 | You can click on the `Generate Preview` in the upper right corner to see the effect of our plugin loading in scratch3.
45 |
46 | 
47 |
48 |
49 | ### Step6
50 |
51 | Finally click on the `export index.js` in the lower right corner to export the plugin source code.
52 |
53 | For standard scratch3, please load index.js into the extension of `scratch-vm`.
54 |
55 | -------------------
56 |
57 | The following steps are only valid for Kittenblock. You can download the latest Kittenblock at [https://www.kittenbot.cn/software/] (https://www.kittenbot.cn/software/).
58 |
59 | ### Step7
60 |
61 | Please create a extension folder in the `extensions` directory of the Kittenblock root directory. Here we will name it `sayhello`. Copy the generated `index.js` to this directory.
62 |
63 | 
64 |
65 | ### Step8
66 |
67 | Create a file named `extension.json` under the changed folder, which contains the following contents:
68 |
69 | {
70 | "name": "Say Hello",
71 | "type": "scratch3",
72 | "image": "logo.png"
73 | }
74 |
75 | Then find a picture you like as the main image of the extension, name it `logo.png`, and put it in the folder. Finally, our `sayhello` folder has the following three files.
76 |
77 | 
78 |
79 | ### Step9
80 |
81 | Open Kittenblock and select `Load External Plugin` in the lower left corner then find the plugin we just added.
82 |
83 | 
84 |
85 | The final effect
86 |
87 | 
88 |
89 | -------------------
90 |
91 | You only need to change the corresponding block execution code in `index.js` to implement the function of a block.
92 |
93 | Please keep tuned to our upcoming updates.
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | 这是[Scratch3](https://scratch.mit.edu) 的插件生成器webapp, 基于[Create React App](https://github.com/facebook/create-react-app)开发,请访问[https://kittenbot.github.io/scratch3-extension/](https://kittenbot.github.io/scratch3-extension/)使用.
2 |
3 | 
4 |
5 |
6 | ## Scratch3插件生成器
7 |
8 | 随着scratch3越来越完善,我们发现很多用户都想自己实现自己的插件。但是编写scratch3的插件并不是一件简单的事情,这需要比较扎实的JavaScript开发能力;而对于专业的JavaScript程序员来说,写scratch3插件又很浪费时间。这个webapp的目的就是可以让你在10分钟内完成自己的scratch3插件框架代码。希望大家喜欢~
9 |
10 | ## 使用方法
11 |
12 | ### Step1
13 |
14 | 打开网页:[https://kittenbot.github.io/scratch3-extension/](https://kittenbot.github.io/scratch3-extension/)
15 |
16 | ### Step2
17 |
18 | 给你的插件取名字和ID。注意插件ID需要全英文,并且不能包含空格和特殊字符串。插件ID在scratch3的运行环境中全局唯一。
19 |
20 | 之后可以给你的插件选取喜欢的颜色。
21 |
22 | 
23 |
24 | ### Step3
25 |
26 | 给插件选择你喜欢的图标,图标建议使用200x200像素以内的正方形png或svg图片。
27 |
28 | 
29 |
30 | ### Step4
31 |
32 | 之后我们来新建一个积木块,点击`添加函数方块`. 之后在弹出的模态框中点击`添加文字变量`,并修改变量的名字为`WORD`. 注意变量的名字需要为英文字母,并且不能包含特殊字符串, 并推荐全部大写.
33 |
34 | 最后我们还要更改我们的积木块ID, 积木块ID需要在当前插件中全局唯一. 这里我们将插件ID命名为`sayhello`.
35 |
36 | - 注意**插件ID**同样需要为英文字母,并且不能包含特殊字符串
37 |
38 | 
39 |
40 | ### Step5
41 |
42 | 大家可以点击右上角的`生成预览`查看我们插件在scratch3中加载的效果。
43 |
44 | 
45 |
46 |
47 | ### Step6
48 |
49 | 最后点击右下角的`export index.js`导出插件源代码.
50 |
51 | 对于标准的scratch3请将index.js加载到scratch-vm的extension中就行了.
52 |
53 | -------------------
54 |
55 | 以下步骤只对Kittenblock有效, 大家可以前往[https://www.kittenbot.cn/software/](https://www.kittenbot.cn/software/)下载最新的Kittenblock.
56 |
57 | ### Step7
58 |
59 | 请到Kittenblock的安装目录的`extension`目录下新建一个插件的文件夹,这里我们命名为`sayhello`. 并将刚刚生成的`index.js`拷贝到该目录下.
60 |
61 | 
62 |
63 | ### Step8
64 |
65 | 在该文件夹下面建立一个名为`extension.json`的文件,里面放入如下的内容:
66 |
67 | {
68 | "name": "Say Hello",
69 | "type": "scratch3",
70 | "image": "logo.png"
71 | }
72 |
73 | 之后找一张你喜欢的图片作为插件的主图片,命名为`logo.png`,并放入该文件夹下. 最后我们的`sayhello`文件夹下有如下三个文件.
74 |
75 | 
76 |
77 | ### Step9
78 |
79 | 打开Kittenblock,并在左下角选择加载外部插件,可以找到我们刚刚加入的插件。
80 |
81 | 
82 |
83 | 最终效果
84 |
85 | 
86 |
87 | -------------------
88 |
89 | 大家只需要在`index.js`中更改对应的积木执行代码就能实现具体积木的功能了。
90 |
91 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | /* config-overrides.js */
2 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
3 |
4 | module.exports = function override(config, env) {
5 | if (!config.plugins) {
6 | config.plugins = [];
7 | }
8 | config.plugins.push(
9 | new MonacoWebpackPlugin()
10 | );
11 | return config;
12 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "s3ext-scaffold",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "https://kittenbot.github.io/scratch3-extension",
6 | "dependencies": {
7 | "antd": "^3.13.6",
8 | "lodash.bindall": "^4.4.0",
9 | "react": "^16.8.3",
10 | "react-color": "^2.17.0",
11 | "react-dom": "^16.8.3",
12 | "react-localization": "^1.0.13",
13 | "react-monaco-editor": "^0.25.1",
14 | "react-scripts": "2.1.5",
15 | "scratch-blocks": "0.1.0-prerelease.1549990124"
16 | },
17 | "scripts": {
18 | "start": "react-app-rewired start",
19 | "build": "react-app-rewired build",
20 | "test": "react-app-rewired test",
21 | "eject": "react-scripts eject",
22 | "deploy": "gh-pages -d build"
23 | },
24 | "eslintConfig": {
25 | "extends": "react-app"
26 | },
27 | "browserslist": [
28 | ">0.2%",
29 | "not dead",
30 | "not ie <= 11",
31 | "not op_mini all"
32 | ],
33 | "devDependencies": {
34 | "gh-pages": "^2.0.1",
35 | "monaco-editor-webpack-plugin": "^1.7.0",
36 | "react-app-rewired": "^2.1.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KittenBot/scratch3-extension/51e0079ac70dc979f03105001e5e04268459b06e/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
15 |
16 |
25 | Scratch3 Extension Generator
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Scratch3Ext",
3 | "name": "Create Scratch3 extension in an easy way",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/media/icons/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/public/media/icons/arrow_button.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/control_forever.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/control_repeat.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/control_stop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/control_wait.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_broadcast_blue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_broadcast_coral.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_broadcast_green.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_broadcast_magenta.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_broadcast_orange.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_broadcast_purple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_when-broadcast-received_blue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_when-broadcast-received_coral.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_when-broadcast-received_green.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_when-broadcast-received_magenta.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_when-broadcast-received_orange.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_when-broadcast-received_purple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/event_whenflagclicked.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/remove.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_blue.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_coral.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_green.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_magenta.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_mystery.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_orange.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_purple.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_white.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/set-led_yellow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_motor-clockwise.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_motor-counterclockwise.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_motor-speed_fast.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_motor-speed_med.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_motor-speed_slow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_when-distance_close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_when-tilt-backward.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_when-tilt-forward.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_when-tilt-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_when-tilt-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/media/icons/wedo_when-tilt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 |
3 | .App {
4 | text-align: center;
5 | }
6 |
7 | .App-logo {
8 | animation: App-logo-spin infinite 20s linear;
9 | height: 40vmin;
10 | pointer-events: none;
11 | }
12 |
13 | .App-header {
14 | background-color: #282c34;
15 | min-height: 100vh;
16 | display: flex;
17 | flex-direction: column;
18 | align-items: center;
19 | justify-content: center;
20 | font-size: calc(10px + 2vmin);
21 | color: white;
22 | }
23 |
24 | .App-link {
25 | color: #61dafb;
26 | }
27 |
28 | @keyframes App-logo-spin {
29 | from {
30 | transform: rotate(0deg);
31 | }
32 | to {
33 | transform: rotate(360deg);
34 | }
35 | }
36 |
37 | .radio-img {
38 | width: 40px;
39 | }
40 |
41 | .trigger {
42 | font-size: 18px;
43 | line-height: 64px;
44 | padding: 0 24px;
45 | cursor: pointer;
46 | transition: color .3s;
47 | }
48 |
49 | .trigger:hover {
50 | color: #1890ff;
51 | }
52 |
53 | .logo {
54 | height: 32px;
55 | margin: 16px;
56 | }
57 |
58 | .icon-img {
59 | max-width: 100px;
60 | margin: 10px;
61 | }
62 |
63 | .color-display{
64 | width: 30px;
65 | height: 30px;
66 | border-radius: 15px;
67 | border: 1px #5B5B5B solid
68 | }
69 |
70 | .color-cover{
71 | position: fixed;
72 | top: 0px;
73 | right: 0px;
74 | bottom: 0px;
75 | left: 0px;
76 | }
77 |
78 | .config-row {
79 | margin: 10px;
80 | }
81 |
82 | .btn-wrap {
83 | margin-top: 5px;
84 | }
85 |
86 | .btn-wrap > * {
87 | margin-left: 5px;
88 | }
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import bindAll from 'lodash.bindall';
2 | import LocalizedStrings from 'react-localization';
3 | import { Checkbox, Row, Col, Button, Layout, Icon, Menu , Divider, Table, Radio, Popconfirm, Input, Modal, Upload, Tooltip, message } from 'antd';
4 | import React, { Component } from 'react';
5 | import Blockly from 'scratch-blocks';
6 |
7 | import { SketchPicker } from 'react-color';
8 | import logo from './s3ext.png';
9 | import './App.css';
10 | import {BlockScriptEditor, CodePreview, BlockGeneratorEditor} from './BlockEditor';
11 | import { string } from 'postcss-selector-parser';
12 |
13 | import micropyImg from './micropy.png';
14 | import arduinoImg from './arduino.png';
15 | import pythonImg from './python.png';
16 |
17 | import {buildJsCode, buildBlockOp,
18 | buildBlockGenCpp, buildBlockGenMpy,
19 | buildEmptyHeadCpp, buildEmptyHeadMpy
20 | } from './CodeBuilder';
21 |
22 | const { SubMenu } = Menu;
23 | const { Header, Content, Footer, Sider } = Layout;
24 | const RadioGroup = Radio.Group;
25 |
26 | let strings = new LocalizedStrings({
27 | en:{
28 | extID: "Extension ID",
29 | extName: "Extension Name",
30 | preview: "Generate Preview",
31 | extdef: "Extension Define",
32 | generator: "Blocks to Code",
33 | maincolor: "Extension Color",
34 | secondcolor: "Parameter Color",
35 | menuIcon: "Menu Icon",
36 | blockIcon: "Block Icon",
37 | addLabel: "Add Label",
38 | addInput: "Add String Parameter",
39 | addInputNum: "Add Number Parameter",
40 | addBool: "Add Boolean Parameter",
41 | addblock: "Add Blocks",
42 | addBlockFun: "Add Functional Block",
43 | addBlockOutput: "Add Output Block",
44 | addBlockBool: "Add Boolean Block",
45 | addBlockHat: "Add Hat Block",
46 | delSure: "delete this block?",
47 | uniqBlockId: "* block ID should be unique",
48 | uniqBlockName: "* block parameter names should be unique",
49 | genHeader: "Edit Header",
50 | promptBlkID: "Please Enter Block ID"
51 | },
52 | zh: {
53 | extID: "插件ID",
54 | extName: "插件名称",
55 | preview: "生成预览",
56 | extdef: "插件定义",
57 | generator: "图形化转代码",
58 | maincolor: "插件颜色",
59 | secondcolor: "变量颜色",
60 | menuIcon: "菜单栏图标",
61 | blockIcon: "方块图标",
62 | addLabel: "添加文本",
63 | addInput: "添加文本变量",
64 | addInputNum: "添加数字变量",
65 | addBool: "添加布尔变量",
66 | addblock: "添加方块",
67 | addBlockFun: "添加函数方块",
68 | addBlockOutput: "添加输出方块",
69 | addBlockBool: "添加布尔方块",
70 | addBlockHat: "添加帽子方块",
71 | delSure: "删除该方块?",
72 | uniqBlockId: "* 积木ID需要全局唯一",
73 | uniqBlockName: "* 积木参数名字需要唯一",
74 | genHeader: "编辑头文件",
75 | promptBlkID: "请输入方块ID"
76 | }
77 | });
78 |
79 | const emptyToolBox = `
80 |
81 |
82 | `;
83 |
84 | const OUTPUT_SHAPE_HEXAGONAL = 1;
85 | const OUTPUT_SHAPE_ROUND = 2;
86 | const OUTPUT_SHAPE_SQUARE = 3;
87 |
88 |
89 | const extOption = [
90 | { label:
Arduino, value: 'arduino' },
91 | { label:
Micro Python, value: 'micropython' }
92 | ];
93 |
94 | class App extends Component {
95 | constructor (props){
96 | super(props);
97 | this.state = {
98 | collapsed: true,
99 | extID: 'testExt',
100 | extName: 'Test',
101 | color1Pick: false,
102 | color2Pick: false,
103 | color1: '#0FBD8C',
104 | color2: '#0DA57A',
105 | indexJS: null,
106 | menuIcon: null,
107 | blockIcon: null,
108 | editBlockID: 'newblock',
109 | blocks: [],
110 | menus: [],
111 | addBlockType: '',
112 | showMutation: false,
113 | blockScript: null,
114 | genOption: [],
115 | genHeadScript: null,
116 | blockGenerator: null,
117 | isShowCodePreview: false
118 | }
119 | bindAll(this, [
120 | "uploadMenuIcon",
121 | "uploadBlockIcon",
122 | "closeMutationModal",
123 | "generatePreview",
124 | "addBlockFun",
125 | "addBlockOutput",
126 | "addBlockBool",
127 | "addBlockHat",
128 | "addLabel",
129 | "addInput",
130 | "addInputNum",
131 | "addBool",
132 | "applyMutation",
133 | "injectDeclareWorkspace",
134 | "makeBlock",
135 | "editBlock",
136 | "deleteBlock",
137 | "saveToJson",
138 | "generateIndexJS",
139 | "loadFromJson",
140 | "exportJs",
141 | "editBlockScript",
142 | "editGeneratorHead",
143 | "editBlockGenerator",
144 | "onExtoptionChange"
145 | ]);
146 |
147 | this.blockColumn = [{
148 | title: 'Op Code',
149 | dataIndex: 'opcode',
150 | key: 'opcode',
151 | width: '20%',
152 | render: text => {text},
153 | }, {
154 | title: 'Preview',
155 | dataIndex: 'svg',
156 | key: 'svg',
157 | render: (text, record) => (
158 |
159 | )
160 | }, , {
161 | title: 'block op',
162 | key: 'blockop',
163 | render: (text, record) => (
164 |
165 | this.editBlockScript(record.opcode)} >
166 |
167 |
168 |
169 |
170 |
171 | this.editBlockGenerator(record.opcode)} >
172 |
173 |
174 |
175 |
176 |
177 | )
178 | }, {
179 | title: 'Action',
180 | key: 'action',
181 | render: (text, record) => (
182 |
183 | this.editBlock(record.opcode)} >Edit {record.name}
184 |
185 | this.deleteBlock(record.opcode)}>
186 | Delete
187 |
188 |
189 | ),
190 | }];
191 | }
192 |
193 | componentDidMount (){
194 | this.previewWorkspace = Blockly.inject('preview', {
195 | media: './media/',
196 | toolbox: emptyToolBox,
197 | zoom: {
198 | startScale: 0.75
199 | }
200 | });
201 |
202 | Blockly.Procedures.externalProcedureDefCallback = function (mutation, cb) {
203 | console.log("externalProcedureDefCallback");
204 | }
205 | this.previewWorkspace.getFlyout().setRecyclingEnabled(false);
206 | window.ws = this.previewWorkspace;
207 | }
208 |
209 | onExtoptionChange (opt){
210 | this.setState({
211 | genOption: opt
212 | })
213 | }
214 |
215 | uploadMenuIcon (file){
216 | let reader = new FileReader();
217 | const _this = this;
218 | reader.onerror = function () {
219 | console.warn("read image file error")
220 | };
221 |
222 | reader.onload = function (ev) {
223 | const dataUri = reader.result;
224 | _this.setState({menuIcon: dataUri});
225 | };
226 | reader.readAsDataURL(file);
227 | }
228 |
229 | uploadBlockIcon (file){
230 | let reader = new FileReader();
231 | const _this = this;
232 | reader.onerror = function () {
233 | console.warn("read image file error")
234 | };
235 |
236 | reader.onload = function (ev) {
237 | const dataUri = reader.result;
238 | _this.setState({blockIcon: dataUri});
239 | };
240 | reader.readAsDataURL(file);
241 | }
242 |
243 | generatePreview (){
244 | const xmlParts = [];
245 | this.previewWorkspace.clear();
246 |
247 | const colorXML = `colour="${this.state.color1}" secondaryColour="${this.state.color2}"`;
248 | let menuIconURI = '';
249 | if (this.state.menuIcon) {
250 | menuIconURI = this.state.menuIcon;
251 | } else if (this.state.blockIcon) {
252 | menuIconURI = this.state.blockIcon;
253 | }
254 | const blockJsons = [];
255 | const menuIconXML = menuIconURI ?
256 | `iconURI="${menuIconURI}"` : '';
257 | xmlParts.push(``);
258 | xmlParts.push(``);
259 | xmlParts.push.apply(xmlParts, this.state.blocks.map(block => {
260 | const extendedOpcode = `${this.state.extID}_${block.opcode}`;
261 | let argIndex = 0;
262 | const blockJSON = {
263 | type: extendedOpcode,
264 | category: this.state.extName,
265 | colour: this.state.color1,
266 | inputsInline: true,
267 | colourSecondary: this.state.color2,
268 | extensions: ['scratch_extension']
269 | };
270 | const iconURI = this.state.blockIcon;
271 |
272 | if (iconURI) {
273 | blockJSON.message0 = '%1 %2';
274 | const iconJSON = {
275 | type: 'field_image',
276 | src: iconURI,
277 | width: 40,
278 | height: 40
279 | };
280 | const separatorJSON = {
281 | type: 'field_vertical_separator'
282 | };
283 | blockJSON.args0 = [
284 | iconJSON,
285 | separatorJSON
286 | ];
287 | argIndex+=1;
288 | }
289 |
290 | blockJSON[`message${argIndex}`] = block.msg;
291 | blockJSON[`args${argIndex}`] = block.args.map(arg => arg.json);
292 |
293 |
294 | if (block.type === 'func'){
295 | blockJSON.outputShape = OUTPUT_SHAPE_SQUARE;
296 | blockJSON.nextStatement = null;
297 | blockJSON.previousStatement = null;
298 | } else if (block.type === 'output'){
299 | blockJSON.outputShape = OUTPUT_SHAPE_ROUND;
300 | blockJSON.output = "String";
301 | blockJSON.checkboxInFlyout = true;
302 | } else if (block.type === 'bool'){
303 | blockJSON.output = "Boolean";
304 | blockJSON.outputShape = OUTPUT_SHAPE_HEXAGONAL;
305 | } else if (block.type === 'hat'){
306 | blockJSON.outputShape = OUTPUT_SHAPE_SQUARE;
307 | blockJSON.nextStatement = null;
308 | blockJSON.previousStatement = undefined; // hack to hat module
309 | }
310 |
311 | blockJsons.push(blockJSON);
312 | const inputXML = block.args.map(arg => {
313 | const inputList = [];
314 | const placeholder = arg.placeholder.replace(/[<"&]/, '_');
315 | const shadowType = arg.shadowType;
316 | const fieldType = arg.fieldType;
317 | const defaultValue = arg.defaultValue || '';
318 | inputList.push(``);
319 | if (shadowType) {
320 | inputList.push(``);
321 | inputList.push(`${defaultValue}`);
322 | inputList.push('');
323 | }
324 | inputList.push('');
325 |
326 | return inputList.join('');
327 | });
328 | let blockXML = `${inputXML.join('')}`;
329 | return blockXML;
330 | }));
331 | xmlParts.push('');
332 | xmlParts.push(``);
333 | Blockly.defineBlocksWithJsonArray(blockJsons);
334 | console.log("extension", xmlParts);
335 | this.previewWorkspace.updateToolbox(xmlParts.join('\n'));
336 | }
337 |
338 | closeMutationModal (){
339 | this.declareWorkspace.clear();
340 | this.setState({showMutation: false})
341 | }
342 |
343 | makeBlock (blockType, mutationText){
344 | this.mutationRoot = this.declareWorkspace.newBlock('procedures_declaration');
345 | // this.mutationRoot.setMovable(false);
346 | this.mutationRoot.setDeletable(false);
347 | this.mutationRoot.contextMenu = false;
348 |
349 | // override default custom procedure insert
350 | this.mutationRoot.addStringNumberExternal = function(isNum) {
351 | Blockly.WidgetDiv.hide(true);
352 | if (isNum){
353 | this.procCode_ = this.procCode_ + ' %n';
354 | this.displayNames_.push('X');
355 | } else {
356 | this.procCode_ = this.procCode_ + ' %s';
357 | this.displayNames_.push('TXT');
358 | }
359 | this.argumentIds_.push(Blockly.utils.genUid());
360 | this.argumentDefaults_.push('');
361 | this.updateDisplay_();
362 | this.focusLastEditor_();
363 | };
364 |
365 | // this.mutationRoot.domToMutation(this.props.mutator);
366 | if (!mutationText){
367 | mutationText = '' +
368 | '' +
374 | '' +
375 | '';
376 | }
377 | const dom = Blockly.Xml.textToDom(mutationText).firstChild;
378 | this.mutationRoot.domToMutation(dom);
379 | this.mutationRoot.initSvg();
380 | this.mutationRoot.render();
381 | if (blockType === 'bool' || blockType === 'output'){
382 | this.mutationRoot.setPreviousStatement(false, null);
383 | this.mutationRoot.setNextStatement(false, null);
384 | this.mutationRoot.setInputsInline(true);
385 | if (blockType === 'output'){
386 | this.mutationRoot.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND);
387 | this.mutationRoot.setOutput(true, 'Boolean');
388 | } else {
389 | this.mutationRoot.setOutputShape(Blockly.OUTPUT_SHAPE_HEXAGONAL);
390 | this.mutationRoot.setOutput(true, 'Number');
391 | }
392 | } else if (blockType === 'hat') {
393 | this.mutationRoot.setPreviousStatement(undefined, null);
394 | this.mutationRoot.setNextStatement(true, null);
395 | this.mutationRoot.setInputsInline(true);
396 | }
397 | const {x, y} = this.mutationRoot.getRelativeToSurfaceXY();
398 | const dy = (360 / 2) - (this.mutationRoot.height / 2) - y;
399 | const dx = (480 / 2) - (this.mutationRoot.width / 2) - x;
400 | this.mutationRoot.moveBy(dx, dy);
401 | window.mu = this.mutationRoot;
402 | }
403 |
404 | injectDeclareWorkspace (ref){
405 | this.blocks = ref;
406 | const oldDefaultToolbox = Blockly.Blocks.defaultToolbox;
407 | Blockly.Blocks.defaultToolbox = null;
408 | this.declareWorkspace = Blockly.inject('declare', {
409 | media: './media/'
410 | });
411 | Blockly.Blocks.defaultToolbox = oldDefaultToolbox;
412 |
413 | const _this = this;
414 | this.declareWorkspace.addChangeListener(function(evt) {
415 | // console.log(Object.getPrototypeOf(evt).type, evt);
416 | if (_this.mutationRoot) {
417 | // todo: blockly turn %n to %s in updateDeclarationProcCode_
418 | _this.mutationRoot.onChangeFn();
419 | }
420 | });
421 | this.makeBlock(this.state.addBlockType);
422 | }
423 |
424 | applyMutation (){
425 | const svg = this.mutationRoot.getSvgRoot();
426 | const bbox = svg.getBBox();
427 | svg.removeAttribute('transform');
428 | let xml = (new XMLSerializer).serializeToString(svg);
429 | xml = ``;
432 |
433 | const mutation = this.mutationRoot.mutationToDom(true)
434 | // console.log(mutation);
435 | const argNames = JSON.parse(mutation.getAttribute('argumentnames'));
436 | const args = [];
437 |
438 | // parse proc code
439 | let argCnt = 0;
440 | const args0 = [];
441 | let proccode = this.mutationRoot.getProcCode();
442 | proccode = proccode.split(" ");
443 | for (let n=0; n${Blockly.Xml.domToText(mutation)}`;
487 | console.log("mutationText >>", mutationText);
488 | const newBlock = {
489 | opcode: this.state.editBlockID,
490 | svg: xml,
491 | msg,
492 | args,
493 | mutationText: mutationText,
494 | type: this.state.addBlockType
495 | };
496 | const blocks = [...this.state.blocks].filter(blk => blk.opcode !== this.state.editBlockID);
497 | blocks.push(newBlock);
498 |
499 | this.setState({
500 | showMutation: false,
501 | blocks: blocks
502 | });
503 | }
504 |
505 | addLabel (){
506 | this.mutationRoot.addLabelExternal();
507 | }
508 | addInput (){
509 | this.mutationRoot.addStringNumberExternal();
510 | }
511 | addInputNum (){
512 | this.mutationRoot.addStringNumberExternal(true);
513 | }
514 | addBool (){
515 | this.mutationRoot.addBooleanExternal();
516 | }
517 | addBlockFun (){
518 | const blkid = prompt(strings.promptBlkID);
519 | if (!blkid || blkid.length == 0) return;
520 | this.setState({
521 | editBlockID: blkid,
522 | showMutation: true,
523 | addBlockType: 'func',
524 | });
525 | if (this.declareWorkspace){
526 | this.declareWorkspace.clear();
527 | this.makeBlock();
528 | }
529 |
530 | }
531 |
532 | addBlockOutput (){
533 | const blkid = prompt(strings.promptBlkID);
534 | if (!blkid || blkid.length == 0) return;
535 | this.setState({
536 | editBlockID: blkid,
537 | addBlockType: 'output',
538 | showMutation: true
539 | });
540 | if (this.declareWorkspace){
541 | this.declareWorkspace.clear();
542 | this.makeBlock('output');
543 | }
544 | }
545 |
546 | addBlockBool (){
547 | const blkid = prompt(strings.promptBlkID);
548 | if (!blkid || blkid.length == 0) return;
549 | this.setState({
550 | editBlockID: blkid,
551 | addBlockType: 'bool',
552 | showMutation: true
553 | });
554 | if (this.declareWorkspace){
555 | this.declareWorkspace.clear();
556 | this.makeBlock('bool');
557 |
558 | }
559 |
560 | }
561 |
562 | addBlockHat (){
563 | const blkid = prompt(strings.promptBlkID);
564 | if (!blkid || blkid.length == 0) return;
565 | this.setState({
566 | editBlockID: blkid,
567 | addBlockType: 'hat',
568 | showMutation: true
569 | });
570 | if (this.declareWorkspace){
571 | this.declareWorkspace.clear();
572 | this.makeBlock('hat');
573 | }
574 |
575 | }
576 |
577 | editBlock (opcode){
578 | const block = this.state.blocks.filter(blk => blk.opcode === opcode);
579 | if (block && block.length == 1){
580 | this.declareWorkspace.clear();
581 | this.makeBlock(block[0].type, block[0].mutationText);
582 | this.setState({
583 | editBlockID: opcode,
584 | showMutation: true,
585 | addBlockType: block[0].type
586 | });
587 | }
588 | }
589 |
590 | deleteBlock (opcode){
591 | const blocks = [...this.state.blocks].filter(blk => blk.opcode !== opcode);
592 | this.setState({blocks});
593 | }
594 |
595 | editBlockScript (opcode){
596 | const block = this.state.blocks.filter(blk => blk.opcode === opcode);
597 | if (block && block.length == 1){
598 | const blk = block[0];
599 | if (!blk.script){
600 | blk.script = buildBlockOp(blk.opcode, blk.args);
601 | }
602 | this.setState({
603 | blockScript: {
604 | opcode: blk.opcode,
605 | script: blk.script,
606 | applyScript: (script) => {
607 | blk.script = script
608 | }
609 | }
610 | });
611 | }
612 | }
613 |
614 | editGeneratorHead (){
615 | const genHeadScript = {
616 | applyGen: (gen) => {
617 | if (gen.genCpp) this.setState({genCppHead: gen.genCpp});
618 | if (gen.genMpy) this.setState({genMpyHead: gen.genMpy});
619 | }
620 | }
621 | for (const code of this.state.genOption){
622 | if (code === 'arduino'){
623 | genHeadScript.genCpp = this.state.genCppHead || buildEmptyHeadCpp(this.state.extID);
624 | } else if(code === 'micropython'){
625 | genHeadScript.genMpy = this.state.genMpyHead || buildEmptyHeadMpy(this.state.extID);
626 | }
627 | }
628 | this.setState({genHeadScript});
629 | }
630 |
631 | editBlockGenerator (opcode){
632 | const block = this.state.blocks.filter(blk => blk.opcode === opcode);
633 | if (block && block.length == 1){
634 | const blk = block[0];
635 | const blockGenerator = {
636 | opcode: blk.opcode,
637 | applyGen: (gen) => {
638 | if (gen.genCpp) blk.genCpp = gen.genCpp;
639 | if (gen.genMpy) blk.genMpy = gen.genMpy;
640 | }
641 | }
642 | if (!blk.gen){
643 | for (const code of this.state.genOption){
644 | if (code === 'arduino'){
645 | blockGenerator.genCpp = blk.genCpp || buildBlockGenCpp(blk.opcode, blk.args);
646 | } else if(code === 'micropython'){
647 | blockGenerator.genMpy = blk.genMpy || buildBlockGenMpy(blk.opcode, blk.args);
648 | }
649 | }
650 | }
651 | this.setState({blockGenerator});
652 | }
653 | }
654 |
655 | saveToJson (){
656 | const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(this.state, null, 2));
657 | const downloadAnchorNode = document.createElement('a');
658 | downloadAnchorNode.setAttribute("href", dataStr);
659 | downloadAnchorNode.setAttribute("download", this.state.extID + ".json");
660 | document.body.appendChild(downloadAnchorNode); // required for firefox
661 | downloadAnchorNode.click();
662 | downloadAnchorNode.remove();
663 | }
664 |
665 | loadFromJson (file){
666 | if (file){
667 | let reader = new FileReader();
668 | const _this = this;
669 | reader.onerror = function () {
670 | console.warn("read image file error")
671 | };
672 | reader.onload = ev => {
673 | this.setState(Object.assign({},
674 | JSON.parse(reader.result)
675 | ))
676 | }
677 | reader.readAsText(file);
678 | }
679 | }
680 |
681 | generateIndexJS (){
682 | const option = {
683 | className: this.state.extID,
684 | extID: this.state.extID,
685 | extName: this.state.extName,
686 | color1: this.state.color1,
687 | color2: this.state.color2,
688 | menuIconURI: this.state.menuIcon ? `"${this.state.menuIcon}"` : 'null',
689 | blockIconURI: this.state.blockIcon ? `"${this.state.blockIcon}"` : 'null',
690 | genCppHead: this.state.genCppHead,
691 | genMpyHead: this.state.genMpyHead,
692 | }
693 | const indexJS = buildJsCode(option, this.state.blocks);
694 | return indexJS;
695 | }
696 |
697 | exportJs (){
698 | this.setState({
699 | indexJS: this.generateIndexJS()
700 | }, () => this.setState({isShowCodePreview: true}));
701 | }
702 |
703 | render() {
704 | return (
705 |
706 |
711 |
712 |

713 |
714 |
720 |
721 |
722 |
723 | this.setState({collapsed: !this.state.collapsed})}
727 | />
728 |
729 |
733 |
734 |
735 | {strings.extdef}
736 |
737 |
738 | {strings.extID}
739 |
740 |
741 | this.setState({extID: e.target.value})} />
742 |
743 |
744 | {strings.extName}
745 |
746 |
747 | this.setState({extName: e.target.value})} />
748 |
749 |
750 |
751 | {strings.maincolor}
752 |
753 | this.setState({color1Pick: true})} />
754 | { this.state.color1Pick ?
755 |
this.setState({color1Pick: false})}/>
756 | this.setState({color1: c.hex})} />
757 |
: null }
758 |
759 |
{strings.secondcolor}
760 |
761 |
this.setState({color2Pick: true})} />
762 | { this.state.color2Pick ?
763 |
this.setState({color2Pick: false})}/>
764 | this.setState({color2: c.hex})} />
765 |
: null }
766 |
767 |
768 |
769 |
770 | {this.state.menuIcon ?
: null}
771 |
778 |
779 |
780 |
781 |
782 | {this.state.blockIcon ?
: null}
783 |
790 |
791 |
792 |
793 |
794 |
{strings.generator}
795 |
796 |
797 |
798 |
799 |
800 |
{strings.addblock}
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 | {strings.uniqBlockName}
844 |
845 |
846 |
847 | Block ID
848 |
849 |
850 | this.setState({editBlockID: e.target.value})} />
851 |
852 |
853 | {strings.uniqBlockId}
854 |
855 |
856 |
857 | {this.state.genHeadScript ?
this.setState({genHeadScript: null})}
861 | /> : null}
862 | {this.state.blockScript ? this.setState({blockScript: null})}
865 | /> : null}
866 | {this.state.blockGenerator ? this.setState({blockGenerator: null})}
870 | /> : null}
871 | {this.state.isShowCodePreview ? this.setState({isShowCodePreview: false})}
874 | /> : null}
875 |
876 | );
877 | }
878 | }
879 |
880 | export default App;
881 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/BlockEditor.js:
--------------------------------------------------------------------------------
1 | import bindAll from 'lodash.bindall';
2 | import LocalizedStrings from 'react-localization';
3 | import { Modal, Tabs } from 'antd';
4 | import React, { Component } from 'react';
5 | import MonacoEditor from 'react-monaco-editor';
6 |
7 | const TabPane = Tabs.TabPane;
8 |
9 | class BlockScriptEditor extends Component {
10 | constructor (props){
11 | super(props);
12 | bindAll(this, [
13 | 'onChange',
14 | 'onApply'
15 | ])
16 | this.state = {
17 | script: this.props.blockScript.script
18 | };
19 | }
20 |
21 | onChange(newValue, e) {
22 | this.setState({
23 | script: newValue
24 | })
25 | }
26 | onApply (){
27 | this.props.blockScript.applyScript(this.state.script);
28 | this.props.onClose();
29 | }
30 | render (){
31 | const {
32 | onClose,
33 | blockScript,
34 | } = this.props;
35 |
36 | return (
43 |
51 | )
52 | }
53 | }
54 |
55 | class BlockGeneratorEditor extends Component {
56 | constructor (props){
57 | super(props);
58 | bindAll(this, [
59 | 'onChange',
60 | 'onApply',
61 | 'switchCode'
62 | ])
63 | this.state = {
64 | genCpp: this.props.gen.genCpp,
65 | genMpy: this.props.gen.genMpy,
66 | };
67 | }
68 |
69 | onChange(newValue, e) {
70 | this.setState({
71 | script: newValue
72 | })
73 | }
74 | onApply (){
75 | this.props.gen.applyGen({
76 | genCpp: this.state.genCpp,
77 | genMpy: this.state.genMpy
78 | });
79 | this.props.onClose();
80 | }
81 | switchCode (c){
82 |
83 | }
84 | render (){
85 | const {
86 | onClose,
87 | genOption,
88 | gen,
89 | } = this.props;
90 |
91 | return (
98 |
99 | {genOption.indexOf('arduino') > -1 ?
100 | this.setState({genCpp: code})}
107 | /> :null}
108 | {genOption.indexOf('micropython') > -1 ?
109 | this.setState({genMpy: code})}
116 | /> :null}
117 |
118 | )
119 | }
120 |
121 | }
122 |
123 |
124 | class CodePreview extends Component {
125 | constructor (props){
126 | super(props);
127 | bindAll(this, [
128 | 'onChange',
129 | 'downloadIndexjs'
130 | ])
131 | this.state = {
132 | script: this.props.code
133 | };
134 | }
135 |
136 | downloadIndexjs (){
137 | const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(this.state.script);
138 | const downloadAnchorNode = document.createElement('a');
139 | downloadAnchorNode.setAttribute("href", dataStr);
140 | downloadAnchorNode.setAttribute("download", "index.js");
141 | document.body.appendChild(downloadAnchorNode); // required for firefox
142 | downloadAnchorNode.click();
143 | downloadAnchorNode.remove();
144 | }
145 | onChange(newValue, e) {
146 | this.setState({
147 | script: newValue
148 | })
149 | }
150 | render (){
151 | const {
152 | code,
153 | onClose,
154 | } = this.props;
155 |
156 | return (
163 |
171 | )
172 | }
173 | }
174 |
175 |
176 |
177 | export {
178 | BlockScriptEditor,
179 | BlockGeneratorEditor,
180 | CodePreview
181 | }
--------------------------------------------------------------------------------
/src/CodeBuilder.js:
--------------------------------------------------------------------------------
1 |
2 | const BlockTypeMap = {
3 | func: "COMMAND",
4 | output: "REPORTER",
5 | bool: "BOOLEAN",
6 | hat: "HAT"
7 | }
8 |
9 | const buildEmptyHeadCpp = function (extID){
10 | return `cppComm(gen){
11 | gen.includes_['${extID}'] = '#include "YourHeader.h"';
12 | gen.definitions_['${extID}'] = 'YourClass object;';
13 | };`
14 | }
15 |
16 | const buildEmptyHeadMpy = function (extID){
17 | return `mpyComm(gen){
18 | gen.includes_['${extID}'] = 'import YourClass';
19 | };`
20 | }
21 |
22 | const buildBlockGenCpp = function (opcode, args){
23 | const code = `${opcode}Cpp (gen, block){\n cppComm(gen);\n return gen.template2code(block, '${opcode}')\n}\n`
24 | return code;
25 | }
26 |
27 | const buildBlockGenMpy = function (opcode, args){
28 | const code = `${opcode}Cpp (gen, block){\n mpyComm(gen);\n return gen.template2code(block, '${opcode}')\n}\n`
29 | return code;
30 | }
31 |
32 | const buildBlockOp = function(opcode, args){
33 | const argDefine = args.reduce((sc, arg) => {
34 | return sc += ` const ${arg.placeholder} = args.${arg.placeholder};\n`
35 | }, "")
36 | const code = `${opcode} (args, util){\n${argDefine}\n return this.write(\`M0 \\n\`);\n}\n`
37 | return code;
38 | }
39 |
40 | const buildJsCode = function(opt, blocks){
41 | const blockFunctions = [];
42 | const blocksInfo = [];
43 |
44 | for (const block of blocks){
45 | let txt = block.msg;
46 | let argIndex = 1;
47 | const blockCode = {
48 | opcode: `'${block.opcode}'`,
49 | blockType: `BlockType.${BlockTypeMap[block.type]}`
50 | };
51 | if (block.type === 'hat'){
52 | blockCode.isEdgeActivated = false
53 | }
54 | if (block.args.length){
55 | blockCode.arguments = {};
56 | for (let n=0;n {
125 | if (parser){
126 | this.reporter = {
127 | parser,
128 | resolve
129 | }
130 | }
131 | this.session.write(data);
132 | })
133 | }
134 | }
135 |
136 | onmessage (data){
137 | const dataStr = this.decoder.decode(data);
138 | this.lineBuffer += dataStr;
139 | if (this.lineBuffer.indexOf('\\n') !== -1){
140 | const lines = this.lineBuffer.split('\\n');
141 | this.lineBuffer = lines.pop();
142 | for (const l of lines){
143 | if (this.reporter){
144 | const {parser, resolve} = this.reporter;
145 | resolve(parser(l));
146 | };
147 | }
148 | }
149 | }
150 |
151 | scan (){
152 | this.comm.getDeviceList().then(result => {
153 | this.runtime.emit(this.runtime.constructor.PERIPHERAL_LIST_UPDATE, result);
154 | });
155 | }
156 |
157 | getInfo (){
158 | return {
159 | id: '${opt.extID}',
160 | name: '${opt.extName}',
161 | color1: '${opt.color1}',
162 | color2: '${opt.color2}',
163 | menuIconURI: menuIconURI,
164 | blockIconURI: blockIconURI,
165 | blocks: ${blkInfoCode}
166 | }
167 | }
168 |
169 | ${blockFunctions.join('\n')}
170 | }
171 |
172 | module.exports = ${opt.className};
173 | `;
174 |
175 | return indexJS;
176 | }
177 |
178 |
179 |
180 | export {
181 | buildJsCode,
182 | buildBlockOp,
183 | buildBlockGenCpp,
184 | buildBlockGenMpy,
185 | buildEmptyHeadCpp,
186 | buildEmptyHeadMpy
187 | };
--------------------------------------------------------------------------------
/src/arduino.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KittenBot/scratch3-extension/51e0079ac70dc979f03105001e5e04268459b06e/src/arduino.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: http://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/micropy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KittenBot/scratch3-extension/51e0079ac70dc979f03105001e5e04268459b06e/src/micropy.png
--------------------------------------------------------------------------------
/src/python.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KittenBot/scratch3-extension/51e0079ac70dc979f03105001e5e04268459b06e/src/python.png
--------------------------------------------------------------------------------
/src/s3ext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KittenBot/scratch3-extension/51e0079ac70dc979f03105001e5e04268459b06e/src/s3ext.png
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------