Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
.secrets
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"anthropic.claude-code"
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"liveServer.settings.port": 5501
|
||||
}
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
# 安装vim工具
|
||||
RUN apk add --no-cache vim
|
||||
|
||||
# 创建配置文件目录
|
||||
RUN mkdir -p /etc/nginx/conf.d
|
||||
|
||||
# 复制静态文件
|
||||
COPY scratch-gui/build /usr/share/nginx/html
|
||||
|
||||
# 复制默认配置文件
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
334
FriendToggle-使用说明.md
Normal file
334
FriendToggle-使用说明.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# 好友开关组件使用说明
|
||||
|
||||
## 📦 组件概述
|
||||
|
||||
好友开关组件是一个独立的、可重用的悬浮按钮组件,支持多种配置选项和主题样式。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 引入组件
|
||||
|
||||
```html
|
||||
<!-- 方式1: 直接引入 -->
|
||||
<script src="./scratch-gui/src/components/friend-toggle/FriendToggle.js"></script>
|
||||
|
||||
<!-- 方式2: ES6 模块导入 -->
|
||||
<script type="module">
|
||||
import FriendToggle from './scratch-gui/src/components/friend-toggle/FriendToggle.js';
|
||||
</script>
|
||||
```
|
||||
|
||||
### 2. 基础使用
|
||||
|
||||
```javascript
|
||||
// 创建一个默认配置的好友开关
|
||||
const friendToggle = new FriendToggle();
|
||||
|
||||
// 创建一个自定义配置的好友开关
|
||||
const customToggle = new FriendToggle({
|
||||
position: 'top-right', // 位置
|
||||
size: 'large', // 大小
|
||||
theme: 'dark', // 主题
|
||||
autoHide: false, // 关闭自动隐藏
|
||||
initialState: true, // 初始激活状态
|
||||
onToggle: (isActive) => {
|
||||
console.log('状态改变:', isActive);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## ⚙️ 配置选项
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `position` | string | `'bottom-right'` | 位置:`'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'` |
|
||||
| `size` | string | `'medium'` | 大小:`'small'`, `'medium'`, `'large'` |
|
||||
| `theme` | string | `'default'` | 主题:`'default'`, `'dark'`, `'colorful'` |
|
||||
| `autoHide` | boolean | `true` | 是否自动隐藏(3秒后) |
|
||||
| `onToggle` | function | `null` | 状态切换回调函数 |
|
||||
| `initialState` | boolean | `false` | 初始状态(激活/未激活) |
|
||||
| `zIndex` | number | `9999` | CSS z-index 值 |
|
||||
|
||||
## 🎨 API 方法
|
||||
|
||||
### 基础方法
|
||||
|
||||
```javascript
|
||||
const toggle = new FriendToggle();
|
||||
|
||||
// 切换状态
|
||||
toggle.toggle();
|
||||
|
||||
// 设置状态
|
||||
toggle.setState(true); // 激活
|
||||
toggle.setState(false); // 未激活
|
||||
|
||||
// 获取状态
|
||||
const isActive = toggle.getState();
|
||||
|
||||
// 显示/隐藏组件
|
||||
toggle.show();
|
||||
toggle.hide();
|
||||
|
||||
// 销毁组件
|
||||
toggle.destroy();
|
||||
```
|
||||
|
||||
### 配置更新
|
||||
|
||||
```javascript
|
||||
// 动态更新配置
|
||||
toggle.updateConfig({
|
||||
position: 'top-left',
|
||||
theme: 'colorful',
|
||||
autoHide: false
|
||||
});
|
||||
```
|
||||
|
||||
## 📡 事件监听
|
||||
|
||||
### 回调函数方式
|
||||
|
||||
```javascript
|
||||
const toggle = new FriendToggle({
|
||||
onToggle: (isActive) => {
|
||||
if (isActive) {
|
||||
console.log('好友模式开启');
|
||||
// 开启好友相关功能
|
||||
} else {
|
||||
console.log('好友模式关闭');
|
||||
// 关闭好友相关功能
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 全局事件监听
|
||||
|
||||
```javascript
|
||||
// 监听自定义事件
|
||||
document.addEventListener('friendToggleChange', (event) => {
|
||||
const { isActive, instance } = event.detail;
|
||||
console.log('组件状态变化:', isActive);
|
||||
console.log('组件实例:', instance);
|
||||
});
|
||||
```
|
||||
|
||||
## 🎯 使用示例
|
||||
|
||||
### 示例1: 基础聊天功能
|
||||
|
||||
```javascript
|
||||
// 创建好友开关
|
||||
const chatToggle = new FriendToggle({
|
||||
position: 'bottom-right',
|
||||
theme: 'default',
|
||||
onToggle: (isActive) => {
|
||||
if (isActive) {
|
||||
// 显示聊天窗口
|
||||
showChatWindow();
|
||||
} else {
|
||||
// 隐藏聊天窗口
|
||||
hideChatWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function showChatWindow() {
|
||||
// 你的聊天窗口显示逻辑
|
||||
console.log('显示聊天窗口');
|
||||
}
|
||||
|
||||
function hideChatWindow() {
|
||||
// 你的聊天窗口隐藏逻辑
|
||||
console.log('隐藏聊天窗口');
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2: 多人协作模式
|
||||
|
||||
```javascript
|
||||
// 创建协作模式开关
|
||||
const collaborationToggle = new FriendToggle({
|
||||
position: 'top-right',
|
||||
size: 'large',
|
||||
theme: 'colorful',
|
||||
autoHide: false,
|
||||
onToggle: (isActive) => {
|
||||
toggleCollaborationMode(isActive);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleCollaborationMode(enabled) {
|
||||
if (enabled) {
|
||||
// 启用协作功能
|
||||
enableRealTimeSync();
|
||||
showCollaborators();
|
||||
console.log('协作模式已开启');
|
||||
} else {
|
||||
// 禁用协作功能
|
||||
disableRealTimeSync();
|
||||
hideCollaborators();
|
||||
console.log('协作模式已关闭');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3: 与现有项目集成
|
||||
|
||||
```javascript
|
||||
// 在 Scratch GUI 中集成
|
||||
import FriendToggle from './components/friend-toggle/FriendToggle.js';
|
||||
|
||||
class MyComponent extends React.Component {
|
||||
componentDidMount() {
|
||||
// 创建好友开关
|
||||
this.friendToggle = new FriendToggle({
|
||||
position: 'bottom-left',
|
||||
onToggle: this.handleFriendToggle.bind(this)
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// 清理组件
|
||||
if (this.friendToggle) {
|
||||
this.friendToggle.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
handleFriendToggle(isActive) {
|
||||
// 处理状态变化
|
||||
this.setState({ friendModeActive: isActive });
|
||||
|
||||
if (isActive) {
|
||||
this.initializeFriendFeatures();
|
||||
} else {
|
||||
this.cleanupFriendFeatures();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// 你的组件渲染逻辑
|
||||
return <div>My Component</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 主题定制
|
||||
|
||||
### 使用预设主题
|
||||
|
||||
```javascript
|
||||
// 默认主题(蓝紫渐变)
|
||||
const defaultToggle = new FriendToggle({ theme: 'default' });
|
||||
|
||||
// 深色主题(黑灰渐变)
|
||||
const darkToggle = new FriendToggle({ theme: 'dark' });
|
||||
|
||||
// 彩色主题(粉色渐变)
|
||||
const colorfulToggle = new FriendToggle({ theme: 'colorful' });
|
||||
```
|
||||
|
||||
### 自定义样式
|
||||
|
||||
```css
|
||||
/* 自定义主题样式 */
|
||||
.friend-toggle.custom-theme {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.friend-toggle.custom-theme.active {
|
||||
background: linear-gradient(135deg, #48dbfb 0%, #0abde3 100%);
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 应用自定义主题
|
||||
const customToggle = new FriendToggle({
|
||||
theme: 'custom-theme'
|
||||
});
|
||||
```
|
||||
|
||||
## 🔧 高级用法
|
||||
|
||||
### 多实例管理
|
||||
|
||||
```javascript
|
||||
class FriendToggleManager {
|
||||
constructor() {
|
||||
this.instances = new Map();
|
||||
}
|
||||
|
||||
create(id, config) {
|
||||
const toggle = new FriendToggle(config);
|
||||
this.instances.set(id, toggle);
|
||||
return toggle;
|
||||
}
|
||||
|
||||
destroy(id) {
|
||||
const toggle = this.instances.get(id);
|
||||
if (toggle) {
|
||||
toggle.destroy();
|
||||
this.instances.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
destroyAll() {
|
||||
this.instances.forEach(toggle => toggle.destroy());
|
||||
this.instances.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用管理器
|
||||
const manager = new FriendToggleManager();
|
||||
manager.create('chat', { position: 'bottom-right' });
|
||||
manager.create('collaboration', { position: 'top-right' });
|
||||
```
|
||||
|
||||
### 动态配置
|
||||
|
||||
```javascript
|
||||
// 根据屏幕尺寸调整组件
|
||||
function createResponsiveToggle() {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
|
||||
return new FriendToggle({
|
||||
size: isMobile ? 'small' : 'medium',
|
||||
position: isMobile ? 'bottom-left' : 'bottom-right',
|
||||
autoHide: isMobile // 移动设备自动隐藏
|
||||
});
|
||||
}
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', () => {
|
||||
// 重新创建响应式组件
|
||||
});
|
||||
```
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q: 组件没有显示?
|
||||
A: 确保正确引入了 JavaScript 文件,并且调用了 `new FriendToggle()`。
|
||||
|
||||
### Q: 样式冲突怎么办?
|
||||
A: 组件使用了独立的 CSS 类名和高 z-index,如果仍有冲突,可以调整 `zIndex` 配置。
|
||||
|
||||
### Q: 如何自定义图标?
|
||||
A: 可以通过 CSS 覆盖 `.friend-toggle .icon` 的内容,或修改组件源码中的图标。
|
||||
|
||||
### Q: 能否在移动设备上使用?
|
||||
A: 可以,组件支持触摸事件,建议在移动设备上使用较小的尺寸。
|
||||
|
||||
## 📝 测试说明
|
||||
|
||||
1. 打开 `test-friend-toggle.html` 文件
|
||||
2. 在浏览器中查看效果
|
||||
3. 使用控制面板测试各种配置
|
||||
4. 观察右下角的悬浮按钮
|
||||
|
||||
## 🔄 更新记录
|
||||
|
||||
- v1.0.0: 初始版本,支持基本的开关功能
|
||||
- 支持 4 种位置、3 种大小、3 种主题
|
||||
- 支持自动隐藏、事件回调等功能
|
||||
36
README.en.md
Normal file
36
README.en.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# code001_html
|
||||
|
||||
#### Description
|
||||
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
98
README.md
Normal file
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
||||
001code-html
|
||||
|
||||
系灵丌编程(001CODE)前端项目。
|
||||
|
||||
## scratch-gui 本地开发
|
||||
|
||||
- 目录位置:`scratch-gui\`
|
||||
- Node 版本建议:`>= 18`,`npm >= 9`(Windows)
|
||||
|
||||
### 安装依赖
|
||||
```bash
|
||||
cd scratch-gui
|
||||
```
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
### 启动开发服务器
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
### 生产构建
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 与子包联动(npm link)
|
||||
|
||||
`scratch-gui` 依赖内部包:`scratch-vm`、`scratch-blocks`。为在本地联动开发它们的改动,使用 `npm link`。
|
||||
|
||||
> 提示:每个子包先安装依赖并构建,再 `npm link`。在 `scratch-gui` 中再链接对应包名。
|
||||
|
||||
### 链接 scratch-vm
|
||||
```bash
|
||||
cd scratch-vm
|
||||
```
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
```bash
|
||||
npm link
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 链接 scratch-blocks
|
||||
```bash
|
||||
cd scratch-blocks
|
||||
```
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
```bash
|
||||
npm link
|
||||
```
|
||||
### scratch-gui 链接 scratch-blocks scratch-vm
|
||||
|
||||
```bash
|
||||
cd ..\scratch-gui
|
||||
```
|
||||
|
||||
```bash
|
||||
npm link scratch-blocks scratch-vm
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 解除链接(恢复使用包管理器安装的版本)
|
||||
```bash
|
||||
cd scratch-gui
|
||||
```
|
||||
```bash
|
||||
npm unlink scratch-vm
|
||||
```
|
||||
```bash
|
||||
npm unlink scratch-render
|
||||
```
|
||||
```bash
|
||||
npm unlink scratch-blocks
|
||||
```
|
||||
```bash
|
||||
npm unlink scratch-l10n
|
||||
```
|
||||
```bash
|
||||
npm i
|
||||
```
|
||||
|
||||
## Docker 运行期配置(可选)
|
||||
|
||||
- 通过 `/env.config.json` 注入运行时配置(例如 `API_BASE` / `DEPLOY_ENV`),ENTRYPOINT 启动时替换占位符。
|
||||
- 开发环境如果未启用 Docker:`env.config.json` 仍为占位时,前端会自动回退读取 `process.env.*`。
|
||||
|
||||
常见问题
|
||||
- 若容器启动失败提示找不到入口脚本,确认 Dockerfile 已 `COPY docker-conf/entrypoint.sh` 并 `chmod +x`,且无 CRLF 行尾。
|
||||
- 确保静态服务器对 `/env.config.json` 返回 `Content-Type: application/json`,并避免 SPA 重写。
|
||||
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||
- ./logs:/var/log/nginx
|
||||
restart: always
|
||||
41
docker-conf/access.log
Normal file
41
docker-conf/access.log
Normal file
@@ -0,0 +1,41 @@
|
||||
172.17.0.1 - - [02/Jan/2025:07:48:02 +0000] "GET / HTTP/1.1" 200 3629 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:48:02 +0000] "GET /favicon.ico HTTP/1.1" 200 4286 "http://localhost:55006/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:40 +0000] "GET /welcome.html HTTP/1.1" 200 5075 "http://localhost:55006/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:41 +0000] "GET /programming-learning.html HTTP/1.1" 200 5270 "http://localhost:55006/welcome.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /editor.html HTTP/1.1" 200 2090 "http://localhost:55006/programming-learning.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/cb3ac3b4a8540f50937a86af7eb4663c.woff2 HTTP/1.1" 200 169496 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/5796214a21c5c1727ed7b08096ee7cff.woff2 HTTP/1.1" 200 99548 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/208e2134a0e95cbdd211a82d2863d88d.woff2 HTTP/1.1" 200 75316 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/2e6994cc7c70b524c649f60346bdcb3a.woff2 HTTP/1.1" 200 20040 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/6cd3397308e11e6d925446470b906f2a.woff2 HTTP/1.1" 200 17528 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/f108ac9d733ce5d45cdea3b7828e8658.woff2 HTTP/1.1" 200 3772 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/75f5f189fdc419824a8134db6eb63881.woff2 HTTP/1.1" 200 31064 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/674db5c2e49df884273ac0f7d221865c.svg HTTP/1.1" 200 2644 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/566d73a090e3d42d5bdfee3d92c79d25.svg HTTP/1.1" 200 2628 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/assets/7efe82fffae735cd083388badd51b3fa.svg HTTP/1.1" 200 2644 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/zoom-in.svg HTTP/1.1" 200 634 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/zoom-out.svg HTTP/1.1" 200 582 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/zoom-reset.svg HTTP/1.1" 200 501 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/repeat.svg HTTP/1.1" 200 1239 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/dropdown-arrow.svg HTTP/1.1" 200 569 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /unity/Build/mstest5.framework.js.br HTTP/1.1" 200 64196 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/repeat.svg HTTP/1.1" 200 1239 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/dropdown-arrow.svg HTTP/1.1" 200 569 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/rotate-left.svg HTTP/1.1" 200 1064 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /static/blocks-media/default/rotate-right.svg HTTP/1.1" 200 1056 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:44 +0000] "GET /unity/Build/mstest5.wasm.br HTTP/1.1" 200 4001975 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /manifest.webmanifest HTTP/1.1" 200 418 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /unity/Build/mstest5.data.br HTTP/1.1" 200 1323231 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /unity/StreamingAssets/aa/settings.json HTTP/1.1" 200 726 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /unity/StreamingAssets/aa/catalog.json HTTP/1.1" 200 13220 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /unity/StreamingAssets/aa/WebGL/defaultlocalgroup_assets_all_9c32404c1dd441741d637293b64520fc.bundle HTTP/1.1" 200 2753189 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /unity/StreamingAssets/aa/WebGL/4d19e3a694666eb8f2942eb443733fac_unitybuiltinshaders_ed633e15b1593dacd50a2344a00a5ba9.bundle HTTP/1.1" 200 18502 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:45 +0000] "GET /favicon.ico HTTP/1.1" 200 4286 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:49:46 +0000] "GET /static/blocks-media/default/dropdown-arrow.svg HTTP/1.1" 200 569 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:01 +0000] "GET /static/blocks-media/default/rotate-right.svg HTTP/1.1" 200 1056 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:01 +0000] "GET /static/blocks-media/default/rotate-left.svg HTTP/1.1" 200 1064 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:01 +0000] "GET /static/blocks-media/default/dropdown-arrow.svg HTTP/1.1" 200 569 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:10 +0000] "GET /static/blocks-media/default/rotate-right.svg HTTP/1.1" 200 1056 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:10 +0000] "GET /static/blocks-media/default/rotate-left.svg HTTP/1.1" 200 1064 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:11 +0000] "GET /static/blocks-media/default/rotate-left.svg HTTP/1.1" 200 1064 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
172.17.0.1 - - [02/Jan/2025:07:50:11 +0000] "GET /static/blocks-media/default/dropdown-arrow.svg HTTP/1.1" 200 569 "http://localhost:55006/editor.html" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
||||
70
docker-conf/conf.d/nginx.conf
Normal file
70
docker-conf/conf.d/nginx.conf
Normal file
@@ -0,0 +1,70 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
index index.html index.htm default.htm default.html;
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
# gzip 配置
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
# 重定向规则
|
||||
location = /c/scssn2025 {
|
||||
return 301 /competition.html?competition_id=13;
|
||||
}
|
||||
|
||||
# Brotli 压缩 WebAssembly 文件处理
|
||||
location ~* \.wasm\.br$ {
|
||||
default_type "";
|
||||
add_header Content-Encoding br;
|
||||
add_header Content-Type application/wasm always;
|
||||
add_header Vary Accept-Encoding;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
location ~* \.asm\.js\.br$ {
|
||||
default_type "";
|
||||
add_header Content-Encoding br;
|
||||
add_header Content-Type application/javascript always;
|
||||
add_header Vary Accept-Encoding;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# 通用 Brotli 文件处理
|
||||
location ~* \.br$ {
|
||||
add_header Content-Encoding br;
|
||||
add_header Vary Accept-Encoding;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# 静态资源缓存配置
|
||||
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
|
||||
expires 30d;
|
||||
error_log off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location ~ .*\.(js|css)?$ {
|
||||
expires 12h;
|
||||
error_log off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-cache, no-store";
|
||||
}
|
||||
|
||||
#禁止访问的文件或目录
|
||||
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 错误日志和访问日志配置
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
}
|
||||
140
docker-conf/error.log
Normal file
140
docker-conf/error.log
Normal file
@@ -0,0 +1,140 @@
|
||||
2025/01/02 07:47:42 [notice] 1#1: using the "epoll" event method
|
||||
2025/01/02 07:47:42 [notice] 1#1: nginx/1.27.3
|
||||
2025/01/02 07:47:42 [notice] 1#1: built by gcc 13.2.1 20240309 (Alpine 13.2.1_git20240309)
|
||||
2025/01/02 07:47:42 [notice] 1#1: OS: Linux 6.10.14-linuxkit
|
||||
2025/01/02 07:47:42 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker processes
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 29
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 30
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 31
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 32
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 33
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 34
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 35
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 36
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 37
|
||||
2025/01/02 07:47:42 [notice] 1#1: start worker process 38
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
|
||||
2025/01/02 07:50:37 [notice] 29#29: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 30#30: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 31#31: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 35#35: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 30#30: exiting
|
||||
2025/01/02 07:50:37 [notice] 34#34: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 36#36: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 38#38: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 32#32: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 33#33: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 36#36: exiting
|
||||
2025/01/02 07:50:37 [notice] 37#37: gracefully shutting down
|
||||
2025/01/02 07:50:37 [notice] 29#29: exiting
|
||||
2025/01/02 07:50:37 [notice] 31#31: exiting
|
||||
2025/01/02 07:50:37 [notice] 32#32: exiting
|
||||
2025/01/02 07:50:37 [notice] 35#35: exiting
|
||||
2025/01/02 07:50:37 [notice] 31#31: exit
|
||||
2025/01/02 07:50:37 [notice] 38#38: exiting
|
||||
2025/01/02 07:50:37 [notice] 34#34: exiting
|
||||
2025/01/02 07:50:37 [notice] 38#38: exit
|
||||
2025/01/02 07:50:37 [notice] 30#30: exit
|
||||
2025/01/02 07:50:37 [notice] 33#33: exiting
|
||||
2025/01/02 07:50:37 [notice] 36#36: exit
|
||||
2025/01/02 07:50:37 [notice] 37#37: exiting
|
||||
2025/01/02 07:50:37 [notice] 33#33: exit
|
||||
2025/01/02 07:50:37 [notice] 29#29: exit
|
||||
2025/01/02 07:50:37 [notice] 35#35: exit
|
||||
2025/01/02 07:50:37 [notice] 32#32: exit
|
||||
2025/01/02 07:50:37 [notice] 34#34: exit
|
||||
2025/01/02 07:50:37 [notice] 37#37: exit
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 17 (SIGCHLD) received from 37
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 29 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 37 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 17 (SIGCHLD) received from 34
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 30 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 32 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 33 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 34 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 35 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 36 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 38 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 17 (SIGCHLD) received from 38
|
||||
2025/01/02 07:50:37 [notice] 1#1: signal 17 (SIGCHLD) received from 31
|
||||
2025/01/02 07:50:37 [notice] 1#1: worker process 31 exited with code 0
|
||||
2025/01/02 07:50:37 [notice] 1#1: exit
|
||||
2025/01/02 07:50:37 [notice] 1#1: using the "epoll" event method
|
||||
2025/01/02 07:50:37 [notice] 1#1: nginx/1.27.3
|
||||
2025/01/02 07:50:37 [notice] 1#1: built by gcc 13.2.1 20240309 (Alpine 13.2.1_git20240309)
|
||||
2025/01/02 07:50:37 [notice] 1#1: OS: Linux 6.10.14-linuxkit
|
||||
2025/01/02 07:50:37 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker processes
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 29
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 30
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 31
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 32
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 33
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 34
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 35
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 36
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 37
|
||||
2025/01/02 07:50:37 [notice] 1#1: start worker process 38
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 3 (SIGQUIT) received, shutting down
|
||||
2025/01/02 09:39:56 [notice] 29#29: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 36#36: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 35#35: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 30#30: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 31#31: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 33#33: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 38#38: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 34#34: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 32#32: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 36#36: exiting
|
||||
2025/01/02 09:39:56 [notice] 37#37: gracefully shutting down
|
||||
2025/01/02 09:39:56 [notice] 29#29: exiting
|
||||
2025/01/02 09:39:56 [notice] 38#38: exiting
|
||||
2025/01/02 09:39:56 [notice] 35#35: exiting
|
||||
2025/01/02 09:39:56 [notice] 31#31: exiting
|
||||
2025/01/02 09:39:56 [notice] 33#33: exiting
|
||||
2025/01/02 09:39:56 [notice] 32#32: exiting
|
||||
2025/01/02 09:39:56 [notice] 30#30: exiting
|
||||
2025/01/02 09:39:56 [notice] 34#34: exiting
|
||||
2025/01/02 09:39:56 [notice] 37#37: exiting
|
||||
2025/01/02 09:39:56 [notice] 34#34: exit
|
||||
2025/01/02 09:39:56 [notice] 35#35: exit
|
||||
2025/01/02 09:39:56 [notice] 33#33: exit
|
||||
2025/01/02 09:39:56 [notice] 36#36: exit
|
||||
2025/01/02 09:39:56 [notice] 37#37: exit
|
||||
2025/01/02 09:39:56 [notice] 31#31: exit
|
||||
2025/01/02 09:39:56 [notice] 30#30: exit
|
||||
2025/01/02 09:39:56 [notice] 38#38: exit
|
||||
2025/01/02 09:39:56 [notice] 32#32: exit
|
||||
2025/01/02 09:39:56 [notice] 29#29: exit
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 35
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 35 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 36
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 36 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 37
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 33 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 37 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 33
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 31
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 31 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 34
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 34 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 38
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 38 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 32
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 32 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 29
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 29 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 29 (SIGIO) received
|
||||
2025/01/02 09:39:56 [notice] 1#1: signal 17 (SIGCHLD) received from 30
|
||||
2025/01/02 09:39:56 [notice] 1#1: worker process 30 exited with code 0
|
||||
2025/01/02 09:39:56 [notice] 1#1: exit
|
||||
71
nginx.conf
Normal file
71
nginx.conf
Normal file
@@ -0,0 +1,71 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
index index.html index.htm default.htm default.html;
|
||||
root /usr/share/nginx/html;
|
||||
|
||||
# gzip 配置
|
||||
gzip on;
|
||||
gzip_static on;
|
||||
gzip_min_length 1k;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.";
|
||||
|
||||
# 重定向规则
|
||||
location = /c/scssn2025 {
|
||||
return 301 /competition.html?competition_id=13;
|
||||
}
|
||||
|
||||
# Brotli 压缩 WebAssembly 文件处理
|
||||
location ~* \.wasm\.br$ {
|
||||
default_type "";
|
||||
add_header Content-Encoding br;
|
||||
add_header Content-Type application/wasm always;
|
||||
add_header Vary Accept-Encoding;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
location ~* \.asm\.js\.br$ {
|
||||
default_type "";
|
||||
add_header Content-Encoding br;
|
||||
add_header Content-Type application/javascript always;
|
||||
add_header Vary Accept-Encoding;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# 通用 Brotli 文件处理
|
||||
location ~* \.br$ {
|
||||
add_header Content-Encoding br;
|
||||
add_header Vary Accept-Encoding;
|
||||
expires 30d;
|
||||
}
|
||||
|
||||
# 静态资源缓存配置
|
||||
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
|
||||
expires 30d;
|
||||
error_log off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location ~ .*\.(js|css)?$ {
|
||||
expires 12h;
|
||||
error_log off;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
add_header Cache-Control "no-cache, no-store";
|
||||
}
|
||||
|
||||
#禁止访问的文件或目录
|
||||
location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
# 错误日志和访问日志配置
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
}
|
||||
896
rank-badges.html
Normal file
896
rank-badges.html
Normal file
@@ -0,0 +1,896 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>称号等级预览</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: 'Arial Black', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: white;
|
||||
font-size: 36px;
|
||||
margin-bottom: 30px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ranks-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rank-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.rank-item:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
width: 128px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.3);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.rank-badge::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||
animation: shine 3s infinite;
|
||||
}
|
||||
|
||||
.rank-text {
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.rank-info {
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.rank-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.rank-score {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 坚韧黑铁 - 呼吸效果 */
|
||||
.rank-1 {
|
||||
background: linear-gradient(45deg, #2c3e50, #34495e);
|
||||
border: 2px solid #95a5a6;
|
||||
box-shadow: 0 0 10px rgba(149, 165, 166, 0.3);
|
||||
animation: breathe 3s ease-in-out infinite;
|
||||
}
|
||||
.rank-1 .rank-text {
|
||||
color: #ecf0f1;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.9), 0 0 8px #95a5a6;
|
||||
}
|
||||
|
||||
/* 英勇黄铜 - 光扫描效果 */
|
||||
.rank-2 {
|
||||
background: linear-gradient(45deg, #b7950b, #f39c12);
|
||||
border: 2px solid #f1c40f;
|
||||
box-shadow: 0 0 20px rgba(241, 196, 15, 0.4);
|
||||
animation: bronzeGlow 2s ease-in-out infinite;
|
||||
}
|
||||
.rank-2 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 12px #f1c40f;
|
||||
font-weight: 900;
|
||||
animation: bronzeTextGlow 2s ease-in-out infinite;
|
||||
}
|
||||
.rank-2::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent, rgba(241, 196, 15, 0.3), transparent);
|
||||
animation: bronzeScan 3s infinite;
|
||||
}
|
||||
|
||||
/* 不屈白银 - 光扫描效果 */
|
||||
.rank-3 {
|
||||
background: linear-gradient(45deg, #85929e, #d5dbdb);
|
||||
border: 2px solid #f8f9fa;
|
||||
box-shadow: 0 0 25px rgba(248, 249, 250, 0.5);
|
||||
position: relative;
|
||||
animation: silverGlow 2.5s ease-in-out infinite;
|
||||
}
|
||||
.rank-3::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent, rgba(248, 249, 250, 0.4), transparent);
|
||||
animation: silverScan 3.5s infinite;
|
||||
}
|
||||
.rank-3 .rank-text {
|
||||
color: #2c3e50;
|
||||
text-shadow: 3px 3px 6px rgba(255,255,255,0.8), 0 0 12px #f8f9fa;
|
||||
font-weight: 900;
|
||||
animation: silverTextGlow 2.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 荣耀黄金 - 光扫描效果 */
|
||||
.rank-4 {
|
||||
background: linear-gradient(45deg, #b7950b, #f1c40f);
|
||||
border: 2px solid #fff3cd;
|
||||
box-shadow: 0 0 30px rgba(241, 196, 15, 0.6);
|
||||
animation: goldGlow 2s ease-in-out infinite;
|
||||
}
|
||||
.rank-4 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 15px #fff3cd;
|
||||
font-weight: 900;
|
||||
animation: goldTextGlow 2s ease-in-out infinite;
|
||||
}
|
||||
.rank-4::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent, rgba(255, 243, 205, 0.4), transparent);
|
||||
animation: goldScan 3s infinite;
|
||||
}
|
||||
|
||||
/* 华贵铂金 - 光扫描效果 */
|
||||
.rank-5 {
|
||||
background: linear-gradient(45deg, #5d6d7e, #aeb6bf);
|
||||
border: 2px solid #d5dbdb;
|
||||
box-shadow: 0 0 35px rgba(213, 219, 219, 0.7);
|
||||
animation: platinumGlow 2s ease-in-out infinite;
|
||||
}
|
||||
.rank-5 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 18px #d5dbdb;
|
||||
font-weight: 900;
|
||||
animation: platinumTextGlow 2s ease-in-out infinite;
|
||||
}
|
||||
.rank-5::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent, rgba(213, 219, 219, 0.4), transparent);
|
||||
animation: platinumScan 2.5s infinite;
|
||||
}
|
||||
|
||||
/* 璀璨钻石 - 闪烁爆发 */
|
||||
.rank-6 {
|
||||
background: linear-gradient(45deg, #1abc9c, #16a085);
|
||||
border: 2px solid #a3e4d7;
|
||||
box-shadow: 0 0 40px rgba(163, 228, 215, 0.8);
|
||||
animation: sparkle 1.5s ease-in-out infinite;
|
||||
}
|
||||
.rank-6 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 20px #a3e4d7, 0 0 30px #1abc9c;
|
||||
font-weight: 900;
|
||||
animation: sparkleText 1.5s ease-in-out infinite;
|
||||
}
|
||||
.rank-6::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
background: conic-gradient(#1abc9c, #a3e4d7, #16a085, #1abc9c);
|
||||
border-radius: 21px;
|
||||
z-index: -1;
|
||||
animation: diamondSpin 2s linear infinite;
|
||||
}
|
||||
.rank-6::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #a3e4d7;
|
||||
border-radius: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: sparkBurst 1.5s ease-out infinite;
|
||||
box-shadow: 0 0 10px #a3e4d7;
|
||||
}
|
||||
|
||||
/* 超凡大师 - 魔法脉冲 */
|
||||
.rank-7 {
|
||||
background: linear-gradient(45deg, #8e44ad, #9b59b6);
|
||||
border: 2px solid #d7bde2;
|
||||
box-shadow: 0 0 45px rgba(215, 189, 226, 0.9);
|
||||
animation: magicPulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
.rank-7 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 25px #d7bde2, 0 0 35px #8e44ad;
|
||||
font-weight: 900;
|
||||
animation: magicTextGlow 1.2s ease-in-out infinite;
|
||||
}
|
||||
.rank-7::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
right: -6px;
|
||||
bottom: -6px;
|
||||
background: conic-gradient(#8e44ad, #d7bde2, #9b59b6, #8e44ad);
|
||||
border-radius: 22px;
|
||||
z-index: -1;
|
||||
animation: magicRotate 2s linear infinite;
|
||||
}
|
||||
.rank-7::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
right: -8px;
|
||||
bottom: -8px;
|
||||
background: radial-gradient(circle, transparent 70%, #d7bde2 70%, #d7bde2 80%, transparent 80%);
|
||||
border-radius: 24px;
|
||||
z-index: -1;
|
||||
animation: magicOrbit 3s linear infinite;
|
||||
}
|
||||
|
||||
/* 傲世宗师 - 烈焰风暴 */
|
||||
.rank-8 {
|
||||
background: linear-gradient(45deg, #e74c3c, #c0392b);
|
||||
border: 2px solid #fadbd8;
|
||||
box-shadow: 0 0 60px rgba(250, 219, 216, 1.2), 0 0 100px rgba(231, 76, 60, 0.6);
|
||||
animation: magicPulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
.rank-8 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 40px #fadbd8, 0 0 60px #e74c3c, 0 0 80px #c0392b;
|
||||
font-weight: 900;
|
||||
animation: magicTextGlow 1.2s ease-in-out infinite;
|
||||
}
|
||||
.rank-8::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: -6px;
|
||||
right: -6px;
|
||||
bottom: -6px;
|
||||
background: conic-gradient(#ff6b6b, #d73643, #ff4757, #ff6b6b);
|
||||
border-radius: 22px;
|
||||
z-index: -1;
|
||||
animation: magicRotate 2s linear infinite;
|
||||
}
|
||||
.rank-8::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: -8px;
|
||||
right: -8px;
|
||||
bottom: -8px;
|
||||
background: radial-gradient(circle, transparent 50%, #ff6b6b 50%, #ff6b6b 60%, transparent 60%);
|
||||
border-radius: 24px;
|
||||
z-index: -1;
|
||||
animation: magicOrbit 3s linear infinite;
|
||||
}
|
||||
|
||||
/* 最强王者 - 终极效果 */
|
||||
.rank-9 {
|
||||
background: linear-gradient(45deg, #f39c12, #e67e22, #d35400);
|
||||
border: 3px solid #fdeaa7;
|
||||
box-shadow: 0 0 50px rgba(253, 234, 167, 0.9), 0 0 100px rgba(243, 156, 18, 0.5);
|
||||
animation: pulse 0.8s infinite alternate, rainbow 4s linear infinite;
|
||||
}
|
||||
.rank-9 .rank-text {
|
||||
color: #ffffff;
|
||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 35px #fdeaa7, 0 0 50px #f39c12, 0 0 70px #e67e22;
|
||||
font-weight: 900;
|
||||
animation: textGlow 2s infinite alternate;
|
||||
}
|
||||
.rank-9::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
right: -5px;
|
||||
bottom: -5px;
|
||||
background: linear-gradient(45deg, #f39c12, #e67e22, #d35400, #f39c12);
|
||||
border-radius: 21px;
|
||||
z-index: -1;
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes shine {
|
||||
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
50% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
/* 1. 呼吸效果 - 坚韧黑铁 */
|
||||
@keyframes breathe {
|
||||
0% { transform: scale(1); box-shadow: 0 0 10px rgba(149, 165, 166, 0.3); }
|
||||
50% { transform: scale(1.05); box-shadow: 0 0 20px rgba(149, 165, 166, 0.6); }
|
||||
100% { transform: scale(1); box-shadow: 0 0 10px rgba(149, 165, 166, 0.3); }
|
||||
}
|
||||
|
||||
/* 2. 光扫描效果 - 英勇黄铜 */
|
||||
@keyframes bronzeGlow {
|
||||
0% { box-shadow: 0 0 20px rgba(241, 196, 15, 0.4); }
|
||||
50% { box-shadow: 0 0 35px rgba(241, 196, 15, 0.8); }
|
||||
100% { box-shadow: 0 0 20px rgba(241, 196, 15, 0.4); }
|
||||
}
|
||||
|
||||
@keyframes bronzeTextGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 12px #f1c40f; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 20px #f1c40f, 0 0 30px #f39c12; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 12px #f1c40f; }
|
||||
}
|
||||
|
||||
@keyframes bronzeScan {
|
||||
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
/* 3. 光扫描效果 - 不屈白银 */
|
||||
@keyframes silverGlow {
|
||||
0% { box-shadow: 0 0 25px rgba(248, 249, 250, 0.5); }
|
||||
50% { box-shadow: 0 0 40px rgba(248, 249, 250, 0.9); }
|
||||
100% { box-shadow: 0 0 25px rgba(248, 249, 250, 0.5); }
|
||||
}
|
||||
|
||||
@keyframes silverScan {
|
||||
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
@keyframes silverTextGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(255,255,255,0.8), 0 0 12px #f8f9fa; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(255,255,255,0.8), 0 0 25px #f8f9fa, 0 0 35px #d5dbdb; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(255,255,255,0.8), 0 0 12px #f8f9fa; }
|
||||
}
|
||||
|
||||
/* 4. 光扫描效果 - 荣耀黄金 */
|
||||
@keyframes goldGlow {
|
||||
0% { box-shadow: 0 0 30px rgba(241, 196, 15, 0.6); }
|
||||
50% { box-shadow: 0 0 50px rgba(241, 196, 15, 1.0); }
|
||||
100% { box-shadow: 0 0 30px rgba(241, 196, 15, 0.6); }
|
||||
}
|
||||
|
||||
@keyframes goldTextGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 15px #fff3cd; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 30px #fff3cd, 0 0 40px #f1c40f; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 15px #fff3cd; }
|
||||
}
|
||||
|
||||
@keyframes goldScan {
|
||||
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
/* 5. 光扫描效果 - 华贵铂金 */
|
||||
@keyframes platinumGlow {
|
||||
0% { box-shadow: 0 0 35px rgba(213, 219, 219, 0.7); }
|
||||
50% { box-shadow: 0 0 55px rgba(213, 219, 219, 1.1); }
|
||||
100% { box-shadow: 0 0 35px rgba(213, 219, 219, 0.7); }
|
||||
}
|
||||
|
||||
@keyframes platinumTextGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 18px #d5dbdb; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 35px #d5dbdb, 0 0 45px #aeb6bf; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 18px #d5dbdb; }
|
||||
}
|
||||
|
||||
@keyframes platinumScan {
|
||||
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
|
||||
100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
|
||||
}
|
||||
|
||||
/* 6. 闪烁爆发 - 璀璨钻石 */
|
||||
@keyframes sparkle {
|
||||
0% { transform: scale(1); box-shadow: 0 0 40px rgba(163, 228, 215, 0.8); }
|
||||
50% { transform: scale(1.02); box-shadow: 0 0 60px rgba(163, 228, 215, 1.2); }
|
||||
100% { transform: scale(1); box-shadow: 0 0 40px rgba(163, 228, 215, 0.8); }
|
||||
}
|
||||
|
||||
@keyframes sparkleText {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 20px #a3e4d7, 0 0 30px #1abc9c; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 30px #a3e4d7, 0 0 40px #1abc9c, 0 0 50px #16a085; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 20px #a3e4d7, 0 0 30px #1abc9c; }
|
||||
}
|
||||
|
||||
@keyframes diamondSpin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes sparkBurst {
|
||||
0% { transform: translate(-50%, -50%) scale(0); opacity: 1; }
|
||||
50% { transform: translate(-50%, -50%) scale(3); opacity: 0.8; }
|
||||
100% { transform: translate(-50%, -50%) scale(5); opacity: 0; }
|
||||
}
|
||||
|
||||
/* 7. 魔法脉冲 - 超凡大师 */
|
||||
@keyframes magicPulse {
|
||||
0% { transform: scale(1); box-shadow: 0 0 45px rgba(215, 189, 226, 0.9); }
|
||||
50% { transform: scale(1.03); box-shadow: 0 0 80px rgba(215, 189, 226, 1.5); }
|
||||
100% { transform: scale(1); box-shadow: 0 0 45px rgba(215, 189, 226, 0.9); }
|
||||
}
|
||||
|
||||
@keyframes magicTextGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 25px #d7bde2, 0 0 35px #8e44ad; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 40px #d7bde2, 0 0 60px #8e44ad, 0 0 80px #9b59b6; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 25px #d7bde2, 0 0 35px #8e44ad; }
|
||||
}
|
||||
|
||||
@keyframes magicRotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes magicOrbit {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(-360deg); }
|
||||
}
|
||||
|
||||
/* 8. 烈焰风暴 - 傲世宗师 */
|
||||
@keyframes flameGlow {
|
||||
0% { box-shadow: 0 0 60px rgba(250, 219, 216, 1.2), 0 0 100px rgba(231, 76, 60, 0.6); }
|
||||
50% { box-shadow: 0 0 80px rgba(250, 219, 216, 1.5), 0 0 120px rgba(231, 76, 60, 0.8); }
|
||||
100% { box-shadow: 0 0 60px rgba(250, 219, 216, 1.2), 0 0 100px rgba(231, 76, 60, 0.6); }
|
||||
}
|
||||
|
||||
@keyframes flameTextGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 40px #fadbd8, 0 0 60px #e74c3c, 0 0 80px #c0392b; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 60px #fadbd8, 0 0 80px #e74c3c, 0 0 100px #c0392b; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 40px #fadbd8, 0 0 60px #e74c3c, 0 0 80px #c0392b; }
|
||||
}
|
||||
|
||||
@keyframes flameRotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes flameOrbit {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(-360deg); }
|
||||
}
|
||||
|
||||
/* 通用动画 */
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 20px rgba(255,255,255,0.3); }
|
||||
100% { box-shadow: 0 0 40px rgba(255,255,255,0.6); }
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes rainbow {
|
||||
0% { filter: hue-rotate(0deg); }
|
||||
100% { filter: hue-rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes textGlow {
|
||||
0% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 18px #d5dbdb; }
|
||||
50% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 30px #d5dbdb, 0 0 40px #aeb6bf; }
|
||||
100% { text-shadow: 3px 3px 6px rgba(0,0,0,0.9), 0 0 18px #d5dbdb; }
|
||||
}
|
||||
|
||||
.description {
|
||||
margin-top: 30px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
max-width: 800px;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 测试区域样式 */
|
||||
.test-section {
|
||||
margin-top: 50px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.test-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.test-button {
|
||||
padding: 8px 16px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.test-button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.test-result {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 60px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.test-title {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="title">🏆 称号等级系统预览 🏆</h1>
|
||||
|
||||
<div class="ranks-container">
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-1">
|
||||
<div class="rank-text">坚韧黑铁</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">坚韧黑铁</div>
|
||||
<div class="rank-score">0 - 999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-2">
|
||||
<div class="rank-text">英勇黄铜</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">英勇黄铜</div>
|
||||
<div class="rank-score">1000 - 1999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-3">
|
||||
<div class="rank-text">不屈白银</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">不屈白银</div>
|
||||
<div class="rank-score">2000 - 2999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-4">
|
||||
<div class="rank-text">荣耀黄金</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">荣耀黄金</div>
|
||||
<div class="rank-score">3000 - 3999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-5">
|
||||
<div class="rank-text">华贵铂金</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">华贵铂金</div>
|
||||
<div class="rank-score">4000 - 4999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-6">
|
||||
<div class="rank-text">璀璨钻石</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">璀璨钻石</div>
|
||||
<div class="rank-score">5000 - 5999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-7">
|
||||
<div class="rank-text">超凡大师</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">超凡大师</div>
|
||||
<div class="rank-score">6000 - 6999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-8">
|
||||
<div class="rank-text">傲世宗师</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">傲世宗师</div>
|
||||
<div class="rank-score">7000 - 7999 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rank-item">
|
||||
<div class="rank-badge rank-9">
|
||||
<div class="rank-text">最强王者</div>
|
||||
</div>
|
||||
<div class="rank-info">
|
||||
<div class="rank-name">最强王者</div>
|
||||
<div class="rank-score">8000+ 积分</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 测试区域 -->
|
||||
<div class="test-section">
|
||||
<h2 class="test-title">🎯 徽章生成器测试</h2>
|
||||
<div class="test-controls">
|
||||
<button class="test-button" onclick="testBadge(1)">坚韧黑铁</button>
|
||||
<button class="test-button" onclick="testBadge(2)">英勇黄铜</button>
|
||||
<button class="test-button" onclick="testBadge(3)">不屈白银</button>
|
||||
<button class="test-button" onclick="testBadge(4)">荣耀黄金</button>
|
||||
<button class="test-button" onclick="testBadge(5)">华贵铂金</button>
|
||||
<button class="test-button" onclick="testBadge(6)">璀璨钻石</button>
|
||||
<button class="test-button" onclick="testBadge(7)">超凡大师</button>
|
||||
<button class="test-button" onclick="testBadge(8)">傲世宗师</button>
|
||||
<button class="test-button" onclick="testBadge(9)">最强王者</button>
|
||||
</div>
|
||||
<div class="test-result" id="testResult">
|
||||
<p style="color: white; opacity: 0.7;">点击上方按钮测试徽章生成器</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 称号等级系统 - 徽章生成器
|
||||
* 根据传入的ID(1-9)生成对应的徽章HTML元素,包含完整的动画效果
|
||||
*/
|
||||
class RankBadgeGenerator {
|
||||
constructor() {
|
||||
// 称号数据配置
|
||||
this.rankData = {
|
||||
1: { name: '坚韧黑铁', scoreRange: '0 - 999 积分' },
|
||||
2: { name: '英勇黄铜', scoreRange: '1000 - 1999 积分' },
|
||||
3: { name: '不屈白银', scoreRange: '2000 - 2999 积分' },
|
||||
4: { name: '荣耀黄金', scoreRange: '3000 - 3999 积分' },
|
||||
5: { name: '华贵铂金', scoreRange: '4000 - 4999 积分' },
|
||||
6: { name: '璀璨钻石', scoreRange: '5000 - 5999 积分' },
|
||||
7: { name: '超凡大师', scoreRange: '6000 - 6999 积分' },
|
||||
8: { name: '傲世宗师', scoreRange: '7000 - 7999 积分' },
|
||||
9: { name: '最强王者', scoreRange: '8000+ 积分' }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取徽章HTML元素
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
* @returns {HTMLElement|null} 返回包含动画效果的徽章元素
|
||||
*/
|
||||
getBadgeById(id) {
|
||||
// 验证ID有效性
|
||||
if (!this.isValidId(id)) {
|
||||
console.error(`无效的徽章ID: ${id},请传入1-9之间的数字`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const rankInfo = this.rankData[id];
|
||||
|
||||
// 创建徽章容器
|
||||
const badgeContainer = document.createElement('div');
|
||||
badgeContainer.className = 'rank-item';
|
||||
|
||||
// 创建徽章元素
|
||||
const badge = document.createElement('div');
|
||||
badge.className = `rank-badge rank-${id}`;
|
||||
|
||||
// 创建徽章文本
|
||||
const badgeText = document.createElement('div');
|
||||
badgeText.className = 'rank-text';
|
||||
badgeText.textContent = rankInfo.name;
|
||||
|
||||
// 创建徽章信息
|
||||
const rankInfoDiv = document.createElement('div');
|
||||
rankInfoDiv.className = 'rank-info';
|
||||
|
||||
const rankName = document.createElement('div');
|
||||
rankName.className = 'rank-name';
|
||||
rankName.textContent = rankInfo.name;
|
||||
|
||||
const rankScore = document.createElement('div');
|
||||
rankScore.className = 'rank-score';
|
||||
rankScore.textContent = rankInfo.scoreRange;
|
||||
|
||||
// 组装元素
|
||||
badge.appendChild(badgeText);
|
||||
rankInfoDiv.appendChild(rankName);
|
||||
rankInfoDiv.appendChild(rankScore);
|
||||
badgeContainer.appendChild(badge);
|
||||
badgeContainer.appendChild(rankInfoDiv);
|
||||
|
||||
return badgeContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅获取徽章部分(不包含信息文本)
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
* @returns {HTMLElement|null} 返回纯徽章元素
|
||||
*/
|
||||
getBadgeOnly(id) {
|
||||
if (!this.isValidId(id)) {
|
||||
console.error(`无效的徽章ID: ${id},请传入1-9之间的数字`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const rankInfo = this.rankData[id];
|
||||
|
||||
// 创建徽章元素
|
||||
const badge = document.createElement('div');
|
||||
badge.className = `rank-badge rank-${id}`;
|
||||
|
||||
// 创建徽章文本
|
||||
const badgeText = document.createElement('div');
|
||||
badgeText.className = 'rank-text';
|
||||
badgeText.textContent = rankInfo.name;
|
||||
|
||||
badge.appendChild(badgeText);
|
||||
|
||||
return badge;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取徽章信息(不包含HTML元素)
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
* @returns {Object|null} 返回徽章信息对象
|
||||
*/
|
||||
getBadgeInfo(id) {
|
||||
if (!this.isValidId(id)) {
|
||||
console.error(`无效的徽章ID: ${id},请传入1-9之间的数字`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: id,
|
||||
name: this.rankData[id].name,
|
||||
scoreRange: this.rankData[id].scoreRange,
|
||||
className: `rank-${id}`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证ID是否有效
|
||||
* @param {number} id - 要验证的ID
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
isValidId(id) {
|
||||
return Number.isInteger(id) && id >= 1 && id <= 9;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有徽章信息
|
||||
* @returns {Array} 所有徽章信息数组
|
||||
*/
|
||||
getAllBadgeInfo() {
|
||||
return Object.keys(this.rankData).map(id => this.getBadgeInfo(parseInt(id)));
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
const rankBadgeGenerator = new RankBadgeGenerator();
|
||||
|
||||
/**
|
||||
* 便捷方法:根据ID获取徽章
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
* @returns {HTMLElement|null} 徽章元素
|
||||
*/
|
||||
function getBadgeById(id) {
|
||||
return rankBadgeGenerator.getBadgeById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:仅获取徽章部分
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
* @returns {HTMLElement|null} 纯徽章元素
|
||||
*/
|
||||
function getBadgeOnly(id) {
|
||||
return rankBadgeGenerator.getBadgeOnly(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷方法:获取徽章信息
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
* @returns {Object|null} 徽章信息
|
||||
*/
|
||||
function getBadgeInfo(id) {
|
||||
return rankBadgeGenerator.getBadgeInfo(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试函数:在测试区域显示指定徽章
|
||||
* @param {number} id - 徽章ID (1-9)
|
||||
*/
|
||||
function testBadge(id) {
|
||||
const testResult = document.getElementById('testResult');
|
||||
testResult.innerHTML = ''; // 清空之前的结果
|
||||
|
||||
const badge = getBadgeById(id);
|
||||
if (badge) {
|
||||
testResult.appendChild(badge);
|
||||
console.log(`成功生成徽章 ID: ${id}`, getBadgeInfo(id));
|
||||
} else {
|
||||
testResult.innerHTML = `<p style="color: #ff6b6b;">生成徽章失败,ID: ${id}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例(在控制台中测试)
|
||||
console.log('=== 称号等级系统徽章生成器 ===');
|
||||
console.log('使用方法:');
|
||||
console.log('1. getBadgeById(id) - 获取完整徽章(包含信息)');
|
||||
console.log('2. getBadgeOnly(id) - 仅获取徽章部分');
|
||||
console.log('3. getBadgeInfo(id) - 获取徽章信息');
|
||||
console.log('4. testBadge(id) - 在页面中测试显示');
|
||||
console.log('');
|
||||
console.log('示例:');
|
||||
console.log('const badge = getBadgeById(9); // 获取最强王者徽章');
|
||||
console.log('document.body.appendChild(badge); // 添加到页面');
|
||||
|
||||
// 页面加载完成后的初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('称号等级系统已加载,可以使用徽章生成器了!');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
scratch-blocks/.editorconfig
Normal file
10
scratch-blocks/.editorconfig
Normal file
@@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
18
scratch-blocks/.eslintignore
Normal file
18
scratch-blocks/.eslintignore
Normal file
@@ -0,0 +1,18 @@
|
||||
*_compressed*.js
|
||||
*_uncompressed*.js
|
||||
/msg/*
|
||||
/core/css.js
|
||||
/i18n/*
|
||||
/tests/jsunit/*
|
||||
/tests/workspace_svg/*
|
||||
/tests/blocks/*
|
||||
/demos/*
|
||||
/accessible/*
|
||||
/appengine/*
|
||||
/shim/*
|
||||
/dist/*
|
||||
/gh-pages/*
|
||||
/webpack.config.js
|
||||
/build/*
|
||||
|
||||
/github-pages/*
|
||||
60
scratch-blocks/.eslintrc
Normal file
60
scratch-blocks/.eslintrc
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"rules": {
|
||||
"curly": ["error", "multi-line"],
|
||||
"eol-last": ["error"],
|
||||
"indent": [
|
||||
"error", 2, # Blockly/Google use 2-space indents
|
||||
# Blockly/Google uses +4 space indents for line continuations.
|
||||
{
|
||||
"SwitchCase": 1,
|
||||
"MemberExpression": 2,
|
||||
"ObjectExpression": 1,
|
||||
"FunctionDeclaration": {
|
||||
"body": 1,
|
||||
"parameters": 2
|
||||
},
|
||||
"FunctionExpression": {
|
||||
"body": 1,
|
||||
"parameters": 2
|
||||
},
|
||||
"CallExpression": {
|
||||
"arguments": 2
|
||||
},
|
||||
# Ignore default rules for ternary expressions.
|
||||
"ignoredNodes": ["ConditionalExpression"]
|
||||
}
|
||||
],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"max-len": ["error", 120, 4],
|
||||
"no-trailing-spaces": ["error", { "skipBlankLines": true }],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"args": "after-used",
|
||||
# Ignore vars starting with an underscore.
|
||||
"varsIgnorePattern": "^_",
|
||||
# Ignore arguments starting with an underscore.
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"no-use-before-define": ["error"],
|
||||
"quotes": ["off"], # Blockly mixes single and double quotes
|
||||
"semi": ["error", "always"],
|
||||
"space-before-function-paren": ["error", "never"], # Blockly doesn't have space before function paren
|
||||
"space-infix-ops": ["error"],
|
||||
"strict": ["off"], # Blockly uses 'use strict' in files
|
||||
"no-cond-assign": ["off"], # Blockly often uses cond-assignment in loops
|
||||
"no-redeclare": ["off"], # Closure style allows redeclarations
|
||||
"valid-jsdoc": ["error", {"requireReturn": false}],
|
||||
"no-console": ["off"],
|
||||
"no-constant-condition": ["off"]
|
||||
},
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"Blockly": true, # Blockly global
|
||||
"goog": true, # goog closure libraries/includes
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
15
scratch-blocks/.github/ISSUE_TEMPLATE.md
vendored
Normal file
15
scratch-blocks/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
### Expected Behavior
|
||||
|
||||
_Please describe what should happen_
|
||||
|
||||
### Actual Behavior
|
||||
|
||||
_Describe what actually happens_
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
_Explain what someone needs to do in order to see what's described in *Actual behavior* above_
|
||||
|
||||
### Operating System and Browser
|
||||
|
||||
_e.g. Mac OS 10.11.6 Safari 10.0_
|
||||
15
scratch-blocks/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
15
scratch-blocks/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
### Resolves
|
||||
|
||||
_What Github issue does this resolve (please include link)?_
|
||||
|
||||
### Proposed Changes
|
||||
|
||||
_Describe what this Pull Request does_
|
||||
|
||||
### Reason for Changes
|
||||
|
||||
_Explain why these changes should be made_
|
||||
|
||||
### Test Coverage
|
||||
|
||||
_Please show how you have added tests to cover your changes_
|
||||
18
scratch-blocks/.github/workflows/build.yml
vendored
Normal file
18
scratch-blocks/.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run test:lint
|
||||
48
scratch-blocks/.gitignore
vendored
Normal file
48
scratch-blocks/.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# NPM
|
||||
/node_modules
|
||||
npm-*
|
||||
|
||||
# Localization / I18N
|
||||
common.pyc
|
||||
.settings
|
||||
.project
|
||||
*.pyc
|
||||
*.komodoproject
|
||||
/nbproject/private/
|
||||
|
||||
# Unused by scratch-blocks
|
||||
dart_compressed.js
|
||||
javascript_compressed.js
|
||||
lua_compressed.js
|
||||
php_compressed.js
|
||||
python_compressed.js
|
||||
|
||||
# Editor
|
||||
.vscode
|
||||
|
||||
/accessible/*
|
||||
/dist
|
||||
/msg/js/*
|
||||
!/msg/js/en.js
|
||||
/msg/json/*
|
||||
!/msg/json/en.json
|
||||
/blockly_compressed_horizontal.js
|
||||
/blockly_compressed_vertical.js
|
||||
/blockly_uncompressed_horizontal.js
|
||||
/blockly_uncompressed_vertical.js
|
||||
/blocks_compressed_horizontal.js
|
||||
/blocks_compressed_vertical.js
|
||||
/blocks_compressed.js
|
||||
/gh-pages/main.js
|
||||
/gh-pages/playgrounds
|
||||
/gh-pages/Gemfile.lock
|
||||
/gh-pages/closure-library
|
||||
/gh-pages/_site
|
||||
/*compiler*.jar
|
||||
/local_blockly_compressed_vertical.js
|
||||
/chromedriver
|
||||
# --flagfiles used on Windows
|
||||
/*.config
|
||||
26
scratch-blocks/.npmignore
Normal file
26
scratch-blocks/.npmignore
Normal file
@@ -0,0 +1,26 @@
|
||||
# Development files
|
||||
/.circleci
|
||||
.eslintrc
|
||||
/.editorconfig
|
||||
/.eslintignore
|
||||
/.gitattributes
|
||||
/.github
|
||||
/.tx
|
||||
/tests
|
||||
/webpack.config.js
|
||||
|
||||
# Localization / I18N
|
||||
common.pyc
|
||||
.settings
|
||||
.project
|
||||
*.pyc
|
||||
*.komodoproject
|
||||
/nbproject/private/
|
||||
|
||||
/accessible/*
|
||||
|
||||
# Build created files
|
||||
/gh-pages
|
||||
|
||||
# Exclude already built packages from testing with npm pack
|
||||
/scratch-blocks-*.{tar,tgz}
|
||||
1
scratch-blocks/.npmrc
Normal file
1
scratch-blocks/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
1
scratch-blocks/.nvmrc
Normal file
1
scratch-blocks/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
v18
|
||||
9
scratch-blocks/.tx/config
Normal file
9
scratch-blocks/.tx/config
Normal file
@@ -0,0 +1,9 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = zh_CN:zh-cn, zh_TW:zh-tw, pt_BR:pt-br, es_419:es-419
|
||||
|
||||
[scratch-editor.blocks]
|
||||
file_filter = msg/json/<lang>.json
|
||||
source_file = msg/json/en.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
674
scratch-blocks/LICENSE
Normal file
674
scratch-blocks/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
53
scratch-blocks/README.md
Normal file
53
scratch-blocks/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# TurboWarp/scratch-blocks
|
||||
|
||||
## Playgrounds
|
||||
|
||||
- **Vertical blocks**: https://turbowarp.github.io/scratch-blocks/tests/vertical_playground_compressed.html
|
||||
|
||||
## Local development
|
||||
|
||||
Requires Node.js (16 or later), Python (2 or 3), and Java. It is known to work in these environments but should work in many others:
|
||||
|
||||
- Windows 10, Python 3.12.1 (Microsoft Store), Node.js 20.10.0 (nodejs.org installer), Java 11 (Temurin-11.0.21+9)
|
||||
- macOS 14.2.1, Python 3.11.6 (Apple), Node.js 20.10.0 (installed manually), Java 21 (Temurin-21.0.1+12)
|
||||
- Ubuntu 22.04, Python 3.10.12 (python3 package), Node.js 20.10.0 (installed manually), Java 11 (openjdk-11-jre package)
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
```
|
||||
|
||||
Open tests/vertical_playground.html in a browser for development. You don't need to rebuild compressed versions for most changes. Open tests/vertical_playground_compressed.html instead to test if the compressed versions built properly.
|
||||
|
||||
To re-build compressed versions, run:
|
||||
|
||||
```sh
|
||||
npm run prepublish
|
||||
```
|
||||
|
||||
scratch-gui development server must be restarted to update linked scratch-blocks.
|
||||
|
||||
<!--
|
||||
#### Scratch Blocks is a library for building creative computing interfaces.
|
||||
[](https://dl.circleci.com/status-badge/redirect/gh/LLK/scratch-blocks/tree/develop)
|
||||
|
||||

|
||||
|
||||
<!--
|
||||
## Introduction
|
||||
Scratch Blocks is a fork of Google's [Blockly](https://github.com/google/blockly) project that provides a design specification and codebase for building creative computing interfaces. Together with the [Scratch Virtual Machine (VM)](https://github.com/LLK/scratch-vm) this codebase allows for the rapid design and development of visual programming interfaces. Unlike [Blockly](https://github.com/google/blockly), Scratch Blocks does not use [code generators](https://developers.google.com/blockly/guides/configure/web/code-generators), but rather leverages the [Scratch Virtual Machine](https://github.com/LLK/scratch-vm) to create highly dynamic, interactive programming environments.
|
||||
|
||||
*This project is in active development and should be considered a "developer preview" at this time.*
|
||||
|
||||
## Two Types of Blocks
|
||||

|
||||
|
||||
Scratch Blocks brings together two different programming "grammars" that the Scratch Team has designed and continued to refine over the past decade. The standard [Scratch](https://scratch.mit.edu) grammar uses blocks that snap together vertically, much like LEGO bricks. For our [ScratchJr](https://scratchjr.org) software, intended for younger children, we developed blocks that are labelled with icons rather than words, and snap together horizontally rather than vertically. We have found that the horizontal grammar is not only friendlier for beginning programmers but also better suited for devices with small screens.
|
||||
|
||||
## Documentation
|
||||
The "getting started" guide including [FAQ](https://scratch.mit.edu/developers#faq) and [design documentation](https://github.com/LLK/scratch-blocks/wiki/Design) can be found in the [wiki](https://github.com/LLK/scratch-blocks/wiki).
|
||||
|
||||
## 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!
|
||||
-->
|
||||
1
scratch-blocks/TRADEMARK
Normal file
1
scratch-blocks/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.
|
||||
61
scratch-blocks/blocks_common/colour.js
Normal file
61
scratch-blocks/blocks_common/colour.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Colour blocks for Blockly.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.colour');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
/**
|
||||
* Pick a random colour.
|
||||
* @return {string} #RRGGBB for random colour.
|
||||
*/
|
||||
function randomColour() {
|
||||
var num = Math.floor(Math.random() * Math.pow(2, 24));
|
||||
return '#' + ('00000' + num.toString(16)).substr(-6);
|
||||
}
|
||||
|
||||
Blockly.Blocks['colour_picker'] = {
|
||||
/**
|
||||
* Block for colour picker.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_colour_slider",
|
||||
"name": "COLOUR",
|
||||
"colour": randomColour()
|
||||
}
|
||||
],
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"output": "Colour"
|
||||
});
|
||||
}
|
||||
};
|
||||
159
scratch-blocks/blocks_common/math.js
Normal file
159
scratch-blocks/blocks_common/math.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Math blocks for Blockly.
|
||||
* @author q.neutron@gmail.com (Quynh Neutron)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.math');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
Blockly.Blocks['math_number'] = {
|
||||
/**
|
||||
* Block for generic numeric value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "NUM",
|
||||
"value": "0"
|
||||
}
|
||||
],
|
||||
"output": "Number",
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['math_integer'] = {
|
||||
/**
|
||||
* Block for integer value (no decimal, + or -).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "NUM",
|
||||
"precision": 1
|
||||
}
|
||||
],
|
||||
"output": "Number",
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['math_whole_number'] = {
|
||||
/**
|
||||
* Block for whole number value, no negatives or decimals.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "NUM",
|
||||
"min": 0,
|
||||
"precision": 1
|
||||
}
|
||||
],
|
||||
"output": "Number",
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['math_positive_number'] = {
|
||||
/**
|
||||
* Block for positive number value, with decimal.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "NUM",
|
||||
"min": 0
|
||||
}
|
||||
],
|
||||
"output": "Number",
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['math_angle'] = {
|
||||
/**
|
||||
* Block for angle picker.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_angle",
|
||||
"name": "NUM",
|
||||
"value": 90
|
||||
}
|
||||
],
|
||||
"output": "Number",
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
54
scratch-blocks/blocks_common/matrix.js
Normal file
54
scratch-blocks/blocks_common/matrix.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Matrix blocks for Blockly.
|
||||
* @author khanning@gmail.com (Kreg Hanning)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.matrix');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
Blockly.Blocks['matrix'] = {
|
||||
/**
|
||||
* Block for matrix value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_matrix",
|
||||
"name": "MATRIX"
|
||||
}
|
||||
],
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"output": "Number",
|
||||
"extensions": ["colours_pen"]
|
||||
});
|
||||
}
|
||||
};
|
||||
58
scratch-blocks/blocks_common/note.js
Normal file
58
scratch-blocks/blocks_common/note.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Note block.
|
||||
* @author ericr@media.mit.edu (Eric Rosenbaum)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.note');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
Blockly.Blocks['note'] = {
|
||||
/**
|
||||
* Block for musical note value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_note",
|
||||
"name": "NOTE",
|
||||
"value": 60
|
||||
}
|
||||
],
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"output": "Number",
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
57
scratch-blocks/blocks_common/text.js
Normal file
57
scratch-blocks/blocks_common/text.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Text blocks for Blockly.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.texts');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
Blockly.Blocks['text'] = {
|
||||
/**
|
||||
* Block for text value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_input",
|
||||
"name": "TEXT"
|
||||
}
|
||||
],
|
||||
"output": "String",
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"colour": Blockly.Colours.textField,
|
||||
"colourSecondary": Blockly.Colours.textField,
|
||||
"colourTertiary": Blockly.Colours.textField,
|
||||
"colourQuaternary": Blockly.Colours.textField
|
||||
});
|
||||
}
|
||||
};
|
||||
212
scratch-blocks/blocks_horizontal/control.js
Normal file
212
scratch-blocks/blocks_horizontal/control.js
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Control blocks for Scratch (Horizontal)
|
||||
* @author ascii@media.mit.edu <Andrew Sliwinski>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.control');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
Blockly.Blocks['control_repeat'] = {
|
||||
/**
|
||||
* Block for repeat n times (external number).
|
||||
* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#so57n9
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_repeat",
|
||||
"message0": "%1 %2 %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
},
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/control_repeat.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TIMES",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.control,
|
||||
"colour": Blockly.Colours.control.primary,
|
||||
"colourSecondary": Blockly.Colours.control.secondary,
|
||||
"colourTertiary": Blockly.Colours.control.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.control.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_forever'] = {
|
||||
/**
|
||||
* Block for repeat n times (external number).
|
||||
* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#5eke39
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_forever",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
},
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/control_forever.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"category": Blockly.Categories.control,
|
||||
"colour": Blockly.Colours.control.primary,
|
||||
"colourSecondary": Blockly.Colours.control.secondary,
|
||||
"colourTertiary": Blockly.Colours.control.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.control.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_repeat'] = {
|
||||
/**
|
||||
* Block for repeat n times (external number).
|
||||
* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#so57n9
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_repeat",
|
||||
"message0": "%1 %2 %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
},
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/control_repeat.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TIMES",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.control,
|
||||
"colour": Blockly.Colours.control.primary,
|
||||
"colourSecondary": Blockly.Colours.control.secondary,
|
||||
"colourTertiary": Blockly.Colours.control.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.control.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_stop'] = {
|
||||
/**
|
||||
* Block for stop all scripts.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_stop",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/control_stop.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Stop"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"category": Blockly.Categories.control,
|
||||
"colour": Blockly.Colours.control.primary,
|
||||
"colourSecondary": Blockly.Colours.control.secondary,
|
||||
"colourTertiary": Blockly.Colours.control.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.control.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_wait'] = {
|
||||
/**
|
||||
* Block to wait (pause) stack.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_wait",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/control_wait.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Wait"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DURATION",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.control,
|
||||
"colour": Blockly.Colours.control.primary,
|
||||
"colourSecondary": Blockly.Colours.control.secondary,
|
||||
"colourTertiary": Blockly.Colours.control.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.control.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
139
scratch-blocks/blocks_horizontal/default_toolbox.js
Normal file
139
scratch-blocks/blocks_horizontal/default_toolbox.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.defaultToolbox');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
/**
|
||||
* @fileoverview Provide a default toolbox XML.
|
||||
*/
|
||||
|
||||
Blockly.Blocks.defaultToolbox = '<xml id="toolbox-categories" style="display: none">' +
|
||||
'<category name="Events">' +
|
||||
'<block type="event_whenflagclicked"></block>' +
|
||||
'<block type="event_whenbroadcastreceived">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_whenbroadcast">' +
|
||||
'<field name="CHOICE">blue</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="event_broadcast">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_broadcast">' +
|
||||
'<field name="CHOICE">blue</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</category>' +
|
||||
'<category name="Control">' +
|
||||
'<block type="control_forever"></block>' +
|
||||
'<block type="control_repeat">' +
|
||||
'<value name="TIMES">' +
|
||||
'<shadow type="math_whole_number">' +
|
||||
'<field name="NUM">4</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="control_stop"></block>' +
|
||||
'<block type="control_wait">' +
|
||||
'<value name="DURATION">' +
|
||||
'<shadow type="math_positive_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</category>' +
|
||||
'<category name="Wedo">' +
|
||||
'<block type="wedo_setcolor">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_wedo_setcolor">' +
|
||||
'<field name="CHOICE">mystery</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="wedo_motorclockwise">' +
|
||||
'<value name="DURATION">' +
|
||||
'<shadow type="math_positive_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="wedo_motorcounterclockwise">' +
|
||||
'<value name="DURATION">' +
|
||||
'<shadow type="math_positive_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="wedo_motorspeed">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_wedo_motorspeed">' +
|
||||
'<field name="CHOICE">fast</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="wedo_whentilt">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_wedo_whentilt">' +
|
||||
'<field name="CHOICE">forward</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="wedo_whendistanceclose"></block>' +
|
||||
'</category>' +
|
||||
'</xml>';
|
||||
|
||||
Blockly.Blocks.defaultToolboxSimple = '<xml id="toolbox-simple" style="display: none">' +
|
||||
'<block type="event_whenflagclicked"></block>' +
|
||||
'<block type="event_whenbroadcastreceived">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_whenbroadcast">' +
|
||||
'<field name="CHOICE">blue</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="event_broadcast">' +
|
||||
'<value name="CHOICE">' +
|
||||
'<shadow type="dropdown_broadcast">' +
|
||||
'<field name="CHOICE">blue</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="control_forever"></block>' +
|
||||
'<block type="control_repeat">' +
|
||||
'<value name="TIMES">' +
|
||||
'<shadow type="math_whole_number">' +
|
||||
'<field name="NUM">4</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="control_stop"></block>' +
|
||||
'<block type="control_wait">' +
|
||||
'<value name="DURATION">' +
|
||||
'<shadow type="math_positive_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</xml>';
|
||||
190
scratch-blocks/blocks_horizontal/event.js
Normal file
190
scratch-blocks/blocks_horizontal/event.js
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Control blocks for Scratch (Horizontal)
|
||||
* @author ascii@media.mit.edu <Andrew Sliwinski>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.event');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
Blockly.Blocks['event_whenflagclicked'] = {
|
||||
/**
|
||||
* Block for when flag clicked.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_whenflagclicked",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/event_whenflagclicked.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "When green flag clicked",
|
||||
"flip_rtl": true
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.event,
|
||||
"colour": Blockly.Colours.event.primary,
|
||||
"colourSecondary": Blockly.Colours.event.secondary,
|
||||
"colourTertiary": Blockly.Colours.event.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.event.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['dropdown_whenbroadcast'] = {
|
||||
/**
|
||||
* Block for when broadcast dropdown (used for shadow).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldIconMenu(
|
||||
[
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_when-broadcast-received_blue.svg',
|
||||
value: 'blue', width: 48, height: 48, alt: 'Blue'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_when-broadcast-received_green.svg',
|
||||
value: 'green', width: 48, height: 48, alt: 'Green'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_when-broadcast-received_coral.svg',
|
||||
value: 'coral', width: 48, height: 48, alt: 'Coral'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_when-broadcast-received_magenta.svg',
|
||||
value: 'magenta', width: 48, height: 48, alt: 'Magenta'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_when-broadcast-received_orange.svg',
|
||||
value: 'orange', width: 48, height: 48, alt: 'Orange'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_when-broadcast-received_purple.svg',
|
||||
value: 'purple', width: 48, height: 48, alt: 'Purple'}
|
||||
]), 'CHOICE');
|
||||
this.setOutput(true);
|
||||
this.setColour(Blockly.Colours.event.primary,
|
||||
Blockly.Colours.event.secondary,
|
||||
Blockly.Colours.event.tertiary,
|
||||
Blockly.Colours.event.quaternary
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenbroadcastreceived'] = {
|
||||
/**
|
||||
* Block for when broadcast received.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_whenbroadcastreceived",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/event_when-broadcast-received_blue.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Broadcast received"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHOICE"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.event,
|
||||
"colour": Blockly.Colours.event.primary,
|
||||
"colourSecondary": Blockly.Colours.event.secondary,
|
||||
"colourTertiary": Blockly.Colours.event.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.event.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['dropdown_broadcast'] = {
|
||||
/**
|
||||
* Block for broadcast dropdown (used for shadow).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldIconMenu(
|
||||
[
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_broadcast_blue.svg',
|
||||
value: 'blue', width: 48, height: 48, alt: 'Blue'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_broadcast_green.svg',
|
||||
value: 'green', width: 48, height: 48, alt: 'Green'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_broadcast_coral.svg',
|
||||
value: 'coral', width: 48, height: 48, alt: 'Coral'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_broadcast_magenta.svg',
|
||||
value: 'magenta', width: 48, height: 48, alt: 'Magenta'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_broadcast_orange.svg',
|
||||
value: 'orange', width: 48, height: 48, alt: 'Orange'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/event_broadcast_purple.svg',
|
||||
value: 'purple', width: 48, height: 48, alt: 'Purple'}
|
||||
]), 'CHOICE');
|
||||
this.setOutput(true);
|
||||
this.setColour(Blockly.Colours.event.primary,
|
||||
Blockly.Colours.event.secondary,
|
||||
Blockly.Colours.event.tertiary,
|
||||
Blockly.Colours.event.quaternary
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_broadcast'] = {
|
||||
/**
|
||||
* Block to send a broadcast.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_broadcast",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/event_broadcast_blue.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Broadcast"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHOICE"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.event,
|
||||
"colour": Blockly.Colours.event.primary,
|
||||
"colourSecondary": Blockly.Colours.event.secondary,
|
||||
"colourTertiary": Blockly.Colours.event.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.event.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
325
scratch-blocks/blocks_horizontal/wedo.js
Normal file
325
scratch-blocks/blocks_horizontal/wedo.js
Normal file
@@ -0,0 +1,325 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Wedo blocks for Scratch (Horizontal)
|
||||
* @author ascii@media.mit.edu <Andrew Sliwinski>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.wedo');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
Blockly.Blocks['dropdown_wedo_setcolor'] = {
|
||||
/**
|
||||
* Block for set color drop-down (used for shadow).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldIconMenu(
|
||||
[
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_mystery.svg',
|
||||
value: 'mystery', width: 48, height: 48, alt: 'Mystery'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_yellow.svg',
|
||||
value: 'yellow', width: 48, height: 48, alt: 'Yellow'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_orange.svg',
|
||||
value: 'orange', width: 48, height: 48, alt: 'Orange'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_coral.svg',
|
||||
value: 'coral', width: 48, height: 48, alt: 'Coral'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_magenta.svg',
|
||||
value: 'magenta', width: 48, height: 48, alt: 'Magenta'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_purple.svg',
|
||||
value: 'purple', width: 48, height: 48, alt: 'Purple'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_blue.svg',
|
||||
value: 'blue', width: 48, height: 48, alt: 'Blue'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_green.svg',
|
||||
value: 'green', width: 48, height: 48, alt: 'Green'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/set-led_white.svg',
|
||||
value: 'white', width: 48, height: 48, alt: 'White'}
|
||||
]), 'CHOICE');
|
||||
this.setOutput(true);
|
||||
this.setColour(Blockly.Colours.looks.primary,
|
||||
Blockly.Colours.looks.secondary,
|
||||
Blockly.Colours.looks.tertiary,
|
||||
Blockly.Colours.looks.quaternary
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['wedo_setcolor'] = {
|
||||
/**
|
||||
* Block to set color of LED
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "wedo_setcolor",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/set-led_blue.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Set LED Color"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHOICE"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.looks,
|
||||
"colour": Blockly.Colours.looks.primary,
|
||||
"colourSecondary": Blockly.Colours.looks.secondary,
|
||||
"colourTertiary": Blockly.Colours.looks.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.looks.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['wedo_motorclockwise'] = {
|
||||
/**
|
||||
* Block to spin motor clockwise.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "wedo_motorclockwise",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/wedo_motor-clockwise.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Turn motor clockwise"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DURATION",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.motion,
|
||||
"colour": Blockly.Colours.motion.primary,
|
||||
"colourSecondary": Blockly.Colours.motion.secondary,
|
||||
"colourTertiary": Blockly.Colours.motion.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.motion.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['wedo_motorcounterclockwise'] = {
|
||||
/**
|
||||
* Block to spin motor counter-clockwise.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "wedo_motorcounterclockwise",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/wedo_motor-counterclockwise.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Turn motor counter-clockwise"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DURATION",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.motion,
|
||||
"colour": Blockly.Colours.motion.primary,
|
||||
"colourSecondary": Blockly.Colours.motion.secondary,
|
||||
"colourTertiary": Blockly.Colours.motion.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.motion.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['dropdown_wedo_motorspeed'] = {
|
||||
/**
|
||||
* Block for motor speed drop-down (used for shadow).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldIconMenu(
|
||||
[
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_motor-speed_slow.svg',
|
||||
value: 'slow', width: 48, height: 48, alt: 'Slow'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_motor-speed_med.svg',
|
||||
value: 'medium', width: 48, height: 48, alt: 'Medium'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_motor-speed_fast.svg',
|
||||
value: 'fast', width: 48, height: 48, alt: 'Fast'}
|
||||
]), 'CHOICE');
|
||||
this.setOutput(true);
|
||||
this.setColour(Blockly.Colours.motion.primary,
|
||||
Blockly.Colours.motion.secondary,
|
||||
Blockly.Colours.motion.tertiary,
|
||||
Blockly.Colours.motion.quaternary
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['wedo_motorspeed'] = {
|
||||
/**
|
||||
* Block to set motor speed.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "wedo_motorspeed",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/wedo_motor-speed_fast.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "Motor Speed"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHOICE"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.motion,
|
||||
"colour": Blockly.Colours.motion.primary,
|
||||
"colourSecondary": Blockly.Colours.motion.secondary,
|
||||
"colourTertiary": Blockly.Colours.motion.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.motion.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['dropdown_wedo_whentilt'] = {
|
||||
/**
|
||||
* Block for when tilt drop-down (used for shadow).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldIconMenu(
|
||||
[
|
||||
{type: 'placeholder', width: 48, height: 48},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_when-tilt-forward.svg',
|
||||
value: 'forward', width: 48, height: 48, alt: 'Tilt forward'},
|
||||
{type: 'placeholder', width: 48, height: 48},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_when-tilt-left.svg',
|
||||
value: 'left', width: 48, height: 48, alt: 'Tilt left'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_when-tilt.svg',
|
||||
value: 'any', width: 48, height: 48, alt: 'Tilt any'},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_when-tilt-right.svg',
|
||||
value: 'right', width: 48, height: 48, alt: 'Tilt right'},
|
||||
{type: 'placeholder', width: 48, height: 48},
|
||||
{src: Blockly.mainWorkspace.options.pathToMedia + 'icons/wedo_when-tilt-backward.svg',
|
||||
value: 'backward', width: 48, height: 48, alt: 'Tilt backward'}
|
||||
]), 'CHOICE');
|
||||
this.setOutput(true);
|
||||
this.setColour(Blockly.Colours.event.primary,
|
||||
Blockly.Colours.event.secondary,
|
||||
Blockly.Colours.event.tertiary,
|
||||
Blockly.Colours.event.quaternary
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['wedo_whentilt'] = {
|
||||
/**
|
||||
* Block for when tilted.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "wedo_whentilt",
|
||||
"message0": "%1 %2",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/wedo_when-tilt.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "When tilted"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHOICE"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.event,
|
||||
"colour": Blockly.Colours.event.primary,
|
||||
"colourSecondary": Blockly.Colours.event.secondary,
|
||||
"colourTertiary": Blockly.Colours.event.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.event.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['wedo_whendistanceclose'] = {
|
||||
/**
|
||||
* Block for when distance sensor is close.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "wedo_whendistanceclose",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "icons/wedo_when-distance_close.svg",
|
||||
"width": 40,
|
||||
"height": 40,
|
||||
"alt": "When distance close"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.event,
|
||||
"colour": Blockly.Colours.event.primary,
|
||||
"colourSecondary": Blockly.Colours.event.secondary,
|
||||
"colourTertiary": Blockly.Colours.event.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.event.quaternary
|
||||
});
|
||||
}
|
||||
};
|
||||
532
scratch-blocks/blocks_vertical/control.js
Normal file
532
scratch-blocks/blocks_vertical/control.js
Normal file
@@ -0,0 +1,532 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.control');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
|
||||
Blockly.Blocks['control_forever'] = {
|
||||
/**
|
||||
* Block for repeat n times (external number).
|
||||
* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#5eke39
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_forever",
|
||||
"message0": Blockly.Msg.CONTROL_FOREVER,
|
||||
"message1": "%1", // Statement
|
||||
"message2": "%1", // Icon
|
||||
"lastDummyAlign2": "RIGHT",
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"args2": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "repeat.svg",
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_end"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_repeat'] = {
|
||||
/**
|
||||
* Block for repeat n times (external number).
|
||||
* https://blockly-demo.appspot.com/static/demos/blockfactory/index.html#so57n9
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_repeat",
|
||||
"message0": Blockly.Msg.CONTROL_REPEAT,
|
||||
"message1": "%1", // Statement
|
||||
"message2": "%1", // Icon
|
||||
"lastDummyAlign2": "RIGHT",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TIMES"
|
||||
}
|
||||
],
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"args2": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "repeat.svg",
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_if'] = {
|
||||
/**
|
||||
* Block for if-then.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"type": "control_if",
|
||||
"message0": Blockly.Msg.CONTROL_IF,
|
||||
"message1": "%1", // Statement
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_if_else'] = {
|
||||
/**
|
||||
* Block for if-else.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"type": "control_if_else",
|
||||
"message0": Blockly.Msg.CONTROL_IF,
|
||||
"message1": "%1",
|
||||
"message2": Blockly.Msg.CONTROL_ELSE,
|
||||
"message3": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"args3": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_stop'] = {
|
||||
/**
|
||||
* Block for stop all scripts.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var ALL_SCRIPTS = 'all';
|
||||
var THIS_SCRIPT = 'this script';
|
||||
var OTHER_SCRIPTS = 'other scripts in sprite';
|
||||
var stopDropdown = new Blockly.FieldDropdown(function() {
|
||||
if (this.sourceBlock_ &&
|
||||
this.sourceBlock_.nextConnection &&
|
||||
this.sourceBlock_.nextConnection.isConnected()) {
|
||||
return [
|
||||
[Blockly.Msg.CONTROL_STOP_OTHER, OTHER_SCRIPTS]
|
||||
];
|
||||
}
|
||||
return [[Blockly.Msg.CONTROL_STOP_ALL, ALL_SCRIPTS],
|
||||
[Blockly.Msg.CONTROL_STOP_THIS, THIS_SCRIPT],
|
||||
[Blockly.Msg.CONTROL_STOP_OTHER, OTHER_SCRIPTS]
|
||||
];
|
||||
}, function(option) {
|
||||
// Create an event group to keep field value and mutator in sync
|
||||
// Return null at the end because setValue is called here already.
|
||||
Blockly.Events.setGroup(true);
|
||||
var oldMutation = Blockly.Xml.domToText(this.sourceBlock_.mutationToDom());
|
||||
this.sourceBlock_.setNextStatement(option == OTHER_SCRIPTS);
|
||||
var newMutation = Blockly.Xml.domToText(this.sourceBlock_.mutationToDom());
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(this.sourceBlock_,
|
||||
'mutation', null, oldMutation, newMutation));
|
||||
this.setValue(option);
|
||||
Blockly.Events.setGroup(false);
|
||||
return null;
|
||||
});
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.CONTROL_STOP)
|
||||
.appendField(stopDropdown, 'STOP_OPTION');
|
||||
this.setCategory(Blockly.Categories.control);
|
||||
this.setColour(Blockly.Colours.control.primary,
|
||||
Blockly.Colours.control.secondary,
|
||||
Blockly.Colours.control.tertiary,
|
||||
Blockly.Colours.control.quaternary
|
||||
);
|
||||
this.setPreviousStatement(true);
|
||||
},
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('hasnext', this.nextConnection != null);
|
||||
return container;
|
||||
},
|
||||
domToMutation: function(xmlElement) {
|
||||
var hasNext = (xmlElement.getAttribute('hasnext') == 'true');
|
||||
this.setNextStatement(hasNext);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_wait'] = {
|
||||
/**
|
||||
* Block to wait (pause) stack.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_wait",
|
||||
"message0": Blockly.Msg.CONTROL_WAIT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DURATION"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_wait_until'] = {
|
||||
/**
|
||||
* Block to wait until a condition becomes true.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_WAITUNTIL,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_repeat_until'] = {
|
||||
/**
|
||||
* Block to repeat until a condition becomes true.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_REPEATUNTIL,
|
||||
"message1": "%1",
|
||||
"message2": "%1",
|
||||
"lastDummyAlign2": "RIGHT",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"args2": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "repeat.svg",
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_while'] = {
|
||||
/**
|
||||
* Block to repeat until a condition becomes false.
|
||||
* (This is an obsolete "hacked" block, for compatibility with 2.0.)
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_WHILE,
|
||||
"message1": "%1",
|
||||
"message2": "%1",
|
||||
"lastDummyAlign2": "RIGHT",
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"args2": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "repeat.svg",
|
||||
"width": 24,
|
||||
"height": 24,
|
||||
"alt": "*",
|
||||
"flip_rtl": true
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_for_each'] = {
|
||||
/**
|
||||
* Block for for-each. This is an obsolete block that is implemented for
|
||||
* compatibility with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"type": "control_for_each",
|
||||
"message0": Blockly.Msg.CONTROL_FOREACH,
|
||||
"message1": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VARIABLE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_start_as_clone'] = {
|
||||
/**
|
||||
* Block for "when I start as a clone" hat.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_start_as_clone",
|
||||
"message0": Blockly.Msg.CONTROL_STARTASCLONE,
|
||||
"args0": [
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_create_clone_of_menu'] = {
|
||||
/**
|
||||
* Create-clone drop-down menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "CLONE_OPTION",
|
||||
"options": [
|
||||
[Blockly.Msg.CONTROL_CREATECLONEOF_MYSELF, '_myself_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": ["colours_control", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_create_clone_of'] = {
|
||||
/**
|
||||
* Block for "create clone of..."
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "control_start_as_clone",
|
||||
"message0": Blockly.Msg.CONTROL_CREATECLONEOF,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CLONE_OPTION"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_delete_this_clone'] = {
|
||||
/**
|
||||
* Block for "delete this clone."
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_DELETETHISCLONE,
|
||||
"args0": [
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_end"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_get_counter'] = {
|
||||
/**
|
||||
* Block to get the counter value. This is an obsolete block that is
|
||||
* implemented for compatibility with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_COUNTER,
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_incr_counter'] = {
|
||||
/**
|
||||
* Block to add one to the counter value. This is an obsolete block that is
|
||||
* implemented for compatibility with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_INCRCOUNTER,
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_clear_counter'] = {
|
||||
/**
|
||||
* Block to clear the counter value. This is an obsolete block that is
|
||||
* implemented for compatibility with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_CLEARCOUNTER,
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['control_all_at_once'] = {
|
||||
/**
|
||||
* Block to run the contained script. This is an obsolete block that is
|
||||
* implemented for compatibility with Scratch 2.0 projects. Note that
|
||||
* this was originally designed to run all of the contained blocks
|
||||
* (sequentially, like normal) within a single frame, but this feature
|
||||
* was removed in place of custom blocks marked "run without screen
|
||||
* refresh". The "all at once" block was changed to run the contained
|
||||
* blocks ordinarily, functioning the same way as an "if" block with a
|
||||
* reporter that is always true (e.g. "if 1 = 1"). Also note that the
|
||||
* Scratch 2.0 spec for this block is "warpSpeed", but the label shows
|
||||
* "all at once".
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROL_ALLATONCE,
|
||||
"message1": "%1", // Statement
|
||||
"args1": [
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "SUBSTACK"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.control,
|
||||
"extensions": ["colours_control", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
666
scratch-blocks/blocks_vertical/data.js
Normal file
666
scratch-blocks/blocks_vertical/data.js
Normal file
@@ -0,0 +1,666 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.data');
|
||||
goog.provide('Blockly.Constants.Data');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
|
||||
Blockly.Blocks['data_variable'] = {
|
||||
/**
|
||||
* Block of Variables
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"lastDummyAlign0": "CENTRE",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable_getter",
|
||||
"text": "",
|
||||
"name": "VARIABLE",
|
||||
"variableType": ""
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.data,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["contextMenu_getVariableBlock", "colours_data", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_setvariableto'] = {
|
||||
/**
|
||||
* Block to set variable to a certain value
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_SETVARIABLETO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VARIABLE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.data,
|
||||
"extensions": ["colours_data", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_changevariableby'] = {
|
||||
/**
|
||||
* Block to change variable by a certain value
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_CHANGEVARIABLEBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VARIABLE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.data,
|
||||
"extensions": ["colours_data", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_showvariable'] = {
|
||||
/**
|
||||
* Block to show a variable
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_SHOWVARIABLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VARIABLE"
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.data,
|
||||
"extensions": ["colours_data"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_hidevariable'] = {
|
||||
/**
|
||||
* Block to hide a variable
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_HIDEVARIABLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VARIABLE"
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"category": Blockly.Categories.data,
|
||||
"extensions": ["colours_data"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_listcontents'] = {
|
||||
/**
|
||||
* List reporter.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable_getter",
|
||||
"text": "",
|
||||
"name": "LIST",
|
||||
"variableType": Blockly.LIST_VARIABLE_TYPE
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["contextMenu_getListBlock", "colours_data_lists", "output_string"],
|
||||
"checkboxInFlyout": true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_listindexall'] = {
|
||||
/**
|
||||
* List index menu, with all option.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_numberdropdown",
|
||||
"name": "INDEX",
|
||||
"value": "1",
|
||||
"min": 1,
|
||||
"precision": 1,
|
||||
"options": [
|
||||
["1", "1"],
|
||||
[Blockly.Msg.DATA_INDEX_LAST, "last"],
|
||||
[Blockly.Msg.DATA_INDEX_ALL, "all"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.data,
|
||||
"extensions": ["colours_textfield", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_listindexrandom'] = {
|
||||
/**
|
||||
* List index menu, with random option.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_numberdropdown",
|
||||
"name": "INDEX",
|
||||
"value": "1",
|
||||
"min": 1,
|
||||
"precision": 1,
|
||||
"options": [
|
||||
["1", "1"],
|
||||
[Blockly.Msg.DATA_INDEX_LAST, "last"],
|
||||
[Blockly.Msg.DATA_INDEX_RANDOM, "random"]
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.data,
|
||||
"extensions": ["colours_textfield", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_addtolist'] = {
|
||||
/**
|
||||
* Block to add item to list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_ADDTOLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ITEM"
|
||||
},
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_deleteoflist'] = {
|
||||
/**
|
||||
* Block to delete item from list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_DELETEOFLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "INDEX"
|
||||
},
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_deletealloflist'] = {
|
||||
/**
|
||||
* Block to delete all items from list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_DELETEALLOFLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_insertatlist'] = {
|
||||
/**
|
||||
* Block to insert item to list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_INSERTATLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ITEM"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "INDEX"
|
||||
},
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_replaceitemoflist'] = {
|
||||
/**
|
||||
* Block to insert item to list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_REPLACEITEMOFLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "INDEX"
|
||||
},
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ITEM"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_itemoflist'] = {
|
||||
/**
|
||||
* Block for reporting item of list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_ITEMOFLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "INDEX"
|
||||
},
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"output": null,
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists"],
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_itemnumoflist'] = {
|
||||
/**
|
||||
* Block for reporting the item # of a string in a list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_ITEMNUMOFLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ITEM"
|
||||
},
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"output": null,
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists"],
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_lengthoflist'] = {
|
||||
/**
|
||||
* Block for reporting length of list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_LENGTHOFLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_listcontainsitem'] = {
|
||||
/**
|
||||
* Block to report whether list contains item.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_LISTCONTAINSITEM,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ITEM"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_showlist'] = {
|
||||
/**
|
||||
* Block to show a list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_SHOWLIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['data_hidelist'] = {
|
||||
/**
|
||||
* Block to hide a list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.DATA_HIDELIST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "LIST",
|
||||
"variableTypes": [Blockly.LIST_VARIABLE_TYPE]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.dataLists,
|
||||
"extensions": ["colours_data_lists", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin to add a context menu for a data_variable block. It adds one item for
|
||||
* each variable defined on the workspace.
|
||||
* @mixin
|
||||
* @augments Blockly.Block
|
||||
* @package
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_VARIABLE_MIXIN = {
|
||||
/**
|
||||
* Add context menu option to change the selected variable.
|
||||
* @param {!Array} options List of menu options to add to.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(options) {
|
||||
var fieldName = 'VARIABLE';
|
||||
if (this.isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
var currentVarName = this.getField(fieldName).text_;
|
||||
if (!this.isInFlyout) {
|
||||
var variablesList = this.workspace.getVariablesOfType('');
|
||||
variablesList.sort(function(a, b) {
|
||||
return Blockly.scratchBlocksUtils.compareStrings(a.name, b.name);
|
||||
});
|
||||
for (var i = 0; i < variablesList.length; i++) {
|
||||
var varName = variablesList[i].name;
|
||||
if (varName == currentVarName) continue;
|
||||
|
||||
var option = {enabled: true};
|
||||
option.text = varName;
|
||||
|
||||
option.callback =
|
||||
Blockly.Constants.Data.VARIABLE_OPTION_CALLBACK_FACTORY(this,
|
||||
variablesList[i].getId(), fieldName);
|
||||
options.push(option);
|
||||
}
|
||||
} else {
|
||||
var renameOption = {
|
||||
text: Blockly.Msg.RENAME_VARIABLE,
|
||||
enabled: true,
|
||||
callback: Blockly.Constants.Data.RENAME_OPTION_CALLBACK_FACTORY(this,
|
||||
fieldName)
|
||||
};
|
||||
var deleteOption = {
|
||||
text: Blockly.Msg.DELETE_VARIABLE.replace('%1', currentVarName),
|
||||
enabled: true,
|
||||
callback: Blockly.Constants.Data.DELETE_OPTION_CALLBACK_FACTORY(this,
|
||||
fieldName)
|
||||
};
|
||||
options.push(renameOption);
|
||||
options.push(deleteOption);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Extensions.registerMixin('contextMenu_getVariableBlock',
|
||||
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_VARIABLE_MIXIN);
|
||||
|
||||
/**
|
||||
* Mixin to add a context menu for a data_listcontents block. It adds one item for
|
||||
* each list defined on the workspace.
|
||||
* @mixin
|
||||
* @augments Blockly.Block
|
||||
* @package
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_LIST_MIXIN = {
|
||||
/**
|
||||
* Add context menu option to change the selected list.
|
||||
* @param {!Array} options List of menu options to add to.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(options) {
|
||||
var fieldName = 'LIST';
|
||||
if (this.isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
var currentVarName = this.getField(fieldName).text_;
|
||||
if (!this.isInFlyout) {
|
||||
var variablesList = this.workspace.getVariablesOfType('list');
|
||||
variablesList.sort(function(a, b) {
|
||||
return Blockly.scratchBlocksUtils.compareStrings(a.name, b.name);
|
||||
});
|
||||
for (var i = 0; i < variablesList.length; i++) {
|
||||
var varName = variablesList[i].name;
|
||||
if (varName == currentVarName) continue;
|
||||
|
||||
var option = {enabled: true};
|
||||
option.text = varName;
|
||||
|
||||
option.callback =
|
||||
Blockly.Constants.Data.VARIABLE_OPTION_CALLBACK_FACTORY(this,
|
||||
variablesList[i].getId(), fieldName);
|
||||
options.push(option);
|
||||
}
|
||||
} else {
|
||||
var renameOption = {
|
||||
text: Blockly.Msg.RENAME_LIST,
|
||||
enabled: true,
|
||||
callback: Blockly.Constants.Data.RENAME_OPTION_CALLBACK_FACTORY(this,
|
||||
fieldName)
|
||||
};
|
||||
var deleteOption = {
|
||||
text: Blockly.Msg.DELETE_LIST.replace('%1', currentVarName),
|
||||
enabled: true,
|
||||
callback: Blockly.Constants.Data.DELETE_OPTION_CALLBACK_FACTORY(this,
|
||||
fieldName)
|
||||
};
|
||||
options.push(renameOption);
|
||||
options.push(deleteOption);
|
||||
}
|
||||
}
|
||||
};
|
||||
Blockly.Extensions.registerMixin('contextMenu_getListBlock',
|
||||
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_LIST_MIXIN);
|
||||
|
||||
/**
|
||||
* Callback factory for dropdown menu options associated with a variable getter
|
||||
* block. Each variable on the workspace gets its own item in the dropdown
|
||||
* menu, and clicking on that item changes the text of the field on the source
|
||||
* block.
|
||||
* @param {!Blockly.Block} block The block to update.
|
||||
* @param {string} id The id of the variable to set on this block.
|
||||
* @param {string} fieldName The name of the field to update on the block.
|
||||
* @return {!function()} A function that updates the block with the new name.
|
||||
*/
|
||||
Blockly.Constants.Data.VARIABLE_OPTION_CALLBACK_FACTORY = function(block,
|
||||
id, fieldName) {
|
||||
return function() {
|
||||
var variableField = block.getField(fieldName);
|
||||
if (!variableField) {
|
||||
console.log("Tried to get a variable field on the wrong type of block.");
|
||||
}
|
||||
variableField.setValue(id);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for rename variable dropdown menu option associated with a
|
||||
* variable getter block.
|
||||
* @param {!Blockly.Block} block The block with the variable to rename.
|
||||
* @param {string} fieldName The name of the field to inspect on the block.
|
||||
* @return {!function()} A function that renames the variable.
|
||||
*/
|
||||
Blockly.Constants.Data.RENAME_OPTION_CALLBACK_FACTORY = function(block,
|
||||
fieldName) {
|
||||
return function() {
|
||||
var workspace = block.workspace;
|
||||
var variable = block.getField(fieldName).getVariable();
|
||||
Blockly.Variables.renameVariable(workspace, variable);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for delete variable dropdown menu option associated with a
|
||||
* variable getter block.
|
||||
* @param {!Blockly.Block} block The block with the variable to delete.
|
||||
* @param {string} fieldName The name of the field to inspect on the block.
|
||||
* @return {!function()} A function that deletes the variable.
|
||||
*/
|
||||
Blockly.Constants.Data.DELETE_OPTION_CALLBACK_FACTORY = function(block,
|
||||
fieldName) {
|
||||
return function() {
|
||||
var workspace = block.workspace;
|
||||
var variable = block.getField(fieldName).getVariable();
|
||||
workspace.deleteVariableById(variable.getId());
|
||||
};
|
||||
};
|
||||
569
scratch-blocks/blocks_vertical/default_toolbox.js
Normal file
569
scratch-blocks/blocks_vertical/default_toolbox.js
Normal file
@@ -0,0 +1,569 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.defaultToolbox');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
/**
|
||||
* @fileoverview Provide a default toolbox XML.
|
||||
*/
|
||||
|
||||
/**
|
||||
* NOTE: This is only used in the scratch-blocks development playground!
|
||||
* The XML here is overridden by scratch-gui.
|
||||
*/
|
||||
|
||||
Blockly.Blocks.defaultToolbox = '<xml id="toolbox-categories" style="display: none">' +
|
||||
'<category name="%{BKY_CATEGORY_MOTION}" id="motion" colour="#4C97FF" secondaryColour="#3373CC">' +
|
||||
'<block type="motion_movesteps" id="motion_movesteps">' +
|
||||
'<value name="STEPS">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_turnright" id="motion_turnright">' +
|
||||
'<value name="DEGREES">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">15</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_turnleft" id="motion_turnleft">' +
|
||||
'<value name="DEGREES">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">15</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_pointindirection" id="motion_pointindirection">' +
|
||||
'<value name="DIRECTION">' +
|
||||
'<shadow type="math_angle">' +
|
||||
'<field name="NUM">90</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_pointtowards" id="motion_pointtowards">' +
|
||||
'<value name="TOWARDS">' +
|
||||
'<shadow type="motion_pointtowards_menu">' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_gotoxy" id="motion_gotoxy">' +
|
||||
'<value name="X">' +
|
||||
'<shadow id="movex" type="math_number">' +
|
||||
'<field name="NUM">0</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="Y">' +
|
||||
'<shadow id="movey" type="math_number">' +
|
||||
'<field name="NUM">0</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_goto" id="motion_goto">' +
|
||||
'<value name="TO">' +
|
||||
'<shadow type="motion_goto_menu">' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_glidesecstoxy" id="motion_glidesecstoxy">' +
|
||||
'<value name="SECS">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="X">' +
|
||||
'<shadow id="glidex" type="math_number">' +
|
||||
'<field name="NUM">0</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="Y">' +
|
||||
'<shadow id="glidey" type="math_number">' +
|
||||
'<field name="NUM">0</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_glideto" id="motion_glideto">' +
|
||||
'<value name="SECS">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="TO">' +
|
||||
'<shadow type="motion_glideto_menu">' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_changexby" id="motion_changexby">' +
|
||||
'<value name="DX">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_setx" id="motion_setx">' +
|
||||
'<value name="X">' +
|
||||
'<shadow id="setx" type="math_number">' +
|
||||
'<field name="NUM">0</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_changeyby" id="motion_changeyby">' +
|
||||
'<value name="DY">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_sety" id="motion_sety">' +
|
||||
'<value name="Y">' +
|
||||
'<shadow id="sety" type="math_number">' +
|
||||
'<field name="NUM">0</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="motion_ifonedgebounce" id="motion_ifonedgebounce"></block>' +
|
||||
'<block type="motion_setrotationstyle" id="motion_setrotationstyle"></block>' +
|
||||
'<block type="motion_xposition" id="motion_xposition"></block>' +
|
||||
'<block type="motion_yposition" id="motion_yposition"></block>' +
|
||||
'<block type="motion_direction" id="motion_direction"></block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_LOOKS}" id="looks" colour="#9966FF" secondaryColour="#774DCB">' +
|
||||
'<block type="looks_show" id="looks_show"></block>' +
|
||||
'<block type="looks_hide" id="looks_hide"></block>' +
|
||||
'<block type="looks_switchcostumeto" id="looks_switchcostumeto">' +
|
||||
'<value name="COSTUME">' +
|
||||
'<shadow type="looks_costume"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_nextcostume" id="looks_nextcostume"></block>' +
|
||||
'<block type="looks_nextbackdrop" id="looks_nextbackdrop"></block>' +
|
||||
'<block type="looks_switchbackdropto" id="looks_switchbackdropto">' +
|
||||
'<value name="BACKDROP">' +
|
||||
'<shadow type="looks_backdrops"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_switchbackdroptoandwait" id="looks_switchbackdroptoandwait">' +
|
||||
'<value name="BACKDROP">' +
|
||||
'<shadow type="looks_backdrops"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_changeeffectby" id="looks_changeeffectby">' +
|
||||
'<value name="CHANGE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_seteffectto" id="looks_seteffectto">' +
|
||||
'<value name="VALUE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_cleargraphiceffects" id="looks_cleargraphiceffects"></block>' +
|
||||
'<block type="looks_changesizeby" id="looks_changesizeby">' +
|
||||
'<value name="CHANGE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_setsizeto" id="looks_setsizeto">' +
|
||||
'<value name="SIZE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">100</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_gotofrontback" id="looks_gotofrontback"></block>' +
|
||||
'<block type="looks_goforwardbackwardlayers" id="looks_goforwardbackwardlayers">' +
|
||||
'<value name="NUM">' +
|
||||
'<shadow type="math_integer">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="looks_costumenumbername" id="looks_costumenumbername"></block>' +
|
||||
'<block type="looks_backdropnumbername" id="looks_backdropnumbername"></block>' +
|
||||
'<block type="looks_size" id="looks_size"></block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_SOUND}" id="sound" colour="#D65CD6" secondaryColour="#BD42BD">' +
|
||||
'<block type="sound_play" id="sound_play">' +
|
||||
'<value name="SOUND_MENU">' +
|
||||
'<shadow type="sound_sounds_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sound_playuntildone" id="sound_playuntildone">' +
|
||||
'<value name="SOUND_MENU">' +
|
||||
'<shadow type="sound_sounds_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sound_stopallsounds" id="sound_stopallsounds"></block>' +
|
||||
'<block type="sound_changeeffectby" id="sound_changeeffectby">' +
|
||||
'<value name="VALUE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sound_seteffectto" id="sound_seteffectto">' +
|
||||
'<value name="VALUE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">100</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sound_cleareffects" id="sound_cleareffects"></block>' +
|
||||
'<block type="sound_changevolumeby" id="sound_changevolumeby">' +
|
||||
'<value name="VOLUME">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">-10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sound_setvolumeto" id="sound_setvolumeto">' +
|
||||
'<value name="VOLUME">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">100</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sound_volume" id="sound_volume"></block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_EVENTS}" id="events" colour="#FFD500" secondaryColour="#CC9900">' +
|
||||
'<block type="event_whenflagclicked" id="event_whenflagclicked"></block>' +
|
||||
'<block type="event_whenkeypressed" id="event_whenkeypressed">' +
|
||||
'</block>' +
|
||||
'<block type="event_whenthisspriteclicked" id="event_whenthisspriteclicked"></block>' +
|
||||
'<block type="event_whenbackdropswitchesto" id="event_whenbackdropswitchesto">' +
|
||||
'</block>' +
|
||||
'<block type="event_whengreaterthan" id="event_whengreaterthan">' +
|
||||
'<value name="VALUE">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="event_whenbroadcastreceived" id="event_whenbroadcastreceived">' +
|
||||
'</block>' +
|
||||
'<block type="event_broadcast" id="event_broadcast">' +
|
||||
'<value name="BROADCAST_INPUT">' +
|
||||
'<shadow type="event_broadcast_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="event_broadcastandwait" id="event_broadcastandwait">' +
|
||||
'<value name="BROADCAST_INPUT">' +
|
||||
'<shadow type="event_broadcast_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_CONTROL}" id="control" colour="#FFAB19" secondaryColour="#CF8B17">' +
|
||||
'<block type="control_wait" id="control_wait">' +
|
||||
'<value name="DURATION">' +
|
||||
'<shadow type="math_positive_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="control_repeat" id="control_repeat">' +
|
||||
'<value name="TIMES">' +
|
||||
'<shadow type="math_whole_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="control_forever" id="control_forever"></block>' +
|
||||
'<block type="control_if" id="control_if"></block>' +
|
||||
'<block type="control_if_else" id="control_if_else"></block>' +
|
||||
'<block type="control_wait_until" id="control_wait_until"></block>' +
|
||||
'<block type="control_repeat_until" id="control_repeat_until"></block>' +
|
||||
'<block type="control_stop" id="control_stop"></block>' +
|
||||
'<block type="control_start_as_clone" id="control_start_as_clone"></block>' +
|
||||
'<block type="control_create_clone_of" id="control_create_clone_of">' +
|
||||
'<value name="CLONE_OPTION">' +
|
||||
'<shadow type="control_create_clone_of_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="control_delete_this_clone" id="control_delete_this_clone"></block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_SENSING}" id="sensing" colour="#4CBFE6" secondaryColour="#2E8EB8">' +
|
||||
'<block type="sensing_touchingobject" id="sensing_touchingobject">' +
|
||||
'<value name="TOUCHINGOBJECTMENU">' +
|
||||
'<shadow type="sensing_touchingobjectmenu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sensing_touchingcolor" id="sensing_touchingcolor">' +
|
||||
'<value name="COLOR">' +
|
||||
'<shadow type="colour_picker"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sensing_coloristouchingcolor" id="sensing_coloristouchingcolor">' +
|
||||
'<value name="COLOR">' +
|
||||
'<shadow type="colour_picker"></shadow>' +
|
||||
'</value>' +
|
||||
'<value name="COLOR2">' +
|
||||
'<shadow type="colour_picker"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sensing_distanceto" id="sensing_distanceto">' +
|
||||
'<value name="DISTANCETOMENU">' +
|
||||
'<shadow type="sensing_distancetomenu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sensing_keypressed" id="sensing_keypressed">' +
|
||||
'<value name="KEY_OPTION">' +
|
||||
'<shadow type="sensing_keyoptions"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sensing_mousedown" id="sensing_mousedown"></block>' +
|
||||
'<block type="sensing_mousex" id="sensing_mousex"></block>' +
|
||||
'<block type="sensing_mousey" id="sensing_mousey"></block>' +
|
||||
'<block type="sensing_setdragmode" id="sensing_setdragmode"></block>' +
|
||||
'<block type="sensing_loudness" id="sensing_loudness"></block>' +
|
||||
'<block type="sensing_timer" id="sensing_timer"></block>' +
|
||||
'<block type="sensing_resettimer" id="sensing_resettimer"></block>' +
|
||||
'<block type="sensing_of" id="sensing_of">' +
|
||||
'<value name="OBJECT">' +
|
||||
'<shadow type="sensing_of_object_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="sensing_current" id="sensing_current"></block>' +
|
||||
'<block type="sensing_dayssince2000" id="sensing_dayssince2000"></block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_OPERATORS}" id="operators" colour="#40BF4A" secondaryColour="#389438">' +
|
||||
'<block type="operator_add" id="operator_add">' +
|
||||
'<value name="NUM1">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="NUM2">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_subtract" id="operator_subtract">' +
|
||||
'<value name="NUM1">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="NUM2">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_multiply" id="operator_multiply">' +
|
||||
'<value name="NUM1">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="NUM2">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_divide" id="operator_divide">' +
|
||||
'<value name="NUM1">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="NUM2">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_random" id="operator_random">' +
|
||||
'<value name="FROM">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="TO">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">10</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_lt" id="operator_lt">' +
|
||||
'<value name="OPERAND1">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="OPERAND2">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_equals" id="operator_equals">' +
|
||||
'<value name="OPERAND1">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="OPERAND2">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_gt" id="operator_gt">' +
|
||||
'<value name="OPERAND1">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="OPERAND2">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_and" id="operator_and"></block>' +
|
||||
'<block type="operator_or" id="operator_or"></block>' +
|
||||
'<block type="operator_not" id="operator_not"></block>' +
|
||||
'<block type="operator_join" id="operator_join">' +
|
||||
'<value name="STRING1">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT">hello</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="STRING2">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT">world</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_letter_of" id="operator_letter_of">' +
|
||||
'<value name="LETTER">' +
|
||||
'<shadow type="math_whole_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="STRING">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT">world</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_length" id="operator_length">' +
|
||||
'<value name="STRING">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT">world</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_contains" id="operator_contains">' +
|
||||
'<value name="STRING1">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT">hello</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="STRING2">' +
|
||||
'<shadow type="text">' +
|
||||
'<field name="TEXT">world</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_mod" id="operator_mod">' +
|
||||
'<value name="NUM1">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="NUM2">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_round" id="operator_round">' +
|
||||
'<value name="NUM">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="operator_mathop" id="operator_mathop">' +
|
||||
'<value name="NUM">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM"></field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_VARIABLES}" id="data" colour="#FF8C1A" secondaryColour="#DB6E00" custom="VARIABLE">' +
|
||||
'</category>' +
|
||||
'<category name="%{BKY_CATEGORY_MYBLOCKS}" id="more" colour="#FF6680" secondaryColour="#FF4D6A" custom="PROCEDURE">' +
|
||||
'</category>' +
|
||||
'<category name="Extensions" id="extensions" colour="#FF6680" secondaryColour="#FF4D6A" ' +
|
||||
'iconURI="../media/extensions/wedo2-block-icon.svg" showStatusButton="true">' +
|
||||
'<block type="extension_pen_down" id="extension_pen_down"></block>' +
|
||||
'<block type="extension_music_drum" id="extension_music_drum">' +
|
||||
'<value name="NUMBER">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">1</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="extension_wedo_motor" id="extension_wedo_motor"></block>' +
|
||||
'<block type="extension_wedo_hat" id="extension_wedo_hat"></block>' +
|
||||
'<block type="extension_wedo_boolean" id="extension_wedo_boolean"></block>' +
|
||||
'<block type="extension_wedo_tilt_reporter" id="extension_wedo_reporter">' +
|
||||
'<value name="TILT">' +
|
||||
'<shadow type="extension_wedo_tilt_menu"></shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="extension_music_reporter" id="extension_music_reporter"></block>' +
|
||||
'<block type="extension_microbit_display" id="extension_microbit_display">' +
|
||||
'<value name="MATRIX">' +
|
||||
'<shadow type="matrix">' +
|
||||
'<field name="MATRIX">0101010101100010101000100</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'<block type="extension_music_play_note" id="extension_music_play_note">' +
|
||||
'<value name="NOTE">' +
|
||||
'<shadow type="note">' +
|
||||
'<field name="NOTE">60</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'<value name="BEATS">' +
|
||||
'<shadow type="math_number">' +
|
||||
'<field name="NUM">0.25</field>' +
|
||||
'</shadow>' +
|
||||
'</value>' +
|
||||
'</block>' +
|
||||
'</category>' +
|
||||
'</xml>';
|
||||
329
scratch-blocks/blocks_vertical/event.js
Normal file
329
scratch-blocks/blocks_vertical/event.js
Normal file
@@ -0,0 +1,329 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.event');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
Blockly.Blocks['event_whentouchingobject'] = {
|
||||
/**
|
||||
* Block for when a sprite is touching an object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.EVENT_WHENTOUCHINGOBJECT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TOUCHINGOBJECTMENU"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_touchingobjectmenu'] = {
|
||||
/**
|
||||
* "Touching [Object]" Block Menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TOUCHINGOBJECTMENU",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_TOUCHINGOBJECT_POINTER, '_mouse_'],
|
||||
[Blockly.Msg.SENSING_TOUCHINGOBJECT_EDGE, '_edge_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": ["colours_event", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenflagclicked'] = {
|
||||
/**
|
||||
* Block for when flag clicked.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_whenflagclicked",
|
||||
"message0": Blockly.Msg.EVENT_WHENFLAGCLICKED,
|
||||
"args0": [
|
||||
// {
|
||||
// "type": "field_image",
|
||||
// "src": Blockly.mainWorkspace.options.pathToMedia + "green-flag.svg",
|
||||
// "width": 24,
|
||||
// "height": 24,
|
||||
// "alt": "flag"
|
||||
// }
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenthisspriteclicked'] = {
|
||||
/**
|
||||
* Block for when this sprite clicked.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.EVENT_WHENTHISSPRITECLICKED,
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenstageclicked'] = {
|
||||
/**
|
||||
* Block for when the stage is clicked.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.EVENT_WHENSTAGECLICKED,
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenbroadcastreceived'] = {
|
||||
/**
|
||||
* Block for when broadcast received.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_whenbroadcastreceived",
|
||||
"message0": Blockly.Msg.EVENT_WHENBROADCASTRECEIVED,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "BROADCAST_OPTION",
|
||||
"variableTypes": [Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE],
|
||||
"variable": Blockly.Msg.DEFAULT_BROADCAST_MESSAGE_NAME
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenbackdropswitchesto'] = {
|
||||
/**
|
||||
* Block for when the current backdrop switched to a selected backdrop.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.EVENT_WHENBACKDROPSWITCHESTO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BACKDROP",
|
||||
"options": [
|
||||
['backdrop1', 'BACKDROP1']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whengreaterthan'] = {
|
||||
/**
|
||||
* Block for when loudness/timer/video motion is greater than the value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.EVENT_WHENGREATERTHAN,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "WHENGREATERTHANMENU",
|
||||
"options": [
|
||||
[Blockly.Msg.EVENT_WHENGREATERTHAN_LOUDNESS, 'LOUDNESS'],
|
||||
[Blockly.Msg.EVENT_WHENGREATERTHAN_TIMER, 'TIMER']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_broadcast_menu'] = {
|
||||
/**
|
||||
* Broadcast drop-down menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "BROADCAST_OPTION",
|
||||
"variableTypes":[Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE],
|
||||
"variable": Blockly.Msg.DEFAULT_BROADCAST_MESSAGE_NAME
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.event.secondary,
|
||||
"colourSecondary": Blockly.Colours.event.secondary,
|
||||
"colourTertiary": Blockly.Colours.event.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.event.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_broadcast'] = {
|
||||
/**
|
||||
* Block to send a broadcast.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_broadcast",
|
||||
"message0": Blockly.Msg.EVENT_BROADCAST,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BROADCAST_INPUT"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_broadcastandwait'] = {
|
||||
/**
|
||||
* Block to send a broadcast.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.EVENT_BROADCASTANDWAIT,
|
||||
"args0": [
|
||||
{
|
||||
"type":"input_value",
|
||||
"name":"BROADCAST_INPUT"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['event_whenkeypressed'] = {
|
||||
/**
|
||||
* Block to send a broadcast.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "event_whenkeypressed",
|
||||
"message0": Blockly.Msg.EVENT_WHENKEYPRESSED,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "KEY_OPTION",
|
||||
"options": [
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_SPACE, 'space'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_UP, 'up arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_DOWN, 'down arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_RIGHT, 'right arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_LEFT, 'left arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_ANY, 'any'],
|
||||
['a', 'a'],
|
||||
['b', 'b'],
|
||||
['c', 'c'],
|
||||
['d', 'd'],
|
||||
['e', 'e'],
|
||||
['f', 'f'],
|
||||
['g', 'g'],
|
||||
['h', 'h'],
|
||||
['i', 'i'],
|
||||
['j', 'j'],
|
||||
['k', 'k'],
|
||||
['l', 'l'],
|
||||
['m', 'm'],
|
||||
['n', 'n'],
|
||||
['o', 'o'],
|
||||
['p', 'p'],
|
||||
['q', 'q'],
|
||||
['r', 'r'],
|
||||
['s', 's'],
|
||||
['t', 't'],
|
||||
['u', 'u'],
|
||||
['v', 'v'],
|
||||
['w', 'w'],
|
||||
['x', 'x'],
|
||||
['y', 'y'],
|
||||
['z', 'z'],
|
||||
['0', '0'],
|
||||
['1', '1'],
|
||||
['2', '2'],
|
||||
['3', '3'],
|
||||
['4', '4'],
|
||||
['5', '5'],
|
||||
['6', '6'],
|
||||
['7', '7'],
|
||||
['8', '8'],
|
||||
['9', '9']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.event,
|
||||
"extensions": ["colours_event", "shape_hat"]
|
||||
});
|
||||
}
|
||||
};
|
||||
294
scratch-blocks/blocks_vertical/extensions.js
Normal file
294
scratch-blocks/blocks_vertical/extensions.js
Normal file
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.extensions');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
Blockly.Blocks['extension_pen_down'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 pen down",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/pen-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "shape_statement", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_music_drum'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 play drum %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/music-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUMBER"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "shape_statement", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_wedo_motor'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 turn a motor %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/wedo2-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
},
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "rotate-right.svg",
|
||||
"width": 24,
|
||||
"height": 24
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "shape_statement", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_wedo_hat'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 when I am wearing a hat",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/wedo2-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "shape_hat", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_wedo_boolean'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 O RLY?",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/wedo2-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "output_boolean", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_wedo_tilt_reporter'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 tilt angle %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/wedo2-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TILT"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "output_number", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_wedo_tilt_menu'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TILT",
|
||||
"options": [
|
||||
['Any', 'Any'],
|
||||
['Whirl', 'Whirl'],
|
||||
['South', 'South'],
|
||||
['Back in time', 'Back in time']
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": ["colours_more", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_music_reporter'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 hey now, you're an all-star",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/music-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.more,
|
||||
"extensions": ["colours_more", "output_number", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_microbit_display'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 display %3",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/microbit-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "MATRIX"
|
||||
},
|
||||
],
|
||||
"category": Blockly.Categories.pen,
|
||||
"extensions": ["colours_pen", "shape_statement", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['extension_music_play_note'] = {
|
||||
/**
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1 %2 play note %3 for %4 beats",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "extensions/music-block-icon.svg",
|
||||
"width": 40,
|
||||
"height": 40
|
||||
},
|
||||
{
|
||||
"type": "field_vertical_separator"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NOTE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BEATS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.pen,
|
||||
"extensions": ["colours_pen", "shape_statement", "scratch_extension"]
|
||||
});
|
||||
}
|
||||
};
|
||||
663
scratch-blocks/blocks_vertical/looks.js
Normal file
663
scratch-blocks/blocks_vertical/looks.js
Normal file
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.looks');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
|
||||
Blockly.Blocks['looks_sayforsecs'] = {
|
||||
/**
|
||||
* Block to say for some time.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SAYFORSECS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "MESSAGE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SECS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_say'] = {
|
||||
/**
|
||||
* Block to say.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SAY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "MESSAGE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['vehicle_move'] = {
|
||||
/**
|
||||
* Block to say.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.Vehicle_Move,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STEPS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['vehicle_turnright'] = {
|
||||
/**
|
||||
* Block to turn right.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.Vehicle_TURNRIGHT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "rotate-right.svg",
|
||||
"width": 24,
|
||||
"height": 24
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DEGREES"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['vehicle_turnleft'] = {
|
||||
/**
|
||||
* Block to turn left.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.Vehicle_TURNLEFT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "rotate-left.svg",
|
||||
"width": 24,
|
||||
"height": 24
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DEGREES"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_thinkforsecs'] = {
|
||||
/**
|
||||
* Block to think for some time.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_THINKFORSECS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "MESSAGE"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SECS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_think'] = {
|
||||
/**
|
||||
* Block to think.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_THINK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "MESSAGE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_show'] = {
|
||||
/**
|
||||
* Show block.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SHOW,
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_hide'] = {
|
||||
/**
|
||||
* Hide block.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_HIDE,
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_hideallsprites'] = {
|
||||
/**
|
||||
* Hide-all-sprites block. Does not actually do anything. This is an
|
||||
* obsolete block that is implemented for compatibility with Scratch 2.0
|
||||
* projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_HIDEALLSPRITES,
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_changeeffectby'] = {
|
||||
/**
|
||||
* Block to change graphic effect.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_CHANGEEFFECTBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "EFFECT",
|
||||
"options": [
|
||||
[Blockly.Msg.LOOKS_EFFECT_COLOR, 'COLOR'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_FISHEYE, 'FISHEYE'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_WHIRL, 'WHIRL'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_PIXELATE, 'PIXELATE'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_MOSAIC, 'MOSAIC'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_BRIGHTNESS, 'BRIGHTNESS'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_GHOST, 'GHOST']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHANGE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_seteffectto'] = {
|
||||
/**
|
||||
* Block to set graphic effect.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SETEFFECTTO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "EFFECT",
|
||||
"options": [
|
||||
[Blockly.Msg.LOOKS_EFFECT_COLOR, 'COLOR'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_FISHEYE, 'FISHEYE'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_WHIRL, 'WHIRL'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_PIXELATE, 'PIXELATE'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_MOSAIC, 'MOSAIC'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_BRIGHTNESS, 'BRIGHTNESS'],
|
||||
[Blockly.Msg.LOOKS_EFFECT_GHOST, 'GHOST']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_cleargraphiceffects'] = {
|
||||
/**
|
||||
* Block to clear graphic effects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_CLEARGRAPHICEFFECTS,
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_changesizeby'] = {
|
||||
/**
|
||||
* Block to change size
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_CHANGESIZEBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHANGE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_setsizeto'] = {
|
||||
/**
|
||||
* Block to set size
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SETSIZETO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SIZE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_size'] = {
|
||||
/**
|
||||
* Block to report size
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SIZE,
|
||||
"category": Blockly.Categories.looks,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_looks", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_changestretchby'] = {
|
||||
/**
|
||||
* Block to change stretch. Does not actually do anything. This is an
|
||||
* obsolete block that is implemented for compatibility with Scratch 1.4
|
||||
* projects as well as 2.0 projects that still have the block.
|
||||
* The "stretch" blocks were introduced in very early versions of Scratch,
|
||||
* but their functionality was removed shortly later. They still appeared
|
||||
* correctly up until (and including) Scratch 1.4 - as "change stretch by"
|
||||
* and "set stretch to" - but were removed altogether in Scratch 2.0, and
|
||||
* displayed as red "undefined" blocks. Some Scratch projects still contain
|
||||
* these blocks, however, and they don't open in 3.0 unless the blocks
|
||||
* actually exist (though they still don't funcitonally do anything).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_CHANGESTRETCHBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CHANGE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_setstretchto'] = {
|
||||
/**
|
||||
* Block to set stretch. Does not actually do anything. This is an obsolete
|
||||
* block that is implemented for compatibility with Scratch 1.4 projects
|
||||
* (see looks_changestretchby).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SETSTRETCHTO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRETCH"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_costume'] = {
|
||||
/**
|
||||
* Costumes drop-down menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "COSTUME",
|
||||
"options": [
|
||||
['costume1', 'COSTUME1'],
|
||||
['costume2', 'COSTUME2']
|
||||
]
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.looks.secondary,
|
||||
"colourSecondary": Blockly.Colours.looks.secondary,
|
||||
"colourTertiary": Blockly.Colours.looks.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.looks.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_switchcostumeto'] = {
|
||||
/**
|
||||
* Block to switch the sprite's costume to the selected one.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SWITCHCOSTUMETO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "COSTUME"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_nextcostume'] = {
|
||||
/**
|
||||
* Block to switch the sprite's costume to the next one.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_NEXTCOSTUME,
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_switchbackdropto'] = {
|
||||
/**
|
||||
* Block to switch the backdrop to the selected one.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SWITCHBACKDROPTO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BACKDROP"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_backdrops'] = {
|
||||
/**
|
||||
* Backdrop list
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"id": "looks_backdrops",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BACKDROP",
|
||||
"options": [
|
||||
['backdrop1', 'BACKDROP1']
|
||||
]
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.looks.secondary,
|
||||
"colourSecondary": Blockly.Colours.looks.secondary,
|
||||
"colourTertiary": Blockly.Colours.looks.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.looks.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_gotofrontback'] = {
|
||||
/**
|
||||
* "Go to front/back" Block.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_GOTOFRONTBACK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "FRONT_BACK",
|
||||
"options": [
|
||||
[Blockly.Msg.LOOKS_GOTOFRONTBACK_FRONT, 'front'],
|
||||
[Blockly.Msg.LOOKS_GOTOFRONTBACK_BACK, 'back']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_goforwardbackwardlayers'] = {
|
||||
/**
|
||||
* "Go forward/backward [Number] Layers" Block.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_GOFORWARDBACKWARDLAYERS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "FORWARD_BACKWARD",
|
||||
"options": [
|
||||
[Blockly.Msg.LOOKS_GOFORWARDBACKWARDLAYERS_FORWARD, 'forward'],
|
||||
[Blockly.Msg.LOOKS_GOFORWARDBACKWARDLAYERS_BACKWARD, 'backward']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_backdropnumbername'] = {
|
||||
/**
|
||||
* Block to report backdrop's number or name
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_BACKDROPNUMBERNAME,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "NUMBER_NAME",
|
||||
"options": [
|
||||
[Blockly.Msg.LOOKS_NUMBERNAME_NUMBER, 'number'],
|
||||
[Blockly.Msg.LOOKS_NUMBERNAME_NAME, 'name']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_looks", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_costumenumbername'] = {
|
||||
/**
|
||||
* Block to report costume's number or name
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_COSTUMENUMBERNAME,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "NUMBER_NAME",
|
||||
"options": [
|
||||
[Blockly.Msg.LOOKS_NUMBERNAME_NUMBER, 'number'],
|
||||
[Blockly.Msg.LOOKS_NUMBERNAME_NAME, 'name']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_looks", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_switchbackdroptoandwait'] = {
|
||||
/**
|
||||
* Block to switch the backdrop to the selected one and wait.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_SWITCHBACKDROPTOANDWAIT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BACKDROP"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['looks_nextbackdrop'] = {
|
||||
/**
|
||||
* Block to switch the backdrop to the next one.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LOOKS_NEXTBACKDROP_BLOCK,
|
||||
"category": Blockly.Categories.looks,
|
||||
"extensions": ["colours_looks", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
683
scratch-blocks/blocks_vertical/motion.js
Normal file
683
scratch-blocks/blocks_vertical/motion.js
Normal file
@@ -0,0 +1,683 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.motion');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
|
||||
Blockly.Blocks['motion_movesteps'] = {
|
||||
/**
|
||||
* Block to move steps.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_MOVESTEPS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STEPS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_testblock'] = {
|
||||
/**
|
||||
* Block to move steps.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_MOVESTEPS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STEPS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_move_f'] = {
|
||||
/**
|
||||
* Block to move steps.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_MOVEFOWRD,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STEPS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_move_b'] = {
|
||||
/**
|
||||
* Block to move steps.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_MOVEBACK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STEPS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_turnright'] = {
|
||||
/**
|
||||
* Block to turn right.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_TURNRIGHT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "rotate-right.svg",
|
||||
"width": 24,
|
||||
"height": 24
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DEGREES"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_turnleft'] = {
|
||||
/**
|
||||
* Block to turn left.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_TURNLEFT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_image",
|
||||
"src": Blockly.mainWorkspace.options.pathToMedia + "rotate-left.svg",
|
||||
"width": 24,
|
||||
"height": 24
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DEGREES"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_jump_move'] = {
|
||||
/**
|
||||
* Block to move steps.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_JUMP_MOVE,
|
||||
"args0": [
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_pointindirection'] = {
|
||||
/**
|
||||
* Block to point in direction.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_POINTINDIRECTION,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DIRECTION"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_cmd_print'] = {
|
||||
/**
|
||||
* Block to move steps.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_PRINT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TEXT"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_pointtowards_menu'] = {
|
||||
/**
|
||||
* Point towards drop-down menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TOWARDS",
|
||||
"options": [
|
||||
[Blockly.Msg.MOTION_POINTTOWARDS_POINTER, '_mouse_'],
|
||||
[Blockly.Msg.MOTION_POINTTOWARDS_RANDOM, '_random_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.motion.secondary,
|
||||
"colourSecondary": Blockly.Colours.motion.secondary,
|
||||
"colourTertiary": Blockly.Colours.motion.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.motion.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_pointtowards'] = {
|
||||
/**
|
||||
* Block to point in direction.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_POINTTOWARDS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TOWARDS"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_goto_menu'] = {
|
||||
/**
|
||||
* Go to drop-down menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TO",
|
||||
"options": [
|
||||
[Blockly.Msg.MOTION_GOTO_POINTER, '_mouse_'],
|
||||
[Blockly.Msg.MOTION_GOTO_RANDOM, '_random_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.motion.secondary,
|
||||
"colourSecondary": Blockly.Colours.motion.secondary,
|
||||
"colourTertiary": Blockly.Colours.motion.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.motion.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_gotoxy'] = {
|
||||
/**
|
||||
* Block to go to X, Y.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_GOTOXY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "X"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "Y"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_goto'] = {
|
||||
/**
|
||||
* Block to go to a menu item.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_GOTO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TO"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_glidesecstoxy'] = {
|
||||
/**
|
||||
* Block to glide for a specified time.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_GLIDESECSTOXY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SECS"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "X"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "Y"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_glideto_menu'] = {
|
||||
/**
|
||||
* Glide to drop-down menu
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TO",
|
||||
"options": [
|
||||
[Blockly.Msg.MOTION_GLIDETO_POINTER, '_mouse_'],
|
||||
[Blockly.Msg.MOTION_GLIDETO_RANDOM, '_random_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.motion.secondary,
|
||||
"colourSecondary": Blockly.Colours.motion.secondary,
|
||||
"colourTertiary": Blockly.Colours.motion.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.motion.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_glideto'] = {
|
||||
/**
|
||||
* Block to glide to a menu item
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_GLIDETO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SECS"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TO"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_changexby'] = {
|
||||
/**
|
||||
* Block to change X.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_CHANGEXBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DX"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_setx'] = {
|
||||
/**
|
||||
* Block to set X.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_SETX,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "X"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_changeyby'] = {
|
||||
/**
|
||||
* Block to change Y.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_CHANGEYBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DY"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_sety'] = {
|
||||
/**
|
||||
* Block to set Y.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_SETY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "Y"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_ifonedgebounce'] = {
|
||||
/**
|
||||
* Block to bounce on edge.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_IFONEDGEBOUNCE,
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_setrotationstyle'] = {
|
||||
/**
|
||||
* Block to set rotation style.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_SETROTATIONSTYLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "STYLE",
|
||||
"options": [
|
||||
[Blockly.Msg.MOTION_SETROTATIONSTYLE_LEFTRIGHT, 'left-right'],
|
||||
[Blockly.Msg.MOTION_SETROTATIONSTYLE_DONTROTATE, 'don\'t rotate'],
|
||||
[Blockly.Msg.MOTION_SETROTATIONSTYLE_ALLAROUND, 'all around']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_xposition'] = {
|
||||
/**
|
||||
* Block to report X.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_XPOSITION,
|
||||
"category": Blockly.Categories.motion,
|
||||
// "checkboxInFlyout": true,
|
||||
"extensions": ["colours_motion", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_yposition'] = {
|
||||
/**
|
||||
* Block to report Y.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_YPOSITION,
|
||||
"category": Blockly.Categories.motion,
|
||||
// "checkboxInFlyout": true,
|
||||
"extensions": ["colours_motion", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_direction'] = {
|
||||
/**
|
||||
* Block to report direction.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_DIRECTION,
|
||||
"category": Blockly.Categories.motion,
|
||||
// "checkboxInFlyout": true,
|
||||
"extensions": ["colours_motion", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_scroll_right'] = {
|
||||
/**
|
||||
* Block to scroll the stage right. Does not actually do anything. This is
|
||||
* an obsolete block that is implemented for compatibility with Scratch 2.0
|
||||
* projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_SCROLLRIGHT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DISTANCE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_scroll_up'] = {
|
||||
/**
|
||||
* Block to scroll the stage up. Does not actually do anything. This is an
|
||||
* obsolete block that is implemented for compatibility with Scratch 2.0
|
||||
* projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_SCROLLUP,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DISTANCE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_align_scene'] = {
|
||||
/**
|
||||
* Block to change the stage's scrolling alignment. Does not actually do
|
||||
* anything. This is an obsolete block that is implemented for compatibility
|
||||
* with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_ALIGNSCENE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "ALIGNMENT",
|
||||
"options": [
|
||||
[Blockly.Msg.MOTION_ALIGNSCENE_BOTTOMLEFT, 'bottom-left'],
|
||||
[Blockly.Msg.MOTION_ALIGNSCENE_BOTTOMRIGHT, 'bottom-right'],
|
||||
[Blockly.Msg.MOTION_ALIGNSCENE_MIDDLE, 'middle'],
|
||||
[Blockly.Msg.MOTION_ALIGNSCENE_TOPLEFT, 'top-left'],
|
||||
[Blockly.Msg.MOTION_ALIGNSCENE_TOPRIGHT, 'top-right']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_xscroll'] = {
|
||||
/**
|
||||
* Block to report the stage's scroll position's X value. Does not actually
|
||||
* do anything. This is an obsolete block that is implemented for
|
||||
* compatibility with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_XSCROLL,
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['motion_yscroll'] = {
|
||||
/**
|
||||
* Block to report the stage's scroll position's Y value. Does not actually
|
||||
* do anything. This is an obsolete block that is implemented for
|
||||
* compatibility with Scratch 2.0 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.MOTION_YSCROLL,
|
||||
"category": Blockly.Categories.motion,
|
||||
"extensions": ["colours_motion", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
470
scratch-blocks/blocks_vertical/operators.js
Normal file
470
scratch-blocks/blocks_vertical/operators.js
Normal file
@@ -0,0 +1,470 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.operators');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
|
||||
Blockly.Blocks['operator_add'] = {
|
||||
/**
|
||||
* Block for adding two numbers.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_ADD,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_subtract'] = {
|
||||
/**
|
||||
* Block for subtracting two numbers.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_SUBTRACT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_multiply'] = {
|
||||
/**
|
||||
* Block for multiplying two numbers.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_MULTIPLY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_divide'] = {
|
||||
/**
|
||||
* Block for dividing two numbers.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_DIVIDE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_random'] = {
|
||||
/**
|
||||
* Block for picking a random number.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_RANDOM,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FROM"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TO"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_lt'] = {
|
||||
/**
|
||||
* Block for less than comparator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_LT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_equals'] = {
|
||||
/**
|
||||
* Block for equals comparator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_EQUALS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_gt'] = {
|
||||
/**
|
||||
* Block for greater than comparator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_GT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_and'] = {
|
||||
/**
|
||||
* Block for "and" boolean comparator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_AND,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND1",
|
||||
"check": "Boolean"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND2",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_or'] = {
|
||||
/**
|
||||
* Block for "or" boolean comparator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_OR,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND1",
|
||||
"check": "Boolean"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND2",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_not'] = {
|
||||
/**
|
||||
* Block for "not" unary boolean operator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_NOT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OPERAND",
|
||||
"check": "Boolean"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_join'] = {
|
||||
/**
|
||||
* Block for string join operator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_JOIN,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRING1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRING2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_letter_of'] = {
|
||||
/**
|
||||
* Block for "letter _ of _" operator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_LETTEROF,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "LETTER"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRING"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_length'] = {
|
||||
/**
|
||||
* Block for string length operator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_LENGTH,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRING"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_contains'] = {
|
||||
/**
|
||||
* Block for _ contains _ operator
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_CONTAINS,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRING1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "STRING2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_mod'] = {
|
||||
/**
|
||||
* Block for mod two numbers.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_MOD,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM1"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_round'] = {
|
||||
/**
|
||||
* Block for rounding a numbers.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_ROUND,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['operator_mathop'] = {
|
||||
/**
|
||||
* Block for "advanced" math ops on a number.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.OPERATORS_MATHOP,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "OPERATOR",
|
||||
"options": [
|
||||
[Blockly.Msg.OPERATORS_MATHOP_ABS, 'abs'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_FLOOR, 'floor'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_CEILING, 'ceiling'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_SQRT, 'sqrt'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_SIN, 'sin'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_COS, 'cos'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_TAN, 'tan'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_ASIN, 'asin'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_ACOS, 'acos'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_ATAN, 'atan'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_LN, 'ln'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_LOG, 'log'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_EEXP, 'e ^'],
|
||||
[Blockly.Msg.OPERATORS_MATHOP_10EXP, '10 ^']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.operators,
|
||||
"extensions": ["colours_operators", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
1051
scratch-blocks/blocks_vertical/procedures.js
Normal file
1051
scratch-blocks/blocks_vertical/procedures.js
Normal file
File diff suppressed because it is too large
Load Diff
821
scratch-blocks/blocks_vertical/sensing.js
Normal file
821
scratch-blocks/blocks_vertical/sensing.js
Normal file
@@ -0,0 +1,821 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.sensing');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
|
||||
Blockly.Blocks['sensing_touchingobject'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_TOUCHINGOBJECT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TOUCHINGOBJECTMENU"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 角色方向转
|
||||
Blockly.Blocks['sensing_001_base_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_001_BASE_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock'],
|
||||
[Blockly.Msg.SENSING_ISMapEdge, 'MapEdge']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_001_forward_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_001_FORWARD_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_001_back_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_001_BACK_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock'],
|
||||
[Blockly.Msg.SENSING_ISMapEdge, 'MapEdge']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_001_left_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_001_LEFT_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock'],
|
||||
[Blockly.Msg.SENSING_ISMapEdge, 'MapEdge']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_001_right_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_001_RIGHT_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock'],
|
||||
[Blockly.Msg.SENSING_ISMapEdge, 'MapEdge']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//世界方向砖
|
||||
|
||||
Blockly.Blocks['sensing_base_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_BASE_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_north_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_NORTH_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_south_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_SOUTH_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_east_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_EAST_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_west_block'] = {
|
||||
/**
|
||||
* Block to Report if its touching a Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_WEST_BLOCK,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "BLOCKS",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_ISWallBlock, 'WallBlock'],
|
||||
[Blockly.Msg.SENSING_ISBaseblock, 'Baseblock'],
|
||||
[Blockly.Msg.SENSING_ISJumpBlock, 'JumpBlock'],
|
||||
[Blockly.Msg.SENSING_ISRideBlock, 'RideBlock'],
|
||||
[Blockly.Msg.SENSING_ISNoneBlock, 'NoneBlock']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_touchingobjectmenu'] = {
|
||||
/**
|
||||
* "Touching [Object]" Block Menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TOUCHINGOBJECTMENU",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_TOUCHINGOBJECT_POINTER, '_mouse_'],
|
||||
[Blockly.Msg.SENSING_TOUCHINGOBJECT_EDGE, '_edge_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": ["colours_sensing", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_touchingcolor'] = {
|
||||
/**
|
||||
* Block to Report if its touching a certain Color.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_TOUCHINGCOLOR,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "COLOR"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_coloristouchingcolor'] = {
|
||||
/**
|
||||
* Block to Report if a color is touching a certain Color.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_COLORISTOUCHINGCOLOR,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "COLOR"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "COLOR2"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_distanceto'] = {
|
||||
/**
|
||||
* Block to Report distance to another Object.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_DISTANCETO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "DISTANCETOMENU"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_distancetomenu'] = {
|
||||
/**
|
||||
* "Distance to [Object]" Block Menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "DISTANCETOMENU",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_DISTANCETO_POINTER, '_mouse_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": ["colours_sensing", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_askandwait'] = {
|
||||
/**
|
||||
* Block to ask a question and wait
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_ASKANDWAIT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "QUESTION"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_answer'] = {
|
||||
/**
|
||||
* Block to report answer
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_ANSWER,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_keypressed'] = {
|
||||
/**
|
||||
* Block to Report if a key is pressed.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_KEYPRESSED,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "KEY_OPTION"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_keyoptions'] = {
|
||||
/**
|
||||
* Options for Keys
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "KEY_OPTION",
|
||||
"options": [
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_SPACE, 'space'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_UP, 'up arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_DOWN, 'down arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_RIGHT, 'right arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_LEFT, 'left arrow'],
|
||||
[Blockly.Msg.EVENT_WHENKEYPRESSED_ANY, 'any'],
|
||||
['a', 'a'],
|
||||
['b', 'b'],
|
||||
['c', 'c'],
|
||||
['d', 'd'],
|
||||
['e', 'e'],
|
||||
['f', 'f'],
|
||||
['g', 'g'],
|
||||
['h', 'h'],
|
||||
['i', 'i'],
|
||||
['j', 'j'],
|
||||
['k', 'k'],
|
||||
['l', 'l'],
|
||||
['m', 'm'],
|
||||
['n', 'n'],
|
||||
['o', 'o'],
|
||||
['p', 'p'],
|
||||
['q', 'q'],
|
||||
['r', 'r'],
|
||||
['s', 's'],
|
||||
['t', 't'],
|
||||
['u', 'u'],
|
||||
['v', 'v'],
|
||||
['w', 'w'],
|
||||
['x', 'x'],
|
||||
['y', 'y'],
|
||||
['z', 'z'],
|
||||
['0', '0'],
|
||||
['1', '1'],
|
||||
['2', '2'],
|
||||
['3', '3'],
|
||||
['4', '4'],
|
||||
['5', '5'],
|
||||
['6', '6'],
|
||||
['7', '7'],
|
||||
['8', '8'],
|
||||
['9', '9']
|
||||
]
|
||||
}
|
||||
],
|
||||
"extensions": ["colours_sensing", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_mousedown'] = {
|
||||
/**
|
||||
* Block to Report if the mouse is down.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_MOUSEDOWN,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_mousex'] = {
|
||||
/**
|
||||
* Block to report mouse's x position
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_MOUSEX,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_mousey'] = {
|
||||
/**
|
||||
* Block to report mouse's y position
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_MOUSEY,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_setdragmode'] = {
|
||||
/**
|
||||
* Block to set drag mode.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_SETDRAGMODE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "DRAG_MODE",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_SETDRAGMODE_DRAGGABLE, 'draggable'],
|
||||
[Blockly.Msg.SENSING_SETDRAGMODE_NOTDRAGGABLE, 'not draggable']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_loudness'] = {
|
||||
/**
|
||||
* Block to report loudness
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_LOUDNESS,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_loud'] = {
|
||||
/**
|
||||
* Block to report if the loudness is "loud" (greater than 10). This is an
|
||||
* obsolete block that is implemented for compatibility with Scratch 2.0 and
|
||||
* 1.4 projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_LOUD,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_boolean"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_timer'] = {
|
||||
/**
|
||||
* Block to report timer
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_TIMER,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": false,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_resettimer'] = {
|
||||
/**
|
||||
* Block to reset timer
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_RESETTIMER,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_of_object_menu'] = {
|
||||
/**
|
||||
* "* of _" object menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "OBJECT",
|
||||
"options": [
|
||||
['Sprite1', 'Sprite1'],
|
||||
['Stage', '_stage_']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Blockly.Blocks['sensing_of'] = {
|
||||
/**
|
||||
* Block to report properties of sprites.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_OF,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "PROPERTY",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_OF_XPOSITION, 'x position'],
|
||||
[Blockly.Msg.SENSING_OF_YPOSITION, 'y position'],
|
||||
[Blockly.Msg.SENSING_OF_DIRECTION, 'direction'],
|
||||
[Blockly.Msg.SENSING_OF_COSTUMENUMBER, 'costume #'],
|
||||
[Blockly.Msg.SENSING_OF_COSTUMENAME, 'costume name'],
|
||||
[Blockly.Msg.SENSING_OF_SIZE, 'size'],
|
||||
[Blockly.Msg.SENSING_OF_VOLUME, 'volume'],
|
||||
[Blockly.Msg.SENSING_OF_BACKDROPNUMBER, 'backdrop #'],
|
||||
[Blockly.Msg.SENSING_OF_BACKDROPNAME, 'backdrop name']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "OBJECT"
|
||||
}
|
||||
],
|
||||
"output": true,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"outputShape": Blockly.OUTPUT_SHAPE_ROUND,
|
||||
"extensions": ["colours_sensing"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_current'] = {
|
||||
/**
|
||||
* Block to Report the current option.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_CURRENT,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "CURRENTMENU",
|
||||
"options": [
|
||||
[Blockly.Msg.SENSING_CURRENT_YEAR, 'YEAR'],
|
||||
[Blockly.Msg.SENSING_CURRENT_MONTH, 'MONTH'],
|
||||
[Blockly.Msg.SENSING_CURRENT_DATE, 'DATE'],
|
||||
[Blockly.Msg.SENSING_CURRENT_DAYOFWEEK, 'DAYOFWEEK'],
|
||||
[Blockly.Msg.SENSING_CURRENT_HOUR, 'HOUR'],
|
||||
[Blockly.Msg.SENSING_CURRENT_MINUTE, 'MINUTE'],
|
||||
[Blockly.Msg.SENSING_CURRENT_SECOND, 'SECOND']
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": false,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_dayssince2000'] = {
|
||||
/**
|
||||
* Block to report days since 2000
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_DAYSSINCE2000,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": false,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_username'] = {
|
||||
/**
|
||||
* Block to report user's username
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_USERNAME,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sensing_userid'] = {
|
||||
/**
|
||||
* Block to report user's ID. Does not actually do anything. This is an
|
||||
* obsolete block that is implemented for compatibility with Scratch 2.0
|
||||
* projects.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SENSING_USERID,
|
||||
"category": Blockly.Categories.sensing,
|
||||
"extensions": ["colours_sensing", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
246
scratch-blocks/blocks_vertical/sound.js
Normal file
246
scratch-blocks/blocks_vertical/sound.js
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.sound');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
Blockly.Blocks['sound_sounds_menu'] = {
|
||||
/**
|
||||
* Sound effects drop-down menu.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "SOUND_MENU",
|
||||
"options": [
|
||||
['1', '0'],
|
||||
['2', '1'],
|
||||
['3', '2'],
|
||||
['4', '3'],
|
||||
['5', '4'],
|
||||
['6', '5'],
|
||||
['7', '6'],
|
||||
['8', '7'],
|
||||
['9', '8'],
|
||||
['10', '9'],
|
||||
['call a function', function() {
|
||||
window.alert('function called!');}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"colour": Blockly.Colours.sounds.secondary,
|
||||
"colourSecondary": Blockly.Colours.sounds.secondary,
|
||||
"colourTertiary": Blockly.Colours.sounds.tertiary,
|
||||
"colourQuaternary": Blockly.Colours.sounds.quaternary,
|
||||
"extensions": ["output_string"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_play'] = {
|
||||
/**
|
||||
* Block to play sound.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_PLAY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SOUND_MENU"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_playuntildone'] = {
|
||||
/**
|
||||
* Block to play sound until done.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_PLAYUNTILDONE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "SOUND_MENU"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_stopallsounds'] = {
|
||||
/**
|
||||
* Block to stop all sounds
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_STOPALLSOUNDS,
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_seteffectto'] = {
|
||||
/**
|
||||
* Block to set the audio effect
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_SETEFFECTO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "EFFECT",
|
||||
"options": [
|
||||
[Blockly.Msg.SOUND_EFFECTS_PITCH, 'PITCH'],
|
||||
[Blockly.Msg.SOUND_EFFECTS_PAN, 'PAN']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Blockly.Blocks['sound_changeeffectby'] = {
|
||||
/**
|
||||
* Block to change the audio effect
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_CHANGEEFFECTBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "EFFECT",
|
||||
"options": [
|
||||
[Blockly.Msg.SOUND_EFFECTS_PITCH, 'PITCH'],
|
||||
[Blockly.Msg.SOUND_EFFECTS_PAN, 'PAN']
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_cleareffects'] = {
|
||||
/**
|
||||
* Block to clear audio effects
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_CLEAREFFECTS,
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_changevolumeby'] = {
|
||||
/**
|
||||
* Block to change the sprite's volume by a certain value
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_CHANGEVOLUMEBY,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VOLUME"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_setvolumeto'] = {
|
||||
/**
|
||||
* Block to set the sprite's volume to a certain percent
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_SETVOLUMETO,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VOLUME"
|
||||
}
|
||||
],
|
||||
"category": Blockly.Categories.sound,
|
||||
"extensions": ["colours_sounds", "shape_statement"]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['sound_volume'] = {
|
||||
/**
|
||||
* Block to report volume
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.SOUND_VOLUME,
|
||||
"category": Blockly.Categories.sound,
|
||||
"checkboxInFlyout": true,
|
||||
"extensions": ["colours_sounds", "output_number"]
|
||||
});
|
||||
}
|
||||
};
|
||||
289
scratch-blocks/blocks_vertical/vertical_extensions.js
Normal file
289
scratch-blocks/blocks_vertical/vertical_extensions.js
Normal file
@@ -0,0 +1,289 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Extensions for vertical blocks in scratch-blocks.
|
||||
* The following extensions can be used to describe a block in Scratch terms.
|
||||
* For instance, a block in the operators colour scheme with a number output
|
||||
* would have the "colours_operators" and "output_number" extensions.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.ScratchBlocks.VerticalExtensions');
|
||||
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
|
||||
/**
|
||||
* Helper function that generates an extension based on a category name.
|
||||
* The generated function will set primary, secondary, tertiary, and quaternary
|
||||
* colours based on the category name.
|
||||
* @param {String} category The name of the category to set colours for.
|
||||
* @return {function} An extension function that sets colours based on the given
|
||||
* category.
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.colourHelper = function(category) {
|
||||
var colours = Blockly.Colours[category];
|
||||
if (!(colours && colours.primary && colours.secondary && colours.tertiary &&
|
||||
colours.quaternary)) {
|
||||
throw new Error('Could not find colours for category "' + category + '"');
|
||||
}
|
||||
/**
|
||||
* Set the primary, secondary, tertiary, and quaternary colours on this block for
|
||||
* the given category.
|
||||
* @this {Blockly.Block}
|
||||
*/
|
||||
return function() {
|
||||
this.setColourFromRawValues_(colours.primary, colours.secondary,
|
||||
colours.tertiary, colours.quaternary);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to set the colours of a text field, which are all the same.
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.COLOUR_TEXTFIELD = function() {
|
||||
this.setColourFromRawValues_(Blockly.Colours.textField,
|
||||
Blockly.Colours.textField, Blockly.Colours.textField,
|
||||
Blockly.Colours.textField);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to make a block fit into a stack of statements, regardless of its
|
||||
* inputs. That means the block should have a previous connection and a next
|
||||
* connection and have inline inputs.
|
||||
* @this {Blockly.Block}
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_STATEMENT = function() {
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to make a block be shaped as a hat block, regardless of its
|
||||
* inputs. That means the block should have a next connection and have inline
|
||||
* inputs, but have no previous connection.
|
||||
* @this {Blockly.Block}
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_HAT = function() {
|
||||
this.setInputsInline(true);
|
||||
this.setNextStatement(true, null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to make a block be shaped as an end block, regardless of its
|
||||
* inputs. That means the block should have a previous connection and have
|
||||
* inline inputs, but have no next connection.
|
||||
* @this {Blockly.Block}
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_END = function() {
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true, null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to make represent a number reporter in Scratch-Blocks.
|
||||
* That means the block has inline inputs, a round output shape, and a 'Number'
|
||||
* output type.
|
||||
* @this {Blockly.Block}
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_NUMBER = function() {
|
||||
this.setInputsInline(true);
|
||||
this.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND);
|
||||
this.setOutput(true, 'Number');
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to make represent a string reporter in Scratch-Blocks.
|
||||
* That means the block has inline inputs, a round output shape, and a 'String'
|
||||
* output type.
|
||||
* @this {Blockly.Block}
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_STRING = function() {
|
||||
this.setInputsInline(true);
|
||||
this.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND);
|
||||
this.setOutput(true, 'String');
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension to make represent a boolean reporter in Scratch-Blocks.
|
||||
* That means the block has inline inputs, a round output shape, and a 'Boolean'
|
||||
* output type.
|
||||
* @this {Blockly.Block}
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_BOOLEAN = function() {
|
||||
this.setInputsInline(true);
|
||||
this.setOutputShape(Blockly.OUTPUT_SHAPE_HEXAGONAL);
|
||||
this.setOutput(true, 'Boolean');
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin to add a context menu for a procedure definition block.
|
||||
* It adds the "edit" option and removes the "duplicate" option.
|
||||
* @mixin
|
||||
* @augments Blockly.Block
|
||||
* @package
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.PROCEDURE_DEF_CONTEXTMENU = {
|
||||
/**
|
||||
* Add the "edit" option and removes the "duplicate" option from the context
|
||||
* menu.
|
||||
* @param {!Array.<!Object>} menuOptions List of menu options to edit.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(menuOptions) {
|
||||
// Add the edit option at the end.
|
||||
menuOptions.push(Blockly.Procedures.makeEditOption(this));
|
||||
|
||||
// Find the delete option and update its callback to be specific to
|
||||
// functions.
|
||||
for (var i = 0, option; option = menuOptions[i]; i++) {
|
||||
if (option.text == Blockly.Msg.DELETE_BLOCK) {
|
||||
var input = this.getInput('custom_block');
|
||||
// this is the root block, not the shadow block.
|
||||
if (input && input.connection && input.connection.targetBlock()) {
|
||||
var procCode = input.connection.targetBlock().getProcCode();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
var rootBlock = this;
|
||||
option.callback = function() {
|
||||
var didDelete = Blockly.Procedures.deleteProcedureDefCallback(
|
||||
procCode, rootBlock);
|
||||
if (!didDelete) {
|
||||
alert(Blockly.Msg.PROCEDURE_USED);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Find and remove the duplicate option
|
||||
for (var i = 0, option; option = menuOptions[i]; i++) {
|
||||
if (option.text == Blockly.Msg.DUPLICATE) {
|
||||
menuOptions.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mixin to add a context menu for a procedure call block.
|
||||
* It adds the "edit" option and the "define" option.
|
||||
* @mixin
|
||||
* @augments Blockly.Block
|
||||
* @package
|
||||
* @readonly
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.PROCEDURE_CALL_CONTEXTMENU = {
|
||||
/**
|
||||
* Add the "edit" option to the context menu.
|
||||
* @todo Add "go to definition" option once implemented.
|
||||
* @param {!Array.<!Object>} menuOptions List of menu options to edit.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(menuOptions) {
|
||||
menuOptions.push(Blockly.Procedures.makeEditOption(this));
|
||||
if (
|
||||
!this.isInFlyout &&
|
||||
Blockly.Procedures.USER_CAN_CHANGE_CALL_TYPE &&
|
||||
this.workspace.procedureReturnsEnabled
|
||||
) {
|
||||
menuOptions.push(Blockly.Procedures.makeChangeTypeOption(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.ScratchBlocks.VerticalExtensions.FROM_EXTENSION = function() {
|
||||
this.isFromExtension = true;
|
||||
};
|
||||
|
||||
Blockly.ScratchBlocks.VerticalExtensions.DEFAULT_EXTENSION_COLORS = function() {
|
||||
this.usesDefaultExtensionColors = true;
|
||||
};
|
||||
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SCRATCH_EXTENSION = function() {
|
||||
this.isScratchExtension = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Register all extensions for scratch-blocks.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ScratchBlocks.VerticalExtensions.registerAll = function() {
|
||||
var categoryNames =
|
||||
['control', 'data', 'data_lists', 'sounds', 'motion', 'looks', 'event',
|
||||
'sensing', 'pen', 'operators', 'more'];
|
||||
// Register functions for all category colours.
|
||||
for (var i = 0; i < categoryNames.length; i++) {
|
||||
var name = categoryNames[i];
|
||||
Blockly.Extensions.register('colours_' + name,
|
||||
Blockly.ScratchBlocks.VerticalExtensions.colourHelper(name));
|
||||
}
|
||||
|
||||
// Text fields transcend categories.
|
||||
Blockly.Extensions.register('colours_textfield',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.COLOUR_TEXTFIELD);
|
||||
|
||||
// Register extensions for common block shapes.
|
||||
Blockly.Extensions.register('shape_statement',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_STATEMENT);
|
||||
Blockly.Extensions.register('shape_hat',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_HAT);
|
||||
Blockly.Extensions.register('shape_end',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SHAPE_END);
|
||||
|
||||
// Output shapes and types are related.
|
||||
Blockly.Extensions.register('output_number',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_NUMBER);
|
||||
Blockly.Extensions.register('output_string',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_STRING);
|
||||
Blockly.Extensions.register('output_boolean',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.OUTPUT_BOOLEAN);
|
||||
|
||||
// Custom procedures have interesting context menus.
|
||||
Blockly.Extensions.registerMixin('procedure_def_contextmenu',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.PROCEDURE_DEF_CONTEXTMENU);
|
||||
Blockly.Extensions.registerMixin('procedure_call_contextmenu',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.PROCEDURE_CALL_CONTEXTMENU);
|
||||
|
||||
// Given to all blocks from an extension.
|
||||
Blockly.Extensions.register('from_extension',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.FROM_EXTENSION);
|
||||
|
||||
// Given to blocks that use the default extension colors ("pen")
|
||||
Blockly.Extensions.register('default_extension_colors',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.DEFAULT_EXTENSION_COLORS);
|
||||
|
||||
// Misleading name. Given to blocks that have an extension icon.
|
||||
Blockly.Extensions.register('scratch_extension',
|
||||
Blockly.ScratchBlocks.VerticalExtensions.SCRATCH_EXTENSION);
|
||||
};
|
||||
|
||||
Blockly.ScratchBlocks.VerticalExtensions.registerAll();
|
||||
647
scratch-blocks/build.py
Executable file
647
scratch-blocks/build.py
Executable file
@@ -0,0 +1,647 @@
|
||||
#!/usr/bin/python2.7
|
||||
# Compresses the core Blockly files into a single JavaScript file.
|
||||
#
|
||||
# Copyright 2012 Google Inc.
|
||||
# https://developers.google.com/blockly/
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This script generates two versions of Blockly's core files:
|
||||
# blockly_compressed.js
|
||||
# blockly_uncompressed.js
|
||||
# The compressed file is a concatenation of all of Blockly's core files which
|
||||
# have been run through Google's Closure Compiler. This is done using the
|
||||
# online API (which takes a few seconds and requires an Internet connection).
|
||||
# The uncompressed file is a script that loads in each of Blockly's core files
|
||||
# one by one. This takes much longer for a browser to load, but is useful
|
||||
# when debugging code since line numbers are meaningful and variables haven't
|
||||
# been renamed. The uncompressed file also allows for a faster developement
|
||||
# cycle since there is no need to rebuild or recompile, just reload.
|
||||
#
|
||||
# This script also generates:
|
||||
# blocks_compressed.js: The compressed common blocks.
|
||||
# blocks_horizontal_compressed.js: The compressed Scratch horizontal blocks.
|
||||
# blocks_vertical_compressed.js: The compressed Scratch vertical blocks.
|
||||
# msg/js/<LANG>.js for every language <LANG> defined in msg/js/<LANG>.json.
|
||||
|
||||
import sys
|
||||
|
||||
import errno, glob, json, os, re, subprocess, threading, codecs, functools, platform
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
import httplib
|
||||
from urllib import urlencode
|
||||
else:
|
||||
import http.client as httplib
|
||||
from urllib.parse import urlencode
|
||||
from importlib import reload
|
||||
|
||||
REMOTE_COMPILER = "remote"
|
||||
|
||||
CLOSURE_DIR = os.path.pardir
|
||||
CLOSURE_ROOT = os.path.pardir
|
||||
CLOSURE_LIBRARY = "closure-library"
|
||||
CLOSURE_COMPILER = REMOTE_COMPILER
|
||||
|
||||
CLOSURE_DIR_NPM = "node_modules"
|
||||
CLOSURE_ROOT_NPM = os.path.join("node_modules")
|
||||
CLOSURE_LIBRARY_NPM = "google-closure-library"
|
||||
CLOSURE_COMPILER_NPM = ("google-closure-compiler.cmd" if os.name == "nt" else "google-closure-compiler")
|
||||
|
||||
def import_path(fullpath):
|
||||
"""Import a file with full path specification.
|
||||
Allows one to import from any directory, something __import__ does not do.
|
||||
|
||||
Args:
|
||||
fullpath: Path and filename of import.
|
||||
|
||||
Returns:
|
||||
An imported module.
|
||||
"""
|
||||
path, filename = os.path.split(fullpath)
|
||||
filename, ext = os.path.splitext(filename)
|
||||
sys.path.append(path)
|
||||
module = __import__(filename)
|
||||
reload(module) # Might be out of date.
|
||||
del sys.path[-1]
|
||||
return module
|
||||
|
||||
def read(filename):
|
||||
f = open(filename)
|
||||
content = "".join(f.readlines())
|
||||
f.close()
|
||||
return content
|
||||
|
||||
HEADER = ("// Do not edit this file; automatically generated by build.py.\n"
|
||||
"'use strict';\n")
|
||||
|
||||
|
||||
class Gen_uncompressed(threading.Thread):
|
||||
"""Generate a JavaScript file that loads Blockly's raw files.
|
||||
Runs in a separate thread.
|
||||
"""
|
||||
def __init__(self, search_paths, vertical, closure_env):
|
||||
threading.Thread.__init__(self)
|
||||
self.search_paths = search_paths
|
||||
self.vertical = vertical
|
||||
self.closure_env = closure_env
|
||||
|
||||
def run(self):
|
||||
if self.vertical:
|
||||
target_filename = 'blockly_uncompressed_vertical.js'
|
||||
else:
|
||||
target_filename = 'blockly_uncompressed_horizontal.js'
|
||||
f = open(target_filename, 'w')
|
||||
f.write(HEADER)
|
||||
f.write(self.format_js("""
|
||||
var isNodeJS = !!(typeof module !== 'undefined' && module.exports &&
|
||||
typeof window === 'undefined');
|
||||
|
||||
if (isNodeJS) {
|
||||
var window = {};
|
||||
require('{closure_library}');
|
||||
}
|
||||
|
||||
window.BLOCKLY_DIR = (function() {
|
||||
if (!isNodeJS) {
|
||||
// Find name of current directory.
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var re = new RegExp('(.+)[\\/]blockly_uncompressed(_vertical|_horizontal|)\\.js$');
|
||||
for (var i = 0, script; script = scripts[i]; i++) {
|
||||
var match = re.exec(script.src);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
alert('Could not detect Blockly\\'s directory name.');
|
||||
}
|
||||
return '';
|
||||
})();
|
||||
|
||||
window.BLOCKLY_BOOT = function() {
|
||||
var dir = '';
|
||||
if (isNodeJS) {
|
||||
require('{closure_library}');
|
||||
dir = 'blockly';
|
||||
} else {
|
||||
// Execute after Closure has loaded.
|
||||
if (!window.goog) {
|
||||
alert('Error: Closure not found. Read this:\\n' +
|
||||
'developers.google.com/blockly/guides/modify/web/closure');
|
||||
}
|
||||
if (window.BLOCKLY_DIR.search(/node_modules/)) {
|
||||
dir = '..';
|
||||
} else {
|
||||
dir = window.BLOCKLY_DIR.match(/[^\\/]+$/)[0];
|
||||
}
|
||||
}
|
||||
"""))
|
||||
add_dependency = []
|
||||
base_path = calcdeps.FindClosureBasePath(self.search_paths)
|
||||
for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths):
|
||||
add_dependency.append(calcdeps.GetDepsLine(dep, base_path))
|
||||
add_dependency.sort() # Deterministic build.
|
||||
add_dependency = '\n'.join(add_dependency)
|
||||
# Find the Blockly directory name and replace it with a JS variable.
|
||||
# This allows blockly_uncompressed.js to be compiled on one computer and be
|
||||
# used on another, even if the directory name differs.
|
||||
m = re.search('[\\/]([^\\/]+)[\\/]core[\\/]blockly.js', add_dependency)
|
||||
add_dependency = re.sub('([\\/])' + re.escape(m.group(1)) +
|
||||
'([\\/]core[\\/])', '\\1" + dir + "\\2', add_dependency)
|
||||
f.write(add_dependency + '\n')
|
||||
|
||||
provides = []
|
||||
for dep in calcdeps.BuildDependenciesFromFiles(self.search_paths):
|
||||
# starts with '../' or 'node_modules/'
|
||||
if not dep.filename.startswith(self.closure_env["closure_root"] + os.sep):
|
||||
provides.extend(dep.provides)
|
||||
provides.sort() # Deterministic build.
|
||||
f.write('\n')
|
||||
f.write('// Load Blockly.\n')
|
||||
for provide in provides:
|
||||
f.write("goog.require('%s');\n" % provide)
|
||||
|
||||
f.write(self.format_js("""
|
||||
delete this.BLOCKLY_DIR;
|
||||
delete this.BLOCKLY_BOOT;
|
||||
};
|
||||
|
||||
if (isNodeJS) {
|
||||
window.BLOCKLY_BOOT();
|
||||
module.exports = Blockly;
|
||||
} else {
|
||||
// Delete any existing Closure (e.g. Soy's nogoog_shim).
|
||||
document.write('<script>var goog = undefined;</script>');
|
||||
// Load fresh Closure Library.
|
||||
document.write('<script src="' + window.BLOCKLY_DIR +
|
||||
'/{closure_dir}/{closure_library}/closure/goog/base.js"></script>');
|
||||
document.write('<script>window.BLOCKLY_BOOT();</script>');
|
||||
}
|
||||
"""))
|
||||
f.close()
|
||||
print("SUCCESS: " + target_filename)
|
||||
|
||||
def format_js(self, code):
|
||||
"""Format JS in a way that python's format method can work with to not
|
||||
consider brace-wrapped sections to be format replacements while still
|
||||
replacing known keys.
|
||||
"""
|
||||
|
||||
key_whitelist = self.closure_env.keys()
|
||||
|
||||
keys_pipe_separated = functools.reduce(lambda accum, key: accum + "|" + key, key_whitelist)
|
||||
begin_brace = re.compile(r"\{(?!%s)" % (keys_pipe_separated,))
|
||||
|
||||
end_brace = re.compile(r"\}")
|
||||
def end_replacement(match):
|
||||
try:
|
||||
maybe_key = match.string[match.string[:match.start()].rindex("{") + 1:match.start()]
|
||||
except ValueError:
|
||||
return "}}"
|
||||
|
||||
if maybe_key and maybe_key in key_whitelist:
|
||||
return "}"
|
||||
else:
|
||||
return "}}"
|
||||
|
||||
return begin_brace.sub("{{", end_brace.sub(end_replacement, code)).format(**self.closure_env)
|
||||
|
||||
class Gen_compressed(threading.Thread):
|
||||
"""Generate a JavaScript file that contains all of Blockly's core and all
|
||||
required parts of Closure, compiled together.
|
||||
Uses the Closure Compiler's online API.
|
||||
Runs in a separate thread.
|
||||
"""
|
||||
def __init__(self, search_paths_vertical, search_paths_horizontal, closure_env):
|
||||
threading.Thread.__init__(self)
|
||||
self.search_paths_vertical = search_paths_vertical
|
||||
self.search_paths_horizontal = search_paths_horizontal
|
||||
self.closure_env = closure_env
|
||||
|
||||
def run(self):
|
||||
self.gen_core(True)
|
||||
self.gen_core(False)
|
||||
self.gen_blocks("horizontal")
|
||||
self.gen_blocks("vertical")
|
||||
self.gen_blocks("common")
|
||||
|
||||
def gen_core(self, vertical):
|
||||
if vertical:
|
||||
target_filename = 'blockly_compressed_vertical.js'
|
||||
search_paths = self.search_paths_vertical
|
||||
else:
|
||||
target_filename = 'blockly_compressed_horizontal.js'
|
||||
search_paths = self.search_paths_horizontal
|
||||
# Define the parameters for the POST request.
|
||||
params = [
|
||||
("compilation_level", "SIMPLE"),
|
||||
|
||||
# remote will filter this out
|
||||
("language_in", "ECMASCRIPT_2017"),
|
||||
("language_out", "ECMASCRIPT5"),
|
||||
("rewrite_polyfills", "false"),
|
||||
("define", "goog.DEBUG=false"),
|
||||
|
||||
# local will filter this out
|
||||
("use_closure_library", "true"),
|
||||
]
|
||||
|
||||
# Read in all the source files.
|
||||
filenames = calcdeps.CalculateDependencies(search_paths,
|
||||
[os.path.join("core", "blockly.js")])
|
||||
filenames.sort() # Deterministic build.
|
||||
for filename in filenames:
|
||||
# Append filenames as false arguments the step before compiling will
|
||||
# either transform them into arguments for local or remote compilation
|
||||
params.append(("js_file", filename))
|
||||
|
||||
self.do_compile(params, target_filename, filenames, "")
|
||||
|
||||
def gen_blocks(self, block_type):
|
||||
if block_type == "horizontal":
|
||||
target_filename = "blocks_compressed_horizontal.js"
|
||||
filenames = glob.glob(os.path.join("blocks_horizontal", "*.js"))
|
||||
elif block_type == "vertical":
|
||||
target_filename = "blocks_compressed_vertical.js"
|
||||
filenames = glob.glob(os.path.join("blocks_vertical", "*.js"))
|
||||
elif block_type == "common":
|
||||
target_filename = "blocks_compressed.js"
|
||||
filenames = glob.glob(os.path.join("blocks_common", "*.js"))
|
||||
|
||||
# glob.glob ordering is platform-dependent and not necessary deterministic
|
||||
filenames.sort() # Deterministic build.
|
||||
|
||||
# Define the parameters for the POST request.
|
||||
params = [
|
||||
("compilation_level", "SIMPLE"),
|
||||
]
|
||||
|
||||
# Read in all the source files.
|
||||
# Add Blockly.Blocks to be compatible with the compiler.
|
||||
params.append(("js_file", os.path.join("build", "gen_blocks.js")))
|
||||
# Add Blockly.Colours for use of centralized colour bank
|
||||
filenames.append(os.path.join("core", "colours.js"))
|
||||
filenames.append(os.path.join("core", "constants.js"))
|
||||
|
||||
for filename in filenames:
|
||||
# Append filenames as false arguments the step before compiling will
|
||||
# either transform them into arguments for local or remote compilation
|
||||
params.append(("js_file", filename))
|
||||
|
||||
# Remove Blockly.Blocks to be compatible with Blockly.
|
||||
remove = "var Blockly={Blocks:{}};"
|
||||
self.do_compile(params, target_filename, filenames, remove)
|
||||
|
||||
def do_compile(self, params, target_filename, filenames, remove):
|
||||
if self.closure_env["closure_compiler"] == REMOTE_COMPILER:
|
||||
do_compile = self.do_compile_remote
|
||||
else:
|
||||
do_compile = self.do_compile_local
|
||||
json_data = do_compile(params, target_filename)
|
||||
|
||||
if self.report_errors(target_filename, filenames, json_data):
|
||||
self.write_output(target_filename, remove, json_data)
|
||||
self.report_stats(target_filename, json_data)
|
||||
|
||||
def do_compile_local(self, params, target_filename):
|
||||
filter_keys = ["use_closure_library"]
|
||||
|
||||
# Drop arg if arg is js_file else add dashes
|
||||
dash_params = []
|
||||
for (arg, value) in params:
|
||||
dash_params.append((value,) if arg == "js_file" else ("--" + arg, value))
|
||||
|
||||
# Flatten dash_params into dash_args if their keys are not in filter_keys
|
||||
dash_args = []
|
||||
for pair in dash_params:
|
||||
if pair[0][2:] not in filter_keys:
|
||||
dash_args.extend(pair)
|
||||
|
||||
# Build the final args array by prepending CLOSURE_COMPILER_NPM to
|
||||
# dash_args and dropping any falsy members
|
||||
args = []
|
||||
for group in [[CLOSURE_COMPILER_NPM], dash_args]:
|
||||
args.extend(filter(lambda item: item, group))
|
||||
|
||||
# On Windows, the command line is too long, so we save the arguments to a file instead
|
||||
use_flagfile = platform.system() == "Windows"
|
||||
if platform.system() == "Windows":
|
||||
flagfile_name = target_filename + ".config"
|
||||
with open(flagfile_name, "w") as f:
|
||||
# \ needs to be escaped still
|
||||
f.write(" ".join(args[1:]).replace("\\", "\\\\"))
|
||||
args = [CLOSURE_COMPILER_NPM, "--flagfile", flagfile_name]
|
||||
|
||||
proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
(stdout, stderr) = proc.communicate()
|
||||
|
||||
if use_flagfile:
|
||||
os.remove(flagfile_name)
|
||||
|
||||
# Build the JSON response.
|
||||
filesizes = [os.path.getsize(value) for (arg, value) in params if arg == "js_file"]
|
||||
return dict(
|
||||
compiledCode=stdout,
|
||||
statistics=dict(
|
||||
originalSize=functools.reduce(lambda v, size: v + size, filesizes, 0),
|
||||
compressedSize=len(stdout),
|
||||
)
|
||||
)
|
||||
|
||||
def do_compile_remote(self, params, target_filename):
|
||||
filter_keys = [
|
||||
"language_in",
|
||||
"language_out",
|
||||
"rewrite_polyfills",
|
||||
"define",
|
||||
]
|
||||
|
||||
params.extend([
|
||||
("output_format", "json"),
|
||||
("output_info", "compiled_code"),
|
||||
("output_info", "warnings"),
|
||||
("output_info", "errors"),
|
||||
("output_info", "statistics"),
|
||||
])
|
||||
|
||||
# Send the request to Google.
|
||||
remoteParams = []
|
||||
for (arg, value) in params:
|
||||
if not arg in filter_keys:
|
||||
if arg == "js_file":
|
||||
if not value.startswith(self.closure_env["closure_root"] + os.sep):
|
||||
remoteParams.append(("js_code", read(value)))
|
||||
# Change the normal compilation_level value SIMPLE to the remove
|
||||
# service's SIMPLE_OPTIMIZATIONS
|
||||
elif arg == "compilation_level" and value == "SIMPLE":
|
||||
remoteParams.append((arg, "SIMPLE_OPTIMIZATIONS"))
|
||||
else:
|
||||
remoteParams.append((arg, value))
|
||||
|
||||
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||
conn = httplib.HTTPSConnection("closure-compiler.appspot.com")
|
||||
conn.request("POST", "/compile", urlencode(remoteParams), headers)
|
||||
response = conn.getresponse()
|
||||
# Decode is necessary for Python 3.4 compatibility
|
||||
json_str = response.read().decode("utf-8")
|
||||
conn.close()
|
||||
|
||||
# Parse the JSON response.
|
||||
return json.loads(json_str)
|
||||
|
||||
def report_errors(self, target_filename, filenames, json_data):
|
||||
def file_lookup(name):
|
||||
if not name.startswith("Input_"):
|
||||
return "???"
|
||||
n = int(name[6:]) - 1
|
||||
return filenames[n]
|
||||
|
||||
if "serverErrors" in json_data:
|
||||
errors = json_data["serverErrors"]
|
||||
for error in errors:
|
||||
print("SERVER ERROR: %s" % target_filename)
|
||||
print(error["error"])
|
||||
elif "errors" in json_data:
|
||||
errors = json_data["errors"]
|
||||
for error in errors:
|
||||
print("FATAL ERROR")
|
||||
print(error["error"])
|
||||
if error["file"]:
|
||||
print("%s at line %d:" % (
|
||||
file_lookup(error["file"]), error["lineno"]))
|
||||
print(error["line"])
|
||||
print((" " * error["charno"]) + "^")
|
||||
sys.exit(1)
|
||||
else:
|
||||
if "warnings" in json_data:
|
||||
warnings = json_data["warnings"]
|
||||
for warning in warnings:
|
||||
print("WARNING")
|
||||
print(warning["warning"])
|
||||
if warning["file"]:
|
||||
print("%s at line %d:" % (
|
||||
file_lookup(warning["file"]), warning["lineno"]))
|
||||
print(warning["line"])
|
||||
print((" " * warning["charno"]) + "^")
|
||||
print()
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def write_output(self, target_filename, remove, json_data):
|
||||
if "compiledCode" not in json_data:
|
||||
print("FATAL ERROR: Compiler did not return compiledCode.")
|
||||
sys.exit(1)
|
||||
|
||||
code = HEADER + "\n" + json_data["compiledCode"].decode("utf-8")
|
||||
code = code.replace(remove, "")
|
||||
|
||||
# Trim down Google's (and only Google's) Apache licences.
|
||||
# The Closure Compiler preserves these.
|
||||
LICENSE = re.compile("""/\\*
|
||||
|
||||
[\\w ]+
|
||||
|
||||
Copyright \\d+ Google Inc.
|
||||
https://developers.google.com/blockly/
|
||||
|
||||
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.
|
||||
\\*/""")
|
||||
code = re.sub(LICENSE, "", code)
|
||||
|
||||
stats = json_data["statistics"]
|
||||
original_b = stats["originalSize"]
|
||||
compressed_b = stats["compressedSize"]
|
||||
if original_b > 0 and compressed_b > 0:
|
||||
f = open(target_filename, "w")
|
||||
f.write(code)
|
||||
f.close()
|
||||
|
||||
def report_stats(self, target_filename, json_data):
|
||||
stats = json_data["statistics"]
|
||||
original_b = stats["originalSize"]
|
||||
compressed_b = stats["compressedSize"]
|
||||
if original_b > 0 and compressed_b > 0:
|
||||
original_kb = int(original_b / 1024 + 0.5)
|
||||
compressed_kb = int(compressed_b / 1024 + 0.5)
|
||||
ratio = int(float(compressed_b) / float(original_b) * 100 + 0.5)
|
||||
print("SUCCESS: " + target_filename)
|
||||
print("Size changed from %d KB to %d KB (%d%%)." % (
|
||||
original_kb, compressed_kb, ratio))
|
||||
else:
|
||||
print("UNKNOWN ERROR")
|
||||
|
||||
|
||||
class Gen_langfiles(threading.Thread):
|
||||
"""Generate JavaScript file for each natural language supported.
|
||||
|
||||
Runs in a separate thread.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def _rebuild(self, srcs, dests):
|
||||
# Determine whether any of the files in srcs is newer than any in dests.
|
||||
try:
|
||||
return (max(os.path.getmtime(src) for src in srcs) >
|
||||
min(os.path.getmtime(dest) for dest in dests))
|
||||
except OSError as e:
|
||||
# Was a file not found?
|
||||
if e.errno == errno.ENOENT:
|
||||
# If it was a source file, we can't proceed.
|
||||
if e.filename in srcs:
|
||||
print("Source file missing: " + e.filename)
|
||||
sys.exit(1)
|
||||
else:
|
||||
# If a destination file was missing, rebuild.
|
||||
return True
|
||||
else:
|
||||
print("Error checking file creation times: " + str(e))
|
||||
|
||||
def run(self):
|
||||
# The files msg/json/{en,qqq,synonyms}.json depend on msg/messages.js.
|
||||
if self._rebuild([os.path.join("msg", "messages.js")],
|
||||
[os.path.join("msg", "json", f) for f in
|
||||
["en.json", "qqq.json", "synonyms.json"]]):
|
||||
try:
|
||||
subprocess.check_call([
|
||||
"python",
|
||||
os.path.join("i18n", "js_to_json.py"),
|
||||
"--input_file", "msg/messages.js",
|
||||
"--output_dir", "msg/json/",
|
||||
"--quiet"])
|
||||
except (subprocess.CalledProcessError, OSError) as e:
|
||||
# Documentation for subprocess.check_call says that CalledProcessError
|
||||
# will be raised on failure, but I found that OSError is also possible.
|
||||
print("Error running i18n/js_to_json.py: ", e)
|
||||
sys.exit(1)
|
||||
|
||||
# Checking whether it is necessary to rebuild the js files would be a lot of
|
||||
# work since we would have to compare each <lang>.json file with each
|
||||
# <lang>.js file. Rebuilding is easy and cheap, so just go ahead and do it.
|
||||
try:
|
||||
# Use create_messages.py to create .js files from .json files.
|
||||
cmd = [
|
||||
"python",
|
||||
os.path.join("i18n", "create_messages.py"),
|
||||
"--source_lang_file", os.path.join("msg", "json", "en.json"),
|
||||
"--source_synonym_file", os.path.join("msg", "json", "synonyms.json"),
|
||||
"--source_constants_file", os.path.join("msg", "json", "constants.json"),
|
||||
"--key_file", os.path.join("msg", "json", "keys.json"),
|
||||
"--output_dir", os.path.join("msg", "js"),
|
||||
"--quiet"]
|
||||
json_files = glob.glob(os.path.join("msg", "json", "*.json"))
|
||||
json_files = [file for file in json_files if not
|
||||
(file.endswith(("keys.json", "synonyms.json", "qqq.json", "constants.json")))]
|
||||
cmd.extend(json_files)
|
||||
subprocess.check_call(cmd)
|
||||
except (subprocess.CalledProcessError, OSError) as e:
|
||||
print("Error running i18n/create_messages.py: ", e)
|
||||
sys.exit(1)
|
||||
|
||||
# Output list of .js files created.
|
||||
for f in json_files:
|
||||
# This assumes the path to the current directory does not contain "json".
|
||||
f = f.replace("json", "js")
|
||||
if os.path.isfile(f):
|
||||
print("SUCCESS: " + f)
|
||||
else:
|
||||
print("FAILED to create " + f)
|
||||
|
||||
def exclude_vertical(item):
|
||||
return not item.endswith("block_render_svg_vertical.js")
|
||||
|
||||
def exclude_horizontal(item):
|
||||
return not item.endswith("block_render_svg_horizontal.js")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
closure_dir = CLOSURE_DIR_NPM
|
||||
closure_root = CLOSURE_ROOT_NPM
|
||||
closure_library = CLOSURE_LIBRARY_NPM
|
||||
closure_compiler = CLOSURE_COMPILER_NPM
|
||||
|
||||
# Load calcdeps from the local library
|
||||
calcdeps = import_path(os.path.join(
|
||||
closure_root, closure_library, "closure", "bin", "calcdeps.py"))
|
||||
|
||||
# Sanity check the local compiler
|
||||
test_args = [closure_compiler, os.path.join("build", "test_input.js")]
|
||||
test_proc = subprocess.Popen(test_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
(stdout, _) = test_proc.communicate()
|
||||
assert stdout.decode("utf-8") == read(os.path.join("build", "test_expect.js"))
|
||||
|
||||
print("Using local compiler: %s ...\n" % CLOSURE_COMPILER_NPM)
|
||||
except (ImportError, AssertionError):
|
||||
if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")):
|
||||
# Dir got renamed when Closure moved from Google Code to GitHub in 2014.
|
||||
print("Error: Closure directory needs to be renamed from"
|
||||
"'closure-library-read-only' to 'closure-library'.\n"
|
||||
"Please rename this directory.")
|
||||
elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")):
|
||||
print("Error: Closure directory needs to be renamed from"
|
||||
"'google-closure-library' to 'closure-library'.\n"
|
||||
"Please rename this directory.")
|
||||
else:
|
||||
print("""Error: Closure not found. Usually this means 'npm ci' failed. Try running it again? More resources:
|
||||
developers.google.com/blockly/guides/modify/web/closure""")
|
||||
sys.exit(1)
|
||||
|
||||
search_paths = list(calcdeps.ExpandDirectories(
|
||||
["core", os.path.join(closure_root, closure_library)]))
|
||||
|
||||
search_paths_horizontal = list(filter(exclude_vertical, search_paths))
|
||||
search_paths_vertical = list(filter(exclude_horizontal, search_paths))
|
||||
|
||||
closure_env = {
|
||||
"closure_dir": closure_dir,
|
||||
"closure_root": closure_root,
|
||||
"closure_library": closure_library,
|
||||
"closure_compiler": closure_compiler,
|
||||
}
|
||||
|
||||
# Run all tasks in parallel threads.
|
||||
# Uncompressed is limited by processor speed.
|
||||
# Compressed is limited by network and server speed.
|
||||
threads = [
|
||||
# Vertical:
|
||||
Gen_uncompressed(search_paths_vertical, True, closure_env),
|
||||
# Horizontal:
|
||||
Gen_uncompressed(search_paths_horizontal, False, closure_env),
|
||||
# Compressed forms of vertical and horizontal.
|
||||
Gen_compressed(search_paths_vertical, search_paths_horizontal, closure_env),
|
||||
|
||||
# This is run locally in a separate thread.
|
||||
# Gen_langfiles()
|
||||
]
|
||||
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
|
||||
# Need to wait for all threads to finish before the main process ends as in Python 3.12,
|
||||
# once the main interpreter is being shutdown, trying to spawn more child threads will
|
||||
# raise "RuntimeError: can't create new thread at interpreter shutdown"
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
1
scratch-blocks/build/gen_blocks.js
Normal file
1
scratch-blocks/build/gen_blocks.js
Normal file
@@ -0,0 +1 @@
|
||||
goog.provide('Blockly.Blocks');
|
||||
1
scratch-blocks/build/test_expect.js
Normal file
1
scratch-blocks/build/test_expect.js
Normal file
@@ -0,0 +1 @@
|
||||
var Blockly={Blocks:{}};
|
||||
1
scratch-blocks/build/test_input.js
Normal file
1
scratch-blocks/build/test_input.js
Normal file
@@ -0,0 +1 @@
|
||||
goog.provide('Blockly.Blocks');
|
||||
101
scratch-blocks/cleanup.sh
Executable file
101
scratch-blocks/cleanup.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script for cleaning up blockly-specific files when merging blockly into scratch-blocks
|
||||
# Removes files and directories that scratch-blocks doesn't want.
|
||||
# Rachel Fenichel (fenichel@google.com)
|
||||
|
||||
# Note: 'ours' is scratch-blocks, 'theirs' is blockly.
|
||||
|
||||
# Formatting helpers.
|
||||
indent() { sed 's/^/ /'; }
|
||||
indent_more() { sed 's/^/\t/'; }
|
||||
empty_lines() { printf '\n\n'; }
|
||||
|
||||
|
||||
empty_lines
|
||||
echo Cleaning up a merge from Blockly to Scratch-Blocks...
|
||||
|
||||
# Get rid of Blockly's internationalization/messages. This is not usually worth
|
||||
# scrolling up to look at.
|
||||
empty_lines
|
||||
echo Cleaning up Blockly message files...
|
||||
# Turn on more powerful globbing
|
||||
shopt -s extglob
|
||||
|
||||
# Having trouble with directories. Let's just go there.
|
||||
cd msg/json
|
||||
git rm -f !(en.json|synonyms.json) | indent_more
|
||||
cd ../..
|
||||
|
||||
# Having trouble with directories. Let's just go there.
|
||||
cd msg/js
|
||||
git rm -f !(en.js) | indent_more
|
||||
cd ../..
|
||||
|
||||
# Turn powerful globbing off again
|
||||
shopt -u extglob
|
||||
|
||||
# Whole directories that we want to get rid of.
|
||||
empty_lines
|
||||
echo Removing blockly-specific directories...
|
||||
dirslist="accessible demos tests/generators appengine blocks local_build"
|
||||
for directory in $dirslist
|
||||
do
|
||||
echo 'Cleaning up' $directory | indent
|
||||
git rm -rf $directory | indent_more
|
||||
done
|
||||
|
||||
# Scratch-blocks does not use generators
|
||||
empty_lines
|
||||
echo Removing generators...
|
||||
generated_langs="dart javascript lua php python"
|
||||
for lang in $generated_langs
|
||||
do
|
||||
echo 'Cleaning up' $lang | indent
|
||||
# Directories containing block generators.
|
||||
git rm -rf generators/${lang} | indent_more
|
||||
done
|
||||
|
||||
# Built stuff that we should get rid of.
|
||||
empty_lines
|
||||
echo Removing built files...
|
||||
built_files="blockly_compressed.js \
|
||||
blockly_uncompressed.js \
|
||||
blockly_accessible_compressed.js \
|
||||
blockly_accessible_uncompressed.js \
|
||||
blocks_compressed.js \
|
||||
dart_compressed.js \
|
||||
php_compressed.js \
|
||||
python_compressed.js \
|
||||
javascript_compressed.js \
|
||||
lua_compressed.js"
|
||||
|
||||
for filename in $built_files
|
||||
do
|
||||
git rm $filename | indent_more
|
||||
done
|
||||
|
||||
empty_lines
|
||||
echo Miscellaneous cleanup...
|
||||
# Use ours.
|
||||
keep_ours=".github/ISSUE_TEMPLATE.md \
|
||||
.github/PULL_REQUEST_TEMPLATE.md \
|
||||
.gitignore \
|
||||
.circleci/config.yml \
|
||||
core/block_animations.js \
|
||||
msg/messages.js \
|
||||
msg/js/en.js \
|
||||
msg/json/en.json"
|
||||
|
||||
|
||||
for filename in $keep_ours
|
||||
do
|
||||
git checkout --ours $filename && git add $filename | indent_more
|
||||
done
|
||||
|
||||
# Scratch-blocks has separate vertical and horizontal playgrounds and block
|
||||
# rendering.
|
||||
git rm -f tests/playground.html core/block_render_svg.js | indent_more
|
||||
|
||||
empty_lines
|
||||
echo Done with cleanup.
|
||||
1837
scratch-blocks/core/block.js
Normal file
1837
scratch-blocks/core/block.js
Normal file
File diff suppressed because it is too large
Load Diff
107
scratch-blocks/core/block_animations.js
Normal file
107
scratch-blocks/core/block_animations.js
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods animating a block on connection and disconnection.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockAnimations');
|
||||
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, animation) when disposing of a block.
|
||||
* @param {!Blockly.BlockSvg} block The block being disposed of.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockAnimations.disposeUiEffect = function(block) {
|
||||
var workspace = block.workspace;
|
||||
var svgGroup = block.getSvgRoot();
|
||||
workspace.getAudioManager().play('delete');
|
||||
|
||||
var xy = workspace.getSvgXY(svgGroup);
|
||||
// Deeply clone the current block.
|
||||
var clone = svgGroup.cloneNode(true);
|
||||
clone.translateX_ = xy.x;
|
||||
clone.translateY_ = xy.y;
|
||||
clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
|
||||
workspace.getParentSvg().appendChild(clone);
|
||||
clone.bBox_ = clone.getBBox();
|
||||
// Start the animation.
|
||||
Blockly.BlockAnimations.disposeUiStep_(clone, workspace.RTL, new Date,
|
||||
workspace.scale);
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate a cloned block and eventually dispose of it.
|
||||
* This is a class method, not an instance method since the original block has
|
||||
* been destroyed and is no longer accessible.
|
||||
* @param {!Element} clone SVG element to animate and dispose of.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @param {!Date} start Date of animation's start.
|
||||
* @param {number} workspaceScale Scale of workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockAnimations.disposeUiStep_ = function(clone, rtl, start,
|
||||
workspaceScale) {
|
||||
var ms = new Date - start;
|
||||
var percent = ms / 150;
|
||||
if (percent > 1) {
|
||||
goog.dom.removeNode(clone);
|
||||
} else {
|
||||
var x = clone.translateX_ +
|
||||
(rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
|
||||
var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
|
||||
var scale = (1 - percent) * workspaceScale;
|
||||
clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
|
||||
' scale(' + scale + ')');
|
||||
setTimeout(Blockly.BlockAnimations.disposeUiStep_, 10, clone, rtl, start,
|
||||
workspaceScale);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, ripple) after a connection has been established.
|
||||
* @param {!Blockly.BlockSvg} block The block being connected.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockAnimations.connectionUiEffect = function(block) {
|
||||
block.workspace.getAudioManager().play('click');
|
||||
};
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, animation) when disconnecting a block.
|
||||
* No-op in scratch-blocks, which has no disconnect animation.
|
||||
* @param {!Blockly.BlockSvg} _block The block being disconnected.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockAnimations.disconnectUiEffect = function(
|
||||
/* eslint-disable no-unused-vars */ _block
|
||||
/* eslint-enable no-unused-vars */) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop the disconnect UI animation immediately.
|
||||
* No-op in scratch-blocks, which has no disconnect animation.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockAnimations.disconnectUiStop = function() {
|
||||
};
|
||||
299
scratch-blocks/core/block_drag_surface.js
Normal file
299
scratch-blocks/core/block_drag_surface.js
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A class that manages a surface for dragging blocks. When a
|
||||
* block drag is started, we move the block (and children) to a separate DOM
|
||||
* element that we move around using translate3d. At the end of the drag, the
|
||||
* blocks are put back in into the SVG they came from. This helps performance by
|
||||
* avoiding repainting the entire SVG on every mouse move while dragging blocks.
|
||||
* @author picklesrus
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockDragSurfaceSvg');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a drag surface for the currently dragged block. This is a separate
|
||||
* SVG that contains only the currently moving block, or nothing.
|
||||
* @param {!Element} container Containing element.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg = function(container) {
|
||||
/**
|
||||
* @type {!Element}
|
||||
* @private
|
||||
*/
|
||||
this.container_ = container;
|
||||
this.createDom();
|
||||
};
|
||||
|
||||
/**
|
||||
* The SVG drag surface. Set once by Blockly.BlockDragSurfaceSvg.createDom.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.SVG_ = null;
|
||||
|
||||
/**
|
||||
* This is where blocks live while they are being dragged if the drag surface
|
||||
* is enabled.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.dragGroup_ = null;
|
||||
|
||||
/**
|
||||
* Containing HTML element; parent of the workspace and the drag surface.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.container_ = null;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the drag surface.
|
||||
* This translation is in pixel units, because the scale is applied to the
|
||||
* drag group rather than the top-level SVG.
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.surfaceXY_ = null;
|
||||
|
||||
/**
|
||||
* ID for the drag shadow filter, set in createDom.
|
||||
* Belongs in Scratch Blocks but not Blockly.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.dragShadowFilterId_ = '';
|
||||
|
||||
/**
|
||||
* Standard deviation for gaussian blur on drag shadow, in px.
|
||||
* Belongs in Scratch Blocks but not Blockly.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.SHADOW_STD_DEVIATION = 6;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.createDom = function() {
|
||||
if (this.SVG_) {
|
||||
return; // Already created.
|
||||
}
|
||||
this.SVG_ = Blockly.utils.createSvgElement('svg',
|
||||
{
|
||||
'xmlns': Blockly.SVG_NS,
|
||||
'xmlns:html': Blockly.HTML_NS,
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'class': 'blocklyBlockDragSurface'
|
||||
}, this.container_);
|
||||
this.dragGroup_ = Blockly.utils.createSvgElement('g', {}, this.SVG_);
|
||||
// Belongs in Scratch Blocks, but not Blockly.
|
||||
var defs = Blockly.utils.createSvgElement('defs', {}, this.SVG_);
|
||||
this.dragShadowFilterId_ = this.createDropShadowDom_(defs);
|
||||
this.dragGroup_.setAttribute(
|
||||
'filter', 'url(#' + this.dragShadowFilterId_ + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Scratch-specific: Create the SVG def for the drop shadow.
|
||||
* @param {Element} defs Defs element to insert the shadow filter definition
|
||||
* @return {string} ID for the filter element
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.createDropShadowDom_ = function(defs) {
|
||||
var rnd = String(Math.random()).substring(2);
|
||||
// Adjust these width/height, x/y properties to stop the shadow from clipping
|
||||
var dragShadowFilter = Blockly.utils.createSvgElement('filter',
|
||||
{
|
||||
'id': 'blocklyDragShadowFilter' + rnd,
|
||||
'height': '140%',
|
||||
'width': '140%',
|
||||
'y': '-20%',
|
||||
'x': '-20%'
|
||||
},
|
||||
defs);
|
||||
Blockly.utils.createSvgElement('feGaussianBlur',
|
||||
{
|
||||
'in': 'SourceAlpha',
|
||||
'stdDeviation': Blockly.BlockDragSurfaceSvg.SHADOW_STD_DEVIATION
|
||||
},
|
||||
dragShadowFilter);
|
||||
var componentTransfer = Blockly.utils.createSvgElement(
|
||||
'feComponentTransfer', {'result': 'offsetBlur'}, dragShadowFilter);
|
||||
// Shadow opacity is specified in the adjustable colour library,
|
||||
// since the darkness of the shadow largely depends on the workspace colour.
|
||||
Blockly.utils.createSvgElement('feFuncA',
|
||||
{
|
||||
'type': 'linear',
|
||||
'slope': Blockly.Colours.dragShadowOpacity
|
||||
},
|
||||
componentTransfer);
|
||||
Blockly.utils.createSvgElement('feComposite',
|
||||
{
|
||||
'in': 'SourceGraphic',
|
||||
'in2': 'offsetBlur',
|
||||
'operator': 'over'
|
||||
},
|
||||
dragShadowFilter);
|
||||
return dragShadowFilter.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the SVG blocks on the drag surface's group and show the surface.
|
||||
* Only one block group should be on the drag surface at a time.
|
||||
* @param {!Element} blocks Block or group of blocks to place on the drag
|
||||
* surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
|
||||
goog.asserts.assert(
|
||||
this.dragGroup_.childNodes.length == 0, 'Already dragging a block.');
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.SVG_.style.display = 'block';
|
||||
this.surfaceXY_ = new goog.math.Coordinate(0, 0);
|
||||
// This allows blocks to be dragged outside of the blockly svg space.
|
||||
// This should be reset to hidden at the end of the block drag.
|
||||
// Note that this behavior is different from blockly where block disappear
|
||||
// "under" the blockly area.
|
||||
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
|
||||
injectionDiv.style.overflow = 'visible';
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to the given position, to
|
||||
* keep in sync with the workspace.
|
||||
* @param {number} x X translation in workspace coordinates.
|
||||
* @param {number} y Y translation in workspace coordinates.
|
||||
* @param {number} scale Scale of the group.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
|
||||
this.scale_ = scale;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
var fixedX = x.toFixed(0);
|
||||
var fixedY = y.toFixed(0);
|
||||
this.dragGroup_.setAttribute('transform',
|
||||
'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the drag surface's SVG based on its internal state.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurfaceInternal_ = function() {
|
||||
var x = this.surfaceXY_.x;
|
||||
var y = this.surfaceXY_.y;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.SVG_.style.display = 'block';
|
||||
|
||||
Blockly.utils.setCssTransform(this.SVG_,
|
||||
'translate(' + x + 'px, ' + y + 'px)');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
* so that the browser avoids repainting the SVG.
|
||||
* Because of this, the drag coordinates must be adjusted by scale.
|
||||
* @param {number} x X translation for the entire surface.
|
||||
* @param {number} y Y translation for the entire surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
this.surfaceXY_ = new goog.math.Coordinate(x * this.scale_, y * this.scale_);
|
||||
this.translateSurfaceInternal_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
* @return {!goog.math.Coordinate} Current translation of the surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
var xy = Blockly.utils.getRelativeXY(this.SVG_);
|
||||
return new goog.math.Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a reference to the drag group (primarily for
|
||||
* BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {Element} Drag surface group element.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.getGroup = function() {
|
||||
return this.dragGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current blocks on the drag surface, if any (primarily
|
||||
* for BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {!Element|undefined} Drag surface block DOM element, or undefined
|
||||
* if no blocks exist.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
|
||||
return this.dragGroup_.firstChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided
|
||||
* element.
|
||||
* If the block is being deleted it doesn't need to go back to the original
|
||||
* surface, since it would be removed immediately during dispose.
|
||||
* @param {Element=} opt_newSurface Surface the dragging blocks should be moved
|
||||
* to, or null if the blocks should be removed from this surface without
|
||||
* being moved to a different surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(opt_newSurface) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
opt_newSurface.appendChild(this.getCurrentBlock());
|
||||
} else {
|
||||
this.dragGroup_.removeChild(this.getCurrentBlock());
|
||||
}
|
||||
this.SVG_.style.display = 'none';
|
||||
goog.asserts.assert(
|
||||
this.dragGroup_.childNodes.length == 0, 'Drag group was not cleared.');
|
||||
this.surfaceXY_ = null;
|
||||
|
||||
// Reset the overflow property back to hidden so that nothing appears outside
|
||||
// of the blockly area.
|
||||
// Note that this behavior is different from blockly. See note in
|
||||
// setBlocksAndShow.
|
||||
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
|
||||
injectionDiv.style.overflow = 'hidden';
|
||||
};
|
||||
424
scratch-blocks/core/block_dragger.js
Normal file
424
scratch-blocks/core/block_dragger.js
Normal file
@@ -0,0 +1,424 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a block visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockDragger');
|
||||
|
||||
goog.require('Blockly.BlockAnimations');
|
||||
goog.require('Blockly.Events.BlockMove');
|
||||
goog.require('Blockly.Events.DragBlockOutside');
|
||||
goog.require('Blockly.Events.EndBlockDrag');
|
||||
goog.require('Blockly.InsertionMarkerManager');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block dragger. It moves blocks around the workspace when they
|
||||
* are being dragged by a mouse or touch.
|
||||
* @param {!Blockly.BlockSvg} block The block to drag.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BlockDragger = function(block, workspace) {
|
||||
/**
|
||||
* The top block in the stack that is being dragged.
|
||||
* @type {!Blockly.BlockSvg}
|
||||
* @private
|
||||
*/
|
||||
this.draggingBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which the block is being dragged.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Object that keeps track of connections on dragged blocks.
|
||||
* @type {!Blockly.InsertionMarkerManager}
|
||||
* @private
|
||||
*/
|
||||
this.draggedConnectionManager_ = new Blockly.InsertionMarkerManager(
|
||||
this.draggingBlock_);
|
||||
|
||||
/**
|
||||
* Which delete area the mouse pointer is over, if any.
|
||||
* One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
this.deleteArea_ = null;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if dropped immediately.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
|
||||
/**
|
||||
* Whether the currently dragged block is outside of the workspace. Keep
|
||||
* track so that we can fire events only when this changes.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wasOutside_ = false;
|
||||
|
||||
/**
|
||||
* The location of the top left corner of the dragging block at the beginning
|
||||
* of the drag in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* A list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @type {Array.<!Object>}
|
||||
* @private
|
||||
*/
|
||||
this.dragIconData_ = Blockly.BlockDragger.initIconData_(block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dispose = function() {
|
||||
this.draggingBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.startWorkspace_ = null;
|
||||
this.dragIconData_.length = 0;
|
||||
|
||||
if (this.draggedConnectionManager_) {
|
||||
this.draggedConnectionManager_.dispose();
|
||||
this.draggedConnectionManager_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @param {!Blockly.BlockSvg} block The root block that is being dragged.
|
||||
* @return {!Array.<!Object>} The list of all icons and their locations.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.initIconData_ = function(block) {
|
||||
// Build a list of icons that need to be moved and where they started.
|
||||
var dragIconData = [];
|
||||
var descendants = block.getDescendants(false);
|
||||
for (var i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
var icons = descendant.getIcons();
|
||||
for (var j = 0; j < icons.length; j++) {
|
||||
var data = {
|
||||
// goog.math.Coordinate with x and y properties (workspace coordinates).
|
||||
location: icons[j].getIconLocation(),
|
||||
// Blockly.Icon
|
||||
icon: icons[j]
|
||||
};
|
||||
dragIconData.push(data);
|
||||
}
|
||||
}
|
||||
return dragIconData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start dragging a block. This includes moving it to the drag surface.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.startBlockDrag = function(currentDragDeltaXY) {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
Blockly.BlockAnimations.disconnectUiStop();
|
||||
|
||||
if (this.draggingBlock_.getParent()) {
|
||||
this.draggingBlock_.unplug();
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBlock_.translate(newLoc.x, newLoc.y);
|
||||
Blockly.BlockAnimations.disconnectUiEffect(this.draggingBlock_);
|
||||
}
|
||||
this.draggingBlock_.setDragging(true);
|
||||
// For future consideration: we may be able to put moveToDragSurface inside
|
||||
// the block dragger, which would also let the block not track the block drag
|
||||
// surface.
|
||||
this.draggingBlock_.moveToDragSurface_();
|
||||
|
||||
var toolbox = this.workspace_.getToolbox();
|
||||
if (toolbox) {
|
||||
var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
toolbox.addStyle(style);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a step of block dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
* @param {!Event} e The most recent move event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
* @return {boolean} True if the event should be propagated, false if not.
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dragBlock = function(e, currentDragDeltaXY) {
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBlock_.moveDuringDrag(newLoc);
|
||||
this.dragIcons_(delta);
|
||||
|
||||
this.deleteArea_ = this.workspace_.isDeleteArea(e);
|
||||
var isOutside = !this.workspace_.isInsideBlocksArea(e);
|
||||
this.draggedConnectionManager_.update(delta, this.deleteArea_, isOutside);
|
||||
if (isOutside !== this.wasOutside_) {
|
||||
this.fireDragOutsideEvent_(isOutside);
|
||||
this.wasOutside_ = isOutside;
|
||||
}
|
||||
|
||||
this.updateCursorDuringBlockDrag_(isOutside);
|
||||
return isOutside;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish a block drag and put the block back on the workspace.
|
||||
* @param {!Event} e The mouseup/touchend event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.endBlockDrag = function(e, currentDragDeltaXY) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBlock(e, currentDragDeltaXY);
|
||||
this.dragIconData_ = [];
|
||||
var isOutside = this.wasOutside_;
|
||||
this.fireEndDragEvent_(isOutside);
|
||||
this.draggingBlock_.setMouseThroughStyle(false);
|
||||
|
||||
Blockly.BlockAnimations.disconnectUiStop();
|
||||
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
this.draggingBlock_.moveOffDragSurface_(newLoc);
|
||||
|
||||
// Scratch-specific: note possible illegal definition deletion for rollback below.
|
||||
var isDeletingProcDef = this.wouldDeleteBlock_ &&
|
||||
(this.draggingBlock_.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE);
|
||||
if (isDeletingProcDef) {
|
||||
var procCodeBeingDeleted = this.draggingBlock_.getInput('custom_block').connection.targetBlock().getProcCode();
|
||||
}
|
||||
|
||||
var deleted = this.maybeDeleteBlock_();
|
||||
if (!deleted) {
|
||||
// These are expensive and don't need to be done if we're deleting.
|
||||
this.draggingBlock_.moveConnections_(delta.x, delta.y);
|
||||
this.draggingBlock_.setDragging(false);
|
||||
this.fireMoveEvent_();
|
||||
if (this.draggedConnectionManager_.wouldConnectBlock()) {
|
||||
// Applying connections also rerenders the relevant blocks.
|
||||
this.draggedConnectionManager_.applyConnections();
|
||||
} else {
|
||||
this.draggingBlock_.render();
|
||||
}
|
||||
this.draggingBlock_.scheduleSnapAndBump();
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
|
||||
var toolbox = this.workspace_.getToolbox();
|
||||
if (toolbox) {
|
||||
var style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
toolbox.removeStyle(style);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
|
||||
if (isOutside) {
|
||||
var ws = this.workspace_;
|
||||
// Reset a drag to outside of scratch-blocks
|
||||
setTimeout(function() {
|
||||
ws.undo();
|
||||
});
|
||||
}
|
||||
|
||||
// Scratch-specific: roll back deletes that create call blocks with defines.
|
||||
// Have to wait for connections to be re-established, so put in setTimeout.
|
||||
// Only do this if we deleted a proc def.
|
||||
if (isDeletingProcDef) {
|
||||
var ws = this.workspace_;
|
||||
setTimeout(function() {
|
||||
var allBlocks = ws.getAllBlocks();
|
||||
for (var i = 0; i < allBlocks.length; i++) {
|
||||
var block = allBlocks[i];
|
||||
if (block.type == Blockly.PROCEDURES_CALL_BLOCK_TYPE) {
|
||||
var procCode = block.getProcCode();
|
||||
// Check for call blocks with no associated define block.
|
||||
if (procCode === procCodeBeingDeleted) {
|
||||
alert(Blockly.Msg.PROCEDURE_USED);
|
||||
ws.undo();
|
||||
return; // There can only be one define deletion at a time.
|
||||
}
|
||||
}
|
||||
}
|
||||
// The proc deletion was valid, update the toolbox.
|
||||
ws.refreshToolboxSelection_();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire an event when the dragged blocks move outside or back into the blocks workspace
|
||||
* @param {?boolean} isOutside True if the drag is going outside the visible area.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.fireDragOutsideEvent_ = function(isOutside) {
|
||||
var event = new Blockly.Events.DragBlockOutside(this.draggingBlock_);
|
||||
event.isOutside = isOutside;
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire an end drag event at the end of a block drag.
|
||||
* @param {?boolean} isOutside True if the drag is going outside the visible area.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.fireEndDragEvent_ = function(isOutside) {
|
||||
var event = new Blockly.Events.EndBlockDrag(this.draggingBlock_, isOutside);
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire a move event at the end of a block drag.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.fireMoveEvent_ = function() {
|
||||
var event = new Blockly.Events.BlockMove(this.draggingBlock_);
|
||||
event.oldCoordinate = this.startXY_;
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shut the trash can and, if necessary, delete the dragging block.
|
||||
* Should be called at the end of a block drag.
|
||||
* @return {boolean} whether the block was deleted.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() {
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
|
||||
if (this.wouldDeleteBlock_) {
|
||||
if (trashcan) {
|
||||
goog.Timer.callOnce(trashcan.close, 100, trashcan);
|
||||
}
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBlock_.dispose(false, true);
|
||||
} else if (trashcan) {
|
||||
// Make sure the trash can is closed.
|
||||
trashcan.close();
|
||||
}
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging block would be deleted if released immediately.
|
||||
* @param {boolean} isOutside True if the cursor is outside of the blocks workspace
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function(isOutside) {
|
||||
this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
if (this.wouldDeleteBlock_) {
|
||||
this.draggingBlock_.setDeleteStyle(true);
|
||||
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
|
||||
trashcan.setOpen_(true);
|
||||
}
|
||||
} else {
|
||||
this.draggingBlock_.setDeleteStyle(false);
|
||||
if (trashcan) {
|
||||
trashcan.setOpen_(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (isOutside) {
|
||||
// Let mouse events through to GUI
|
||||
this.draggingBlock_.setMouseThroughStyle(true);
|
||||
} else {
|
||||
this.draggingBlock_.setMouseThroughStyle(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a coordinate object from pixels to workspace units, including a
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
* @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values
|
||||
* in css pixel units.
|
||||
* @return {!goog.math.Coordinate} The input coordinate divided by the workspace
|
||||
* scale.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
|
||||
var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale,
|
||||
pixelCoord.y / this.workspace_.scale);
|
||||
if (this.workspace_.isMutator) {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same as
|
||||
// the scale on the parent workspace.
|
||||
// Fix that for dragging.
|
||||
var mainScale = this.workspace_.options.parentWorkspace.scale;
|
||||
result = result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move all of the icons connected to this drag.
|
||||
* @param {!goog.math.Coordinate} dxy How far to move the icons from their
|
||||
* original positions, in workspace units.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragger.prototype.dragIcons_ = function(dxy) {
|
||||
// Moving icons moves their associated bubbles.
|
||||
for (var i = 0; i < this.dragIconData_.length; i++) {
|
||||
var data = this.dragIconData_[i];
|
||||
data.icon.setIconLocation(goog.math.Coordinate.sum(data.location, dxy));
|
||||
}
|
||||
};
|
||||
531
scratch-blocks/core/block_events.js
Normal file
531
scratch-blocks/core/block_events.js
Normal file
@@ -0,0 +1,531 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Classes for all types of block events.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Events.BlockBase');
|
||||
goog.provide('Blockly.Events.BlockChange');
|
||||
goog.provide('Blockly.Events.BlockCreate');
|
||||
goog.provide('Blockly.Events.BlockDelete');
|
||||
goog.provide('Blockly.Events.BlockMove');
|
||||
goog.provide('Blockly.Events.Change'); // Deprecated.
|
||||
goog.provide('Blockly.Events.Create'); // Deprecated.
|
||||
goog.provide('Blockly.Events.Delete'); // Deprecated.
|
||||
goog.provide('Blockly.Events.Move'); // Deprecated.
|
||||
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.Events.Abstract');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a block event.
|
||||
* @param {Blockly.Block} block The block this event corresponds to.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockBase = function(block) {
|
||||
Blockly.Events.BlockBase.superClass_.constructor.call(this);
|
||||
|
||||
/**
|
||||
* The block id for the block this event pertains to
|
||||
* @type {string}
|
||||
*/
|
||||
this.blockId = block.id;
|
||||
this.workspaceId = block.workspace.id;
|
||||
};
|
||||
goog.inherits(Blockly.Events.BlockBase, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.BlockBase.prototype.toJson = function() {
|
||||
var json = Blockly.Events.BlockBase.superClass_.toJson.call(this);
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.BlockBase.prototype.fromJson = function(json) {
|
||||
Blockly.Events.BlockBase.superClass_.toJson.call(this);
|
||||
this.blockId = json['blockId'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
* @param {Blockly.Block} block The changed block. Null for a blank event.
|
||||
* @param {string} element One of 'field', 'comment', 'disabled', etc.
|
||||
* @param {?string} name Name of input or field affected, or null.
|
||||
* @param {*} oldValue Previous value of element.
|
||||
* @param {*} newValue New value of element.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.Change = function(block, element, name, oldValue, newValue) {
|
||||
if (!block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.Change.superClass_.constructor.call(this, block);
|
||||
this.element = element;
|
||||
this.name = name;
|
||||
this.oldValue = oldValue;
|
||||
this.newValue = newValue;
|
||||
};
|
||||
goog.inherits(Blockly.Events.Change, Blockly.Events.BlockBase);
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
* @param {Blockly.Block} block The changed block. Null for a blank event.
|
||||
* @param {string} element One of 'field', 'comment', 'disabled', etc.
|
||||
* @param {?string} name Name of input or field affected, or null.
|
||||
* @param {*} oldValue Previous value of element.
|
||||
* @param {*} newValue New value of element.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockChange = Blockly.Events.Change;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.Change.prototype.type = Blockly.Events.CHANGE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.Change.prototype.toJson = function() {
|
||||
var json = Blockly.Events.Change.superClass_.toJson.call(this);
|
||||
json['element'] = this.element;
|
||||
if (this.name) {
|
||||
json['name'] = this.name;
|
||||
}
|
||||
json['newValue'] = this.newValue;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.Change.prototype.fromJson = function(json) {
|
||||
Blockly.Events.Change.superClass_.fromJson.call(this, json);
|
||||
this.element = json['element'];
|
||||
this.name = json['name'];
|
||||
this.newValue = json['newValue'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.Change.prototype.isNull = function() {
|
||||
return this.oldValue == this.newValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Change.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn("Can't change non-existent block: " + this.blockId);
|
||||
return;
|
||||
}
|
||||
if (block.mutator) {
|
||||
// Close the mutator (if open) since we don't want to update it.
|
||||
block.mutator.setVisible(false);
|
||||
}
|
||||
var value = forward ? this.newValue : this.oldValue;
|
||||
switch (this.element) {
|
||||
case 'field':
|
||||
var field = block.getField(this.name);
|
||||
if (field) {
|
||||
// Run the validator for any side-effects it may have.
|
||||
// The validator's opinion on validity is ignored.
|
||||
field.callValidator(value);
|
||||
field.setValue(value);
|
||||
} else {
|
||||
console.warn("Can't set non-existent field: " + this.name);
|
||||
}
|
||||
break;
|
||||
case 'comment':
|
||||
block.setCommentText(value || null);
|
||||
break;
|
||||
case 'collapsed':
|
||||
block.setCollapsed(value);
|
||||
break;
|
||||
case 'disabled':
|
||||
block.setDisabled(value);
|
||||
break;
|
||||
case 'inline':
|
||||
block.setInputsInline(value);
|
||||
break;
|
||||
case 'mutation':
|
||||
var oldMutation = '';
|
||||
if (block.mutationToDom) {
|
||||
var oldMutationDom = block.mutationToDom();
|
||||
oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
|
||||
}
|
||||
if (block.domToMutation) {
|
||||
value = value || '<mutation></mutation>';
|
||||
var dom = Blockly.Xml.textToDom('<xml>' + value + '</xml>');
|
||||
block.domToMutation(dom.firstChild);
|
||||
}
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
block, 'mutation', null, oldMutation, value));
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown change type: ' + this.element);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
* @param {Blockly.Block} block The created block. Null for a blank event.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.Create = function(block) {
|
||||
if (!block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.Create.superClass_.constructor.call(this, block);
|
||||
|
||||
if (block.workspace.rendered) {
|
||||
this.xml = Blockly.Xml.blockToDomWithXY(block);
|
||||
} else {
|
||||
this.xml = Blockly.Xml.blockToDom(block);
|
||||
}
|
||||
this.ids = Blockly.Events.getDescendantIds_(block);
|
||||
};
|
||||
goog.inherits(Blockly.Events.Create, Blockly.Events.BlockBase);
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
* @param {Blockly.Block} block The created block. Null for a blank event.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockCreate = Blockly.Events.Create;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.Create.prototype.type = Blockly.Events.CREATE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.Create.prototype.toJson = function() {
|
||||
var json = Blockly.Events.Create.superClass_.toJson.call(this);
|
||||
json['xml'] = Blockly.Xml.domToText(this.xml);
|
||||
json['ids'] = this.ids;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.Create.prototype.fromJson = function(json) {
|
||||
Blockly.Events.Create.superClass_.fromJson.call(this, json);
|
||||
this.xml = Blockly.Xml.textToDom('<xml>' + json['xml'] + '</xml>').firstChild;
|
||||
this.ids = json['ids'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Create.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
} else {
|
||||
for (var i = 0, id; id = this.ids[i]; i++) {
|
||||
var block = workspace.getBlockById(id);
|
||||
if (block) {
|
||||
block.dispose(false, false);
|
||||
} else if (id == this.blockId) {
|
||||
// Only complain about root-level block.
|
||||
console.warn("Can't uncreate non-existent block: " + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a block deletion event.
|
||||
* @param {Blockly.Block} block The deleted block. Null for a blank event.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.Delete = function(block) {
|
||||
if (!block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
if (block.getParent()) {
|
||||
throw 'Connected blocks cannot be deleted.';
|
||||
}
|
||||
Blockly.Events.Delete.superClass_.constructor.call(this, block);
|
||||
|
||||
if (block.workspace.rendered) {
|
||||
this.oldXml = Blockly.Xml.blockToDomWithXY(block);
|
||||
} else {
|
||||
this.oldXml = Blockly.Xml.blockToDom(block);
|
||||
}
|
||||
this.ids = Blockly.Events.getDescendantIds_(block);
|
||||
};
|
||||
goog.inherits(Blockly.Events.Delete, Blockly.Events.BlockBase);
|
||||
|
||||
/**
|
||||
* Class for a block deletion event.
|
||||
* @param {Blockly.Block} block The deleted block. Null for a blank event.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockDelete = Blockly.Events.Delete;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.Delete.prototype.type = Blockly.Events.DELETE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.Delete.prototype.toJson = function() {
|
||||
var json = Blockly.Events.Delete.superClass_.toJson.call(this);
|
||||
json['ids'] = this.ids;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.Delete.prototype.fromJson = function(json) {
|
||||
Blockly.Events.Delete.superClass_.fromJson.call(this, json);
|
||||
this.ids = json['ids'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a deletion event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Delete.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
for (var i = 0, id; id = this.ids[i]; i++) {
|
||||
var block = workspace.getBlockById(id);
|
||||
if (block) {
|
||||
block.dispose(false, false);
|
||||
} else if (id == this.blockId) {
|
||||
// Only complain about root-level block.
|
||||
console.warn("Can't delete non-existent block: " + id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.oldXml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a block move event. Created before the move.
|
||||
* @param {Blockly.Block} block The moved block. Null for a blank event.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.Move = function(block) {
|
||||
if (!block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.Move.superClass_.constructor.call(this, block);
|
||||
var location = this.currentLocation_();
|
||||
this.oldParentId = location.parentId;
|
||||
this.oldInputName = location.inputName;
|
||||
this.oldCoordinate = location.coordinate;
|
||||
};
|
||||
goog.inherits(Blockly.Events.Move, Blockly.Events.BlockBase);
|
||||
|
||||
/**
|
||||
* Class for a block move event. Created before the move.
|
||||
* @param {Blockly.Block} block The moved block. Null for a blank event.
|
||||
* @extends {Blockly.Events.BlockBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.BlockMove = Blockly.Events.Move;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.Move.prototype.type = Blockly.Events.MOVE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.Move.prototype.toJson = function() {
|
||||
var json = Blockly.Events.Move.superClass_.toJson.call(this);
|
||||
if (this.newParentId) {
|
||||
json['newParentId'] = this.newParentId;
|
||||
}
|
||||
if (this.newInputName) {
|
||||
json['newInputName'] = this.newInputName;
|
||||
}
|
||||
if (this.newCoordinate) {
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' +
|
||||
Math.round(this.newCoordinate.y);
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.Move.prototype.fromJson = function(json) {
|
||||
Blockly.Events.Move.superClass_.fromJson.call(this, json);
|
||||
this.newParentId = json['newParentId'];
|
||||
this.newInputName = json['newInputName'];
|
||||
if (json['newCoordinate']) {
|
||||
var xy = json['newCoordinate'].split(',');
|
||||
this.newCoordinate =
|
||||
new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1]));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the block's new location. Called after the move.
|
||||
*/
|
||||
Blockly.Events.Move.prototype.recordNew = function() {
|
||||
var location = this.currentLocation_();
|
||||
this.newParentId = location.parentId;
|
||||
this.newInputName = location.inputName;
|
||||
this.newCoordinate = location.coordinate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the parentId and input if the block is connected,
|
||||
* or the XY location if disconnected.
|
||||
* @return {!Object} Collection of location info.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.Move.prototype.currentLocation_ = function() {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
var location = {};
|
||||
var parent = block.getParent();
|
||||
if (parent) {
|
||||
location.parentId = parent.id;
|
||||
var input = parent.getInputWithBlock(block);
|
||||
if (input) {
|
||||
location.inputName = input.name;
|
||||
}
|
||||
} else {
|
||||
var blockXY = block.getRelativeToSurfaceXY();
|
||||
// The X position in the block move event should be the language agnostic
|
||||
// position of the block. I.e. it should not be different in LTR vs. RTL.
|
||||
var rtlAwareX = workspace.RTL ? workspace.getWidth() - blockXY.x : blockXY.x;
|
||||
location.coordinate = new goog.math.Coordinate(rtlAwareX, blockXY.y);
|
||||
}
|
||||
return location;
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.Move.prototype.isNull = function() {
|
||||
return this.oldParentId == this.newParentId &&
|
||||
this.oldInputName == this.newInputName &&
|
||||
goog.math.Coordinate.equals(this.oldCoordinate, this.newCoordinate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a move event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Move.prototype.run = function(forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn("Can't move non-existent block: " + this.blockId);
|
||||
return;
|
||||
}
|
||||
var parentId = forward ? this.newParentId : this.oldParentId;
|
||||
var inputName = forward ? this.newInputName : this.oldInputName;
|
||||
var coordinate = forward ? this.newCoordinate : this.oldCoordinate;
|
||||
var parentBlock = null;
|
||||
if (parentId) {
|
||||
parentBlock = workspace.getBlockById(parentId);
|
||||
if (!parentBlock) {
|
||||
console.warn("Can't connect to non-existent block: " + parentId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (block.getParent()) {
|
||||
block.unplug();
|
||||
}
|
||||
if (coordinate) {
|
||||
var xy = block.getRelativeToSurfaceXY();
|
||||
var rtlAwareX = workspace.RTL ? workspace.getWidth() - coordinate.x : coordinate.x;
|
||||
block.moveBy(rtlAwareX - xy.x, coordinate.y - xy.y);
|
||||
} else {
|
||||
var blockConnection = block.outputConnection || block.previousConnection;
|
||||
var parentConnection;
|
||||
if (inputName) {
|
||||
var input = parentBlock.getInput(inputName);
|
||||
if (input) {
|
||||
parentConnection = input.connection;
|
||||
}
|
||||
} else if (blockConnection.type == Blockly.PREVIOUS_STATEMENT) {
|
||||
parentConnection = parentBlock.nextConnection;
|
||||
}
|
||||
if (parentConnection) {
|
||||
blockConnection.connect(parentConnection);
|
||||
} else {
|
||||
console.warn("Can't connect to non-existent input: " + inputName);
|
||||
}
|
||||
}
|
||||
};
|
||||
892
scratch-blocks/core/block_render_svg_horizontal.js
Normal file
892
scratch-blocks/core/block_render_svg_horizontal.js
Normal file
@@ -0,0 +1,892 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for graphically rendering a block as SVG.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockSvg.render');
|
||||
|
||||
goog.require('Blockly.BlockSvg');
|
||||
|
||||
|
||||
// UI constants for rendering blocks.
|
||||
/**
|
||||
* Grid unit to pixels conversion
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.GRID_UNIT = 4;
|
||||
|
||||
/**
|
||||
* Horizontal space between elements.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.SEP_SPACE_X = 3 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Vertical space between elements.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.SEP_SPACE_Y = 3 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Vertical space above blocks with statements.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.STATEMENT_BLOCK_SPACE = 3 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Height of user inputs
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_HEIGHT = 8 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Width of user inputs
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_WIDTH = 12 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Editable field padding (left/right of the text).
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.EDITABLE_FIELD_PADDING = 0;
|
||||
|
||||
/**
|
||||
* Minimum width of user inputs during editing
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT = 13 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Maximum width of user inputs during editing
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT = 24 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Maximum height of user inputs during editing
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_HEIGHT_MAX_EDIT = 10 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Top padding of user inputs
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_TOP_PADDING = 0.25 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Corner radius of number inputs
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.NUMBER_FIELD_CORNER_RADIUS = 4 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Corner radius of text inputs
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.TEXT_FIELD_CORNER_RADIUS = 1 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Default radius for a field, in px.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_DEFAULT_CORNER_RADIUS = 4 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Minimum width of a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.MIN_BLOCK_X = 1 / 2 * 16 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Minimum height of a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.MIN_BLOCK_Y = 16 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Width of horizontal puzzle tab.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.TAB_WIDTH = 2 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Rounded corner radius.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.CORNER_RADIUS = 1 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Rounded corner radius.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.HAT_CORNER_RADIUS = 8 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Full height of connector notch including rounded corner.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.NOTCH_HEIGHT = 8 * Blockly.BlockSvg.GRID_UNIT + 2;
|
||||
|
||||
/**
|
||||
* Width of connector notch
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.NOTCH_WIDTH = 2 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* SVG path for drawing next/previous notch from top to bottom.
|
||||
* Drawn in pixel units since Bezier control points are off the grid.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.NOTCH_PATH_DOWN =
|
||||
'c 0,2 1,3 2,4 ' +
|
||||
'l 4,4 ' +
|
||||
'c 1,1 2,2 2,4 ' +
|
||||
'v 12 ' +
|
||||
'c 0,2 -1,3 -2,4 ' +
|
||||
'l -4,4 ' +
|
||||
'c -1,1 -2,2 -2,4';
|
||||
|
||||
/**
|
||||
* SVG path for drawing next/previous notch from bottom to top.
|
||||
* Drawn in pixel units since Bezier control points are off the grid.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.NOTCH_PATH_UP =
|
||||
'c 0,-2 1,-3 2,-4 ' +
|
||||
'l 4,-4 ' +
|
||||
'c 1,-1 2,-2 2,-4 ' +
|
||||
'v -12 ' +
|
||||
'c 0,-2 -1,-3 -2,-4 ' +
|
||||
'l -4,-4 ' +
|
||||
'c -1,-1 -2,-2 -2,-4';
|
||||
|
||||
/**
|
||||
* Width of rendered image field in px
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.IMAGE_FIELD_WIDTH = 10 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* Height of rendered image field in px
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.IMAGE_FIELD_HEIGHT = 10 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* y-offset of the top of the field shadow block from the bottom of the block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_Y_OFFSET = -2 * Blockly.BlockSvg.GRID_UNIT;
|
||||
|
||||
/**
|
||||
* SVG start point for drawing the top-left corner.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.TOP_LEFT_CORNER_START =
|
||||
'm ' + Blockly.BlockSvg.CORNER_RADIUS + ',0';
|
||||
|
||||
/**
|
||||
* SVG path for drawing the rounded top-left corner.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.TOP_LEFT_CORNER =
|
||||
'A ' + Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
|
||||
'0,' + Blockly.BlockSvg.CORNER_RADIUS;
|
||||
|
||||
/**
|
||||
* SVG start point for drawing the top-left corner.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.HAT_TOP_LEFT_CORNER_START =
|
||||
'm ' + Blockly.BlockSvg.HAT_CORNER_RADIUS + ',0';
|
||||
/**
|
||||
* SVG path for drawing the rounded top-left corner.
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.HAT_TOP_LEFT_CORNER =
|
||||
'A ' + Blockly.BlockSvg.HAT_CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.HAT_CORNER_RADIUS + ' 0 0,0 ' +
|
||||
'0,' + Blockly.BlockSvg.HAT_CORNER_RADIUS;
|
||||
|
||||
/**
|
||||
* @type {Object} An object containing computed measurements of this block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.renderingMetrics_ = null;
|
||||
|
||||
/**
|
||||
* Max text display length for a field (per-horizontal/vertical)
|
||||
* @const
|
||||
*/
|
||||
Blockly.BlockSvg.MAX_DISPLAY_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* Point size of text field before animation. Must match size in CSS.
|
||||
* See implementation in field_textinput.
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_INITIAL = 12;
|
||||
|
||||
/**
|
||||
* Point size of text field after animation.
|
||||
* See implementation in field_textinput.
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_FINAL = 14;
|
||||
|
||||
/**
|
||||
* Whether text fields are allowed to expand past their truncated block size.
|
||||
* @const{boolean}
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_TEXTINPUT_EXPAND_PAST_TRUNCATION = true;
|
||||
|
||||
/**
|
||||
* Whether text fields should animate their positioning.
|
||||
* @const{boolean}
|
||||
*/
|
||||
Blockly.BlockSvg.FIELD_TEXTINPUT_ANIMATE_POSITIONING = true;
|
||||
|
||||
/**
|
||||
* @param {!Object} first An object containing computed measurements of a
|
||||
* block.
|
||||
* @param {!Object} second Another object containing computed measurements of a
|
||||
* block.
|
||||
* @return {boolean} Whether the two sets of metrics are equivalent.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.metricsAreEquivalent_ = function(first, second) {
|
||||
if (first.statement != second.statement) {
|
||||
return false;
|
||||
}
|
||||
if (first.imageField != second.imageField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((first.height != second.height) ||
|
||||
(first.width != second.width) ||
|
||||
(first.bayHeight != second.bayHeight) ||
|
||||
(first.bayWidth != second.bayWidth) ||
|
||||
(first.fieldRadius != second.fieldRadius) ||
|
||||
(first.startHat != second.startHat)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound) after a connection has been established.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.connectionUiEffect = function() {
|
||||
this.workspace.getAudioManager().play('click');
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the colour of a block.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.updateColour = function() {
|
||||
var fillColour = (this.isGlowing_) ? this.getColourSecondary() : this.getColour();
|
||||
var strokeColour = this.getColourTertiary();
|
||||
|
||||
// Render block stroke
|
||||
this.svgPath_.setAttribute('stroke', strokeColour);
|
||||
|
||||
// Render block fill
|
||||
var fillColour = (this.isGlowingBlock_) ? this.getColourSecondary() : this.getColour();
|
||||
this.svgPath_.setAttribute('fill', fillColour);
|
||||
|
||||
// Render opacity
|
||||
this.svgPath_.setAttribute('fill-opacity', this.getOpacity());
|
||||
|
||||
// Bump every dropdown to change its colour.
|
||||
for (var x = 0, input; input = this.inputList[x]; x++) {
|
||||
for (var y = 0, field; field = input.fieldRow[y]; y++) {
|
||||
field.setText(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Visual effect to show that if the dragging block is dropped, this block will
|
||||
* be replaced. If a shadow block it will disappear. Otherwise it will bump.
|
||||
* @param {boolean} add True if highlighting should be added.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.highlightForReplacement = function(add) {
|
||||
if (add) {
|
||||
var replacementGlowFilterId = this.workspace.options.replacementGlowFilterId
|
||||
|| 'blocklyReplacementGlowFilter';
|
||||
this.svgPath_.setAttribute('filter', 'url(#' + replacementGlowFilterId + ')');
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
} else {
|
||||
this.svgPath_.removeAttribute('filter');
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a bounding box describing the dimensions of this block
|
||||
* and any blocks stacked below it.
|
||||
* @param {boolean=} opt_ignoreFields True if we should ignore fields in the
|
||||
* size calculation, and just give the size of the base block(s).
|
||||
* @return {!{height: number, width: number}} Object with height and width properties.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getHeightWidth = function(opt_ignoreFields) {
|
||||
var height = this.height;
|
||||
var width = this.width;
|
||||
// Add the size of the field shadow block.
|
||||
if (!opt_ignoreFields && this.getFieldShadowBlock_()) {
|
||||
height += Blockly.BlockSvg.FIELD_Y_OFFSET;
|
||||
height += Blockly.BlockSvg.FIELD_HEIGHT;
|
||||
}
|
||||
// Recursively add size of subsequent blocks.
|
||||
var nextBlock = this.getNextBlock();
|
||||
if (nextBlock) {
|
||||
var nextHeightWidth = nextBlock.getHeightWidth(opt_ignoreFields);
|
||||
width += nextHeightWidth.width;
|
||||
width -= Blockly.BlockSvg.NOTCH_WIDTH; // Exclude width of connected notch.
|
||||
height = Math.max(height, nextHeightWidth.height);
|
||||
}
|
||||
return {height: height, width: width};
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the block.
|
||||
* Lays out and reflows a block based on its contents and settings.
|
||||
* @param {boolean=} opt_bubble If false, just render this block.
|
||||
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.render = function(opt_bubble) {
|
||||
Blockly.Field.startCache();
|
||||
this.rendered = true;
|
||||
|
||||
var oldMetrics = this.renderingMetrics_;
|
||||
var metrics = this.renderCompute_();
|
||||
|
||||
// Don't redraw if we don't need to.
|
||||
if (oldMetrics &&
|
||||
Blockly.BlockSvg.metricsAreEquivalent_(oldMetrics, metrics)) {
|
||||
// Skipping the redraw is fine, but we may still have to tighten up our
|
||||
// connections with child blocks.
|
||||
if (metrics.statement && metrics.statement.connection &&
|
||||
metrics.statement.targetConnection) {
|
||||
metrics.statement.connection.tighten_();
|
||||
}
|
||||
if (this.nextConnection && this.nextConnection.targetConnection) {
|
||||
this.nextConnection.tighten_();
|
||||
}
|
||||
} else {
|
||||
this.height = metrics.height;
|
||||
this.width = metrics.width;
|
||||
this.renderDraw_(metrics);
|
||||
this.renderClassify_(metrics);
|
||||
this.renderingMetrics_ = metrics;
|
||||
}
|
||||
|
||||
if (opt_bubble !== false) {
|
||||
// Render all blocks above this one (propagate a reflow).
|
||||
var parentBlock = this.getParent();
|
||||
if (parentBlock) {
|
||||
parentBlock.render(true);
|
||||
} else {
|
||||
// Top-most block. Fire an event to allow scrollbars to resize.
|
||||
Blockly.resizeSvgContents(this.workspace);
|
||||
}
|
||||
}
|
||||
Blockly.Field.stopCache();
|
||||
|
||||
this.updateIntersectionObserver();
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the height and widths for each row and field.
|
||||
* @return {!Array.<!Array.<!Object>>} 2D array of objects, each containing
|
||||
* position information.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderCompute_ = function() {
|
||||
var metrics = {
|
||||
statement: null,
|
||||
imageField: null,
|
||||
iconMenu: null,
|
||||
width: 0,
|
||||
height: 0,
|
||||
bayHeight: 0,
|
||||
bayWidth: 0,
|
||||
bayNotchAtRight: true,
|
||||
fieldRadius: 0,
|
||||
startHat: false,
|
||||
endCap: false
|
||||
};
|
||||
|
||||
// Does block have a statement?
|
||||
for (var i = 0, input; input = this.inputList[i]; i++) {
|
||||
if (input.type == Blockly.NEXT_STATEMENT) {
|
||||
metrics.statement = input;
|
||||
// Compute minimum input size.
|
||||
metrics.bayHeight = Blockly.BlockSvg.MIN_BLOCK_Y;
|
||||
metrics.bayWidth = Blockly.BlockSvg.MIN_BLOCK_X;
|
||||
// Expand input size if there is a connection.
|
||||
if (input.connection && input.connection.targetConnection) {
|
||||
var linkedBlock = input.connection.targetBlock();
|
||||
var bBox = linkedBlock.getHeightWidth(true);
|
||||
metrics.bayHeight = Math.max(metrics.bayHeight, bBox.height);
|
||||
metrics.bayWidth = Math.max(metrics.bayWidth, bBox.width);
|
||||
}
|
||||
var linkedBlock = input.connection.targetBlock();
|
||||
if (linkedBlock && !linkedBlock.lastConnectionInStack()) {
|
||||
metrics.bayNotchAtRight = false;
|
||||
} else {
|
||||
metrics.bayWidth -= Blockly.BlockSvg.NOTCH_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
// Find image field, input fields
|
||||
for (var j = 0, field; field = input.fieldRow[j]; j++) {
|
||||
if (field instanceof Blockly.FieldImage) {
|
||||
metrics.imageField = field;
|
||||
}
|
||||
if (field instanceof Blockly.FieldIconMenu) {
|
||||
metrics.iconMenu = field;
|
||||
}
|
||||
if (field instanceof Blockly.FieldTextInput) {
|
||||
metrics.fieldRadius = field.getBorderRadius();
|
||||
} else {
|
||||
metrics.fieldRadius = Blockly.BlockSvg.FIELD_DEFAULT_CORNER_RADIUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether a block is a start hat or end cap by checking connections.
|
||||
if (this.nextConnection && !this.previousConnection) {
|
||||
metrics.startHat = true;
|
||||
}
|
||||
|
||||
// End caps have no bay, a previous, no output, and no next.
|
||||
if (!this.nextConnection && this.previousConnection &&
|
||||
!this.outputConnection && !metrics.statement) {
|
||||
metrics.endCap = true;
|
||||
}
|
||||
|
||||
// If this block is an icon menu shadow, attempt to set the parent's
|
||||
// ImageField src to the one that represents the current value of the field.
|
||||
if (metrics.iconMenu) {
|
||||
var currentSrc = metrics.iconMenu.getSrcForValue(metrics.iconMenu.getValue());
|
||||
if (currentSrc) {
|
||||
metrics.iconMenu.setParentFieldImage(currentSrc);
|
||||
}
|
||||
}
|
||||
|
||||
// Always render image field at 40x40 px
|
||||
// Normal block sizing
|
||||
metrics.width = Blockly.BlockSvg.SEP_SPACE_X * 2 + Blockly.BlockSvg.IMAGE_FIELD_WIDTH;
|
||||
metrics.height = Blockly.BlockSvg.SEP_SPACE_Y * 2 + Blockly.BlockSvg.IMAGE_FIELD_HEIGHT;
|
||||
|
||||
if (this.outputConnection) {
|
||||
// Field shadow block
|
||||
metrics.height = Blockly.BlockSvg.FIELD_HEIGHT;
|
||||
metrics.width = Blockly.BlockSvg.FIELD_WIDTH;
|
||||
}
|
||||
if (metrics.statement) {
|
||||
// Block with statement (e.g., repeat, forever)
|
||||
metrics.width += metrics.bayWidth + 4 * Blockly.BlockSvg.CORNER_RADIUS + 2 * Blockly.BlockSvg.GRID_UNIT;
|
||||
metrics.height = metrics.bayHeight + Blockly.BlockSvg.STATEMENT_BLOCK_SPACE;
|
||||
}
|
||||
if (metrics.startHat) {
|
||||
// Start hats are 1 unit wider to account for optical effect of curve.
|
||||
metrics.width += 1 * Blockly.BlockSvg.GRID_UNIT;
|
||||
}
|
||||
if (metrics.endCap) {
|
||||
// End caps are 1 unit wider to account for optical effect of no notch.
|
||||
metrics.width += 1 * Blockly.BlockSvg.GRID_UNIT;
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Draw the path of the block.
|
||||
* Move the fields to the correct locations.
|
||||
* @param {!Object} metrics An object containing computed measurements of the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderDraw_ = function(metrics) {
|
||||
// Fetch the block's coordinates on the surface for use in anchoring
|
||||
// the connections.
|
||||
var connectionsXY = this.getRelativeToSurfaceXY();
|
||||
// Assemble the block's path.
|
||||
var steps = [];
|
||||
|
||||
this.renderDrawLeft_(steps, connectionsXY, metrics);
|
||||
this.renderDrawBottom_(steps, connectionsXY, metrics);
|
||||
this.renderDrawRight_(steps, connectionsXY, metrics);
|
||||
this.renderDrawTop_(steps, connectionsXY, metrics);
|
||||
|
||||
var pathString = steps.join(' ');
|
||||
this.svgPath_.setAttribute('d', pathString);
|
||||
|
||||
if (this.RTL) {
|
||||
// Mirror the block's path.
|
||||
// This is awesome.
|
||||
this.svgPath_.setAttribute('transform', 'scale(-1 1)');
|
||||
}
|
||||
|
||||
// Horizontal blocks have a single Image Field that is specially positioned
|
||||
if (metrics.imageField) {
|
||||
var imageField = metrics.imageField.getSvgRoot();
|
||||
var imageFieldSize = metrics.imageField.getSize();
|
||||
// Image field's position is calculated relative to the "end" edge of the
|
||||
// block.
|
||||
var imageFieldX = metrics.width - imageFieldSize.width -
|
||||
Blockly.BlockSvg.SEP_SPACE_X / 1.5;
|
||||
var imageFieldY = metrics.height - imageFieldSize.height -
|
||||
Blockly.BlockSvg.SEP_SPACE_Y;
|
||||
if (metrics.endCap) {
|
||||
// End-cap image is offset by a grid unit to account for optical effect of no notch.
|
||||
imageFieldX -= Blockly.BlockSvg.GRID_UNIT;
|
||||
}
|
||||
var imageFieldScale = "scale(1 1)";
|
||||
if (this.RTL) {
|
||||
// Do we want to mirror the Image Field left-to-right?
|
||||
if (metrics.imageField.getFlipRTL()) {
|
||||
imageFieldScale = "scale(-1 1)";
|
||||
imageFieldX = -metrics.width + imageFieldSize.width +
|
||||
Blockly.BlockSvg.SEP_SPACE_X / 1.5;
|
||||
} else {
|
||||
// If not, don't offset by imageFieldSize.width
|
||||
imageFieldX = -metrics.width + Blockly.BlockSvg.SEP_SPACE_X / 1.5;
|
||||
}
|
||||
}
|
||||
if (imageField) {
|
||||
// Fields are invisible on insertion marker.
|
||||
if (this.isInsertionMarker()) {
|
||||
imageField.setAttribute('display', 'none');
|
||||
}
|
||||
imageField.setAttribute('transform',
|
||||
'translate(' + imageFieldX + ',' + imageFieldY + ') ' +
|
||||
imageFieldScale);
|
||||
}
|
||||
}
|
||||
|
||||
// Position value input
|
||||
if (this.getFieldShadowBlock_()) {
|
||||
var input = this.getFieldShadowBlock_().getSvgRoot();
|
||||
var valueX = (Blockly.BlockSvg.NOTCH_WIDTH +
|
||||
(metrics.bayWidth ? 2 * Blockly.BlockSvg.GRID_UNIT +
|
||||
Blockly.BlockSvg.NOTCH_WIDTH * 2 : 0) + metrics.bayWidth);
|
||||
if (metrics.startHat) {
|
||||
// Start hats add some left margin to field for visual balance
|
||||
valueX += Blockly.BlockSvg.GRID_UNIT * 2;
|
||||
}
|
||||
if (this.RTL) {
|
||||
valueX = -valueX;
|
||||
}
|
||||
var valueY = (metrics.height + Blockly.BlockSvg.FIELD_Y_OFFSET);
|
||||
var transformation = 'translate(' + valueX + ',' + valueY + ')';
|
||||
input.setAttribute('transform', transformation);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Give the block an attribute 'data-shapes' that lists its shape[s], and an
|
||||
* attribute 'data-category' with its category.
|
||||
* @param {!Object} metrics An object containing computed measurements of the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderClassify_ = function(metrics) {
|
||||
var shapes = [];
|
||||
|
||||
if (this.isShadow_) {
|
||||
shapes.push('argument');
|
||||
} else {
|
||||
if(metrics.statement) {
|
||||
shapes.push('c-block');
|
||||
}
|
||||
if (metrics.startHat) {
|
||||
shapes.push('hat'); // c-block+hats are possible (e.x. reprter procedures)
|
||||
} else if (!metrics.statement) {
|
||||
shapes.push('stack'); //only call it "stack" if it's not a c-block
|
||||
}
|
||||
if (!this.nextConnection) {
|
||||
shapes.push('end');
|
||||
}
|
||||
}
|
||||
|
||||
this.svgGroup_.setAttribute('data-shapes', shapes.join(' '));
|
||||
|
||||
if (this.getCategory()) {
|
||||
this.svgGroup_.setAttribute('data-category', this.getCategory());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the left edge of the block.
|
||||
* @param {!Array.<string>} steps Path of block outline.
|
||||
* @param {!Object} connectionsXY Location of block.
|
||||
* @param {!Object} metrics An object containing computed measurements of the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps, connectionsXY, metrics) {
|
||||
// Top edge.
|
||||
if (metrics.startHat) {
|
||||
// Hat block
|
||||
// Position the cursor at the top-left starting point.
|
||||
steps.push(Blockly.BlockSvg.HAT_TOP_LEFT_CORNER_START);
|
||||
// Top-left rounded corner.
|
||||
steps.push(Blockly.BlockSvg.HAT_TOP_LEFT_CORNER);
|
||||
} else if (this.previousConnection) {
|
||||
// Regular block
|
||||
// Position the cursor at the top-left starting point.
|
||||
steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER_START);
|
||||
// Top-left rounded corner.
|
||||
steps.push(Blockly.BlockSvg.TOP_LEFT_CORNER);
|
||||
var cursorY = metrics.height - Blockly.BlockSvg.CORNER_RADIUS -
|
||||
Blockly.BlockSvg.SEP_SPACE_Y - Blockly.BlockSvg.NOTCH_HEIGHT;
|
||||
steps.push('V', cursorY);
|
||||
steps.push(Blockly.BlockSvg.NOTCH_PATH_DOWN);
|
||||
// Create previous block connection.
|
||||
var connectionX = connectionsXY.x;
|
||||
var connectionY = connectionsXY.y + metrics.height -
|
||||
Blockly.BlockSvg.CORNER_RADIUS * 2;
|
||||
this.previousConnection.moveTo(connectionX, connectionY);
|
||||
// This connection will be tightened when the parent renders.
|
||||
steps.push('V', metrics.height - Blockly.BlockSvg.CORNER_RADIUS);
|
||||
} else {
|
||||
// Input
|
||||
// Position the cursor at the top-left starting point.
|
||||
steps.push('m', metrics.fieldRadius + ',0');
|
||||
// Top-left rounded corner.
|
||||
steps.push(
|
||||
'A', metrics.fieldRadius + ',' + metrics.fieldRadius,
|
||||
'0', '0,0', '0,' + metrics.fieldRadius);
|
||||
steps.push(
|
||||
'V', metrics.height - metrics.fieldRadius);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the bottom edge of the block.
|
||||
* @param {!Array.<string>} steps Path of block outline.
|
||||
* @param {!Object} connectionsXY Location of block.
|
||||
* @param {!Object} metrics An object containing computed measurements of the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps,
|
||||
connectionsXY, metrics) {
|
||||
|
||||
if (metrics.startHat) {
|
||||
steps.push('a', Blockly.BlockSvg.HAT_CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.HAT_CORNER_RADIUS + ' 0 0,0 ' +
|
||||
Blockly.BlockSvg.HAT_CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.HAT_CORNER_RADIUS);
|
||||
} else if (this.previousConnection) {
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
} else {
|
||||
// Input
|
||||
steps.push(
|
||||
'a', metrics.fieldRadius + ',' + metrics.fieldRadius,
|
||||
'0', '0,0', metrics.fieldRadius + ',' + metrics.fieldRadius);
|
||||
}
|
||||
|
||||
// Has statement
|
||||
if (metrics.statement) {
|
||||
steps.push('h', 4 * Blockly.BlockSvg.GRID_UNIT);
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',-' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
steps.push('v', -2.5 * Blockly.BlockSvg.GRID_UNIT);
|
||||
steps.push(Blockly.BlockSvg.NOTCH_PATH_UP);
|
||||
// @todo Why 3?
|
||||
steps.push('v', -metrics.bayHeight + (Blockly.BlockSvg.CORNER_RADIUS * 3) +
|
||||
Blockly.BlockSvg.NOTCH_HEIGHT + 2 * Blockly.BlockSvg.GRID_UNIT);
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',-' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
steps.push('h', metrics.bayWidth - (Blockly.BlockSvg.CORNER_RADIUS * 2));
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
if (metrics.bayNotchAtRight) {
|
||||
steps.push('v', metrics.bayHeight - (Blockly.BlockSvg.CORNER_RADIUS * 3) -
|
||||
Blockly.BlockSvg.NOTCH_HEIGHT - 2 * Blockly.BlockSvg.GRID_UNIT);
|
||||
steps.push(Blockly.BlockSvg.NOTCH_PATH_DOWN);
|
||||
}
|
||||
steps.push('V', metrics.bayHeight + 2 * Blockly.BlockSvg.GRID_UNIT);
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
|
||||
// Create statement connection.
|
||||
var connectionX = connectionsXY.x + Blockly.BlockSvg.CORNER_RADIUS * 2 +
|
||||
4 * Blockly.BlockSvg.GRID_UNIT;
|
||||
if (this.RTL) {
|
||||
connectionX = connectionsXY.x - Blockly.BlockSvg.CORNER_RADIUS * 2 -
|
||||
4 * Blockly.BlockSvg.GRID_UNIT;
|
||||
}
|
||||
var connectionY = connectionsXY.y + metrics.height -
|
||||
Blockly.BlockSvg.CORNER_RADIUS * 2;
|
||||
metrics.statement.connection.moveTo(connectionX, connectionY);
|
||||
if (metrics.statement.connection.targetConnection) {
|
||||
metrics.statement.connection.tighten_();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.isShadow()) {
|
||||
steps.push('H', metrics.width - Blockly.BlockSvg.CORNER_RADIUS);
|
||||
} else {
|
||||
// input
|
||||
steps.push('H', metrics.width - metrics.fieldRadius);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the right edge of the block.
|
||||
* @param {!Array.<string>} steps Path of block outline.
|
||||
* @param {!Object} connectionsXY Location of block.
|
||||
* @param {!Object} metrics An object containing computed measurements of the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps, connectionsXY, metrics) {
|
||||
if (!this.isShadow()) {
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 ' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',-' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
steps.push('v', -2.5 * Blockly.BlockSvg.GRID_UNIT);
|
||||
} else {
|
||||
// Input
|
||||
steps.push(
|
||||
'a', metrics.fieldRadius + ',' + metrics.fieldRadius,
|
||||
'0', '0,0', metrics.fieldRadius + ',' + -1 * metrics.fieldRadius);
|
||||
steps.push('v', -1 * (metrics.height - metrics.fieldRadius * 2));
|
||||
}
|
||||
|
||||
if (this.nextConnection) {
|
||||
steps.push(Blockly.BlockSvg.NOTCH_PATH_UP);
|
||||
|
||||
// Include width of notch in block width.
|
||||
this.width += Blockly.BlockSvg.NOTCH_WIDTH;
|
||||
|
||||
// Create next block connection.
|
||||
var connectionX;
|
||||
if (this.RTL) {
|
||||
connectionX = connectionsXY.x - metrics.width;
|
||||
} else {
|
||||
connectionX = connectionsXY.x + metrics.width;
|
||||
}
|
||||
var connectionY = connectionsXY.y + metrics.height -
|
||||
Blockly.BlockSvg.CORNER_RADIUS * 2;
|
||||
this.nextConnection.moveTo(connectionX, connectionY);
|
||||
if (this.nextConnection.targetConnection) {
|
||||
this.nextConnection.tighten_();
|
||||
}
|
||||
steps.push('V', Blockly.BlockSvg.CORNER_RADIUS);
|
||||
} else if (!this.isShadow()) {
|
||||
steps.push('V', Blockly.BlockSvg.CORNER_RADIUS);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the top edge of the block.
|
||||
* @param {!Array.<string>} steps Path of block outline.
|
||||
* @param {!Object} connectionsXY Location of block.
|
||||
* @param {!Object} metrics An object containing computed measurements of the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.renderDrawTop_ = function(steps, connectionsXY, metrics) {
|
||||
if (!this.isShadow()) {
|
||||
steps.push('a', Blockly.BlockSvg.CORNER_RADIUS + ',' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,0 -' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS + ',-' +
|
||||
Blockly.BlockSvg.CORNER_RADIUS);
|
||||
} else {
|
||||
steps.push(
|
||||
'a', metrics.fieldRadius + ',' + metrics.fieldRadius,
|
||||
'0', '0,0', '-' + metrics.fieldRadius + ',-' + metrics.fieldRadius);
|
||||
}
|
||||
steps.push('z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the field shadow block, if this block has one.
|
||||
* <p>This is horizontal Scratch-specific, as "fields" are implemented as inputs
|
||||
* with shadow blocks, and there is only one per block.
|
||||
* @return {Blockly.BlockSvg} The field shadow block, or null if not found.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.getFieldShadowBlock_ = function() {
|
||||
for (var i = 0, child; child = this.childBlocks_[i]; i++) {
|
||||
if (child.isShadow()) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Position an new block correctly, so that it doesn't move the existing block
|
||||
* when connected to it.
|
||||
* @param {!Blockly.Block} newBlock The block to position - either the first
|
||||
* block in a dragged stack or an insertion marker.
|
||||
* @param {!Blockly.Connection} newConnection The connection on the new block's
|
||||
* stack - either a connection on newBlock, or the last NEXT_STATEMENT
|
||||
* connection on the stack if the stack's being dropped before another
|
||||
* block.
|
||||
* @param {!Blockly.Connection} existingConnection The connection on the
|
||||
* existing block, which newBlock should line up with.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.positionNewBlock = function(newBlock, newConnection, existingConnection) {
|
||||
// We only need to position the new block if it's before the existing one,
|
||||
// otherwise its position is set by the previous block.
|
||||
if (newConnection.type == Blockly.NEXT_STATEMENT) {
|
||||
var dx = existingConnection.x_ - newConnection.x_;
|
||||
var dy = existingConnection.y_ - newConnection.y_;
|
||||
|
||||
// When putting a c-block around another c-block, the outer block must
|
||||
// positioned above the inner block, as its connection point will stretch
|
||||
// downwards when connected.
|
||||
if (newConnection == newBlock.getFirstStatementConnection()) {
|
||||
dy -= existingConnection.sourceBlock_.getHeightWidth(true).height -
|
||||
Blockly.BlockSvg.MIN_BLOCK_Y;
|
||||
}
|
||||
|
||||
newBlock.moveBy(dx, dy);
|
||||
}
|
||||
};
|
||||
1734
scratch-blocks/core/block_render_svg_vertical.js
Normal file
1734
scratch-blocks/core/block_render_svg_vertical.js
Normal file
File diff suppressed because it is too large
Load Diff
1355
scratch-blocks/core/block_svg.js
Normal file
1355
scratch-blocks/core/block_svg.js
Normal file
File diff suppressed because it is too large
Load Diff
618
scratch-blocks/core/blockly.js
Normal file
618
scratch-blocks/core/blockly.js
Normal file
@@ -0,0 +1,618 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Core JavaScript library for Blockly.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The top level namespace used to access the Blockly library.
|
||||
* @namespace Blockly
|
||||
**/
|
||||
goog.provide('Blockly');
|
||||
|
||||
goog.require('Blockly.BlockSvg.render');
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.FieldAngle');
|
||||
goog.require('Blockly.FieldCheckbox');
|
||||
goog.require('Blockly.FieldColour');
|
||||
goog.require('Blockly.FieldColourSlider');
|
||||
// Date picker commented out since it increases footprint by 60%.
|
||||
// Add it only if you need it.
|
||||
//goog.require('Blockly.FieldDate');
|
||||
goog.require('Blockly.FieldDropdown');
|
||||
goog.require('Blockly.FieldIconMenu');
|
||||
goog.require('Blockly.FieldImage');
|
||||
goog.require('Blockly.FieldNote');
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('Blockly.FieldTextInputRemovable');
|
||||
goog.require('Blockly.FieldTextDropdown');
|
||||
goog.require('Blockly.FieldNumber');
|
||||
goog.require('Blockly.FieldNumberDropdown');
|
||||
goog.require('Blockly.FieldMatrix');
|
||||
goog.require('Blockly.FieldVariable');
|
||||
goog.require('Blockly.FieldVerticalSeparator');
|
||||
goog.require('Blockly.Generator');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.Procedures');
|
||||
goog.require('Blockly.ScratchMsgs');
|
||||
goog.require('Blockly.Toolbox');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.WidgetDiv');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.inject');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('goog.color');
|
||||
|
||||
|
||||
// Turn off debugging when compiled.
|
||||
/* eslint-disable no-unused-vars */
|
||||
var CLOSURE_DEFINES = {'goog.DEBUG': false};
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
/**
|
||||
* The main workspace most recently used.
|
||||
* Set by Blockly.WorkspaceSvg.prototype.markFocused
|
||||
* @type {Blockly.Workspace}
|
||||
*/
|
||||
Blockly.mainWorkspace = null;
|
||||
|
||||
/**
|
||||
* Currently selected block.
|
||||
* @type {Blockly.Block}
|
||||
*/
|
||||
Blockly.selected = null;
|
||||
|
||||
/**
|
||||
* All of the connections on blocks that are currently being dragged.
|
||||
* @type {!Array.<!Blockly.Connection>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.draggingConnections_ = [];
|
||||
|
||||
/**
|
||||
* Contents of the local clipboard.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.clipboardXml_ = null;
|
||||
|
||||
/**
|
||||
* Source of the local clipboard.
|
||||
* @type {Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
Blockly.clipboardSource_ = null;
|
||||
|
||||
/**
|
||||
* Cached value for whether 3D is supported.
|
||||
* @type {!boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.cache3dSupported_ = null;
|
||||
|
||||
/**
|
||||
* Convert a hue (HSV model) into an RGB hex triplet.
|
||||
* @param {number} hue Hue on a colour wheel (0-360).
|
||||
* @return {string} RGB code, e.g. '#5ba65b'.
|
||||
*/
|
||||
Blockly.hueToRgb = function(hue) {
|
||||
return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
|
||||
Blockly.HSV_VALUE * 255);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the specified SVG image.
|
||||
* @param {!Element} svg SVG image.
|
||||
* @return {!Object} Contains width and height properties.
|
||||
*/
|
||||
Blockly.svgSize = function(svg) {
|
||||
return {
|
||||
width: svg.cachedWidth_,
|
||||
height: svg.cachedHeight_
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Size the workspace when the contents change. This also updates
|
||||
* scrollbars accordingly.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
|
||||
*/
|
||||
Blockly.resizeSvgContents = function(workspace) {
|
||||
workspace.resizeContents();
|
||||
};
|
||||
|
||||
/**
|
||||
* Size the SVG image to completely fill its container. Call this when the view
|
||||
* actually changes sizes (e.g. on a window resize/device orientation change).
|
||||
* See Blockly.resizeSvgContents to resize the workspace when the contents
|
||||
* change (e.g. when a block is added or removed).
|
||||
* Record the height/width of the SVG image.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
|
||||
*/
|
||||
Blockly.svgResize = function(workspace) {
|
||||
var mainWorkspace = workspace;
|
||||
while (mainWorkspace.options.parentWorkspace) {
|
||||
mainWorkspace = mainWorkspace.options.parentWorkspace;
|
||||
}
|
||||
var svg = mainWorkspace.getParentSvg();
|
||||
var div = svg.parentNode;
|
||||
if (!div) {
|
||||
// Workspace deleted, or something.
|
||||
return;
|
||||
}
|
||||
var width = div.offsetWidth;
|
||||
var height = div.offsetHeight;
|
||||
if (svg.cachedWidth_ != width) {
|
||||
svg.setAttribute('width', width + 'px');
|
||||
svg.cachedWidth_ = width;
|
||||
}
|
||||
if (svg.cachedHeight_ != height) {
|
||||
svg.setAttribute('height', height + 'px');
|
||||
svg.cachedHeight_ = height;
|
||||
}
|
||||
mainWorkspace.resize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a key-down on SVG drawing surface. Does nothing if the main workspace is not visible.
|
||||
* @param {!Event} e Key down event.
|
||||
* @private
|
||||
*/
|
||||
// TODO (https://github.com/google/blockly/issues/1998) handle cases where there are multiple workspaces
|
||||
// and non-main workspaces are able to accept input.
|
||||
Blockly.onKeyDown_ = function(e) {
|
||||
if (Blockly.mainWorkspace.options.readOnly || Blockly.utils.isTargetInput(e)
|
||||
|| (Blockly.mainWorkspace.rendered && !Blockly.mainWorkspace.isVisible())) {
|
||||
// No key actions on readonly workspaces.
|
||||
// When focused on an HTML text input widget, don't trap any keys.
|
||||
// Ignore keypresses on rendered workspaces that have been explicitly
|
||||
// hidden.
|
||||
return;
|
||||
}
|
||||
var deleteBlock = false;
|
||||
if (e.keyCode == 27) {
|
||||
// Pressing esc closes the context menu and any drop-down
|
||||
Blockly.hideChaff();
|
||||
Blockly.DropDownDiv.hide();
|
||||
} else if (e.keyCode == 8 || e.keyCode == 46) {
|
||||
// Delete or backspace.
|
||||
// Stop the browser from going back to the previous page.
|
||||
// Do this first to prevent an error in the delete code from resulting in
|
||||
// data loss.
|
||||
e.preventDefault();
|
||||
// Don't delete while dragging. Jeez.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected && Blockly.selected.isDeletable()) {
|
||||
deleteBlock = true;
|
||||
}
|
||||
} else if (e.altKey || e.ctrlKey || e.metaKey) {
|
||||
// Don't use meta keys during drags.
|
||||
if (Blockly.mainWorkspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
if (Blockly.selected &&
|
||||
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
|
||||
// Don't allow copying immovable or undeletable blocks. The next step
|
||||
// would be to paste, which would create additional undeletable/immovable
|
||||
// blocks on the workspace.
|
||||
if (e.keyCode == 67) {
|
||||
// 'c' for copy.
|
||||
Blockly.hideChaff();
|
||||
Blockly.copy_(Blockly.selected);
|
||||
} else if (e.keyCode == 88 && !Blockly.selected.workspace.isFlyout) {
|
||||
// 'x' for cut, but not in a flyout.
|
||||
// Don't even copy the selected item in the flyout.
|
||||
Blockly.copy_(Blockly.selected);
|
||||
deleteBlock = true;
|
||||
}
|
||||
}
|
||||
if (e.keyCode == 86) {
|
||||
// 'v' for paste.
|
||||
if (Blockly.clipboardXml_) {
|
||||
Blockly.Events.setGroup(true);
|
||||
// Pasting always pastes to the main workspace, even if the copy started
|
||||
// in a flyout workspace.
|
||||
var workspace = Blockly.clipboardSource_;
|
||||
if (workspace.isFlyout) {
|
||||
workspace = workspace.targetWorkspace;
|
||||
}
|
||||
workspace.paste(Blockly.clipboardXml_);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
} else if (e.keyCode == 90 || e.keyCode === 89) {
|
||||
// 'z' for undo 'Z' is for redo. 'y' is always redo.
|
||||
e.preventDefault();
|
||||
Blockly.hideChaff();
|
||||
Blockly.mainWorkspace.undo(e.shiftKey || e.keyCode === 89);
|
||||
}
|
||||
}
|
||||
// Common code for delete and cut.
|
||||
// Don't delete in the flyout.
|
||||
if (deleteBlock && !Blockly.selected.workspace.isFlyout) {
|
||||
Blockly.Events.setGroup(true);
|
||||
Blockly.hideChaff();
|
||||
Blockly.selected.dispose(/* heal */ true, true);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toCopy Block or Workspace Comment
|
||||
* to be copied.
|
||||
* @private
|
||||
*/
|
||||
Blockly.copy_ = function(toCopy) {
|
||||
if (toCopy.isComment) {
|
||||
var xml = toCopy.toXmlWithXY();
|
||||
} else {
|
||||
var xml = Blockly.Xml.blockToDom(toCopy);
|
||||
// Encode start position in XML.
|
||||
var xy = toCopy.getRelativeToSurfaceXY();
|
||||
xml.setAttribute('x', toCopy.RTL ? -xy.x : xy.x);
|
||||
xml.setAttribute('y', xy.y);
|
||||
}
|
||||
Blockly.clipboardXml_ = xml;
|
||||
Blockly.clipboardSource_ = toCopy.workspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
* @param {!Blockly.Block | !Blockly.WorkspaceComment} toDuplicate Block or
|
||||
* Workspace Comment to be copied.
|
||||
* @private
|
||||
*/
|
||||
Blockly.duplicate_ = function(toDuplicate) {
|
||||
// Save the clipboard.
|
||||
var clipboardXml = Blockly.clipboardXml_;
|
||||
var clipboardSource = Blockly.clipboardSource_;
|
||||
|
||||
// Create a duplicate via a copy/paste operation.
|
||||
Blockly.copy_(toDuplicate);
|
||||
toDuplicate.workspace.paste(Blockly.clipboardXml_);
|
||||
|
||||
// Restore the clipboard.
|
||||
Blockly.clipboardXml_ = clipboardXml;
|
||||
Blockly.clipboardSource_ = clipboardSource;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the native context menu, unless the focus is on an HTML input widget.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.onContextMenu_ = function(e) {
|
||||
if (!Blockly.utils.isTargetInput(e)) {
|
||||
// When focused on an HTML text input widget, don't cancel the context menu.
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Close tooltips, context menus, dropdown selections, etc.
|
||||
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
|
||||
*/
|
||||
Blockly.hideChaff = function(opt_allowToolbox) {
|
||||
Blockly.hideChaffInternal_(opt_allowToolbox);
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Close tooltips, context menus, dropdown selections, etc.
|
||||
* For some elements (e.g. field text inputs), rather than hiding, it will
|
||||
* move them.
|
||||
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
|
||||
*/
|
||||
Blockly.hideChaffOnResize = function(opt_allowToolbox) {
|
||||
Blockly.hideChaffInternal_(opt_allowToolbox);
|
||||
Blockly.WidgetDiv.repositionForWindowResize();
|
||||
};
|
||||
|
||||
/**
|
||||
* Does a majority of the work for hideChaff including tooltips, dropdowns,
|
||||
* toolbox, etc. It does not deal with the WidgetDiv.
|
||||
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
|
||||
* @private
|
||||
*/
|
||||
Blockly.hideChaffInternal_ = function(opt_allowToolbox) {
|
||||
Blockly.Tooltip.hide();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (!opt_allowToolbox) {
|
||||
var workspace = Blockly.getMainWorkspace();
|
||||
if (workspace.toolbox_ &&
|
||||
workspace.toolbox_.flyout_ &&
|
||||
workspace.toolbox_.flyout_.autoClose) {
|
||||
workspace.toolbox_.clearSelection();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the main workspace. Returns the last used main workspace (based on
|
||||
* focus). Try not to use this function, particularly if there are multiple
|
||||
* Blockly instances on a page.
|
||||
* @return {!Blockly.Workspace} The main workspace.
|
||||
*/
|
||||
Blockly.getMainWorkspace = function() {
|
||||
return Blockly.mainWorkspace;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper to window.alert() that app developers may override to
|
||||
* provide alternatives to the modal browser window.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {function()=} opt_callback The callback when the alert is dismissed.
|
||||
*/
|
||||
Blockly.alert = function(message, opt_callback) {
|
||||
window.alert(message);
|
||||
if (opt_callback) {
|
||||
opt_callback();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper to window.confirm() that app developers may override to
|
||||
* provide alternatives to the modal browser window.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {!function(boolean)} callback The callback for handling user response.
|
||||
*/
|
||||
Blockly.confirm = function(message, callback) {
|
||||
callback(window.confirm(message));
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper to window.prompt() that app developers may override to provide
|
||||
* alternatives to the modal browser window. Built-in browser prompts are
|
||||
* often used for better text input experience on mobile device. We strongly
|
||||
* recommend testing mobile when overriding this.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {string} defaultValue The value to initialize the prompt with.
|
||||
* @param {!function(string)} callback The callback for handling user response.
|
||||
* @param {?string} _opt_title An optional title for the prompt.
|
||||
* @param {?string} _opt_varType An optional variable type for variable specific
|
||||
* prompt behavior.
|
||||
*/
|
||||
Blockly.prompt = function(message, defaultValue, callback, _opt_title,
|
||||
_opt_varType) {
|
||||
// opt_title and opt_varType are unused because we only need them to pass
|
||||
// information to the scratch-gui, which overwrites this function
|
||||
callback(window.prompt(message, defaultValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback for status buttons. The window.alert is here for testing and
|
||||
* should be overridden.
|
||||
* @param {string} id An identifier.
|
||||
*/
|
||||
Blockly.statusButtonCallback = function(id) {
|
||||
window.alert('status button was pressed for ' + id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Refresh the visual state of a status button in all extension category headers.
|
||||
* @param {Blockly.Workspace} workspace A workspace.
|
||||
*/
|
||||
Blockly.refreshStatusButtons = function(workspace) {
|
||||
var buttons = workspace.getFlyout().buttons_;
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
if (buttons[i] instanceof Blockly.FlyoutExtensionCategoryHeader) {
|
||||
buttons[i].refreshStatus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for defining a block from JSON. The resulting function has
|
||||
* the correct value of jsonDef at the point in code where jsonInit is called.
|
||||
* @param {!Object} jsonDef The JSON definition of a block.
|
||||
* @return {function()} A function that calls jsonInit with the correct value
|
||||
* of jsonDef.
|
||||
* @private
|
||||
*/
|
||||
Blockly.jsonInitFactory_ = function(jsonDef) {
|
||||
return function() {
|
||||
this.jsonInit(jsonDef);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Define blocks from an array of JSON block definitions, as might be generated
|
||||
* by the Blockly Developer Tools.
|
||||
* @param {!Array.<!Object>} jsonArray An array of JSON block definitions.
|
||||
*/
|
||||
Blockly.defineBlocksWithJsonArray = function(jsonArray) {
|
||||
for (var i = 0; i < jsonArray.length; i++) {
|
||||
var elem = jsonArray[i];
|
||||
if (!elem) {
|
||||
console.warn(
|
||||
'Block definition #' + i + ' in JSON array is ' + elem + '. ' +
|
||||
'Skipping.');
|
||||
} else {
|
||||
var typename = elem.type;
|
||||
if (typename == null || typename === '') {
|
||||
console.warn(
|
||||
'Block definition #' + i +
|
||||
' in JSON array is missing a type attribute. Skipping.');
|
||||
} else {
|
||||
Blockly.Blocks[typename] = {
|
||||
init: Blockly.jsonInitFactory_(elem)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. When calling the function, verifies that
|
||||
* it belongs to the touch stream that is currently being processed, and splits
|
||||
* multitouch events into multiple events as needed.
|
||||
* @param {!EventTarget} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @param {boolean=} opt_noCaptureIdentifier True if triggering on this event
|
||||
* should not block execution of other event handlers on this touch or other
|
||||
* simultaneous touches.
|
||||
* @param {boolean=} opt_noPreventDefault True if triggering on this event
|
||||
* should prevent the default handler. False by default. If
|
||||
* opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be
|
||||
* provided.
|
||||
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
*/
|
||||
Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
|
||||
opt_noCaptureIdentifier, opt_noPreventDefault) {
|
||||
var handled = false;
|
||||
var wrapFunc = function(e) {
|
||||
var captureIdentifier = !opt_noCaptureIdentifier;
|
||||
// Handle each touch point separately. If the event was a mouse event, this
|
||||
// will hand back an array with one element, which we're fine handling.
|
||||
var events = Blockly.Touch.splitEventByTouches(e);
|
||||
for (var i = 0, event; event = events[i]; i++) {
|
||||
if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) {
|
||||
continue;
|
||||
}
|
||||
Blockly.Touch.setClientFromTouch(event);
|
||||
if (thisObject) {
|
||||
func.call(thisObject, event);
|
||||
} else {
|
||||
func(event);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
wrapFunc(e);
|
||||
// Calling preventDefault stops the browser from scrolling/zooming the
|
||||
// page.
|
||||
var preventDef = !opt_noPreventDefault;
|
||||
if (handled && preventDef) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(type, touchWrapFunc, false);
|
||||
bindData.push([node, type, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. Handles multitouch events by using the
|
||||
* coordinates of the first changed touch, and doesn't do any safety checks for
|
||||
* simultaneous event processing.
|
||||
* @deprecated in favor of bindEventWithChecks_, but preserved for external
|
||||
* users.
|
||||
* @param {!EventTarget} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.bindEvent_ = function(node, name, thisObject, func) {
|
||||
var wrapFunc = function(e) {
|
||||
if (thisObject) {
|
||||
func.call(thisObject, e);
|
||||
} else {
|
||||
func(e);
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches.length == 1) {
|
||||
// Map the touch event's properties to the event.
|
||||
var touchPoint = e.changedTouches[0];
|
||||
e.clientX = touchPoint.clientX;
|
||||
e.clientY = touchPoint.clientY;
|
||||
}
|
||||
wrapFunc(e);
|
||||
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
e.preventDefault();
|
||||
};
|
||||
for (var i = 0, type; type = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(type, touchWrapFunc, false);
|
||||
bindData.push([node, type, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
* @param {!Array.<!Array>} bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @return {!Function} The function call.
|
||||
* @private
|
||||
*/
|
||||
Blockly.unbindEvent_ = function(bindData) {
|
||||
while (bindData.length) {
|
||||
var bindDatum = bindData.pop();
|
||||
var node = bindDatum[0];
|
||||
var name = bindDatum[1];
|
||||
var func = bindDatum[2];
|
||||
node.removeEventListener(name, func, false);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the given string a number (includes negative and decimals).
|
||||
* @param {string} str Input string.
|
||||
* @return {boolean} True if number, false otherwise.
|
||||
*/
|
||||
Blockly.isNumber = function(str) {
|
||||
return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
|
||||
};
|
||||
|
||||
// IE9 does not have a console. Create a stub to stop errors.
|
||||
if (!goog.global['console']) {
|
||||
goog.global['console'] = {
|
||||
'log': function() {},
|
||||
'warn': function() {}
|
||||
};
|
||||
}
|
||||
|
||||
// Export symbols that would otherwise be renamed by Closure compiler.
|
||||
if (!goog.global['Blockly']) {
|
||||
goog.global['Blockly'] = {};
|
||||
}
|
||||
goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
|
||||
37
scratch-blocks/core/blocks.js
Normal file
37
scratch-blocks/core/blocks.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2013 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A mapping of block type names to block prototype objects.
|
||||
* @author spertus@google.com (Ellen Spertus)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A mapping of block type names to block prototype objects.
|
||||
* @name Blockly.Blocks
|
||||
*/
|
||||
goog.provide('Blockly.Blocks');
|
||||
|
||||
/*
|
||||
* A mapping of block type names to block prototype objects.
|
||||
* @type {!Object.<string,Object>}
|
||||
*/
|
||||
Blockly.Blocks = new Object(null);
|
||||
664
scratch-blocks/core/bubble.js
Normal file
664
scratch-blocks/core/bubble.js
Normal file
@@ -0,0 +1,664 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a UI bubble.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Bubble');
|
||||
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.Workspace');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for UI bubble.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the
|
||||
* bubble.
|
||||
* @param {!Element} content SVG content for the bubble.
|
||||
* @param {Element} shape SVG element to avoid eclipsing.
|
||||
* @param {!goog.math.Coordinate} anchorXY Absolute position of bubble's anchor
|
||||
* point.
|
||||
* @param {?number} bubbleWidth Width of bubble, or null if not resizable.
|
||||
* @param {?number} bubbleHeight Height of bubble, or null if not resizable.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Bubble = function(workspace, content, shape, anchorXY,
|
||||
bubbleWidth, bubbleHeight) {
|
||||
this.workspace_ = workspace;
|
||||
this.content_ = content;
|
||||
this.shape_ = shape;
|
||||
|
||||
var angle = Blockly.Bubble.ARROW_ANGLE;
|
||||
if (this.workspace_.RTL) {
|
||||
angle = -angle;
|
||||
}
|
||||
this.arrow_radians_ = Blockly.utils.toRadians(angle);
|
||||
|
||||
var canvas = workspace.getBubbleCanvas();
|
||||
canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
|
||||
|
||||
this.setAnchorLocation(anchorXY);
|
||||
if (!bubbleWidth || !bubbleHeight) {
|
||||
var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
|
||||
bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
}
|
||||
this.setBubbleSize(bubbleWidth, bubbleHeight);
|
||||
|
||||
// Render the bubble.
|
||||
this.positionBubble_();
|
||||
this.renderArrow_();
|
||||
this.rendered_ = true;
|
||||
|
||||
if (!workspace.options.readOnly) {
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.bubbleBack_, 'mousedown', this, this.bubbleMouseDown_);
|
||||
if (this.resizeGroup_) {
|
||||
Blockly.bindEventWithChecks_(
|
||||
this.resizeGroup_, 'mousedown', this, this.resizeMouseDown_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Width of the border around the bubble.
|
||||
*/
|
||||
Blockly.Bubble.BORDER_WIDTH = 6;
|
||||
|
||||
/**
|
||||
* Determines the thickness of the base of the arrow in relation to the size
|
||||
* of the bubble. Higher numbers result in thinner arrows.
|
||||
*/
|
||||
Blockly.Bubble.ARROW_THICKNESS = 5;
|
||||
|
||||
/**
|
||||
* The number of degrees that the arrow bends counter-clockwise.
|
||||
*/
|
||||
Blockly.Bubble.ARROW_ANGLE = 20;
|
||||
|
||||
/**
|
||||
* The sharpness of the arrow's bend. Higher numbers result in smoother arrows.
|
||||
*/
|
||||
Blockly.Bubble.ARROW_BEND = 4;
|
||||
|
||||
/**
|
||||
* Distance between arrow point and anchor point.
|
||||
*/
|
||||
Blockly.Bubble.ANCHOR_RADIUS = 8;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseUp occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wrapper function called when a mouseMove occurs during a drag operation.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.onMouseMoveWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Function to call on resize of bubble.
|
||||
* @type {Function}
|
||||
*/
|
||||
Blockly.Bubble.prototype.resizeCallback_ = null;
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.unbindDragEvents_ = function() {
|
||||
if (Blockly.Bubble.onMouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_);
|
||||
Blockly.Bubble.onMouseUpWrapper_ = null;
|
||||
}
|
||||
if (Blockly.Bubble.onMouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_);
|
||||
Blockly.Bubble.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Handle a mouse-up event while dragging a bubble's border or resize handle.
|
||||
* @param {!Event} e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.bubbleMouseUp_ = function(/*e*/) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
Blockly.Bubble.unbindDragEvents_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Flag to stop incremental rendering during construction.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.rendered_ = false;
|
||||
|
||||
/**
|
||||
* Absolute coordinate of anchor point, in workspace coordinates.
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.anchorXY_ = null;
|
||||
|
||||
/**
|
||||
* Relative X coordinate of bubble with respect to the anchor's centre,
|
||||
* in workspace units.
|
||||
* In RTL mode the initial value is negated.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.relativeLeft_ = 0;
|
||||
|
||||
/**
|
||||
* Relative Y coordinate of bubble with respect to the anchor's centre.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.relativeTop_ = 0;
|
||||
|
||||
/**
|
||||
* Width of bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.width_ = 0;
|
||||
|
||||
/**
|
||||
* Height of bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.height_ = 0;
|
||||
|
||||
/**
|
||||
* Automatically position and reposition the bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.autoLayout_ = true;
|
||||
|
||||
/**
|
||||
* Create the bubble's DOM.
|
||||
* @param {!Element} content SVG content for the bubble.
|
||||
* @param {boolean} hasResize Add diagonal resize gripper if true.
|
||||
* @return {!Element} The bubble's SVG group.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
|
||||
/* Create the bubble. Here's the markup that will be generated:
|
||||
<g>
|
||||
<g filter="url(#blocklyEmbossFilter837493)">
|
||||
<path d="... Z" />
|
||||
<rect class="blocklyDraggable" rx="8" ry="8" width="180" height="180"/>
|
||||
</g>
|
||||
<g transform="translate(165, 165)" class="blocklyResizeSE">
|
||||
<polygon points="0,15 15,15 15,0"/>
|
||||
<line class="blocklyResizeLine" x1="5" y1="14" x2="14" y2="5"/>
|
||||
<line class="blocklyResizeLine" x1="10" y1="14" x2="14" y2="10"/>
|
||||
</g>
|
||||
[...content goes here...]
|
||||
</g>
|
||||
*/
|
||||
this.bubbleGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
var filter =
|
||||
{'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'};
|
||||
if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) {
|
||||
// Multiple reports that JavaFX can't handle filters. UserAgent:
|
||||
// Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44
|
||||
// (KHTML, like Gecko) JavaFX/8.0 Safari/537.44
|
||||
// https://github.com/google/blockly/issues/99
|
||||
filter = {};
|
||||
}
|
||||
var bubbleEmboss = Blockly.utils.createSvgElement('g',
|
||||
filter, this.bubbleGroup_);
|
||||
this.bubbleArrow_ = Blockly.utils.createSvgElement('path', {}, bubbleEmboss);
|
||||
this.bubbleBack_ = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'class': 'blocklyDraggable',
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
'rx': Blockly.Bubble.BORDER_WIDTH,
|
||||
'ry': Blockly.Bubble.BORDER_WIDTH
|
||||
},
|
||||
bubbleEmboss);
|
||||
if (hasResize) {
|
||||
this.resizeGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': this.workspace_.RTL ?
|
||||
'blocklyResizeSW' : 'blocklyResizeSE'},
|
||||
this.bubbleGroup_);
|
||||
var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
Blockly.utils.createSvgElement('polygon',
|
||||
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
||||
this.resizeGroup_);
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{
|
||||
'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize / 3, 'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1, 'y2': resizeSize / 3
|
||||
}, this.resizeGroup_);
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{
|
||||
'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize * 2 / 3,
|
||||
'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1,
|
||||
'y2': resizeSize * 2 / 3
|
||||
}, this.resizeGroup_);
|
||||
} else {
|
||||
this.resizeGroup_ = null;
|
||||
}
|
||||
this.bubbleGroup_.appendChild(content);
|
||||
return this.bubbleGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the root node of the bubble's SVG group.
|
||||
* @return {Element} The root SVG node of the bubble's group.
|
||||
*/
|
||||
Blockly.Bubble.prototype.getSvgRoot = function() {
|
||||
return this.bubbleGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose the block's ID on the bubble's top-level SVG group.
|
||||
* @param {string} id ID of block.
|
||||
*/
|
||||
Blockly.Bubble.prototype.setSvgId = function(id) {
|
||||
if (this.bubbleGroup_.dataset) {
|
||||
this.bubbleGroup_.dataset.blockId = id;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's border.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
|
||||
var gesture = this.workspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBubbleStart(e, this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the context menu for this bubble.
|
||||
* @param {!Event} _e Mouse event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.showContextMenu_ = function(_e) {
|
||||
// NOP on bubbles, but used by the bubble dragger to pass events to
|
||||
// workspace comments.
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether this bubble is deletable or not.
|
||||
* @return {boolean} True if deletable.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Bubble.prototype.isDeletable = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's resize corner.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
|
||||
this.promote_();
|
||||
Blockly.Bubble.unbindDragEvents_();
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// No right-click.
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
// Left-click (or middle click)
|
||||
this.workspace_.startDrag(e, new goog.math.Coordinate(
|
||||
this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
|
||||
|
||||
Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
|
||||
'mouseup', this, Blockly.Bubble.bubbleMouseUp_);
|
||||
Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEventWithChecks_(document,
|
||||
'mousemove', this, this.resizeMouseMove_);
|
||||
Blockly.hideChaff();
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize this bubble to follow the mouse.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.resizeMouseMove_ = function(e) {
|
||||
this.autoLayout_ = false;
|
||||
var newXY = this.workspace_.moveDrag(e);
|
||||
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||
if (this.workspace_.RTL) {
|
||||
// RTL requires the bubble to move its left edge.
|
||||
this.positionBubble_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a function as a callback event for when the bubble is resized.
|
||||
* @param {!Function} callback The function to call on resize.
|
||||
*/
|
||||
Blockly.Bubble.prototype.registerResizeEvent = function(callback) {
|
||||
this.resizeCallback_ = callback;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this bubble to the top of the stack.
|
||||
* @return {!boolean} Whether or not the bubble has been moved.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.promote_ = function() {
|
||||
var svgGroup = this.bubbleGroup_.parentNode;
|
||||
if (svgGroup.lastChild !== this.bubbleGroup_) {
|
||||
svgGroup.appendChild(this.bubbleGroup_);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notification that the anchor has moved.
|
||||
* Update the arrow and bubble accordingly.
|
||||
* @param {!goog.math.Coordinate} xy Absolute location.
|
||||
*/
|
||||
Blockly.Bubble.prototype.setAnchorLocation = function(xy) {
|
||||
this.anchorXY_ = xy;
|
||||
if (this.rendered_) {
|
||||
this.positionBubble_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Position the bubble so that it does not fall off-screen.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.layoutBubble_ = function() {
|
||||
// Compute the preferred bubble location.
|
||||
var relativeLeft = -this.width_ / 4;
|
||||
var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y;
|
||||
// Prevent the bubble from being off-screen.
|
||||
var metrics = this.workspace_.getMetrics();
|
||||
metrics.viewWidth /= this.workspace_.scale;
|
||||
metrics.viewLeft /= this.workspace_.scale;
|
||||
var anchorX = this.anchorXY_.x;
|
||||
if (this.workspace_.RTL) {
|
||||
if (anchorX - metrics.viewLeft - relativeLeft - this.width_ <
|
||||
Blockly.Scrollbar.scrollbarThickness) {
|
||||
// Slide the bubble right until it is onscreen.
|
||||
relativeLeft = anchorX - metrics.viewLeft - this.width_ -
|
||||
Blockly.Scrollbar.scrollbarThickness;
|
||||
} else if (anchorX - metrics.viewLeft - relativeLeft >
|
||||
metrics.viewWidth) {
|
||||
// Slide the bubble left until it is onscreen.
|
||||
relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth;
|
||||
}
|
||||
} else {
|
||||
if (anchorX + relativeLeft < metrics.viewLeft) {
|
||||
// Slide the bubble right until it is onscreen.
|
||||
relativeLeft = metrics.viewLeft - anchorX;
|
||||
} else if (metrics.viewLeft + metrics.viewWidth <
|
||||
anchorX + relativeLeft + this.width_ +
|
||||
Blockly.BlockSvg.SEP_SPACE_X +
|
||||
Blockly.Scrollbar.scrollbarThickness) {
|
||||
// Slide the bubble left until it is onscreen.
|
||||
relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX -
|
||||
this.width_ - Blockly.Scrollbar.scrollbarThickness;
|
||||
}
|
||||
}
|
||||
if (this.anchorXY_.y + relativeTop < metrics.viewTop) {
|
||||
// Slide the bubble below the block.
|
||||
var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox();
|
||||
relativeTop = bBox.height;
|
||||
}
|
||||
this.relativeLeft_ = relativeLeft;
|
||||
this.relativeTop_ = relativeTop;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the bubble to a location relative to the anchor's centre.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.positionBubble_ = function() {
|
||||
var left = this.anchorXY_.x;
|
||||
if (this.workspace_.RTL) {
|
||||
left -= this.relativeLeft_ ;
|
||||
} else {
|
||||
left += this.relativeLeft_;
|
||||
}
|
||||
var top = this.relativeTop_ + this.anchorXY_.y;
|
||||
this.moveTo(left, top);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the bubble group to the specified location in workspace coordinates.
|
||||
* @param {number} x The x position to move to.
|
||||
* @param {number} y The y position to move to.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Bubble.prototype.moveTo = function(x, y) {
|
||||
this.bubbleGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the dimensions of this bubble.
|
||||
* @return {!Object} Object with width and height properties.
|
||||
*/
|
||||
Blockly.Bubble.prototype.getBubbleSize = function() {
|
||||
return {width: this.width_, height: this.height_};
|
||||
};
|
||||
|
||||
/**
|
||||
* Size this bubble.
|
||||
* @param {number} width Width of the bubble.
|
||||
* @param {number} height Height of the bubble.
|
||||
*/
|
||||
Blockly.Bubble.prototype.setBubbleSize = function(width, height) {
|
||||
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
// Minimum size of a bubble.
|
||||
width = Math.max(width, doubleBorderWidth + 45);
|
||||
height = Math.max(height, doubleBorderWidth + 20);
|
||||
this.width_ = width;
|
||||
this.height_ = height;
|
||||
this.bubbleBack_.setAttribute('width', width);
|
||||
this.bubbleBack_.setAttribute('height', height);
|
||||
if (this.resizeGroup_) {
|
||||
if (this.workspace_.RTL) {
|
||||
// Mirror the resize group.
|
||||
var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
this.resizeGroup_.setAttribute('transform', 'translate(' +
|
||||
resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)');
|
||||
} else {
|
||||
this.resizeGroup_.setAttribute('transform', 'translate(' +
|
||||
(width - doubleBorderWidth) + ',' +
|
||||
(height - doubleBorderWidth) + ')');
|
||||
}
|
||||
}
|
||||
if (this.rendered_) {
|
||||
if (this.autoLayout_) {
|
||||
this.layoutBubble_();
|
||||
}
|
||||
this.positionBubble_();
|
||||
this.renderArrow_();
|
||||
}
|
||||
// Allow the contents to resize.
|
||||
if (this.resizeCallback_) {
|
||||
this.resizeCallback_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the arrow between the bubble and the origin.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Bubble.prototype.renderArrow_ = function() {
|
||||
var steps = [];
|
||||
// Find the relative coordinates of the center of the bubble.
|
||||
var relBubbleX = this.width_ / 2;
|
||||
var relBubbleY = this.height_ / 2;
|
||||
// Find the relative coordinates of the center of the anchor.
|
||||
var relAnchorX = -this.relativeLeft_;
|
||||
var relAnchorY = -this.relativeTop_;
|
||||
if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) {
|
||||
// Null case. Bubble is directly on top of the anchor.
|
||||
// Short circuit this rather than wade through divide by zeros.
|
||||
steps.push('M ' + relBubbleX + ',' + relBubbleY);
|
||||
} else {
|
||||
// Compute the angle of the arrow's line.
|
||||
var rise = relAnchorY - relBubbleY;
|
||||
var run = relAnchorX - relBubbleX;
|
||||
if (this.workspace_.RTL) {
|
||||
run *= -1;
|
||||
}
|
||||
var hypotenuse = Math.sqrt(rise * rise + run * run);
|
||||
var angle = Math.acos(run / hypotenuse);
|
||||
if (rise < 0) {
|
||||
angle = 2 * Math.PI - angle;
|
||||
}
|
||||
// Compute a line perpendicular to the arrow.
|
||||
var rightAngle = angle + Math.PI / 2;
|
||||
if (rightAngle > Math.PI * 2) {
|
||||
rightAngle -= Math.PI * 2;
|
||||
}
|
||||
var rightRise = Math.sin(rightAngle);
|
||||
var rightRun = Math.cos(rightAngle);
|
||||
|
||||
// Calculate the thickness of the base of the arrow.
|
||||
var bubbleSize = this.getBubbleSize();
|
||||
var thickness = (bubbleSize.width + bubbleSize.height) /
|
||||
Blockly.Bubble.ARROW_THICKNESS;
|
||||
thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4;
|
||||
|
||||
// Back the tip of the arrow off of the anchor.
|
||||
var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse;
|
||||
relAnchorX = relBubbleX + backoffRatio * run;
|
||||
relAnchorY = relBubbleY + backoffRatio * rise;
|
||||
|
||||
// Coordinates for the base of the arrow.
|
||||
var baseX1 = relBubbleX + thickness * rightRun;
|
||||
var baseY1 = relBubbleY + thickness * rightRise;
|
||||
var baseX2 = relBubbleX - thickness * rightRun;
|
||||
var baseY2 = relBubbleY - thickness * rightRise;
|
||||
|
||||
// Distortion to curve the arrow.
|
||||
var swirlAngle = angle + this.arrow_radians_;
|
||||
if (swirlAngle > Math.PI * 2) {
|
||||
swirlAngle -= Math.PI * 2;
|
||||
}
|
||||
var swirlRise = Math.sin(swirlAngle) *
|
||||
hypotenuse / Blockly.Bubble.ARROW_BEND;
|
||||
var swirlRun = Math.cos(swirlAngle) *
|
||||
hypotenuse / Blockly.Bubble.ARROW_BEND;
|
||||
|
||||
steps.push('M' + baseX1 + ',' + baseY1);
|
||||
steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) +
|
||||
' ' + relAnchorX + ',' + relAnchorY +
|
||||
' ' + relAnchorX + ',' + relAnchorY);
|
||||
steps.push('C' + relAnchorX + ',' + relAnchorY +
|
||||
' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) +
|
||||
' ' + baseX2 + ',' + baseY2);
|
||||
}
|
||||
steps.push('z');
|
||||
this.bubbleArrow_.setAttribute('d', steps.join(' '));
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the colour of a bubble.
|
||||
* @param {string} hexColour Hex code of colour.
|
||||
*/
|
||||
Blockly.Bubble.prototype.setColour = function(hexColour) {
|
||||
this.bubbleBack_.setAttribute('fill', hexColour);
|
||||
this.bubbleArrow_.setAttribute('fill', hexColour);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this bubble.
|
||||
*/
|
||||
Blockly.Bubble.prototype.dispose = function() {
|
||||
Blockly.Bubble.unbindDragEvents_();
|
||||
// Dispose of and unlink the bubble.
|
||||
goog.dom.removeNode(this.bubbleGroup_);
|
||||
this.bubbleGroup_ = null;
|
||||
this.bubbleArrow_ = null;
|
||||
this.bubbleBack_ = null;
|
||||
this.resizeGroup_ = null;
|
||||
this.workspace_ = null;
|
||||
this.content_ = null;
|
||||
this.shape_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this bubble during a drag, taking into account whether or not there is
|
||||
* a drag surface.
|
||||
* @param {?Blockly.BlockDragSurfaceSvg} dragSurface The surface that carries
|
||||
* rendered items during a drag, or null if no drag surface is in use.
|
||||
* @param {!goog.math.Coordinate} newLoc The location to translate to, in
|
||||
* workspace coordinates.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Bubble.prototype.moveDuringDrag = function(dragSurface, newLoc) {
|
||||
if (dragSurface) {
|
||||
dragSurface.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
this.moveTo(newLoc.x, newLoc.y);
|
||||
}
|
||||
if (this.workspace_.RTL) {
|
||||
this.relativeLeft_ = this.anchorXY_.x - newLoc.x - this.width_;
|
||||
} else {
|
||||
this.relativeLeft_ = newLoc.x - this.anchorXY_.x;
|
||||
}
|
||||
this.relativeTop_ = newLoc.y - this.anchorXY_.y;
|
||||
this.renderArrow_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the coordinates of the top corner of this bubble's starting edge (e.g.
|
||||
* top left corner in LTR and top right corner in RTL) relative
|
||||
* to the drawing surface's origin (0,0), in workspace units.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
*/
|
||||
Blockly.Bubble.prototype.getRelativeToSurfaceXY = function() {
|
||||
return new goog.math.Coordinate(
|
||||
this.workspace_.RTL ? this.anchorXY_.x - this.relativeLeft_ : this.anchorXY_.x + this.relativeLeft_,
|
||||
this.anchorXY_.y + this.relativeTop_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether auto-layout of this bubble is enabled. The first time a bubble
|
||||
* is shown it positions itself to not cover any blocks. Once a user has
|
||||
* dragged it to reposition, it renders where the user put it.
|
||||
* @param {boolean} enable True if auto-layout should be enabled, false
|
||||
* otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Bubble.prototype.setAutoLayout = function(enable) {
|
||||
this.autoLayout_ = enable;
|
||||
};
|
||||
285
scratch-blocks/core/bubble_dragger.js
Normal file
285
scratch-blocks/core/bubble_dragger.js
Normal file
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a bubble visually.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BubbleDragger');
|
||||
|
||||
goog.require('Blockly.Bubble');
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
goog.require('Blockly.WorkspaceCommentSvg');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a bubble dragger. It moves things on the bubble canvas around the
|
||||
* workspace when they are being dragged by a mouse or touch. These can be
|
||||
* block comments, mutators, warnings, or workspace comments.
|
||||
* @param {!Blockly.Bubble|!Blockly.WorkspaceCommentSvg} bubble The item on the
|
||||
* bubble canvas to drag.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace to drag on.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BubbleDragger = function(bubble, workspace) {
|
||||
/**
|
||||
* The item on the bubble canvas that is being dragged.
|
||||
* @type {!Blockly.Bubble|!Blockly.WorkspaceCommentSvg}
|
||||
* @private
|
||||
*/
|
||||
this.draggingBubble_ = bubble;
|
||||
|
||||
/**
|
||||
* The workspace on which the bubble is being dragged.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Which delete area the mouse pointer is over, if any.
|
||||
* One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
this.deleteArea_ = null;
|
||||
|
||||
/**
|
||||
* Whether the bubble would be deleted if dropped immediately.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBubble_ = false;
|
||||
|
||||
/**
|
||||
* The location of the top left corner of the dragging bubble's body at the
|
||||
* beginning of the drag, in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* The drag surface to move bubbles to during a drag, or null if none should
|
||||
* be used. Block dragging and bubble dragging use the same surface.
|
||||
* @type {?Blockly.BlockDragSurfaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.dragSurface_ =
|
||||
Blockly.utils.is3dSupported() && !!workspace.getBlockDragSurface() ?
|
||||
workspace.getBlockDragSurface() : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.dispose = function() {
|
||||
this.draggingBubble_ = null;
|
||||
this.workspace_ = null;
|
||||
this.dragSurface_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start dragging a bubble. This includes moving it to the drag surface.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.startBubbleDrag = function() {
|
||||
if (!Blockly.Events.getGroup()) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
this.draggingBubble_.setAutoLayout(false);
|
||||
if (this.dragSurface_) {
|
||||
this.moveToDragSurface_();
|
||||
}
|
||||
|
||||
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true);
|
||||
|
||||
var toolbox = this.workspace_.getToolbox();
|
||||
if (toolbox) {
|
||||
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
toolbox.addStyle(style);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a step of bubble dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
* @param {!Event} e The most recent move event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.dragBubble = function(e, currentDragDeltaXY) {
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc);
|
||||
|
||||
if (this.draggingBubble_.isDeletable()) {
|
||||
this.deleteArea_ = this.workspace_.isDeleteArea(e);
|
||||
this.updateCursorDuringBubbleDrag_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shut the trash can and, if necessary, delete the dragging bubble.
|
||||
* Should be called at the end of a bubble drag.
|
||||
* @return {boolean} whether the bubble was deleted.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.maybeDeleteBubble_ = function() {
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
|
||||
if (this.wouldDeleteBubble_) {
|
||||
if (trashcan) {
|
||||
setTimeout(trashcan.close.bind(trashcan), 100);
|
||||
}
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBubble_.dispose(false, true);
|
||||
} else if (trashcan) {
|
||||
// Make sure the trash can is closed.
|
||||
trashcan.close();
|
||||
}
|
||||
return this.wouldDeleteBubble_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging bubble would be deleted if released immediately.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.updateCursorDuringBubbleDrag_ = function() {
|
||||
this.wouldDeleteBubble_ = this.deleteArea_ != Blockly.DELETE_AREA_NONE;
|
||||
var trashcan = this.workspace_.trashcan;
|
||||
if (this.wouldDeleteBubble_) {
|
||||
this.draggingBubble_.setDeleteStyle(true);
|
||||
if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
|
||||
trashcan.setOpen_(true);
|
||||
}
|
||||
} else {
|
||||
this.draggingBubble_.setDeleteStyle(false);
|
||||
if (trashcan) {
|
||||
trashcan.setOpen_(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish a bubble drag and put the bubble back on the workspace.
|
||||
* @param {!Event} e The mouseup/touchend event.
|
||||
* @param {!goog.math.Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.endBubbleDrag = function(
|
||||
e, currentDragDeltaXY) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBubble(e, currentDragDeltaXY);
|
||||
|
||||
var delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
var newLoc = goog.math.Coordinate.sum(this.startXY_, delta);
|
||||
|
||||
// Move the bubble to its final location.
|
||||
this.draggingBubble_.moveTo(newLoc.x, newLoc.y);
|
||||
var deleted = this.maybeDeleteBubble_();
|
||||
|
||||
if (!deleted) {
|
||||
// Put everything back onto the bubble canvas.
|
||||
if (this.dragSurface_) {
|
||||
this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas());
|
||||
}
|
||||
|
||||
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(false);
|
||||
this.fireMoveEvent_();
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
|
||||
if (this.workspace_.toolbox_) {
|
||||
var style = this.draggingBubble_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
this.workspace_.toolbox_.removeStyle(style);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire a move event at the end of a bubble drag.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.fireMoveEvent_ = function() {
|
||||
var event = null;
|
||||
if (this.draggingBubble_.isComment) {
|
||||
event = new Blockly.Events.CommentMove(this.draggingBubble_);
|
||||
} else if (this.draggingBubble_ instanceof Blockly.ScratchBubble) {
|
||||
event = new Blockly.Events.CommentMove(this.draggingBubble_.comment);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
event.setOldCoordinate(this.startXY_);
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a coordinate object from pixels to workspace units, including a
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
* @param {!goog.math.Coordinate} pixelCoord A coordinate with x and y values
|
||||
* in css pixel units.
|
||||
* @return {!goog.math.Coordinate} The input coordinate divided by the workspace
|
||||
* scale.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.pixelsToWorkspaceUnits_ = function(pixelCoord) {
|
||||
var result = new goog.math.Coordinate(pixelCoord.x / this.workspace_.scale,
|
||||
pixelCoord.y / this.workspace_.scale);
|
||||
if (this.workspace_.isMutator) {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same as
|
||||
// the scale on the parent workspace.
|
||||
// Fix that for dragging.
|
||||
var mainScale = this.workspace_.options.parentWorkspace.scale;
|
||||
result = result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
/**
|
||||
* Move the bubble onto the drag surface at the beginning of a drag. Move the
|
||||
* drag surface to preserve the apparent location of the bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BubbleDragger.prototype.moveToDragSurface_ = function() {
|
||||
this.draggingBubble_.moveTo(0, 0);
|
||||
this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y);
|
||||
// Execute the move on the top-level SVG component.
|
||||
this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot());
|
||||
};
|
||||
177
scratch-blocks/core/colours.js
Normal file
177
scratch-blocks/core/colours.js
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Colours');
|
||||
|
||||
Blockly.Colours = {
|
||||
// SVG colours: these must be specificed in #RRGGBB style
|
||||
// To add an opacity, this must be specified as a separate property (for SVG fill-opacity)
|
||||
"motion": {
|
||||
"primary": "#4C97FF",
|
||||
"secondary": "#4280D7",
|
||||
"tertiary": "#3373CC",
|
||||
"quaternary": "#3373CC"
|
||||
},
|
||||
"looks": {
|
||||
"primary": "#9966FF",
|
||||
"secondary": "#855CD6",
|
||||
"tertiary": "#774DCB",
|
||||
"quaternary": "#774DCB"
|
||||
},
|
||||
"sounds": {
|
||||
"primary": "#CF63CF",
|
||||
"secondary": "#C94FC9",
|
||||
"tertiary": "#BD42BD",
|
||||
"quaternary": "#BD42BD"
|
||||
},
|
||||
"control": {
|
||||
"primary": "#FFAB19",
|
||||
"secondary": "#EC9C13",
|
||||
"tertiary": "#CF8B17",
|
||||
"quaternary": "#CF8B17"
|
||||
},
|
||||
"event": {
|
||||
"primary": "#FFBF00",
|
||||
"secondary": "#E6AC00",
|
||||
"tertiary": "#CC9900",
|
||||
"quaternary": "#CC9900"
|
||||
},
|
||||
"sensing": {
|
||||
"primary": "#5CB1D6",
|
||||
"secondary": "#47A8D1",
|
||||
"tertiary": "#2E8EB8",
|
||||
"quaternary": "#2E8EB8"
|
||||
},
|
||||
"pen": {
|
||||
"primary": "#0fBD8C",
|
||||
"secondary": "#0DA57A",
|
||||
"tertiary": "#0B8E69",
|
||||
"quaternary": "#0B8E69"
|
||||
},
|
||||
"operators": {
|
||||
"primary": "#59C059",
|
||||
"secondary": "#46B946",
|
||||
"tertiary": "#389438",
|
||||
"quaternary": "#389438"
|
||||
},
|
||||
"data": {
|
||||
"primary": "#FF8C1A",
|
||||
"secondary": "#FF8000",
|
||||
"tertiary": "#DB6E00",
|
||||
"quaternary": "#DB6E00"
|
||||
},
|
||||
// This is not a new category, but rather for differentiation
|
||||
// between lists and scalar variables.
|
||||
"data_lists": {
|
||||
"primary": "#FF661A",
|
||||
"secondary": "#FF5500",
|
||||
"tertiary": "#E64D00",
|
||||
"quaternary": "#E64D00"
|
||||
},
|
||||
"more": {
|
||||
"primary": "#FF6680",
|
||||
"secondary": "#FF4D6A",
|
||||
"tertiary": "#FF3355",
|
||||
"quaternary": "#FF3355"
|
||||
},
|
||||
"text": "#FFFFFF",
|
||||
"workspace": "#F9F9F9",
|
||||
"toolboxHover": "#4C97FF",
|
||||
"toolboxSelected": "#e9eef2",
|
||||
"toolboxText": "#575E75",
|
||||
"blackText": "#575E75",
|
||||
"toolbox": "#FFFFFF",
|
||||
"flyout": "#F9F9F9",
|
||||
"scrollbar": "#CECDCE",
|
||||
"scrollbarHover": '#CECDCE',
|
||||
"textField": "#FFFFFF",
|
||||
"textFieldText": "#575E75",
|
||||
"insertionMarker": "#000000",
|
||||
"insertionMarkerOpacity": 0.2,
|
||||
"dragShadowOpacity": 0.3,
|
||||
"stackGlow": "#FFF200",
|
||||
"stackGlowSize": 4,
|
||||
"stackGlowOpacity": 1,
|
||||
"replacementGlow": "#FFFFFF",
|
||||
"replacementGlowSize": 2,
|
||||
"replacementGlowOpacity": 1,
|
||||
"colourPickerStroke": "#FFFFFF",
|
||||
// CSS colours: support RGBA
|
||||
"fieldShadow": "rgba(0,0,0,0.1)",
|
||||
"dropDownShadow": "rgba(0, 0, 0, .3)",
|
||||
"numPadBackground": "#547AB2",
|
||||
"numPadBorder": "#435F91",
|
||||
"numPadActiveBackground": "#435F91",
|
||||
"numPadText": "white", // Do not use hex here, it cannot be inlined with data-uri SVG
|
||||
"valueReportBackground": "#FFFFFF",
|
||||
"valueReportBorder": "#AAAAAA",
|
||||
"valueReportForeground": "#000000",
|
||||
"menuHover": "rgba(0, 0, 0, 0.2)",
|
||||
"contextMenuBackground": "#ffffff",
|
||||
"contextMenuBorder": "#cccccc",
|
||||
"contextMenuForeground": "#000000",
|
||||
"contextMenuActiveBackground": "#d6e9f8",
|
||||
"contextMenuDisabledForeground": "#cccccc",
|
||||
"flyoutLabelColor": "#575E75",
|
||||
"checkboxInactiveBackground": "#ffffff",
|
||||
"checkboxInactiveBorder": "#c8c8c8",
|
||||
"checkboxActiveBackground": "#4C97FF",
|
||||
"checkboxActiveBorder": "#3373CC",
|
||||
"checkboxCheck": "#ffffff",
|
||||
"buttonActiveBackground": "#ffffff",
|
||||
"buttonForeground": "#575E75",
|
||||
"buttonBorder": "#c6c6c6",
|
||||
"zoomIconFilter": "none"
|
||||
};
|
||||
|
||||
/**
|
||||
* Override the colours in Blockly.Colours with new values basded on the
|
||||
* given dictionary.
|
||||
* @param {!Object} colours Dictionary of colour properties and new values.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Colours.overrideColours = function(colours) {
|
||||
// Colour overrides provided by the injection
|
||||
if (colours) {
|
||||
for (var colourProperty in colours) {
|
||||
if (colours.hasOwnProperty(colourProperty) &&
|
||||
Blockly.Colours.hasOwnProperty(colourProperty)) {
|
||||
// If a property is in both colours option and Blockly.Colours,
|
||||
// set the Blockly.Colours value to the override.
|
||||
// Override Blockly category color object properties with those
|
||||
// provided.
|
||||
var colourPropertyValue = colours[colourProperty];
|
||||
if (goog.isObject(colourPropertyValue)) {
|
||||
for (var colourSequence in colourPropertyValue) {
|
||||
if (colourPropertyValue.hasOwnProperty(colourSequence) &&
|
||||
Blockly.Colours[colourProperty].hasOwnProperty(colourSequence)) {
|
||||
Blockly.Colours[colourProperty][colourSequence] =
|
||||
colourPropertyValue[colourSequence];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Blockly.Colours[colourProperty] = colourPropertyValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
293
scratch-blocks/core/comment.js
Normal file
293
scratch-blocks/core/comment.js
Normal file
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a code comment.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Comment');
|
||||
|
||||
goog.require('Blockly.Bubble');
|
||||
goog.require('Blockly.Events.BlockChange');
|
||||
goog.require('Blockly.Events.Ui');
|
||||
goog.require('Blockly.Icon');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment.
|
||||
* @param {!Blockly.Block} block The block associated with this comment.
|
||||
* @extends {Blockly.Icon}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Comment = function(block) {
|
||||
Blockly.Comment.superClass_.constructor.call(this, block);
|
||||
this.createIcon();
|
||||
};
|
||||
goog.inherits(Blockly.Comment, Blockly.Icon);
|
||||
|
||||
/**
|
||||
* Comment text (if bubble is not visible).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.text_ = '';
|
||||
|
||||
/**
|
||||
* Width of bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.width_ = 160;
|
||||
|
||||
/**
|
||||
* Height of bubble.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.height_ = 80;
|
||||
|
||||
/**
|
||||
* Draw the comment icon.
|
||||
* @param {!Element} group The icon group.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.drawIcon_ = function(group) {
|
||||
// Circle.
|
||||
Blockly.utils.createSvgElement('circle',
|
||||
{'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
|
||||
group);
|
||||
// Can't use a real '?' text character since different browsers and operating
|
||||
// systems render it differently.
|
||||
// Body of question mark.
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{
|
||||
'class': 'blocklyIconSymbol',
|
||||
'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
|
||||
'0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
|
||||
'-1.201,0.998 -1.201,1.528 -1.204,2.19z'
|
||||
},
|
||||
group);
|
||||
// Dot of question mark.
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'class': 'blocklyIconSymbol',
|
||||
'x': '6.8',
|
||||
'y': '10.78',
|
||||
'height': '2',
|
||||
'width': '2'
|
||||
},
|
||||
group);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the editor for the comment's bubble.
|
||||
* @return {!Element} The top-level node of the editor.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.createEditor_ = function() {
|
||||
/* Create the editor. Here's the markup that will be generated:
|
||||
<foreignObject x="8" y="8" width="164" height="164">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
|
||||
<textarea xmlns="http://www.w3.org/1999/xhtml"
|
||||
class="blocklyCommentTextarea"
|
||||
style="height: 164px; width: 164px;"></textarea>
|
||||
</body>
|
||||
</foreignObject>
|
||||
*/
|
||||
this.foreignObject_ = Blockly.utils.createSvgElement('foreignObject',
|
||||
{'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
|
||||
null);
|
||||
var body = document.createElementNS(Blockly.HTML_NS, 'body');
|
||||
body.setAttribute('xmlns', Blockly.HTML_NS);
|
||||
body.className = 'blocklyMinimalBody';
|
||||
var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea');
|
||||
textarea.className = 'blocklyCommentTextarea';
|
||||
textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
|
||||
body.appendChild(textarea);
|
||||
this.textarea_ = textarea;
|
||||
this.foreignObject_.appendChild(body);
|
||||
Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.textareaFocus_);
|
||||
// Don't zoom with mousewheel.
|
||||
Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
Blockly.bindEventWithChecks_(textarea, 'change', this, function(_e) {
|
||||
if (this.text_ != textarea.value) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.block_, 'comment', null, this.text_, textarea.value));
|
||||
this.text_ = textarea.value;
|
||||
}
|
||||
});
|
||||
setTimeout(function() {
|
||||
textarea.focus();
|
||||
}, 0);
|
||||
return this.foreignObject_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add or remove editability of the comment.
|
||||
* @override
|
||||
*/
|
||||
Blockly.Comment.prototype.updateEditable = function() {
|
||||
if (this.isVisible()) {
|
||||
// Toggling visibility will force a rerendering.
|
||||
this.setVisible(false);
|
||||
this.setVisible(true);
|
||||
}
|
||||
// Allow the icon to update.
|
||||
Blockly.Icon.prototype.updateEditable.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback function triggered when the bubble has resized.
|
||||
* Resize the text area accordingly.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.resizeBubble_ = function() {
|
||||
if (this.isVisible()) {
|
||||
var size = this.bubble_.getBubbleSize();
|
||||
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth);
|
||||
this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth);
|
||||
this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px';
|
||||
this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show or hide the comment bubble.
|
||||
* @param {boolean} visible True if the bubble should be visible.
|
||||
*/
|
||||
Blockly.Comment.prototype.setVisible = function(visible) {
|
||||
if (visible == this.isVisible()) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
Blockly.Events.fire(
|
||||
new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
|
||||
if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) {
|
||||
// Steal the code from warnings to make an uneditable text bubble.
|
||||
// MSIE does not support foreignobject; textareas are impossible.
|
||||
// http://msdn.microsoft.com/en-us/library/hh834675%28v=vs.85%29.aspx
|
||||
// Always treat comments in IE as uneditable.
|
||||
Blockly.Warning.prototype.setVisible.call(this, visible);
|
||||
return;
|
||||
}
|
||||
// Save the bubble stats before the visibility switch.
|
||||
var text = this.getText();
|
||||
var size = this.getBubbleSize();
|
||||
if (visible) {
|
||||
// Create the bubble.
|
||||
this.bubble_ = new Blockly.Bubble(
|
||||
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
|
||||
this.createEditor_(), this.block_.svgPath_,
|
||||
this.iconXY_, this.width_, this.height_);
|
||||
// Expose this comment's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
|
||||
this.updateColour();
|
||||
} else {
|
||||
// Dispose of the bubble.
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
this.textarea_ = null;
|
||||
this.foreignObject_ = null;
|
||||
}
|
||||
// Restore the bubble stats after the visibility switch.
|
||||
this.setText(text);
|
||||
this.setBubbleSize(size.width, size.height);
|
||||
};
|
||||
|
||||
/**
|
||||
* Bring the comment to the top of the stack when clicked on.
|
||||
* @param {!Event} _e Mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Comment.prototype.textareaFocus_ = function(_e) {
|
||||
// Ideally this would be hooked to the focus event for the comment.
|
||||
// This is tied to mousedown, however doing so in Firefox swallows the cursor
|
||||
// for unknown reasons.
|
||||
// See https://github.com/LLK/scratch-blocks/issues/1631 for more history.
|
||||
if (this.bubble_.promote_()) {
|
||||
// Since the act of moving this node within the DOM causes a loss of focus,
|
||||
// we need to reapply the focus.
|
||||
this.textarea_.focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the dimensions of this comment's bubble.
|
||||
* @return {!Object} Object with width and height properties.
|
||||
*/
|
||||
Blockly.Comment.prototype.getBubbleSize = function() {
|
||||
if (this.isVisible()) {
|
||||
return this.bubble_.getBubbleSize();
|
||||
} else {
|
||||
return {width: this.width_, height: this.height_};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Size this comment's bubble.
|
||||
* @param {number} width Width of the bubble.
|
||||
* @param {number} height Height of the bubble.
|
||||
*/
|
||||
Blockly.Comment.prototype.setBubbleSize = function(width, height) {
|
||||
if (this.textarea_) {
|
||||
this.bubble_.setBubbleSize(width, height);
|
||||
} else {
|
||||
this.width_ = width;
|
||||
this.height_ = height;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns this comment's text.
|
||||
* @return {string} Comment text.
|
||||
*/
|
||||
Blockly.Comment.prototype.getText = function() {
|
||||
return this.textarea_ ? this.textarea_.value : this.text_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set this comment's text.
|
||||
* @param {string} text Comment text.
|
||||
*/
|
||||
Blockly.Comment.prototype.setText = function(text) {
|
||||
if (this.text_ != text) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.block_, 'comment', null, this.text_, text));
|
||||
this.text_ = text;
|
||||
}
|
||||
if (this.textarea_) {
|
||||
this.textarea_.value = text;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this comment.
|
||||
*/
|
||||
Blockly.Comment.prototype.dispose = function() {
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
this.setText(''); // Fire event to delete comment.
|
||||
}
|
||||
this.block_.comment = null;
|
||||
Blockly.Icon.prototype.dispose.call(this);
|
||||
};
|
||||
539
scratch-blocks/core/comment_events.js
Normal file
539
scratch-blocks/core/comment_events.js
Normal file
@@ -0,0 +1,539 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Classes for all comment events.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Events.CommentBase');
|
||||
goog.provide('Blockly.Events.CommentChange');
|
||||
goog.provide('Blockly.Events.CommentCreate');
|
||||
goog.provide('Blockly.Events.CommentDelete');
|
||||
goog.provide('Blockly.Events.CommentMove');
|
||||
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.Events.Abstract');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a comment event.
|
||||
* @param {Blockly.WorkspaceComment | Blockly.ScratchBlockComment} comment
|
||||
* The comment this event corresponds to.
|
||||
* @extends {Blockly.Events.Abstract}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentBase = function(comment) {
|
||||
/**
|
||||
* The ID of the comment this event pertains to.
|
||||
* @type {string}
|
||||
*/
|
||||
this.commentId = comment.id;
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.workspaceId = comment.workspace.id;
|
||||
|
||||
/**
|
||||
* The ID of the block this comment belongs to or null if it is not a block
|
||||
* comment.
|
||||
* @type {string}
|
||||
*/
|
||||
this.blockId = comment.blockId || null;
|
||||
|
||||
/**
|
||||
* The event group id for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = Blockly.Events.group_;
|
||||
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = Blockly.Events.recordUndo;
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentBase, Blockly.Events.Abstract);
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentBase.prototype.toJson = function() {
|
||||
var json = {
|
||||
'type': this.type
|
||||
};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
if (this.commentId) {
|
||||
json['commentId'] = this.commentId;
|
||||
}
|
||||
if (this.blockId) {
|
||||
json['blockId'] = this.blockId;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentBase.prototype.fromJson = function(json) {
|
||||
this.commentId = json['commentId'];
|
||||
this.group = json['group'];
|
||||
this.blockId = json['blockId'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for finding the comment this event pertains to.
|
||||
* @return {?(Blockly.WorkspaceComment | Blockly.ScratchBlockComment)}
|
||||
* The comment this event pertains to, or null if it no longer exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.CommentBase.prototype.getComment_ = function() {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
return workspace.getCommentById(this.commentId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment change event.
|
||||
* @param {Blockly.WorkspaceComment | Blockly.ScratchBlockComment} comment
|
||||
* The comment that is being changed. Null for a blank event.
|
||||
* @param {!object} oldContents Object containing previous state of a comment's
|
||||
* properties. The possible properties can be: 'minimized', 'text', or
|
||||
* 'width' and 'height' together. Must contain the same property (or in the
|
||||
* case of 'width' and 'height' properties) as the 'newContents' param.
|
||||
* @param {!object} newContents Object containing the new state of a comment's
|
||||
* properties. The possible properties can be: 'minimized', 'text', or
|
||||
* 'width' and 'height' together. Must contain the same property (or in the
|
||||
* case of 'width' and 'height' properties) as the 'oldContents' param.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentChange = function(comment, oldContents, newContents) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentChange.superClass_.constructor.call(this, comment);
|
||||
this.oldContents_ = oldContents;
|
||||
this.newContents_ = newContents;
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentChange, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.type = Blockly.Events.COMMENT_CHANGE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentChange.superClass_.toJson.call(this);
|
||||
json['newContents'] = this.newContents_;
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentChange.superClass_.fromJson.call(this, json);
|
||||
this.newContents_ = json['newValue'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.isNull = function() {
|
||||
return this.oldContents_ == this.newContents_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentChange.prototype.run = function(forward) {
|
||||
var comment = this.getComment_();
|
||||
if (!comment) {
|
||||
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
var contents = forward ? this.newContents_ : this.oldContents_;
|
||||
|
||||
if (contents.hasOwnProperty('minimized')) {
|
||||
comment.setMinimized(contents.minimized);
|
||||
}
|
||||
if (contents.hasOwnProperty('width') && contents.hasOwnProperty('height')) {
|
||||
comment.setSize(contents.width, contents.height);
|
||||
}
|
||||
if (contents.hasOwnProperty('text')) {
|
||||
comment.setText(contents.text);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment creation event.
|
||||
* @param {Blockly.WorkspaceComment | Blockly.ScratchBlockComment} comment
|
||||
* The created comment. Null for a blank event.
|
||||
* @param {string=} opt_blockId Optional id for the block this comment belongs
|
||||
* to, if it is a block comment.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentCreate = function(comment) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentCreate.superClass_.constructor.call(this, comment);
|
||||
|
||||
/**
|
||||
* The text content of this comment.
|
||||
* @type {string}
|
||||
*/
|
||||
this.text = comment.getText();
|
||||
|
||||
/**
|
||||
* The XY position of this comment on the workspace.
|
||||
* @type {goog.math.Coordinate}
|
||||
*/
|
||||
this.xy = comment.getXY();
|
||||
|
||||
var hw = comment.getHeightWidth();
|
||||
|
||||
/**
|
||||
* The width of this comment when it is full size.
|
||||
* @type {number}
|
||||
*/
|
||||
this.width = hw.width;
|
||||
|
||||
/**
|
||||
* The height of this comment when it is full size.
|
||||
* @type {number}
|
||||
*/
|
||||
this.height = hw.height;
|
||||
|
||||
/**
|
||||
* Whether or not this comment is minimized.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.minimized = comment.isMinimized() || false;
|
||||
|
||||
this.xml = comment.toXmlWithXY();
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentCreate, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.type = Blockly.Events.COMMENT_CREATE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* TODO (github.com/google/blockly/issues/1266): "Full" and "minimal"
|
||||
* serialization.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentCreate.superClass_.toJson.call(this);
|
||||
json['xml'] = Blockly.Xml.domToText(this.xml);
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentCreate.superClass_.fromJson.call(this, json);
|
||||
this.xml = Blockly.Xml.textToDom('<xml>' + json['xml'] + '</xml>').firstChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentCreate.prototype.run = function(forward) {
|
||||
if (forward) {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (this.blockId) {
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
if (block) {
|
||||
block.setCommentText('', this.commentId, this.xy.x, this.xy.y, this.minimized);
|
||||
}
|
||||
} else {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
}
|
||||
} else {
|
||||
var comment = this.getComment_();
|
||||
if (comment) {
|
||||
comment.dispose(false, false);
|
||||
} else {
|
||||
// Only complain about root-level block.
|
||||
console.warn("Can't uncreate non-existent comment: " + this.commentId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment deletion event.
|
||||
* @param {Blockly.WorkspaceComment | Blockly.ScratchBlockComment} comment
|
||||
* The deleted comment. Null for a blank event.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentDelete = function(comment) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentDelete.superClass_.constructor.call(this, comment);
|
||||
this.xy = comment.getXY();
|
||||
this.minimized = comment.isMinimized() || false;
|
||||
this.text = comment.getText();
|
||||
var hw = comment.getHeightWidth();
|
||||
this.height = hw.height;
|
||||
this.width = hw.width;
|
||||
|
||||
this.xml = comment.toXmlWithXY();
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentDelete, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.type = Blockly.Events.COMMENT_DELETE;
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* TODO (github.com/google/blockly/issues/1266): "Full" and "minimal"
|
||||
* serialization.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentDelete.superClass_.toJson.call(this);
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentDelete.superClass_.fromJson.call(this, json);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentDelete.prototype.run = function(forward) {
|
||||
if (forward) {
|
||||
var comment = this.getComment_();
|
||||
if (comment) {
|
||||
comment.dispose(false, false);
|
||||
} else {
|
||||
// Only complain about root-level block.
|
||||
console.warn("Can't delete non-existent comment: " + this.commentId);
|
||||
}
|
||||
} else {
|
||||
var workspace = this.getEventWorkspace_();
|
||||
if (this.blockId) {
|
||||
var block = workspace.getBlockById(this.blockId);
|
||||
block.setCommentText(this.text, this.commentId, this.xy.x, this.xy.y, this.minimized);
|
||||
block.comment.setSize(this.width, this.height);
|
||||
} else {
|
||||
var xml = goog.dom.createDom('xml');
|
||||
xml.appendChild(this.xml);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class for a comment move event. Created before the move.
|
||||
* @param {Blockly.WorkspaceComment | Blockly.ScratchBlockComment} comment
|
||||
* The comment that is being moved. Null for a blank event.
|
||||
* @extends {Blockly.Events.CommentBase}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.CommentMove = function(comment) {
|
||||
if (!comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
Blockly.Events.CommentMove.superClass_.constructor.call(this, comment);
|
||||
|
||||
/**
|
||||
* The comment that is being moved. Will be cleared after recording the new
|
||||
* location.
|
||||
* @type {?Blockly.WorkspaceComment | Blockly.ScratchBlockComment}
|
||||
*/
|
||||
this.comment_ = comment;
|
||||
|
||||
this.workspaceWidth_ = comment.workspace.getWidth();
|
||||
/**
|
||||
* The location before the move, in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
*/
|
||||
this.oldCoordinate_ = this.currentLocation_();
|
||||
|
||||
/**
|
||||
* The location after the move, in workspace coordinates.
|
||||
* @type {!goog.math.Coordinate}
|
||||
*/
|
||||
this.newCoordinate_ = null;
|
||||
};
|
||||
goog.inherits(Blockly.Events.CommentMove, Blockly.Events.CommentBase);
|
||||
|
||||
/**
|
||||
* Calculate the current, language agnostic location of the comment.
|
||||
* This value should not report different numbers in LTR vs. RTL.
|
||||
* @return {goog.math.Coordinate} The location of the comment.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.currentLocation_ = function() {
|
||||
var xy = this.comment_.getXY();
|
||||
if (!this.comment_.workspace.RTL) {
|
||||
return xy;
|
||||
}
|
||||
|
||||
var rtlAwareX;
|
||||
if (this.comment_ instanceof Blockly.ScratchBlockComment) {
|
||||
var commentWidth = this.comment_.getBubbleSize().width;
|
||||
rtlAwareX = this.workspaceWidth_ - xy.x - commentWidth;
|
||||
} else {
|
||||
rtlAwareX = this.workspaceWidth_ - xy.x;
|
||||
}
|
||||
return new goog.math.Coordinate(rtlAwareX, xy.y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Record the comment's new location. Called after the move. Can only be
|
||||
* called once.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.recordNew = function() {
|
||||
if (!this.comment_) {
|
||||
throw new Error('Tried to record the new position of a comment on the ' +
|
||||
'same event twice.');
|
||||
}
|
||||
this.newCoordinate_ = this.currentLocation_();
|
||||
this.comment_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.type = Blockly.Events.COMMENT_MOVE;
|
||||
|
||||
/**
|
||||
* Override the location before the move. Use this if you don't create the
|
||||
* event until the end of the move, but you know the original location.
|
||||
* @param {!goog.math.Coordinate} xy The location before the move, in workspace
|
||||
* coordinates.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.setOldCoordinate = function(xy) {
|
||||
this.oldCoordinate_ = new goog.math.Coordinate(this.comment_.workspace.RTL ?
|
||||
this.workspaceWidth_ - xy.x : xy.x, xy.y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* TODO (github.com/google/blockly/issues/1266): "Full" and "minimal"
|
||||
* serialization.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.toJson = function() {
|
||||
var json = Blockly.Events.CommentMove.superClass_.toJson.call(this);
|
||||
if (this.newCoordinate_) {
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
|
||||
Math.round(this.newCoordinate_.y);
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.fromJson = function(json) {
|
||||
Blockly.Events.CommentMove.superClass_.fromJson.call(this, json);
|
||||
|
||||
if (json['newCoordinate']) {
|
||||
var xy = json['newCoordinate'].split(',');
|
||||
this.newCoordinate_ =
|
||||
new goog.math.Coordinate(parseFloat(xy[0]), parseFloat(xy[1]));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.isNull = function() {
|
||||
return goog.math.Coordinate.equals(this.oldCoordinate_, this.newCoordinate_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run a move event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.CommentMove.prototype.run = function(forward) {
|
||||
var comment = this.getComment_();
|
||||
if (!comment) {
|
||||
console.warn('Can\'t move non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
|
||||
var target = forward ? this.newCoordinate_ : this.oldCoordinate_;
|
||||
|
||||
if (comment instanceof Blockly.ScratchBlockComment) {
|
||||
if (comment.workspace.RTL) {
|
||||
comment.moveTo(this.workspaceWidth_ - target.x, target.y);
|
||||
} else {
|
||||
comment.moveTo(target.x, target.y);
|
||||
}
|
||||
} else {
|
||||
// TODO: Check if the comment is being dragged, and give up if so.
|
||||
var current = comment.getXY();
|
||||
if (comment.workspace.RTL) {
|
||||
var deltaX = target.x - (this.workspaceWidth_ - current.x);
|
||||
comment.moveBy(-deltaX, target.y - current.y);
|
||||
} else {
|
||||
comment.moveBy(target.x - current.x, target.y - current.y);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
776
scratch-blocks/core/connection.js
Normal file
776
scratch-blocks/core/connection.js
Normal file
@@ -0,0 +1,776 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Components for creating connections between blocks.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Connection');
|
||||
|
||||
goog.require('Blockly.Events.BlockMove');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a connection between blocks.
|
||||
* @param {!Blockly.Block} source The block establishing this connection.
|
||||
* @param {number} type The type of the connection.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Connection = function(source, type) {
|
||||
/**
|
||||
* @type {!Blockly.Block}
|
||||
* @protected
|
||||
*/
|
||||
this.sourceBlock_ = source;
|
||||
/** @type {number} */
|
||||
this.type = type;
|
||||
// Shortcut for the databases for this connection's workspace.
|
||||
if (source.workspace.connectionDBList) {
|
||||
this.db_ = source.workspace.connectionDBList[type];
|
||||
this.dbOpposite_ =
|
||||
source.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]];
|
||||
this.hidden_ = !this.db_;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constants for checking whether two connections are compatible.
|
||||
*/
|
||||
Blockly.Connection.CAN_CONNECT = 0;
|
||||
Blockly.Connection.REASON_SELF_CONNECTION = 1;
|
||||
Blockly.Connection.REASON_WRONG_TYPE = 2;
|
||||
Blockly.Connection.REASON_TARGET_NULL = 3;
|
||||
Blockly.Connection.REASON_CHECKS_FAILED = 4;
|
||||
Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5;
|
||||
Blockly.Connection.REASON_SHADOW_PARENT = 6;
|
||||
// Fixes #1127, but may be the wrong solution.
|
||||
Blockly.Connection.REASON_CUSTOM_PROCEDURE = 7;
|
||||
|
||||
/**
|
||||
* Connection this connection connects to. Null if not connected.
|
||||
* @type {Blockly.Connection}
|
||||
*/
|
||||
Blockly.Connection.prototype.targetConnection = null;
|
||||
|
||||
/**
|
||||
* List of compatible value types. Null if all types are compatible.
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.check_ = null;
|
||||
|
||||
/**
|
||||
* DOM representation of a shadow block, or null if none.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.shadowDom_ = null;
|
||||
|
||||
/**
|
||||
* Horizontal location of this connection.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.x_ = 0;
|
||||
|
||||
/**
|
||||
* Vertical location of this connection.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.y_ = 0;
|
||||
|
||||
/**
|
||||
* Has this connection been added to the connection database?
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.inDB_ = false;
|
||||
|
||||
/**
|
||||
* Connection database for connections of this type on the current workspace.
|
||||
* @type {Blockly.ConnectionDB}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.db_ = null;
|
||||
|
||||
/**
|
||||
* Connection database for connections compatible with this type on the
|
||||
* current workspace.
|
||||
* @type {Blockly.ConnectionDB}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.dbOpposite_ = null;
|
||||
|
||||
/**
|
||||
* Whether this connections is hidden (not tracked in a database) or not.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.hidden_ = null;
|
||||
|
||||
/**
|
||||
* Connect two connections together. This is the connection on the superior
|
||||
* block.
|
||||
* @param {!Blockly.Connection} childConnection Connection on inferior block.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.connect_ = function(childConnection) {
|
||||
var parentConnection = this;
|
||||
var parentBlock = parentConnection.getSourceBlock();
|
||||
var childBlock = childConnection.getSourceBlock();
|
||||
var isSurroundingC = false;
|
||||
if (parentConnection == parentBlock.getFirstStatementConnection()) {
|
||||
isSurroundingC = true;
|
||||
}
|
||||
|
||||
if (Blockly.Events.isEnabled() && !childBlock.isInsertionMarker()) {
|
||||
childBlock.workspace.procedureReturnsWillChange();
|
||||
}
|
||||
|
||||
// Disconnect any existing parent on the child connection.
|
||||
if (childConnection.isConnected()) {
|
||||
// Scratch-specific behaviour:
|
||||
// If we're using a c-shaped block to surround a stack, remember where the
|
||||
// stack used to be connected.
|
||||
if (isSurroundingC) {
|
||||
var previousParentConnection = childConnection.targetConnection;
|
||||
}
|
||||
childConnection.disconnect();
|
||||
}
|
||||
if (parentConnection.isConnected()) {
|
||||
// Other connection is already connected to something.
|
||||
// Disconnect it and reattach it or bump it as needed.
|
||||
var orphanBlock = parentConnection.targetBlock();
|
||||
var shadowDom = parentConnection.getShadowDom();
|
||||
// Temporarily set the shadow DOM to null so it does not respawn.
|
||||
parentConnection.setShadowDom(null);
|
||||
// Displaced shadow blocks dissolve rather than reattaching or bumping.
|
||||
if (orphanBlock.isShadow()) {
|
||||
// Save the shadow block so that field values are preserved.
|
||||
shadowDom = Blockly.Xml.blockToDom(orphanBlock);
|
||||
orphanBlock.dispose();
|
||||
orphanBlock = null;
|
||||
} else if (parentConnection.type == Blockly.NEXT_STATEMENT) {
|
||||
// Statement connections.
|
||||
// Statement blocks may be inserted into the middle of a stack.
|
||||
// Split the stack.
|
||||
if (!orphanBlock.previousConnection) {
|
||||
throw 'Orphan block does not have a previous connection.';
|
||||
}
|
||||
// Attempt to reattach the orphan at the bottom of the newly inserted
|
||||
// block. Since this block may be a stack, walk down to the end.
|
||||
var newBlock = childBlock;
|
||||
while (newBlock.nextConnection) {
|
||||
var nextBlock = newBlock.getNextBlock();
|
||||
if (nextBlock && !nextBlock.isShadow()) {
|
||||
newBlock = nextBlock;
|
||||
} else {
|
||||
if (orphanBlock.previousConnection.checkType_(
|
||||
newBlock.nextConnection)) {
|
||||
newBlock.nextConnection.connect(orphanBlock.previousConnection);
|
||||
orphanBlock = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (orphanBlock) {
|
||||
// Unable to reattach orphan.
|
||||
parentConnection.disconnect();
|
||||
if (Blockly.Events.recordUndo) {
|
||||
// Bump it off to the side after a moment.
|
||||
var group = Blockly.Events.getGroup();
|
||||
setTimeout(function() {
|
||||
// Verify orphan hasn't been deleted or reconnected (user on meth).
|
||||
if (orphanBlock.workspace && !orphanBlock.getParent()) {
|
||||
Blockly.Events.setGroup(group);
|
||||
if (orphanBlock.outputConnection) {
|
||||
orphanBlock.outputConnection.bumpAwayFrom_(parentConnection);
|
||||
} else if (orphanBlock.previousConnection) {
|
||||
orphanBlock.previousConnection.bumpAwayFrom_(parentConnection);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
}, Blockly.BUMP_DELAY);
|
||||
}
|
||||
}
|
||||
// Restore the shadow DOM.
|
||||
parentConnection.setShadowDom(shadowDom);
|
||||
}
|
||||
|
||||
if (isSurroundingC && previousParentConnection) {
|
||||
previousParentConnection.connect(parentBlock.previousConnection);
|
||||
}
|
||||
|
||||
var event;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
event = new Blockly.Events.BlockMove(childBlock);
|
||||
}
|
||||
// Establish the connections.
|
||||
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
|
||||
// Demote the inferior block so that one is a child of the superior one.
|
||||
childBlock.setParent(parentBlock);
|
||||
if (event) {
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links to this connection (not including from the source object).
|
||||
*/
|
||||
Blockly.Connection.prototype.dispose = function() {
|
||||
if (this.isConnected()) {
|
||||
throw 'Disconnect connection before disposing of it.';
|
||||
}
|
||||
if (this.inDB_) {
|
||||
this.db_.removeConnection_(this);
|
||||
}
|
||||
this.db_ = null;
|
||||
this.dbOpposite_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} true if the connection is not connected or is connected to
|
||||
* an insertion marker, false otherwise.
|
||||
*/
|
||||
Blockly.Connection.prototype.isConnectedToNonInsertionMarker = function() {
|
||||
return this.targetConnection && !this.targetBlock().isInsertionMarker();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the source block for this connection.
|
||||
* @return {Blockly.Block} The source block, or null if there is none.
|
||||
*/
|
||||
Blockly.Connection.prototype.getSourceBlock = function() {
|
||||
return this.sourceBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Does the connection belong to a superior block (higher in the source stack)?
|
||||
* @return {boolean} True if connection faces down or right.
|
||||
*/
|
||||
Blockly.Connection.prototype.isSuperior = function() {
|
||||
return this.type == Blockly.INPUT_VALUE ||
|
||||
this.type == Blockly.NEXT_STATEMENT;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the connection connected?
|
||||
* @return {boolean} True if connection is connected to another connection.
|
||||
*/
|
||||
Blockly.Connection.prototype.isConnected = function() {
|
||||
return !!this.targetConnection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the current connection can connect with the target
|
||||
* connection.
|
||||
* @param {Blockly.Connection} target Connection to check compatibility with.
|
||||
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
|
||||
* an error code otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
|
||||
if (!target) {
|
||||
return Blockly.Connection.REASON_TARGET_NULL;
|
||||
}
|
||||
if (this.isSuperior()) {
|
||||
var blockA = this.sourceBlock_;
|
||||
var blockB = target.getSourceBlock();
|
||||
var superiorConn = this;
|
||||
} else {
|
||||
var blockB = this.sourceBlock_;
|
||||
var blockA = target.getSourceBlock();
|
||||
var superiorConn = target;
|
||||
}
|
||||
if (blockA && blockA == blockB) {
|
||||
return Blockly.Connection.REASON_SELF_CONNECTION;
|
||||
} else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) {
|
||||
return Blockly.Connection.REASON_WRONG_TYPE;
|
||||
} else if (blockA && blockB && blockA.workspace !== blockB.workspace) {
|
||||
return Blockly.Connection.REASON_DIFFERENT_WORKSPACES;
|
||||
} else if (!this.checkType_(target)) {
|
||||
return Blockly.Connection.REASON_CHECKS_FAILED;
|
||||
} else if (blockA.isShadow() && !blockB.isShadow()) {
|
||||
return Blockly.Connection.REASON_SHADOW_PARENT;
|
||||
} else if ((blockA.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE &&
|
||||
blockB.type != Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE &&
|
||||
superiorConn == blockA.getInput('custom_block').connection) ||
|
||||
(blockB.type == Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE &&
|
||||
blockA.type != Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE)) {
|
||||
// Hack to fix #1127: Fail attempts to connect to the custom_block input
|
||||
// on a defnoreturn block, unless the connecting block is a specific type.
|
||||
// And hack to fix #1534: Fail attempts to connect anything but a
|
||||
// defnoreturn block to a prototype block.
|
||||
return Blockly.Connection.REASON_CUSTOM_PROCEDURE;
|
||||
}
|
||||
return Blockly.Connection.CAN_CONNECT;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the current connection and target connection are compatible
|
||||
* and throws an exception if they are not.
|
||||
* @param {Blockly.Connection} target The connection to check compatibility
|
||||
* with.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.checkConnection_ = function(target) {
|
||||
switch (this.canConnectWithReason_(target)) {
|
||||
case Blockly.Connection.CAN_CONNECT:
|
||||
break;
|
||||
case Blockly.Connection.REASON_SELF_CONNECTION:
|
||||
throw 'Attempted to connect a block to itself.';
|
||||
case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:
|
||||
// Usually this means one block has been deleted.
|
||||
throw 'Blocks not on same workspace.';
|
||||
case Blockly.Connection.REASON_WRONG_TYPE:
|
||||
throw 'Attempt to connect incompatible types.';
|
||||
case Blockly.Connection.REASON_TARGET_NULL:
|
||||
throw 'Target connection is null.';
|
||||
case Blockly.Connection.REASON_CHECKS_FAILED:
|
||||
var msg = 'Connection checks failed. ';
|
||||
msg += this + ' expected ' + this.check_ + ', found ' + target.check_;
|
||||
throw msg;
|
||||
case Blockly.Connection.REASON_SHADOW_PARENT:
|
||||
throw 'Connecting non-shadow to shadow block.';
|
||||
case Blockly.Connection.REASON_CUSTOM_PROCEDURE:
|
||||
throw 'Trying to replace a shadow on a custom procedure definition.';
|
||||
default:
|
||||
throw 'Unknown connection failure: this should never happen!';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the two connections can be dragged to connect to each other.
|
||||
* This is used by the connection database when searching for the closest
|
||||
* connection.
|
||||
* @param {!Blockly.Connection} candidate A nearby connection to check, which
|
||||
* must be a previous connection.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
*/
|
||||
Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) {
|
||||
if (this.targetConnection) {
|
||||
// This connection is already occupied.
|
||||
// A next connection will never disconnect itself mid-drag.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't let blocks try to connect to themselves or ones they nest.
|
||||
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstStatementConnection =
|
||||
this.sourceBlock_.getFirstStatementConnection();
|
||||
// Is it a C-shaped (e.g. repeat) or E-shaped (e.g. if-else) block?
|
||||
var isComplexStatement = firstStatementConnection != null;
|
||||
var isFirstStatementConnection = this == firstStatementConnection;
|
||||
var isNextConnection = this == this.sourceBlock_.nextConnection;
|
||||
|
||||
// Scratch-specific behaviour: can connect to the first statement input of a
|
||||
// C-shaped or E-shaped block, or to the next connection of any statement
|
||||
// block, but not to the second statement input of an E-shaped block.
|
||||
if (isComplexStatement && !isFirstStatementConnection && !isNextConnection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Complex blocks with no previous connection will not be allowed to connect
|
||||
// mid-stack.
|
||||
var sourceHasPreviousConn = this.sourceBlock_.previousConnection != null;
|
||||
|
||||
if (isFirstStatementConnection && sourceHasPreviousConn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNextConnection ||
|
||||
(isFirstStatementConnection && !sourceHasPreviousConn)) {
|
||||
// If the candidate is the first connection in a stack, we can connect.
|
||||
if (!candidate.targetConnection) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var targetBlock = candidate.targetBlock();
|
||||
// If it is connected a real block, game over.
|
||||
if (!targetBlock.isInsertionMarker()) {
|
||||
return false;
|
||||
}
|
||||
// If it's connected to an insertion marker but that insertion marker
|
||||
// is the first block in a stack, it's still fine. If that insertion
|
||||
// marker is in the middle of a stack, it won't work.
|
||||
return !targetBlock.getPreviousBlock();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the two connections can be dragged to connect to each other.
|
||||
* This is used by the connection database when searching for the closest
|
||||
* connection.
|
||||
* @param {!Blockly.Connection} candidate A nearby connection to check.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
*/
|
||||
Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
||||
|
||||
// Don't consider insertion markers.
|
||||
if (candidate.sourceBlock_.isInsertionMarker()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Type checking.
|
||||
var canConnect = this.canConnectWithReason_(candidate);
|
||||
if (canConnect != Blockly.Connection.CAN_CONNECT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstStatementConnection =
|
||||
this.sourceBlock_.getFirstStatementConnection();
|
||||
switch (candidate.type) {
|
||||
case Blockly.PREVIOUS_STATEMENT:
|
||||
return this.canConnectToPrevious_(candidate);
|
||||
case Blockly.OUTPUT_VALUE: {
|
||||
// Can't drag an input to an output--you have to move the inferior block.
|
||||
return false;
|
||||
}
|
||||
case Blockly.INPUT_VALUE: {
|
||||
// Offering to connect the left (male) of a value block to an already
|
||||
// connected value pair is ok, we'll splice it in.
|
||||
// However, don't offer to splice into an unmovable block.
|
||||
if (candidate.targetConnection &&
|
||||
!candidate.targetBlock().isMovable() &&
|
||||
!candidate.targetBlock().isShadow()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Blockly.NEXT_STATEMENT: {
|
||||
// Scratch-specific behaviour:
|
||||
// If this is a c-block, we can't connect this block's
|
||||
// previous connection unless we're connecting to the end of the last
|
||||
// block on a stack or there's already a block connected inside the c.
|
||||
if (firstStatementConnection &&
|
||||
this == this.sourceBlock_.previousConnection &&
|
||||
candidate.isConnectedToNonInsertionMarker() &&
|
||||
!firstStatementConnection.targetConnection) {
|
||||
return false;
|
||||
}
|
||||
// Don't let a block with no next connection bump other blocks out of the
|
||||
// stack. But covering up a shadow block or stack of shadow blocks is
|
||||
// fine. Similarly, replacing a terminal statement with another terminal
|
||||
// statement is allowed.
|
||||
if (candidate.isConnectedToNonInsertionMarker() &&
|
||||
!this.sourceBlock_.nextConnection &&
|
||||
!candidate.targetBlock().isShadow() &&
|
||||
candidate.targetBlock().nextConnection) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw 'Unknown connection type in isConnectionAllowed';
|
||||
}
|
||||
|
||||
// Don't let blocks try to connect to themselves or ones they nest.
|
||||
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect this connection to another connection.
|
||||
* @param {!Blockly.Connection} otherConnection Connection to connect to.
|
||||
*/
|
||||
Blockly.Connection.prototype.connect = function(otherConnection) {
|
||||
if (this.targetConnection == otherConnection) {
|
||||
// Already connected together. NOP.
|
||||
return;
|
||||
}
|
||||
this.checkConnection_(otherConnection);
|
||||
// Determine which block is superior (higher in the source stack).
|
||||
if (this.isSuperior()) {
|
||||
// Superior block.
|
||||
this.connect_(otherConnection);
|
||||
} else {
|
||||
// Inferior block.
|
||||
otherConnection.connect_(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update two connections to target each other.
|
||||
* @param {Blockly.Connection} first The first connection to update.
|
||||
* @param {Blockly.Connection} second The second connection to update.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.connectReciprocally_ = function(first, second) {
|
||||
goog.asserts.assert(first && second, 'Cannot connect null connections.');
|
||||
first.targetConnection = second;
|
||||
second.targetConnection = first;
|
||||
};
|
||||
|
||||
/**
|
||||
* Does the given block have one and only one connection point that will accept
|
||||
* an orphaned block?
|
||||
* @param {!Blockly.Block} block The superior block.
|
||||
* @param {!Blockly.Block} orphanBlock The inferior block.
|
||||
* @return {Blockly.Connection} The suitable connection point on 'block',
|
||||
* or null.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
|
||||
var connection = false;
|
||||
for (var i = 0; i < block.inputList.length; i++) {
|
||||
var thisConnection = block.inputList[i].connection;
|
||||
if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
|
||||
orphanBlock.outputConnection.checkType_(thisConnection)) {
|
||||
if (connection) {
|
||||
return null; // More than one connection.
|
||||
}
|
||||
connection = thisConnection;
|
||||
}
|
||||
}
|
||||
return connection;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect this connection.
|
||||
*/
|
||||
Blockly.Connection.prototype.disconnect = function() {
|
||||
var otherConnection = this.targetConnection;
|
||||
goog.asserts.assert(otherConnection, 'Source connection not connected.');
|
||||
goog.asserts.assert(otherConnection.targetConnection == this,
|
||||
'Target connection not connected to source connection.');
|
||||
|
||||
var parentBlock, childBlock, parentConnection;
|
||||
if (this.isSuperior()) {
|
||||
// Superior block.
|
||||
parentBlock = this.sourceBlock_;
|
||||
childBlock = otherConnection.getSourceBlock();
|
||||
parentConnection = this;
|
||||
} else {
|
||||
// Inferior block.
|
||||
parentBlock = otherConnection.getSourceBlock();
|
||||
childBlock = this.sourceBlock_;
|
||||
parentConnection = otherConnection;
|
||||
}
|
||||
this.disconnectInternal_(parentBlock, childBlock);
|
||||
parentConnection.respawnShadow_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect two blocks that are connected by this connection.
|
||||
* @param {!Blockly.Block} parentBlock The superior block.
|
||||
* @param {!Blockly.Block} childBlock The inferior block.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
|
||||
childBlock) {
|
||||
if (Blockly.Events.isEnabled() && !childBlock.isInsertionMarker()) {
|
||||
childBlock.workspace.procedureReturnsWillChange();
|
||||
}
|
||||
|
||||
var event;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
event = new Blockly.Events.BlockMove(childBlock);
|
||||
}
|
||||
|
||||
var otherConnection = this.targetConnection;
|
||||
otherConnection.targetConnection = null;
|
||||
this.targetConnection = null;
|
||||
childBlock.setParent(null);
|
||||
if (event) {
|
||||
event.recordNew();
|
||||
Blockly.Events.fire(event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respawn the shadow block if there was one connected to the this connection.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.respawnShadow_ = function() {
|
||||
var parentBlock = this.getSourceBlock();
|
||||
var shadow = this.getShadowDom();
|
||||
if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
|
||||
var blockShadow =
|
||||
Blockly.Xml.domToBlock(shadow, parentBlock.workspace);
|
||||
if (blockShadow.outputConnection) {
|
||||
this.connect(blockShadow.outputConnection);
|
||||
} else if (blockShadow.previousConnection) {
|
||||
this.connect(blockShadow.previousConnection);
|
||||
} else {
|
||||
throw 'Child block does not have output or previous statement.';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the block that this connection connects to.
|
||||
* @return {Blockly.Block} The connected block or null if none is connected.
|
||||
*/
|
||||
Blockly.Connection.prototype.targetBlock = function() {
|
||||
if (this.isConnected()) {
|
||||
return this.targetConnection.getSourceBlock();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is this connection compatible with another connection with respect to the
|
||||
* value type system. E.g. square_root("Hello") is not compatible.
|
||||
* @param {!Blockly.Connection} otherConnection Connection to compare against.
|
||||
* @return {boolean} True if the connections share a type.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Connection.prototype.checkType_ = function(otherConnection) {
|
||||
if (!this.check_ || !otherConnection.check_) {
|
||||
// One or both sides are promiscuous enough that anything will fit.
|
||||
return true;
|
||||
}
|
||||
// Find any intersection in the check lists.
|
||||
for (var i = 0; i < this.check_.length; i++) {
|
||||
if (otherConnection.check_.indexOf(this.check_[i]) != -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// No intersection.
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to be called when this connection's compatible types have changed.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.onCheckChanged_ = function() {
|
||||
// The new value type may not be compatible with the existing connection.
|
||||
if (this.isConnected() && !this.checkType_(this.targetConnection)) {
|
||||
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
|
||||
child.unplug();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change a connection's compatibility.
|
||||
* @param {*} check Compatible value type or list of value types.
|
||||
* Null if all types are compatible.
|
||||
* @return {!Blockly.Connection} The connection being modified
|
||||
* (to allow chaining).
|
||||
*/
|
||||
Blockly.Connection.prototype.setCheck = function(check) {
|
||||
if (check) {
|
||||
// Ensure that check is in an array.
|
||||
if (!goog.isArray(check)) {
|
||||
check = [check];
|
||||
}
|
||||
this.check_ = check;
|
||||
this.onCheckChanged_();
|
||||
} else {
|
||||
this.check_ = null;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a shape enum for this connection.
|
||||
* Used in scratch-blocks to draw unoccupied inputs.
|
||||
* @return {number} Enum representing shape.
|
||||
*/
|
||||
Blockly.Connection.prototype.getOutputShape = function() {
|
||||
if (!this.check_) return Blockly.OUTPUT_SHAPE_ROUND;
|
||||
if (this.check_.indexOf('Boolean') !== -1) {
|
||||
return Blockly.OUTPUT_SHAPE_HEXAGONAL;
|
||||
}
|
||||
if (this.check_.indexOf('Number') !== -1) {
|
||||
return Blockly.OUTPUT_SHAPE_ROUND;
|
||||
}
|
||||
if (this.check_.indexOf('String') !== -1) {
|
||||
return Blockly.OUTPUT_SHAPE_SQUARE;
|
||||
}
|
||||
return Blockly.OUTPUT_SHAPE_ROUND;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change a connection's shadow block.
|
||||
* @param {Element} shadow DOM representation of a block or null.
|
||||
*/
|
||||
Blockly.Connection.prototype.setShadowDom = function(shadow) {
|
||||
this.shadowDom_ = shadow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a connection's shadow block.
|
||||
* @return {Element} shadow DOM representation of a block or null.
|
||||
*/
|
||||
Blockly.Connection.prototype.getShadowDom = function() {
|
||||
return this.shadowDom_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all nearby compatible connections to this connection.
|
||||
* Type checking does not apply, since this function is used for bumping.
|
||||
*
|
||||
* Headless configurations (the default) do not have neighboring connection,
|
||||
* and always return an empty list (the default).
|
||||
* {@link Blockly.RenderedConnection} overrides this behavior with a list
|
||||
* computed from the rendered positioning.
|
||||
* @param {number} maxLimit The maximum radius to another connection.
|
||||
* @return {!Array.<!Blockly.Connection>} List of connections.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Connection.prototype.neighbours_ = function(/* maxLimit */) {
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* This method returns a string describing this Connection in developer terms
|
||||
* (English only). Intended to on be used in console logs and errors.
|
||||
* @return {string} The description.
|
||||
*/
|
||||
Blockly.Connection.prototype.toString = function() {
|
||||
var msg;
|
||||
var block = this.sourceBlock_;
|
||||
if (!block) {
|
||||
return 'Orphan Connection';
|
||||
} else if (block.outputConnection == this) {
|
||||
msg = 'Output Connection of ';
|
||||
} else if (block.previousConnection == this) {
|
||||
msg = 'Previous Connection of ';
|
||||
} else if (block.nextConnection == this) {
|
||||
msg = 'Next Connection of ';
|
||||
} else {
|
||||
var parentInput = goog.array.find(block.inputList, function(input) {
|
||||
return input.connection == this;
|
||||
}, this);
|
||||
if (parentInput) {
|
||||
msg = 'Input "' + parentInput.name + '" connection on ';
|
||||
} else {
|
||||
console.warn('Connection not actually connected to sourceBlock_');
|
||||
return 'Orphan Connection';
|
||||
}
|
||||
}
|
||||
return msg + block.toDevString();
|
||||
};
|
||||
300
scratch-blocks/core/connection_db.js
Normal file
300
scratch-blocks/core/connection_db.js
Normal file
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Components for managing connections between blocks.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.ConnectionDB');
|
||||
|
||||
goog.require('Blockly.Connection');
|
||||
|
||||
|
||||
/**
|
||||
* Database of connections.
|
||||
* Connections are stored in order of their vertical component. This way
|
||||
* connections in an area may be looked up quickly using a binary search.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.ConnectionDB = function() {
|
||||
/**
|
||||
* Array of connections sorted by y coordinate.
|
||||
* @type {!Array.<!Blockly.Connection>}
|
||||
* @private
|
||||
*/
|
||||
this.connections_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a connection to the database. Must not already exist in DB.
|
||||
* @param {!Blockly.Connection} connection The connection to be added.
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.addConnection = function(connection) {
|
||||
if (connection.inDB_) {
|
||||
throw Error('Connection already in database.');
|
||||
}
|
||||
if (connection.getSourceBlock().isInFlyout) {
|
||||
// Don't bother maintaining a database of connections in a flyout.
|
||||
return;
|
||||
}
|
||||
var position = this.findPositionForConnection_(connection);
|
||||
this.connections_.splice(position, 0, connection);
|
||||
connection.inDB_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the given connection.
|
||||
* Starts by doing a binary search to find the approximate location, then
|
||||
* linearly searches nearby for the exact connection.
|
||||
* @param {!Blockly.Connection} conn The connection to find.
|
||||
* @return {number} The index of the connection, or -1 if the connection was
|
||||
* not found.
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.findConnection = function(conn) {
|
||||
if (!this.connections_.length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
var bestGuess = this.findPositionForConnection_(conn);
|
||||
if (bestGuess >= this.connections_.length) {
|
||||
// Not in list
|
||||
return -1;
|
||||
}
|
||||
|
||||
var yPos = conn.y_;
|
||||
// Walk forward and back on the y axis looking for the connection.
|
||||
var pointerMin = bestGuess;
|
||||
var pointerMax = bestGuess;
|
||||
while (pointerMin >= 0 && this.connections_[pointerMin].y_ == yPos) {
|
||||
if (this.connections_[pointerMin] == conn) {
|
||||
return pointerMin;
|
||||
}
|
||||
pointerMin--;
|
||||
}
|
||||
|
||||
while (pointerMax < this.connections_.length &&
|
||||
this.connections_[pointerMax].y_ == yPos) {
|
||||
if (this.connections_[pointerMax] == conn) {
|
||||
return pointerMax;
|
||||
}
|
||||
pointerMax++;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a candidate position for inserting this connection into the list.
|
||||
* This will be in the correct y order but makes no guarantees about ordering in
|
||||
* the x axis.
|
||||
* @param {!Blockly.Connection} connection The connection to insert.
|
||||
* @return {number} The candidate index.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.findPositionForConnection_ = function(
|
||||
connection) {
|
||||
if (!this.connections_.length) {
|
||||
return 0;
|
||||
}
|
||||
var pointerMin = 0;
|
||||
var pointerMax = this.connections_.length;
|
||||
while (pointerMin < pointerMax) {
|
||||
var pointerMid = Math.floor((pointerMin + pointerMax) / 2);
|
||||
if (this.connections_[pointerMid].y_ < connection.y_) {
|
||||
pointerMin = pointerMid + 1;
|
||||
} else if (this.connections_[pointerMid].y_ > connection.y_) {
|
||||
pointerMax = pointerMid;
|
||||
} else {
|
||||
pointerMin = pointerMid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return pointerMin;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a connection from the database. Must already exist in DB.
|
||||
* @param {!Blockly.Connection} connection The connection to be removed.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.removeConnection_ = function(connection) {
|
||||
if (!connection.inDB_) {
|
||||
throw Error('Connection not in database.');
|
||||
}
|
||||
var removalIndex = this.findConnection(connection);
|
||||
if (removalIndex == -1) {
|
||||
throw Error('Unable to find connection in connectionDB.');
|
||||
}
|
||||
connection.inDB_ = false;
|
||||
this.connections_.splice(removalIndex, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all nearby connections to the given connection.
|
||||
* Type checking does not apply, since this function is used for bumping.
|
||||
* @param {!Blockly.Connection} connection The connection whose neighbours
|
||||
* should be returned.
|
||||
* @param {number} maxRadius The maximum radius to another connection.
|
||||
* @return {!Array.<Blockly.Connection>} List of connections.
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.getNeighbours = function(connection, maxRadius) {
|
||||
var db = this.connections_;
|
||||
var currentX = connection.x_;
|
||||
var currentY = connection.y_;
|
||||
|
||||
// Binary search to find the closest y location.
|
||||
var pointerMin = 0;
|
||||
var pointerMax = db.length - 2;
|
||||
var pointerMid = pointerMax;
|
||||
while (pointerMin < pointerMid) {
|
||||
if (db[pointerMid].y_ < currentY) {
|
||||
pointerMin = pointerMid;
|
||||
} else {
|
||||
pointerMax = pointerMid;
|
||||
}
|
||||
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
|
||||
}
|
||||
|
||||
var neighbours = [];
|
||||
/**
|
||||
* Computes if the current connection is within the allowed radius of another
|
||||
* connection.
|
||||
* This function is a closure and has access to outside variables.
|
||||
* @param {number} yIndex The other connection's index in the database.
|
||||
* @return {boolean} True if the current connection's vertical distance from
|
||||
* the other connection is less than the allowed radius.
|
||||
*/
|
||||
function checkConnection_(yIndex) {
|
||||
var dx = currentX - db[yIndex].x_;
|
||||
var dy = currentY - db[yIndex].y_;
|
||||
var r = Math.sqrt(dx * dx + dy * dy);
|
||||
if (r <= maxRadius) {
|
||||
neighbours.push(db[yIndex]);
|
||||
}
|
||||
return dy < maxRadius;
|
||||
}
|
||||
|
||||
// Walk forward and back on the y axis looking for the closest x,y point.
|
||||
pointerMin = pointerMid;
|
||||
pointerMax = pointerMid;
|
||||
if (db.length) {
|
||||
while (pointerMin >= 0 && checkConnection_(pointerMin)) {
|
||||
pointerMin--;
|
||||
}
|
||||
do {
|
||||
pointerMax++;
|
||||
} while (pointerMax < db.length && checkConnection_(pointerMax));
|
||||
}
|
||||
|
||||
return neighbours;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Is the candidate connection close to the reference connection.
|
||||
* Extremely fast; only looks at Y distance.
|
||||
* @param {number} index Index in database of candidate connection.
|
||||
* @param {number} baseY Reference connection's Y value.
|
||||
* @param {number} maxRadius The maximum radius to another connection.
|
||||
* @return {boolean} True if connection is in range.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.isInYRange_ = function(index, baseY, maxRadius) {
|
||||
return (Math.abs(this.connections_[index].y_ - baseY) <= maxRadius);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the closest compatible connection to this connection.
|
||||
* @param {!Blockly.Connection} conn The connection searching for a compatible
|
||||
* mate.
|
||||
* @param {number} maxRadius The maximum radius to another connection.
|
||||
* @param {!goog.math.Coordinate} dxy Offset between this connection's location
|
||||
* in the database and the current location (as a result of dragging).
|
||||
* @return {!{connection: ?Blockly.Connection, radius: number}} Contains two
|
||||
* properties:' connection' which is either another connection or null,
|
||||
* and 'radius' which is the distance.
|
||||
*/
|
||||
Blockly.ConnectionDB.prototype.searchForClosest = function(conn, maxRadius,
|
||||
dxy) {
|
||||
// Don't bother.
|
||||
if (!this.connections_.length) {
|
||||
return {connection: null, radius: maxRadius};
|
||||
}
|
||||
|
||||
// Stash the values of x and y from before the drag.
|
||||
var baseY = conn.y_;
|
||||
var baseX = conn.x_;
|
||||
|
||||
conn.x_ = baseX + dxy.x;
|
||||
conn.y_ = baseY + dxy.y;
|
||||
|
||||
// findPositionForConnection finds an index for insertion, which is always
|
||||
// after any block with the same y index. We want to search both forward
|
||||
// and back, so search on both sides of the index.
|
||||
var closestIndex = this.findPositionForConnection_(conn);
|
||||
|
||||
var bestConnection = null;
|
||||
var bestRadius = maxRadius;
|
||||
var temp;
|
||||
|
||||
// Walk forward and back on the y axis looking for the closest x,y point.
|
||||
var pointerMin = closestIndex - 1;
|
||||
while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y_, maxRadius)) {
|
||||
temp = this.connections_[pointerMin];
|
||||
if (conn.isConnectionAllowed(temp, bestRadius)) {
|
||||
bestConnection = temp;
|
||||
bestRadius = temp.distanceFrom(conn);
|
||||
}
|
||||
pointerMin--;
|
||||
}
|
||||
|
||||
var pointerMax = closestIndex;
|
||||
while (pointerMax < this.connections_.length &&
|
||||
this.isInYRange_(pointerMax, conn.y_, maxRadius)) {
|
||||
temp = this.connections_[pointerMax];
|
||||
if (conn.isConnectionAllowed(temp, bestRadius)) {
|
||||
bestConnection = temp;
|
||||
bestRadius = temp.distanceFrom(conn);
|
||||
}
|
||||
pointerMax++;
|
||||
}
|
||||
|
||||
// Reset the values of x and y.
|
||||
conn.x_ = baseX;
|
||||
conn.y_ = baseY;
|
||||
|
||||
// If there were no valid connections, bestConnection will be null.
|
||||
return {connection: bestConnection, radius: bestRadius};
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize a set of connection DBs for a specified workspace.
|
||||
* @param {!Blockly.Workspace} workspace The workspace this DB is for.
|
||||
*/
|
||||
Blockly.ConnectionDB.init = function(workspace) {
|
||||
// Create four databases, one for each connection type.
|
||||
var dbList = [];
|
||||
dbList[Blockly.INPUT_VALUE] = new Blockly.ConnectionDB();
|
||||
dbList[Blockly.OUTPUT_VALUE] = new Blockly.ConnectionDB();
|
||||
dbList[Blockly.NEXT_STATEMENT] = new Blockly.ConnectionDB();
|
||||
dbList[Blockly.PREVIOUS_STATEMENT] = new Blockly.ConnectionDB();
|
||||
workspace.connectionDBList = dbList;
|
||||
};
|
||||
408
scratch-blocks/core/constants.js
Normal file
408
scratch-blocks/core/constants.js
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Blockly constants.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.constants');
|
||||
|
||||
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag starts.
|
||||
*/
|
||||
Blockly.DRAG_RADIUS = 3;
|
||||
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag/scroll starts from the
|
||||
* flyout. Because the drag-intention is determined when this is reached, it is
|
||||
* larger than Blockly.DRAG_RADIUS so that the drag-direction is clearer.
|
||||
*/
|
||||
Blockly.FLYOUT_DRAG_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together.
|
||||
*/
|
||||
Blockly.SNAP_RADIUS = 48;
|
||||
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together,
|
||||
* when a connection is already highlighted.
|
||||
*/
|
||||
Blockly.CONNECTING_SNAP_RADIUS = 68;
|
||||
|
||||
/**
|
||||
* How much to prefer staying connected to the current connection over moving to
|
||||
* a new connection. The current previewed connection is considered to be this
|
||||
* much closer to the matching connection on the block than it actually is.
|
||||
*/
|
||||
Blockly.CURRENT_CONNECTION_PREFERENCE = 20;
|
||||
|
||||
/**
|
||||
* Delay in ms between trigger and bumping unconnected block out of alignment.
|
||||
*/
|
||||
Blockly.BUMP_DELAY = 0;
|
||||
|
||||
/**
|
||||
* Number of characters to truncate a collapsed block to.
|
||||
*/
|
||||
Blockly.COLLAPSE_CHARS = 30;
|
||||
|
||||
/**
|
||||
* Length in ms for a touch to become a long press.
|
||||
*/
|
||||
Blockly.LONGPRESS = 750;
|
||||
|
||||
/**
|
||||
* Distance to scroll when a mouse wheel event is received and its delta mode
|
||||
* is line (0x1) instead of pixel (0x0). In these cases, a single "scroll" has
|
||||
* a delta of 1, which makes the workspace scroll very slowly (just one pixel).
|
||||
* To compensate, that delta is multiplied by this value.
|
||||
* @const
|
||||
* @package
|
||||
*/
|
||||
Blockly.LINE_SCROLL_MULTIPLIER = 15;
|
||||
|
||||
/**
|
||||
* Prevent a sound from playing if another sound preceded it within this many
|
||||
* milliseconds.
|
||||
*/
|
||||
Blockly.SOUND_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* When dragging a block out of a stack, split the stack in two (true), or drag
|
||||
* out the block healing the stack (false).
|
||||
*/
|
||||
Blockly.DRAG_STACK = true;
|
||||
|
||||
/**
|
||||
* The richness of block colours, regardless of the hue.
|
||||
* Must be in the range of 0 (inclusive) to 1 (exclusive).
|
||||
*/
|
||||
Blockly.HSV_SATURATION = 0.45;
|
||||
|
||||
/**
|
||||
* The intensity of block colours, regardless of the hue.
|
||||
* Must be in the range of 0 (inclusive) to 1 (exclusive).
|
||||
*/
|
||||
Blockly.HSV_VALUE = 0.65;
|
||||
|
||||
/**
|
||||
* Sprited icons and images.
|
||||
*/
|
||||
Blockly.SPRITE = {
|
||||
width: 96,
|
||||
height: 124,
|
||||
url: 'sprites.png'
|
||||
};
|
||||
|
||||
// Constants below this point are not intended to be changed.
|
||||
|
||||
/**
|
||||
* Required name space for SVG elements.
|
||||
* @const
|
||||
*/
|
||||
Blockly.SVG_NS = 'http://www.w3.org/2000/svg';
|
||||
|
||||
/**
|
||||
* Required name space for HTML elements.
|
||||
* @const
|
||||
*/
|
||||
Blockly.HTML_NS = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
/**
|
||||
* ENUM for a right-facing value input. E.g. 'set item to' or 'return'.
|
||||
* @const
|
||||
*/
|
||||
Blockly.INPUT_VALUE = 1;
|
||||
|
||||
/**
|
||||
* ENUM for a left-facing value output. E.g. 'random fraction'.
|
||||
* @const
|
||||
*/
|
||||
Blockly.OUTPUT_VALUE = 2;
|
||||
|
||||
/**
|
||||
* ENUM for a down-facing block stack. E.g. 'if-do' or 'else'.
|
||||
* @const
|
||||
*/
|
||||
Blockly.NEXT_STATEMENT = 3;
|
||||
|
||||
/**
|
||||
* ENUM for an up-facing block stack. E.g. 'break out of loop'.
|
||||
* @const
|
||||
*/
|
||||
Blockly.PREVIOUS_STATEMENT = 4;
|
||||
|
||||
/**
|
||||
* ENUM for an dummy input. Used to add field(s) with no input.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DUMMY_INPUT = 5;
|
||||
|
||||
/**
|
||||
* ENUM for left alignment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.ALIGN_LEFT = -1;
|
||||
|
||||
/**
|
||||
* ENUM for centre alignment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.ALIGN_CENTRE = 0;
|
||||
|
||||
/**
|
||||
* ENUM for right alignment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.ALIGN_RIGHT = 1;
|
||||
|
||||
/**
|
||||
* ENUM for no drag operation.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DRAG_NONE = 0;
|
||||
|
||||
/**
|
||||
* ENUM for inside the sticky DRAG_RADIUS.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DRAG_STICKY = 1;
|
||||
|
||||
/**
|
||||
* ENUM for inside the non-sticky DRAG_RADIUS, for differentiating between
|
||||
* clicks and drags.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DRAG_BEGIN = 1;
|
||||
|
||||
/**
|
||||
* ENUM for freely draggable (outside the DRAG_RADIUS, if one applies).
|
||||
* @const
|
||||
*/
|
||||
Blockly.DRAG_FREE = 2;
|
||||
|
||||
/**
|
||||
* Lookup table for determining the opposite type of a connection.
|
||||
* @const
|
||||
*/
|
||||
Blockly.OPPOSITE_TYPE = [];
|
||||
Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE] = Blockly.OUTPUT_VALUE;
|
||||
Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE] = Blockly.INPUT_VALUE;
|
||||
Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT] = Blockly.PREVIOUS_STATEMENT;
|
||||
Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT] = Blockly.NEXT_STATEMENT;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at top of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_TOP = 0;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at bottom of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_BOTTOM = 1;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at left of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_LEFT = 2;
|
||||
|
||||
/**
|
||||
* ENUM for toolbox and flyout at right of screen.
|
||||
* @const
|
||||
*/
|
||||
Blockly.TOOLBOX_AT_RIGHT = 3;
|
||||
|
||||
/**
|
||||
* ENUM for output shape: hexagonal (booleans/predicates).
|
||||
* @const
|
||||
*/
|
||||
Blockly.OUTPUT_SHAPE_HEXAGONAL = 1;
|
||||
|
||||
/**
|
||||
* ENUM for output shape: rounded (numbers).
|
||||
* @const
|
||||
*/
|
||||
Blockly.OUTPUT_SHAPE_ROUND = 2;
|
||||
|
||||
/**
|
||||
* ENUM for output shape: squared (any/all values; strings).
|
||||
* @const
|
||||
*/
|
||||
Blockly.OUTPUT_SHAPE_SQUARE = 3;
|
||||
|
||||
/**
|
||||
* ENUM for categories.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Categories = {
|
||||
"motion": "motion",
|
||||
"looks": "looks",
|
||||
"sound": "sounds",
|
||||
"pen": "pen",
|
||||
"data": "data",
|
||||
"dataLists": "data-lists",
|
||||
"event": "events",
|
||||
"control": "control",
|
||||
"sensing": "sensing",
|
||||
"operators": "operators",
|
||||
"more": "more"
|
||||
};
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is not in any delete areas.
|
||||
* Null for backwards compatibility reasons.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_NONE = null;
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is in the delete area of the trash can.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_TRASH = 1;
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is in the delete area of the toolbox or
|
||||
* flyout.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_TOOLBOX = 2;
|
||||
|
||||
/**
|
||||
* String for use in the "custom" attribute of a category in toolbox xml.
|
||||
* This string indicates that the category should be dynamically populated with
|
||||
* variable blocks.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.VARIABLE_CATEGORY_NAME = 'VARIABLE';
|
||||
|
||||
/**
|
||||
* String for use in the "custom" attribute of a category in toolbox xml.
|
||||
* This string indicates that the category should be dynamically populated with
|
||||
* procedure blocks.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.PROCEDURE_CATEGORY_NAME = 'PROCEDURE';
|
||||
|
||||
/**
|
||||
* String for use in the dropdown created in field_variable.
|
||||
* This string indicates that this option in the dropdown is 'Rename
|
||||
* variable...' and if selected, should trigger the prompt to rename a variable.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.RENAME_VARIABLE_ID = 'RENAME_VARIABLE_ID';
|
||||
|
||||
/**
|
||||
* String for use in the dropdown created in field_variable.
|
||||
* This string indicates that this option in the dropdown is 'Delete the "%1"
|
||||
* variable' and if selected, should trigger the prompt to delete a variable.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.DELETE_VARIABLE_ID = 'DELETE_VARIABLE_ID';
|
||||
|
||||
/**
|
||||
* String for use in the dropdown created in field_variable,
|
||||
* specifically for broadcast messages.
|
||||
* This string indicates that this option in the dropdown is 'New message...'
|
||||
* and if selected, should trigger the prompt to create a new message.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.NEW_BROADCAST_MESSAGE_ID = 'NEW_BROADCAST_MESSAGE_ID';
|
||||
|
||||
/**
|
||||
* String representing the variable type of broadcast message blocks.
|
||||
* This string, for use in differentiating between types of variables,
|
||||
* indicates that the current variable is a broadcast message.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE = 'broadcast_msg';
|
||||
|
||||
/**
|
||||
* String representing the variable type of list blocks.
|
||||
* This string, for use in differentiating between types of variables,
|
||||
* indicates that the current variable is a list.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.LIST_VARIABLE_TYPE = 'list';
|
||||
|
||||
// TODO (#1251) Replace '' below with 'scalar', and start using this constant
|
||||
// everywhere.
|
||||
/**
|
||||
* String representing the variable type of scalar variables.
|
||||
* This string, for use in differentiating between types of variables,
|
||||
* indicates that the current variable is a scalar variable.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.SCALAR_VARIABLE_TYPE = '';
|
||||
|
||||
/**
|
||||
* The type of all procedure definition blocks.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE = 'procedures_definition';
|
||||
|
||||
/**
|
||||
* The type of all procedure prototype blocks.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE = 'procedures_prototype';
|
||||
|
||||
/**
|
||||
* The type of all procedure call blocks.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.PROCEDURES_CALL_BLOCK_TYPE = 'procedures_call';
|
||||
|
||||
/**
|
||||
* Enum for procedure call statements.
|
||||
*/
|
||||
Blockly.PROCEDURES_CALL_TYPE_STATEMENT = 0;
|
||||
|
||||
/**
|
||||
* Enum for procedure call round reporters.
|
||||
*/
|
||||
Blockly.PROCEDURES_CALL_TYPE_REPORTER = 1;
|
||||
|
||||
/**
|
||||
* Enum for procedure call booleans.
|
||||
*/
|
||||
Blockly.PROCEDURES_CALL_TYPE_BOOLEAN = 2;
|
||||
|
||||
/**
|
||||
* The type of all procedure return blocks.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.PROCEDURES_RETURN_BLOCK_TYPE = 'procedures_return';
|
||||
|
||||
/**
|
||||
* ENUM for flyout status button states.
|
||||
* @const
|
||||
*/
|
||||
Blockly.StatusButtonState = {
|
||||
"READY": "ready",
|
||||
"NOT_READY": "not ready",
|
||||
};
|
||||
534
scratch-blocks/core/contextmenu.js
Normal file
534
scratch-blocks/core/contextmenu.js
Normal file
@@ -0,0 +1,534 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Functionality for the right-click context menus.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @name Blockly.ContextMenu
|
||||
* @namespace
|
||||
*/
|
||||
goog.provide('Blockly.ContextMenu');
|
||||
|
||||
goog.require('Blockly.Events.BlockCreate');
|
||||
goog.require('Blockly.scratchBlocksUtils');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.utils.uiMenu');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.Menu');
|
||||
goog.require('goog.ui.MenuItem');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Which block is the context menu attached to?
|
||||
* @type {Blockly.Block}
|
||||
*/
|
||||
Blockly.ContextMenu.currentBlock = null;
|
||||
|
||||
/**
|
||||
* Opaque data that can be passed to unbindEvent_.
|
||||
* @type {Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.eventWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Construct the menu based on the list of options and show the menu.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @param {!Array.<!Object>} options Array of menu options.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
*/
|
||||
Blockly.ContextMenu.show = function(e, options, rtl) {
|
||||
Blockly.WidgetDiv.show(Blockly.ContextMenu, rtl, null);
|
||||
if (!options.length) {
|
||||
Blockly.ContextMenu.hide();
|
||||
return;
|
||||
}
|
||||
var menu = Blockly.ContextMenu.populate_(options, rtl);
|
||||
|
||||
goog.events.listen(
|
||||
menu, goog.ui.Component.EventType.ACTION, Blockly.ContextMenu.hide);
|
||||
|
||||
Blockly.ContextMenu.position_(menu, e, rtl);
|
||||
// 1ms delay is required for focusing on context menus because some other
|
||||
// mouse event is still waiting in the queue and clears focus.
|
||||
setTimeout(function() {menu.getElement().focus();}, 1);
|
||||
Blockly.ContextMenu.currentBlock = null; // May be set by Blockly.Block.
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the context menu object and populate it with the given options.
|
||||
* @param {!Array.<!Object>} options Array of menu options.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @return {!goog.ui.Menu} The menu that will be shown on right click.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.populate_ = function(options, rtl) {
|
||||
/* Here's what one option object looks like:
|
||||
{text: 'Make It So',
|
||||
enabled: true,
|
||||
callback: Blockly.MakeItSo}
|
||||
*/
|
||||
var menu = new goog.ui.Menu();
|
||||
menu.setRightToLeft(rtl);
|
||||
|
||||
// Sometimes the context menu can be created such that the mouse is hovering over an item in the menu
|
||||
// When this happens, a contextmenu event is immediately sent to that item
|
||||
// Obviously we don't want that to trigger an item to be selected.
|
||||
var acceptContextMenuEvents = false;
|
||||
setTimeout(function() {
|
||||
acceptContextMenuEvents = true;
|
||||
});
|
||||
|
||||
for (var i = 0, option; option = options[i]; i++) {
|
||||
var menuItem = new goog.ui.MenuItem(option.text);
|
||||
menuItem.setRightToLeft(rtl);
|
||||
menu.addChild(menuItem, true);
|
||||
menuItem.setEnabled(option.enabled);
|
||||
if (option.enabled) {
|
||||
goog.events.listen(
|
||||
menuItem, goog.ui.Component.EventType.ACTION, option.callback);
|
||||
menuItem.handleContextMenu = function(/* e */) {
|
||||
if (!acceptContextMenuEvents) {
|
||||
return;
|
||||
}
|
||||
// Right-clicking on menu option should count as a click.
|
||||
goog.events.dispatchEvent(this, goog.ui.Component.EventType.ACTION);
|
||||
};
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the menu to the page and position it correctly.
|
||||
* @param {!goog.ui.Menu} menu The menu to add and position.
|
||||
* @param {!Event} e Mouse event for the right click that is making the context
|
||||
* menu appear.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.position_ = function(menu, e, rtl) {
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
var viewportBBox = Blockly.utils.getViewportBBox();
|
||||
// This one is just a point, but we'll pretend that it's a rect so we can use
|
||||
// some helper functions.
|
||||
var anchorBBox = {
|
||||
top: e.clientY + viewportBBox.top,
|
||||
bottom: e.clientY + viewportBBox.top,
|
||||
left: e.clientX + viewportBBox.left,
|
||||
right: e.clientX + viewportBBox.left
|
||||
};
|
||||
|
||||
Blockly.ContextMenu.createWidget_(menu);
|
||||
var menuSize = Blockly.utils.uiMenu.getSize(menu);
|
||||
|
||||
if (rtl) {
|
||||
Blockly.utils.uiMenu.adjustBBoxesForRTL(viewportBBox, anchorBBox, menuSize);
|
||||
}
|
||||
|
||||
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, menuSize, rtl);
|
||||
// Calling menuDom.focus() has to wait until after the menu has been placed
|
||||
// correctly. Otherwise it will cause a page scroll to get the misplaced menu
|
||||
// in view. See issue #1329.
|
||||
menu.getElement().focus();
|
||||
|
||||
// https://github.com/LLK/scratch-blocks/pull/2834
|
||||
menu.setVisible(true, true, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create and render the menu widget inside Blockly's widget div.
|
||||
* @param {!goog.ui.Menu} menu The menu to add to the widget div.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.createWidget_ = function(menu) {
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
menu.render(div);
|
||||
var menuDom = menu.getElement();
|
||||
Blockly.utils.addClass(menuDom, 'blocklyContextMenu');
|
||||
// Prevent system context menu when right-clicking a Blockly context menu.
|
||||
Blockly.bindEventWithChecks_(
|
||||
menuDom, 'contextmenu', null, Blockly.utils.noEvent);
|
||||
// Enable autofocus after the initial render to avoid issue #1329.
|
||||
menu.setAllowAutoFocus(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the context menu.
|
||||
*/
|
||||
Blockly.ContextMenu.hide = function() {
|
||||
Blockly.WidgetDiv.hideIfOwner(Blockly.ContextMenu);
|
||||
Blockly.ContextMenu.currentBlock = null;
|
||||
if (Blockly.ContextMenu.eventWrapper_) {
|
||||
Blockly.unbindEvent_(Blockly.ContextMenu.eventWrapper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a callback function that creates and configures a block,
|
||||
* then places the new block next to the original.
|
||||
* @param {!Blockly.Block} block Original block.
|
||||
* @param {!Element} xml XML representation of new block.
|
||||
* @return {!Function} Function that creates a block.
|
||||
*/
|
||||
Blockly.ContextMenu.callbackFactory = function(block, xml) {
|
||||
return function() {
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
var newBlock = Blockly.Xml.domToBlock(xml, block.workspace);
|
||||
// Move the new block next to the old block.
|
||||
var xy = block.getRelativeToSurfaceXY();
|
||||
if (block.RTL) {
|
||||
xy.x -= Blockly.SNAP_RADIUS;
|
||||
} else {
|
||||
xy.x += Blockly.SNAP_RADIUS;
|
||||
}
|
||||
xy.y += Blockly.SNAP_RADIUS * 2;
|
||||
newBlock.moveBy(xy.x, xy.y);
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
if (Blockly.Events.isEnabled() && !newBlock.isShadow()) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock));
|
||||
}
|
||||
newBlock.select();
|
||||
};
|
||||
};
|
||||
|
||||
// Helper functions for creating context menu options.
|
||||
|
||||
/**
|
||||
* Make a context menu option for deleting the current block.
|
||||
* @param {!Blockly.BlockSvg} block The block where the right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.blockDeleteOption = function(block) {
|
||||
// Option to delete this block but not blocks lower in the stack.
|
||||
// Count the number of blocks that are nested in this block,
|
||||
// ignoring shadows and without ordering.
|
||||
var descendantCount = block.getDescendants(false, true).length;
|
||||
var nextBlock = block.getNextBlock();
|
||||
if (nextBlock) {
|
||||
// Blocks in the current stack would survive this block's deletion.
|
||||
descendantCount -= nextBlock.getDescendants(false, true).length;
|
||||
}
|
||||
var deleteOption = {
|
||||
text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK :
|
||||
Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)),
|
||||
enabled: true,
|
||||
callback: function() {
|
||||
Blockly.Events.setGroup(true);
|
||||
block.dispose(true, true);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
return deleteOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for showing help for the current block.
|
||||
* @param {!Blockly.BlockSvg} block The block where the right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.blockHelpOption = function(block) {
|
||||
var url = goog.isFunction(block.helpUrl) ? block.helpUrl() : block.helpUrl;
|
||||
var helpOption = {
|
||||
enabled: !!url,
|
||||
text: Blockly.Msg.HELP,
|
||||
callback: function() {
|
||||
block.showHelp_();
|
||||
}
|
||||
};
|
||||
return helpOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for duplicating the current block.
|
||||
* @param {!Blockly.BlockSvg} block The block where the right-click originated.
|
||||
* @param {!Event} event Event that caused the context menu to open.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.blockDuplicateOption = function(block, event) {
|
||||
var duplicateOption = {
|
||||
text: Blockly.Msg.DUPLICATE,
|
||||
enabled: true,
|
||||
callback:
|
||||
Blockly.scratchBlocksUtils.duplicateAndDragCallback(block, event)
|
||||
};
|
||||
return duplicateOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for adding or removing comments on the current
|
||||
* block.
|
||||
* @param {!Blockly.BlockSvg} block The block where the right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.blockCommentOption = function(block) {
|
||||
var commentOption = {
|
||||
enabled: !goog.userAgent.IE
|
||||
};
|
||||
// If there's already a comment, add an option to delete it.
|
||||
if (block.comment) {
|
||||
commentOption.text = Blockly.Msg.REMOVE_COMMENT;
|
||||
commentOption.callback = function() {
|
||||
block.setCommentText(null);
|
||||
};
|
||||
} else {
|
||||
// If there's no comment, add an option to create a comment.
|
||||
commentOption.text = Blockly.Msg.ADD_COMMENT;
|
||||
commentOption.callback = function() {
|
||||
block.setCommentText('');
|
||||
block.comment.focus();
|
||||
};
|
||||
}
|
||||
return commentOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for undoing the most recent action on the
|
||||
* workspace.
|
||||
* @param {!Blockly.WorkspaceSvg} ws The workspace where the right-click
|
||||
* originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.wsUndoOption = function(ws) {
|
||||
return {
|
||||
text: Blockly.Msg.UNDO,
|
||||
enabled: ws.hasUndoStack(),
|
||||
callback: ws.undo.bind(ws, false)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for redoing the most recent action on the
|
||||
* workspace.
|
||||
* @param {!Blockly.WorkspaceSvg} ws The workspace where the right-click
|
||||
* originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.wsRedoOption = function(ws) {
|
||||
return {
|
||||
text: Blockly.Msg.REDO,
|
||||
enabled: ws.hasRedoStack(),
|
||||
callback: ws.undo.bind(ws, true)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for cleaning up blocks on the workspace, by
|
||||
* aligning them vertically.
|
||||
* @param {!Blockly.WorkspaceSvg} ws The workspace where the right-click
|
||||
* originated.
|
||||
* @param {number} numTopBlocks The number of top blocks on the workspace.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.wsCleanupOption = function(ws, numTopBlocks) {
|
||||
return {
|
||||
text: Blockly.Msg.CLEAN_UP,
|
||||
enabled: numTopBlocks > 1,
|
||||
callback: ws.cleanUp.bind(ws, true)
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for toggling delete state on blocks on the workspace, to be
|
||||
* called from a right-click menu.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} topBlocks The list of top blocks on the
|
||||
* the workspace.
|
||||
* @param {boolean} shouldCollapse True if the blocks should be collapsed, false
|
||||
* if they should be expanded.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenu.toggleCollapseFn_ = function(topBlocks, shouldCollapse) {
|
||||
// Add a little animation to collapsing and expanding.
|
||||
var DELAY = 10;
|
||||
var ms = 0;
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
var block = topBlocks[i];
|
||||
while (block) {
|
||||
setTimeout(block.setCollapsed.bind(block, shouldCollapse), ms);
|
||||
block = block.getNextBlock();
|
||||
ms += DELAY;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for collapsing all block stacks on the workspace.
|
||||
* @param {boolean} hasExpandedBlocks Whether there are any non-collapsed blocks
|
||||
* on the workspace.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} topBlocks The list of top blocks on the
|
||||
* the workspace.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.wsCollapseOption = function(hasExpandedBlocks, topBlocks) {
|
||||
return {
|
||||
enabled: hasExpandedBlocks,
|
||||
text: Blockly.Msg.COLLAPSE_ALL,
|
||||
callback: function() {
|
||||
Blockly.ContextMenu.toggleCollapseFn_(topBlocks, true);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for expanding all block stacks on the workspace.
|
||||
* @param {boolean} hasCollapsedBlocks Whether there are any collapsed blocks
|
||||
* on the workspace.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} topBlocks The list of top blocks on the
|
||||
* the workspace.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.wsExpandOption = function(hasCollapsedBlocks, topBlocks) {
|
||||
return {
|
||||
enabled: hasCollapsedBlocks,
|
||||
text: Blockly.Msg.EXPAND_ALL,
|
||||
callback: function() {
|
||||
Blockly.ContextMenu.toggleCollapseFn_(topBlocks, false);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for deleting the current workspace comment.
|
||||
* @param {!Blockly.WorkspaceCommentSvg} comment The workspace comment where the
|
||||
* right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.commentDeleteOption = function(comment) {
|
||||
var deleteOption = {
|
||||
text: Blockly.Msg.DELETE,
|
||||
enabled: true,
|
||||
callback: function() {
|
||||
Blockly.Events.setGroup(true);
|
||||
comment.dispose(true, true);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
return deleteOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for duplicating the current workspace comment.
|
||||
* @param {!Blockly.WorkspaceCommentSvg} comment The workspace comment where the
|
||||
* right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.commentDuplicateOption = function(comment) {
|
||||
var duplicateOption = {
|
||||
text: Blockly.Msg.DUPLICATE,
|
||||
enabled: true,
|
||||
callback: function() {
|
||||
Blockly.duplicate_(comment);
|
||||
}
|
||||
};
|
||||
return duplicateOption;
|
||||
};
|
||||
|
||||
/**
|
||||
* Make a context menu option for adding a comment on the workspace.
|
||||
* @param {!Blockly.WorkspaceSvg} ws The workspace where the right-click
|
||||
* originated.
|
||||
* @param {!Event} e The right-click mouse event.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenu.workspaceCommentOption = function(ws, e) {
|
||||
// Helper function to create and position a comment correctly based on the
|
||||
// location of the mouse event.
|
||||
var addWsComment = function() {
|
||||
// Disable events while this comment is getting created
|
||||
// so that we can fire a single create event for this comment
|
||||
// at the end (instead of CommentCreate followed by CommentMove,
|
||||
// which results in unexpected undo behavior).
|
||||
var disabled = false;
|
||||
if (Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.disable();
|
||||
disabled = true;
|
||||
}
|
||||
var comment = new Blockly.WorkspaceCommentSvg(
|
||||
ws, '', Blockly.WorkspaceCommentSvg.DEFAULT_SIZE,
|
||||
Blockly.WorkspaceCommentSvg.DEFAULT_SIZE, false);
|
||||
|
||||
var injectionDiv = ws.getInjectionDiv();
|
||||
// Bounding rect coordinates are in client coordinates, meaning that they
|
||||
// are in pixels relative to the upper left corner of the visible browser
|
||||
// window. These coordinates change when you scroll the browser window.
|
||||
var boundingRect = injectionDiv.getBoundingClientRect();
|
||||
|
||||
// The client coordinates offset by the injection div's upper left corner.
|
||||
var clientOffsetPixels = new goog.math.Coordinate(
|
||||
e.clientX - boundingRect.left, e.clientY - boundingRect.top);
|
||||
|
||||
// The offset in pixels between the main workspace's origin and the upper
|
||||
// left corner of the injection div.
|
||||
var mainOffsetPixels = ws.getOriginOffsetInPixels();
|
||||
|
||||
// The position of the new comment in pixels relative to the origin of the
|
||||
// main workspace.
|
||||
var finalOffsetPixels = goog.math.Coordinate.difference(clientOffsetPixels,
|
||||
mainOffsetPixels);
|
||||
|
||||
// The position of the new comment in main workspace coordinates.
|
||||
var finalOffsetMainWs = finalOffsetPixels.scale(1 / ws.scale);
|
||||
|
||||
var commentX = finalOffsetMainWs.x;
|
||||
var commentY = finalOffsetMainWs.y;
|
||||
comment.moveBy(commentX, commentY);
|
||||
if (ws.rendered) {
|
||||
comment.initSvg();
|
||||
comment.render(false);
|
||||
comment.select();
|
||||
}
|
||||
if (disabled) {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
Blockly.WorkspaceComment.fireCreateEvent(comment);
|
||||
};
|
||||
|
||||
var wsCommentOption = {enabled: true};
|
||||
wsCommentOption.text = Blockly.Msg.ADD_COMMENT;
|
||||
wsCommentOption.callback = function() {
|
||||
addWsComment();
|
||||
};
|
||||
return wsCommentOption;
|
||||
};
|
||||
|
||||
// End helper functions for creating context menu options.
|
||||
1356
scratch-blocks/core/css.js
Normal file
1356
scratch-blocks/core/css.js
Normal file
File diff suppressed because it is too large
Load Diff
490
scratch-blocks/core/data_category.js
Normal file
490
scratch-blocks/core/data_category.js
Normal file
@@ -0,0 +1,490 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Data Flyout components including variable and list blocks.
|
||||
* @author marisaleung@google.com (Marisa Leung)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @name Blockly.DataCategory
|
||||
* @namespace
|
||||
**/
|
||||
goog.provide('Blockly.DataCategory');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
goog.require('Blockly.VariableModel');
|
||||
goog.require('Blockly.Variables');
|
||||
goog.require('Blockly.Workspace');
|
||||
|
||||
/**
|
||||
* Construct the blocks required by the flyout for the variable category.
|
||||
* @param {!Blockly.Workspace} workspace The workspace containing variables.
|
||||
* @return {!Array.<!Element>} Array of XML block elements.
|
||||
*/
|
||||
Blockly.DataCategory = function(workspace) {
|
||||
var variableModelList = workspace.getVariablesOfType('');
|
||||
variableModelList.sort(Blockly.VariableModel.compareByName);
|
||||
var xmlList = [];
|
||||
|
||||
Blockly.DataCategory.addCreateButton(xmlList, workspace, 'VARIABLE');
|
||||
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
Blockly.DataCategory.addDataVariable(xmlList, variableModelList[i]);
|
||||
}
|
||||
|
||||
if (variableModelList.length > 0) {
|
||||
xmlList[xmlList.length - 1].setAttribute('gap', 28);
|
||||
var firstVariable = variableModelList[0];
|
||||
|
||||
Blockly.DataCategory.addSetVariableTo(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addChangeVariableBy(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addShowVariable(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addHideVariable(xmlList, firstVariable);
|
||||
}
|
||||
|
||||
// Now add list variables to the flyout
|
||||
Blockly.DataCategory.addCreateButton(xmlList, workspace, 'LIST');
|
||||
variableModelList = workspace.getVariablesOfType(Blockly.LIST_VARIABLE_TYPE);
|
||||
variableModelList.sort(Blockly.VariableModel.compareByName);
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
Blockly.DataCategory.addDataList(xmlList, variableModelList[i]);
|
||||
}
|
||||
|
||||
if (variableModelList.length > 0) {
|
||||
xmlList[xmlList.length - 1].setAttribute('gap', 28);
|
||||
var firstVariable = variableModelList[0];
|
||||
|
||||
Blockly.DataCategory.addAddToList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addSep(xmlList);
|
||||
Blockly.DataCategory.addDeleteOfList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addDeleteAllOfList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addInsertAtList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addReplaceItemOfList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addSep(xmlList);
|
||||
Blockly.DataCategory.addItemOfList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addItemNumberOfList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addLengthOfList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addListContainsItem(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addSep(xmlList);
|
||||
Blockly.DataCategory.addShowList(xmlList, firstVariable);
|
||||
Blockly.DataCategory.addHideList(xmlList, firstVariable);
|
||||
}
|
||||
|
||||
return xmlList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_variable block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addDataVariable = function(xmlList, variable) {
|
||||
// <block id="variableId" type="data_variable">
|
||||
// <field name="VARIABLE">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_variable', 'VARIABLE');
|
||||
// In the flyout, this ID must match variable ID for monitor syncing reasons
|
||||
xmlList[xmlList.length - 1].setAttribute('id', variable.getId());
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_setvariableto block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addSetVariableTo = function(xmlList, variable) {
|
||||
// <block type="data_setvariableto" gap="20">
|
||||
// <value name="VARIABLE">
|
||||
// <shadow type="data_variablemenu"></shadow>
|
||||
// </value>
|
||||
// <value name="VALUE">
|
||||
// <shadow type="text">
|
||||
// <field name="TEXT">0</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_setvariableto',
|
||||
'VARIABLE', ['VALUE', 'text', 0]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_changevariableby block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addChangeVariableBy = function(xmlList, variable) {
|
||||
// <block type="data_changevariableby">
|
||||
// <value name="VARIABLE">
|
||||
// <shadow type="data_variablemenu"></shadow>
|
||||
// </value>
|
||||
// <value name="VALUE">
|
||||
// <shadow type="math_number">
|
||||
// <field name="NUM">1</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_changevariableby',
|
||||
'VARIABLE', ['VALUE', 'math_number', 1]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_showVariable block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addShowVariable = function(xmlList, variable) {
|
||||
// <block type="data_showvariable">
|
||||
// <value name="VARIABLE">
|
||||
// <shadow type="data_variablemenu"></shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_showvariable',
|
||||
'VARIABLE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_hideVariable block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addHideVariable = function(xmlList, variable) {
|
||||
// <block type="data_hidevariable">
|
||||
// <value name="VARIABLE">
|
||||
// <shadow type="data_variablemenu"></shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_hidevariable',
|
||||
'VARIABLE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_listcontents block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addDataList = function(xmlList, variable) {
|
||||
// <block id="variableId" type="data_listcontents">
|
||||
// <field name="LIST">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_listcontents', 'LIST');
|
||||
// In the flyout, this ID must match variable ID for monitor syncing reasons
|
||||
xmlList[xmlList.length - 1].setAttribute('id', variable.getId());
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_addtolist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addAddToList = function(xmlList, variable) {
|
||||
// <block type="data_addtolist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// <value name="ITEM">
|
||||
// <shadow type="text">
|
||||
// <field name="TEXT">thing</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_addtolist', 'LIST',
|
||||
['ITEM', 'text', Blockly.Msg.DEFAULT_LIST_ITEM]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_deleteoflist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addDeleteOfList = function(xmlList, variable) {
|
||||
// <block type="data_deleteoflist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// <value name="INDEX">
|
||||
// <shadow type="math_integer">
|
||||
// <field name="NUM">1</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_deleteoflist', 'LIST',
|
||||
['INDEX', 'math_integer', 1]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_deleteoflist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addDeleteAllOfList = function(xmlList, variable) {
|
||||
// <block type="data_deletealloflist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_deletealloflist',
|
||||
'LIST');
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_insertatlist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addInsertAtList = function(xmlList, variable) {
|
||||
// <block type="data_insertatlist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// <value name="INDEX">
|
||||
// <shadow type="math_integer">
|
||||
// <field name="NUM">1</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// <value name="ITEM">
|
||||
// <shadow type="text">
|
||||
// <field name="TEXT">thing</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_insertatlist', 'LIST',
|
||||
['INDEX', 'math_integer', 1], ['ITEM', 'text', Blockly.Msg.DEFAULT_LIST_ITEM]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_replaceitemoflist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addReplaceItemOfList = function(xmlList, variable) {
|
||||
// <block type="data_replaceitemoflist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// <value name="INDEX">
|
||||
// <shadow type="math_integer">
|
||||
// <field name="NUM">1</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// <value name="ITEM">
|
||||
// <shadow type="text">
|
||||
// <field name="TEXT">thing</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_replaceitemoflist',
|
||||
'LIST', ['INDEX', 'math_integer', 1], ['ITEM', 'text', Blockly.Msg.DEFAULT_LIST_ITEM]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_itemoflist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addItemOfList = function(xmlList, variable) {
|
||||
// <block type="data_itemoflist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// <value name="INDEX">
|
||||
// <shadow type="math_integer">
|
||||
// <field name="NUM">1</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_itemoflist', 'LIST',
|
||||
['INDEX', 'math_integer', 1]);
|
||||
};
|
||||
|
||||
/** Construct and add a data_itemnumoflist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addItemNumberOfList = function(xmlList, variable) {
|
||||
// <block type="data_itemnumoflist">
|
||||
// <value name="ITEM">
|
||||
// <shadow type="text">
|
||||
// <field name="TEXT">thing</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_itemnumoflist',
|
||||
'LIST', ['ITEM', 'text', Blockly.Msg.DEFAULT_LIST_ITEM]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_lengthoflist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addLengthOfList = function(xmlList, variable) {
|
||||
// <block type="data_lengthoflist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_lengthoflist', 'LIST');
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_listcontainsitem block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addListContainsItem = function(xmlList, variable) {
|
||||
// <block type="data_listcontainsitem">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// <value name="ITEM">
|
||||
// <shadow type="text">
|
||||
// <field name="TEXT">thing</field>
|
||||
// </shadow>
|
||||
// </value>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_listcontainsitem',
|
||||
'LIST', ['ITEM', 'text', Blockly.Msg.DEFAULT_LIST_ITEM]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_showlist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addShowList = function(xmlList, variable) {
|
||||
// <block type="data_showlist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_showlist', 'LIST');
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct and add a data_hidelist block to xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
*/
|
||||
Blockly.DataCategory.addHideList = function(xmlList, variable) {
|
||||
// <block type="data_hidelist">
|
||||
// <field name="LIST" variabletype="list" id="">variablename</field>
|
||||
// </block>
|
||||
Blockly.DataCategory.addBlock(xmlList, variable, 'data_hidelist', 'LIST');
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a create variable button and push it to the xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {Blockly.Workspace} workspace Workspace to register callback to.
|
||||
* @param {string} type Type of variable this is for. For example, 'LIST' or
|
||||
* 'VARIABLE'.
|
||||
*/
|
||||
Blockly.DataCategory.addCreateButton = function(xmlList, workspace, type) {
|
||||
var button = goog.dom.createDom('button');
|
||||
// Set default msg, callbackKey, and callback values for type 'VARIABLE'
|
||||
var msg = Blockly.Msg.NEW_VARIABLE;
|
||||
var callbackKey = 'CREATE_VARIABLE';
|
||||
var callback = function(button) {
|
||||
Blockly.Variables.createVariable(button.getTargetWorkspace(), null, '');};
|
||||
|
||||
if (type === 'LIST') {
|
||||
msg = Blockly.Msg.NEW_LIST;
|
||||
callbackKey = 'CREATE_LIST';
|
||||
callback = function(button) {
|
||||
Blockly.Variables.createVariable(button.getTargetWorkspace(), null,
|
||||
Blockly.LIST_VARIABLE_TYPE);};
|
||||
}
|
||||
button.setAttribute('text', msg);
|
||||
button.setAttribute('callbackKey', callbackKey);
|
||||
workspace.registerButtonCallback(callbackKey, callback);
|
||||
xmlList.push(button);
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a variable block with the given variable, blockType, and optional
|
||||
* value tags. Add the variable block to the given xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
* @param {?Blockly.VariableModel} variable Variable to select in the field.
|
||||
* @param {string} blockType Type of block. For example, 'data_hidelist' or
|
||||
* data_showlist'.
|
||||
* @param {string} fieldName Name of field in block. For example: 'VARIABLE' or
|
||||
* 'LIST'.
|
||||
* @param {?Array.<string>} opt_value Optional array containing the value name
|
||||
* and shadow type of value tags.
|
||||
* @param {?Array.<string>} opt_secondValue Optional array containing the value
|
||||
* name and shadow type of a second pair of value tags.
|
||||
*/
|
||||
Blockly.DataCategory.addBlock = function(xmlList, variable, blockType,
|
||||
fieldName, opt_value, opt_secondValue) {
|
||||
if (Blockly.Blocks[blockType]) {
|
||||
var firstValueField;
|
||||
var secondValueField;
|
||||
if (opt_value) {
|
||||
firstValueField = Blockly.DataCategory.createValue(opt_value[0],
|
||||
opt_value[1], opt_value[2]);
|
||||
}
|
||||
if (opt_secondValue) {
|
||||
secondValueField = Blockly.DataCategory.createValue(opt_secondValue[0],
|
||||
opt_secondValue[1], opt_secondValue[2]);
|
||||
}
|
||||
|
||||
var gap = 10;
|
||||
var blockText = '<xml>' +
|
||||
'<block type="' + blockType + '" gap="' + gap + '">' +
|
||||
Blockly.Variables.generateVariableFieldXml_(variable, fieldName) +
|
||||
firstValueField + secondValueField +
|
||||
'</block>' +
|
||||
'</xml>';
|
||||
var block = Blockly.Xml.textToDom(blockText).firstChild;
|
||||
xmlList.push(block);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the text representation of a value dom element with a shadow of the
|
||||
* indicated type inside.
|
||||
* @param {string} valueName Name of the value tags.
|
||||
* @param {string} type The type of the shadow tags.
|
||||
* @param {string|number} value The default shadow value.
|
||||
* @return {string} The generated dom element in text.
|
||||
*/
|
||||
Blockly.DataCategory.createValue = function(valueName, type, value) {
|
||||
var fieldName;
|
||||
switch (valueName) {
|
||||
case 'ITEM':
|
||||
fieldName = 'TEXT';
|
||||
break;
|
||||
case 'INDEX':
|
||||
fieldName = 'NUM';
|
||||
break;
|
||||
case 'VALUE':
|
||||
if (type === 'math_number') {
|
||||
fieldName = 'NUM';
|
||||
} else {
|
||||
fieldName = 'TEXT';
|
||||
}
|
||||
break;
|
||||
}
|
||||
var valueField =
|
||||
'<value name="' + valueName + '">' +
|
||||
'<shadow type="' + type + '">' +
|
||||
'<field name="' + fieldName + '">' + value + '</field>' +
|
||||
'</shadow>' +
|
||||
'</value>';
|
||||
return valueField;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a block separator. Add the separator to the given xmlList.
|
||||
* @param {!Array.<!Element>} xmlList Array of XML block elements.
|
||||
*/
|
||||
Blockly.DataCategory.addSep = function(xmlList) {
|
||||
var gap = 36;
|
||||
var sepText = '<xml>' +
|
||||
'<sep gap="' + gap + '"/>' +
|
||||
'</xml>';
|
||||
var sep = Blockly.Xml.textToDom(sepText).firstChild;
|
||||
xmlList.push(sep);
|
||||
};
|
||||
260
scratch-blocks/core/dragged_connection_manager.js
Normal file
260
scratch-blocks/core/dragged_connection_manager.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class that controls updates to connections during drags.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.DraggedConnectionManager');
|
||||
|
||||
goog.require('Blockly.BlockAnimations');
|
||||
goog.require('Blockly.RenderedConnection');
|
||||
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class that controls updates to connections during drags. It is primarily
|
||||
* responsible for finding the closest eligible connection and highlighting or
|
||||
* unhiglighting it as needed during a drag.
|
||||
* @param {!Blockly.BlockSvg} block The top block in the stack being dragged.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.DraggedConnectionManager = function(block) {
|
||||
Blockly.selected = block;
|
||||
|
||||
/**
|
||||
* The top block in the stack being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
this.topBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which these connections are being dragged.
|
||||
* Does not change during a drag.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = block.workspace;
|
||||
|
||||
/**
|
||||
* The connections on the dragging blocks that are available to connect to
|
||||
* other blocks. This includes all open connections on the top block, as well
|
||||
* as the last connection on the block stack.
|
||||
* Does not change during a drag.
|
||||
* @type {!Array.<!Blockly.RenderedConnection>}
|
||||
* @private
|
||||
*/
|
||||
this.availableConnections_ = this.initAvailableConnections_();
|
||||
|
||||
/**
|
||||
* The connection that this block would connect to if released immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.closestConnection_ = null;
|
||||
|
||||
/**
|
||||
* The connection that would connect to this.closestConnection_ if this block
|
||||
* were released immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {Blockly.RenderedConnection}
|
||||
* @private
|
||||
*/
|
||||
this.localConnection_ = null;
|
||||
|
||||
/**
|
||||
* The distance between this.closestConnection_ and this.localConnection_,
|
||||
* in workspace units.
|
||||
* Updated on every mouse move.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.radiusConnection_ = 0;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if it were dropped immediately.
|
||||
* Updated on every mouse move.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.dispose = function() {
|
||||
this.topBlock_ = null;
|
||||
this.workspace_ = null;
|
||||
this.availableConnections_.length = 0;
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the block would be deleted if dropped immediately, based on
|
||||
* information from the most recent move event.
|
||||
* @return {boolean} true if the block would be deleted if dropped immediately.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.wouldDeleteBlock = function() {
|
||||
return this.wouldDeleteBlock_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return whether the block would be connected if dropped immediately, based on
|
||||
* information from the most recent move event.
|
||||
* @return {boolean} true if the block would be connected if dropped immediately.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.wouldConnectBlock = function() {
|
||||
return !!this.closestConnection_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to the closest connection and render the results.
|
||||
* This should be called at the end of a drag.
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.applyConnections = function() {
|
||||
if (this.closestConnection_) {
|
||||
// Connect two blocks together.
|
||||
this.localConnection_.connect(this.closestConnection_);
|
||||
if (this.topBlock_.rendered) {
|
||||
// Trigger a connection animation.
|
||||
// Determine which connection is inferior (lower in the source stack).
|
||||
var inferiorConnection = this.localConnection_.isSuperior() ?
|
||||
this.closestConnection_ : this.localConnection_;
|
||||
Blockly.BlockAnimations.connectionUiEffect(
|
||||
inferiorConnection.getSourceBlock());
|
||||
// Bring the just-edited stack to the front.
|
||||
var rootBlock = this.topBlock_.getRootBlock();
|
||||
rootBlock.bringToFront();
|
||||
}
|
||||
this.removeHighlighting_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update highlighted connections based on the most recent move location.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to drag start,
|
||||
* in workspace units.
|
||||
* @param {?number} deleteArea One of {@link Blockly.DELETE_AREA_TRASH},
|
||||
* {@link Blockly.DELETE_AREA_TOOLBOX}, or {@link Blockly.DELETE_AREA_NONE}.
|
||||
* @param {?boolean} isOutside True if the drag is going outside the blocks workspace
|
||||
* @package
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.update = function(dxy, deleteArea, isOutside) {
|
||||
var oldClosestConnection;
|
||||
var closestConnectionChanged;
|
||||
// If dragged outside, don't connect, since the connections aren't visible.
|
||||
if (!isOutside) {
|
||||
oldClosestConnection = this.closestConnection_;
|
||||
closestConnectionChanged = this.updateClosest_(dxy);
|
||||
if (closestConnectionChanged && oldClosestConnection) {
|
||||
oldClosestConnection.unhighlight();
|
||||
}
|
||||
} else if (this.closestConnection_) {
|
||||
this.closestConnection_.unhighlight();
|
||||
this.closestConnection_ = null;
|
||||
}
|
||||
|
||||
// Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
// the toolbox over connecting to other blocks.
|
||||
var wouldConnect = !!this.closestConnection_ &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = !!deleteArea && !this.topBlock_.getParent() &&
|
||||
this.topBlock_.isDeletable();
|
||||
this.wouldDeleteBlock_ = wouldDelete && !wouldConnect;
|
||||
|
||||
if (!this.wouldDeleteBlock_ && closestConnectionChanged &&
|
||||
this.closestConnection_) {
|
||||
this.addHighlighting_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove highlighting from the currently highlighted connection, if it exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.removeHighlighting_ = function() {
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.unhighlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add highlighting to the closest connection, if it exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.addHighlighting_ = function() {
|
||||
if (this.closestConnection_) {
|
||||
this.closestConnection_.highlight();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Populate the list of available connections on this block stack. This should
|
||||
* only be called once, at the beginning of a drag.
|
||||
* @return {!Array.<!Blockly.RenderedConnection>} a list of available
|
||||
* connections.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.initAvailableConnections_ = function() {
|
||||
var available = this.topBlock_.getConnections_(false);
|
||||
// Also check the last connection on this stack
|
||||
var lastOnStack = this.topBlock_.lastConnectionInStack();
|
||||
if (lastOnStack && lastOnStack != this.topBlock_.nextConnection) {
|
||||
available.push(lastOnStack);
|
||||
}
|
||||
return available;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the new closest connection, and update internal state in response.
|
||||
* @param {!goog.math.Coordinate} dxy Position relative to the drag start,
|
||||
* in workspace units.
|
||||
* @return {boolean} Whether the closest connection has changed.
|
||||
* @private
|
||||
*/
|
||||
Blockly.DraggedConnectionManager.prototype.updateClosest_ = function(dxy) {
|
||||
var oldClosestConnection = this.closestConnection_;
|
||||
|
||||
this.closestConnection_ = null;
|
||||
this.localConnection_ = null;
|
||||
this.radiusConnection_ = Blockly.SNAP_RADIUS;
|
||||
for (var i = 0; i < this.availableConnections_.length; i++) {
|
||||
var myConnection = this.availableConnections_[i];
|
||||
var neighbour = myConnection.closest(this.radiusConnection_, dxy);
|
||||
if (neighbour.connection) {
|
||||
this.closestConnection_ = neighbour.connection;
|
||||
this.localConnection_ = myConnection;
|
||||
this.radiusConnection_ = neighbour.radius;
|
||||
}
|
||||
}
|
||||
return oldClosestConnection != this.closestConnection_;
|
||||
};
|
||||
408
scratch-blocks/core/dropdowndiv.js
Normal file
408
scratch-blocks/core/dropdowndiv.js
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A div that floats on top of the workspace, for drop-down menus.
|
||||
* The drop-down can be kept inside the workspace, animate in/out, etc.
|
||||
* @author tmickel@mit.edu (Tim Mickel)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.DropDownDiv');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.style');
|
||||
|
||||
/**
|
||||
* Class for drop-down div.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.DropDownDiv = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* The div element. Set once by Blockly.DropDownDiv.createDom.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DropDownDiv.DIV_ = null;
|
||||
|
||||
/**
|
||||
* Drop-downs will appear within the bounds of this element if possible.
|
||||
* Set in Blockly.DropDownDiv.setBoundsElement.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DropDownDiv.boundsElement_ = null;
|
||||
|
||||
/**
|
||||
* The object currently using the drop-down.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DropDownDiv.owner_ = null;
|
||||
|
||||
/**
|
||||
* Arrow size in px. Should match the value in CSS (need to position pre-render).
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.DropDownDiv.ARROW_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Drop-down border size in px. Should match the value in CSS (need to position the arrow).
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.DropDownDiv.BORDER_SIZE = 1;
|
||||
|
||||
/**
|
||||
* Amount the arrow must be kept away from the edges of the main drop-down div, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.DropDownDiv.ARROW_HORIZONTAL_PADDING = 12;
|
||||
|
||||
/**
|
||||
* Amount drop-downs should be padded away from the source, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.DropDownDiv.PADDING_Y = 20;
|
||||
|
||||
/**
|
||||
* Length of animations in seconds.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.DropDownDiv.ANIMATION_TIME = 0.25;
|
||||
|
||||
/**
|
||||
* Timer for animation out, to be cleared if we need to immediately hide
|
||||
* without disrupting new shows.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.DropDownDiv.animateOutTimer_ = null;
|
||||
|
||||
/**
|
||||
* Callback for when the drop-down is hidden.
|
||||
* @type {Function}
|
||||
*/
|
||||
Blockly.DropDownDiv.onHide_ = 0;
|
||||
|
||||
/**
|
||||
* Create and insert the DOM element for this div.
|
||||
* @param {Element} container Element that the div should be contained in.
|
||||
*/
|
||||
Blockly.DropDownDiv.createDom = function() {
|
||||
if (Blockly.DropDownDiv.DIV_) {
|
||||
return; // Already created.
|
||||
}
|
||||
Blockly.DropDownDiv.DIV_ = goog.dom.createDom('div', 'blocklyDropDownDiv');
|
||||
document.body.appendChild(Blockly.DropDownDiv.DIV_);
|
||||
Blockly.DropDownDiv.content_ = goog.dom.createDom('div', 'blocklyDropDownContent');
|
||||
Blockly.DropDownDiv.DIV_.appendChild(Blockly.DropDownDiv.content_);
|
||||
Blockly.DropDownDiv.arrow_ = goog.dom.createDom('div', 'blocklyDropDownArrow');
|
||||
Blockly.DropDownDiv.DIV_.appendChild(Blockly.DropDownDiv.arrow_);
|
||||
|
||||
// Transition animation for transform: translate() and opacity.
|
||||
Blockly.DropDownDiv.DIV_.style.transition = 'transform ' +
|
||||
Blockly.DropDownDiv.ANIMATION_TIME + 's, ' +
|
||||
'opacity ' + Blockly.DropDownDiv.ANIMATION_TIME + 's';
|
||||
};
|
||||
|
||||
/**
|
||||
* Set an element to maintain bounds within. Drop-downs will appear
|
||||
* within the box of this element if possible.
|
||||
* @param {Element} boundsElement Element to bound drop-down to.
|
||||
*/
|
||||
Blockly.DropDownDiv.setBoundsElement = function(boundsElement) {
|
||||
Blockly.DropDownDiv.boundsElement_ = boundsElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide the div for inserting content into the drop-down.
|
||||
* @return {Element} Div to populate with content
|
||||
*/
|
||||
Blockly.DropDownDiv.getContentDiv = function() {
|
||||
return Blockly.DropDownDiv.content_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the content of the drop-down.
|
||||
*/
|
||||
Blockly.DropDownDiv.clearContent = function() {
|
||||
Blockly.DropDownDiv.content_.innerHTML = '';
|
||||
Blockly.DropDownDiv.content_.style.width = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the colour for the drop-down.
|
||||
* @param {string} backgroundColour Any CSS color for the background
|
||||
* @param {string} borderColour Any CSS color for the border
|
||||
*/
|
||||
Blockly.DropDownDiv.setColour = function(backgroundColour, borderColour) {
|
||||
Blockly.DropDownDiv.DIV_.style.backgroundColor = backgroundColour;
|
||||
Blockly.DropDownDiv.DIV_.style.borderColor = borderColour;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the category for the drop-down.
|
||||
* @param {string} category The new category for the drop-down.
|
||||
*/
|
||||
Blockly.DropDownDiv.setCategory = function(category) {
|
||||
Blockly.DropDownDiv.DIV_.setAttribute('data-category', category);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shortcut to show and place the drop-down with positioning determined
|
||||
* by a particular block. The primary position will be below the block,
|
||||
* and the secondary position above the block. Drop-down will be
|
||||
* constrained to the block's workspace.
|
||||
* @param {Object} owner The object showing the drop-down
|
||||
* @param {!Blockly.Block} block Block to position the drop-down around.
|
||||
* @param {Function=} opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @param {Number} opt_secondaryYOffset Optional Y offset for above-block positioning.
|
||||
* @return {boolean} True if the menu rendered below block; false if above.
|
||||
*/
|
||||
Blockly.DropDownDiv.showPositionedByBlock = function(owner, block,
|
||||
opt_onHide, opt_secondaryYOffset) {
|
||||
var scale = block.workspace.scale;
|
||||
var bBox = {width: block.width, height: block.height};
|
||||
bBox.width *= scale;
|
||||
bBox.height *= scale;
|
||||
var position = block.getSvgRoot().getBoundingClientRect();
|
||||
// If we can fit it, render below the block.
|
||||
var primaryX = position.left + bBox.width / 2;
|
||||
var primaryY = position.top + bBox.height;
|
||||
// If we can't fit it, render above the entire parent block.
|
||||
var secondaryX = primaryX;
|
||||
var secondaryY = position.top;
|
||||
if (opt_secondaryYOffset) {
|
||||
secondaryY += opt_secondaryYOffset;
|
||||
}
|
||||
// Set bounds to workspace; show the drop-down.
|
||||
Blockly.DropDownDiv.setBoundsElement(block.workspace.getParentSvg().parentNode);
|
||||
return Blockly.DropDownDiv.show(this, primaryX, primaryY, secondaryX, secondaryY, opt_onHide);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show and place the drop-down.
|
||||
* The drop-down is placed with an absolute "origin point" (x, y) - i.e.,
|
||||
* the arrow will point at this origin and box will positioned below or above it.
|
||||
* If we can maintain the container bounds at the primary point, the arrow will
|
||||
* point there, and the container will be positioned below it.
|
||||
* If we can't maintain the container bounds at the primary point, fall-back to the
|
||||
* secondary point and position above.
|
||||
* @param {Object} owner The object showing the drop-down
|
||||
* @param {number} primaryX Desired origin point x, in absolute px
|
||||
* @param {number} primaryY Desired origin point y, in absolute px
|
||||
* @param {number} secondaryX Secondary/alternative origin point x, in absolute px
|
||||
* @param {number} secondaryY Secondary/alternative origin point y, in absolute px
|
||||
* @param {Function=} opt_onHide Optional callback for when the drop-down is hidden
|
||||
* @return {boolean} True if the menu rendered at the primary origin point.
|
||||
*/
|
||||
Blockly.DropDownDiv.show = function(owner, primaryX, primaryY, secondaryX, secondaryY, opt_onHide) {
|
||||
Blockly.DropDownDiv.owner_ = owner;
|
||||
Blockly.DropDownDiv.onHide_ = opt_onHide;
|
||||
var div = Blockly.DropDownDiv.DIV_;
|
||||
var metrics = Blockly.DropDownDiv.getPositionMetrics(primaryX, primaryY, secondaryX, secondaryY);
|
||||
// Update arrow CSS
|
||||
Blockly.DropDownDiv.arrow_.style.transform = 'translate(' +
|
||||
metrics.arrowX + 'px,' + metrics.arrowY + 'px) rotate(45deg)';
|
||||
Blockly.DropDownDiv.arrow_.setAttribute('class',
|
||||
metrics.arrowAtTop ? 'blocklyDropDownArrow arrowTop' : 'blocklyDropDownArrow arrowBottom');
|
||||
// Set direction based on owner's rtl
|
||||
div.style.direction = owner.sourceBlock_ && owner.sourceBlock_.RTL ? 'rtl' : 'ltr';
|
||||
|
||||
// When we change `translate` multiple times in close succession,
|
||||
// Chrome may choose to wait and apply them all at once.
|
||||
// Since we want the translation to initial X, Y to be immediate,
|
||||
// and the translation to final X, Y to be animated,
|
||||
// we saw problems where both would be applied after animation was turned on,
|
||||
// making the dropdown appear to fly in from (0, 0).
|
||||
// Using both `left`, `top` for the initial translation and then `translate`
|
||||
// for the animated transition to final X, Y is a workaround.
|
||||
|
||||
// First apply initial translation.
|
||||
div.style.left = metrics.initialX + 'px';
|
||||
div.style.top = metrics.initialY + 'px';
|
||||
// Show the div.
|
||||
div.style.display = 'block';
|
||||
div.style.opacity = 1;
|
||||
// Add final translate, animated through `transition`.
|
||||
// Coordinates are relative to (initialX, initialY),
|
||||
// where the drop-down is absolutely positioned.
|
||||
var dx = (metrics.finalX - metrics.initialX);
|
||||
var dy = (metrics.finalY - metrics.initialY);
|
||||
div.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
|
||||
return metrics.arrowAtTop;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to position the drop-down and the arrow, maintaining bounds.
|
||||
* See explanation of origin points in Blockly.DropDownDiv.show.
|
||||
* @param {number} primaryX Desired origin point x, in absolute px
|
||||
* @param {number} primaryY Desired origin point y, in absolute px
|
||||
* @param {number} secondaryX Secondary/alternative origin point x, in absolute px
|
||||
* @param {number} secondaryY Secondary/alternative origin point y, in absolute px
|
||||
* @returns {Object} Various final metrics, including rendered positions for drop-down and arrow.
|
||||
*/
|
||||
Blockly.DropDownDiv.getPositionMetrics = function(primaryX, primaryY, secondaryX, secondaryY) {
|
||||
var div = Blockly.DropDownDiv.DIV_;
|
||||
var boundPosition = Blockly.DropDownDiv.boundsElement_.getBoundingClientRect();
|
||||
|
||||
var boundSize = goog.style.getSize(Blockly.DropDownDiv.boundsElement_);
|
||||
var divSize = goog.style.getSize(div);
|
||||
|
||||
// First decide if we will render at primary or secondary position
|
||||
// i.e., above or below
|
||||
// renderX, renderY will eventually be the final rendered position of the box.
|
||||
var renderX, renderY, renderedSecondary;
|
||||
// Can the div fit inside the bounds if we render below the primary point?
|
||||
if (primaryY + divSize.height > boundPosition.top + boundSize.height) {
|
||||
// We can't fit below in terms of y. Can we fit above?
|
||||
if (secondaryY - divSize.height < boundPosition.top) {
|
||||
// We also can't fit above, so just render below anyway.
|
||||
renderX = primaryX;
|
||||
renderY = primaryY + Blockly.DropDownDiv.PADDING_Y;
|
||||
renderedSecondary = false;
|
||||
} else {
|
||||
// We can fit above, render secondary
|
||||
renderX = secondaryX;
|
||||
renderY = secondaryY - divSize.height - Blockly.DropDownDiv.PADDING_Y;
|
||||
renderedSecondary = true;
|
||||
}
|
||||
} else {
|
||||
// We can fit below, render primary
|
||||
renderX = primaryX;
|
||||
renderY = primaryY + Blockly.DropDownDiv.PADDING_Y;
|
||||
renderedSecondary = false;
|
||||
}
|
||||
// First calculate the absolute arrow X
|
||||
// This needs to be done before positioning the div, since the arrow
|
||||
// wants to be as close to the origin point as possible.
|
||||
var arrowX = renderX - Blockly.DropDownDiv.ARROW_SIZE / 2;
|
||||
// Keep in overall bounds
|
||||
arrowX = Math.max(boundPosition.left, Math.min(arrowX, boundPosition.left + boundSize.width));
|
||||
|
||||
// Adjust the x-position of the drop-down so that the div is centered and within bounds.
|
||||
var centerX = divSize.width / 2;
|
||||
renderX -= centerX;
|
||||
// Fit horizontally in the bounds.
|
||||
renderX = Math.max(
|
||||
boundPosition.left,
|
||||
Math.min(renderX, boundPosition.left + boundSize.width - divSize.width)
|
||||
);
|
||||
// After we've finished caclulating renderX, adjust the arrow to be relative to it.
|
||||
arrowX -= renderX;
|
||||
|
||||
// Pad the arrow by some pixels, primarily so that it doesn't render on top of a rounded border.
|
||||
arrowX = Math.max(
|
||||
Blockly.DropDownDiv.ARROW_HORIZONTAL_PADDING,
|
||||
Math.min(arrowX, divSize.width - Blockly.DropDownDiv.ARROW_HORIZONTAL_PADDING - Blockly.DropDownDiv.ARROW_SIZE)
|
||||
);
|
||||
|
||||
// Calculate arrow Y. If we rendered secondary, add on bottom.
|
||||
// Extra pixels are added so that it covers the border of the div.
|
||||
var arrowY = (renderedSecondary) ? divSize.height - Blockly.DropDownDiv.BORDER_SIZE : 0;
|
||||
arrowY -= (Blockly.DropDownDiv.ARROW_SIZE / 2) + Blockly.DropDownDiv.BORDER_SIZE;
|
||||
|
||||
// Initial position calculated without any padding to provide an animation point.
|
||||
var initialX = renderX; // X position remains constant during animation.
|
||||
var initialY;
|
||||
if (renderedSecondary) {
|
||||
initialY = secondaryY - divSize.height; // No padding on Y
|
||||
} else {
|
||||
initialY = primaryY; // No padding on Y
|
||||
}
|
||||
|
||||
return {
|
||||
initialX: initialX,
|
||||
initialY : initialY,
|
||||
finalX: renderX,
|
||||
finalY: renderY,
|
||||
arrowX: arrowX,
|
||||
arrowY: arrowY,
|
||||
arrowAtTop: !renderedSecondary
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the container visible?
|
||||
* @return {boolean} True if visible.
|
||||
*/
|
||||
Blockly.DropDownDiv.isVisible = function() {
|
||||
return !!Blockly.DropDownDiv.owner_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the menu only if it is owned by the provided object.
|
||||
* @param {Object} owner Object which must be owning the drop-down to hide
|
||||
* @return {Boolean} True if hidden
|
||||
*/
|
||||
Blockly.DropDownDiv.hideIfOwner = function(owner) {
|
||||
if (Blockly.DropDownDiv.owner_ === owner) {
|
||||
Blockly.DropDownDiv.hide();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the menu, triggering animation.
|
||||
*/
|
||||
Blockly.DropDownDiv.hide = function() {
|
||||
// Start the animation by setting the translation and fading out.
|
||||
var div = Blockly.DropDownDiv.DIV_;
|
||||
// Reset to (initialX, initialY) - i.e., no translation.
|
||||
div.style.transform = 'translate(0px, 0px)';
|
||||
div.style.opacity = 0;
|
||||
Blockly.DropDownDiv.animateOutTimer_ = setTimeout(function() {
|
||||
// Finish animation - reset all values to default.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
}, Blockly.DropDownDiv.ANIMATION_TIME * 1000);
|
||||
if (Blockly.DropDownDiv.onHide_) {
|
||||
Blockly.DropDownDiv.onHide_();
|
||||
Blockly.DropDownDiv.onHide_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the menu, without animation.
|
||||
*/
|
||||
Blockly.DropDownDiv.hideWithoutAnimation = function() {
|
||||
if (!Blockly.DropDownDiv.isVisible()) {
|
||||
return;
|
||||
}
|
||||
var div = Blockly.DropDownDiv.DIV_;
|
||||
Blockly.DropDownDiv.animateOutTimer_ && window.clearTimeout(Blockly.DropDownDiv.animateOutTimer_);
|
||||
div.style.transform = '';
|
||||
div.style.top = '';
|
||||
div.style.left = '';
|
||||
div.style.display = 'none';
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
Blockly.DropDownDiv.owner_ = null;
|
||||
if (Blockly.DropDownDiv.onHide_) {
|
||||
Blockly.DropDownDiv.onHide_();
|
||||
Blockly.DropDownDiv.onHide_ = null;
|
||||
}
|
||||
};
|
||||
429
scratch-blocks/core/events.js
Normal file
429
scratch-blocks/core/events.js
Normal file
@@ -0,0 +1,429 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Events fired as a result of actions in Blockly's editor.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Events fired as a result of actions in Blockly's editor.
|
||||
* @namespace Blockly.Events
|
||||
*/
|
||||
goog.provide('Blockly.Events');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Group ID for new events. Grouped events are indivisible.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.group_ = '';
|
||||
|
||||
/**
|
||||
* Sets whether events should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
Blockly.Events.recordUndo = true;
|
||||
|
||||
/**
|
||||
* Allow change events to be created and fired.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.disabled_ = 0;
|
||||
|
||||
/**
|
||||
* Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.CREATE = 'create';
|
||||
|
||||
/**
|
||||
* Name of event that creates a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE;
|
||||
|
||||
/**
|
||||
* Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE;
|
||||
|
||||
/**
|
||||
* Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.CHANGE = 'change';
|
||||
|
||||
/**
|
||||
* Name of event that changes a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE;
|
||||
|
||||
/**
|
||||
* Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.MOVE = 'move';
|
||||
|
||||
/**
|
||||
* Name of event that drags a block outside of or into the blocks workspace
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.DRAG_OUTSIDE = 'dragOutside';
|
||||
|
||||
/**
|
||||
* Name of event that ends a block drag
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.END_DRAG = 'endDrag';
|
||||
|
||||
/**
|
||||
* Name of event that moves a block.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE;
|
||||
|
||||
/**
|
||||
* Name of event that creates a variable.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.VAR_CREATE = 'var_create';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a variable.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.VAR_DELETE = 'var_delete';
|
||||
|
||||
/**
|
||||
* Name of event that renames a variable.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.VAR_RENAME = 'var_rename';
|
||||
|
||||
/**
|
||||
* Name of event that creates a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_CREATE = 'comment_create';
|
||||
|
||||
/**
|
||||
* Name of event that moves a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_MOVE = 'comment_move';
|
||||
|
||||
/**
|
||||
* Name of event that changes a comment's property
|
||||
* (text content, size, or minimized state).
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_CHANGE = 'comment_change';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a comment.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.COMMENT_DELETE = 'comment_delete';
|
||||
|
||||
/**
|
||||
* Name of event that records a UI change.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Events.UI = 'ui';
|
||||
|
||||
/**
|
||||
* List of events queued for firing.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.FIRE_QUEUE_ = [];
|
||||
|
||||
/**
|
||||
* Create a custom event and fire it.
|
||||
* @param {!Blockly.Events.Abstract} event Custom data for event.
|
||||
*/
|
||||
Blockly.Events.fire = function(event) {
|
||||
if (!Blockly.Events.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
if (!Blockly.Events.FIRE_QUEUE_.length) {
|
||||
// First event added; schedule a firing of the event queue.
|
||||
setTimeout(Blockly.Events.fireNow_, 0);
|
||||
}
|
||||
Blockly.Events.FIRE_QUEUE_.push(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fire all queued events.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.fireNow_ = function() {
|
||||
var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
|
||||
Blockly.Events.FIRE_QUEUE_.length = 0;
|
||||
for (var i = 0, event; event = queue[i]; i++) {
|
||||
var workspace = Blockly.Workspace.getById(event.workspaceId);
|
||||
if (workspace) {
|
||||
workspace.fireChangeListener(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter the queued events and merge duplicates.
|
||||
* @param {!Array.<!Blockly.Events.Abstract>} queueIn Array of events.
|
||||
* @param {boolean} forward True if forward (redo), false if backward (undo).
|
||||
* @return {!Array.<!Blockly.Events.Abstract>} Array of filtered events.
|
||||
*/
|
||||
Blockly.Events.filter = function(queueIn, forward) {
|
||||
var queue = goog.array.clone(queueIn);
|
||||
if (!forward) {
|
||||
// Undo is merged in reverse order.
|
||||
queue.reverse();
|
||||
}
|
||||
var mergedQueue = [];
|
||||
var hash = Object.create(null);
|
||||
// Merge duplicates.
|
||||
for (var i = 0, event; event = queue[i]; i++) {
|
||||
if (!event.isNull()) {
|
||||
var key = [event.type, event.blockId, event.workspaceId].join(' ');
|
||||
|
||||
var lastEntry = hash[key];
|
||||
var lastEvent = lastEntry ? lastEntry.event : null;
|
||||
if (!lastEntry) {
|
||||
// Each item in the hash table has the event and the index of that event
|
||||
// in the input array. This lets us make sure we only merge adjacent
|
||||
// move events.
|
||||
hash[key] = {event: event, index: i};
|
||||
mergedQueue.push(event);
|
||||
} else if (event.type == Blockly.Events.MOVE &&
|
||||
lastEntry.index == i - 1) {
|
||||
// Merge move events.
|
||||
lastEvent.newParentId = event.newParentId;
|
||||
lastEvent.newInputName = event.newInputName;
|
||||
lastEvent.newCoordinate = event.newCoordinate;
|
||||
lastEntry.index = i;
|
||||
} else if (event.type == Blockly.Events.CHANGE &&
|
||||
event.element == lastEvent.element &&
|
||||
event.name == lastEvent.name) {
|
||||
// Merge change events.
|
||||
lastEvent.newValue = event.newValue;
|
||||
} else if (event.type == Blockly.Events.UI &&
|
||||
event.element == 'click' &&
|
||||
(lastEvent.element == 'commentOpen' ||
|
||||
lastEvent.element == 'mutatorOpen' ||
|
||||
lastEvent.element == 'warningOpen')) {
|
||||
// Merge click events.
|
||||
lastEvent.newValue = event.newValue;
|
||||
} else {
|
||||
// Collision: newer events should merge into this event to maintain order
|
||||
hash[key] = {event: event, index: 1};
|
||||
mergedQueue.push(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Filter out any events that have become null due to merging.
|
||||
queue = mergedQueue.filter(function(e) { return !e.isNull(); });
|
||||
if (!forward) {
|
||||
// Restore undo order.
|
||||
queue.reverse();
|
||||
}
|
||||
// Move mutation events to the top of the queue.
|
||||
// Intentionally skip first event.
|
||||
for (var i = 1, event; event = queue[i]; i++) {
|
||||
if (event.type == Blockly.Events.CHANGE &&
|
||||
event.element == 'mutation') {
|
||||
queue.unshift(queue.splice(i, 1)[0]);
|
||||
}
|
||||
}
|
||||
return queue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Modify pending undo events so that when they are fired they don't land
|
||||
* in the undo stack. Called by Blockly.Workspace.clearUndo.
|
||||
*/
|
||||
Blockly.Events.clearPendingUndo = function() {
|
||||
for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) {
|
||||
event.recordUndo = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop sending events. Every call to this function MUST also call enable.
|
||||
*/
|
||||
Blockly.Events.disable = function() {
|
||||
Blockly.Events.disabled_++;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start sending events. Unless events were already disabled when the
|
||||
* corresponding call to disable was made.
|
||||
*/
|
||||
Blockly.Events.enable = function() {
|
||||
Blockly.Events.disabled_--;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether events may be fired or not.
|
||||
* @return {boolean} True if enabled.
|
||||
*/
|
||||
Blockly.Events.isEnabled = function() {
|
||||
return Blockly.Events.disabled_ == 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Current group.
|
||||
* @return {string} ID string.
|
||||
*/
|
||||
Blockly.Events.getGroup = function() {
|
||||
return Blockly.Events.group_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start or stop a group.
|
||||
* @param {boolean|string} state True to start new group, false to end group.
|
||||
* String to set group explicitly.
|
||||
*/
|
||||
Blockly.Events.setGroup = function(state) {
|
||||
if (typeof state == 'boolean') {
|
||||
Blockly.Events.group_ = state ? Blockly.utils.genUid() : '';
|
||||
} else {
|
||||
Blockly.Events.group_ = state;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute a list of the IDs of the specified block and all its descendants.
|
||||
* @param {!Blockly.Block} block The root block.
|
||||
* @return {!Array.<string>} List of block IDs.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Events.getDescendantIds_ = function(block) {
|
||||
var ids = [];
|
||||
var descendants = block.getDescendants(false);
|
||||
for (var i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
ids[i] = descendant.id;
|
||||
}
|
||||
return ids;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON into an event.
|
||||
* @param {!Object} json JSON representation.
|
||||
* @param {!Blockly.Workspace} workspace Target workspace for event.
|
||||
* @return {!Blockly.Events.Abstract} The event represented by the JSON.
|
||||
*/
|
||||
Blockly.Events.fromJson = function(json, workspace) {
|
||||
var event;
|
||||
switch (json.type) {
|
||||
case Blockly.Events.CREATE:
|
||||
event = new Blockly.Events.Create(null);
|
||||
break;
|
||||
case Blockly.Events.DELETE:
|
||||
event = new Blockly.Events.Delete(null);
|
||||
break;
|
||||
case Blockly.Events.CHANGE:
|
||||
event = new Blockly.Events.Change(null);
|
||||
break;
|
||||
case Blockly.Events.MOVE:
|
||||
event = new Blockly.Events.Move(null);
|
||||
break;
|
||||
case Blockly.Events.VAR_CREATE:
|
||||
event = new Blockly.Events.VarCreate(null);
|
||||
break;
|
||||
case Blockly.Events.VAR_DELETE:
|
||||
event = new Blockly.Events.VarDelete(null);
|
||||
break;
|
||||
case Blockly.Events.VAR_RENAME:
|
||||
event = new Blockly.Events.VarRename(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_CREATE:
|
||||
event = new Blockly.Events.CommentCreate(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_CHANGE:
|
||||
event = new Blockly.Events.CommentChange(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_MOVE:
|
||||
event = new Blockly.Events.CommentMove(null);
|
||||
break;
|
||||
case Blockly.Events.COMMENT_DELETE:
|
||||
event = new Blockly.Events.CommentDelete(null);
|
||||
break;
|
||||
case Blockly.Events.UI:
|
||||
event = new Blockly.Events.Ui(null);
|
||||
break;
|
||||
case Blockly.Events.DRAG_OUTSIDE:
|
||||
event = new Blockly.Events.DragBlockOutside(null);
|
||||
break;
|
||||
case Blockly.Events.END_DRAG:
|
||||
event = new Blockly.Events.EndBlockDrag(null, false);
|
||||
break;
|
||||
default:
|
||||
throw 'Unknown event type.';
|
||||
}
|
||||
event.fromJson(json);
|
||||
event.workspaceId = workspace.id;
|
||||
return event;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable/disable a block depending on whether it is properly connected.
|
||||
* Use this on applications where all blocks should be connected to a top block.
|
||||
* Recommend setting the 'disable' option to 'false' in the config so that
|
||||
* users don't try to reenable disabled orphan blocks.
|
||||
* @param {!Blockly.Events.Abstract} event Custom data for event.
|
||||
*/
|
||||
Blockly.Events.disableOrphans = function(event) {
|
||||
if (event.type == Blockly.Events.MOVE ||
|
||||
event.type == Blockly.Events.CREATE) {
|
||||
Blockly.Events.disable();
|
||||
var workspace = Blockly.Workspace.getById(event.workspaceId);
|
||||
var block = workspace.getBlockById(event.blockId);
|
||||
if (block) {
|
||||
if (block.getParent() && !block.getParent().disabled) {
|
||||
var children = block.getDescendants(false);
|
||||
for (var i = 0, child; child = children[i]; i++) {
|
||||
child.setDisabled(false);
|
||||
}
|
||||
} else if ((block.outputConnection || block.previousConnection) &&
|
||||
!workspace.isDragging()) {
|
||||
do {
|
||||
block.setDisabled(true);
|
||||
block = block.getNextBlock();
|
||||
} while (block);
|
||||
}
|
||||
}
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
};
|
||||
113
scratch-blocks/core/events_abstract.js
Normal file
113
scratch-blocks/core/events_abstract.js
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Abstract class for events fired as a result of actions in
|
||||
* Blockly's editor.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Events.Abstract');
|
||||
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
/**
|
||||
* Abstract class for an event.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Events.Abstract = function() {
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.workspaceId = undefined;
|
||||
|
||||
/**
|
||||
* The event group id for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = Blockly.Events.group_;
|
||||
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = Blockly.Events.recordUndo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.toJson = function() {
|
||||
var json = {
|
||||
'type': this.type
|
||||
};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.fromJson = function(json) {
|
||||
this.group = json['group'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* By default we assume events are non-null. Subclasses may override to
|
||||
* indicate that they do not change state.
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.isNull = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run an event.
|
||||
* @param {boolean} _forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.run = function(_forward) {
|
||||
// Defined by subclasses.
|
||||
};
|
||||
|
||||
/**
|
||||
* Get workspace the event belongs to.
|
||||
* @return {Blockly.Workspace} The workspace the event belongs to.
|
||||
* @throws {Error} if workspace is null.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Events.Abstract.prototype.getEventWorkspace_ = function() {
|
||||
var workspace = Blockly.Workspace.getById(this.workspaceId);
|
||||
if (!workspace) {
|
||||
throw Error('Workspace is null. Event must have been generated from real' +
|
||||
' Blockly events.');
|
||||
}
|
||||
return workspace;
|
||||
};
|
||||
450
scratch-blocks/core/extensions.js
Normal file
450
scratch-blocks/core/extensions.js
Normal file
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Extensions are functions that help initialize blocks, usually
|
||||
* adding dynamic behavior such as onchange handlers and mutators. These
|
||||
* are applied using Block.applyExtension(), or the JSON "extensions"
|
||||
* array attribute.
|
||||
* @author Anm@anm.me (Andrew n marshall)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @name Blockly.Extensions
|
||||
* @namespace
|
||||
**/
|
||||
goog.provide('Blockly.Extensions');
|
||||
|
||||
goog.require('Blockly.Mutator');
|
||||
goog.require('Blockly.utils');
|
||||
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* The set of all registered extensions, keyed by extension name/id.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.ALL_ = {};
|
||||
|
||||
/**
|
||||
* Registers a new extension function. Extensions are functions that help
|
||||
* initialize blocks, usually adding dynamic behavior such as onchange
|
||||
* handlers and mutators. These are applied using Block.applyExtension(), or
|
||||
* the JSON "extensions" array attribute.
|
||||
* @param {string} name The name of this extension.
|
||||
* @param {Function} initFn The function to initialize an extended block.
|
||||
* @throws {Error} if the extension name is empty, the extension is already
|
||||
* registered, or extensionFn is not a function.
|
||||
*/
|
||||
Blockly.Extensions.register = function(name, initFn) {
|
||||
if (!goog.isString(name) || goog.string.isEmptyOrWhitespace(name)) {
|
||||
throw new Error('Error: Invalid extension name "' + name + '"');
|
||||
}
|
||||
if (Blockly.Extensions.ALL_[name]) {
|
||||
throw new Error('Error: Extension "' + name + '" is already registered.');
|
||||
}
|
||||
if (!goog.isFunction(initFn)) {
|
||||
throw new Error('Error: Extension "' + name + '" must be a function');
|
||||
}
|
||||
Blockly.Extensions.ALL_[name] = initFn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new extension function that adds all key/value of mixinObj.
|
||||
* @param {string} name The name of this extension.
|
||||
* @param {!Object} mixinObj The values to mix in.
|
||||
* @throws {Error} if the extension name is empty or the extension is already
|
||||
* registered.
|
||||
*/
|
||||
Blockly.Extensions.registerMixin = function(name, mixinObj) {
|
||||
if (!goog.isObject(mixinObj)){
|
||||
throw new Error('Error: Mixin "' + name + '" must be a object');
|
||||
}
|
||||
Blockly.Extensions.register(name, function() {
|
||||
this.mixin(mixinObj);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers a new extension function that adds a mutator to the block.
|
||||
* At register time this performs some basic sanity checks on the mutator.
|
||||
* The wrapper may also add a mutator dialog to the block, if both compose and
|
||||
* decompose are defined on the mixin.
|
||||
* @param {string} name The name of this mutator extension.
|
||||
* @param {!Object} mixinObj The values to mix in.
|
||||
* @param {(function())=} opt_helperFn An optional function to apply after
|
||||
* mixing in the object.
|
||||
* @param {Array.<string>=} opt_blockList A list of blocks to appear in the
|
||||
* flyout of the mutator dialog.
|
||||
* @throws {Error} if the mutation is invalid or can't be applied to the block.
|
||||
*/
|
||||
Blockly.Extensions.registerMutator = function(name, mixinObj, opt_helperFn,
|
||||
opt_blockList) {
|
||||
var errorPrefix = 'Error when registering mutator "' + name + '": ';
|
||||
|
||||
// Sanity check the mixin object before registering it.
|
||||
Blockly.Extensions.checkHasFunction_(
|
||||
errorPrefix, mixinObj.domToMutation, 'domToMutation');
|
||||
Blockly.Extensions.checkHasFunction_(
|
||||
errorPrefix, mixinObj.mutationToDom, 'mutationToDom');
|
||||
|
||||
var hasMutatorDialog =
|
||||
Blockly.Extensions.checkMutatorDialog_(mixinObj, errorPrefix);
|
||||
|
||||
if (opt_helperFn && !goog.isFunction(opt_helperFn)) {
|
||||
throw new Error('Extension "' + name + '" is not a function');
|
||||
}
|
||||
|
||||
// Sanity checks passed.
|
||||
Blockly.Extensions.register(name, function() {
|
||||
if (hasMutatorDialog) {
|
||||
this.setMutator(new Blockly.Mutator(opt_blockList));
|
||||
}
|
||||
// Mixin the object.
|
||||
this.mixin(mixinObj);
|
||||
|
||||
if (opt_helperFn) {
|
||||
opt_helperFn.apply(this);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies an extension method to a block. This should only be called during
|
||||
* block construction.
|
||||
* @param {string} name The name of the extension.
|
||||
* @param {!Blockly.Block} block The block to apply the named extension to.
|
||||
* @param {boolean} isMutator True if this extension defines a mutator.
|
||||
* @throws {Error} if the extension is not found.
|
||||
*/
|
||||
Blockly.Extensions.apply = function(name, block, isMutator) {
|
||||
var extensionFn = Blockly.Extensions.ALL_[name];
|
||||
if (!goog.isFunction(extensionFn)) {
|
||||
throw new Error('Error: Extension "' + name + '" not found.');
|
||||
}
|
||||
if (isMutator) {
|
||||
// Fail early if the block already has mutation properties.
|
||||
Blockly.Extensions.checkNoMutatorProperties_(name, block);
|
||||
} else {
|
||||
// Record the old properties so we can make sure they don't change after
|
||||
// applying the extension.
|
||||
var mutatorProperties = Blockly.Extensions.getMutatorProperties_(block);
|
||||
}
|
||||
extensionFn.apply(block);
|
||||
|
||||
if (isMutator) {
|
||||
var errorPrefix = 'Error after applying mutator "' + name + '": ';
|
||||
Blockly.Extensions.checkBlockHasMutatorProperties_(errorPrefix, block);
|
||||
} else {
|
||||
if (!Blockly.Extensions.mutatorPropertiesMatch_(mutatorProperties, block)) {
|
||||
throw new Error('Error when applying extension "' + name + '": ' +
|
||||
'mutation properties changed when applying a non-mutator extension.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the given value is a function.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @param {*} func Function to check.
|
||||
* @param {string} propertyName Which property to check.
|
||||
* @throws {Error} if the property does not exist or is not a function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkHasFunction_ = function(errorPrefix, func,
|
||||
propertyName) {
|
||||
if (!func) {
|
||||
throw new Error(errorPrefix +
|
||||
'missing required property "' + propertyName + '"');
|
||||
} else if (typeof func != 'function') {
|
||||
throw new Error(errorPrefix +
|
||||
'" required property "' + propertyName + '" must be a function');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the given block does not have any of the four mutator properties
|
||||
* defined on it. This function should be called before applying a mutator
|
||||
* extension to a block, to make sure we are not overwriting properties.
|
||||
* @param {string} mutationName The name of the mutation to reference in error
|
||||
* messages.
|
||||
* @param {!Blockly.Block} block The block to check.
|
||||
* @throws {Error} if any of the properties already exist on the block.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkNoMutatorProperties_ = function(mutationName, block) {
|
||||
var properties = Blockly.Extensions.getMutatorProperties_(block);
|
||||
if (properties.length) {
|
||||
throw new Error('Error: tried to apply mutation "' + mutationName +
|
||||
'" to a block that already has mutator functions.' +
|
||||
' Block id: ' + block.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the given object has both or neither of the functions required
|
||||
* to have a mutator dialog.
|
||||
* These functions are 'compose' and 'decompose'. If a block has one, it must
|
||||
* have both.
|
||||
* @param {!Object} object The object to check.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @return {boolean} True if the object has both functions. False if it has
|
||||
* neither function.
|
||||
* @throws {Error} if the object has only one of the functions.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkMutatorDialog_ = function(object, errorPrefix) {
|
||||
var hasCompose = object.compose !== undefined;
|
||||
var hasDecompose = object.decompose !== undefined;
|
||||
|
||||
if (hasCompose && hasDecompose) {
|
||||
if (typeof object.compose != 'function') {
|
||||
throw new Error(errorPrefix + 'compose must be a function.');
|
||||
} else if (typeof object.decompose != 'function') {
|
||||
throw new Error(errorPrefix + 'decompose must be a function.');
|
||||
}
|
||||
return true;
|
||||
} else if (!hasCompose && !hasDecompose) {
|
||||
return false;
|
||||
} else {
|
||||
throw new Error(errorPrefix +
|
||||
'Must have both or neither of "compose" and "decompose"');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that a block has required mutator properties. This should be called
|
||||
* after applying a mutation extension.
|
||||
* @param {string} errorPrefix The string to prepend to any error message.
|
||||
* @param {!Blockly.Block} block The block to inspect.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkBlockHasMutatorProperties_ = function(errorPrefix,
|
||||
block) {
|
||||
if (typeof block.domToMutation !== 'function') {
|
||||
throw new Error(errorPrefix + 'Applying a mutator didn\'t add "domToMutation"');
|
||||
}
|
||||
if (typeof block.mutationToDom != 'function') {
|
||||
throw new Error(errorPrefix +
|
||||
'Applying a mutator didn\'t add "mutationToDom"');
|
||||
}
|
||||
|
||||
// A block with a mutator isn't required to have a mutation dialog, but
|
||||
// it should still have both or neither of compose and decompose.
|
||||
Blockly.Extensions.checkMutatorDialog_(block, errorPrefix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of values of mutator properties on the given block.
|
||||
* @param {!Blockly.Block} block The block to inspect.
|
||||
* @return {!Array.<Object>} a list with all of the defined properties, which
|
||||
* should be functions, but may be anything other than undefined.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.getMutatorProperties_ = function(block) {
|
||||
var result = [];
|
||||
// List each function explicitly by reference to allow for renaming
|
||||
// during compilation.
|
||||
if (block.domToMutation !== undefined) {
|
||||
result.push(block.domToMutation);
|
||||
}
|
||||
if (block.mutationToDom !== undefined) {
|
||||
result.push(block.mutationToDom);
|
||||
}
|
||||
if (block.compose !== undefined) {
|
||||
result.push(block.compose);
|
||||
}
|
||||
if (block.decompose !== undefined) {
|
||||
result.push(block.decompose);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that the current mutator properties match a list of old mutator
|
||||
* properties. This should be called after applying a non-mutator extension,
|
||||
* to verify that the extension didn't change properties it shouldn't.
|
||||
* @param {!Array.<Object>} oldProperties The old values to compare to.
|
||||
* @param {!Blockly.Block} block The block to inspect for new values.
|
||||
* @return {boolean} True if the property lists match.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.mutatorPropertiesMatch_ = function(oldProperties, block) {
|
||||
var newProperties = Blockly.Extensions.getMutatorProperties_(block);
|
||||
if (newProperties.length != oldProperties.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < newProperties.length; i++) {
|
||||
if (oldProperties[i] != newProperties[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds an extension function that will map a dropdown value to a tooltip
|
||||
* string.
|
||||
*
|
||||
* This method includes multiple checks to ensure tooltips, dropdown options,
|
||||
* and message references are aligned. This aims to catch errors as early as
|
||||
* possible, without requiring developers to manually test tooltips under each
|
||||
* option. After the page is loaded, each tooltip text string will be checked
|
||||
* for matching message keys in the internationalized string table. Deferring
|
||||
* this until the page is loaded decouples loading dependencies. Later, upon
|
||||
* loading the first block of any given type, the extension will validate every
|
||||
* dropdown option has a matching tooltip in the lookupTable. Errors are
|
||||
* reported as warnings in the console, and are never fatal.
|
||||
* @param {string} dropdownName The name of the field whose value is the key
|
||||
* to the lookup table.
|
||||
* @param {!Object.<string, string>} lookupTable The table of field values to
|
||||
* tooltip text.
|
||||
* @return {Function} The extension function.
|
||||
*/
|
||||
Blockly.Extensions.buildTooltipForDropdown = function(dropdownName,
|
||||
lookupTable) {
|
||||
// List of block types already validated, to minimize duplicate warnings.
|
||||
var blockTypesChecked = [];
|
||||
|
||||
// Check the tooltip string messages for invalid references.
|
||||
// Wait for load, in case Blockly.Msg is not yet populated.
|
||||
// runAfterPageLoad() does not run in a Node.js environment due to lack of
|
||||
// document object, in which case skip the validation.
|
||||
if (document) { // Relies on document.readyState
|
||||
Blockly.utils.runAfterPageLoad(function() {
|
||||
for (var key in lookupTable) {
|
||||
// Will print warnings is reference is missing.
|
||||
Blockly.utils.checkMessageReferences(lookupTable[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual extension.
|
||||
* @this {Blockly.Block}
|
||||
*/
|
||||
var extensionFn = function() {
|
||||
if (this.type && blockTypesChecked.indexOf(this.type) === -1) {
|
||||
Blockly.Extensions.checkDropdownOptionsInTable_(
|
||||
this, dropdownName, lookupTable);
|
||||
blockTypesChecked.push(this.type);
|
||||
}
|
||||
|
||||
this.setTooltip(function() {
|
||||
var value = this.getFieldValue(dropdownName);
|
||||
var tooltip = lookupTable[value];
|
||||
if (tooltip == null) {
|
||||
if (blockTypesChecked.indexOf(this.type) === -1) {
|
||||
// Warn for missing values on generated tooltips.
|
||||
var warning = 'No tooltip mapping for value ' + value +
|
||||
' of field ' + dropdownName;
|
||||
if (this.type != null) {
|
||||
warning += (' of block type ' + this.type);
|
||||
}
|
||||
console.warn(warning + '.');
|
||||
}
|
||||
} else {
|
||||
tooltip = Blockly.utils.replaceMessageReferences(tooltip);
|
||||
}
|
||||
return tooltip;
|
||||
}.bind(this));
|
||||
};
|
||||
return extensionFn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks all options keys are present in the provided string lookup table.
|
||||
* Emits console warnings when they are not.
|
||||
* @param {!Blockly.Block} block The block containing the dropdown
|
||||
* @param {string} dropdownName The name of the dropdown
|
||||
* @param {!Object.<string, string>} lookupTable The string lookup table
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.checkDropdownOptionsInTable_ = function(block, dropdownName,
|
||||
lookupTable) {
|
||||
// Validate all dropdown options have values.
|
||||
var dropdown = block.getField(dropdownName);
|
||||
if (!dropdown.isOptionListDynamic()) {
|
||||
var options = dropdown.getOptions();
|
||||
for (var i = 0; i < options.length; ++i) {
|
||||
var optionKey = options[i][1]; // label, then value
|
||||
if (lookupTable[optionKey] == null) {
|
||||
console.warn('No tooltip mapping for value ' + optionKey +
|
||||
' of field ' + dropdownName + ' of block type ' + block.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds an extension function that will install a dynamic tooltip. The
|
||||
* tooltip message should include the string '%1' and that string will be
|
||||
* replaced with the value of the named field.
|
||||
* @param {string} msgTemplate The template form to of the message text, with
|
||||
* %1 placeholder.
|
||||
* @param {string} fieldName The field with the replacement value.
|
||||
* @returns {Function} The extension function.
|
||||
*/
|
||||
Blockly.Extensions.buildTooltipWithFieldValue =
|
||||
function(msgTemplate, fieldName) {
|
||||
// Check the tooltip string messages for invalid references.
|
||||
// Wait for load, in case Blockly.Msg is not yet populated.
|
||||
// runAfterPageLoad() does not run in a Node.js environment due to lack of
|
||||
// document object, in which case skip the validation.
|
||||
if (document) { // Relies on document.readyState
|
||||
Blockly.utils.runAfterPageLoad(function() {
|
||||
// Will print warnings is reference is missing.
|
||||
Blockly.utils.checkMessageReferences(msgTemplate);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual extension.
|
||||
* @this {Blockly.Block}
|
||||
*/
|
||||
var extensionFn = function() {
|
||||
this.setTooltip(function() {
|
||||
return Blockly.utils.replaceMessageReferences(msgTemplate)
|
||||
.replace('%1', this.getFieldValue(fieldName));
|
||||
}.bind(this));
|
||||
};
|
||||
return extensionFn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures the tooltip to mimic the parent block when connected. Otherwise,
|
||||
* uses the tooltip text at the time this extension is initialized. This takes
|
||||
* advantage of the fact that all other values from JSON are initialized before
|
||||
* extensions.
|
||||
* @this {Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Extensions.extensionParentTooltip_ = function() {
|
||||
this.tooltipWhenNotConnected_ = this.tooltip;
|
||||
this.setTooltip(function() {
|
||||
var parent = this.getParent();
|
||||
return (parent && parent.getInputsInline() && parent.tooltip) ||
|
||||
this.tooltipWhenNotConnected_;
|
||||
}.bind(this));
|
||||
};
|
||||
Blockly.Extensions.register('parent_tooltip_when_inline',
|
||||
Blockly.Extensions.extensionParentTooltip_);
|
||||
810
scratch-blocks/core/field.js
Normal file
810
scratch-blocks/core/field.js
Normal file
@@ -0,0 +1,810 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Field. Used for editable titles, variables, etc.
|
||||
* This is an abstract class that defines the UI on the block. Actual
|
||||
* instances would be Blockly.FieldTextInput, Blockly.FieldDropdown, etc.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Field');
|
||||
|
||||
goog.require('Blockly.Events.BlockChange');
|
||||
goog.require('Blockly.Gesture');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Size');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for an editable field.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns either the accepted text, a replacement
|
||||
* text, or null to abort the change.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.Field = function(text, opt_validator) {
|
||||
this.size_ = new goog.math.Size(
|
||||
Blockly.BlockSvg.FIELD_WIDTH,
|
||||
Blockly.BlockSvg.FIELD_HEIGHT);
|
||||
this.setValue(text);
|
||||
this.setValidator(opt_validator);
|
||||
|
||||
/**
|
||||
* Maximum characters of text to display before adding an ellipsis.
|
||||
* Same for strings and numbers.
|
||||
* @type {number}
|
||||
*/
|
||||
this.maxDisplayLength = Blockly.BlockSvg.MAX_DISPLAY_LENGTH;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The set of all registered fields, keyed by field type as used in the JSON
|
||||
* definition of a block.
|
||||
* @type {!Object<string, !{fromJson: Function}>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.TYPE_MAP_ = {};
|
||||
|
||||
/**
|
||||
* Registers a field type. May also override an existing field type.
|
||||
* Blockly.Field.fromJson uses this registry to find the appropriate field.
|
||||
* @param {!string} type The field type name as used in the JSON definition.
|
||||
* @param {!{fromJson: Function}} fieldClass The field class containing a
|
||||
* fromJson function that can construct an instance of the field.
|
||||
* @throws {Error} if the type name is empty, or the fieldClass is not an
|
||||
* object containing a fromJson function.
|
||||
*/
|
||||
Blockly.Field.register = function(type, fieldClass) {
|
||||
if (!goog.isString(type) || goog.string.isEmptyOrWhitespace(type)) {
|
||||
throw new Error('Invalid field type "' + type + '"');
|
||||
}
|
||||
if (!goog.isObject(fieldClass) || !goog.isFunction(fieldClass.fromJson)) {
|
||||
throw new Error('Field "' + fieldClass +
|
||||
'" must have a fromJson function');
|
||||
}
|
||||
Blockly.Field.TYPE_MAP_[type] = fieldClass;
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a Field from a JSON arg object.
|
||||
* Finds the appropriate registered field by the type name as registered using
|
||||
* Blockly.Field.register.
|
||||
* @param {!Object} options A JSON object with a type and options specific
|
||||
* to the field type.
|
||||
* @returns {?Blockly.Field} The new field instance or null if a field wasn't
|
||||
* found with the given type name
|
||||
* @package
|
||||
*/
|
||||
Blockly.Field.fromJson = function(options) {
|
||||
var fieldClass = Blockly.Field.TYPE_MAP_[options['type']];
|
||||
if (fieldClass) {
|
||||
return fieldClass.fromJson(options);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporary cache of text widths.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.cacheWidths_ = null;
|
||||
|
||||
/**
|
||||
* Number of current references to cache.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.cacheReference_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Name of field. Unique within each block.
|
||||
* Static labels are usually unnamed.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
Blockly.Field.prototype.name = undefined;
|
||||
|
||||
/**
|
||||
* CSS class name for the text element.
|
||||
* @type {string}
|
||||
* @package
|
||||
*/
|
||||
Blockly.Field.prototype.className_ = 'blocklyText';
|
||||
|
||||
/**
|
||||
* Visible text to display.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.text_ = '';
|
||||
|
||||
/**
|
||||
* Block this field is attached to. Starts as null, then in set in init.
|
||||
* @type {Blockly.Block}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.sourceBlock_ = null;
|
||||
|
||||
/**
|
||||
* Is the field visible, or hidden due to the block being collapsed?
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.visible_ = true;
|
||||
|
||||
/**
|
||||
* Null, or an array of the field's argTypes (for styling).
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.argType_ = null;
|
||||
|
||||
/**
|
||||
* Validation function called when user edits an editable field.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.validator_ = null;
|
||||
|
||||
/**
|
||||
* Whether to assume user is using a touch device for interactions.
|
||||
* Used to show different UI for touch interactions, e.g.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.useTouchInteraction_ = false;
|
||||
|
||||
/**
|
||||
* Non-breaking space.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Field.NBSP = '\u00A0';
|
||||
|
||||
/**
|
||||
* Text offset used for IE/Edge.
|
||||
* @const
|
||||
*/
|
||||
Blockly.Field.IE_TEXT_OFFSET = '0.3em';
|
||||
|
||||
/**
|
||||
* Editable fields usually show some sort of UI for the user to change them.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.Field.prototype.EDITABLE = true;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the XML renderer, non-serializable fields
|
||||
* are not. Editable fields should be serialized.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.Field.prototype.SERIALIZABLE = true;
|
||||
|
||||
/**
|
||||
* Attach this field to a block.
|
||||
* @param {!Blockly.Block} block The block containing this field.
|
||||
*/
|
||||
Blockly.Field.prototype.setSourceBlock = function(block) {
|
||||
goog.asserts.assert(!this.sourceBlock_, 'Field already bound to a block.');
|
||||
this.sourceBlock_ = block;
|
||||
};
|
||||
|
||||
/**
|
||||
* Install this field on a block.
|
||||
*/
|
||||
Blockly.Field.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Field has already been initialized once.
|
||||
return;
|
||||
}
|
||||
// Build the DOM.
|
||||
this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
if (!this.visible_) {
|
||||
this.fieldGroup_.style.display = 'none';
|
||||
}
|
||||
// Add an attribute to cassify the type of field.
|
||||
if (this.getArgTypes() !== null) {
|
||||
if (this.sourceBlock_.isShadow()) {
|
||||
this.sourceBlock_.svgGroup_.setAttribute('data-argument-type',
|
||||
this.getArgTypes());
|
||||
} else {
|
||||
// Fields without a shadow wrapper, like square dropdowns.
|
||||
this.fieldGroup_.setAttribute('data-argument-type', this.getArgTypes());
|
||||
}
|
||||
}
|
||||
// Adjust X to be flipped for RTL. Position is relative to horizontal start of source block.
|
||||
var size = this.getSize();
|
||||
var fieldX = (this.sourceBlock_.RTL) ? -size.width / 2 : size.width / 2;
|
||||
/** @type {!Element} */
|
||||
this.textElement_ = Blockly.utils.createSvgElement('text',
|
||||
{
|
||||
'class': this.className_,
|
||||
'x': fieldX,
|
||||
'y': size.height / 2 + Blockly.BlockSvg.FIELD_TOP_PADDING,
|
||||
'dominant-baseline': 'middle',
|
||||
'dy': goog.userAgent.EDGE_OR_IE ? Blockly.Field.IE_TEXT_OFFSET : '0',
|
||||
'text-anchor': 'middle'
|
||||
}, this.fieldGroup_);
|
||||
|
||||
this.updateEditable();
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
||||
// Force a render.
|
||||
this.render_();
|
||||
this.size_.width = 0;
|
||||
this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(
|
||||
this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the model of the field after it has been installed on a block.
|
||||
* No-op by default.
|
||||
*/
|
||||
Blockly.Field.prototype.initModel = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all DOM objects belonging to this editable field.
|
||||
*/
|
||||
Blockly.Field.prototype.dispose = function() {
|
||||
if (this.mouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(this.mouseDownWrapper_);
|
||||
this.mouseDownWrapper_ = null;
|
||||
}
|
||||
this.sourceBlock_ = null;
|
||||
goog.dom.removeNode(this.fieldGroup_);
|
||||
this.fieldGroup_ = null;
|
||||
this.textElement_ = null;
|
||||
this.validator_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add or remove the UI indicating if this field is editable or not.
|
||||
*/
|
||||
Blockly.Field.prototype.updateEditable = function() {
|
||||
var group = this.fieldGroup_;
|
||||
if (!this.EDITABLE || !group) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_.isEditable()) {
|
||||
Blockly.utils.addClass(group, 'blocklyEditableText');
|
||||
Blockly.utils.removeClass(group, 'blocklyNonEditableText');
|
||||
this.fieldGroup_.style.cursor = this.CURSOR;
|
||||
} else {
|
||||
Blockly.utils.addClass(group, 'blocklyNonEditableText');
|
||||
Blockly.utils.removeClass(group, 'blocklyEditableText');
|
||||
this.fieldGroup_.style.cursor = '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether this field is currently editable. Some fields are never
|
||||
* editable (e.g. text labels). Those fields are not serialized to XML. Other
|
||||
* fields may be editable, and therefore serialized, but may exist on
|
||||
* non-editable blocks.
|
||||
* @return {boolean} whether this field is editable and on an editable block
|
||||
*/
|
||||
Blockly.Field.prototype.isCurrentlyEditable = function() {
|
||||
return this.EDITABLE && !!this.sourceBlock_ && this.sourceBlock_.isEditable();
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets whether this editable field is visible or not.
|
||||
* @return {boolean} True if visible.
|
||||
*/
|
||||
Blockly.Field.prototype.isVisible = function() {
|
||||
return this.visible_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets whether this editable field is visible or not.
|
||||
* @param {boolean} visible True if visible.
|
||||
*/
|
||||
Blockly.Field.prototype.setVisible = function(visible) {
|
||||
if (this.visible_ == visible) {
|
||||
return;
|
||||
}
|
||||
this.visible_ = visible;
|
||||
var root = this.getSvgRoot();
|
||||
if (root) {
|
||||
root.style.display = visible ? 'block' : 'none';
|
||||
this.render_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a string to the field's array of argTypes (used for styling).
|
||||
* @param {string} argType New argType.
|
||||
*/
|
||||
Blockly.Field.prototype.addArgType = function(argType) {
|
||||
if (this.argType_ == null) {
|
||||
this.argType_ = [];
|
||||
}
|
||||
this.argType_.push(argType);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the field's argTypes joined as a string, or returns null (used for styling).
|
||||
* @return {string} argType string, or null.
|
||||
*/
|
||||
Blockly.Field.prototype.getArgTypes = function() {
|
||||
if (this.argType_ === null || this.argType_.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
return this.argType_.join(' ');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a new validation function for editable fields.
|
||||
* @param {Function} handler New validation function, or null.
|
||||
*/
|
||||
Blockly.Field.prototype.setValidator = function(handler) {
|
||||
this.validator_ = handler;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the validation function for editable fields.
|
||||
* @return {Function} Validation function, or null.
|
||||
*/
|
||||
Blockly.Field.prototype.getValidator = function() {
|
||||
return this.validator_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a change. Does nothing. Subclasses may override this.
|
||||
* @param {string} text The user's text.
|
||||
* @return {string} No change needed.
|
||||
*/
|
||||
Blockly.Field.prototype.classValidator = function(text) {
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls the validation function for this field, as well as all the validation
|
||||
* function for the field's class and its parents.
|
||||
* @param {string} text Proposed text.
|
||||
* @return {?string} Revised text, or null if invalid.
|
||||
*/
|
||||
Blockly.Field.prototype.callValidator = function(text) {
|
||||
var classResult = this.classValidator(text);
|
||||
if (classResult === null) {
|
||||
// Class validator rejects value. Game over.
|
||||
return null;
|
||||
} else if (classResult !== undefined) {
|
||||
text = classResult;
|
||||
}
|
||||
var userValidator = this.getValidator();
|
||||
if (userValidator) {
|
||||
var userResult = userValidator.call(this, text);
|
||||
if (userResult === null) {
|
||||
// User validator rejects value. Game over.
|
||||
return null;
|
||||
} else if (userResult !== undefined) {
|
||||
text = userResult;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the group element for this editable field.
|
||||
* Used for measuring the size and for positioning.
|
||||
* @return {!Element} The group element.
|
||||
*/
|
||||
Blockly.Field.prototype.getSvgRoot = function() {
|
||||
return /** @type {!Element} */ (this.fieldGroup_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the border with the correct width.
|
||||
* Saves the computed width in a property.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.render_ = function() {
|
||||
if (this.visible_ && this.textElement_) {
|
||||
// Replace the text.
|
||||
this.textElement_.textContent = this.getDisplayText_();
|
||||
this.updateWidth();
|
||||
|
||||
// Update text centering, based on newly calculated width.
|
||||
var centerTextX = (this.size_.width - this.arrowWidth_) / 2;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
centerTextX += this.arrowWidth_;
|
||||
}
|
||||
|
||||
// In a text-editing shadow block's field,
|
||||
// if half the text length is not at least center of
|
||||
// visible field (FIELD_WIDTH), center it there instead,
|
||||
// unless there is a drop-down arrow.
|
||||
if (this.sourceBlock_.isShadow() && !this.positionArrow) {
|
||||
var minOffset = Blockly.BlockSvg.FIELD_WIDTH / 2;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
// X position starts at the left edge of the block, in both RTL and LTR.
|
||||
// First offset by the width of the block to move to the right edge,
|
||||
// and then subtract to move to the same position as LTR.
|
||||
var minCenter = this.size_.width - minOffset;
|
||||
centerTextX = Math.min(minCenter, centerTextX);
|
||||
} else {
|
||||
// (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2
|
||||
// if the text is longer.
|
||||
centerTextX = Math.max(minOffset, centerTextX);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply new text element x position.
|
||||
this.textElement_.setAttribute('x', centerTextX);
|
||||
}
|
||||
|
||||
// Update any drawn box to the correct width and height.
|
||||
if (this.box_) {
|
||||
this.box_.setAttribute('width', this.size_.width);
|
||||
this.box_.setAttribute('height', this.size_.height);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the width of the field. This calls getCachedWidth which won't cache
|
||||
* the approximated width on IE/Edge when `getComputedTextLength` fails. Once
|
||||
* it eventually does succeed, the result will be cached.
|
||||
**/
|
||||
Blockly.Field.prototype.updateWidth = function() {
|
||||
// Calculate width of field
|
||||
var width = Blockly.Field.getCachedWidth(this.textElement_);
|
||||
|
||||
// Add padding to left and right of text.
|
||||
if (this.EDITABLE) {
|
||||
width += Blockly.BlockSvg.EDITABLE_FIELD_PADDING;
|
||||
}
|
||||
|
||||
// Adjust width for drop-down arrows.
|
||||
this.arrowWidth_ = 0;
|
||||
if (this.positionArrow) {
|
||||
this.arrowWidth_ = this.positionArrow(width);
|
||||
width += this.arrowWidth_;
|
||||
}
|
||||
|
||||
// Add padding to any drawn box.
|
||||
if (this.box_) {
|
||||
width += 2 * Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||
}
|
||||
|
||||
// Set width of the field.
|
||||
this.size_.width = width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the width of a text element, caching it in the process.
|
||||
* @param {!Element} textElement An SVG 'text' element.
|
||||
* @return {number} Width of element.
|
||||
*/
|
||||
Blockly.Field.getCachedWidth = function(textElement) {
|
||||
var key = textElement.textContent + '\n' + textElement.className.baseVal;
|
||||
var width;
|
||||
|
||||
// Return the cached width if it exists.
|
||||
if (Blockly.Field.cacheWidths_) {
|
||||
width = Blockly.Field.cacheWidths_[key];
|
||||
if (width) {
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to compute fetch the width of the SVG text element.
|
||||
try {
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
width = textElement.getBBox().width;
|
||||
} else {
|
||||
width = textElement.getComputedTextLength();
|
||||
}
|
||||
} catch (e) {
|
||||
// In other cases where we fail to geth the computed text. Instead, use an
|
||||
// approximation and do not cache the result. At some later point in time
|
||||
// when the block is inserted into the visible DOM, this method will be
|
||||
// called again and, at that point in time, will not throw an exception.
|
||||
return textElement.textContent.length * 8;
|
||||
}
|
||||
|
||||
// Cache the computed width and return.
|
||||
if (Blockly.Field.cacheWidths_) {
|
||||
Blockly.Field.cacheWidths_[key] = width;
|
||||
}
|
||||
return width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start caching field widths. Every call to this function MUST also call
|
||||
* stopCache. Caches must not survive between execution threads.
|
||||
*/
|
||||
Blockly.Field.startCache = function() {
|
||||
Blockly.Field.cacheReference_++;
|
||||
if (!Blockly.Field.cacheWidths_) {
|
||||
Blockly.Field.cacheWidths_ = {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop caching field widths. Unless caching was already on when the
|
||||
* corresponding call to startCache was made.
|
||||
*/
|
||||
Blockly.Field.stopCache = function() {
|
||||
Blockly.Field.cacheReference_--;
|
||||
if (!Blockly.Field.cacheReference_) {
|
||||
Blockly.Field.cacheWidths_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the height and width of the field.
|
||||
* @return {!goog.math.Size} Height and width.
|
||||
*/
|
||||
Blockly.Field.prototype.getSize = function() {
|
||||
if (!this.size_.width) {
|
||||
this.render_();
|
||||
}
|
||||
return this.size_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the bounding box of the rendered field, accounting for workspace
|
||||
* scaling.
|
||||
* @return {!Object} An object with top, bottom, left, and right in pixels
|
||||
* relative to the top left corner of the page (window coordinates).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.getScaledBBox_ = function() {
|
||||
var size = this.getSize();
|
||||
var scaledHeight = size.height * this.sourceBlock_.workspace.scale;
|
||||
var scaledWidth = size.width * this.sourceBlock_.workspace.scale;
|
||||
var xy = this.getAbsoluteXY_();
|
||||
return {
|
||||
top: xy.y,
|
||||
bottom: xy.y + scaledHeight,
|
||||
left: xy.x,
|
||||
right: xy.x + scaledWidth
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field as displayed on screen. May differ from getText
|
||||
* due to ellipsis, and other formatting.
|
||||
* @return {string} Currently displayed text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.getDisplayText_ = function() {
|
||||
var text = this.text_;
|
||||
if (!text) {
|
||||
// Prevent the field from disappearing if empty.
|
||||
return Blockly.Field.NBSP;
|
||||
}
|
||||
if (text.length > this.maxDisplayLength) {
|
||||
// Truncate displayed string and add an ellipsis ('...').
|
||||
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
|
||||
}
|
||||
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
|
||||
text = text.replace(/\s/g, Blockly.Field.NBSP);
|
||||
if (this.sourceBlock_.RTL) {
|
||||
// The SVG is LTR, force text to be RTL unless a number.
|
||||
if (this.sourceBlock_.editable_ && this.sourceBlock_.type === 'math_number') {
|
||||
text = '\u202A' + text + '\u202C';
|
||||
} else {
|
||||
text = '\u202B' + text + '\u202C';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field.
|
||||
* @return {string} Current text.
|
||||
*/
|
||||
Blockly.Field.prototype.getText = function() {
|
||||
return this.text_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the text in this field. Trigger a rerender of the source block.
|
||||
* @param {*} newText New text.
|
||||
*/
|
||||
Blockly.Field.prototype.setText = function(newText) {
|
||||
if (newText === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
newText = String(newText);
|
||||
if (newText === this.text_) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
this.text_ = newText;
|
||||
this.forceRerender();
|
||||
};
|
||||
|
||||
/**
|
||||
* Force a rerender of the block that this field is installed on, which will
|
||||
* rerender this field and adjust for any sizing changes.
|
||||
* Other fields on the same block will not rerender, because their sizes have
|
||||
* already been recorded.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Field.prototype.forceRerender = function() {
|
||||
// Set width to 0 to force a rerender of this field.
|
||||
this.size_.width = 0;
|
||||
|
||||
if (this.sourceBlock_ && this.sourceBlock_.rendered) {
|
||||
this.sourceBlock_.render();
|
||||
this.sourceBlock_.bumpNeighbours_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the text node of this field to display the current text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.updateTextNode_ = function() {
|
||||
if (!this.textElement_) {
|
||||
// Not rendered yet.
|
||||
return;
|
||||
}
|
||||
var text = this.text_;
|
||||
if (text.length > this.maxDisplayLength) {
|
||||
// Truncate displayed string and add an ellipsis ('...').
|
||||
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
|
||||
// Add special class for sizing font when truncated
|
||||
this.textElement_.setAttribute('class', this.className_ + ' blocklyTextTruncated');
|
||||
} else {
|
||||
this.textElement_.setAttribute('class', this.className_);
|
||||
}
|
||||
// Empty the text element.
|
||||
goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_));
|
||||
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
|
||||
text = text.replace(/\s/g, Blockly.Field.NBSP);
|
||||
if (this.sourceBlock_.RTL && text) {
|
||||
// The SVG is LTR, force text to be RTL.
|
||||
if (this.sourceBlock_.editable_ && this.sourceBlock_.type === 'math_number') {
|
||||
text = '\u202A' + text + '\u202C';
|
||||
} else {
|
||||
text = '\u202B' + text + '\u202C';
|
||||
}
|
||||
}
|
||||
if (!text) {
|
||||
// Prevent the field from disappearing if empty.
|
||||
text = Blockly.Field.NBSP;
|
||||
}
|
||||
var textNode = document.createTextNode(text);
|
||||
this.textElement_.appendChild(textNode);
|
||||
|
||||
// Cached width is obsolete. Clear it.
|
||||
this.size_.width = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* By default there is no difference between the human-readable text and
|
||||
* the language-neutral values. Subclasses (such as dropdown) may define this.
|
||||
* @return {string} Current value.
|
||||
*/
|
||||
Blockly.Field.prototype.getValue = function() {
|
||||
return this.getText();
|
||||
};
|
||||
|
||||
/**
|
||||
* By default there is no difference between the human-readable text and
|
||||
* the language-neutral values. Subclasses (such as dropdown) may define this.
|
||||
* @param {string} newValue New value.
|
||||
*/
|
||||
Blockly.Field.prototype.setValue = function(newValue) {
|
||||
if (newValue === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
var oldValue = this.getValue();
|
||||
if (oldValue == newValue) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, oldValue, newValue));
|
||||
}
|
||||
this.setText(newValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a mouse down event on a field.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.onMouseDown_ = function(e) {
|
||||
if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_.workspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
var gesture = this.sourceBlock_.workspace.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartField(this);
|
||||
}
|
||||
this.useTouchInteraction_ = Blockly.Touch.getTouchIdentifierFromEvent(e) !== 'mouse';
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the tooltip text for this field.
|
||||
* @param {string|!Element} _newTip Text for tooltip or a parent element to
|
||||
* link to for its tooltip.
|
||||
* @abstract
|
||||
*/
|
||||
Blockly.Field.prototype.setTooltip = function(_newTip) {
|
||||
// Non-abstract sub-classes may wish to implement this. See FieldLabel.
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the element to bind the click handler to. When this element is
|
||||
* clicked on an editable field, the editor will open.
|
||||
*
|
||||
* If the block has only one field and no output connection, we handle clicks
|
||||
* over the whole block. Otherwise, handle clicks over the the group containing
|
||||
* the field.
|
||||
*
|
||||
* @return {!Element} Element to bind click handler to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.getClickTarget_ = function() {
|
||||
var nFields = 0;
|
||||
|
||||
for (var i = 0, input; input = this.sourceBlock_.inputList[i]; i++) {
|
||||
nFields += input.fieldRow.length;
|
||||
}
|
||||
if (nFields <= 1 && this.sourceBlock_.outputConnection) {
|
||||
return this.sourceBlock_.getSvgRoot();
|
||||
} else {
|
||||
return this.getSvgRoot();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the absolute coordinates of the top-left corner of this field.
|
||||
* The origin (0,0) is the top-left corner of the page body.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.getAbsoluteXY_ = function() {
|
||||
return goog.style.getPageOffset(this.getClickTarget_());
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this field references any Blockly variables. If true it may need to
|
||||
* be handled differently during serialization and deserialization. Subclasses
|
||||
* may override this.
|
||||
* @return {boolean} True if this field has any variable references.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Field.prototype.referencesVariables = function() {
|
||||
return false;
|
||||
};
|
||||
398
scratch-blocks/core/field_angle.js
Normal file
398
scratch-blocks/core/field_angle.js
Normal file
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2013 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Angle input field.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldAngle');
|
||||
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('goog.math');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for an editable angle field.
|
||||
* @param {(string|number)=} opt_value The initial content of the field. The
|
||||
* value should cast to a number, and if it does not, '0' will be used.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns the accepted text or null to abort
|
||||
* the change.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldAngle = function(opt_value, opt_validator) {
|
||||
// Add degree symbol: '360°' (LTR) or '°360' (RTL)
|
||||
this.symbol_ = Blockly.utils.createSvgElement('tspan', {}, null);
|
||||
this.symbol_.appendChild(document.createTextNode('\u00B0'));
|
||||
|
||||
var numRestrictor = new RegExp("[\\d]|[\\.]|[-]|[eE]");
|
||||
|
||||
opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
|
||||
Blockly.FieldAngle.superClass_.constructor.call(
|
||||
this, opt_value, opt_validator, numRestrictor);
|
||||
this.addArgType('angle');
|
||||
};
|
||||
goog.inherits(Blockly.FieldAngle, Blockly.FieldTextInput);
|
||||
|
||||
/**
|
||||
* Construct a FieldAngle from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (angle).
|
||||
* @returns {!Blockly.FieldAngle} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldAngle.fromJson = function(options) {
|
||||
return new Blockly.FieldAngle(options['angle']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Round angles to the nearest 15 degrees when using mouse.
|
||||
* Set to 0 to disable rounding.
|
||||
*/
|
||||
Blockly.FieldAngle.ROUND = 15;
|
||||
|
||||
/**
|
||||
* Half the width of protractor image.
|
||||
*/
|
||||
Blockly.FieldAngle.HALF = 120 / 2;
|
||||
|
||||
/* The following two settings work together to set the behaviour of the angle
|
||||
* picker. While many combinations are possible, two modes are typical:
|
||||
* Math mode.
|
||||
* 0 deg is right, 90 is up. This is the style used by protractors.
|
||||
* Blockly.FieldAngle.CLOCKWISE = false;
|
||||
* Blockly.FieldAngle.OFFSET = 0;
|
||||
* Compass mode.
|
||||
* 0 deg is up, 90 is right. This is the style used by maps.
|
||||
* Blockly.FieldAngle.CLOCKWISE = true;
|
||||
* Blockly.FieldAngle.OFFSET = 90;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Angle increases clockwise (true) or counterclockwise (false).
|
||||
*/
|
||||
Blockly.FieldAngle.CLOCKWISE = true;
|
||||
|
||||
/**
|
||||
* Offset the location of 0 degrees (and all angles) by a constant.
|
||||
* Usually either 0 (0 = right) or 90 (0 = up).
|
||||
*/
|
||||
Blockly.FieldAngle.OFFSET = 90;
|
||||
|
||||
/**
|
||||
* Maximum allowed angle before wrapping.
|
||||
* Usually either 360 (for 0 to 359.9) or 180 (for -179.9 to 180).
|
||||
*/
|
||||
Blockly.FieldAngle.WRAP = 180;
|
||||
|
||||
/**
|
||||
* Radius of drag handle
|
||||
*/
|
||||
Blockly.FieldAngle.HANDLE_RADIUS = 10;
|
||||
|
||||
/**
|
||||
* Width of drag handle arrow
|
||||
*/
|
||||
Blockly.FieldAngle.ARROW_WIDTH = Blockly.FieldAngle.HANDLE_RADIUS;
|
||||
|
||||
/**
|
||||
* Half the stroke-width used for the "glow" around the drag handle, rounded up to nearest whole pixel
|
||||
*/
|
||||
|
||||
Blockly.FieldAngle.HANDLE_GLOW_WIDTH = 3;
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
*/
|
||||
Blockly.FieldAngle.RADIUS = Blockly.FieldAngle.HALF
|
||||
- Blockly.FieldAngle.HANDLE_RADIUS - Blockly.FieldAngle.HANDLE_GLOW_WIDTH;
|
||||
|
||||
/**
|
||||
* Radius of central dot circle.
|
||||
*/
|
||||
Blockly.FieldAngle.CENTER_RADIUS = 2;
|
||||
|
||||
/**
|
||||
* Path to the arrow svg icon.
|
||||
*/
|
||||
Blockly.FieldAngle.ARROW_SVG_PATH = 'icons/arrow.svg';
|
||||
|
||||
/**
|
||||
* Clean up this FieldAngle, as well as the inherited FieldTextInput.
|
||||
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.dispose_ = function() {
|
||||
var thisField = this;
|
||||
return function() {
|
||||
Blockly.FieldAngle.superClass_.dispose_.call(thisField)();
|
||||
thisField.gauge_ = null;
|
||||
if (thisField.mouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.mouseDownWrapper_);
|
||||
}
|
||||
if (thisField.mouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.mouseUpWrapper_);
|
||||
}
|
||||
if (thisField.mouseMoveWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.mouseMoveWrapper_);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the inline free-text editor on top of the text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.showEditor_ = function() {
|
||||
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
||||
Blockly.FieldAngle.superClass_.showEditor_.call(this, this.useTouchInteraction_);
|
||||
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
var div = Blockly.DropDownDiv.getContentDiv();
|
||||
// Build the SVG DOM.
|
||||
var svg = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'height': (Blockly.FieldAngle.HALF * 2) + 'px',
|
||||
'width': (Blockly.FieldAngle.HALF * 2) + 'px'
|
||||
}, div);
|
||||
Blockly.utils.createSvgElement('circle', {
|
||||
'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF,
|
||||
'r': Blockly.FieldAngle.RADIUS,
|
||||
'class': 'blocklyAngleCircle'
|
||||
}, svg);
|
||||
this.gauge_ = Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyAngleGauge'}, svg);
|
||||
// The moving line, x2 and y2 are set in updateGraph_
|
||||
this.line_ = Blockly.utils.createSvgElement('line',{
|
||||
'x1': Blockly.FieldAngle.HALF,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'class': 'blocklyAngleLine'
|
||||
}, svg);
|
||||
// The fixed vertical line at the offset
|
||||
var offsetRadians = Math.PI * Blockly.FieldAngle.OFFSET / 180;
|
||||
Blockly.utils.createSvgElement('line', {
|
||||
'x1': Blockly.FieldAngle.HALF,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS * Math.cos(offsetRadians),
|
||||
'y2': Blockly.FieldAngle.HALF - Blockly.FieldAngle.RADIUS * Math.sin(offsetRadians),
|
||||
'class': 'blocklyAngleLine'
|
||||
}, svg);
|
||||
// Draw markers around the edge.
|
||||
for (var angle = 0; angle < 360; angle += 15) {
|
||||
Blockly.utils.createSvgElement('line', {
|
||||
'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS - 13,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS - 7,
|
||||
'y2': Blockly.FieldAngle.HALF,
|
||||
'class': 'blocklyAngleMarks',
|
||||
'transform': 'rotate(' + angle + ',' +
|
||||
Blockly.FieldAngle.HALF + ',' + Blockly.FieldAngle.HALF + ')'
|
||||
}, svg);
|
||||
}
|
||||
// Center point
|
||||
Blockly.utils.createSvgElement('circle', {
|
||||
'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF,
|
||||
'r': Blockly.FieldAngle.CENTER_RADIUS,
|
||||
'class': 'blocklyAngleCenterPoint'
|
||||
}, svg);
|
||||
// Handle group: a circle and the arrow image
|
||||
this.handle_ = Blockly.utils.createSvgElement('g', {}, svg);
|
||||
Blockly.utils.createSvgElement('circle', {
|
||||
'cx': 0,
|
||||
'cy': 0,
|
||||
'r': Blockly.FieldAngle.HANDLE_RADIUS,
|
||||
'class': 'blocklyAngleDragHandle'
|
||||
}, this.handle_);
|
||||
this.arrowSvg_ = Blockly.utils.createSvgElement('image',
|
||||
{
|
||||
'width': Blockly.FieldAngle.ARROW_WIDTH,
|
||||
'height': Blockly.FieldAngle.ARROW_WIDTH,
|
||||
'x': -Blockly.FieldAngle.ARROW_WIDTH / 2,
|
||||
'y': -Blockly.FieldAngle.ARROW_WIDTH / 2,
|
||||
'class': 'blocklyAngleDragArrow'
|
||||
},
|
||||
this.handle_);
|
||||
this.arrowSvg_.setAttributeNS(
|
||||
'http://www.w3.org/1999/xlink',
|
||||
'xlink:href',
|
||||
Blockly.mainWorkspace.options.pathToMedia + Blockly.FieldAngle.ARROW_SVG_PATH
|
||||
);
|
||||
|
||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.parentBlock_.getColour(),
|
||||
this.sourceBlock_.getColourTertiary());
|
||||
Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
|
||||
Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
|
||||
|
||||
this.mouseDownWrapper_ =
|
||||
Blockly.bindEvent_(this.handle_, 'mousedown', this, this.onMouseDown);
|
||||
|
||||
this.updateGraph_();
|
||||
};
|
||||
/**
|
||||
* Set the angle to match the mouse's position.
|
||||
* @param {!Event} e Mouse move event.
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.onMouseDown = function() {
|
||||
this.mouseMoveWrapper_ = Blockly.bindEvent_(document.body, 'mousemove', this, this.onMouseMove);
|
||||
this.mouseUpWrapper_ = Blockly.bindEvent_(document.body, 'mouseup', this, this.onMouseUp);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the angle to match the mouse's position.
|
||||
* @param {!Event} e Mouse move event.
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.onMouseUp = function() {
|
||||
Blockly.unbindEvent_(this.mouseMoveWrapper_);
|
||||
Blockly.unbindEvent_(this.mouseUpWrapper_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the angle to match the mouse's position.
|
||||
* @param {!Event} e Mouse move event.
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.onMouseMove = function(e) {
|
||||
e.preventDefault();
|
||||
var bBox = this.gauge_.ownerSVGElement.getBoundingClientRect();
|
||||
var dx = e.clientX - bBox.left - Blockly.FieldAngle.HALF;
|
||||
var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF;
|
||||
var angle = Math.atan(-dy / dx);
|
||||
if (isNaN(angle)) {
|
||||
// This shouldn't happen, but let's not let this error propagate further.
|
||||
return;
|
||||
}
|
||||
angle = goog.math.toDegrees(angle);
|
||||
// 0: East, 90: North, 180: West, 270: South.
|
||||
if (dx < 0) {
|
||||
angle += 180;
|
||||
} else if (dy > 0) {
|
||||
angle += 360;
|
||||
}
|
||||
if (Blockly.FieldAngle.CLOCKWISE) {
|
||||
angle = Blockly.FieldAngle.OFFSET + 360 - angle;
|
||||
} else {
|
||||
angle -= Blockly.FieldAngle.OFFSET;
|
||||
}
|
||||
if (Blockly.FieldAngle.ROUND) {
|
||||
angle = Math.round(angle / Blockly.FieldAngle.ROUND) *
|
||||
Blockly.FieldAngle.ROUND;
|
||||
}
|
||||
angle = this.callValidator(angle);
|
||||
Blockly.FieldTextInput.htmlInput_.value = angle;
|
||||
this.setValue(angle);
|
||||
this.validate_();
|
||||
this.resizeEditor_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert a degree symbol.
|
||||
* @param {?string} text New text.
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.setText = function(text) {
|
||||
Blockly.FieldAngle.superClass_.setText.call(this, text);
|
||||
if (!this.textElement_) {
|
||||
// Not rendered yet.
|
||||
return;
|
||||
}
|
||||
this.updateGraph_();
|
||||
// Cached width is obsolete. Clear it.
|
||||
this.size_.width = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redraw the graph with the current angle.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.updateGraph_ = function() {
|
||||
if (!this.gauge_) {
|
||||
return;
|
||||
}
|
||||
var angleDegrees = Number(this.getText()) % 360 + Blockly.FieldAngle.OFFSET;
|
||||
var angleRadians = goog.math.toRadians(angleDegrees);
|
||||
var path = ['M ', Blockly.FieldAngle.HALF, ',', Blockly.FieldAngle.HALF];
|
||||
var x2 = Blockly.FieldAngle.HALF;
|
||||
var y2 = Blockly.FieldAngle.HALF;
|
||||
if (!isNaN(angleRadians)) {
|
||||
var angle1 = goog.math.toRadians(Blockly.FieldAngle.OFFSET);
|
||||
var x1 = Math.cos(angle1) * Blockly.FieldAngle.RADIUS;
|
||||
var y1 = Math.sin(angle1) * -Blockly.FieldAngle.RADIUS;
|
||||
if (Blockly.FieldAngle.CLOCKWISE) {
|
||||
angleRadians = 2 * angle1 - angleRadians;
|
||||
}
|
||||
x2 += Math.cos(angleRadians) * Blockly.FieldAngle.RADIUS;
|
||||
y2 -= Math.sin(angleRadians) * Blockly.FieldAngle.RADIUS;
|
||||
// Use large arc only if input value is greater than wrap
|
||||
var largeFlag = Math.abs(angleDegrees - Blockly.FieldAngle.OFFSET) > 180 ? 1 : 0;
|
||||
var sweepFlag = Number(Blockly.FieldAngle.CLOCKWISE);
|
||||
if (angleDegrees < Blockly.FieldAngle.OFFSET) {
|
||||
sweepFlag = 1 - sweepFlag; // Sweep opposite direction if less than the offset
|
||||
}
|
||||
path.push(' l ', x1, ',', y1,
|
||||
' A ', Blockly.FieldAngle.RADIUS, ',', Blockly.FieldAngle.RADIUS,
|
||||
' 0 ', largeFlag, ' ', sweepFlag, ' ', x2, ',', y2, ' z');
|
||||
|
||||
// Image rotation needs to be set in degrees
|
||||
if (Blockly.FieldAngle.CLOCKWISE) {
|
||||
var imageRotation = angleDegrees + 2 * Blockly.FieldAngle.OFFSET;
|
||||
} else {
|
||||
var imageRotation = -angleDegrees;
|
||||
}
|
||||
this.arrowSvg_.setAttribute('transform', 'rotate(' + (imageRotation) + ')');
|
||||
}
|
||||
this.gauge_.setAttribute('d', path.join(''));
|
||||
this.line_.setAttribute('x2', x2);
|
||||
this.line_.setAttribute('y2', y2);
|
||||
this.handle_.setAttribute('transform', 'translate(' + x2 + ',' + y2 + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that only an angle may be entered.
|
||||
* @param {string} text The user's text.
|
||||
* @return {?string} A string representing a valid angle, or null if invalid.
|
||||
*/
|
||||
Blockly.FieldAngle.prototype.classValidator = function(text) {
|
||||
if (text === null) {
|
||||
return null;
|
||||
}
|
||||
var n = parseFloat(text || 0);
|
||||
if (isNaN(n)) {
|
||||
return null;
|
||||
}
|
||||
n = n % 360;
|
||||
if (n < 0) {
|
||||
n += 360;
|
||||
}
|
||||
if (n > Blockly.FieldAngle.WRAP) {
|
||||
n -= 360;
|
||||
}
|
||||
return String(n);
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_angle', Blockly.FieldAngle);
|
||||
133
scratch-blocks/core/field_checkbox.js
Normal file
133
scratch-blocks/core/field_checkbox.js
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Checkbox field. Checked or not checked.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldCheckbox');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a checkbox field.
|
||||
* @param {string} state The initial state of the field ('TRUE' or 'FALSE').
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* option is selected. Its sole argument is the new checkbox state. If
|
||||
* it returns a value, this becomes the new checkbox state, unless the
|
||||
* value is null, in which case the change is aborted.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldCheckbox = function(state, opt_validator) {
|
||||
Blockly.FieldCheckbox.superClass_.constructor.call(this, '', opt_validator);
|
||||
// Set the initial state.
|
||||
this.setValue(state);
|
||||
this.addArgType('checkbox');
|
||||
};
|
||||
goog.inherits(Blockly.FieldCheckbox, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldCheckbox from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (checked).
|
||||
* @returns {!Blockly.FieldCheckbox} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldCheckbox.fromJson = function(options) {
|
||||
return new Blockly.FieldCheckbox(options['checked'] ? 'TRUE' : 'FALSE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Character for the checkmark.
|
||||
*/
|
||||
Blockly.FieldCheckbox.CHECK_CHAR = '\u2713';
|
||||
|
||||
/**
|
||||
* Mouse cursor style when over the hotspot that initiates editability.
|
||||
*/
|
||||
Blockly.FieldCheckbox.prototype.CURSOR = 'default';
|
||||
|
||||
/**
|
||||
* Install this checkbox on a block.
|
||||
*/
|
||||
Blockly.FieldCheckbox.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Checkbox has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldCheckbox.superClass_.init.call(this);
|
||||
// The checkbox doesn't use the inherited text element.
|
||||
// Instead it uses a custom checkmark element that is either visible or not.
|
||||
this.checkElement_ = Blockly.utils.createSvgElement('text',
|
||||
{'class': 'blocklyText blocklyCheckbox', 'x': -3, 'y': 14},
|
||||
this.fieldGroup_);
|
||||
var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);
|
||||
this.checkElement_.appendChild(textNode);
|
||||
this.checkElement_.style.display = this.state_ ? 'block' : 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* Return 'TRUE' if the checkbox is checked, 'FALSE' otherwise.
|
||||
* @return {string} Current state.
|
||||
*/
|
||||
Blockly.FieldCheckbox.prototype.getValue = function() {
|
||||
return String(this.state_).toUpperCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the checkbox to be checked if newBool is 'TRUE' or true,
|
||||
* unchecks otherwise.
|
||||
* @param {string|boolean} newBool New state.
|
||||
*/
|
||||
Blockly.FieldCheckbox.prototype.setValue = function(newBool) {
|
||||
var newState = (typeof newBool == 'string') ?
|
||||
(newBool.toUpperCase() == 'TRUE') : !!newBool;
|
||||
if (this.state_ !== newState) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.state_, newState));
|
||||
}
|
||||
this.state_ = newState;
|
||||
if (this.checkElement_) {
|
||||
this.checkElement_.style.display = newState ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the state of the checkbox.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldCheckbox.prototype.showEditor_ = function() {
|
||||
var newState = !this.state_;
|
||||
if (this.sourceBlock_) {
|
||||
// Call any validation function, and allow it to override.
|
||||
newState = this.callValidator(newState);
|
||||
}
|
||||
if (newState !== null) {
|
||||
this.setValue(String(newState).toUpperCase());
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_checkbox', Blockly.FieldCheckbox);
|
||||
253
scratch-blocks/core/field_colour.js
Normal file
253
scratch-blocks/core/field_colour.js
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Colour input field.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldColour');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.utils');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.ColorPicker');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a colour input field.
|
||||
* @param {string} colour The initial colour in '#rrggbb' format.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* colour is selected. Its sole argument is the new colour value. Its
|
||||
* return value becomes the selected colour, unless it is undefined, in
|
||||
* which case the new colour stands, or it is null, in which case the change
|
||||
* is aborted.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldColour = function(colour, opt_validator) {
|
||||
Blockly.FieldColour.superClass_.constructor.call(this, colour, opt_validator);
|
||||
this.addArgType('colour');
|
||||
};
|
||||
goog.inherits(Blockly.FieldColour, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldColour from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (colour).
|
||||
* @returns {!Blockly.FieldColour} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldColour.fromJson = function(options) {
|
||||
return new Blockly.FieldColour(options['colour']);
|
||||
};
|
||||
|
||||
/**
|
||||
* By default use the global constants for colours.
|
||||
* @type {Array.<string>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColour.prototype.colours_ = null;
|
||||
|
||||
/**
|
||||
* By default use the global constants for columns.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColour.prototype.columns_ = 0;
|
||||
|
||||
/**
|
||||
* Install this field on a block.
|
||||
* @param {!Blockly.Block} block The block containing this field.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.init = function(block) {
|
||||
if (this.fieldGroup_) {
|
||||
// Colour field has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldColour.superClass_.init.call(this, block);
|
||||
this.setValue(this.getValue());
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse cursor style when over the hotspot that initiates the editor.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.CURSOR = 'default';
|
||||
|
||||
/**
|
||||
* Close the colour picker if this input is being deleted.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.dispose = function() {
|
||||
Blockly.WidgetDiv.hideIfOwner(this);
|
||||
Blockly.FieldColour.superClass_.dispose.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current colour.
|
||||
* @return {string} Current colour in '#rrggbb' format.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.getValue = function() {
|
||||
return this.colour_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the colour.
|
||||
* @param {string} colour The new colour in '#rrggbb' format.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.setValue = function(colour) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled() &&
|
||||
this.colour_ != colour) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.colour_, colour));
|
||||
}
|
||||
this.colour_ = colour;
|
||||
if (this.sourceBlock_) {
|
||||
// Set the primary, secondary, tertiary, and quaternary colour to this value.
|
||||
// The renderer expects to be able to use the secondary color as the fill for a shadow.
|
||||
this.sourceBlock_.setColour(colour, colour, colour, colour);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field. Used when the block is collapsed.
|
||||
* @return {string} Current text.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.getText = function() {
|
||||
var colour = this.colour_;
|
||||
// Try to use #rgb format if possible, rather than #rrggbb.
|
||||
var m = colour.match(/^#(.)\1(.)\2(.)\3$/);
|
||||
if (m) {
|
||||
colour = '#' + m[1] + m[2] + m[3];
|
||||
}
|
||||
return colour;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the fixed height and width.
|
||||
* @return {!goog.math.Size} Height and width.
|
||||
*/
|
||||
Blockly.FieldColour.prototype.getSize = function() {
|
||||
return new goog.math.Size(Blockly.BlockSvg.FIELD_WIDTH, Blockly.BlockSvg.FIELD_HEIGHT);
|
||||
};
|
||||
|
||||
/**
|
||||
* An array of colour strings for the palette.
|
||||
* See bottom of this page for the default:
|
||||
* http://docs.closure-library.googlecode.com/git/closure_goog_ui_colorpicker.js.source.html
|
||||
* @type {!Array.<string>}
|
||||
*/
|
||||
Blockly.FieldColour.COLOURS = goog.ui.ColorPicker.SIMPLE_GRID_COLORS;
|
||||
|
||||
/**
|
||||
* Number of columns in the palette.
|
||||
*/
|
||||
Blockly.FieldColour.COLUMNS = 7;
|
||||
|
||||
/**
|
||||
* Set a custom colour grid for this field.
|
||||
* @param {Array.<string>} colours Array of colours for this block,
|
||||
* or null to use default (Blockly.FieldColour.COLOURS).
|
||||
* @return {!Blockly.FieldColour} Returns itself (for method chaining).
|
||||
*/
|
||||
Blockly.FieldColour.prototype.setColours = function(colours) {
|
||||
this.colours_ = colours;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a custom grid size for this field.
|
||||
* @param {number} columns Number of columns for this block,
|
||||
* or 0 to use default (Blockly.FieldColour.COLUMNS).
|
||||
* @return {!Blockly.FieldColour} Returns itself (for method chaining).
|
||||
*/
|
||||
Blockly.FieldColour.prototype.setColumns = function(columns) {
|
||||
this.columns_ = columns;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a palette under the colour field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColour.prototype.showEditor_ = function() {
|
||||
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
|
||||
Blockly.FieldColour.widgetDispose_);
|
||||
|
||||
// Record viewport dimensions before adding the widget.
|
||||
var viewportBBox = Blockly.utils.getViewportBBox();
|
||||
var anchorBBox = this.getScaledBBox_();
|
||||
|
||||
// Create and add the colour picker, then record the size.
|
||||
var picker = this.createWidget_();
|
||||
var paletteSize = goog.style.getSize(picker.getElement());
|
||||
|
||||
// Position the picker to line up with the field.
|
||||
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, paletteSize,
|
||||
this.sourceBlock_.RTL);
|
||||
|
||||
// Configure event handler.
|
||||
var thisField = this;
|
||||
Blockly.FieldColour.changeEventKey_ = goog.events.listen(picker,
|
||||
goog.ui.ColorPicker.EventType.CHANGE,
|
||||
function(event) {
|
||||
var colour = event.target.getSelectedColor() || '#000000';
|
||||
Blockly.WidgetDiv.hide();
|
||||
if (thisField.sourceBlock_) {
|
||||
// Call any validation function, and allow it to override.
|
||||
colour = thisField.callValidator(colour);
|
||||
}
|
||||
if (colour !== null) {
|
||||
thisField.setValue(colour);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a color picker widget and render it inside the widget div.
|
||||
* @return {!goog.ui.ColorPicker} The newly created color picker.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColour.prototype.createWidget_ = function() {
|
||||
// Create the palette using Closure.
|
||||
var picker = new goog.ui.ColorPicker();
|
||||
picker.setSize(this.columns_ || Blockly.FieldColour.COLUMNS);
|
||||
picker.setColors(this.colours_ || Blockly.FieldColour.COLOURS);
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
picker.render(div);
|
||||
picker.setSelectedColor(this.getValue());
|
||||
return picker;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the colour palette.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColour.widgetDispose_ = function() {
|
||||
if (Blockly.FieldColour.changeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_colour', Blockly.FieldColour);
|
||||
387
scratch-blocks/core/field_colour_slider.js
Normal file
387
scratch-blocks/core/field_colour_slider.js
Normal file
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Colour input field.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldColourSlider');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.color');
|
||||
goog.require('goog.ui.Slider');
|
||||
|
||||
/**
|
||||
* Class for a slider-based colour input field.
|
||||
* @param {string} colour The initial colour in '#rrggbb' format.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* colour is selected. Its sole argument is the new colour value. Its
|
||||
* return value becomes the selected colour, unless it is undefined, in
|
||||
* which case the new colour stands, or it is null, in which case the change
|
||||
* is aborted.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldColourSlider = function(colour, opt_validator) {
|
||||
Blockly.FieldColourSlider.superClass_.constructor.call(this, colour, opt_validator);
|
||||
this.addArgType('colour');
|
||||
|
||||
// Flag to track whether or not the slider callbacks should execute
|
||||
this.sliderCallbacksEnabled_ = false;
|
||||
};
|
||||
goog.inherits(Blockly.FieldColourSlider, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldColourSlider from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (colour).
|
||||
* @returns {!Blockly.FieldColourSlider} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldColourSlider.fromJson = function(options) {
|
||||
return new Blockly.FieldColourSlider(options['colour']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to be called if eyedropper can be activated.
|
||||
* If defined, an eyedropper button will be added to the color picker.
|
||||
* The button calls this function with a callback to update the field value.
|
||||
* BEWARE: This is not a stable API, so it is being marked as private. It may change.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.activateEyedropper_ = null;
|
||||
|
||||
/**
|
||||
* Path to the eyedropper svg icon.
|
||||
*/
|
||||
Blockly.FieldColourSlider.EYEDROPPER_PATH = 'eyedropper.svg';
|
||||
|
||||
/**
|
||||
* Install this field on a block.
|
||||
* @param {!Blockly.Block} block The block containing this field.
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.init = function(block) {
|
||||
if (this.fieldGroup_) {
|
||||
// Colour slider has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldColourSlider.superClass_.init.call(this, block);
|
||||
this.setValue(this.getValue());
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current colour.
|
||||
* @return {string} Current colour in '#rrggbb' format.
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.getValue = function() {
|
||||
return this.colour_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the colour.
|
||||
* @param {string} colour The new colour in '#rrggbb' format.
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.setValue = function(colour) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled() &&
|
||||
this.colour_ != colour) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.colour_, colour));
|
||||
}
|
||||
this.colour_ = colour;
|
||||
if (this.sourceBlock_) {
|
||||
// Set the colours to this value.
|
||||
// The renderer expects to be able to use the secondary colour as the fill for a shadow.
|
||||
this.sourceBlock_.setColour(colour, colour, this.sourceBlock_.getColourTertiary(),
|
||||
this.sourceBlock_.getColourQuaternary());
|
||||
}
|
||||
this.updateSliderHandles_();
|
||||
this.updateDom_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the hue, saturation or value CSS gradient for the slide backgrounds.
|
||||
* @param {string} channel – Either "hue", "saturation" or "value".
|
||||
* @return {string} Array colour hex colour stops for the given channel
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.createColourStops_ = function(channel) {
|
||||
var stops = [];
|
||||
for(var n = 0; n <= 360; n += 20) {
|
||||
switch (channel) {
|
||||
case 'hue':
|
||||
stops.push(goog.color.hsvToHex(n, this.saturation_, this.brightness_));
|
||||
break;
|
||||
case 'saturation':
|
||||
stops.push(goog.color.hsvToHex(this.hue_, n / 360, this.brightness_));
|
||||
break;
|
||||
case 'brightness':
|
||||
stops.push(goog.color.hsvToHex(this.hue_, this.saturation_, 255 * n / 360));
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown channel for colour sliders: " + channel);
|
||||
}
|
||||
}
|
||||
return stops;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the gradient CSS properties for the given node and channel
|
||||
* @param {Node} node - The DOM node the gradient will be set on.
|
||||
* @param {string} channel – Either "hue", "saturation" or "value".
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.setGradient_ = function(node, channel) {
|
||||
var gradient = this.createColourStops_(channel).join(',');
|
||||
goog.style.setStyle(node, 'background',
|
||||
'-moz-linear-gradient(left, ' + gradient + ')');
|
||||
goog.style.setStyle(node, 'background',
|
||||
'-webkit-linear-gradient(left, ' + gradient + ')');
|
||||
goog.style.setStyle(node, 'background',
|
||||
'-o-linear-gradient(left, ' + gradient + ')');
|
||||
goog.style.setStyle(node, 'background',
|
||||
'-ms-linear-gradient(left, ' + gradient + ')');
|
||||
goog.style.setStyle(node, 'background',
|
||||
'linear-gradient(left, ' + gradient + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the readouts and slider backgrounds after value has changed.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.updateDom_ = function() {
|
||||
if (this.hueSlider_) {
|
||||
// Update the slider backgrounds
|
||||
this.setGradient_(this.hueSlider_.getElement(), 'hue');
|
||||
this.setGradient_(this.saturationSlider_.getElement(), 'saturation');
|
||||
this.setGradient_(this.brightnessSlider_.getElement(), 'brightness');
|
||||
|
||||
// Update the readouts
|
||||
this.hueReadout_.textContent = Math.floor(100 * this.hue_ / 360).toFixed(0);
|
||||
this.saturationReadout_.textContent = Math.floor(100 * this.saturation_).toFixed(0);
|
||||
this.brightnessReadout_.textContent = Math.floor(100 * this.brightness_ / 255).toFixed(0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the slider handle positions from the current field value.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.updateSliderHandles_ = function() {
|
||||
if (this.hueSlider_) {
|
||||
// Don't let the following calls to setValue for each of the sliders
|
||||
// trigger the slider callbacks (which then call setValue on this field again
|
||||
// unnecessarily)
|
||||
this.sliderCallbacksEnabled_ = false;
|
||||
this.hueSlider_.setValue(this.hue_);
|
||||
this.saturationSlider_.setValue(this.saturation_);
|
||||
this.brightnessSlider_.setValue(this.brightness_);
|
||||
this.sliderCallbacksEnabled_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field. Used when the block is collapsed.
|
||||
* @return {string} Current text.
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.getText = function() {
|
||||
var colour = this.colour_;
|
||||
// Try to use #rgb format if possible, rather than #rrggbb.
|
||||
var m = colour.match(/^#(.)\1(.)\2(.)\3$/);
|
||||
if (m) {
|
||||
colour = '#' + m[1] + m[2] + m[3];
|
||||
}
|
||||
return colour;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create label and readout DOM elements, returning the readout
|
||||
* @param {string} labelText - Text for the label
|
||||
* @return {Array} The container node and the readout node.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.createLabelDom_ = function(labelText) {
|
||||
var labelContainer = document.createElement('div');
|
||||
labelContainer.setAttribute('class', 'scratchColourPickerLabel');
|
||||
var readout = document.createElement('span');
|
||||
readout.setAttribute('class', 'scratchColourPickerReadout');
|
||||
var label = document.createElement('span');
|
||||
label.setAttribute('class', 'scratchColourPickerLabelText');
|
||||
label.textContent = labelText;
|
||||
labelContainer.appendChild(label);
|
||||
labelContainer.appendChild(readout);
|
||||
return [labelContainer, readout];
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory for creating the different slider callbacks
|
||||
* @param {string} channel - One of "hue", "saturation" or "brightness"
|
||||
* @return {function} the callback for slider update
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.sliderCallbackFactory_ = function(channel) {
|
||||
var thisField = this;
|
||||
return function(event) {
|
||||
if (!thisField.sliderCallbacksEnabled_) return;
|
||||
var channelValue = event.target.getValue();
|
||||
switch (channel) {
|
||||
case 'hue':
|
||||
thisField.hue_ = channelValue;
|
||||
break;
|
||||
case 'saturation':
|
||||
thisField.saturation_ = channelValue;
|
||||
break;
|
||||
case 'brightness':
|
||||
thisField.brightness_ = channelValue;
|
||||
break;
|
||||
}
|
||||
var colour = goog.color.hsvToHex(thisField.hue_, thisField.saturation_, thisField.brightness_);
|
||||
if (thisField.sourceBlock_) {
|
||||
// Call any validation function, and allow it to override.
|
||||
colour = thisField.callValidator(colour);
|
||||
}
|
||||
if (colour !== null) {
|
||||
thisField.setValue(colour, true);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Activate the eyedropper, passing in a callback for setting the field value.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.activateEyedropperInternal_ = function() {
|
||||
var thisField = this;
|
||||
Blockly.FieldColourSlider.activateEyedropper_(function(value) {
|
||||
// Update the internal hue/saturation/brightness values so sliders update.
|
||||
var hsv = goog.color.hexToHsv(value);
|
||||
thisField.hue_ = hsv[0];
|
||||
thisField.saturation_ = hsv[1];
|
||||
thisField.brightness_ = hsv[2];
|
||||
thisField.setValue(value);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create hue, saturation and brightness sliders under the colour field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldColourSlider.prototype.showEditor_ = function() {
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
var div = Blockly.DropDownDiv.getContentDiv();
|
||||
|
||||
// Init color component values that are used while the editor is open
|
||||
// in order to keep the slider values stable.
|
||||
var hsv = goog.color.hexToHsv(this.getValue());
|
||||
this.hue_ = hsv[0];
|
||||
this.saturation_ = hsv[1];
|
||||
this.brightness_ = hsv[2];
|
||||
|
||||
var hueElements = this.createLabelDom_(Blockly.Msg.COLOUR_HUE_LABEL);
|
||||
div.appendChild(hueElements[0]);
|
||||
this.hueReadout_ = hueElements[1];
|
||||
this.hueSlider_ = new goog.ui.Slider();
|
||||
this.hueSlider_.setUnitIncrement(5);
|
||||
this.hueSlider_.setMinimum(0);
|
||||
this.hueSlider_.setMaximum(360);
|
||||
this.hueSlider_.setMoveToPointEnabled(true);
|
||||
this.hueSlider_.render(div);
|
||||
|
||||
var saturationElements =
|
||||
this.createLabelDom_(Blockly.Msg.COLOUR_SATURATION_LABEL);
|
||||
div.appendChild(saturationElements[0]);
|
||||
this.saturationReadout_ = saturationElements[1];
|
||||
this.saturationSlider_ = new goog.ui.Slider();
|
||||
this.saturationSlider_.setMoveToPointEnabled(true);
|
||||
this.saturationSlider_.setUnitIncrement(0.01);
|
||||
this.saturationSlider_.setStep(0.001);
|
||||
this.saturationSlider_.setMinimum(0);
|
||||
this.saturationSlider_.setMaximum(1.0);
|
||||
this.saturationSlider_.render(div);
|
||||
|
||||
var brightnessElements =
|
||||
this.createLabelDom_(Blockly.Msg.COLOUR_BRIGHTNESS_LABEL);
|
||||
div.appendChild(brightnessElements[0]);
|
||||
this.brightnessReadout_ = brightnessElements[1];
|
||||
this.brightnessSlider_ = new goog.ui.Slider();
|
||||
this.brightnessSlider_.setUnitIncrement(2);
|
||||
this.brightnessSlider_.setMinimum(0);
|
||||
this.brightnessSlider_.setMaximum(255);
|
||||
this.brightnessSlider_.setMoveToPointEnabled(true);
|
||||
this.brightnessSlider_.render(div);
|
||||
|
||||
if (Blockly.FieldColourSlider.activateEyedropper_) {
|
||||
var button = document.createElement('button');
|
||||
button.setAttribute('class', 'scratchEyedropper');
|
||||
var image = document.createElement('img');
|
||||
image.src = Blockly.mainWorkspace.options.pathToMedia + Blockly.FieldColourSlider.EYEDROPPER_PATH;
|
||||
button.appendChild(image);
|
||||
div.appendChild(button);
|
||||
Blockly.FieldColourSlider.eyedropperEventData_ =
|
||||
Blockly.bindEventWithChecks_(button, 'click', this,
|
||||
this.activateEyedropperInternal_);
|
||||
}
|
||||
|
||||
Blockly.DropDownDiv.setColour(Blockly.Colours.valueReportBackground, Blockly.Colours.valueReportBorder);
|
||||
Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
|
||||
Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
|
||||
|
||||
// Set value updates the slider positions
|
||||
// Do this before attaching callbacks to avoid extra events from initial set
|
||||
this.setValue(this.getValue());
|
||||
|
||||
// Enable callbacks for the sliders
|
||||
this.sliderCallbacksEnabled_ = true;
|
||||
|
||||
Blockly.FieldColourSlider.hueChangeEventKey_ = goog.events.listen(this.hueSlider_,
|
||||
goog.ui.Component.EventType.CHANGE,
|
||||
this.sliderCallbackFactory_('hue'));
|
||||
Blockly.FieldColourSlider.saturationChangeEventKey_ = goog.events.listen(this.saturationSlider_,
|
||||
goog.ui.Component.EventType.CHANGE,
|
||||
this.sliderCallbackFactory_('saturation'));
|
||||
Blockly.FieldColourSlider.brightnessChangeEventKey_ = goog.events.listen(this.brightnessSlider_,
|
||||
goog.ui.Component.EventType.CHANGE,
|
||||
this.sliderCallbackFactory_('brightness'));
|
||||
};
|
||||
|
||||
Blockly.FieldColourSlider.prototype.dispose = function() {
|
||||
if (Blockly.FieldColourSlider.hueChangeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldColourSlider.hueChangeEventKey_);
|
||||
}
|
||||
if (Blockly.FieldColourSlider.saturationChangeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldColourSlider.saturationChangeEventKey_);
|
||||
}
|
||||
if (Blockly.FieldColourSlider.brightnessChangeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldColourSlider.brightnessChangeEventKey_);
|
||||
}
|
||||
if (Blockly.FieldColourSlider.eyedropperEventData_) {
|
||||
Blockly.unbindEvent_(Blockly.FieldColourSlider.eyedropperEventData_);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
Blockly.FieldColourSlider.superClass_.dispose.call(this);
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_colour_slider', Blockly.FieldColourSlider);
|
||||
353
scratch-blocks/core/field_date.js
Normal file
353
scratch-blocks/core/field_date.js
Normal file
@@ -0,0 +1,353 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2015 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Date input field.
|
||||
* @author pkendall64@gmail.com (Paul Kendall)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldDate');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.utils');
|
||||
|
||||
goog.require('goog.date');
|
||||
goog.require('goog.date.DateTime');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.i18n.DateTimeSymbols');
|
||||
goog.require('goog.i18n.DateTimeSymbols_he');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.DatePicker');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a date input field.
|
||||
* @param {string} date The initial date.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* date is selected. Its sole argument is the new date value. Its
|
||||
* return value becomes the selected date, unless it is undefined, in
|
||||
* which case the new date stands, or it is null, in which case the change
|
||||
* is aborted.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldDate = function(date, opt_validator) {
|
||||
if (!date) {
|
||||
date = new goog.date.Date().toIsoString(true);
|
||||
}
|
||||
Blockly.FieldDate.superClass_.constructor.call(this, date, opt_validator);
|
||||
this.setValue(date);
|
||||
this.addArgType('date');
|
||||
};
|
||||
goog.inherits(Blockly.FieldDate, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldDate from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (date).
|
||||
* @returns {!Blockly.FieldDate} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldDate.fromJson = function(options) {
|
||||
return new Blockly.FieldDate(options['date']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse cursor style when over the hotspot that initiates the editor.
|
||||
*/
|
||||
Blockly.FieldDate.prototype.CURSOR = 'text';
|
||||
|
||||
/**
|
||||
* Close the colour picker if this input is being deleted.
|
||||
*/
|
||||
Blockly.FieldDate.prototype.dispose = function() {
|
||||
Blockly.WidgetDiv.hideIfOwner(this);
|
||||
Blockly.FieldDate.superClass_.dispose.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the current date.
|
||||
* @return {string} Current date.
|
||||
*/
|
||||
Blockly.FieldDate.prototype.getValue = function() {
|
||||
return this.date_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the date.
|
||||
* @param {string} date The new date.
|
||||
*/
|
||||
Blockly.FieldDate.prototype.setValue = function(date) {
|
||||
if (this.sourceBlock_) {
|
||||
var validated = this.callValidator(date);
|
||||
// If the new date is invalid, validation returns null.
|
||||
// In this case we still want to display the illegal result.
|
||||
if (validated !== null) {
|
||||
date = validated;
|
||||
}
|
||||
}
|
||||
this.date_ = date;
|
||||
Blockly.Field.prototype.setText.call(this, date);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a date picker under the date field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDate.prototype.showEditor_ = function() {
|
||||
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
|
||||
Blockly.FieldDate.widgetDispose_);
|
||||
|
||||
// Record viewport dimensions before adding the picker.
|
||||
var viewportBBox = Blockly.utils.getViewportBBox();
|
||||
var anchorBBox = this.getScaledBBox_();
|
||||
|
||||
// Create and add the date picker, then record the size.
|
||||
var picker = this.createWidget_();
|
||||
var pickerSize = goog.style.getSize(picker.getElement());
|
||||
|
||||
// Position the picker to line up with the field.
|
||||
Blockly.WidgetDiv.positionWithAnchor(viewportBBox, anchorBBox, pickerSize,
|
||||
this.sourceBlock_.RTL);
|
||||
|
||||
// Configure event handler.
|
||||
var thisField = this;
|
||||
Blockly.FieldDate.changeEventKey_ = goog.events.listen(picker,
|
||||
goog.ui.DatePicker.Events.CHANGE,
|
||||
function(event) {
|
||||
var date = event.date ? event.date.toIsoString(true) : '';
|
||||
Blockly.WidgetDiv.hide();
|
||||
if (thisField.sourceBlock_) {
|
||||
// Call any validation function, and allow it to override.
|
||||
date = thisField.callValidator(date);
|
||||
}
|
||||
thisField.setValue(date);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a date picker widget and render it inside the widget div.
|
||||
* @return {!goog.ui.DatePicker} The newly created date picker.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDate.prototype.createWidget_ = function() {
|
||||
// Create the date picker using Closure.
|
||||
Blockly.FieldDate.loadLanguage_();
|
||||
var picker = new goog.ui.DatePicker();
|
||||
picker.setAllowNone(false);
|
||||
picker.setShowWeekNum(false);
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
picker.render(div);
|
||||
picker.setDate(goog.date.DateTime.fromIsoString(this.getValue()));
|
||||
return picker;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the date picker.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDate.widgetDispose_ = function() {
|
||||
if (Blockly.FieldDate.changeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the best language pack by scanning the Blockly.Msg object for a
|
||||
* language that matches the available languages in Closure.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDate.loadLanguage_ = function() {
|
||||
var reg = /^DateTimeSymbols_(.+)$/;
|
||||
for (var prop in goog.i18n) {
|
||||
var m = prop.match(reg);
|
||||
if (m) {
|
||||
var lang = m[1].toLowerCase().replace('_', '.'); // E.g. 'pt.br'
|
||||
if (goog.getObjectByName(lang, Blockly.Msg)) {
|
||||
goog.i18n.DateTimeSymbols = goog.i18n[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS for date picker. See css.js for use.
|
||||
*/
|
||||
Blockly.FieldDate.CSS = [
|
||||
/* Copied from: goog/css/datepicker.css */
|
||||
/**
|
||||
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by the Apache License, Version 2.0.
|
||||
* See the COPYING file for details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Standard styling for a goog.ui.DatePicker.
|
||||
*
|
||||
* @author arv@google.com (Erik Arvidsson)
|
||||
*/
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker,',
|
||||
'.blocklyWidgetDiv .goog-date-picker th,',
|
||||
'.blocklyWidgetDiv .goog-date-picker td {',
|
||||
' font: 13px Arial, sans-serif;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker {',
|
||||
' -moz-user-focus: normal;',
|
||||
' -moz-user-select: none;',
|
||||
' position: relative;',
|
||||
' border: 1px solid #000;',
|
||||
' float: left;',
|
||||
' padding: 2px;',
|
||||
' color: #000;',
|
||||
' background: #c3d9ff;',
|
||||
' cursor: default;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker th {',
|
||||
' text-align: center;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker td {',
|
||||
' text-align: center;',
|
||||
' vertical-align: middle;',
|
||||
' padding: 1px 3px;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-menu {',
|
||||
' position: absolute;',
|
||||
' background: threedface;',
|
||||
' border: 1px solid gray;',
|
||||
' -moz-user-focus: normal;',
|
||||
' z-index: 1;',
|
||||
' outline: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-menu ul {',
|
||||
' list-style: none;',
|
||||
' margin: 0px;',
|
||||
' padding: 0px;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-menu ul li {',
|
||||
' cursor: default;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-menu-selected {',
|
||||
' background: #ccf;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker th {',
|
||||
' font-size: .9em;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker td div {',
|
||||
' float: left;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker button {',
|
||||
' padding: 0px;',
|
||||
' margin: 1px 0;',
|
||||
' border: 0;',
|
||||
' color: #20c;',
|
||||
' font-weight: bold;',
|
||||
' background: transparent;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-date {',
|
||||
' background: #fff;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-week,',
|
||||
'.blocklyWidgetDiv .goog-date-picker-wday {',
|
||||
' padding: 1px 3px;',
|
||||
' border: 0;',
|
||||
' border-color: #a2bbdd;',
|
||||
' border-style: solid;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-week {',
|
||||
' border-right-width: 1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-wday {',
|
||||
' border-bottom-width: 1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-head td {',
|
||||
' text-align: center;',
|
||||
'}',
|
||||
|
||||
/** Use td.className instead of !important */
|
||||
'.blocklyWidgetDiv td.goog-date-picker-today-cont {',
|
||||
' text-align: center;',
|
||||
'}',
|
||||
|
||||
/** Use td.className instead of !important */
|
||||
'.blocklyWidgetDiv td.goog-date-picker-none-cont {',
|
||||
' text-align: center;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-month {',
|
||||
' min-width: 11ex;',
|
||||
' white-space: nowrap;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-year {',
|
||||
' min-width: 6ex;',
|
||||
' white-space: nowrap;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-monthyear {',
|
||||
' white-space: nowrap;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker table {',
|
||||
' border-collapse: collapse;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-other-month {',
|
||||
' color: #888;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-wkend-start,',
|
||||
'.blocklyWidgetDiv .goog-date-picker-wkend-end {',
|
||||
' background: #eee;',
|
||||
'}',
|
||||
|
||||
/** Use td.className instead of !important */
|
||||
'.blocklyWidgetDiv td.goog-date-picker-selected {',
|
||||
' background: #c3d9ff;',
|
||||
'}',
|
||||
|
||||
'.blocklyWidgetDiv .goog-date-picker-today {',
|
||||
' background: #9ab;',
|
||||
' font-weight: bold !important;',
|
||||
' border-color: #246 #9bd #9bd #246;',
|
||||
' color: #fff;',
|
||||
'}'
|
||||
];
|
||||
|
||||
Blockly.Field.register('field_date', Blockly.FieldDate);
|
||||
447
scratch-blocks/core/field_dropdown.js
Normal file
447
scratch-blocks/core/field_dropdown.js
Normal file
@@ -0,0 +1,447 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Dropdown input field. Used for editable titles and variables.
|
||||
* In the interests of a consistent UI, the toolbox shares some functions and
|
||||
* properties with the context menu.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldDropdown');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.Menu');
|
||||
goog.require('goog.ui.MenuItem');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for an editable dropdown field.
|
||||
* @param {(!Array.<!Array>|!Function)} menuGenerator An array of options
|
||||
* for a dropdown list, or a function which generates these options.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* option is selected, with the newly selected value as its sole argument.
|
||||
* If it returns a value, that value (which must be one of the options) will
|
||||
* become selected in place of the newly selected option, unless the return
|
||||
* value is null, in which case the change is aborted.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldDropdown = function(menuGenerator, opt_validator) {
|
||||
this.menuGenerator_ = menuGenerator;
|
||||
this.trimOptions_();
|
||||
var firstTuple = this.getOptions()[0];
|
||||
|
||||
// Call parent's constructor.
|
||||
Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1],
|
||||
opt_validator);
|
||||
this.addArgType('dropdown');
|
||||
};
|
||||
goog.inherits(Blockly.FieldDropdown, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldDropdown from a JSON arg object.
|
||||
* @param {!Object} element A JSON object with options.
|
||||
* @returns {!Blockly.FieldDropdown} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldDropdown.fromJson = function(element) {
|
||||
return new Blockly.FieldDropdown(element['options']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Horizontal distance that a checkmark overhangs the dropdown.
|
||||
*/
|
||||
Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25;
|
||||
|
||||
/**
|
||||
* Mouse cursor style when over the hotspot that initiates the editor.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.CURSOR = 'default';
|
||||
|
||||
/**
|
||||
* Closure menu item currently selected.
|
||||
* @type {?goog.ui.MenuItem}
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.selectedItem = null;
|
||||
|
||||
/**
|
||||
* Language-neutral currently selected string or image object.
|
||||
* @type {string|!Object}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.value_ = '';
|
||||
|
||||
/**
|
||||
* SVG image element if currently selected option is an image, or null.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.imageElement_ = null;
|
||||
|
||||
/**
|
||||
* Object with src, height, width, and alt attributes if currently selected
|
||||
* option is an image, or null.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.imageJson_ = null;
|
||||
|
||||
/**
|
||||
* Install this dropdown on a block.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Dropdown has already been initialized once.
|
||||
return;
|
||||
}
|
||||
// Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL)
|
||||
// Positioned on render, after text size is calculated.
|
||||
/** @type {Number} */
|
||||
this.arrowSize_ = 12;
|
||||
/** @type {Number} */
|
||||
this.arrowX_ = 0;
|
||||
/** @type {Number} */
|
||||
this.arrowY_ = 11;
|
||||
this.arrow_ = Blockly.utils.createSvgElement('image', {
|
||||
'height': this.arrowSize_ + 'px',
|
||||
'width': this.arrowSize_ + 'px'
|
||||
});
|
||||
this.arrow_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||
'xlink:href', Blockly.mainWorkspace.options.pathToMedia + 'dropdown-arrow.svg');
|
||||
this.className_ += ' blocklyDropdownText';
|
||||
|
||||
Blockly.FieldDropdown.superClass_.init.call(this);
|
||||
// If not in a shadow block, draw a box.
|
||||
if (!this.sourceBlock_.isShadow()) {
|
||||
this.box_ = Blockly.utils.createSvgElement('rect', {
|
||||
'rx': Blockly.BlockSvg.CORNER_RADIUS,
|
||||
'ry': Blockly.BlockSvg.CORNER_RADIUS,
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
'width': this.size_.width,
|
||||
'height': this.size_.height,
|
||||
'stroke': this.sourceBlock_.getColourTertiary(),
|
||||
'fill': this.sourceBlock_.getColour(),
|
||||
'class': 'blocklyBlockBackground',
|
||||
'fill-opacity': 1
|
||||
}, null);
|
||||
this.fieldGroup_.insertBefore(this.box_, this.textElement_);
|
||||
}
|
||||
// Force a reset of the text to add the arrow.
|
||||
var text = this.text_;
|
||||
this.text_ = null;
|
||||
this.setText(text);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a dropdown menu under the text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.showEditor_ = function() {
|
||||
var options = this.getOptions();
|
||||
if (options.length == 0) return;
|
||||
|
||||
this.dropDownOpen_ = true;
|
||||
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
|
||||
var contentDiv = Blockly.DropDownDiv.getContentDiv();
|
||||
|
||||
var thisField = this;
|
||||
|
||||
function callback(e) {
|
||||
var menu = this;
|
||||
var menuItem = e.target;
|
||||
if (menuItem) {
|
||||
thisField.onItemSelected(menu, menuItem);
|
||||
}
|
||||
Blockly.DropDownDiv.hide();
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
|
||||
var menu = new goog.ui.Menu();
|
||||
menu.setRightToLeft(this.sourceBlock_.RTL);
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var content = options[i][0]; // Human-readable text or image.
|
||||
var value = options[i][1]; // Language-neutral value.
|
||||
if (typeof content == 'object') {
|
||||
// An image, not text.
|
||||
var image = new Image(content['width'], content['height']);
|
||||
image.src = content['src'];
|
||||
image.alt = content['alt'] || '';
|
||||
content = image;
|
||||
}
|
||||
var menuItem = new goog.ui.MenuItem(content);
|
||||
menuItem.setRightToLeft(this.sourceBlock_.RTL);
|
||||
menuItem.setValue(value);
|
||||
menuItem.setCheckable(true);
|
||||
menu.addChild(menuItem, true);
|
||||
var checked = (value == this.value_);
|
||||
menuItem.setChecked(checked);
|
||||
if (checked) {
|
||||
this.selectedItem = menuItem;
|
||||
}
|
||||
}
|
||||
// Listen for mouse/keyboard events.
|
||||
goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback);
|
||||
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
menu.render(contentDiv);
|
||||
var menuDom = menu.getElement();
|
||||
Blockly.utils.addClass(menuDom, 'blocklyDropdownMenu');
|
||||
// Record menuSize after adding menu.
|
||||
var menuSize = goog.style.getSize(menuDom);
|
||||
// Recalculate height for the total content, not only box height.
|
||||
menuSize.height = menuDom.scrollHeight;
|
||||
|
||||
var primaryColour = (this.sourceBlock_.isShadow()) ?
|
||||
this.sourceBlock_.parentBlock_.getColour() : this.sourceBlock_.getColour();
|
||||
|
||||
Blockly.DropDownDiv.setColour(primaryColour, this.sourceBlock_.getColourTertiary());
|
||||
|
||||
var category = (this.sourceBlock_.isShadow()) ?
|
||||
this.sourceBlock_.parentBlock_.getCategory() : this.sourceBlock_.getCategory();
|
||||
Blockly.DropDownDiv.setCategory(category);
|
||||
|
||||
// Calculate positioning based on the field position.
|
||||
var scale = this.sourceBlock_.workspace.scale;
|
||||
var bBox = {width: this.size_.width, height: this.size_.height};
|
||||
bBox.width *= scale;
|
||||
bBox.height *= scale;
|
||||
var position = this.fieldGroup_.getBoundingClientRect();
|
||||
var primaryX = position.left + bBox.width / 2;
|
||||
var primaryY = position.top + bBox.height;
|
||||
var secondaryX = primaryX;
|
||||
var secondaryY = position.top;
|
||||
// Set bounds to workspace; show the drop-down.
|
||||
Blockly.DropDownDiv.setBoundsElement(this.sourceBlock_.workspace.getParentSvg().parentNode);
|
||||
Blockly.DropDownDiv.show(
|
||||
this, primaryX, primaryY, secondaryX, secondaryY, this.onHide.bind(this));
|
||||
|
||||
menu.setAllowAutoFocus(true);
|
||||
menuDom.focus();
|
||||
|
||||
// Update colour to look selected.
|
||||
if (!this.disableColourChange_) {
|
||||
if (this.sourceBlock_.isShadow()) {
|
||||
this.sourceBlock_.setShadowColour(this.sourceBlock_.getColourQuaternary());
|
||||
} else if (this.box_) {
|
||||
this.box_.setAttribute('fill', this.sourceBlock_.getColourQuaternary());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when the drop-down is hidden.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.onHide = function() {
|
||||
this.dropDownOpen_ = false;
|
||||
// Update colour to look selected.
|
||||
if (!this.disableColourChange_ && this.sourceBlock_) {
|
||||
if (this.sourceBlock_.isShadow()) {
|
||||
this.sourceBlock_.clearShadowColour();
|
||||
} else if (this.box_) {
|
||||
this.box_.setAttribute('fill', this.sourceBlock_.getColour());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the selection of an item in the dropdown menu.
|
||||
* @param {!goog.ui.Menu} menu The Menu component clicked.
|
||||
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) {
|
||||
var value = menuItem.getValue();
|
||||
if (this.sourceBlock_) {
|
||||
// Call any validation function, and allow it to override.
|
||||
value = this.callValidator(value);
|
||||
}
|
||||
// If the value of the menu item is a function, call it and do not select it.
|
||||
if (typeof value == 'function') {
|
||||
value();
|
||||
return;
|
||||
}
|
||||
if (value !== null) {
|
||||
this.setValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.trimOptions_ = function() {
|
||||
this.prefixField = null;
|
||||
this.suffixField = null;
|
||||
var options = this.menuGenerator_;
|
||||
if (!goog.isArray(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Localize label text and image alt text.
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var label = options[i][0];
|
||||
if (typeof label == 'string') {
|
||||
options[i][0] = Blockly.utils.replaceMessageReferences(label);
|
||||
} else {
|
||||
if (label.alt != null) {
|
||||
options[i][0].alt = Blockly.utils.replaceMessageReferences(label.alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the option list is generated by a function.
|
||||
* Otherwise false.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.isOptionListDynamic = function() {
|
||||
return goog.isFunction(this.menuGenerator_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a list of the options for this dropdown.
|
||||
* @return {!Array.<!Array>} Array of option tuples:
|
||||
* (human-readable text or image, language-neutral name).
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.getOptions = function() {
|
||||
if (goog.isFunction(this.menuGenerator_)) {
|
||||
return this.menuGenerator_.call(this);
|
||||
}
|
||||
return /** @type {!Array.<!Array.<string>>} */ (this.menuGenerator_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the language-neutral value from this dropdown menu.
|
||||
* @return {string} Current text.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.getValue = function() {
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the language-neutral value for this dropdown menu.
|
||||
* @param {string} newValue New value to set.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.setValue = function(newValue) {
|
||||
if (newValue === null || newValue === this.value_) {
|
||||
return; // No change if null.
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.value_, newValue));
|
||||
}
|
||||
// Clear menu item for old value.
|
||||
if (this.selectedItem) {
|
||||
this.selectedItem.setChecked(false);
|
||||
this.selectedItem = null;
|
||||
}
|
||||
this.value_ = newValue;
|
||||
// Look up and display the human-readable text.
|
||||
var options = this.getOptions();
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
// Options are tuples of human-readable text and language-neutral values.
|
||||
if (options[i][1] == newValue) {
|
||||
var content = options[i][0];
|
||||
if (typeof content == 'object') {
|
||||
this.imageJson_ = content;
|
||||
this.text_ = content.alt;
|
||||
} else {
|
||||
this.imageJson_ = null;
|
||||
this.text_ = content;
|
||||
}
|
||||
// Always rerender if either the value or the text has changed.
|
||||
this.forceRerender();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Value not found. Add it, maybe it will become valid once set
|
||||
// (like variable names).
|
||||
this.text_ = newValue;
|
||||
this.forceRerender();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the text in this field. Trigger a rerender of the source block.
|
||||
* @param {?string} text New text.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.setText = function(text) {
|
||||
if (text === null || text === this.text_) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
this.text_ = text;
|
||||
this.updateTextNode_();
|
||||
|
||||
if (this.textElement_) {
|
||||
this.textElement_.parentNode.appendChild(this.arrow_);
|
||||
}
|
||||
if (this.sourceBlock_ && this.sourceBlock_.rendered) {
|
||||
this.sourceBlock_.render();
|
||||
this.sourceBlock_.bumpNeighbours_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Position a drop-down arrow at the appropriate location at render-time.
|
||||
* @param {number} x X position the arrow is being rendered at, in px.
|
||||
* @return {number} Amount of space the arrow is taking up, in px.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.positionArrow = function(x) {
|
||||
if (!this.arrow_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var addedWidth = 0;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
this.arrowX_ = this.arrowSize_ - Blockly.BlockSvg.DROPDOWN_ARROW_PADDING;
|
||||
addedWidth = this.arrowSize_ + Blockly.BlockSvg.DROPDOWN_ARROW_PADDING;
|
||||
} else {
|
||||
this.arrowX_ = x + Blockly.BlockSvg.DROPDOWN_ARROW_PADDING / 2;
|
||||
addedWidth = this.arrowSize_ + Blockly.BlockSvg.DROPDOWN_ARROW_PADDING;
|
||||
}
|
||||
if (this.box_) {
|
||||
// Bump positioning to the right for a box-type drop-down.
|
||||
this.arrowX_ += Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||
}
|
||||
this.arrow_.setAttribute('transform',
|
||||
'translate(' + this.arrowX_ + ',' + this.arrowY_ + ')');
|
||||
return addedWidth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the dropdown menu if this input is being deleted.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.dispose = function() {
|
||||
this.selectedItem = null;
|
||||
Blockly.WidgetDiv.hideIfOwner(this);
|
||||
Blockly.FieldDropdown.superClass_.dispose.call(this);
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_dropdown', Blockly.FieldDropdown);
|
||||
309
scratch-blocks/core/field_iconmenu.js
Normal file
309
scratch-blocks/core/field_iconmenu.js
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Icon picker input field.
|
||||
* This is primarily for use in Scratch Horizontal blocks.
|
||||
* Pops open a drop-down with icons; when an icon is selected, it replaces
|
||||
* the icon (image field) in the original block.
|
||||
* @author tmickel@mit.edu (Tim Mickel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldIconMenu');
|
||||
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
|
||||
/**
|
||||
* Class for an icon menu field.
|
||||
* @param {Object} icons List of icons. These take the same options as an Image Field.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldIconMenu = function(icons) {
|
||||
/** @type {object} */
|
||||
this.icons_ = icons;
|
||||
// Example:
|
||||
// [{src: '...', width: 20, height: 20, alt: '...', value: 'machine_value'}, ...]
|
||||
// First icon provides the default values.
|
||||
var defaultValue = icons[0].value;
|
||||
Blockly.FieldIconMenu.superClass_.constructor.call(this, defaultValue);
|
||||
this.addArgType('iconmenu');
|
||||
};
|
||||
goog.inherits(Blockly.FieldIconMenu, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldIconMenu from a JSON arg object.
|
||||
* @param {!Object} element A JSON object with options.
|
||||
* @returns {!Blockly.FieldIconMenu} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldIconMenu.fromJson = function(element) {
|
||||
return new Blockly.FieldIconMenu(element['options']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fixed width of the drop-down, in px. Icon buttons will flow inside this width.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldIconMenu.DROPDOWN_WIDTH = 168;
|
||||
|
||||
/**
|
||||
* Save the primary colour of the source block while the menu is open, for reset.
|
||||
* @type {number|string}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldIconMenu.savedPrimary_ = null;
|
||||
|
||||
/**
|
||||
* Called when the field is placed on a block.
|
||||
* @param {Block} block The owning block.
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.init = function(block) {
|
||||
if (this.fieldGroup_) {
|
||||
// Icon menu has already been initialized once.
|
||||
return;
|
||||
}
|
||||
// Render the arrow icon
|
||||
// Fixed sizes in px. Saved for creating the flip transform of the menu renders above the button.
|
||||
var arrowSize = 12;
|
||||
/** @type {Number} */
|
||||
this.arrowX_ = 18;
|
||||
/** @type {Number} */
|
||||
this.arrowY_ = 10;
|
||||
if (block.RTL) {
|
||||
// In RTL, the icon position is flipped and rendered from the right (offset by width)
|
||||
this.arrowX_ = -this.arrowX_ - arrowSize;
|
||||
}
|
||||
/** @type {Element} */
|
||||
this.arrowIcon_ = Blockly.utils.createSvgElement('image', {
|
||||
'height': arrowSize + 'px',
|
||||
'width': arrowSize + 'px',
|
||||
'transform': 'translate(' + this.arrowX_ + ',' + this.arrowY_ + ')'
|
||||
});
|
||||
this.arrowIcon_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||
'xlink:href', Blockly.mainWorkspace.options.pathToMedia + 'dropdown-arrow.svg');
|
||||
block.getSvgRoot().appendChild(this.arrowIcon_);
|
||||
Blockly.FieldIconMenu.superClass_.init.call(this, block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Mouse cursor style when over the hotspot that initiates the editor.
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.CURSOR = 'default';
|
||||
|
||||
/**
|
||||
* Set the language-neutral value for this icon drop-down menu.
|
||||
* @param {?string} newValue New value.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.setValue = function(newValue) {
|
||||
if (newValue === null || newValue === this.value_) {
|
||||
return; // No change
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
this.sourceBlock_, 'field', this.name, this.value_, newValue));
|
||||
}
|
||||
this.value_ = newValue;
|
||||
// Find the relevant icon in this.icons_ to get the image src.
|
||||
this.setParentFieldImage(this.getSrcForValue(this.value_));
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the parent block's FieldImage and set its src.
|
||||
* @param {?string} src New src for the parent block FieldImage.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.setParentFieldImage = function(src) {
|
||||
// Only attempt if we have a set sourceBlock_ and parentBlock_
|
||||
// It's possible that this function could be called before
|
||||
// a parent block is set; in that case, fail silently.
|
||||
if (this.sourceBlock_ && this.sourceBlock_.parentBlock_) {
|
||||
var parentBlock = this.sourceBlock_.parentBlock_;
|
||||
// Loop through all inputs' fields to find the first FieldImage
|
||||
for (var i = 0, input; input = parentBlock.inputList[i]; i++) {
|
||||
for (var j = 0, field; field = input.fieldRow[j]; j++) {
|
||||
if (field instanceof Blockly.FieldImage) {
|
||||
// Src for a FieldImage is stored in its value.
|
||||
field.setValue(src);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the language-neutral value from this drop-down menu.
|
||||
* @return {string} Current language-neutral value.
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.getValue = function() {
|
||||
return this.value_;
|
||||
};
|
||||
|
||||
/**
|
||||
* For a language-neutral value, get the src for the image that represents it.
|
||||
* @param {string} value Language-neutral value to look up.
|
||||
* @return {string} Src to image representing value
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.getSrcForValue = function(value) {
|
||||
for (var i = 0, icon; icon = this.icons_[i]; i++) {
|
||||
if (icon.value === value) {
|
||||
return icon.src;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the drop-down menu for editing this field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.showEditor_ = function() {
|
||||
// If there is an existing drop-down we own, this is a request to hide the drop-down.
|
||||
if (Blockly.DropDownDiv.hideIfOwner(this)) {
|
||||
return;
|
||||
}
|
||||
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
// Populate the drop-down with the icons for this field.
|
||||
var contentDiv = Blockly.DropDownDiv.getContentDiv();
|
||||
// Accessibility properties
|
||||
contentDiv.setAttribute('role', 'menu');
|
||||
contentDiv.setAttribute('aria-haspopup', 'true');
|
||||
for (var i = 0, icon; icon = this.icons_[i]; i++) {
|
||||
// Icons with the type property placeholder take up space but don't have any functionality
|
||||
// Use for special-case layouts
|
||||
if (icon.type == 'placeholder') {
|
||||
var placeholder = document.createElement('span');
|
||||
placeholder.setAttribute('class', 'blocklyDropDownPlaceholder');
|
||||
placeholder.style.width = icon.width + 'px';
|
||||
placeholder.style.height = icon.height + 'px';
|
||||
contentDiv.appendChild(placeholder);
|
||||
continue;
|
||||
}
|
||||
var button = document.createElement('button');
|
||||
button.setAttribute('id', ':' + i); // For aria-activedescendant
|
||||
button.setAttribute('role', 'menuitem');
|
||||
button.setAttribute('class', 'blocklyDropDownButton');
|
||||
button.title = icon.alt;
|
||||
button.style.width = icon.width + 'px';
|
||||
button.style.height = icon.height + 'px';
|
||||
var backgroundColor = this.sourceBlock_.getColour();
|
||||
if (icon.value == this.getValue()) {
|
||||
// This icon is selected, show it in a different colour
|
||||
backgroundColor = this.sourceBlock_.getColourTertiary();
|
||||
button.setAttribute('aria-selected', 'true');
|
||||
}
|
||||
button.style.backgroundColor = backgroundColor;
|
||||
button.style.borderColor = this.sourceBlock_.getColourTertiary();
|
||||
Blockly.bindEvent_(button, 'click', this, this.buttonClick_);
|
||||
Blockly.bindEvent_(button, 'mouseup', this, this.buttonClick_);
|
||||
// These are applied manually instead of using the :hover pseudoclass
|
||||
// because Android has a bad long press "helper" menu and green highlight
|
||||
// that we must prevent with ontouchstart preventDefault
|
||||
Blockly.bindEvent_(button, 'mousedown', button, function(e) {
|
||||
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||
e.preventDefault();
|
||||
});
|
||||
Blockly.bindEvent_(button, 'mouseover', button, function() {
|
||||
this.setAttribute('class', 'blocklyDropDownButton blocklyDropDownButtonHover');
|
||||
contentDiv.setAttribute('aria-activedescendant', this.id);
|
||||
});
|
||||
Blockly.bindEvent_(button, 'mouseout', button, function() {
|
||||
this.setAttribute('class', 'blocklyDropDownButton');
|
||||
contentDiv.removeAttribute('aria-activedescendant');
|
||||
});
|
||||
var buttonImg = document.createElement('img');
|
||||
buttonImg.src = icon.src;
|
||||
//buttonImg.alt = icon.alt;
|
||||
// Upon click/touch, we will be able to get the clicked element as e.target
|
||||
// Store a data attribute on all possible click targets so we can match it to the icon.
|
||||
button.setAttribute('data-value', icon.value);
|
||||
buttonImg.setAttribute('data-value', icon.value);
|
||||
button.appendChild(buttonImg);
|
||||
contentDiv.appendChild(button);
|
||||
}
|
||||
contentDiv.style.width = Blockly.FieldIconMenu.DROPDOWN_WIDTH + 'px';
|
||||
|
||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(), this.sourceBlock_.getColourTertiary());
|
||||
Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
|
||||
|
||||
// Update source block colour to look selected
|
||||
this.savedPrimary_ = this.sourceBlock_.getColour();
|
||||
this.sourceBlock_.setColour(this.sourceBlock_.getColourSecondary(),
|
||||
this.sourceBlock_.getColourSecondary(),
|
||||
this.sourceBlock_.getColourTertiary(),
|
||||
this.sourceBlock_.getColourQuaternary());
|
||||
|
||||
var scale = this.sourceBlock_.workspace.scale;
|
||||
// Offset for icon-type horizontal blocks.
|
||||
var secondaryYOffset = (
|
||||
-(Blockly.BlockSvg.MIN_BLOCK_Y * scale) - (Blockly.BlockSvg.FIELD_Y_OFFSET * scale)
|
||||
);
|
||||
var renderedPrimary = Blockly.DropDownDiv.showPositionedByBlock(
|
||||
this, this.sourceBlock_, this.onHide_.bind(this), secondaryYOffset);
|
||||
if (!renderedPrimary) {
|
||||
// Adjust for rotation
|
||||
var arrowX = this.arrowX_ + Blockly.DropDownDiv.ARROW_SIZE / 1.5 + 1;
|
||||
var arrowY = this.arrowY_ + Blockly.DropDownDiv.ARROW_SIZE / 1.5;
|
||||
// Flip the arrow on the button
|
||||
this.arrowIcon_.setAttribute('transform',
|
||||
'translate(' + arrowX + ',' + arrowY + ') rotate(180)');}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when a button is clicked inside the drop-down.
|
||||
* Should be bound to the FieldIconMenu.
|
||||
* @param {Event} e DOM event for the click/touch
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.buttonClick_ = function(e) {
|
||||
var value = e.target.getAttribute('data-value');
|
||||
this.setValue(value);
|
||||
Blockly.DropDownDiv.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when the drop-down is hidden.
|
||||
*/
|
||||
Blockly.FieldIconMenu.prototype.onHide_ = function() {
|
||||
// Reset the button colour and clear accessibility properties
|
||||
// Only attempt to do this reset if sourceBlock_ is not disposed.
|
||||
// It could become disposed before an onHide_, for example,
|
||||
// when a block is dragged from the flyout.
|
||||
if (this.sourceBlock_) {
|
||||
this.sourceBlock_.setColour(this.savedPrimary_,
|
||||
this.sourceBlock_.getColourSecondary(),
|
||||
this.sourceBlock_.getColourTertiary(),
|
||||
this.sourceBlock_.getColourQuaternary());
|
||||
}
|
||||
Blockly.DropDownDiv.content_.removeAttribute('role');
|
||||
Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
|
||||
Blockly.DropDownDiv.content_.removeAttribute('aria-activedescendant');
|
||||
// Unflip the arrow if appropriate
|
||||
this.arrowIcon_.setAttribute('transform', 'translate(' + this.arrowX_ + ',' + this.arrowY_ + ')');
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_iconmenu', Blockly.FieldIconMenu);
|
||||
200
scratch-blocks/core/field_image.js
Normal file
200
scratch-blocks/core/field_image.js
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Image field. Used for pictures, icons, etc.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldImage');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Size');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for an image on a block.
|
||||
* @param {string} src The URL of the image.
|
||||
* @param {number} width Width of the image.
|
||||
* @param {number} height Height of the image.
|
||||
* @param {string=} opt_alt Optional alt text for when block is collapsed.
|
||||
* @param {boolean} flip_rtl Whether to flip the icon in RTL
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldImage = function(src, width, height, opt_alt, flip_rtl) {
|
||||
this.sourceBlock_ = null;
|
||||
|
||||
// Ensure height and width are numbers. Strings are bad at math.
|
||||
this.height_ = Number(height);
|
||||
this.width_ = Number(width);
|
||||
this.size_ = new goog.math.Size(this.width_, this.height_);
|
||||
this.text_ = opt_alt || '';
|
||||
this.flipRTL_ = flip_rtl;
|
||||
this.setValue(src);
|
||||
};
|
||||
goog.inherits(Blockly.FieldImage, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldImage from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (src, width, height, alt,
|
||||
* and flipRtl/flip_rtl).
|
||||
* @returns {!Blockly.FieldImage} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldImage.fromJson = function(options) {
|
||||
var src = Blockly.utils.replaceMessageReferences(options['src']);
|
||||
var width = Number(Blockly.utils.replaceMessageReferences(options['width']));
|
||||
var height =
|
||||
Number(Blockly.utils.replaceMessageReferences(options['height']));
|
||||
var alt = Blockly.utils.replaceMessageReferences(options['alt']);
|
||||
var flip_rtl = !!options['flip_rtl'] || !!options['flipRtl'];
|
||||
return new Blockly.FieldImage(src, width, height, alt, flip_rtl);
|
||||
};
|
||||
|
||||
/**
|
||||
* Editable fields are saved by the XML renderer, non-editable fields are not.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.EDITABLE = false;
|
||||
|
||||
/**
|
||||
* Install this image on a block.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Image has already been initialized once.
|
||||
return;
|
||||
}
|
||||
// Build the DOM.
|
||||
/** @type {SVGElement} */
|
||||
this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
if (!this.visible_) {
|
||||
this.fieldGroup_.style.display = 'none';
|
||||
}
|
||||
/** @type {SVGElement} */
|
||||
this.imageElement_ = Blockly.utils.createSvgElement(
|
||||
'image',
|
||||
{
|
||||
'height': this.height_ + 'px',
|
||||
'width': this.width_ + 'px'
|
||||
},
|
||||
this.fieldGroup_);
|
||||
this.setValue(this.src_);
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
||||
|
||||
// Configure the field to be transparent with respect to tooltips.
|
||||
this.setTooltip(this.sourceBlock_);
|
||||
Blockly.Tooltip.bindMouseEvents(this.imageElement_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all DOM objects belonging to this text.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.dispose = function() {
|
||||
goog.dom.removeNode(this.fieldGroup_);
|
||||
this.fieldGroup_ = null;
|
||||
this.imageElement_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the tooltip text for this field.
|
||||
* @param {string|!Element} newTip Text for tooltip or a parent element to
|
||||
* link to for its tooltip.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.setTooltip = function(newTip) {
|
||||
this.imageElement_.tooltip = newTip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the source URL of this image.
|
||||
* @return {string} Current text.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldImage.prototype.getValue = function() {
|
||||
return this.src_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the source URL of this image.
|
||||
* @param {?string} src New source.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldImage.prototype.setValue = function(src) {
|
||||
if (src === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
this.src_ = src;
|
||||
if (this.imageElement_) {
|
||||
// Extension blocks can't rely on having access to pathToMedia, so we allow this fake URL
|
||||
// protocol instead.
|
||||
var mediaPrefix = 'media://';
|
||||
if (src.startsWith(mediaPrefix)) {
|
||||
var pathToMedia = this.sourceBlock_.workspace.options.pathToMedia;
|
||||
src = pathToMedia + src.substring(mediaPrefix.length);
|
||||
}
|
||||
this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||
'xlink:href', src || '');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get whether to flip this image in RTL
|
||||
* @return {boolean} True if we should flip in RTL.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.getFlipRTL = function() {
|
||||
return this.flipRTL_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the alt text of this image.
|
||||
* @param {?string} alt New alt text.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldImage.prototype.setText = function(alt) {
|
||||
if (alt === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
this.text_ = alt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Images are fixed width, no need to render.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldImage.prototype.render_ = function() {
|
||||
// NOP
|
||||
};
|
||||
|
||||
/**
|
||||
* Images are fixed width, no need to update.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldImage.prototype.updateWidth = function() {
|
||||
// NOP
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_image', Blockly.FieldImage);
|
||||
136
scratch-blocks/core/field_label.js
Normal file
136
scratch-blocks/core/field_label.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Non-editable text field. Used for titles, labels, etc.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldLabel');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.Tooltip');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.math.Size');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a non-editable field.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {string=} opt_class Optional CSS class for the field's text.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldLabel = function(text, opt_class) {
|
||||
this.size_ = new goog.math.Size(0, 0);
|
||||
this.class_ = opt_class;
|
||||
this.setValue(text);
|
||||
};
|
||||
goog.inherits(Blockly.FieldLabel, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldLabel from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (text, and class).
|
||||
* @returns {!Blockly.FieldLabel} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldLabel.fromJson = function(options) {
|
||||
var text = Blockly.utils.replaceMessageReferences(options['text']);
|
||||
return new Blockly.FieldLabel(text, options['class']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Editable fields usually show some sort of UI for the user to change them.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldLabel.prototype.EDITABLE = false;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the XML renderer, non-serializable fields
|
||||
* are not. Editable fields should be serialized.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldLabel.prototype.SERIALIZABLE = false;
|
||||
|
||||
/**
|
||||
* Install this text on a block.
|
||||
*/
|
||||
Blockly.FieldLabel.prototype.init = function() {
|
||||
if (this.textElement_) {
|
||||
// Text has already been initialized once.
|
||||
return;
|
||||
}
|
||||
// Build the DOM.
|
||||
this.textElement_ = Blockly.utils.createSvgElement('text',
|
||||
{
|
||||
'class': 'blocklyText',
|
||||
'y': Blockly.BlockSvg.FIELD_TOP_PADDING,
|
||||
'text-anchor': 'middle',
|
||||
'dominant-baseline': 'middle',
|
||||
'dy': goog.userAgent.EDGE_OR_IE ? Blockly.Field.IE_TEXT_OFFSET : '0'
|
||||
}, null);
|
||||
if (this.class_) {
|
||||
Blockly.utils.addClass(this.textElement_, this.class_);
|
||||
}
|
||||
if (!this.visible_) {
|
||||
this.textElement_.style.display = 'none';
|
||||
}
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.textElement_);
|
||||
|
||||
// Configure the field to be transparent with respect to tooltips.
|
||||
this.textElement_.tooltip = this.sourceBlock_;
|
||||
Blockly.Tooltip.bindMouseEvents(this.textElement_);
|
||||
// Force a render.
|
||||
this.render_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of all DOM objects belonging to this text.
|
||||
*/
|
||||
Blockly.FieldLabel.prototype.dispose = function() {
|
||||
goog.dom.removeNode(this.textElement_);
|
||||
this.textElement_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the group element for this field.
|
||||
* Used for measuring the size and for positioning.
|
||||
* @return {!Element} The group element.
|
||||
*/
|
||||
Blockly.FieldLabel.prototype.getSvgRoot = function() {
|
||||
return /** @type {!Element} */ (this.textElement_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the tooltip text for this field.
|
||||
* @param {string|!Element} newTip Text for tooltip or a parent element to
|
||||
* link to for its tooltip.
|
||||
*/
|
||||
Blockly.FieldLabel.prototype.setTooltip = function(newTip) {
|
||||
this.textElement_.tooltip = newTip;
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_label', Blockly.FieldLabel);
|
||||
125
scratch-blocks/core/field_label_serializable.js
Normal file
125
scratch-blocks/core/field_label_serializable.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Serialized label field. Behaves like a normal label but is
|
||||
* always serialized to XML. It may only be edited programmatically.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldLabelSerializable');
|
||||
|
||||
goog.require('Blockly.FieldLabel');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable getter field.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {string} opt_class Optional CSS class for the field's text.
|
||||
* @extends {Blockly.FieldLabel}
|
||||
* @constructor
|
||||
*
|
||||
*/
|
||||
Blockly.FieldLabelSerializable = function(text, opt_class) {
|
||||
Blockly.FieldLabelSerializable.superClass_.constructor.call(this, text,
|
||||
opt_class);
|
||||
// Used in base field rendering, but we don't need it.
|
||||
this.arrowWidth_ = 0;
|
||||
};
|
||||
goog.inherits(Blockly.FieldLabelSerializable, Blockly.FieldLabel);
|
||||
|
||||
/**
|
||||
* Construct a FieldLabelSerializable from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (text, and class).
|
||||
* @returns {!Blockly.FieldLabelSerializable} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldLabelSerializable.fromJson = function(options) {
|
||||
var text = Blockly.utils.replaceMessageReferences(options['text']);
|
||||
return new Blockly.FieldLabelSerializable(text, options['class']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Editable fields usually show some sort of UI for the user to change them.
|
||||
* This field should be serialized, but only edited programmatically.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldLabelSerializable.prototype.EDITABLE = false;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the XML renderer, non-serializable fields
|
||||
* are not. This field should be serialized, but only edited programmatically.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldLabelSerializable.prototype.SERIALIZABLE = true;
|
||||
|
||||
/**
|
||||
* Updates the width of the field. This calls getCachedWidth which won't cache
|
||||
* the approximated width on IE/Edge when `getComputedTextLength` fails. Once
|
||||
* it eventually does succeed, the result will be cached.
|
||||
**/
|
||||
Blockly.FieldLabelSerializable.prototype.updateWidth = function() {
|
||||
// Set width of the field.
|
||||
// Unlike the base Field class, this doesn't add space to editable fields.
|
||||
this.size_.width = Blockly.Field.getCachedWidth(this.textElement_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the border with the correct width.
|
||||
* Saves the computed width in a property.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldLabelSerializable.prototype.render_ = function() {
|
||||
if (this.visible_ && this.textElement_) {
|
||||
// Replace the text.
|
||||
goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_));
|
||||
var textNode = document.createTextNode(this.getDisplayText_());
|
||||
this.textElement_.appendChild(textNode);
|
||||
this.updateWidth();
|
||||
|
||||
// Update text centering, based on newly calculated width.
|
||||
var centerTextX = this.size_.width / 2;
|
||||
|
||||
// If half the text length is not at least center of
|
||||
// visible field (FIELD_WIDTH), center it there instead.
|
||||
var minOffset = Blockly.BlockSvg.FIELD_WIDTH / 2;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
// X position starts at the left edge of the block, in both RTL and LTR.
|
||||
// First offset by the width of the block to move to the right edge,
|
||||
// and then subtract to move to the same position as LTR.
|
||||
var minCenter = this.size_.width - minOffset;
|
||||
centerTextX = Math.min(minCenter, centerTextX);
|
||||
} else {
|
||||
// (width / 2) should exceed Blockly.BlockSvg.FIELD_WIDTH / 2
|
||||
// if the text is longer.
|
||||
centerTextX = Math.max(minOffset, centerTextX);
|
||||
}
|
||||
// Apply new text element x position.
|
||||
this.textElement_.setAttribute('x', centerTextX);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Field.register(
|
||||
'field_label_serializable', Blockly.FieldLabelSerializable);
|
||||
566
scratch-blocks/core/field_matrix.js
Normal file
566
scratch-blocks/core/field_matrix.js
Normal file
@@ -0,0 +1,566 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview 5x5 matrix input field.
|
||||
* Displays an editable 5x5 matrix for controlling LED arrays.
|
||||
* @author khanning@gmail.com (Kreg Hanning)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldMatrix');
|
||||
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
|
||||
/**
|
||||
* Class for a matrix field.
|
||||
* @param {number} matrix The default matrix value represented by a 25-bit integer.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldMatrix = function(matrix) {
|
||||
Blockly.FieldMatrix.superClass_.constructor.call(this, matrix);
|
||||
this.addArgType('matrix');
|
||||
/**
|
||||
* Array of SVGElement<rect> for matrix thumbnail image on block field.
|
||||
* @type {!Array<SVGElement>}
|
||||
* @private
|
||||
*/
|
||||
this.ledThumbNodes_ = [];
|
||||
/**
|
||||
* Array of SVGElement<rect> for matrix editor in dropdown menu.
|
||||
* @type {!Array<SVGElement>}
|
||||
* @private
|
||||
*/
|
||||
this.ledButtons_ = [];
|
||||
/**
|
||||
* String for storing current matrix value.
|
||||
* @type {!String]
|
||||
* @private
|
||||
*/
|
||||
this.matrix_ = '';
|
||||
/**
|
||||
* SVGElement for LED matrix in editor.
|
||||
* @type {?SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.matrixStage_ = null;
|
||||
/**
|
||||
* SVG image for dropdown arrow.
|
||||
* @type {?SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.arrow_ = null;
|
||||
/**
|
||||
* String indicating matrix paint style.
|
||||
* value can be [null, 'fill', 'clear'].
|
||||
* @type {?String}
|
||||
* @private
|
||||
*/
|
||||
this.paintStyle_ = null;
|
||||
/**
|
||||
* Touch event wrapper.
|
||||
* Runs when the field is selected.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.mouseDownWrapper_ = null;
|
||||
/**
|
||||
* Touch event wrapper.
|
||||
* Runs when the clear button editor button is selected.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.clearButtonWrapper_ = null;
|
||||
/**
|
||||
* Touch event wrapper.
|
||||
* Runs when the fill button editor button is selected.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.fillButtonWrapper_ = null;
|
||||
/**
|
||||
* Touch event wrapper.
|
||||
* Runs when the matrix editor is touched.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.matrixTouchWrapper_ = null;
|
||||
/**
|
||||
* Touch event wrapper.
|
||||
* Runs when the matrix editor touch event moves.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.matrixMoveWrapper_ = null;
|
||||
/**
|
||||
* Touch event wrapper.
|
||||
* Runs when the matrix editor is released.
|
||||
* @type {!Array}
|
||||
* @private
|
||||
*/
|
||||
this.matrixReleaseWrapper_ = null;
|
||||
};
|
||||
goog.inherits(Blockly.FieldMatrix, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldMatrix from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (matrix).
|
||||
* @returns {!Blockly.FieldMatrix} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldMatrix.fromJson = function(options) {
|
||||
return new Blockly.FieldMatrix(options['matrix']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fixed size of the matrix thumbnail in the input field, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.THUMBNAIL_SIZE = 26;
|
||||
|
||||
/**
|
||||
* Fixed size of each matrix thumbnail node, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.THUMBNAIL_NODE_SIZE = 4;
|
||||
|
||||
/**
|
||||
* Fixed size of each matrix thumbnail node, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.THUMBNAIL_NODE_PAD = 1;
|
||||
|
||||
/**
|
||||
* Fixed size of arrow icon in drop down menu, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.ARROW_SIZE = 12;
|
||||
|
||||
/**
|
||||
* Fixed size of each button inside the 5x5 matrix, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.MATRIX_NODE_SIZE = 18;
|
||||
|
||||
/**
|
||||
* Fixed corner radius for 5x5 matrix buttons, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.MATRIX_NODE_RADIUS = 4;
|
||||
|
||||
/**
|
||||
* Fixed padding for 5x5 matrix buttons, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.MATRIX_NODE_PAD = 5;
|
||||
|
||||
/**
|
||||
* String with 25 '0' chars.
|
||||
* Used for clearing a matrix or filling an LED node array.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.ZEROS = '0000000000000000000000000';
|
||||
|
||||
/**
|
||||
* String with 25 '1' chars.
|
||||
* Used for filling a matrix.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldMatrix.ONES = '1111111111111111111111111';
|
||||
|
||||
/**
|
||||
* Called when the field is placed on a block.
|
||||
* @param {Block} block The owning block.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Matrix menu has already been initialized once.
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the DOM.
|
||||
this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
this.size_.width = Blockly.FieldMatrix.THUMBNAIL_SIZE +
|
||||
Blockly.FieldMatrix.ARROW_SIZE + (Blockly.BlockSvg.DROPDOWN_ARROW_PADDING * 1.5);
|
||||
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
||||
|
||||
var thumbX = Blockly.BlockSvg.DROPDOWN_ARROW_PADDING / 2;
|
||||
var thumbY = (this.size_.height - Blockly.FieldMatrix.THUMBNAIL_SIZE) / 2;
|
||||
var thumbnail = Blockly.utils.createSvgElement('g', {
|
||||
'transform': 'translate(' + thumbX + ', ' + thumbY + ')',
|
||||
'pointer-events': 'bounding-box', 'cursor': 'pointer'
|
||||
}, this.fieldGroup_);
|
||||
this.ledThumbNodes_ = [];
|
||||
var nodeSize = Blockly.FieldMatrix.THUMBNAIL_NODE_SIZE;
|
||||
var nodePad = Blockly.FieldMatrix.THUMBNAIL_NODE_PAD;
|
||||
for (var i = 0; i < 5; i++) {
|
||||
for (var n = 0; n < 5; n++) {
|
||||
var attr = {
|
||||
'x': ((nodeSize + nodePad) * n) + nodePad,
|
||||
'y': ((nodeSize + nodePad) * i) + nodePad,
|
||||
'width': nodeSize, 'height': nodeSize,
|
||||
'rx': nodePad, 'ry': nodePad
|
||||
};
|
||||
this.ledThumbNodes_.push(
|
||||
Blockly.utils.createSvgElement('rect', attr, thumbnail)
|
||||
);
|
||||
}
|
||||
thumbnail.style.cursor = 'default';
|
||||
this.updateMatrix_();
|
||||
}
|
||||
|
||||
if (!this.arrow_) {
|
||||
var arrowX = Blockly.FieldMatrix.THUMBNAIL_SIZE +
|
||||
Blockly.BlockSvg.DROPDOWN_ARROW_PADDING * 1.5;
|
||||
var arrowY = (this.size_.height - Blockly.FieldMatrix.ARROW_SIZE) / 2;
|
||||
this.arrow_ = Blockly.utils.createSvgElement('image', {
|
||||
'height': Blockly.FieldMatrix.ARROW_SIZE + 'px',
|
||||
'width': Blockly.FieldMatrix.ARROW_SIZE + 'px',
|
||||
'transform': 'translate(' + arrowX + ', ' + arrowY + ')'
|
||||
}, this.fieldGroup_);
|
||||
this.arrow_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||
'xlink:href', Blockly.mainWorkspace.options.pathToMedia +
|
||||
'dropdown-arrow.svg');
|
||||
this.arrow_.style.cursor = 'default';
|
||||
}
|
||||
|
||||
this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(
|
||||
this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value for this matrix menu.
|
||||
* @param {string} matrix The new matrix value represented by a 25-bit integer.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.setValue = function(matrix) {
|
||||
if (!matrix || matrix === this.matrix_) {
|
||||
return; // No change
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
this.sourceBlock_, 'field', this.name, this.matrix_, matrix));
|
||||
}
|
||||
matrix = matrix + Blockly.FieldMatrix.ZEROS.substr(0, 25 - matrix.length);
|
||||
this.matrix_ = matrix;
|
||||
this.updateMatrix_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the value from this matrix menu.
|
||||
* @return {string} Current matrix value.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.getValue = function() {
|
||||
return String(this.matrix_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the drop-down menu for editing this field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.showEditor_ = function() {
|
||||
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
var div = Blockly.DropDownDiv.getContentDiv();
|
||||
// Build the SVG DOM.
|
||||
var matrixSize = (Blockly.FieldMatrix.MATRIX_NODE_SIZE * 5) +
|
||||
(Blockly.FieldMatrix.MATRIX_NODE_PAD * 6);
|
||||
this.matrixStage_ = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'height': matrixSize + 'px',
|
||||
'width': matrixSize + 'px'
|
||||
}, div);
|
||||
// Create the 5x5 matrix
|
||||
this.ledButtons_ = [];
|
||||
for (var i = 0; i < 5; i++) {
|
||||
for (var n = 0; n < 5; n++) {
|
||||
var x = (Blockly.FieldMatrix.MATRIX_NODE_SIZE * n) +
|
||||
(Blockly.FieldMatrix.MATRIX_NODE_PAD * (n + 1));
|
||||
var y = (Blockly.FieldMatrix.MATRIX_NODE_SIZE * i) +
|
||||
(Blockly.FieldMatrix.MATRIX_NODE_PAD * (i + 1));
|
||||
var attr = {
|
||||
'x': x + 'px', 'y': y + 'px',
|
||||
'width': Blockly.FieldMatrix.MATRIX_NODE_SIZE,
|
||||
'height': Blockly.FieldMatrix.MATRIX_NODE_SIZE,
|
||||
'rx': Blockly.FieldMatrix.MATRIX_NODE_RADIUS,
|
||||
'ry': Blockly.FieldMatrix.MATRIX_NODE_RADIUS
|
||||
};
|
||||
var led = Blockly.utils.createSvgElement('rect', attr, this.matrixStage_);
|
||||
this.matrixStage_.appendChild(led);
|
||||
this.ledButtons_.push(led);
|
||||
}
|
||||
}
|
||||
// Div for lower button menu
|
||||
var buttonDiv = document.createElement('div');
|
||||
// Button to clear matrix
|
||||
var clearButtonDiv = document.createElement('div');
|
||||
clearButtonDiv.className = 'scratchMatrixButtonDiv';
|
||||
var clearButton = this.createButton_(this.sourceBlock_.colourSecondary_);
|
||||
clearButtonDiv.appendChild(clearButton);
|
||||
// Button to fill matrix
|
||||
var fillButtonDiv = document.createElement('div');
|
||||
fillButtonDiv.className = 'scratchMatrixButtonDiv';
|
||||
var fillButton = this.createButton_('#FFFFFF');
|
||||
fillButtonDiv.appendChild(fillButton);
|
||||
|
||||
buttonDiv.appendChild(clearButtonDiv);
|
||||
buttonDiv.appendChild(fillButtonDiv);
|
||||
div.appendChild(buttonDiv);
|
||||
|
||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.getColour(),
|
||||
this.sourceBlock_.getColourTertiary());
|
||||
Blockly.DropDownDiv.setCategory(this.sourceBlock_.getCategory());
|
||||
Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
|
||||
|
||||
this.matrixTouchWrapper_ =
|
||||
Blockly.bindEvent_(this.matrixStage_, 'mousedown', this, this.onMouseDown);
|
||||
this.clearButtonWrapper_ =
|
||||
Blockly.bindEvent_(clearButton, 'click', this, this.clearMatrix_);
|
||||
this.fillButtonWrapper_ =
|
||||
Blockly.bindEvent_(fillButton, 'click', this, this.fillMatrix_);
|
||||
|
||||
// Update the matrix for the current value
|
||||
this.updateMatrix_();
|
||||
|
||||
};
|
||||
|
||||
this.nodeCallback_ = function(e, num) {
|
||||
console.log(num);
|
||||
};
|
||||
|
||||
/**
|
||||
* Make an svg object that resembles a 3x3 matrix to be used as a button.
|
||||
* @param {string} fill The color to fill the matrix nodes.
|
||||
* @return {SvgElement} The button svg element.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.createButton_ = function(fill) {
|
||||
var button = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'height': Blockly.FieldMatrix.MATRIX_NODE_SIZE + 'px',
|
||||
'width': Blockly.FieldMatrix.MATRIX_NODE_SIZE + 'px'
|
||||
});
|
||||
var nodeSize = Blockly.FieldMatrix.MATRIX_NODE_SIZE / 4;
|
||||
var nodePad = Blockly.FieldMatrix.MATRIX_NODE_SIZE / 16;
|
||||
for (var i = 0; i < 3; i++) {
|
||||
for (var n = 0; n < 3; n++) {
|
||||
Blockly.utils.createSvgElement('rect', {
|
||||
'x': ((nodeSize + nodePad) * n) + nodePad,
|
||||
'y': ((nodeSize + nodePad) * i) + nodePad,
|
||||
'width': nodeSize, 'height': nodeSize,
|
||||
'rx': nodePad, 'ry': nodePad,
|
||||
'fill': fill
|
||||
}, button);
|
||||
}
|
||||
}
|
||||
return button;
|
||||
};
|
||||
|
||||
/**
|
||||
* Redraw the matrix with the current value.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.updateMatrix_ = function() {
|
||||
for (var i = 0; i < this.matrix_.length; i++) {
|
||||
if (this.matrix_[i] === '0') {
|
||||
this.fillMatrixNode_(this.ledButtons_, i, this.sourceBlock_.colourSecondary_);
|
||||
this.fillMatrixNode_(this.ledThumbNodes_, i, this.sourceBlock_.colour_);
|
||||
} else {
|
||||
this.fillMatrixNode_(this.ledButtons_, i, '#FFFFFF');
|
||||
this.fillMatrixNode_(this.ledThumbNodes_, i, '#FFFFFF');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the matrix.
|
||||
* @param {!Event} e Mouse event.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.clearMatrix_ = function(e) {
|
||||
if (e.button != 0) return;
|
||||
this.setValue(Blockly.FieldMatrix.ZEROS);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill the matrix.
|
||||
* @param {!Event} e Mouse event.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.fillMatrix_ = function(e) {
|
||||
if (e.button != 0) return;
|
||||
this.setValue(Blockly.FieldMatrix.ONES);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill matrix node with specified colour.
|
||||
* @param {!Array<SVGElement>} node The array of matrix nodes.
|
||||
* @param {!number} index The index of the matrix node.
|
||||
* @param {!string} fill The fill colour in '#rrggbb' format.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.fillMatrixNode_ = function(node, index, fill) {
|
||||
if (!node || !node[index] || !fill) return;
|
||||
node[index].setAttribute('fill', fill);
|
||||
};
|
||||
|
||||
Blockly.FieldMatrix.prototype.setLEDNode_ = function(led, state) {
|
||||
if (led < 0 || led > 24) return;
|
||||
var matrix = this.matrix_.substr(0, led) + state + this.matrix_.substr(led + 1);
|
||||
this.setValue(matrix);
|
||||
};
|
||||
|
||||
Blockly.FieldMatrix.prototype.fillLEDNode_ = function(led) {
|
||||
if (led < 0 || led > 24) return;
|
||||
this.setLEDNode_(led, '1');
|
||||
};
|
||||
|
||||
Blockly.FieldMatrix.prototype.clearLEDNode_ = function(led) {
|
||||
if (led < 0 || led > 24) return;
|
||||
this.setLEDNode_(led, '0');
|
||||
};
|
||||
|
||||
Blockly.FieldMatrix.prototype.toggleLEDNode_ = function(led) {
|
||||
if (led < 0 || led > 24) return;
|
||||
if (this.matrix_.charAt(led) === '0') {
|
||||
this.setLEDNode_(led, '1');
|
||||
} else {
|
||||
this.setLEDNode_(led, '0');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle matrix nodes on and off.
|
||||
* @param {!Event} e Mouse event.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.onMouseDown = function(e) {
|
||||
this.matrixMoveWrapper_ =
|
||||
Blockly.bindEvent_(document.body, 'mousemove', this, this.onMouseMove);
|
||||
this.matrixReleaseWrapper_ =
|
||||
Blockly.bindEvent_(document.body, 'mouseup', this, this.onMouseUp);
|
||||
var ledHit = this.checkForLED_(e);
|
||||
if (ledHit > -1) {
|
||||
if (this.matrix_.charAt(ledHit) === '0') {
|
||||
this.paintStyle_ = 'fill';
|
||||
} else {
|
||||
this.paintStyle_ = 'clear';
|
||||
}
|
||||
this.toggleLEDNode_(ledHit);
|
||||
this.updateMatrix_();
|
||||
} else {
|
||||
this.paintStyle_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind mouse move event and clear the paint style.
|
||||
* @param {!Event} e Mouse move event.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.onMouseUp = function() {
|
||||
Blockly.unbindEvent_(this.matrixMoveWrapper_);
|
||||
Blockly.unbindEvent_(this.matrixReleaseWrapper_);
|
||||
this.paintStyle_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle matrix nodes on and off by dragging mouse.
|
||||
* @param {!Event} e Mouse move event.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.onMouseMove = function(e) {
|
||||
e.preventDefault();
|
||||
if (this.paintStyle_) {
|
||||
var led = this.checkForLED_(e);
|
||||
if (led < 0) return;
|
||||
if (this.paintStyle_ === 'clear') {
|
||||
this.clearLEDNode_(led);
|
||||
} else if (this.paintStyle_ === 'fill') {
|
||||
this.fillLEDNode_(led);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if mouse coordinates collide with a matrix node.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @return {number} The matching matrix node or -1 for none.
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.checkForLED_ = function(e) {
|
||||
var bBox = this.matrixStage_.getBoundingClientRect();
|
||||
var nodeSize = Blockly.FieldMatrix.MATRIX_NODE_SIZE;
|
||||
var nodePad = Blockly.FieldMatrix.MATRIX_NODE_PAD;
|
||||
var dx = e.clientX - bBox.left;
|
||||
var dy = e.clientY - bBox.top;
|
||||
var min = nodePad / 2;
|
||||
var max = bBox.width - (nodePad / 2);
|
||||
if (dx < min || dx > max || dy < min || dy > max) {
|
||||
return -1;
|
||||
}
|
||||
var xDiv = Math.trunc((dx - nodePad / 2) / (nodeSize + nodePad));
|
||||
var yDiv = Math.trunc((dy - nodePad / 2) / (nodeSize + nodePad));
|
||||
return xDiv + (yDiv * nodePad);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up this FieldMatrix, as well as the inherited Field.
|
||||
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldMatrix.prototype.dispose_ = function() {
|
||||
var thisField = this;
|
||||
return function() {
|
||||
Blockly.FieldMatrix.superClass_.dispose_.call(thisField)();
|
||||
thisField.matrixStage_ = null;
|
||||
if (thisField.mouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.mouseDownWrapper_);
|
||||
}
|
||||
if (thisField.matrixTouchWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.matrixTouchWrapper_);
|
||||
}
|
||||
if (thisField.matrixReleaseWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.matrixReleaseWrapper_);
|
||||
}
|
||||
if (thisField.matrixMoveWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.matrixMoveWrapper_);
|
||||
}
|
||||
if (thisField.clearButtonWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.clearButtonWrapper_);
|
||||
}
|
||||
if (thisField.fillButtonWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.fillButtonWrapper_);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_matrix', Blockly.FieldMatrix);
|
||||
850
scratch-blocks/core/field_note.js
Normal file
850
scratch-blocks/core/field_note.js
Normal file
@@ -0,0 +1,850 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2018 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Note input field, for selecting a musical note on a piano.
|
||||
* @author ericr@media.mit.edu (Eric Rosenbaum)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldNote');
|
||||
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('goog.math');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
/**
|
||||
* Class for a note input field, for selecting a musical note on a piano.
|
||||
* @param {(string|number)=} opt_value The initial content of the field. The
|
||||
* value should cast to a number, and if it does not, '0' will be used.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns the accepted text or null to abort
|
||||
* the change.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldNote = function(opt_value, opt_validator) {
|
||||
opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
|
||||
Blockly.FieldNote.superClass_.constructor.call(
|
||||
this, opt_value, opt_validator);
|
||||
this.addArgType('note');
|
||||
|
||||
/**
|
||||
* Width of the field. Computed when drawing it, and used for animation.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.fieldEditorWidth_ = 0;
|
||||
|
||||
/**
|
||||
* Height of the field. Computed when drawing it.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.fieldEditorHeight_ = 0;
|
||||
|
||||
/**
|
||||
* The piano SVG.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.pianoSVG_ = null;
|
||||
|
||||
/**
|
||||
* Array of SVG elements representing the clickable piano keys.
|
||||
* @type {!Array<SVGElement>}
|
||||
* @private
|
||||
*/
|
||||
this.keySVGs_ = [];
|
||||
|
||||
/**
|
||||
* Note name indicator at the top of the field.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.noteNameText_ = null;
|
||||
|
||||
/**
|
||||
* Note name indicator on the low C key.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.lowCText_ = null;
|
||||
|
||||
/**
|
||||
* Note name indicator on the low C key.
|
||||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.highCText_ = null;
|
||||
|
||||
/**
|
||||
* Octave number of the currently displayed range of keys.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.displayedOctave_ = null;
|
||||
|
||||
/**
|
||||
* Current animation position of the piano SVG, as it shifts left or right to
|
||||
* change octaves.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.animationPos_ = 0;
|
||||
|
||||
/**
|
||||
* Target position for the animation as the piano SVG shifts left or right.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.animationTarget_ = 0;
|
||||
|
||||
/**
|
||||
* A flag indicating that the mouse is currently down. Used in combination with
|
||||
* mouse enter events to update the key selection while dragging.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.mouseIsDown_ = false;
|
||||
|
||||
/**
|
||||
* An array of wrappers for mouse down events on piano keys.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.mouseDownWrappers_ = [];
|
||||
|
||||
/**
|
||||
* A wrapper for the mouse up event.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.mouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* An array of wrappers for mouse enter events on piano keys.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.mouseEnterWrappers_ = [];
|
||||
|
||||
/**
|
||||
* A wrapper for the mouse down event on the octave down button.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.octaveDownMouseDownWrapper_ = null;
|
||||
|
||||
/**
|
||||
* A wrapper for the mouse down event on the octave up button.
|
||||
* @type {!Array.<!Array>}
|
||||
* @private
|
||||
*/
|
||||
this.octaveUpMouseDownWrapper_ = null;
|
||||
};
|
||||
goog.inherits(Blockly.FieldNote, Blockly.FieldTextInput);
|
||||
|
||||
/**
|
||||
* Inset in pixels of content displayed in the field, caused by parent properties.
|
||||
* The inset is actually determined by the CSS property blocklyDropDownDiv- it is
|
||||
* the sum of the padding and border thickness.
|
||||
*/
|
||||
Blockly.FieldNote.INSET = 5;
|
||||
|
||||
/**
|
||||
* Height of the top area of the field, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.TOP_MENU_HEIGHT = 32 - Blockly.FieldNote.INSET;
|
||||
|
||||
/**
|
||||
* Padding on the top and sides of the field, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.EDGE_PADDING = 1;
|
||||
|
||||
/**
|
||||
* Height of the drop shadow on the piano, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.SHADOW_HEIGHT = 4;
|
||||
|
||||
/**
|
||||
* Color for the shadow on the piano.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.SHADOW_COLOR = '#000';
|
||||
|
||||
/**
|
||||
* Opacity for the shadow on the piano.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.SHADOW_OPACITY = .2;
|
||||
|
||||
/**
|
||||
* A color for the white piano keys.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.WHITE_KEY_COLOR = '#FFFFFF';
|
||||
|
||||
/**
|
||||
* A color for the black piano keys.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.BLACK_KEY_COLOR = '#323133';
|
||||
|
||||
/**
|
||||
* A color for stroke around black piano keys.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.BLACK_KEY_STROKE = '#555555';
|
||||
|
||||
/**
|
||||
* A color for the selected state of a piano key.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.KEY_SELECTED_COLOR = '#b0d6ff';
|
||||
|
||||
/**
|
||||
* The number of white keys in one octave on the piano.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.NUM_WHITE_KEYS = 8;
|
||||
|
||||
/**
|
||||
* Height of a white piano key, in px.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.WHITE_KEY_HEIGHT = 72;
|
||||
|
||||
/**
|
||||
* Width of a white piano key, in px.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.WHITE_KEY_WIDTH = 40;
|
||||
|
||||
/**
|
||||
* Height of a black piano key, in px.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.BLACK_KEY_HEIGHT = 40;
|
||||
|
||||
/**
|
||||
* Width of a black piano key, in px.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.BLACK_KEY_WIDTH = 32;
|
||||
|
||||
/**
|
||||
* Radius of the curved bottom corner of a piano key, in px.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.KEY_RADIUS = 6;
|
||||
|
||||
/**
|
||||
* Bottom padding for the labels on C keys.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.KEY_LABEL_PADDING = 8;
|
||||
|
||||
/**
|
||||
* An array of objects with data describing the keys on the piano.
|
||||
* @type {Array.<{name: String, pitch: Number, isBlack: boolean}>}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.KEY_INFO = [
|
||||
{name: 'C', pitch: 0},
|
||||
{name: 'C♯', pitch: 1, isBlack: true},
|
||||
{name: 'D', pitch: 2},
|
||||
{name: 'E♭', pitch: 3, isBlack: true},
|
||||
{name: 'E', pitch: 4},
|
||||
{name: 'F', pitch: 5},
|
||||
{name: 'F♯', pitch: 6, isBlack: true},
|
||||
{name: 'G', pitch: 7},
|
||||
{name: 'G♯', pitch: 8, isBlack: true},
|
||||
{name: 'A', pitch: 9},
|
||||
{name: 'B♭', pitch: 10, isBlack: true},
|
||||
{name: 'B', pitch: 11},
|
||||
{name: 'C', pitch: 12}
|
||||
];
|
||||
|
||||
/**
|
||||
* The MIDI note number of the highest note selectable on the piano.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.MAX_NOTE = 130;
|
||||
|
||||
/**
|
||||
* The fraction of the distance to the target location to move the piano at each
|
||||
* step of the animation.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.ANIMATION_FRACTION = 0.2;
|
||||
|
||||
/**
|
||||
* Path to the arrow svg icon, used on the octave buttons.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.ARROW_SVG_PATH = 'icons/arrow_button.svg';
|
||||
|
||||
/**
|
||||
* The size of the square octave buttons.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNote.OCTAVE_BUTTON_SIZE = 32;
|
||||
|
||||
/**
|
||||
* Construct a FieldNote from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options.
|
||||
* @returns {!Blockly.FieldNote} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldNote.fromJson = function(options) {
|
||||
return new Blockly.FieldNote(options['note']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean up this FieldNote, as well as the inherited FieldTextInput.
|
||||
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.dispose_ = function() {
|
||||
var thisField = this;
|
||||
return function() {
|
||||
Blockly.FieldNote.superClass_.dispose_.call(thisField)();
|
||||
thisField.mouseDownWrappers_.forEach(function(wrapper) {
|
||||
Blockly.unbindEvent_(wrapper);
|
||||
});
|
||||
thisField.mouseEnterWrappers_.forEach(function(wrapper) {
|
||||
Blockly.unbindEvent_(wrapper);
|
||||
});
|
||||
if (thisField.mouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.mouseUpWrapper_);
|
||||
}
|
||||
if (thisField.octaveDownMouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.octaveDownMouseDownWrapper_);
|
||||
}
|
||||
if (thisField.octaveUpMouseDownWrapper_) {
|
||||
Blockly.unbindEvent_(thisField.octaveUpMouseDownWrapper_);
|
||||
}
|
||||
this.pianoSVG_ = null;
|
||||
this.keySVGs_.length = 0;
|
||||
this.noteNameText_ = null;
|
||||
this.lowCText_ = null;
|
||||
this.highCText_ = null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a field with piano keys.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.showEditor_ = function() {
|
||||
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
||||
Blockly.FieldNote.superClass_.showEditor_.call(this, this.useTouchInteraction_);
|
||||
|
||||
// If there is an existing drop-down someone else owns, hide it immediately and clear it.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
|
||||
// Build the SVG DOM.
|
||||
var div = Blockly.DropDownDiv.getContentDiv();
|
||||
|
||||
this.fieldEditorWidth_ = Blockly.FieldNote.NUM_WHITE_KEYS * Blockly.FieldNote.WHITE_KEY_WIDTH +
|
||||
Blockly.FieldNote.EDGE_PADDING;
|
||||
this.fieldEditorHeight_ = Blockly.FieldNote.TOP_MENU_HEIGHT +
|
||||
Blockly.FieldNote.WHITE_KEY_HEIGHT +
|
||||
Blockly.FieldNote.EDGE_PADDING;
|
||||
|
||||
var svg = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'height': this.fieldEditorHeight_ + 'px',
|
||||
'width': this.fieldEditorWidth_ + 'px'
|
||||
}, div);
|
||||
|
||||
// Add the white and black keys
|
||||
// Since we are adding the keys from left to right in order, they need
|
||||
// to be in two groups in order to layer correctly.
|
||||
this.pianoSVG_ = Blockly.utils.createSvgElement('g', {}, svg);
|
||||
var whiteKeyGroup = Blockly.utils.createSvgElement('g', {}, this.pianoSVG_);
|
||||
var blackKeyGroup = Blockly.utils.createSvgElement('g', {}, this.pianoSVG_);
|
||||
|
||||
// Add three piano octaves, so we can animate moving up or down an octave.
|
||||
// Only the middle octave gets bound to events.
|
||||
this.keySVGs_ = [];
|
||||
this.addPianoOctave_(-this.fieldEditorWidth_ + Blockly.FieldNote.EDGE_PADDING,
|
||||
whiteKeyGroup, blackKeyGroup, null);
|
||||
this.addPianoOctave_(0, whiteKeyGroup, blackKeyGroup, this.keySVGs_);
|
||||
this.addPianoOctave_(this.fieldEditorWidth_ - Blockly.FieldNote.EDGE_PADDING,
|
||||
whiteKeyGroup, blackKeyGroup, null);
|
||||
|
||||
// Note name indicator at the top of the field
|
||||
this.noteNameText_ = Blockly.utils.createSvgElement('text',
|
||||
{
|
||||
'x': this.fieldEditorWidth_ / 2,
|
||||
'y': Blockly.FieldNote.TOP_MENU_HEIGHT / 2,
|
||||
'class': 'blocklyText',
|
||||
'text-anchor': 'middle',
|
||||
'dominant-baseline': 'middle',
|
||||
}, svg);
|
||||
|
||||
// Note names on the low and high C keys
|
||||
var lowCX = Blockly.FieldNote.WHITE_KEY_WIDTH / 2;
|
||||
this.lowCText_ = this.addCKeyLabel_(lowCX, svg);
|
||||
var highCX = lowCX + (Blockly.FieldNote.WHITE_KEY_WIDTH *
|
||||
(Blockly.FieldNote.NUM_WHITE_KEYS - 1));
|
||||
this.highCText_ = this.addCKeyLabel_(highCX, svg);
|
||||
|
||||
// Horizontal line at the top of the keys
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{
|
||||
'stroke': this.sourceBlock_.getColourTertiary(),
|
||||
'x1': 0,
|
||||
'y1': Blockly.FieldNote.TOP_MENU_HEIGHT,
|
||||
'x2': this.fieldEditorWidth_,
|
||||
'y2': Blockly.FieldNote.TOP_MENU_HEIGHT
|
||||
}, svg);
|
||||
|
||||
// Drop shadow at the top of the keys
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'x': 0,
|
||||
'y': Blockly.FieldNote.TOP_MENU_HEIGHT,
|
||||
'width': this.fieldEditorWidth_,
|
||||
'height': Blockly.FieldNote.SHADOW_HEIGHT,
|
||||
'fill': Blockly.FieldNote.SHADOW_COLOR,
|
||||
'fill-opacity': Blockly.FieldNote.SHADOW_OPACITY
|
||||
}, svg);
|
||||
|
||||
// Octave buttons
|
||||
this.octaveDownButton = this.addOctaveButton_(0, true, svg);
|
||||
this.octaveUpButton = this.addOctaveButton_(
|
||||
(this.fieldEditorWidth_ + Blockly.FieldNote.INSET * 2) -
|
||||
Blockly.FieldNote.OCTAVE_BUTTON_SIZE, false, svg);
|
||||
|
||||
this.octaveDownMouseDownWrapper_ =
|
||||
Blockly.bindEvent_(this.octaveDownButton, 'mousedown', this, function() {
|
||||
this.changeOctaveBy_(-1);
|
||||
});
|
||||
this.octaveUpMouseDownWrapper_ =
|
||||
Blockly.bindEvent_(this.octaveUpButton, 'mousedown', this,function() {
|
||||
this.changeOctaveBy_(1);
|
||||
});
|
||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.parentBlock_.getColour(),
|
||||
this.sourceBlock_.getColourTertiary());
|
||||
Blockly.DropDownDiv.setCategory(this.sourceBlock_.parentBlock_.getCategory());
|
||||
Blockly.DropDownDiv.showPositionedByBlock(this, this.sourceBlock_);
|
||||
|
||||
this.updateSelection_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add one octave of piano keys drawn using SVG.
|
||||
* @param {number} x The x position of the left edge of this octave of keys.
|
||||
* @param {SVGElement} whiteKeyGroup The group for all white piano keys.
|
||||
* @param {SvgElement} blackKeyGroup The group for all black piano keys.
|
||||
* @param {!Array.<SvgElement>} keySVGarray An array containing all the key SVGs.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.addPianoOctave_ = function(x, whiteKeyGroup, blackKeyGroup, keySVGarray) {
|
||||
var xIncrement, width, height, fill, stroke, group;
|
||||
x += Blockly.FieldNote.EDGE_PADDING / 2;
|
||||
var y = Blockly.FieldNote.TOP_MENU_HEIGHT;
|
||||
for (var i = 0; i < Blockly.FieldNote.KEY_INFO.length; i++) {
|
||||
// Draw a black or white key
|
||||
if (Blockly.FieldNote.KEY_INFO[i].isBlack) {
|
||||
// Black keys are shifted back half a key
|
||||
x -= Blockly.FieldNote.BLACK_KEY_WIDTH / 2;
|
||||
xIncrement = Blockly.FieldNote.BLACK_KEY_WIDTH / 2;
|
||||
width = Blockly.FieldNote.BLACK_KEY_WIDTH;
|
||||
height = Blockly.FieldNote.BLACK_KEY_HEIGHT;
|
||||
fill = Blockly.FieldNote.BLACK_KEY_COLOR;
|
||||
stroke = Blockly.FieldNote.BLACK_KEY_STROKE;
|
||||
group = blackKeyGroup;
|
||||
} else {
|
||||
xIncrement = Blockly.FieldNote.WHITE_KEY_WIDTH;
|
||||
width = Blockly.FieldNote.WHITE_KEY_WIDTH;
|
||||
height = Blockly.FieldNote.WHITE_KEY_HEIGHT;
|
||||
fill = Blockly.FieldNote.WHITE_KEY_COLOR;
|
||||
stroke = this.sourceBlock_.getColourTertiary();
|
||||
group = whiteKeyGroup;
|
||||
}
|
||||
var attr = {
|
||||
'd': this.getPianoKeyPath_(x, y, width, height),
|
||||
'fill': fill,
|
||||
'stroke': stroke
|
||||
};
|
||||
x += xIncrement;
|
||||
|
||||
var keySVG = Blockly.utils.createSvgElement('path', attr, group);
|
||||
|
||||
if (keySVGarray) {
|
||||
keySVGarray[i] = keySVG;
|
||||
keySVG.setAttribute('data-pitch', Blockly.FieldNote.KEY_INFO[i].pitch);
|
||||
keySVG.setAttribute('data-name', Blockly.FieldNote.KEY_INFO[i].name);
|
||||
keySVG.setAttribute('data-isBlack', Blockly.FieldNote.KEY_INFO[i].isBlack);
|
||||
|
||||
this.mouseDownWrappers_[i] =
|
||||
Blockly.bindEvent_(keySVG, 'mousedown', this, this.onMouseDownOnKey_);
|
||||
this.mouseEnterWrappers_[i] =
|
||||
Blockly.bindEvent_(keySVG, 'mouseenter', this, this.onMouseEnter_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the SVG path string for a piano key shape: a rectangle with rounded
|
||||
* corners at the bottom.
|
||||
* @param {number} x the x position for the key.
|
||||
* @param {number} y the y position for the key.
|
||||
* @param {number} width the width of the key.
|
||||
* @param {number} height the height of the key.
|
||||
* @returns {string} the SVG path as a string.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.getPianoKeyPath_ = function(x, y, width, height) {
|
||||
return 'M' + x + ' ' + y + ' ' +
|
||||
'L' + x + ' ' + (y + height - Blockly.FieldNote.KEY_RADIUS) + ' ' +
|
||||
'Q' + x + ' ' + (y + height) + ' ' +
|
||||
(x + Blockly.FieldNote.KEY_RADIUS) + ' ' + (y + height) + ' ' +
|
||||
'L' + (x + width - Blockly.FieldNote.KEY_RADIUS) + ' ' + (y + height) + ' ' +
|
||||
'Q' + (x + width) + ' ' + (y + height) + ' ' +
|
||||
(x + width) + ' ' + (y + height - Blockly.FieldNote.KEY_RADIUS) + ' ' +
|
||||
'L' + (x + width) + ' ' + y + ' ' +
|
||||
'L' + x + ' ' + y;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a button for switching the displayed octave of the piano up or down.
|
||||
* @param {number} x The x position of the button.
|
||||
* @param {boolean} flipped If true, the icon should be flipped.
|
||||
* @param {SvgElement} svg The svg element to add the buttons to.
|
||||
* @returns {SvgElement} A group containing the button SVG elements.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.addOctaveButton_ = function(x, flipped, svg) {
|
||||
var group = Blockly.utils.createSvgElement('g', {}, svg);
|
||||
var imageSize = Blockly.FieldNote.OCTAVE_BUTTON_SIZE;
|
||||
var arrow = Blockly.utils.createSvgElement('image',
|
||||
{
|
||||
'width': imageSize,
|
||||
'height': imageSize,
|
||||
'x': x - Blockly.FieldNote.INSET,
|
||||
'y': -1 * Blockly.FieldNote.INSET
|
||||
}, group);
|
||||
arrow.setAttributeNS(
|
||||
'http://www.w3.org/1999/xlink',
|
||||
'xlink:href',
|
||||
Blockly.mainWorkspace.options.pathToMedia + Blockly.FieldNote.ARROW_SVG_PATH
|
||||
);
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{
|
||||
'stroke': this.sourceBlock_.getColourTertiary(),
|
||||
'x1': x - Blockly.FieldNote.INSET,
|
||||
'y1': 0,
|
||||
'x2': x - Blockly.FieldNote.INSET,
|
||||
'y2': Blockly.FieldNote.TOP_MENU_HEIGHT - Blockly.FieldNote.INSET
|
||||
}, group);
|
||||
if (flipped) {
|
||||
var translateX = -1 * Blockly.FieldNote.OCTAVE_BUTTON_SIZE + (Blockly.FieldNote.INSET * 2);
|
||||
group.setAttribute('transform', 'scale(-1, 1) ' +
|
||||
'translate(' + translateX + ', 0)');
|
||||
}
|
||||
return group;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an SVG text label for display on the C keys of the piano.
|
||||
* @param {number} x The x position for the label.
|
||||
* @param {SvgElement} svg The SVG element to add the label to.
|
||||
* @returns {SvgElement} The SVG element containing the label.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.addCKeyLabel_ = function(x, svg) {
|
||||
return Blockly.utils.createSvgElement('text',
|
||||
{
|
||||
'x': x,
|
||||
'y': Blockly.FieldNote.TOP_MENU_HEIGHT + Blockly.FieldNote.WHITE_KEY_HEIGHT -
|
||||
Blockly.FieldNote.KEY_LABEL_PADDING,
|
||||
'class': 'scratchNotePickerKeyLabel',
|
||||
'text-anchor': 'middle'
|
||||
}, svg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the visibility of the C key labels.
|
||||
* @param {boolean} visible If true, set labels to be visible.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.setCKeyLabelsVisible_ = function(visible) {
|
||||
if (visible) {
|
||||
this.fadeSvgToOpacity_(this.lowCText_, 1);
|
||||
this.fadeSvgToOpacity_(this.highCText_, 1);
|
||||
} else {
|
||||
this.fadeSvgToOpacity_(this.lowCText_, 0);
|
||||
this.fadeSvgToOpacity_(this.highCText_, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate an SVG to fade it in or out to a target opacity.
|
||||
* @param {SvgElement} svg The SVG element to apply the fade to.
|
||||
* @param {number} opacity The target opacity.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.fadeSvgToOpacity_ = function(svg, opacity) {
|
||||
svg.setAttribute('style', 'opacity: ' + opacity + '; transition: opacity 0.1s;');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the mouse down event on a piano key.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.onMouseDownOnKey_ = function(e) {
|
||||
this.mouseIsDown_ = true;
|
||||
this.mouseUpWrapper_ = Blockly.bindEvent_(document.body, 'mouseup', this, this.onMouseUp_);
|
||||
this.selectNoteWithMouseEvent_(e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the mouse up event following a mouse down on a piano key.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.onMouseUp_ = function() {
|
||||
this.mouseIsDown_ = false;
|
||||
Blockly.unbindEvent_(this.mouseUpWrapper_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the event when the mouse enters a piano key.
|
||||
* @param {!Event} e Mouse enter event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.onMouseEnter_ = function(e) {
|
||||
if (this.mouseIsDown_) {
|
||||
this.selectNoteWithMouseEvent_(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Use the data in a mouse event to select a new note, and play it.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.selectNoteWithMouseEvent_ = function(e) {
|
||||
var newNoteNum = Number(e.target.getAttribute('data-pitch')) + this.displayedOctave_ * 12;
|
||||
this.setNoteNum_(newNoteNum);
|
||||
this.playNoteInternal_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Play a note, by calling the externally overriden play note function.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.playNoteInternal_ = function() {
|
||||
if (Blockly.FieldNote.playNote_) {
|
||||
Blockly.FieldNote.playNote_(
|
||||
this.getValue(),
|
||||
this.sourceBlock_.parentBlock_.getCategory()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to play a musical note corresponding to the key selected.
|
||||
* Overridden externally.
|
||||
* @param {number} noteNum the MIDI note number to play.
|
||||
* @param {string} id An id to select a scratch extension to play the note.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.playNote_ = function(/* noteNum, id*/) {
|
||||
return;
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the selected note by a number of octaves, and start the animation.
|
||||
* @param {number} octaves The number of octaves to change by.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.changeOctaveBy_ = function(octaves) {
|
||||
this.displayedOctave_ += octaves;
|
||||
if (this.displayedOctave_ < 0) {
|
||||
this.displayedOctave_ = 0;
|
||||
return;
|
||||
}
|
||||
var maxOctave = Math.floor(Blockly.FieldNote.MAX_NOTE / 12);
|
||||
if (this.displayedOctave_ > maxOctave) {
|
||||
this.displayedOctave_ = maxOctave;
|
||||
return;
|
||||
}
|
||||
|
||||
var newNote = Number(this.getText()) + (octaves * 12);
|
||||
this.setNoteNum_(newNote);
|
||||
|
||||
this.animationTarget_ = this.fieldEditorWidth_ * octaves * -1;
|
||||
this.animationPos_ = 0;
|
||||
this.stepOctaveAnimation_();
|
||||
this.setCKeyLabelsVisible_(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate the piano up or down an octave by sliding it to the left or right.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.stepOctaveAnimation_ = function() {
|
||||
var absDiff = Math.abs(this.animationPos_ - this.animationTarget_);
|
||||
if (absDiff < 1) {
|
||||
this.pianoSVG_.setAttribute('transform', 'translate(0, 0)');
|
||||
this.setCKeyLabelsVisible_(true);
|
||||
this.playNoteInternal_();
|
||||
return;
|
||||
}
|
||||
this.animationPos_ += (this.animationTarget_ - this.animationPos_) *
|
||||
Blockly.FieldNote.ANIMATION_FRACTION;
|
||||
this.pianoSVG_.setAttribute('transform', 'translate(' + this.animationPos_ + ',0)');
|
||||
requestAnimationFrame(this.stepOctaveAnimation_.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the selected note number, and update the piano display and the input field.
|
||||
* @param {number} noteNum The MIDI note number to select.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.setNoteNum_ = function(noteNum) {
|
||||
noteNum = this.callValidator(noteNum);
|
||||
this.setValue(noteNum);
|
||||
Blockly.FieldTextInput.htmlInput_.value = noteNum;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the text in this field. Triggers a rerender of the source block, and
|
||||
* updates the selection on the field.
|
||||
* @param {?string} text New text.
|
||||
*/
|
||||
Blockly.FieldNote.prototype.setText = function(text) {
|
||||
Blockly.FieldNote.superClass_.setText.call(this, text);
|
||||
if (!this.textElement_) {
|
||||
// Not rendered yet.
|
||||
return;
|
||||
}
|
||||
this.updateSelection_();
|
||||
// Cached width is obsolete. Clear it.
|
||||
this.size_.width = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* For a MIDI note number, find the index of the corresponding piano key.
|
||||
* @param {number} noteNum The note number.
|
||||
* @returns {number} The index of the piano key.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.noteNumToKeyIndex_ = function(noteNum) {
|
||||
return Math.floor(noteNum) - (this.displayedOctave_ * 12);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the selected note and labels on the field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNote.prototype.updateSelection_ = function() {
|
||||
var noteNum = Number(this.getText());
|
||||
|
||||
// If the note is outside the currently displayed octave, update it
|
||||
if (this.displayedOctave_ == null ||
|
||||
noteNum > ((this.displayedOctave_ * 12) + 12) ||
|
||||
noteNum < (this.displayedOctave_ * 12)) {
|
||||
this.displayedOctave_ = Math.floor(noteNum / 12);
|
||||
}
|
||||
|
||||
var index = this.noteNumToKeyIndex_(noteNum);
|
||||
|
||||
// Clear the highlight on all keys
|
||||
this.keySVGs_.forEach(function(svg) {
|
||||
var isBlack = svg.getAttribute('data-isBlack');
|
||||
if (isBlack === 'true') {
|
||||
svg.setAttribute('fill', Blockly.FieldNote.BLACK_KEY_COLOR);
|
||||
} else {
|
||||
svg.setAttribute('fill', Blockly.FieldNote.WHITE_KEY_COLOR);
|
||||
}
|
||||
});
|
||||
// Set the highlight on the selected key
|
||||
if (this.keySVGs_[index]) {
|
||||
this.keySVGs_[index].setAttribute('fill', Blockly.FieldNote.KEY_SELECTED_COLOR);
|
||||
// Update the note name text
|
||||
var noteName = Blockly.FieldNote.KEY_INFO[index].name;
|
||||
this.noteNameText_.textContent = noteName + ' (' + Math.floor(noteNum) + ')';
|
||||
// Update the low and high C note names
|
||||
var lowCNum = this.displayedOctave_ * 12;
|
||||
this.lowCText_.textContent = 'C(' + lowCNum + ')';
|
||||
this.highCText_.textContent = 'C(' + (lowCNum + 12) + ')';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that only a valid MIDI note number may be entered.
|
||||
* @param {string} text The user's text.
|
||||
* @return {?string} A string representing a valid note number, or null if invalid.
|
||||
*/
|
||||
Blockly.FieldNote.prototype.classValidator = function(text) {
|
||||
if (text === null) {
|
||||
return null;
|
||||
}
|
||||
var n = parseFloat(text || 0);
|
||||
if (isNaN(n)) {
|
||||
return null;
|
||||
}
|
||||
if (n < 0) {
|
||||
n = 0;
|
||||
}
|
||||
if (n > Blockly.FieldNote.MAX_NOTE) {
|
||||
n = Blockly.FieldNote.MAX_NOTE;
|
||||
}
|
||||
return String(n);
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_note', Blockly.FieldNote);
|
||||
366
scratch-blocks/core/field_number.js
Normal file
366
scratch-blocks/core/field_number.js
Normal file
@@ -0,0 +1,366 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Field for numbers. Includes validator and numpad on touch.
|
||||
* @author tmickel@mit.edu (Tim Mickel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldNumber');
|
||||
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('Blockly.Touch');
|
||||
goog.require('goog.math');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
/**
|
||||
* Class for an editable number field.
|
||||
* In scratch-blocks, the min/max/precision properties are only used
|
||||
* to construct a restrictor on typable characters, and to inform the pop-up
|
||||
* numpad on touch devices.
|
||||
* These properties are included here (i.e. instead of just accepting a
|
||||
* decimalAllowed, negativeAllowed) to maintain API compatibility with Blockly
|
||||
* and Blockly for Android.
|
||||
* @param {(string|number)=} opt_value The initial content of the field. The value
|
||||
* should cast to a number, and if it does not, '0' will be used.
|
||||
* @param {(string|number)=} opt_min Minimum value.
|
||||
* @param {(string|number)=} opt_max Maximum value.
|
||||
* @param {(string|number)=} opt_precision Precision for value.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns the accepted text or null to abort
|
||||
* the change.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldNumber = function(opt_value, opt_min, opt_max, opt_precision,
|
||||
opt_validator) {
|
||||
var numRestrictor = this.getNumRestrictor(opt_min, opt_max, opt_precision);
|
||||
opt_value = (opt_value && !isNaN(opt_value)) ? String(opt_value) : '0';
|
||||
Blockly.FieldNumber.superClass_.constructor.call(
|
||||
this, opt_value, opt_validator, numRestrictor);
|
||||
this.addArgType('number');
|
||||
};
|
||||
goog.inherits(Blockly.FieldNumber, Blockly.FieldTextInput);
|
||||
|
||||
/**
|
||||
* Construct a FieldNumber from a JSON arg object.
|
||||
* @param {!Object} options A JSON object with options (value, min, max, and
|
||||
* precision).
|
||||
* @returns {!Blockly.FieldNumber} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldNumber.fromJson = function(options) {
|
||||
return new Blockly.FieldNumber(options['value'],
|
||||
options['min'], options['max'], options['precision']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fixed width of the num-pad drop-down, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNumber.DROPDOWN_WIDTH = 168;
|
||||
|
||||
/**
|
||||
* Buttons for the num-pad, in order from the top left.
|
||||
* Values are strings of the number or symbol will be added to the field text
|
||||
* when the button is pressed.
|
||||
* @type {Array.<string>}
|
||||
* @const
|
||||
*/
|
||||
// Calculator order
|
||||
Blockly.FieldNumber.NUMPAD_BUTTONS =
|
||||
['7', '8', '9', '4', '5', '6', '1', '2', '3', '.', '0', '-', ' '];
|
||||
|
||||
/**
|
||||
* Src for the delete icon to be shown on the num-pad.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
Blockly.FieldNumber.NUMPAD_DELETE_ICON = 'data:image/svg+xml;utf8,' +
|
||||
'<svg ' +
|
||||
'xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">' +
|
||||
'<path d="M28.89,11.45H16.79a2.86,2.86,0,0,0-2,.84L9.09,1' +
|
||||
'8a2.85,2.85,0,0,0,0,4l5.69,5.69a2.86,2.86,0,0,0,2,.84h12' +
|
||||
'.1a2.86,2.86,0,0,0,2.86-2.86V14.31A2.86,2.86,0,0,0,28.89' +
|
||||
',11.45ZM27.15,22.73a1,1,0,0,1,0,1.41,1,1,0,0,1-.71.3,1,1' +
|
||||
',0,0,1-.71-0.3L23,21.41l-2.73,2.73a1,1,0,0,1-1.41,0,1,1,' +
|
||||
'0,0,1,0-1.41L21.59,20l-2.73-2.73a1,1,0,0,1,0-1.41,1,1,0,' +
|
||||
'0,1,1.41,0L23,18.59l2.73-2.73a1,1,0,1,1,1.42,1.41L24.42,20Z" fill="' +
|
||||
Blockly.Colours.numPadText + '"/></svg>';
|
||||
|
||||
/**
|
||||
* Currently active field during an edit.
|
||||
* Used to give a reference to the num-pad button callbacks.
|
||||
* @type {?FieldNumber}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNumber.activeField_ = null;
|
||||
|
||||
/**
|
||||
* Return an appropriate restrictor, depending on whether this FieldNumber
|
||||
* allows decimal or negative numbers.
|
||||
* @param {number|string|undefined} opt_min Minimum value.
|
||||
* @param {number|string|undefined} opt_max Maximum value.
|
||||
* @param {number|string|undefined} opt_precision Precision for value.
|
||||
* @return {!RegExp} Regular expression for this FieldNumber's restrictor.
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.getNumRestrictor = function(opt_min, opt_max,
|
||||
opt_precision) {
|
||||
this.setConstraints_(opt_min, opt_max, opt_precision);
|
||||
var pattern = "[\\d]"; // Always allow digits.
|
||||
if (this.decimalAllowed_) {
|
||||
pattern += "|[\\.]";
|
||||
}
|
||||
if (this.negativeAllowed_) {
|
||||
pattern += "|[-]";
|
||||
}
|
||||
if (this.exponentialAllowed_) {
|
||||
pattern += "|[eE]";
|
||||
}
|
||||
return new RegExp(pattern);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the constraints for this field.
|
||||
* @param {number=} opt_min Minimum number allowed.
|
||||
* @param {number=} opt_max Maximum number allowed.
|
||||
* @param {number=} opt_precision Step allowed between numbers
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.setConstraints_ = function(opt_min, opt_max,
|
||||
opt_precision) {
|
||||
this.decimalAllowed_ = (typeof opt_precision == 'undefined') ||
|
||||
isNaN(opt_precision) || (opt_precision == 0) ||
|
||||
(Math.floor(opt_precision) != opt_precision);
|
||||
this.negativeAllowed_ = (typeof opt_min == 'undefined') || isNaN(opt_min) ||
|
||||
opt_min < 0;
|
||||
this.exponentialAllowed_ = this.decimalAllowed_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the inline free-text editor on top of the text and the num-pad if
|
||||
* appropriate.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.showEditor_ = function() {
|
||||
Blockly.FieldNumber.activeField_ = this;
|
||||
// Do not focus on mobile devices so we can show the num-pad
|
||||
var showNumPad = this.useTouchInteraction_;
|
||||
Blockly.FieldNumber.superClass_.showEditor_.call(this, false, showNumPad);
|
||||
|
||||
// Show a numeric keypad in the drop-down on touch
|
||||
if (showNumPad) {
|
||||
this.showNumPad_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the number pad.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.showNumPad_ = function() {
|
||||
// If there is an existing drop-down someone else owns, hide it immediately
|
||||
// and clear it.
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
Blockly.DropDownDiv.clearContent();
|
||||
|
||||
var contentDiv = Blockly.DropDownDiv.getContentDiv();
|
||||
|
||||
// Accessibility properties
|
||||
contentDiv.setAttribute('role', 'menu');
|
||||
contentDiv.setAttribute('aria-haspopup', 'true');
|
||||
|
||||
this.addButtons_(contentDiv);
|
||||
|
||||
// Set colour and size of drop-down
|
||||
Blockly.DropDownDiv.setColour(this.sourceBlock_.parentBlock_.getColour(),
|
||||
this.sourceBlock_.getColourTertiary());
|
||||
contentDiv.style.width = Blockly.FieldNumber.DROPDOWN_WIDTH + 'px';
|
||||
|
||||
this.position_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Figure out where to place the drop-down, and move it there.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.position_ = function() {
|
||||
// Calculate positioning for the drop-down
|
||||
// sourceBlock_ is the rendered shadow field input box
|
||||
var scale = this.sourceBlock_.workspace.scale;
|
||||
var bBox = this.sourceBlock_.getHeightWidth();
|
||||
bBox.width *= scale;
|
||||
bBox.height *= scale;
|
||||
var position = this.getAbsoluteXY_();
|
||||
// If we can fit it, render below the shadow block
|
||||
var primaryX = position.x + bBox.width / 2;
|
||||
var primaryY = position.y + bBox.height;
|
||||
// If we can't fit it, render above the entire parent block
|
||||
var secondaryX = primaryX;
|
||||
var secondaryY = position.y;
|
||||
|
||||
Blockly.DropDownDiv.setBoundsElement(
|
||||
this.sourceBlock_.workspace.getParentSvg().parentNode);
|
||||
Blockly.DropDownDiv.show(this, primaryX, primaryY, secondaryX, secondaryY,
|
||||
this.onHide_.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Add number, punctuation, and erase buttons to the numeric keypad's content
|
||||
* div.
|
||||
* @param {Element} contentDiv The div for the numeric keypad.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.addButtons_ = function(contentDiv) {
|
||||
var buttonColour = this.sourceBlock_.parentBlock_.getColour();
|
||||
var buttonBorderColour = this.sourceBlock_.parentBlock_.getColourTertiary();
|
||||
|
||||
// Add numeric keypad buttons
|
||||
var buttons = Blockly.FieldNumber.NUMPAD_BUTTONS;
|
||||
for (var i = 0, buttonText; buttonText = buttons[i]; i++) {
|
||||
var button = document.createElement('button');
|
||||
button.setAttribute('role', 'menuitem');
|
||||
button.setAttribute('class', 'blocklyNumPadButton');
|
||||
button.setAttribute('style',
|
||||
'background:' + buttonColour + ';' +
|
||||
'border: 1px solid ' + buttonBorderColour + ';');
|
||||
button.title = buttonText;
|
||||
button.textContent = buttonText;
|
||||
Blockly.bindEvent_(button, 'mousedown', button,
|
||||
Blockly.FieldNumber.numPadButtonTouch);
|
||||
if (buttonText == '.' && !this.decimalAllowed_) {
|
||||
// Don't show the decimal point for inputs that must be round numbers
|
||||
button.setAttribute('style', 'visibility: hidden');
|
||||
} else if (buttonText == '-' && !this.negativeAllowed_) {
|
||||
continue;
|
||||
} else if (buttonText == ' ' && !this.negativeAllowed_) {
|
||||
continue;
|
||||
} else if (buttonText == ' ' && this.negativeAllowed_) {
|
||||
button.setAttribute('style', 'visibility: hidden');
|
||||
}
|
||||
contentDiv.appendChild(button);
|
||||
}
|
||||
// Add erase button to the end
|
||||
var eraseButton = document.createElement('button');
|
||||
eraseButton.setAttribute('role', 'menuitem');
|
||||
eraseButton.setAttribute('class', 'blocklyNumPadButton');
|
||||
eraseButton.setAttribute('style',
|
||||
'background:' + buttonColour + ';' +
|
||||
'border: 1px solid ' + buttonBorderColour + ';');
|
||||
eraseButton.title = 'Delete';
|
||||
|
||||
var eraseImage = document.createElement('img');
|
||||
eraseImage.src = Blockly.FieldNumber.NUMPAD_DELETE_ICON;
|
||||
eraseButton.appendChild(eraseImage);
|
||||
|
||||
Blockly.bindEvent_(eraseButton, 'mousedown', null,
|
||||
Blockly.FieldNumber.numPadEraseButtonTouch);
|
||||
contentDiv.appendChild(eraseButton);
|
||||
};
|
||||
|
||||
/**
|
||||
* Call for when a num-pad number or punctuation button is touched.
|
||||
* Determine what the user is inputting and update the text field appropriately.
|
||||
* @param {Event} e DOM event triggering the touch.
|
||||
*/
|
||||
Blockly.FieldNumber.numPadButtonTouch = function(e) {
|
||||
// String of the button (e.g., '7')
|
||||
var spliceValue = this.innerHTML;
|
||||
// Old value of the text field
|
||||
var oldValue = Blockly.FieldTextInput.htmlInput_.value;
|
||||
// Determine the selected portion of the text field
|
||||
var selectionStart = Blockly.FieldTextInput.htmlInput_.selectionStart;
|
||||
var selectionEnd = Blockly.FieldTextInput.htmlInput_.selectionEnd;
|
||||
|
||||
// Splice in the new value
|
||||
var newValue = oldValue.slice(0, selectionStart) + spliceValue +
|
||||
oldValue.slice(selectionEnd);
|
||||
|
||||
// Set new value and advance the cursor
|
||||
Blockly.FieldNumber.updateDisplay_(newValue, selectionStart + spliceValue.length);
|
||||
|
||||
// This is just a click.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
|
||||
// Prevent default to not lose input focus
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Call for when the num-pad erase button is touched.
|
||||
* Determine what the user is asking to erase, and erase it.
|
||||
* @param {Event} e DOM event triggering the touch.
|
||||
*/
|
||||
Blockly.FieldNumber.numPadEraseButtonTouch = function(e) {
|
||||
// Old value of the text field
|
||||
var oldValue = Blockly.FieldTextInput.htmlInput_.value;
|
||||
// Determine what is selected to erase (if anything)
|
||||
var selectionStart = Blockly.FieldTextInput.htmlInput_.selectionStart;
|
||||
var selectionEnd = Blockly.FieldTextInput.htmlInput_.selectionEnd;
|
||||
|
||||
// If selection is zero-length, shift start to the left 1 character
|
||||
if (selectionStart == selectionEnd) {
|
||||
selectionStart = Math.max(0, selectionStart - 1);
|
||||
}
|
||||
|
||||
// Cut out selected range
|
||||
var newValue = oldValue.slice(0, selectionStart) +
|
||||
oldValue.slice(selectionEnd);
|
||||
|
||||
Blockly.FieldNumber.updateDisplay_(newValue, selectionStart);
|
||||
|
||||
// This is just a click.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
|
||||
// Prevent default to not lose input focus which resets cursors in Chrome
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the displayed value and resize/scroll the text field as needed.
|
||||
* @param {string} newValue The new text to display.
|
||||
* @param {string} newSelection The new index to put the cursor
|
||||
* @private.
|
||||
*/
|
||||
Blockly.FieldNumber.updateDisplay_ = function(newValue, newSelection) {
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
// Updates the display. The actual setValue occurs when editing ends.
|
||||
htmlInput.value = newValue;
|
||||
// Resize and scroll the text field appropriately
|
||||
Blockly.FieldNumber.superClass_.resizeEditor_.call(
|
||||
Blockly.FieldNumber.activeField_);
|
||||
htmlInput.setSelectionRange(newSelection, newSelection);
|
||||
htmlInput.scrollLeft = htmlInput.scrollWidth;
|
||||
Blockly.FieldNumber.activeField_.validate_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when the drop-down is hidden.
|
||||
*/
|
||||
Blockly.FieldNumber.prototype.onHide_ = function() {
|
||||
// Clear accessibility properties
|
||||
Blockly.DropDownDiv.content_.removeAttribute('role');
|
||||
Blockly.DropDownDiv.content_.removeAttribute('aria-haspopup');
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_number', Blockly.FieldNumber);
|
||||
77
scratch-blocks/core/field_numberdropdown.js
Normal file
77
scratch-blocks/core/field_numberdropdown.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2013 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Combination number + drop-down field
|
||||
* @author tmickel@mit.edu (Tim Mickel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldNumberDropdown');
|
||||
|
||||
goog.require('Blockly.FieldTextDropdown');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a combination number + drop-down field.
|
||||
* @param {number|string} value The initial content of the field.
|
||||
* @param {(!Array.<!Array.<string>>|!Function)} menuGenerator An array of
|
||||
* options for a dropdown list, or a function which generates these options.
|
||||
* @param {number|string|undefined} opt_min Minimum value.
|
||||
* @param {number|string|undefined} opt_max Maximum value.
|
||||
* @param {number|string|undefined} opt_precision Precision for value.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns the accepted text or null to abort
|
||||
* the change.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldNumberDropdown = function(value, menuGenerator, opt_min, opt_max,
|
||||
opt_precision, opt_validator) {
|
||||
this.setConstraints_ = Blockly.FieldNumber.prototype.setConstraints_;
|
||||
|
||||
var numRestrictor = Blockly.FieldNumber.prototype.getNumRestrictor.call(
|
||||
this, opt_min, opt_max, opt_precision
|
||||
);
|
||||
Blockly.FieldNumberDropdown.superClass_.constructor.call(
|
||||
this, value, menuGenerator, opt_validator, numRestrictor
|
||||
);
|
||||
this.addArgType('numberdropdown');
|
||||
};
|
||||
goog.inherits(Blockly.FieldNumberDropdown, Blockly.FieldTextDropdown);
|
||||
|
||||
/**
|
||||
* Construct a FieldTextDropdown from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} element A JSON object with options.
|
||||
* @returns {!Blockly.FieldNumberDropdown} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldNumberDropdown.fromJson = function(element) {
|
||||
return new Blockly.FieldNumberDropdown(
|
||||
element['value'], element['options'],
|
||||
element['min'], element['max'], element['precision']
|
||||
);
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_numberdropdown', Blockly.FieldNumberDropdown);
|
||||
164
scratch-blocks/core/field_textdropdown.js
Normal file
164
scratch-blocks/core/field_textdropdown.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2013 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Combination text + drop-down field
|
||||
* @author tmickel@mit.edu (Tim Mickel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldTextDropdown');
|
||||
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('Blockly.FieldDropdown');
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a combination text + drop-down field.
|
||||
* @param {string} text The initial content of the text field.
|
||||
* @param {(!Array.<!Array.<string>>|!Function)} menuGenerator An array of
|
||||
* options for a dropdown list, or a function which generates these options.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns the accepted text or null to abort
|
||||
* the change.
|
||||
* @param {RegExp=} opt_restrictor An optional regular expression to restrict
|
||||
* typed text to. Text that doesn't match the restrictor will never show
|
||||
* in the text field.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldTextDropdown = function(text, menuGenerator, opt_validator, opt_restrictor) {
|
||||
this.menuGenerator_ = menuGenerator;
|
||||
Blockly.FieldDropdown.prototype.trimOptions_.call(this);
|
||||
Blockly.FieldTextDropdown.superClass_.constructor.call(this, text, opt_validator, opt_restrictor);
|
||||
this.addArgType('textdropdown');
|
||||
};
|
||||
goog.inherits(Blockly.FieldTextDropdown, Blockly.FieldTextInput);
|
||||
|
||||
/**
|
||||
* Construct a FieldTextDropdown from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} element A JSON object with options.
|
||||
* @returns {!Blockly.FieldTextDropdown} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldTextDropdown.fromJson = function(element) {
|
||||
var field =
|
||||
new Blockly.FieldTextDropdown(element['text'], element['options']);
|
||||
if (typeof element['spellcheck'] == 'boolean') {
|
||||
field.setSpellcheck(element['spellcheck']);
|
||||
}
|
||||
return field;
|
||||
};
|
||||
|
||||
/**
|
||||
* Install this text drop-down field on a block.
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Text input + dropdown has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldTextDropdown.superClass_.init.call(this);
|
||||
// Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL)
|
||||
// Positioned on render, after text size is calculated.
|
||||
if (!this.arrow_) {
|
||||
/** @type {Number} */
|
||||
this.arrowSize_ = 12;
|
||||
/** @type {Number} */
|
||||
this.arrowX_ = 0;
|
||||
/** @type {Number} */
|
||||
this.arrowY_ = 11;
|
||||
this.arrow_ = Blockly.utils.createSvgElement('image',
|
||||
{
|
||||
'height': this.arrowSize_ + 'px',
|
||||
'width': this.arrowSize_ + 'px'
|
||||
});
|
||||
this.arrow_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||
'xlink:href', Blockly.mainWorkspace.options.pathToMedia + 'dropdown-arrow-dark.svg');
|
||||
this.arrow_.style.cursor = 'pointer';
|
||||
this.fieldGroup_.appendChild(this.arrow_);
|
||||
this.mouseUpWrapper_ =
|
||||
Blockly.bindEvent_(this.arrow_, 'mouseup', this, this.showDropdown_);
|
||||
}
|
||||
// Prevent the drop-down handler from changing the field colour on open.
|
||||
this.disableColourChange_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the input widget if this input is being deleted.
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.dispose = function() {
|
||||
if (this.mouseUpWrapper_) {
|
||||
Blockly.unbindEvent_(this.mouseUpWrapper_);
|
||||
this.mouseUpWrapper_ = null;
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
}
|
||||
Blockly.FieldTextDropdown.superClass_.dispose.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* If the drop-down isn't open, show the text editor.
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.showEditor_ = function() {
|
||||
if (!this.dropDownOpen_) {
|
||||
Blockly.FieldTextDropdown.superClass_.showEditor_.call(this, null, null,
|
||||
true, function() {
|
||||
// When the drop-down arrow is clicked, hide text editor and show drop-down.
|
||||
Blockly.WidgetDiv.hide();
|
||||
this.showDropdown_();
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a list of the options for this dropdown.
|
||||
* See: Blockly.FieldDropDown.prototype.getOptions_.
|
||||
* @return {!Array.<!Array.<string>>} Array of option tuples:
|
||||
* (human-readable text, language-neutral name).
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.getOptions_ = Blockly.FieldDropdown.prototype.getOptions_;
|
||||
|
||||
/**
|
||||
* Position a drop-down arrow at the appropriate location at render-time.
|
||||
* See: Blockly.FieldDropDown.prototype.positionArrow.
|
||||
* @param {number} x X position the arrow is being rendered at, in px.
|
||||
* @return {number} Amount of space the arrow is taking up, in px.
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.positionArrow = Blockly.FieldDropdown.prototype.positionArrow;
|
||||
|
||||
/**
|
||||
* Create the dropdown menu.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.showDropdown_ = Blockly.FieldDropdown.prototype.showEditor_;
|
||||
|
||||
/**
|
||||
* Callback when the drop-down menu is hidden.
|
||||
*/
|
||||
Blockly.FieldTextDropdown.prototype.onHide = Blockly.FieldDropdown.prototype.onHide;
|
||||
|
||||
Blockly.Field.register('field_textdropdown', Blockly.FieldTextDropdown);
|
||||
675
scratch-blocks/core/field_textinput.js
Normal file
675
scratch-blocks/core/field_textinput.js
Normal file
@@ -0,0 +1,675 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Text input field.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldTextInput');
|
||||
|
||||
goog.require('Blockly.BlockSvg.render');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.Field');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.scratchBlocksUtils');
|
||||
goog.require('Blockly.utils');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
/**
|
||||
* Class for an editable text field.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns either the accepted text, a replacement
|
||||
* text, or null to abort the change.
|
||||
* @param {RegExp=} opt_restrictor An optional regular expression to restrict
|
||||
* typed text to. Text that doesn't match the restrictor will never show
|
||||
* in the text field.
|
||||
* @extends {Blockly.Field}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldTextInput = function(text, opt_validator, opt_restrictor) {
|
||||
Blockly.FieldTextInput.superClass_.constructor.call(this, text,
|
||||
opt_validator);
|
||||
this.setRestrictor(opt_restrictor);
|
||||
this.addArgType('text');
|
||||
};
|
||||
goog.inherits(Blockly.FieldTextInput, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldTextInput from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (text, class, and
|
||||
* spellcheck).
|
||||
* @returns {!Blockly.FieldTextInput} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldTextInput.fromJson = function(options) {
|
||||
var text = Blockly.utils.replaceMessageReferences(options['text']) || '';
|
||||
var field = new Blockly.FieldTextInput(text, options['class']);
|
||||
if (typeof options['spellcheck'] === 'boolean') {
|
||||
field.setSpellcheck(options['spellcheck']);
|
||||
}
|
||||
return field;
|
||||
};
|
||||
|
||||
/**
|
||||
* Length of animations in seconds.
|
||||
*/
|
||||
Blockly.FieldTextInput.ANIMATION_TIME = 0.25;
|
||||
|
||||
/**
|
||||
* Padding to use for text measurement for the field during editing, in px.
|
||||
*/
|
||||
Blockly.FieldTextInput.TEXT_MEASURE_PADDING_MAGIC = 45;
|
||||
|
||||
/**
|
||||
* The HTML input element for the user to type, or null if no FieldTextInput
|
||||
* editor is currently open.
|
||||
* @type {HTMLInputElement}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.htmlInput_ = null;
|
||||
|
||||
/**
|
||||
* Mouse cursor style when over the hotspot that initiates the editor.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.CURSOR = 'text';
|
||||
|
||||
/**
|
||||
* Allow browser to spellcheck this field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.spellcheck_ = true;
|
||||
|
||||
/**
|
||||
* Install this text field on a block.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Field has already been initialized once.
|
||||
return;
|
||||
}
|
||||
|
||||
var notInShadow = !this.sourceBlock_.isShadow();
|
||||
|
||||
if (notInShadow) {
|
||||
this.className_ += ' blocklyEditableLabel';
|
||||
}
|
||||
|
||||
Blockly.FieldTextInput.superClass_.init.call(this);
|
||||
|
||||
// If not in a shadow block, draw a box.
|
||||
if (notInShadow) {
|
||||
this.box_ = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'x': 0,
|
||||
'y': 0,
|
||||
'width': this.size_.width,
|
||||
'height': this.size_.height,
|
||||
'fill': this.sourceBlock_.getColourTertiary()
|
||||
}
|
||||
);
|
||||
this.fieldGroup_.insertBefore(this.box_, this.textElement_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the input widget if this input is being deleted.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.dispose = function() {
|
||||
Blockly.WidgetDiv.hideIfOwner(this);
|
||||
Blockly.FieldTextInput.superClass_.dispose.call(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of this field.
|
||||
* @param {?string} newValue New value.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.setValue = function(newValue) {
|
||||
if (newValue === null) {
|
||||
return; // No change if null.
|
||||
}
|
||||
if (this.sourceBlock_) {
|
||||
var validated = this.callValidator(newValue);
|
||||
// If the new value is invalid, validation returns null.
|
||||
// In this case we still want to display the illegal result.
|
||||
if (validated !== null) {
|
||||
newValue = validated;
|
||||
}
|
||||
}
|
||||
Blockly.Field.prototype.setValue.call(this, newValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the text in this field and fire a change event.
|
||||
* @param {*} newText New text.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.setText = function(newText) {
|
||||
if (newText === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
newText = String(newText);
|
||||
if (newText === this.text_) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, this.text_, newText));
|
||||
}
|
||||
Blockly.Field.prototype.setText.call(this, newText);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this field is spellchecked by the browser.
|
||||
* @param {boolean} check True if checked.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.setSpellcheck = function(check) {
|
||||
this.spellcheck_ = check;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the restrictor regex for this text input.
|
||||
* Text that doesn't match the restrictor will never show in the text field.
|
||||
* @param {?RegExp} restrictor Regular expression to restrict text.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.setRestrictor = function(restrictor) {
|
||||
this.restrictor_ = restrictor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the inline free-text editor on top of the text.
|
||||
* @param {boolean=} opt_quietInput True if editor should be created without
|
||||
* focus. Defaults to false.
|
||||
* @param {boolean=} opt_readOnly True if editor should be created with HTML
|
||||
* input set to read-only, to prevent virtual keyboards.
|
||||
* @param {boolean=} opt_withArrow True to show drop-down arrow in text editor.
|
||||
* @param {Function=} opt_arrowCallback Callback for when drop-down arrow clicked.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.showEditor_ = function(
|
||||
opt_quietInput, opt_readOnly, opt_withArrow, opt_arrowCallback) {
|
||||
this.workspace_ = this.sourceBlock_.workspace;
|
||||
var quietInput = opt_quietInput || false;
|
||||
var readOnly = opt_readOnly || false;
|
||||
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL,
|
||||
this.widgetDispose_(), this.widgetDisposeAnimationFinished_(),
|
||||
Blockly.FieldTextInput.ANIMATION_TIME);
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
// Apply text-input-specific fixed CSS
|
||||
div.className += ' fieldTextInput';
|
||||
// Create the input.
|
||||
var htmlInput =
|
||||
goog.dom.createDom(goog.dom.TagName.INPUT, 'blocklyHtmlInput');
|
||||
htmlInput.setAttribute('spellcheck', this.spellcheck_);
|
||||
if (readOnly) {
|
||||
htmlInput.setAttribute('readonly', 'true');
|
||||
}
|
||||
/** @type {!HTMLInputElement} */
|
||||
Blockly.FieldTextInput.htmlInput_ = htmlInput;
|
||||
div.appendChild(htmlInput);
|
||||
|
||||
if (opt_withArrow) {
|
||||
// Move text in input to account for displayed drop-down arrow.
|
||||
if (this.sourceBlock_.RTL) {
|
||||
htmlInput.style.paddingLeft = (this.arrowSize_ + Blockly.BlockSvg.DROPDOWN_ARROW_PADDING) + 'px';
|
||||
} else {
|
||||
htmlInput.style.paddingRight = (this.arrowSize_ + Blockly.BlockSvg.DROPDOWN_ARROW_PADDING) + 'px';
|
||||
}
|
||||
// Create the arrow.
|
||||
var dropDownArrow =
|
||||
goog.dom.createDom(goog.dom.TagName.IMG, 'blocklyTextDropDownArrow');
|
||||
dropDownArrow.setAttribute('src',
|
||||
Blockly.mainWorkspace.options.pathToMedia + 'dropdown-arrow-dark.svg');
|
||||
dropDownArrow.style.width = this.arrowSize_ + 'px';
|
||||
dropDownArrow.style.height = this.arrowSize_ + 'px';
|
||||
dropDownArrow.style.top = this.arrowY_ + 'px';
|
||||
dropDownArrow.style.cursor = 'pointer';
|
||||
// Magic number for positioning the drop-down arrow on top of the text editor.
|
||||
var dropdownArrowMagic = '11px';
|
||||
if (this.sourceBlock_.RTL) {
|
||||
dropDownArrow.style.left = dropdownArrowMagic;
|
||||
} else {
|
||||
dropDownArrow.style.right = dropdownArrowMagic;
|
||||
}
|
||||
if (opt_arrowCallback) {
|
||||
htmlInput.dropDownArrowMouseWrapper_ = Blockly.bindEvent_(dropDownArrow,
|
||||
'mousedown', this, opt_arrowCallback);
|
||||
}
|
||||
div.appendChild(dropDownArrow);
|
||||
}
|
||||
|
||||
htmlInput.value = htmlInput.defaultValue = this.text_;
|
||||
htmlInput.oldValue_ = null;
|
||||
this.validate_();
|
||||
this.resizeEditor_();
|
||||
if (!quietInput) {
|
||||
htmlInput.focus();
|
||||
htmlInput.select();
|
||||
// For iOS only
|
||||
htmlInput.setSelectionRange(0, 99999);
|
||||
}
|
||||
|
||||
this.bindEvents_(htmlInput, quietInput || readOnly);
|
||||
|
||||
// Add animation transition properties
|
||||
var transitionProperties = 'box-shadow ' + Blockly.FieldTextInput.ANIMATION_TIME + 's';
|
||||
if (Blockly.BlockSvg.FIELD_TEXTINPUT_ANIMATE_POSITIONING) {
|
||||
div.style.transition += ',padding ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
|
||||
'width ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
|
||||
'height ' + Blockly.FieldTextInput.ANIMATION_TIME + 's,' +
|
||||
'margin-left ' + Blockly.FieldTextInput.ANIMATION_TIME + 's';
|
||||
}
|
||||
div.style.transition = transitionProperties;
|
||||
htmlInput.style.transition = 'font-size ' + Blockly.FieldTextInput.ANIMATION_TIME + 's';
|
||||
// The animated properties themselves
|
||||
htmlInput.style.fontSize = Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_FINAL + 'pt';
|
||||
div.style.boxShadow = '0px 0px 0px 4px ' + Blockly.Colours.fieldShadow;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind handlers for user input on this field and size changes on the workspace.
|
||||
* @param {!HTMLInputElement} htmlInput The htmlInput created in showEditor, to
|
||||
* which event handlers will be bound.
|
||||
* @param {boolean} bindGlobalKeypress Whether to bind a keypress listener to enable
|
||||
* keyboard editing without focusing the field.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.bindEvents_ = function(
|
||||
htmlInput, bindGlobalKeypress) {
|
||||
// Bind to keydown -- trap Enter without IME and Esc to hide.
|
||||
htmlInput.onKeyDownWrapper_ =
|
||||
Blockly.bindEventWithChecks_(htmlInput, 'keydown', this,
|
||||
this.onHtmlInputKeyDown_);
|
||||
// Bind to keyup -- trap Enter; resize after every keystroke.
|
||||
htmlInput.onKeyUpWrapper_ =
|
||||
Blockly.bindEventWithChecks_(htmlInput, 'keyup', this,
|
||||
this.onHtmlInputChange_);
|
||||
// Bind to keyPress -- repeatedly resize when holding down a key.
|
||||
htmlInput.onKeyPressWrapper_ =
|
||||
Blockly.bindEventWithChecks_(htmlInput, 'keypress', this,
|
||||
this.onHtmlInputChange_);
|
||||
// For modern browsers (IE 9+, Chrome, Firefox, etc.) that support the
|
||||
// DOM input event, also trigger onHtmlInputChange_ then. The input event
|
||||
// is triggered on keypress but after the value of the text input
|
||||
// has updated, allowing us to resize the block at that time.
|
||||
htmlInput.onInputWrapper_ =
|
||||
Blockly.bindEvent_(htmlInput, 'input', this, this.onHtmlInputChange_);
|
||||
htmlInput.onWorkspaceChangeWrapper_ = this.resizeEditor_.bind(this);
|
||||
this.workspace_.addChangeListener(htmlInput.onWorkspaceChangeWrapper_);
|
||||
|
||||
if (bindGlobalKeypress) {
|
||||
htmlInput.onDocumentKeyDownWrapper_ =
|
||||
Blockly.bindEventWithChecks_(document, 'keydown', this,
|
||||
this.onDocumentKeyDown_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind handlers for user input and workspace size changes.
|
||||
* @param {!HTMLInputElement} htmlInput The html for this text input.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.unbindEvents_ = function(htmlInput) {
|
||||
Blockly.unbindEvent_(htmlInput.onKeyDownWrapper_);
|
||||
Blockly.unbindEvent_(htmlInput.onKeyUpWrapper_);
|
||||
Blockly.unbindEvent_(htmlInput.onKeyPressWrapper_);
|
||||
Blockly.unbindEvent_(htmlInput.onInputWrapper_);
|
||||
this.workspace_.removeChangeListener(
|
||||
htmlInput.onWorkspaceChangeWrapper_);
|
||||
|
||||
// Remove document handler only if it was added (e.g. in quiet mode)
|
||||
if (htmlInput.onDocumentKeyDownWrapper_) {
|
||||
Blockly.unbindEvent_(htmlInput.onDocumentKeyDownWrapper_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle key down to the editor.
|
||||
* @param {!Event} e Keyboard event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.onHtmlInputKeyDown_ = function(e) {
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
var tabKey = 9, enterKey = 13, escKey = 27;
|
||||
if (e.keyCode == enterKey) {
|
||||
Blockly.WidgetDiv.hide();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
} else if (e.keyCode == escKey) {
|
||||
htmlInput.value = htmlInput.defaultValue;
|
||||
Blockly.WidgetDiv.hide();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
} else if (e.keyCode == tabKey) {
|
||||
Blockly.WidgetDiv.hide();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
this.sourceBlock_.tab(this, !e.shiftKey);
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.FieldTextInput.prototype.onDocumentKeyDown_ = function(e) {
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
var targetMatches = e.target === htmlInput;
|
||||
var targetIsInput = e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA';
|
||||
if (targetMatches || !targetIsInput) { // Ignore keys into other inputs
|
||||
htmlInput.removeAttribute('readonly');
|
||||
htmlInput.value = ''; // Reset the input, new value is picked up by input keypress
|
||||
htmlInput.focus();
|
||||
Blockly.unbindEvent_(htmlInput.onDocumentKeyDownWrapper_);
|
||||
htmlInput.onDocumentKeyDownWrapper_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Key codes that are whitelisted from the restrictor.
|
||||
* These are only needed and used on Gecko (Firefox).
|
||||
* See: https://github.com/LLK/scratch-blocks/issues/503.
|
||||
*/
|
||||
Blockly.FieldTextInput.GECKO_KEYCODE_WHITELIST = [
|
||||
97, // Select all, META-A.
|
||||
99, // Copy, META-C.
|
||||
118, // Paste, META-V.
|
||||
120 // Cut, META-X.
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle a change to the editor.
|
||||
* @param {!Event} e Keyboard event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) {
|
||||
// Check if the key matches the restrictor.
|
||||
if (e.type === 'keypress' && this.restrictor_) {
|
||||
var keyCode;
|
||||
var isWhitelisted = false;
|
||||
if (goog.userAgent.GECKO) {
|
||||
// e.keyCode is not available in Gecko.
|
||||
keyCode = e.charCode;
|
||||
// Gecko reports control characters (e.g., left, right, copy, paste)
|
||||
// in the key event - whitelist these from being restricted.
|
||||
// < 32 and 127 (delete) are control characters.
|
||||
// See: http://www.theasciicode.com.ar/ascii-control-characters/delete-ascii-code-127.html
|
||||
if (keyCode < 32 || keyCode == 127) {
|
||||
isWhitelisted = true;
|
||||
} else if (e.metaKey || e.ctrlKey) {
|
||||
// For combos (ctrl-v, ctrl-c, etc.), Gecko reports the ASCII letter
|
||||
// and the metaKey/ctrlKey flags.
|
||||
isWhitelisted = Blockly.FieldTextInput.GECKO_KEYCODE_WHITELIST.indexOf(keyCode) > -1;
|
||||
}
|
||||
} else {
|
||||
keyCode = e.keyCode;
|
||||
}
|
||||
var char = String.fromCharCode(keyCode);
|
||||
if (!isWhitelisted && !this.restrictor_.test(char) && e.preventDefault) {
|
||||
// Failed to pass restrictor.
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
// Update source block.
|
||||
var text = htmlInput.value;
|
||||
if (text !== htmlInput.oldValue_) {
|
||||
htmlInput.oldValue_ = text;
|
||||
this.setText(text);
|
||||
this.validate_();
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
// Cursor key. Render the source block to show the caret moving.
|
||||
// Chrome only (version 26, OS X).
|
||||
this.sourceBlock_.render();
|
||||
}
|
||||
this.resizeEditor_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if the contents of the editor validates.
|
||||
* Style the editor accordingly.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.validate_ = function() {
|
||||
var valid = true;
|
||||
goog.asserts.assertObject(Blockly.FieldTextInput.htmlInput_);
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
if (this.sourceBlock_) {
|
||||
valid = this.callValidator(htmlInput.value);
|
||||
}
|
||||
if (valid === null) {
|
||||
Blockly.utils.addClass(htmlInput, 'blocklyInvalidInput');
|
||||
} else {
|
||||
Blockly.utils.removeClass(htmlInput, 'blocklyInvalidInput');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize the editor and the underlying block to fit the text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.resizeEditor_ = function() {
|
||||
var scale = this.sourceBlock_.workspace.scale;
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
|
||||
var initialWidth;
|
||||
if (this.sourceBlock_.isShadow()) {
|
||||
initialWidth = this.sourceBlock_.getHeightWidth().width * scale;
|
||||
} else {
|
||||
initialWidth = this.size_.width * scale;
|
||||
}
|
||||
|
||||
var width;
|
||||
if (Blockly.BlockSvg.FIELD_TEXTINPUT_EXPAND_PAST_TRUNCATION) {
|
||||
// Resize the box based on the measured width of the text, pre-truncation
|
||||
var textWidth = Blockly.scratchBlocksUtils.measureText(
|
||||
Blockly.FieldTextInput.htmlInput_.style.fontSize,
|
||||
Blockly.FieldTextInput.htmlInput_.style.fontFamily,
|
||||
Blockly.FieldTextInput.htmlInput_.style.fontWeight,
|
||||
Blockly.FieldTextInput.htmlInput_.value
|
||||
);
|
||||
// Size drawn in the canvas needs padding and scaling
|
||||
textWidth += Blockly.FieldTextInput.TEXT_MEASURE_PADDING_MAGIC;
|
||||
textWidth *= scale;
|
||||
width = textWidth;
|
||||
} else {
|
||||
// Set width to (truncated) block size.
|
||||
width = initialWidth;
|
||||
}
|
||||
// The width must be at least FIELD_WIDTH and at most FIELD_WIDTH_MAX_EDIT
|
||||
width = Math.max(width, Blockly.BlockSvg.FIELD_WIDTH_MIN_EDIT * scale);
|
||||
width = Math.min(width, Blockly.BlockSvg.FIELD_WIDTH_MAX_EDIT * scale);
|
||||
// Add 1px to width and height to account for border (pre-scale)
|
||||
div.style.width = (width / scale + 1) + 'px';
|
||||
div.style.height = (Blockly.BlockSvg.FIELD_HEIGHT_MAX_EDIT + 1) + 'px';
|
||||
div.style.transform = 'scale(' + scale + ')';
|
||||
|
||||
// Use margin-left to animate repositioning of the box (value is unscaled).
|
||||
// This is the difference between the default position and the positioning
|
||||
// after growing the box.
|
||||
div.style.marginLeft = -0.5 * (width - initialWidth) + 'px';
|
||||
|
||||
// Add 0.5px to account for slight difference between SVG and CSS border
|
||||
var borderRadius = this.getBorderRadius() + 0.5;
|
||||
div.style.borderRadius = borderRadius + 'px';
|
||||
Blockly.FieldTextInput.htmlInput_.style.borderRadius = borderRadius + 'px';
|
||||
// Pull stroke colour from the existing shadow block
|
||||
var strokeColour = this.sourceBlock_.getColourTertiary();
|
||||
div.style.borderColor = strokeColour;
|
||||
|
||||
var xy = this.getAbsoluteXY_();
|
||||
// Account for border width, post-scale
|
||||
xy.x -= scale / 2;
|
||||
xy.y -= scale / 2;
|
||||
// In RTL mode block fields and LTR input fields the left edge moves,
|
||||
// whereas the right edge is fixed. Reposition the editor.
|
||||
if (this.sourceBlock_.RTL) {
|
||||
xy.x += width;
|
||||
xy.x -= div.offsetWidth * scale;
|
||||
xy.x += 1 * scale;
|
||||
}
|
||||
// Shift by a few pixels to line up exactly.
|
||||
xy.y += 1 * scale;
|
||||
if (goog.userAgent.GECKO && Blockly.WidgetDiv.DIV.style.top) {
|
||||
// Firefox mis-reports the location of the border by a pixel
|
||||
// once the WidgetDiv is moved into position.
|
||||
xy.x += 2 * scale;
|
||||
xy.y += 1 * scale;
|
||||
}
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
xy.y -= 1 * scale;
|
||||
}
|
||||
// Finally, set the actual style
|
||||
div.style.left = xy.x + 'px';
|
||||
div.style.top = xy.y + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
* Border radius for drawing this field, called when rendering the owning shadow block.
|
||||
* @return {Number} Border radius in px.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.getBorderRadius = function() {
|
||||
if (this.sourceBlock_.getOutputShape() == Blockly.OUTPUT_SHAPE_ROUND) {
|
||||
return Blockly.BlockSvg.NUMBER_FIELD_CORNER_RADIUS;
|
||||
}
|
||||
return Blockly.BlockSvg.TEXT_FIELD_CORNER_RADIUS;
|
||||
};
|
||||
|
||||
/**
|
||||
* Close the editor, save the results, and start animating the disposal of elements.
|
||||
* @return {!Function} Closure to call on destruction of the WidgetDiv.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
|
||||
var thisField = this;
|
||||
return function() {
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
// Save the edit (if it validates).
|
||||
thisField.maybeSaveEdit_();
|
||||
|
||||
thisField.unbindEvents_(htmlInput);
|
||||
if (htmlInput.dropDownArrowMouseWrapper_) {
|
||||
Blockly.unbindEvent_(htmlInput.dropDownArrowMouseWrapper_);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
|
||||
// Animation of disposal
|
||||
htmlInput.style.fontSize = Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_INITIAL + 'pt';
|
||||
div.style.boxShadow = '';
|
||||
// Resize to actual size of final source block.
|
||||
if (thisField.sourceBlock_) {
|
||||
if (thisField.sourceBlock_.isShadow()) {
|
||||
var size = thisField.sourceBlock_.getHeightWidth();
|
||||
div.style.width = (size.width + 1) + 'px';
|
||||
div.style.height = (size.height + 1) + 'px';
|
||||
} else {
|
||||
div.style.width = (thisField.size_.width + 1) + 'px';
|
||||
div.style.height = (Blockly.BlockSvg.FIELD_HEIGHT_MAX_EDIT + 1) + 'px';
|
||||
}
|
||||
}
|
||||
div.style.marginLeft = 0;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Final disposal of the text field's elements and properties.
|
||||
* @return {!Function} Closure to call on finish animation of the WidgetDiv.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.widgetDisposeAnimationFinished_ = function() {
|
||||
return function() {
|
||||
// Delete style properties.
|
||||
var style = Blockly.WidgetDiv.DIV.style;
|
||||
style.width = 'auto';
|
||||
style.height = 'auto';
|
||||
style.fontSize = '';
|
||||
// Reset class
|
||||
Blockly.WidgetDiv.DIV.className = 'blocklyWidgetDiv';
|
||||
// Remove all styles
|
||||
Blockly.WidgetDiv.DIV.removeAttribute('style');
|
||||
Blockly.FieldTextInput.htmlInput_.style.transition = '';
|
||||
Blockly.FieldTextInput.htmlInput_ = null;
|
||||
};
|
||||
};
|
||||
|
||||
Blockly.FieldTextInput.prototype.maybeSaveEdit_ = function() {
|
||||
var htmlInput = Blockly.FieldTextInput.htmlInput_;
|
||||
// Save the edit (if it validates).
|
||||
var text = htmlInput.value;
|
||||
if (this.sourceBlock_) {
|
||||
var text1 = this.callValidator(text);
|
||||
if (text1 === null) {
|
||||
// Invalid edit.
|
||||
text = htmlInput.defaultValue;
|
||||
} else {
|
||||
// Validation function has changed the text.
|
||||
text = text1;
|
||||
if (this.onFinishEditing_) {
|
||||
this.onFinishEditing_(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setText(text);
|
||||
this.sourceBlock_.rendered && this.sourceBlock_.render();
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that only a number may be entered.
|
||||
* @param {string} text The user's text.
|
||||
* @return {?string} A string representing a valid number, or null if invalid.
|
||||
*/
|
||||
Blockly.FieldTextInput.numberValidator = function(text) {
|
||||
console.warn('Blockly.FieldTextInput.numberValidator is deprecated. ' +
|
||||
'Use Blockly.FieldNumber instead.');
|
||||
if (text === null) {
|
||||
return null;
|
||||
}
|
||||
text = String(text);
|
||||
// TODO: Handle cases like 'ten', '1.203,14', etc.
|
||||
// 'O' is sometimes mistaken for '0' by inexperienced users.
|
||||
text = text.replace(/O/ig, '0');
|
||||
// Strip out thousands separators.
|
||||
text = text.replace(/,/g, '');
|
||||
var n = parseFloat(text || 0);
|
||||
return isNaN(n) ? null : String(n);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ensure that only a nonnegative integer may be entered.
|
||||
* @param {string} text The user's text.
|
||||
* @return {?string} A string representing a valid int, or null if invalid.
|
||||
*/
|
||||
Blockly.FieldTextInput.nonnegativeIntegerValidator = function(text) {
|
||||
var n = Blockly.FieldTextInput.numberValidator(text);
|
||||
if (n) {
|
||||
n = String(Math.max(0, Math.floor(n)));
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_input', Blockly.FieldTextInput);
|
||||
105
scratch-blocks/core/field_textinput_removable.js
Normal file
105
scratch-blocks/core/field_textinput_removable.js
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Text input field with floating "remove" button.
|
||||
* @author pkaplan@media.mit.edu (Paul Kaplan)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldTextInputRemovable');
|
||||
|
||||
goog.require('Blockly.BlockSvg.render');
|
||||
goog.require('Blockly.Colours');
|
||||
goog.require('Blockly.FieldTextInput');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
|
||||
/**
|
||||
* Class for an editable text field displaying a deletion icon when selected.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {Function=} opt_validator An optional function that is called
|
||||
* to validate any constraints on what the user entered. Takes the new
|
||||
* text as an argument and returns either the accepted text, a replacement
|
||||
* text, or null to abort the change.
|
||||
* @param {RegExp=} opt_restrictor An optional regular expression to restrict
|
||||
* typed text to. Text that doesn't match the restrictor will never show
|
||||
* in the text field.
|
||||
* @extends {Blockly.FieldTextInput}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldTextInputRemovable = function(text, opt_validator, opt_restrictor) {
|
||||
Blockly.FieldTextInputRemovable.superClass_.constructor.call(this, text,
|
||||
opt_validator, opt_restrictor);
|
||||
};
|
||||
goog.inherits(Blockly.FieldTextInputRemovable, Blockly.FieldTextInput);
|
||||
|
||||
/**
|
||||
* Show the inline free-text editor on top of the text with the remove button.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInputRemovable.prototype.showEditor_ = function() {
|
||||
Blockly.FieldTextInputRemovable.superClass_.showEditor_.call(this);
|
||||
|
||||
var div = Blockly.WidgetDiv.DIV;
|
||||
div.className += ' removableTextInput';
|
||||
var removeButton =
|
||||
goog.dom.createDom(goog.dom.TagName.IMG, 'blocklyTextRemoveIcon');
|
||||
removeButton.setAttribute('src',
|
||||
Blockly.mainWorkspace.options.pathToMedia + 'icons/remove.svg');
|
||||
this.removeButtonMouseWrapper_ = Blockly.bindEvent_(removeButton,
|
||||
'mousedown', this, this.removeCallback_);
|
||||
div.appendChild(removeButton);
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to call when remove button is called. Checks for removeFieldCallback
|
||||
* on sourceBlock and calls it if possible.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldTextInputRemovable.prototype.removeCallback_ = function() {
|
||||
if (this.sourceBlock_ && this.sourceBlock_.removeFieldCallback) {
|
||||
this.sourceBlock_.removeFieldCallback(this);
|
||||
} else {
|
||||
console.warn('Expected a source block with removeFieldCallback');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to construct a FieldTextInputRemovable from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (text, class, and
|
||||
* spellcheck).
|
||||
* @returns {!Blockly.FieldTextInputRemovable} The new text input.
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldTextInputRemovable.fromJson = function(options) {
|
||||
var text = Blockly.utils.replaceMessageReferences(options['text']);
|
||||
var field = new Blockly.FieldTextInputRemovable(text, options['class']);
|
||||
if (typeof options['spellcheck'] == 'boolean') {
|
||||
field.setSpellcheck(options['spellcheck']);
|
||||
}
|
||||
return field;
|
||||
};
|
||||
|
||||
Blockly.Field.register(
|
||||
'field_input_removable', Blockly.FieldTextInputRemovable);
|
||||
385
scratch-blocks/core/field_variable.js
Normal file
385
scratch-blocks/core/field_variable.js
Normal file
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Variable input field.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldVariable');
|
||||
|
||||
goog.require('Blockly.FieldDropdown');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.VariableModel');
|
||||
goog.require('Blockly.Variables');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable's dropdown field.
|
||||
* @param {?string} varname The default name for the variable. If null,
|
||||
* a unique variable name will be generated.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* option is selected. Its sole argument is the new option value.
|
||||
* @param {Array.<string>} opt_variableTypes A list of the types of variables to
|
||||
* include in the dropdown.
|
||||
* @extends {Blockly.FieldDropdown}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
|
||||
// The FieldDropdown constructor would call setValue, which might create a
|
||||
// spurious variable. Just do the relevant parts of the constructor.
|
||||
this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate;
|
||||
this.size_ = new goog.math.Size(Blockly.BlockSvg.FIELD_WIDTH,
|
||||
Blockly.BlockSvg.FIELD_HEIGHT);
|
||||
this.setValidator(opt_validator);
|
||||
// TODO (blockly #1499): Add opt_default_type to match default value.
|
||||
// If not set, ''.
|
||||
this.defaultVariableName = (varname || '');
|
||||
var hasSingleVarType = opt_variableTypes && (opt_variableTypes.length == 1);
|
||||
this.defaultType_ = hasSingleVarType ? opt_variableTypes[0] : '';
|
||||
this.variableTypes = opt_variableTypes;
|
||||
this.addArgType('variable');
|
||||
|
||||
this.value_ = null;
|
||||
};
|
||||
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);
|
||||
|
||||
/**
|
||||
* Construct a FieldVariable from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (variable,
|
||||
* variableTypes, and defaultType).
|
||||
* @returns {!Blockly.FieldVariable} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldVariable.fromJson = function(options) {
|
||||
var varname = Blockly.utils.replaceMessageReferences(options['variable']);
|
||||
var variableTypes = options['variableTypes'];
|
||||
return new Blockly.FieldVariable(varname, null, variableTypes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize everything needed to render this field. This includes making sure
|
||||
* that the field's value is valid.
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Dropdown has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldVariable.superClass_.init.call(this);
|
||||
|
||||
// TODO (blockly #1010): Change from init/initModel to initView/initModel
|
||||
this.initModel();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the model for this field if it has not already been initialized.
|
||||
* If the value has not been set to a variable by the first render, we make up a
|
||||
* variable rather than let the value be invalid.
|
||||
* @package
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.initModel = function() {
|
||||
if (this.variable_) {
|
||||
return; // Initialization already happened.
|
||||
}
|
||||
this.workspace_ = this.sourceBlock_.workspace;
|
||||
// Initialize this field if it's in a broadcast block in the flyout
|
||||
var variable = this.initFlyoutBroadcast_(this.workspace_);
|
||||
if (!variable) {
|
||||
var variable = Blockly.Variables.getOrCreateVariablePackage(
|
||||
this.workspace_, null, this.defaultVariableName, this.defaultType_);
|
||||
}
|
||||
// Don't fire a change event for this setValue. It would have null as the
|
||||
// old value, which is not valid.
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
this.setValue(variable.getId());
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize broadcast blocks in the flyout.
|
||||
* Implicit deletion of broadcast messages from the scratch vm may cause
|
||||
* broadcast blocks in the flyout to change which variable they display as the
|
||||
* selected option when the workspace is refreshed.
|
||||
* Re-sort the broadcast messages by name, and set the field value to the id
|
||||
* of the variable that comes first in sorted order.
|
||||
* @param {!Blockly.Workspace} workspace The flyout workspace containing the
|
||||
* broadcast block.
|
||||
* @return {string} The variable of type 'broadcast_msg' that comes
|
||||
* first in sorted order.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.initFlyoutBroadcast_ = function(workspace) {
|
||||
// Using shorter name for this constant
|
||||
var broadcastMsgType = Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE;
|
||||
var broadcastVars = workspace.getVariablesOfType(broadcastMsgType);
|
||||
if(workspace.isFlyout && this.defaultType_ == broadcastMsgType &&
|
||||
broadcastVars.length != 0) {
|
||||
broadcastVars.sort(Blockly.VariableModel.compareByName);
|
||||
return broadcastVars[0];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this field.
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldVariable.dispose = function() {
|
||||
Blockly.FieldVariable.superClass_.dispose.call(this);
|
||||
this.workspace_ = null;
|
||||
this.variableMap_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach this field to a block.
|
||||
* @param {!Blockly.Block} block The block containing this field.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.setSourceBlock = function(block) {
|
||||
goog.asserts.assert(!block.isShadow(),
|
||||
'Variable fields are not allowed to exist on shadow blocks.');
|
||||
Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the variable's ID.
|
||||
* @return {string} Current variable's ID.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getValue = function() {
|
||||
return this.variable_ ? this.variable_.getId() : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field, which is the selected variable's name.
|
||||
* @return {string} The selected variable's name, or the empty string if no
|
||||
* variable is selected.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getText = function() {
|
||||
return this.variable_ ? this.variable_.name : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the variable model for the selected variable.
|
||||
* Not guaranteed to be in the variable map on the workspace (e.g. if accessed
|
||||
* after the variable has been deleted).
|
||||
* @return {?Blockly.VariableModel} the selected variable, or null if none was
|
||||
* selected.
|
||||
* @package
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getVariable = function() {
|
||||
return this.variable_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the variable ID.
|
||||
* @param {string} id New variable ID, which must reference an existing
|
||||
* variable.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.setValue = function(id) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
var variable = Blockly.Variables.getVariable(workspace, id);
|
||||
|
||||
if (!variable) {
|
||||
throw new Error('Variable id doesn\'t point to a real variable! ID was ' +
|
||||
id);
|
||||
}
|
||||
// Type checks!
|
||||
var type = variable.type;
|
||||
if (!this.typeIsAllowed_(type)) {
|
||||
throw new Error('Variable type doesn\'t match this field! Type was ' +
|
||||
type);
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
var oldValue = this.variable_ ? this.variable_.getId() : null;
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, oldValue, id));
|
||||
}
|
||||
this.variable_ = variable;
|
||||
this.value_ = id;
|
||||
this.setText(variable.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the given variable type is allowed on this field.
|
||||
* @param {string} type The type to check.
|
||||
* @return {boolean} True if the type is in the list of allowed types.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) {
|
||||
var typeList = this.getVariableTypes_();
|
||||
if (!typeList) {
|
||||
return true; // If it's null, all types are valid.
|
||||
}
|
||||
for (var i = 0; i < typeList.length; i++) {
|
||||
if (type == typeList[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a list of variable types to include in the dropdown.
|
||||
* @return {!Array.<string>} Array of variable types.
|
||||
* @throws {Error} if variableTypes is an empty array.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
|
||||
// TODO (#1513): Try to avoid calling this every time the field is edited.
|
||||
var variableTypes = this.variableTypes;
|
||||
if (variableTypes === null) {
|
||||
// If variableTypes is null, return all variable types.
|
||||
if (this.sourceBlock_) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
return workspace.getVariableTypes();
|
||||
}
|
||||
}
|
||||
variableTypes = variableTypes || [''];
|
||||
if (variableTypes.length == 0) {
|
||||
// Throw an error if variableTypes is an empty list.
|
||||
var name = this.getText();
|
||||
throw new Error('\'variableTypes\' of field variable ' +
|
||||
name + ' was an empty list');
|
||||
}
|
||||
return variableTypes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a sorted list of variable names for variable dropdown menus.
|
||||
* Include a special option at the end for creating a new variable name.
|
||||
* @return {!Array.<string>} Array of variable names.
|
||||
* @this {Blockly.FieldVariable}
|
||||
*/
|
||||
Blockly.FieldVariable.dropdownCreate = function() {
|
||||
if (!this.variable_) {
|
||||
throw new Error('Tried to call dropdownCreate on a variable field with no' +
|
||||
' variable selected.');
|
||||
}
|
||||
var variableModelList = [];
|
||||
var name = this.getText();
|
||||
var workspace = null;
|
||||
if (this.sourceBlock_) {
|
||||
workspace = this.sourceBlock_.workspace;
|
||||
}
|
||||
if (workspace) {
|
||||
var variableTypes = this.getVariableTypes_();
|
||||
var variableModelList = [];
|
||||
// Get a copy of the list, so that adding rename and new variable options
|
||||
// doesn't modify the workspace's list.
|
||||
for (var i = 0; i < variableTypes.length; i++) {
|
||||
var variableType = variableTypes[i];
|
||||
var variables = workspace.getVariablesOfType(variableType);
|
||||
variableModelList = variableModelList.concat(variables);
|
||||
|
||||
var potentialVarMap = workspace.getPotentialVariableMap();
|
||||
if (potentialVarMap) {
|
||||
var potentialVars = potentialVarMap.getVariablesOfType(variableType);
|
||||
variableModelList = variableModelList.concat(potentialVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
variableModelList.sort(Blockly.VariableModel.compareByName);
|
||||
|
||||
var options = [];
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
// Set the uuid as the internal representation of the variable.
|
||||
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
|
||||
}
|
||||
if (this.defaultType_ == Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE) {
|
||||
options.unshift(
|
||||
[Blockly.Msg.NEW_BROADCAST_MESSAGE, Blockly.NEW_BROADCAST_MESSAGE_ID]);
|
||||
} else {
|
||||
// Scalar variables and lists have the same backing action, but the option
|
||||
// text is different.
|
||||
if (this.defaultType_ == Blockly.LIST_VARIABLE_TYPE) {
|
||||
var renameText = Blockly.Msg.RENAME_LIST;
|
||||
var deleteText = Blockly.Msg.DELETE_LIST;
|
||||
} else {
|
||||
var renameText = Blockly.Msg.RENAME_VARIABLE;
|
||||
var deleteText = Blockly.Msg.DELETE_VARIABLE;
|
||||
}
|
||||
options.push([renameText, Blockly.RENAME_VARIABLE_ID]);
|
||||
if (deleteText) {
|
||||
options.push(
|
||||
[
|
||||
deleteText.replace('%1', name),
|
||||
Blockly.DELETE_VARIABLE_ID
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the selection of an item in the variable dropdown menu.
|
||||
* Special case the 'Rename variable...', 'Delete variable...',
|
||||
* and 'New message...' options.
|
||||
* In the rename case, prompt the user for a new name.
|
||||
* @param {!goog.ui.Menu} menu The Menu component clicked.
|
||||
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
|
||||
var id = menuItem.getValue();
|
||||
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
if (id == Blockly.RENAME_VARIABLE_ID) {
|
||||
// Rename variable.
|
||||
Blockly.Variables.renameVariable(workspace, this.variable_);
|
||||
return;
|
||||
} else if (id == Blockly.DELETE_VARIABLE_ID) {
|
||||
// Delete variable.
|
||||
workspace.deleteVariableById(this.variable_.getId());
|
||||
return;
|
||||
} else if (id == Blockly.NEW_BROADCAST_MESSAGE_ID) {
|
||||
var thisField = this;
|
||||
var updateField = function(varId) {
|
||||
if (varId) {
|
||||
thisField.setValue(varId);
|
||||
}
|
||||
};
|
||||
Blockly.Variables.createVariable(workspace, updateField,
|
||||
Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO (blockly #1529): Call any validation function, and allow it to override.
|
||||
}
|
||||
this.setValue(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Overrides referencesVariables(), indicating this field refers to a variable.
|
||||
* @return {boolean} True.
|
||||
* @package
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.referencesVariables = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_variable', Blockly.FieldVariable);
|
||||
185
scratch-blocks/core/field_variable_getter.js
Normal file
185
scratch-blocks/core/field_variable_getter.js
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2017 Google Inc.
|
||||
* https://developers.google.com/blockly/
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Variable getter field. Appears as a label but has a variable
|
||||
* picker in the right-click menu.
|
||||
* @author fenichel@google.com (Rachel Fenichel)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldVariableGetter');
|
||||
|
||||
goog.require('Blockly.Field');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable getter field.
|
||||
* @param {string} text The initial content of the field.
|
||||
* @param {string} name Optional CSS class for the field's text.
|
||||
* @param {string} opt_varType The type of variable this field is associated with.
|
||||
* @extends {Blockly.FieldLabel}
|
||||
* @constructor
|
||||
*
|
||||
*/
|
||||
Blockly.FieldVariableGetter = function(text, name, opt_varType) {
|
||||
this.size_ = new goog.math.Size(Blockly.BlockSvg.FIELD_WIDTH,
|
||||
Blockly.BlockSvg.FIELD_HEIGHT);
|
||||
this.text_ = text;
|
||||
|
||||
/**
|
||||
* Maximum characters of text to display before adding an ellipsis.
|
||||
* Same for strings and numbers.
|
||||
* @type {number}
|
||||
*/
|
||||
this.maxDisplayLength = Blockly.BlockSvg.MAX_DISPLAY_LENGTH;
|
||||
|
||||
this.name_ = name;
|
||||
this.variableType_ = opt_varType ? opt_varType : '';
|
||||
};
|
||||
goog.inherits(Blockly.FieldVariableGetter, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Construct a FieldVariableGetter from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (variable,
|
||||
* variableTypes, and defaultType).
|
||||
* @returns {!Blockly.FieldVariableGetter} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldVariableGetter.fromJson = function(options) {
|
||||
var varname = Blockly.utils.replaceMessageReferences(options['text']);
|
||||
return new Blockly.FieldVariableGetter(varname, options['name'],
|
||||
options['class'], options['variableType']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Editable fields usually show some sort of UI for the user to change them.
|
||||
* This field should be serialized, but only edited programmatically.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.EDITABLE = false;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the XML renderer, non-serializable fields
|
||||
* are not. This field should be serialized, but only edited programmatically.
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.SERIALIZABLE = true;
|
||||
|
||||
/**
|
||||
* Install this field on a block.
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Field has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldVariableGetter.superClass_.init.call(this);
|
||||
if (this.variable_) {
|
||||
return; // Initialization already happened.
|
||||
}
|
||||
this.workspace_ = this.sourceBlock_.workspace;
|
||||
var variable = Blockly.Variables.getOrCreateVariablePackage(
|
||||
this.workspace_, null, this.text_, this.variableType_);
|
||||
this.setValue(variable.getId());
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the variable's ID.
|
||||
* @return {string} Current variable's ID.
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.getValue = function() {
|
||||
return this.variable_ ? this.variable_.getId() : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field.
|
||||
* @return {string} Current text.
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.getText = function() {
|
||||
return this.variable_ ? this.variable_.name : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the variable model for the variable associated with this field.
|
||||
* Not guaranteed to be in the variable map on the workspace (e.g. if accessed
|
||||
* after the variable has been deleted).
|
||||
* @return {?Blockly.VariableModel} the selected variable, or null if none was
|
||||
* selected.
|
||||
* @package
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.getVariable = function() {
|
||||
return this.variable_;
|
||||
};
|
||||
|
||||
Blockly.FieldVariableGetter.prototype.setValue = function(id) {
|
||||
// What do I do when id is null? That happens when undoing a change event
|
||||
// for the first time the value was set.
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
var variable = Blockly.Variables.getVariable(workspace, id);
|
||||
|
||||
if (!variable) {
|
||||
throw new Error('Variable id doesn\'t point to a real variable! ID was ' +
|
||||
id);
|
||||
}
|
||||
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
var oldValue = this.variable_ ? this.variable_.getId() : null;
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, oldValue, variable.getId()));
|
||||
}
|
||||
this.variable_ = variable;
|
||||
this.value_ = id;
|
||||
this.setText(variable.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* This field is editable, but only through the right-click menu.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.showEditor_ = function() {
|
||||
// nop.
|
||||
};
|
||||
|
||||
/**
|
||||
* Add or remove the UI indicating if this field is editable or not.
|
||||
* This field is editable, but only through the right-click menu.
|
||||
* Suppress default editable behaviour.
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.updateEditable = function() {
|
||||
// nop.
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this field references any Blockly variables. If true it may need to
|
||||
* be handled differently during serialization and deserialization. Subclasses
|
||||
* may override this.
|
||||
* @return {boolean} True if this field has any variable references.
|
||||
* @package
|
||||
*/
|
||||
Blockly.FieldVariableGetter.prototype.referencesVariables = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_variable_getter', Blockly.FieldVariableGetter);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user