mirror of
https://github.com/google/blockly.git
synced 2026-01-09 01:50:11 +01:00
222
.eslintrc.js
Normal file
222
.eslintrc.js
Normal file
@@ -0,0 +1,222 @@
|
||||
const rules = {
|
||||
'curly': ['error'],
|
||||
'eol-last': ['error'],
|
||||
'keyword-spacing': ['error'],
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
'max-len': [
|
||||
'error',
|
||||
{
|
||||
'code': 100,
|
||||
'tabWidth': 4,
|
||||
'ignoreStrings': true,
|
||||
'ignoreRegExpLiterals': true,
|
||||
'ignoreUrls': true,
|
||||
},
|
||||
],
|
||||
'no-trailing-spaces': ['error', {'skipBlankLines': true}],
|
||||
'no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
'args': 'after-used',
|
||||
// Ignore vars starting with an underscore.
|
||||
'varsIgnorePattern': '^_',
|
||||
// Ignore arguments starting with an underscore.
|
||||
'argsIgnorePattern': '^_',
|
||||
},
|
||||
],
|
||||
// Blockly uses for exporting symbols. no-self-assign added in eslint 5.
|
||||
'no-self-assign': ['off'],
|
||||
// Blockly uses single quotes except for JSON blobs, which must use double
|
||||
// quotes.
|
||||
'quotes': ['off'],
|
||||
'semi': ['error', 'always'],
|
||||
// Blockly doesn't have space before function paren when defining functions.
|
||||
'space-before-function-paren': ['error', 'never'],
|
||||
// Blockly doesn't have space before function paren when calling functions.
|
||||
'func-call-spacing': ['error', 'never'],
|
||||
'space-infix-ops': ['error'],
|
||||
// Blockly uses 'use strict' in files.
|
||||
'strict': ['off'],
|
||||
// Closure style allows redeclarations.
|
||||
'no-redeclare': ['off'],
|
||||
'valid-jsdoc': ['error'],
|
||||
'no-console': ['off'],
|
||||
'no-multi-spaces': ['error', {'ignoreEOLComments': true}],
|
||||
'operator-linebreak': ['error', 'after'],
|
||||
'spaced-comment': [
|
||||
'error',
|
||||
'always',
|
||||
{
|
||||
'block': {
|
||||
'balanced': true,
|
||||
},
|
||||
'exceptions': ['*'],
|
||||
},
|
||||
],
|
||||
// Blockly uses prefixes for optional arguments and test-only functions.
|
||||
'camelcase': [
|
||||
'error',
|
||||
{
|
||||
'properties': 'never',
|
||||
'allow': ['^opt_', '^_opt_', '^testOnly_'],
|
||||
},
|
||||
],
|
||||
// Use clang-format for indentation by running `npm run format`.
|
||||
'indent': ['off'],
|
||||
// Blockly uses capital letters for some non-constructor namespaces.
|
||||
// Keep them for legacy reasons.
|
||||
'new-cap': ['off'],
|
||||
// Mostly use default rules for brace style, but allow single-line blocks.
|
||||
'brace-style': ['error', '1tbs', {'allowSingleLine': true}],
|
||||
// Blockly uses objects as maps, but uses Object.create(null) to
|
||||
// instantiate them.
|
||||
'guard-for-in': ['off'],
|
||||
'prefer-spread': ['off'],
|
||||
'comma-dangle': [
|
||||
'error',
|
||||
{
|
||||
'arrays': 'always-multiline',
|
||||
'objects': 'always-multiline',
|
||||
'imports': 'always-multiline',
|
||||
'exports': 'always-multiline',
|
||||
'functions': 'ignore',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Build shared settings for TS linting and add in the config differences.
|
||||
* @return {Object} The override TS linting for given files and a given
|
||||
* tsconfig.
|
||||
*/
|
||||
function buildTSOverride({files, tsconfig}) {
|
||||
return {
|
||||
'files': files,
|
||||
'plugins': [
|
||||
'@typescript-eslint/eslint-plugin',
|
||||
'jsdoc',
|
||||
],
|
||||
'settings': {
|
||||
'jsdoc': {
|
||||
'mode': 'typescript',
|
||||
},
|
||||
},
|
||||
'parser': '@typescript-eslint/parser',
|
||||
'parserOptions': {
|
||||
'project': tsconfig,
|
||||
'tsconfigRootDir': '.',
|
||||
'ecmaVersion': 2020,
|
||||
'sourceType': 'module',
|
||||
},
|
||||
'extends': [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:jsdoc/recommended',
|
||||
],
|
||||
'rules': {
|
||||
// TS rules
|
||||
// Blockly uses namespaces to do declaration merging in some cases.
|
||||
'@typescript-eslint/no-namespace': ['off'],
|
||||
// Use the updated TypeScript-specific rule.
|
||||
'no-invalid-this': ['off'],
|
||||
'@typescript-eslint/no-invalid-this': ['error'],
|
||||
// Needs decision. 601 problems.
|
||||
'@typescript-eslint/no-non-null-assertion': ['off'],
|
||||
// Use TS-specific rule.
|
||||
'no-unused-vars': ['off'],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
'argsIgnorePattern': '^_',
|
||||
'varsIgnorePattern': '^_',
|
||||
},
|
||||
],
|
||||
'func-call-spacing': ['off'],
|
||||
'@typescript-eslint/func-call-spacing': ['warn'],
|
||||
// Temporarily disable. 23 problems.
|
||||
'@typescript-eslint/no-explicit-any': ['off'],
|
||||
// Temporarily disable. 128 problems.
|
||||
'require-jsdoc': ['off'],
|
||||
// Temporarily disable. 55 problems.
|
||||
'@typescript-eslint/ban-types': ['off'],
|
||||
// Temporarily disable. 33 problems.
|
||||
'@typescript-eslint/no-empty-function': ['off'],
|
||||
// Temporarily disable. 3 problems.
|
||||
'@typescript-eslint/no-empty-interface': ['off'],
|
||||
|
||||
// TsDoc rules (using JsDoc plugin)
|
||||
// Disable built-in jsdoc verifier.
|
||||
'valid-jsdoc': ['off'],
|
||||
// Don't require types in params and returns docs.
|
||||
'jsdoc/require-param-type': ['off'],
|
||||
'jsdoc/require-returns-type': ['off'],
|
||||
// params and returns docs are optional.
|
||||
'jsdoc/require-param-description': ['off'],
|
||||
'jsdoc/require-returns': ['off'],
|
||||
// Disable for now (breaks on `this` which is not really a param).
|
||||
'jsdoc/require-param': ['off'],
|
||||
// Don't auto-add missing jsdoc. Only required on exported items.
|
||||
'jsdoc/require-jsdoc': [
|
||||
'warn',
|
||||
{
|
||||
'enableFixer': false,
|
||||
'publicOnly': true,
|
||||
},
|
||||
],
|
||||
// Disable because of false alarms with Closure-supported tags.
|
||||
// Re-enable after Closure is removed.
|
||||
'jsdoc/check-tag-names': ['off'],
|
||||
// Re-enable after Closure is removed. There shouldn't even be
|
||||
// types in the TsDoc.
|
||||
// These are "types" because of Closure's @suppress {warningName}
|
||||
'jsdoc/no-undefined-types': ['off'],
|
||||
'jsdoc/valid-types': ['off'],
|
||||
// Disabled due to not handling `this`. If re-enabled,
|
||||
// checkDestructured option
|
||||
// should be left as false.
|
||||
'jsdoc/check-param-names': ['off', {'checkDestructured': false}],
|
||||
// Allow any text in the license tag. Other checks are not relevant.
|
||||
'jsdoc/check-values': ['off'],
|
||||
'jsdoc/newline-after-description': ['error'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE: When this output is put directly in `module.exports`, the formatter
|
||||
// does not align with the linter.
|
||||
const eslintJSON = {
|
||||
'rules': rules,
|
||||
'env': {
|
||||
'es2020': true,
|
||||
'browser': true,
|
||||
},
|
||||
'globals': {
|
||||
'goog': true,
|
||||
'exports': true,
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'google',
|
||||
],
|
||||
// TypeScript-specific config. Uses above rules plus these.
|
||||
'overrides': [
|
||||
buildTSOverride({
|
||||
files: ['./core/**/*.ts', './core/**/*.tsx'],
|
||||
tsconfig: './tsconfig.json',
|
||||
}),
|
||||
buildTSOverride({
|
||||
files: [
|
||||
'./tests/typescript/**/*.ts',
|
||||
'./tests/typescript/**/*.tsx',
|
||||
],
|
||||
tsconfig: './tests/typescript/tsconfig.json',
|
||||
}),
|
||||
{
|
||||
'files': ['./.eslintrc.js'],
|
||||
'env': {
|
||||
'node': true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = eslintJSON;
|
||||
165
.eslintrc.json
165
.eslintrc.json
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"curly": ["error"],
|
||||
"eol-last": ["error"],
|
||||
"keyword-spacing": ["error"],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"max-len": [
|
||||
"error",
|
||||
{
|
||||
"code": 100,
|
||||
"tabWidth": 4,
|
||||
"ignoreStrings": true,
|
||||
"ignoreRegExpLiterals": true,
|
||||
"ignoreUrls": true
|
||||
}
|
||||
],
|
||||
"no-trailing-spaces": ["error", { "skipBlankLines": true }],
|
||||
"no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"args": "after-used",
|
||||
// Ignore vars starting with an underscore.
|
||||
"varsIgnorePattern": "^_",
|
||||
// Ignore arguments starting with an underscore.
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
// Blockly uses for exporting symbols. no-self-assign added in eslint 5.
|
||||
"no-self-assign": ["off"],
|
||||
// Blockly uses single quotes except for JSON blobs, which must use double quotes.
|
||||
"quotes": ["off"],
|
||||
"semi": ["error", "always"],
|
||||
// Blockly doesn't have space before function paren when defining functions.
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
// Blockly doesn't have space before function paren when calling functions.
|
||||
"func-call-spacing": ["error", "never"],
|
||||
"space-infix-ops": ["error"],
|
||||
// Blockly uses 'use strict' in files.
|
||||
"strict": ["off"],
|
||||
// Closure style allows redeclarations.
|
||||
"no-redeclare": ["off"],
|
||||
"valid-jsdoc": ["error"],
|
||||
"no-console": ["off"],
|
||||
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"spaced-comment": ["error", "always", {
|
||||
"block": {
|
||||
"balanced": true
|
||||
},
|
||||
"exceptions": ["*"]
|
||||
}],
|
||||
// Blockly uses prefixes for optional arguments and test-only functions.
|
||||
"camelcase": ["error", {
|
||||
"properties": "never",
|
||||
"allow": ["^opt_", "^_opt_", "^testOnly_"]
|
||||
}],
|
||||
// Use clang-format for indentation by running `npm run format`.
|
||||
"indent": ["off"],
|
||||
// Blockly uses capital letters for some non-constructor namespaces.
|
||||
// Keep them for legacy reasons.
|
||||
"new-cap": ["off"],
|
||||
// Mostly use default rules for brace style, but allow single-line blocks.
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
// Blockly uses objects as maps, but uses Object.create(null) to
|
||||
// instantiate them.
|
||||
"guard-for-in": ["off"],
|
||||
"prefer-spread": ["off"],
|
||||
"comma-dangle": ["error", {
|
||||
"arrays": "always-multiline",
|
||||
"objects": "always-multiline",
|
||||
"imports": "always-multiline",
|
||||
"exports": "always-multiline",
|
||||
"functions": "ignore"
|
||||
}]
|
||||
},
|
||||
"env": {
|
||||
"es2020": true,
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"goog": true,
|
||||
"exports": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended", "google"
|
||||
],
|
||||
// TypeScript-specific config. Uses above rules plus these.
|
||||
"overrides": [{
|
||||
"files": ["**/*.ts", "**/*.tsx"],
|
||||
"plugins": [
|
||||
"@typescript-eslint/eslint-plugin",
|
||||
"jsdoc"
|
||||
],
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
"mode": "typescript"
|
||||
}
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"tsconfigRootDir": ".",
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:jsdoc/recommended"
|
||||
],
|
||||
"rules": {
|
||||
// TS rules
|
||||
// Blockly uses namespaces to do declaration merging in some cases.
|
||||
"@typescript-eslint/no-namespace": ["off"],
|
||||
// Use the updated TypeScript-specific rule.
|
||||
"no-invalid-this": ["off"],
|
||||
"@typescript-eslint/no-invalid-this": ["error"],
|
||||
// Needs decision. 601 problems.
|
||||
"@typescript-eslint/no-non-null-assertion": ["off"],
|
||||
// Use TS-specific rule.
|
||||
"no-unused-vars": ["off"],
|
||||
"@typescript-eslint/no-unused-vars": ["warn", {
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_"
|
||||
}],
|
||||
"func-call-spacing": ["off"],
|
||||
"@typescript-eslint/func-call-spacing": ["warn"],
|
||||
// Temporarily disable. 23 problems.
|
||||
"@typescript-eslint/no-explicit-any": ["off"],
|
||||
// Temporarily disable. 128 problems.
|
||||
"require-jsdoc": ["off"],
|
||||
// Temporarily disable. 55 problems.
|
||||
"@typescript-eslint/ban-types": ["off"],
|
||||
// Temporarily disable. 33 problems.
|
||||
"@typescript-eslint/no-empty-function": ["off"],
|
||||
// Temporarily disable. 3 problems.
|
||||
"@typescript-eslint/no-empty-interface": ["off"],
|
||||
|
||||
// TsDoc rules (using JsDoc plugin)
|
||||
// Disable built-in jsdoc verifier.
|
||||
"valid-jsdoc": ["off"],
|
||||
// Don't require types in params and returns docs.
|
||||
"jsdoc/require-param-type": ["off"],
|
||||
"jsdoc/require-returns-type": ["off"],
|
||||
// params and returns docs are optional.
|
||||
"jsdoc/require-param-description": ["off"],
|
||||
"jsdoc/require-returns": ["off"],
|
||||
// Disable for now (breaks on `this` which is not really a param).
|
||||
"jsdoc/require-param": ["off"],
|
||||
// Don't auto-add missing jsdoc. Only required on exported items.
|
||||
"jsdoc/require-jsdoc": ["warn", {"enableFixer": false, "publicOnly": true}],
|
||||
// Disable because of false alarms with Closure-supported tags.
|
||||
// Re-enable after Closure is removed.
|
||||
"jsdoc/check-tag-names": ["off"],
|
||||
// Re-enable after Closure is removed. There shouldn't even be types in the TsDoc.
|
||||
// These are "types" because of Closure's @suppress {warningName}
|
||||
"jsdoc/no-undefined-types": ["off"],
|
||||
"jsdoc/valid-types": ["off"],
|
||||
// Disabled due to not handling `this`. If re-enabled, checkDestructured option
|
||||
// should be left as false.
|
||||
"jsdoc/check-param-names": ["off", {"checkDestructured": false}],
|
||||
// Allow any text in the license tag. Other checks are not relevant.
|
||||
"jsdoc/check-values": ["off"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
65
.github/ISSUE_TEMPLATE/bug_report.md
vendored
65
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,65 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help us improve
|
||||
labels: 'type: bug, issue: triage'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
- Thanks for opening an issue for us! Before you open an issue,
|
||||
- please check if a similar issue exists or has been closed before.
|
||||
-
|
||||
- If you're asking a question about how to use Blockly in your application,
|
||||
- please ask questions on the mailing list, instead of filing issues:
|
||||
- https://groups.google.com/forum/#!forum/blockly
|
||||
-->
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
|
||||
<!-- Explain what someone needs to do in order to see what's described above -->
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Screenshots**
|
||||
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Stack Traces**
|
||||
|
||||
<!-- Please open up the console. If you see any Blockly-related errors,
|
||||
- paste them between the quotes below.
|
||||
-
|
||||
- Ignore any instances of...
|
||||
- "Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause()."
|
||||
-->
|
||||
|
||||
```
|
||||
Replace with error stack trace.
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
58
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve
|
||||
labels: 'issue: bug, issue: triage'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Thank you for taking the time to fill out a bug report!
|
||||
If you have a question about how to use Blockly in your application,
|
||||
please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue.
|
||||
- type: checkboxes
|
||||
id: duplicates
|
||||
attributes:
|
||||
label: Check for duplicates
|
||||
options:
|
||||
- label: I have searched for similar issues before opening a new one.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide a clear and concise description of the bug.
|
||||
placeholder: What happened? What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: What steps should we take to reproduce the issue?
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
- type: textarea
|
||||
id: stack-trace
|
||||
attributes:
|
||||
label: Stack trace
|
||||
description: If you saw an error message or stack trace, please include it here.
|
||||
placeholder: The text in this section will be formatted automatically; no need to include backticks.
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: Screenshots can help us see the behavior you're describing. Please add a screenshot or gif, especially if you are describing a rendering or visual bug.
|
||||
placeholder: Paste or drag-and-drop an image to upload it.
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: Browsers
|
||||
description: Please select all browsers you've observed this behavior on. If the bug isn't browser-specific, you can leave this blank.
|
||||
multiple: true
|
||||
options:
|
||||
- Chrome desktop
|
||||
- Safari desktop
|
||||
- Firefox desktop
|
||||
- Android mobile
|
||||
- iOS mobile
|
||||
44
.github/ISSUE_TEMPLATE/documentation.md
vendored
44
.github/ISSUE_TEMPLATE/documentation.md
vendored
@@ -1,44 +0,0 @@
|
||||
---
|
||||
name: Documentation
|
||||
about: Report an issue with our documentation
|
||||
labels: 'type: documentation, issue: triage'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
- Thanks for helping us improve our developer site documentation!
|
||||
- Use this template to describe issues with the content at
|
||||
- developers.google.com/blockly/guides
|
||||
-->
|
||||
|
||||
**Where**
|
||||
|
||||
<!-- A link to the page with the documentation you want us to update.
|
||||
- More specific is better. If no page exists, describe what the page
|
||||
- should be, and where.
|
||||
-->
|
||||
|
||||
**What**
|
||||
|
||||
<!-- What kind of content is it?
|
||||
- Check a box with an 'x' between the brackets: [x]
|
||||
-->
|
||||
|
||||
- [ ] Text
|
||||
- [ ] Image or Gif
|
||||
- [ ] Other
|
||||
|
||||
**Old content**
|
||||
|
||||
<!-- What the documentation currently says -->
|
||||
|
||||
**Suggested content**
|
||||
|
||||
<!-- Your suggestion for improved documentation -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here.
|
||||
- If this is related to a specific pull request, link to it.
|
||||
-->
|
||||
39
.github/ISSUE_TEMPLATE/documentation.yaml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/documentation.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
name: Documentation
|
||||
description: Report an issue with our documentation
|
||||
labels: 'issue: docs, issue: triage'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Thanks for helping us improve our developer site documentation!
|
||||
Use this template to describe issues with the content on our
|
||||
[developer site](https://developers.google.com/blockly/guides).
|
||||
- type: input
|
||||
id: link
|
||||
attributes:
|
||||
label: Location
|
||||
description: >
|
||||
A link to the page with the documentation you want us to be updated.
|
||||
If no page exists, describe what the page should be, and where.
|
||||
- type: checkboxes
|
||||
id: type
|
||||
attributes:
|
||||
label: Type
|
||||
description: What kind of content is it?
|
||||
options:
|
||||
- label: Text
|
||||
- label: Image or Gif
|
||||
- label: Other
|
||||
- type: textarea
|
||||
id: content
|
||||
attributes:
|
||||
label: Suggested content
|
||||
description: Your suggestion for improved documentation. If it's helpful, also include the old content for comparison.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem. If this is related to a specific pull request, link to it.
|
||||
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
labels: 'type: feature request, issue: triage'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
40
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
40
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: 'issue: feature request, issue: triage'
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: >
|
||||
Thank you for taking the time to fill out a feature request!
|
||||
If you have a question about how to use Blockly in your application,
|
||||
please ask on the [forum](https://groups.google.com/forum/#!forum/blockly) instead of filing an issue.
|
||||
- type: checkboxes
|
||||
id: duplicates
|
||||
attributes:
|
||||
label: Check for duplicates
|
||||
options:
|
||||
- label: I have searched for similar issues before opening a new one.
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem
|
||||
description: Is your feature request related to a problem? Please describe.
|
||||
placeholder: I'm always frustrated when...
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Request
|
||||
description: Describe your feature request and how it solves your problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Describe any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
2
.github/workflows/appengine_deploy.yml
vendored
2
.github/workflows/appengine_deploy.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
path: _deploy/
|
||||
|
||||
- name: Deploy to App Engine
|
||||
uses: google-github-actions/deploy-appengine@v1.0.0
|
||||
uses: google-github-actions/deploy-appengine@v1.2.2
|
||||
# For parameters see:
|
||||
# https://github.com/google-github-actions/deploy-appengine#inputs
|
||||
with:
|
||||
|
||||
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@@ -10,6 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 10
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
@@ -17,8 +18,9 @@ jobs:
|
||||
# TODO (#2114): re-enable osx build.
|
||||
# os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
node-version: [14.x, 16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
node-version: [14.x, 16.x, 18.x]
|
||||
# See supported Node.js release schedule at
|
||||
# https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -53,6 +55,7 @@ jobs:
|
||||
CI: true
|
||||
|
||||
lint:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -67,3 +70,18 @@ jobs:
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
clang-formatter:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DoozyX/clang-format-lint-action@v0.15
|
||||
with:
|
||||
source: 'core'
|
||||
extensions: 'js,ts'
|
||||
# This should be as close as possible to the version that the npm
|
||||
# package supports. This can be found by running:
|
||||
# npx clang-format --version.
|
||||
clangFormatVersion: 15
|
||||
|
||||
28
.github/workflows/check_clang_format.yml
vendored
28
.github/workflows/check_clang_format.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Check clang format
|
||||
|
||||
# N.B.: Runs with a read-only repo token. Safe(ish) to check out the
|
||||
# submitted branch.
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
clang-formatter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DoozyX/clang-format-lint-action@v0.15
|
||||
with:
|
||||
source: 'core'
|
||||
extensions: 'js,ts'
|
||||
# This should be as close as possible to the version that the npm
|
||||
# package supports. This can be found by running:
|
||||
# npx clang-format --version.
|
||||
clangFormatVersion: 15
|
||||
|
||||
# The Report clang format workflow (report_clang_format.yml) will
|
||||
# run (if required) after this one to post a comment to the PR.
|
||||
# (Note that the version of that workflow run will be the one on
|
||||
# the master (default) branch, not the PR target branch.)
|
||||
2
.github/workflows/update_metadata.yml
vendored
2
.github/workflows/update_metadata.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
run: source ./tests/scripts/update_metadata.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04
|
||||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54
|
||||
with:
|
||||
commit-message: Update build artifact sizes in check_metadata.sh
|
||||
delete-branch: true
|
||||
|
||||
@@ -83,4 +83,4 @@ We typically triage all bugs within 2 working days, which includes adding any ap
|
||||
## Good to Know
|
||||
|
||||
* Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com)
|
||||
* We support IE11 and test it using [BrowserStack](https://browserstack.com)
|
||||
* We test browsers using [BrowserStack](https://browserstack.com)
|
||||
|
||||
@@ -44,7 +44,7 @@ BlocklyStorage.restoreBlocks = function(opt_workspace) {
|
||||
var url = window.location.href.split('#')[0];
|
||||
if ('localStorage' in window && window.localStorage[url]) {
|
||||
var workspace = opt_workspace || Blockly.getMainWorkspace();
|
||||
var xml = Blockly.Xml.textToDom(window.localStorage[url]);
|
||||
var xml = Blockly.utils.xml.textToDom(window.localStorage[url]);
|
||||
Blockly.Xml.domToWorkspace(xml, workspace);
|
||||
}
|
||||
};
|
||||
@@ -168,7 +168,7 @@ BlocklyStorage.monitorChanges_ = function(workspace) {
|
||||
*/
|
||||
BlocklyStorage.loadXml_ = function(xml, workspace) {
|
||||
try {
|
||||
xml = Blockly.Xml.textToDom(xml);
|
||||
xml = Blockly.utils.xml.textToDom(xml);
|
||||
} catch (e) {
|
||||
BlocklyStorage.alert(BlocklyStorage.XML_ERROR + '\nXML: ' + xml);
|
||||
return;
|
||||
|
||||
@@ -7,24 +7,19 @@
|
||||
/**
|
||||
* @fileoverview Colour blocks for Blockly.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.module('Blockly.libraryBlocks.colour');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.libraryBlocks.colour');
|
||||
|
||||
// const {BlockDefinition} = goog.requireType('Blockly.blocks');
|
||||
// TODO (6248): Properly import the BlockDefinition type.
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const BlockDefinition = Object;
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.FieldColour');
|
||||
import type {BlockDefinition} from '../core/blocks.js';
|
||||
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
|
||||
import '../core/field_colour.js';
|
||||
|
||||
|
||||
/**
|
||||
* A dictionary of the block definitions provided by this module.
|
||||
* @type {!Object<string, !BlockDefinition>}
|
||||
*/
|
||||
const blocks = createBlockDefinitionsFromJsonArray([
|
||||
export const blocks = createBlockDefinitionsFromJsonArray([
|
||||
// Block for colour picker.
|
||||
{
|
||||
'type': 'colour_picker',
|
||||
@@ -115,7 +110,6 @@ const blocks = createBlockDefinitionsFromJsonArray([
|
||||
'tooltip': '%{BKY_COLOUR_BLEND_TOOLTIP}',
|
||||
},
|
||||
]);
|
||||
exports.blocks = blocks;
|
||||
|
||||
// Register provided blocks.
|
||||
defineBlocks(blocks);
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
goog.module('Blockly.libraryBlocks.lists');
|
||||
|
||||
const fieldRegistry = goog.require('Blockly.fieldRegistry');
|
||||
const xmlUtils = goog.require('Blockly.utils.xml');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const {Align} = goog.require('Blockly.Input');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
@@ -22,7 +22,6 @@ const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const BlockDefinition = Object;
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {FieldDropdown} = goog.require('Blockly.FieldDropdown');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {Mutator} = goog.require('Blockly.Mutator');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
@@ -315,8 +314,11 @@ blocks['lists_indexOf'] = {
|
||||
this.setOutput(true, 'Number');
|
||||
this.appendValueInput('VALUE').setCheck('Array').appendField(
|
||||
Msg['LISTS_INDEX_OF_INPUT_IN_LIST']);
|
||||
this.appendValueInput('FIND').appendField(
|
||||
new FieldDropdown(OPERATORS), 'END');
|
||||
const operatorsDropdown = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: OPERATORS,
|
||||
});
|
||||
this.appendValueInput('FIND').appendField(operatorsDropdown, 'END');
|
||||
this.setInputsInline(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
const thisBlock = this;
|
||||
@@ -347,8 +349,11 @@ blocks['lists_getIndex'] = {
|
||||
];
|
||||
this.setHelpUrl(Msg['LISTS_GET_INDEX_HELPURL']);
|
||||
this.setStyle('list_blocks');
|
||||
const modeMenu = new FieldDropdown(
|
||||
MODE,
|
||||
const modeMenu = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: MODE,
|
||||
});
|
||||
modeMenu.setValidator(
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
@@ -480,7 +485,7 @@ blocks['lists_getIndex'] = {
|
||||
this.updateStatement_(true);
|
||||
} else if (typeof state === 'string') {
|
||||
// backward compatible for json serialised mutations
|
||||
this.domToMutation(Xml.textToDom(state));
|
||||
this.domToMutation(xmlUtils.textToDom(state));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -526,8 +531,11 @@ blocks['lists_getIndex'] = {
|
||||
} else {
|
||||
this.appendDummyInput('AT');
|
||||
}
|
||||
const menu = new FieldDropdown(
|
||||
this.WHERE_OPTIONS,
|
||||
const menu = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: this.WHERE_OPTIONS,
|
||||
});
|
||||
menu.setValidator(
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
@@ -576,8 +584,12 @@ blocks['lists_setIndex'] = {
|
||||
this.setStyle('list_blocks');
|
||||
this.appendValueInput('LIST').setCheck('Array').appendField(
|
||||
Msg['LISTS_SET_INDEX_INPUT_IN_LIST']);
|
||||
const operationDropdown = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: MODE,
|
||||
});
|
||||
this.appendDummyInput()
|
||||
.appendField(new FieldDropdown(MODE), 'MODE')
|
||||
.appendField(operationDropdown, 'MODE')
|
||||
.appendField('', 'SPACE');
|
||||
this.appendDummyInput('AT');
|
||||
this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']);
|
||||
@@ -689,8 +701,11 @@ blocks['lists_setIndex'] = {
|
||||
} else {
|
||||
this.appendDummyInput('AT');
|
||||
}
|
||||
const menu = new FieldDropdown(
|
||||
this.WHERE_OPTIONS,
|
||||
const menu = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: this.WHERE_OPTIONS,
|
||||
});
|
||||
menu.setValidator(
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
@@ -817,8 +832,11 @@ blocks['lists_getSublist'] = {
|
||||
} else {
|
||||
this.appendDummyInput('AT' + n);
|
||||
}
|
||||
const menu = new FieldDropdown(
|
||||
this['WHERE_OPTIONS_' + n],
|
||||
const menu = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: this['WHERE_OPTIONS_' + n],
|
||||
});
|
||||
menu.setValidator(
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
@@ -899,14 +917,16 @@ blocks['lists_split'] = {
|
||||
init: function() {
|
||||
// Assign 'this' to a variable for use in the closures below.
|
||||
const thisBlock = this;
|
||||
const dropdown = new FieldDropdown(
|
||||
[
|
||||
[Msg['LISTS_SPLIT_LIST_FROM_TEXT'], 'SPLIT'],
|
||||
[Msg['LISTS_SPLIT_TEXT_FROM_LIST'], 'JOIN'],
|
||||
],
|
||||
function(newMode) {
|
||||
thisBlock.updateType_(newMode);
|
||||
});
|
||||
const dropdown = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: [
|
||||
[Msg['LISTS_SPLIT_LIST_FROM_TEXT'], 'SPLIT'],
|
||||
[Msg['LISTS_SPLIT_TEXT_FROM_LIST'], 'JOIN'],
|
||||
],
|
||||
});
|
||||
dropdown.setValidator(function(newMode) {
|
||||
thisBlock.updateType_(newMode);
|
||||
});
|
||||
this.setHelpUrl(Msg['LISTS_SPLIT_HELPURL']);
|
||||
this.setStyle('list_blocks');
|
||||
this.appendValueInput('INPUT').setCheck('String').appendField(
|
||||
|
||||
@@ -6,37 +6,28 @@
|
||||
|
||||
/**
|
||||
* @fileoverview Math blocks for Blockly.
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.module('Blockly.libraryBlocks.math');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.libraryBlocks.math');
|
||||
|
||||
const Extensions = goog.require('Blockly.Extensions');
|
||||
// N.B.: Blockly.FieldDropdown needed for type AND side-effects.
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const FieldDropdown = goog.require('Blockly.FieldDropdown');
|
||||
const xmlUtils = goog.require('Blockly.utils.xml');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
// const {BlockDefinition} = goog.requireType('Blockly.blocks');
|
||||
// TODO (6248): Properly import the BlockDefinition type.
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const BlockDefinition = Object;
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.FieldLabel');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.FieldNumber');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.FieldVariable');
|
||||
import * as Extensions from '../core/extensions.js';
|
||||
import type {Field} from '../core/field.js';
|
||||
import type {FieldDropdown} from '../core/field_dropdown.js';
|
||||
import * as xmlUtils from '../core/utils/xml.js';
|
||||
import type {Block} from '../core/block.js';
|
||||
import type {BlockDefinition} from '../core/blocks.js';
|
||||
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
|
||||
import '../core/field_dropdown.js';
|
||||
import '../core/field_label.js';
|
||||
import '../core/field_number.js';
|
||||
import '../core/field_variable.js';
|
||||
|
||||
|
||||
/**
|
||||
* A dictionary of the block definitions provided by this module.
|
||||
* @type {!Object<string, !BlockDefinition>}
|
||||
*/
|
||||
const blocks = createBlockDefinitionsFromJsonArray([
|
||||
export const blocks = createBlockDefinitionsFromJsonArray([
|
||||
// Block for numeric value.
|
||||
{
|
||||
'type': 'math_number',
|
||||
@@ -391,11 +382,11 @@ const blocks = createBlockDefinitionsFromJsonArray([
|
||||
'helpUrl': '%{BKY_MATH_ATAN2_HELPURL}',
|
||||
},
|
||||
]);
|
||||
exports.blocks = blocks;
|
||||
|
||||
/**
|
||||
* Mapping of math block OP value to tooltip message for blocks
|
||||
* math_arithmetic, math_simple, math_trig, and math_on_lists.
|
||||
*
|
||||
* @see {Extensions#buildTooltipForDropdown}
|
||||
* @package
|
||||
* @readonly
|
||||
@@ -440,10 +431,15 @@ Extensions.register(
|
||||
'math_op_tooltip',
|
||||
Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP));
|
||||
|
||||
/** Type of a block that has IS_DIVISBLEBY_MUTATOR_MIXIN */
|
||||
type DivisiblebyBlock = Block&DivisiblebyMixin;
|
||||
interface DivisiblebyMixin extends DivisiblebyMixinType {};
|
||||
type DivisiblebyMixinType = typeof IS_DIVISIBLEBY_MUTATOR_MIXIN;
|
||||
|
||||
/**
|
||||
* Mixin for mutator functions in the 'math_is_divisibleby_mutator'
|
||||
* extension.
|
||||
*
|
||||
* @mixin
|
||||
* @augments Block
|
||||
* @package
|
||||
@@ -452,22 +448,22 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
|
||||
/**
|
||||
* Create XML to represent whether the 'divisorInput' should be present.
|
||||
* Backwards compatible serialization implementation.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this {Block}
|
||||
*
|
||||
* @returns XML storage element.
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
mutationToDom: function(this: DivisiblebyBlock): Element {
|
||||
const container = xmlUtils.createElement('mutation');
|
||||
const divisorInput = (this.getFieldValue('PROPERTY') === 'DIVISIBLE_BY');
|
||||
container.setAttribute('divisor_input', divisorInput);
|
||||
container.setAttribute('divisor_input', String(divisorInput));
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the 'divisorInput'.
|
||||
* Backwards compatible serialization implementation.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this {Block}
|
||||
*
|
||||
* @param xmlElement XML storage element.
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
domToMutation: function(this: DivisiblebyBlock, xmlElement: Element) {
|
||||
const divisorInput = (xmlElement.getAttribute('divisor_input') === 'true');
|
||||
this.updateShape_(divisorInput);
|
||||
},
|
||||
@@ -479,11 +475,10 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
|
||||
|
||||
/**
|
||||
* Modify this block to have (or not have) an input for 'is divisible by'.
|
||||
* @param {boolean} divisorInput True if this block has a divisor input.
|
||||
* @private
|
||||
* @this {Block}
|
||||
*
|
||||
* @param divisorInput True if this block has a divisor input.
|
||||
*/
|
||||
updateShape_: function(divisorInput) {
|
||||
updateShape_: function(this: DivisiblebyBlock, divisorInput: boolean) {
|
||||
// Add or remove a Value Input.
|
||||
const inputExists = this.getInput('DIVISOR');
|
||||
if (divisorInput) {
|
||||
@@ -500,20 +495,15 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
|
||||
* 'math_is_divisibleby_mutator' extension to the 'math_property' block that
|
||||
* can update the block shape (add/remove divisor input) based on whether
|
||||
* property is "divisible by".
|
||||
* @this {Block}
|
||||
* @package
|
||||
*/
|
||||
const IS_DIVISIBLE_MUTATOR_EXTENSION = function() {
|
||||
this.getField('PROPERTY')
|
||||
.setValidator(
|
||||
/**
|
||||
* @this {FieldDropdown}
|
||||
* @param {*} option The selected dropdown option.
|
||||
*/
|
||||
function(option) {
|
||||
const divisorInput = (option === 'DIVISIBLE_BY');
|
||||
this.getSourceBlock().updateShape_(divisorInput);
|
||||
});
|
||||
const IS_DIVISIBLE_MUTATOR_EXTENSION = function(this: DivisiblebyBlock) {
|
||||
this.getField('PROPERTY')!.setValidator(
|
||||
/** @param option The selected dropdown option. */
|
||||
function(this: FieldDropdown, option: string) {
|
||||
const divisorInput = (option === 'DIVISIBLE_BY');
|
||||
(this.getSourceBlock() as DivisiblebyBlock).updateShape_(divisorInput);
|
||||
return undefined; // FieldValidators can't be void. Use option as-is.
|
||||
});
|
||||
};
|
||||
|
||||
Extensions.registerMutator(
|
||||
@@ -525,35 +515,35 @@ Extensions.register(
|
||||
'math_change_tooltip',
|
||||
Extensions.buildTooltipWithFieldText('%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR'));
|
||||
|
||||
/** Type of a block that has LIST_MODES_MUTATOR_MIXIN */
|
||||
type ListModesBlock = Block&ListModesMixin;
|
||||
interface ListModesMixin extends ListModesMixinType {};
|
||||
type ListModesMixinType = typeof LIST_MODES_MUTATOR_MIXIN;
|
||||
|
||||
/**
|
||||
* Mixin with mutator methods to support alternate output based if the
|
||||
* 'math_on_list' block uses the 'MODE' operation.
|
||||
* @mixin
|
||||
* @augments Block
|
||||
* @package
|
||||
* @readonly
|
||||
*/
|
||||
const LIST_MODES_MUTATOR_MIXIN = {
|
||||
/**
|
||||
* Modify this block to have the correct output type.
|
||||
* @param {string} newOp Either 'MODE' or some op than returns a number.
|
||||
* @private
|
||||
* @this {Block}
|
||||
*
|
||||
* @param newOp Either 'MODE' or some op than returns a number.
|
||||
*/
|
||||
updateType_: function(newOp) {
|
||||
updateType_: function(this: ListModesBlock, newOp: string) {
|
||||
if (newOp === 'MODE') {
|
||||
this.outputConnection.setCheck('Array');
|
||||
this.outputConnection!.setCheck('Array');
|
||||
} else {
|
||||
this.outputConnection.setCheck('Number');
|
||||
this.outputConnection!.setCheck('Number');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create XML to represent the output type.
|
||||
* Backwards compatible serialization implementation.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this {Block}
|
||||
*
|
||||
* @returns XML storage element.
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
mutationToDom: function(this: ListModesBlock): Element {
|
||||
const container = xmlUtils.createElement('mutation');
|
||||
container.setAttribute('op', this.getFieldValue('OP'));
|
||||
return container;
|
||||
@@ -561,11 +551,13 @@ const LIST_MODES_MUTATOR_MIXIN = {
|
||||
/**
|
||||
* Parse XML to restore the output type.
|
||||
* Backwards compatible serialization implementation.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this {Block}
|
||||
*
|
||||
* @param xmlElement XML storage element.
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
this.updateType_(xmlElement.getAttribute('op'));
|
||||
domToMutation: function(this: ListModesBlock, xmlElement: Element) {
|
||||
const op = xmlElement.getAttribute('op');
|
||||
if (op === null) throw new TypeError('xmlElement had no op attribute');
|
||||
this.updateType_(op);
|
||||
},
|
||||
|
||||
// This block does not need JSO serialization hooks (saveExtraState and
|
||||
@@ -577,13 +569,13 @@ const LIST_MODES_MUTATOR_MIXIN = {
|
||||
/**
|
||||
* Extension to 'math_on_list' blocks that allows support of
|
||||
* modes operation (outputs a list of numbers).
|
||||
* @this {Block}
|
||||
* @package
|
||||
*/
|
||||
const LIST_MODES_MUTATOR_EXTENSION = function() {
|
||||
this.getField('OP').setValidator(function(newOp) {
|
||||
this.updateType_(newOp);
|
||||
}.bind(this));
|
||||
const LIST_MODES_MUTATOR_EXTENSION = function(this: ListModesBlock) {
|
||||
this.getField('OP')!.setValidator(
|
||||
function(this: ListModesBlock, newOp: string) {
|
||||
this.updateType_(newOp);
|
||||
return undefined;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Extensions.registerMutator(
|
||||
@@ -19,6 +19,7 @@ const Events = goog.require('Blockly.Events');
|
||||
const Procedures = goog.require('Blockly.Procedures');
|
||||
const Variables = goog.require('Blockly.Variables');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const fieldRegistry = goog.require('Blockly.fieldRegistry');
|
||||
const xmlUtils = goog.require('Blockly.utils.xml');
|
||||
const {Align} = goog.require('Blockly.Input');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
@@ -28,10 +29,6 @@ const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const BlockDefinition = Object;
|
||||
const {config} = goog.require('Blockly.config');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {FieldCheckbox} = goog.require('Blockly.FieldCheckbox');
|
||||
const {FieldLabel} = goog.require('Blockly.FieldLabel');
|
||||
const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {Mutator} = goog.require('Blockly.Mutator');
|
||||
const {Names} = goog.require('Blockly.Names');
|
||||
@@ -454,7 +451,11 @@ blocks['procedures_defnoreturn'] = {
|
||||
*/
|
||||
init: function() {
|
||||
const initName = Procedures.findLegalName('', this);
|
||||
const nameField = new FieldTextInput(initName, Procedures.rename);
|
||||
const nameField = fieldRegistry.fromJson({
|
||||
type: 'field_input',
|
||||
text: initName,
|
||||
});
|
||||
nameField.setValidator(Procedures.rename);
|
||||
nameField.setSpellcheck(false);
|
||||
this.appendDummyInput()
|
||||
.appendField(Msg['PROCEDURES_DEFNORETURN_TITLE'])
|
||||
@@ -497,7 +498,11 @@ blocks['procedures_defreturn'] = {
|
||||
*/
|
||||
init: function() {
|
||||
const initName = Procedures.findLegalName('', this);
|
||||
const nameField = new FieldTextInput(initName, Procedures.rename);
|
||||
const nameField = fieldRegistry.fromJson({
|
||||
type: 'field_input',
|
||||
text: initName,
|
||||
});
|
||||
nameField.setValidator(Procedures.rename);
|
||||
nameField.setSpellcheck(false);
|
||||
this.appendDummyInput()
|
||||
.appendField(Msg['PROCEDURES_DEFRETURN_TITLE'])
|
||||
@@ -546,7 +551,12 @@ blocks['procedures_mutatorcontainer'] = {
|
||||
this.appendStatementInput('STACK');
|
||||
this.appendDummyInput('STATEMENT_INPUT')
|
||||
.appendField(Msg['PROCEDURES_ALLOW_STATEMENTS'])
|
||||
.appendField(new FieldCheckbox('TRUE'), 'STATEMENTS');
|
||||
.appendField(
|
||||
fieldRegistry.fromJson({
|
||||
type: 'field_checkbox',
|
||||
checked: true,
|
||||
}),
|
||||
'STATEMENTS');
|
||||
this.setStyle('procedure_blocks');
|
||||
this.setTooltip(Msg['PROCEDURES_MUTATORCONTAINER_TOOLTIP']);
|
||||
this.contextMenu = false;
|
||||
@@ -559,7 +569,11 @@ blocks['procedures_mutatorarg'] = {
|
||||
* @this {Block}
|
||||
*/
|
||||
init: function() {
|
||||
const field = new FieldTextInput(Procedures.DEFAULT_ARG, this.validator_);
|
||||
const field = fieldRegistry.fromJson({
|
||||
type: 'field_input',
|
||||
text: Procedures.DEFAULT_ARG,
|
||||
});
|
||||
field.setValidator(this.validator_);
|
||||
// Hack: override showEditor to do just a little bit more work.
|
||||
// We don't have a good place to hook into the start of a text edit.
|
||||
field.oldShowEditorFn_ = field.showEditor_;
|
||||
@@ -810,7 +824,10 @@ const PROCEDURE_CALL_COMMON = {
|
||||
}
|
||||
} else {
|
||||
// Add new input.
|
||||
const newField = new FieldLabel(this.arguments_[i]);
|
||||
const newField = fieldRegistry.fromJson({
|
||||
type: 'field_label',
|
||||
text: this.arguments_[i],
|
||||
});
|
||||
const input = this.appendValueInput('ARG' + i)
|
||||
.setAlign(Align.RIGHT)
|
||||
.appendField(newField, 'ARGNAME' + i);
|
||||
|
||||
@@ -14,6 +14,7 @@ goog.module('Blockly.libraryBlocks.texts');
|
||||
|
||||
const Extensions = goog.require('Blockly.Extensions');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const fieldRegistry = goog.require('Blockly.fieldRegistry');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const xmlUtils = goog.require('Blockly.utils.xml');
|
||||
const {Align} = goog.require('Blockly.Input');
|
||||
@@ -24,9 +25,6 @@ const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const BlockDefinition = Object;
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {FieldDropdown} = goog.require('Blockly.FieldDropdown');
|
||||
const {FieldImage} = goog.require('Blockly.FieldImage');
|
||||
const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
|
||||
const {Mutator} = goog.require('Blockly.Mutator');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
@@ -338,8 +336,11 @@ blocks['text_getSubstring'] = {
|
||||
this.removeInput('TAIL', true);
|
||||
this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']);
|
||||
}
|
||||
const menu = new FieldDropdown(
|
||||
this['WHERE_OPTIONS_' + n],
|
||||
const menu = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: this['WHERE_OPTIONS_' + n],
|
||||
});
|
||||
menu.setValidator(
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
@@ -385,7 +386,11 @@ blocks['text_changeCase'] = {
|
||||
this.setHelpUrl(Msg['TEXT_CHANGECASE_HELPURL']);
|
||||
this.setStyle('text_blocks');
|
||||
this.appendValueInput('TEXT').setCheck('String').appendField(
|
||||
new FieldDropdown(OPERATORS), 'CASE');
|
||||
fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: OPERATORS,
|
||||
}),
|
||||
'CASE');
|
||||
this.setOutput(true, 'String');
|
||||
this.setTooltip(Msg['TEXT_CHANGECASE_TOOLTIP']);
|
||||
},
|
||||
@@ -405,7 +410,11 @@ blocks['text_trim'] = {
|
||||
this.setHelpUrl(Msg['TEXT_TRIM_HELPURL']);
|
||||
this.setStyle('text_blocks');
|
||||
this.appendValueInput('TEXT').setCheck('String').appendField(
|
||||
new FieldDropdown(OPERATORS), 'MODE');
|
||||
fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: OPERATORS,
|
||||
}),
|
||||
'MODE');
|
||||
this.setOutput(true, 'String');
|
||||
this.setTooltip(Msg['TEXT_TRIM_TOOLTIP']);
|
||||
},
|
||||
@@ -486,7 +495,11 @@ blocks['text_prompt_ext'] = {
|
||||
this.setStyle('text_blocks');
|
||||
// Assign 'this' to a variable for use in the closures below.
|
||||
const thisBlock = this;
|
||||
const dropdown = new FieldDropdown(TYPES, function(newOp) {
|
||||
const dropdown = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: TYPES,
|
||||
});
|
||||
dropdown.setValidator(function(newOp) {
|
||||
thisBlock.updateType_(newOp);
|
||||
});
|
||||
this.appendValueInput('TEXT').appendField(dropdown, 'TYPE');
|
||||
@@ -522,13 +535,22 @@ blocks['text_prompt'] = {
|
||||
const thisBlock = this;
|
||||
this.setHelpUrl(Msg['TEXT_PROMPT_HELPURL']);
|
||||
this.setStyle('text_blocks');
|
||||
const dropdown = new FieldDropdown(TYPES, function(newOp) {
|
||||
const dropdown = fieldRegistry.fromJson({
|
||||
type: 'field_dropdown',
|
||||
options: TYPES,
|
||||
});
|
||||
dropdown.setValidator(function(newOp) {
|
||||
thisBlock.updateType_(newOp);
|
||||
});
|
||||
this.appendDummyInput()
|
||||
.appendField(dropdown, 'TYPE')
|
||||
.appendField(this.newQuote_(true))
|
||||
.appendField(new FieldTextInput(''), 'TEXT')
|
||||
.appendField(
|
||||
fieldRegistry.fromJson({
|
||||
type: 'field_input',
|
||||
text: '',
|
||||
}),
|
||||
'TEXT')
|
||||
.appendField(this.newQuote_(false));
|
||||
this.setOutput(true, 'String');
|
||||
this.setTooltip(function() {
|
||||
@@ -696,9 +718,13 @@ const QUOTE_IMAGE_MIXIN = {
|
||||
const isLeft = this.RTL ? !open : open;
|
||||
const dataUri =
|
||||
isLeft ? this.QUOTE_IMAGE_LEFT_DATAURI : this.QUOTE_IMAGE_RIGHT_DATAURI;
|
||||
return new FieldImage(
|
||||
dataUri, this.QUOTE_IMAGE_WIDTH, this.QUOTE_IMAGE_HEIGHT,
|
||||
isLeft ? '\u201C' : '\u201D');
|
||||
return fieldRegistry.fromJson({
|
||||
type: 'field_image',
|
||||
src: dataUri,
|
||||
width: this.QUOTE_IMAGE_WIDTH,
|
||||
height: this.QUOTE_IMAGE_HEIGHT,
|
||||
alt: isLeft ? '\u201C' : '\u201D',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2006 The Closure Library Authors. 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.
|
||||
|
||||
|
||||
"""Calculates JavaScript dependencies without requiring Google's build system.
|
||||
|
||||
It iterates over a number of search paths and builds a dependency tree. With
|
||||
the inputs provided, it walks the dependency tree and outputs all the files
|
||||
required for compilation.
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
|
||||
req_regex = re.compile(_BASE_REGEX_STRING % 'require')
|
||||
prov_regex = re.compile(_BASE_REGEX_STRING % 'provide')
|
||||
ns_regex = re.compile('^ns:((\w+\.)*(\w+))$')
|
||||
|
||||
|
||||
def IsValidFile(ref):
|
||||
"""Returns true if the provided reference is a file and exists."""
|
||||
return os.path.isfile(ref)
|
||||
|
||||
|
||||
def IsJsFile(ref):
|
||||
"""Returns true if the provided reference is a JavaScript file."""
|
||||
return ref.endswith('.js')
|
||||
|
||||
|
||||
def IsNamespace(ref):
|
||||
"""Returns true if the provided reference is a namespace."""
|
||||
return re.match(ns_regex, ref) is not None
|
||||
|
||||
|
||||
def IsDirectory(ref):
|
||||
"""Returns true if the provided reference is a directory."""
|
||||
return os.path.isdir(ref)
|
||||
|
||||
|
||||
def ExpandDirectories(refs):
|
||||
"""Expands any directory references into inputs.
|
||||
|
||||
Description:
|
||||
Looks for any directories in the provided references. Found directories
|
||||
are recursively searched for .js files, which are then added to the result
|
||||
list.
|
||||
|
||||
Args:
|
||||
refs: a list of references such as files, directories, and namespaces
|
||||
|
||||
Returns:
|
||||
A list of references with directories removed and replaced by any
|
||||
.js files that are found in them. Also, the paths will be normalized.
|
||||
"""
|
||||
result = []
|
||||
for ref in refs:
|
||||
if IsDirectory(ref):
|
||||
# Disable 'Unused variable' for subdirs
|
||||
# pylint: disable=unused-variable
|
||||
for (directory, subdirs, filenames) in os.walk(ref):
|
||||
for filename in filenames:
|
||||
if IsJsFile(filename):
|
||||
result.append(os.path.join(directory, filename))
|
||||
else:
|
||||
result.append(ref)
|
||||
return map(os.path.normpath, result)
|
||||
|
||||
|
||||
class DependencyInfo(object):
|
||||
"""Represents a dependency that is used to build and walk a tree."""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.provides = []
|
||||
self.requires = []
|
||||
|
||||
def __str__(self):
|
||||
return '%s Provides: %s Requires: %s' % (self.filename,
|
||||
repr(self.provides),
|
||||
repr(self.requires))
|
||||
|
||||
|
||||
def BuildDependenciesFromFiles(files):
|
||||
"""Build a list of dependencies from a list of files.
|
||||
|
||||
Description:
|
||||
Takes a list of files, extracts their provides and requires, and builds
|
||||
out a list of dependency objects.
|
||||
|
||||
Args:
|
||||
files: a list of files to be parsed for goog.provides and goog.requires.
|
||||
|
||||
Returns:
|
||||
A list of dependency objects, one for each file in the files argument.
|
||||
"""
|
||||
result = []
|
||||
filenames = set()
|
||||
for filename in files:
|
||||
if filename in filenames:
|
||||
continue
|
||||
|
||||
# Python 3 requires the file encoding to be specified
|
||||
if (sys.version_info[0] < 3):
|
||||
file_handle = open(filename, 'r')
|
||||
else:
|
||||
file_handle = open(filename, 'r', encoding='utf8')
|
||||
|
||||
try:
|
||||
dep = CreateDependencyInfo(filename, file_handle)
|
||||
result.append(dep)
|
||||
finally:
|
||||
file_handle.close()
|
||||
|
||||
filenames.add(filename)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def CreateDependencyInfo(filename, source):
|
||||
"""Create dependency info.
|
||||
|
||||
Args:
|
||||
filename: Filename for source.
|
||||
source: File-like object containing source.
|
||||
|
||||
Returns:
|
||||
A DependencyInfo object with provides and requires filled.
|
||||
"""
|
||||
dep = DependencyInfo(filename)
|
||||
for line in source:
|
||||
if re.match(req_regex, line):
|
||||
dep.requires.append(re.search(req_regex, line).group(1))
|
||||
if re.match(prov_regex, line):
|
||||
dep.provides.append(re.search(prov_regex, line).group(1))
|
||||
return dep
|
||||
|
||||
|
||||
def BuildDependencyHashFromDependencies(deps):
|
||||
"""Builds a hash for searching dependencies by the namespaces they provide.
|
||||
|
||||
Description:
|
||||
Dependency objects can provide multiple namespaces. This method enumerates
|
||||
the provides of each dependency and adds them to a hash that can be used
|
||||
to easily resolve a given dependency by a namespace it provides.
|
||||
|
||||
Args:
|
||||
deps: a list of dependency objects used to build the hash.
|
||||
|
||||
Raises:
|
||||
Exception: If a multiple files try to provide the same namepace.
|
||||
|
||||
Returns:
|
||||
A hash table { namespace: dependency } that can be used to resolve a
|
||||
dependency by a namespace it provides.
|
||||
"""
|
||||
dep_hash = {}
|
||||
for dep in deps:
|
||||
for provide in dep.provides:
|
||||
if provide in dep_hash:
|
||||
raise Exception('Duplicate provide (%s) in (%s, %s)' % (
|
||||
provide,
|
||||
dep_hash[provide].filename,
|
||||
dep.filename))
|
||||
dep_hash[provide] = dep
|
||||
return dep_hash
|
||||
|
||||
|
||||
def CalculateDependencies(paths, inputs):
|
||||
"""Calculates the dependencies for given inputs.
|
||||
|
||||
Description:
|
||||
This method takes a list of paths (files, directories) and builds a
|
||||
searchable data structure based on the namespaces that each .js file
|
||||
provides. It then parses through each input, resolving dependencies
|
||||
against this data structure. The final output is a list of files,
|
||||
including the inputs, that represent all of the code that is needed to
|
||||
compile the given inputs.
|
||||
|
||||
Args:
|
||||
paths: the references (files, directories) that are used to build the
|
||||
dependency hash.
|
||||
inputs: the inputs (files, directories, namespaces) that have dependencies
|
||||
that need to be calculated.
|
||||
|
||||
Raises:
|
||||
Exception: if a provided input is invalid.
|
||||
|
||||
Returns:
|
||||
A list of all files, including inputs, that are needed to compile the given
|
||||
inputs.
|
||||
"""
|
||||
deps = BuildDependenciesFromFiles(paths + inputs)
|
||||
search_hash = BuildDependencyHashFromDependencies(deps)
|
||||
result_list = []
|
||||
seen_list = []
|
||||
for input_file in inputs:
|
||||
if IsNamespace(input_file):
|
||||
namespace = re.search(ns_regex, input_file).group(1)
|
||||
if namespace not in search_hash:
|
||||
raise Exception('Invalid namespace (%s)' % namespace)
|
||||
input_file = search_hash[namespace].filename
|
||||
if not IsValidFile(input_file) or not IsJsFile(input_file):
|
||||
raise Exception('Invalid file (%s)' % input_file)
|
||||
seen_list.append(input_file)
|
||||
file_handle = open(input_file, 'r')
|
||||
try:
|
||||
for line in file_handle:
|
||||
if re.match(req_regex, line):
|
||||
require = re.search(req_regex, line).group(1)
|
||||
ResolveDependencies(require, search_hash, result_list, seen_list)
|
||||
finally:
|
||||
file_handle.close()
|
||||
result_list.append(input_file)
|
||||
|
||||
return result_list
|
||||
|
||||
|
||||
def FindClosureBasePath(paths):
|
||||
"""Given a list of file paths, return Closure base.js path, if any.
|
||||
|
||||
Args:
|
||||
paths: A list of paths.
|
||||
|
||||
Returns:
|
||||
The path to Closure's base.js file including filename, if found.
|
||||
"""
|
||||
|
||||
for path in paths:
|
||||
pathname, filename = os.path.split(path)
|
||||
|
||||
if filename == 'base.js':
|
||||
f = open(path)
|
||||
|
||||
is_base = False
|
||||
|
||||
# Sanity check that this is the Closure base file. Check that this
|
||||
# is where goog is defined. This is determined by the @provideGoog
|
||||
# flag.
|
||||
for line in f:
|
||||
if '@provideGoog' in line:
|
||||
is_base = True
|
||||
break
|
||||
|
||||
f.close()
|
||||
|
||||
if is_base:
|
||||
return path
|
||||
|
||||
def ResolveDependencies(require, search_hash, result_list, seen_list):
|
||||
"""Takes a given requirement and resolves all of the dependencies for it.
|
||||
|
||||
Description:
|
||||
A given requirement may require other dependencies. This method
|
||||
recursively resolves all dependencies for the given requirement.
|
||||
|
||||
Raises:
|
||||
Exception: when require does not exist in the search_hash.
|
||||
|
||||
Args:
|
||||
require: the namespace to resolve dependencies for.
|
||||
search_hash: the data structure used for resolving dependencies.
|
||||
result_list: a list of filenames that have been calculated as dependencies.
|
||||
This variable is the output for this function.
|
||||
seen_list: a list of filenames that have been 'seen'. This is required
|
||||
for the dependency->dependent ordering.
|
||||
"""
|
||||
if require not in search_hash:
|
||||
raise Exception('Missing provider for (%s)' % require)
|
||||
|
||||
dep = search_hash[require]
|
||||
if not dep.filename in seen_list:
|
||||
seen_list.append(dep.filename)
|
||||
for sub_require in dep.requires:
|
||||
ResolveDependencies(sub_require, search_hash, result_list, seen_list)
|
||||
result_list.append(dep.filename)
|
||||
|
||||
|
||||
def GetDepsLine(dep, base_path):
|
||||
"""Returns a JS string for a dependency statement in the deps.js file.
|
||||
|
||||
Args:
|
||||
dep: The dependency that we're printing.
|
||||
base_path: The path to Closure's base.js including filename.
|
||||
"""
|
||||
return 'goog.addDependency("%s", %s, %s);' % (
|
||||
GetRelpath(dep.filename, base_path), dep.provides, dep.requires)
|
||||
|
||||
|
||||
def GetRelpath(path, start):
|
||||
"""Return a relative path to |path| from |start|."""
|
||||
# NOTE: Python 2.6 provides os.path.relpath, which has almost the same
|
||||
# functionality as this function. Since we want to support 2.4, we have
|
||||
# to implement it manually. :(
|
||||
path_list = os.path.abspath(os.path.normpath(path)).split(os.sep)
|
||||
start_list = os.path.abspath(
|
||||
os.path.normpath(os.path.dirname(start))).split(os.sep)
|
||||
|
||||
common_prefix_count = 0
|
||||
for i in range(0, min(len(path_list), len(start_list))):
|
||||
if path_list[i] != start_list[i]:
|
||||
break
|
||||
common_prefix_count += 1
|
||||
|
||||
# Always use forward slashes, because this will get expanded to a url,
|
||||
# not a file path.
|
||||
return '/'.join(['..'] * (len(start_list) - common_prefix_count) +
|
||||
path_list[common_prefix_count:])
|
||||
@@ -617,8 +617,10 @@ goog.declareModuleId = function(namespace) {
|
||||
'within an ES6 module');
|
||||
}
|
||||
if (goog.moduleLoaderState_ && goog.moduleLoaderState_.moduleName) {
|
||||
// throw new Error(
|
||||
// 'goog.declareModuleId may only be called once per module.');
|
||||
throw new Error(
|
||||
'goog.declareModuleId may only be called once per module.' +
|
||||
'This error can also be caused by circular imports, which ' +
|
||||
'are not supported by debug module loader.');
|
||||
}
|
||||
if (namespace in goog.loadedModules_) {
|
||||
throw new Error(
|
||||
@@ -725,7 +727,7 @@ if (!COMPILED) {
|
||||
|
||||
// NOTE: We add goog.module as an implicit namespace as goog.module is defined
|
||||
// here and because the existing module package has not been moved yet out of
|
||||
// the goog.module namespace. This satisifies both the debug loader and
|
||||
// the goog.module namespace. This satisfies both the debug loader and
|
||||
// ahead-of-time dependency management.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line
|
||||
type AnyDuringMigration = any;
|
||||
|
||||
300
core/block.ts
300
core/block.ts
@@ -35,7 +35,6 @@ import {Align, Input} from './input.js';
|
||||
import {inputTypes} from './input_types.js';
|
||||
import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
|
||||
import type {IDeletable} from './interfaces/i_deletable.js';
|
||||
import {ASTNode} from './keyboard_nav/ast_node.js';
|
||||
import type {Mutator} from './mutator.js';
|
||||
import * as Tooltip from './tooltip.js';
|
||||
import * as arrayUtils from './utils/array.js';
|
||||
@@ -50,8 +49,6 @@ import type {Workspace} from './workspace.js';
|
||||
/**
|
||||
* Class for one block.
|
||||
* Not normally called directly, workspace.newBlock() is preferred.
|
||||
*
|
||||
* @alias Blockly.Block
|
||||
*/
|
||||
export class Block implements IASTNodeLocation, IDeletable {
|
||||
/**
|
||||
@@ -59,7 +56,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* changes. This is usually only called from the constructor, the block type
|
||||
* initializer function, or an extension initializer function.
|
||||
*/
|
||||
onchange?: ((p1: Abstract) => AnyDuringMigration)|null;
|
||||
onchange?: ((p1: Abstract) => void)|null;
|
||||
|
||||
/** The language-neutral ID given to the collapsed input. */
|
||||
static readonly COLLAPSED_INPUT_NAME: string = constants.COLLAPSED_INPUT_NAME;
|
||||
@@ -93,39 +90,38 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
protected styleName_ = '';
|
||||
|
||||
/** An optional method called during initialization. */
|
||||
init?: (() => AnyDuringMigration)|null = undefined;
|
||||
init?: (() => void);
|
||||
|
||||
/** An optional method called during disposal. */
|
||||
destroy?: (() => void) = undefined;
|
||||
destroy?: (() => void);
|
||||
|
||||
/**
|
||||
* An optional serialization method for defining how to serialize the
|
||||
* mutation state to XML. This must be coupled with defining
|
||||
* `domToMutation`.
|
||||
*/
|
||||
mutationToDom?: ((...p1: AnyDuringMigration[]) => Element)|null = undefined;
|
||||
mutationToDom?: (...p1: AnyDuringMigration[]) => Element;
|
||||
|
||||
/**
|
||||
* An optional deserialization method for defining how to deserialize the
|
||||
* mutation state from XML. This must be coupled with defining
|
||||
* `mutationToDom`.
|
||||
*/
|
||||
domToMutation?: ((p1: Element) => AnyDuringMigration)|null = undefined;
|
||||
domToMutation?: (p1: Element) => void;
|
||||
|
||||
/**
|
||||
* An optional serialization method for defining how to serialize the
|
||||
* block's extra state (eg mutation state) to something JSON compatible.
|
||||
* This must be coupled with defining `loadExtraState`.
|
||||
*/
|
||||
saveExtraState?: (() => AnyDuringMigration)|null = undefined;
|
||||
saveExtraState?: () => AnyDuringMigration;
|
||||
|
||||
/**
|
||||
* An optional serialization method for defining how to deserialize the
|
||||
* block's extra state (eg mutation state) from something JSON compatible.
|
||||
* This must be coupled with defining `saveExtraState`.
|
||||
*/
|
||||
loadExtraState?:
|
||||
((p1: AnyDuringMigration) => AnyDuringMigration)|null = undefined;
|
||||
loadExtraState?: (p1: AnyDuringMigration) => void;
|
||||
|
||||
/**
|
||||
* An optional property for suppressing adding STATEMENT_PREFIX and
|
||||
@@ -139,31 +135,25 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* shown to the user, but are declared as global variables in the generated
|
||||
* code.
|
||||
*/
|
||||
getDeveloperVariables?: (() => string[]) = undefined;
|
||||
getDeveloperVariables?: () => string[];
|
||||
|
||||
/**
|
||||
* An optional function that reconfigures the block based on the contents of
|
||||
* the mutator dialog.
|
||||
*/
|
||||
compose?: ((p1: Block) => void) = undefined;
|
||||
compose?: (p1: Block) => void;
|
||||
|
||||
/**
|
||||
* An optional function that populates the mutator's dialog with
|
||||
* this block's components.
|
||||
*/
|
||||
decompose?: ((p1: Workspace) => Block) = undefined;
|
||||
decompose?: (p1: Workspace) => Block;
|
||||
id: string;
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Connection'.
|
||||
outputConnection: Connection = null as AnyDuringMigration;
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Connection'.
|
||||
nextConnection: Connection = null as AnyDuringMigration;
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Connection'.
|
||||
previousConnection: Connection = null as AnyDuringMigration;
|
||||
outputConnection: Connection|null = null;
|
||||
nextConnection: Connection|null = null;
|
||||
previousConnection: Connection|null = null;
|
||||
inputList: Input[] = [];
|
||||
inputsInline?: boolean = undefined;
|
||||
inputsInline?: boolean;
|
||||
private disabled = false;
|
||||
tooltip: Tooltip.TipInfo = '';
|
||||
contextMenu = true;
|
||||
@@ -205,19 +195,17 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
protected isInsertionMarker_ = false;
|
||||
|
||||
/** Name of the type of hat. */
|
||||
hat?: string = undefined;
|
||||
hat?: string;
|
||||
|
||||
rendered: boolean|null = null;
|
||||
|
||||
/**
|
||||
* String for block help, or function that returns a URL. Null for no help.
|
||||
*/
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type 'string
|
||||
// | Function'.
|
||||
helpUrl: string|Function = null as AnyDuringMigration;
|
||||
helpUrl: string|Function|null = null;
|
||||
|
||||
/** A bound callback function to use when the parent workspace changes. */
|
||||
private onchangeWrapper_: ((p1: Abstract) => AnyDuringMigration)|null = null;
|
||||
private onchangeWrapper_: ((p1: Abstract) => void)|null = null;
|
||||
|
||||
/**
|
||||
* A count of statement inputs on the block.
|
||||
@@ -301,9 +289,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(this));
|
||||
}
|
||||
} finally {
|
||||
if (!existingGroup) {
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
eventUtils.setGroup(existingGroup);
|
||||
// In case init threw, recordUndo flag should still be reset.
|
||||
eventUtils.setRecordUndo(initialUndoFlag);
|
||||
}
|
||||
@@ -324,44 +310,45 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
dispose(healStack: boolean) {
|
||||
if (this.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
// Terminate onchange event calls.
|
||||
if (this.isDeadOrDying()) return;
|
||||
|
||||
// Dispose of this change listener before unplugging.
|
||||
// Technically not necessary due to the event firing delay.
|
||||
// But future-proofing.
|
||||
if (this.onchangeWrapper_) {
|
||||
this.workspace.removeChangeListener(this.onchangeWrapper_);
|
||||
}
|
||||
|
||||
this.unplug(healStack);
|
||||
if (eventUtils.isEnabled()) {
|
||||
// Constructing the delete event is costly. Only perform if necessary.
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_DELETE))(this));
|
||||
}
|
||||
eventUtils.disable();
|
||||
this.workspace.removeTopBlock(this);
|
||||
this.disposeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of this block without doing things required by the top block.
|
||||
* E.g. does not fire events, unplug the block, etc.
|
||||
*/
|
||||
protected disposeInternal() {
|
||||
if (this.isDeadOrDying()) return;
|
||||
|
||||
if (this.onchangeWrapper_) {
|
||||
this.workspace.removeChangeListener(this.onchangeWrapper_);
|
||||
}
|
||||
|
||||
eventUtils.disable();
|
||||
try {
|
||||
// This block is now at the top of the workspace.
|
||||
// Remove this block from the workspace's list of top-most blocks.
|
||||
this.workspace.removeTopBlock(this);
|
||||
this.workspace.removeTypedBlock(this);
|
||||
// Remove from block database.
|
||||
this.workspace.removeBlockById(this.id);
|
||||
this.disposing = true;
|
||||
|
||||
// First, dispose of all my children.
|
||||
for (let i = this.childBlocks_.length - 1; i >= 0; i--) {
|
||||
this.childBlocks_[i].dispose(false);
|
||||
}
|
||||
// Then dispose of myself.
|
||||
// Dispose of all inputs and their fields.
|
||||
for (let i = 0, input; input = this.inputList[i]; i++) {
|
||||
input.dispose();
|
||||
}
|
||||
this.childBlocks_.forEach((c) => c.disposeInternal());
|
||||
this.inputList.forEach((i) => i.dispose());
|
||||
this.inputList.length = 0;
|
||||
// Dispose of any remaining connections (next/previous/output).
|
||||
const connections = this.getConnections_(true);
|
||||
for (let i = 0, connection; connection = connections[i]; i++) {
|
||||
connection.dispose();
|
||||
}
|
||||
this.getConnections_(true).forEach((c) => c.dispose());
|
||||
} finally {
|
||||
eventUtils.enable();
|
||||
if (typeof this.destroy === 'function') {
|
||||
@@ -424,7 +411,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
*/
|
||||
private unplugFromRow_(opt_healStack?: boolean) {
|
||||
let parentConnection = null;
|
||||
if (this.outputConnection.isConnected()) {
|
||||
if (this.outputConnection?.isConnected()) {
|
||||
parentConnection = this.outputConnection.targetConnection;
|
||||
// Disconnect from any superior block.
|
||||
this.outputConnection.disconnect();
|
||||
@@ -489,7 +476,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
*/
|
||||
private unplugFromStack_(opt_healStack?: boolean) {
|
||||
let previousTarget = null;
|
||||
if (this.previousConnection.isConnected()) {
|
||||
if (this.previousConnection?.isConnected()) {
|
||||
// Remember the connection that any next statements need to connect to.
|
||||
previousTarget = this.previousConnection.targetConnection;
|
||||
// Detach this block from the parent's tree.
|
||||
@@ -498,7 +485,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
const nextBlock = this.getNextBlock();
|
||||
if (opt_healStack && nextBlock && !nextBlock.isShadow()) {
|
||||
// Disconnect the next statement.
|
||||
const nextTarget = this.nextConnection.targetConnection;
|
||||
const nextTarget = this.nextConnection?.targetConnection ?? null;
|
||||
nextTarget?.disconnect();
|
||||
if (previousTarget &&
|
||||
this.workspace.connectionChecker.canConnect(
|
||||
@@ -797,6 +784,15 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
!this.workspace.options.readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this block's own deletable property is true or false.
|
||||
*
|
||||
* @returns True if the block's deletable property is true, false otherwise.
|
||||
*/
|
||||
isOwnDeletable(): boolean {
|
||||
return this.deletable_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this block is deletable or not.
|
||||
*
|
||||
@@ -810,12 +806,23 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* Get whether this block is movable or not.
|
||||
*
|
||||
* @returns True if movable.
|
||||
* @internal
|
||||
*/
|
||||
isMovable(): boolean {
|
||||
return this.movable_ && !this.isShadow_ && !this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this block's own movable property is true or false.
|
||||
*
|
||||
* @returns True if the block's movable property is true, false otherwise.
|
||||
* @internal
|
||||
*/
|
||||
isOwnMovable(): boolean {
|
||||
return this.movable_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this block is movable or not.
|
||||
*
|
||||
@@ -884,12 +891,22 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* Get whether this block is editable or not.
|
||||
*
|
||||
* @returns True if editable.
|
||||
* @internal
|
||||
*/
|
||||
isEditable(): boolean {
|
||||
return this.editable_ && !this.isDeadOrDying() &&
|
||||
!this.workspace.options.readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this block's own editable property is true or false.
|
||||
*
|
||||
* @returns True if the block's editable property is true, false otherwise.
|
||||
*/
|
||||
isOwnEditable(): boolean {
|
||||
return this.editable_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this block is editable or not.
|
||||
*
|
||||
@@ -1024,7 +1041,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @param onchangeFn The callback to call when the block's workspace changes.
|
||||
* @throws {Error} if onchangeFn is not falsey and not a function.
|
||||
*/
|
||||
setOnChange(onchangeFn: (p1: Abstract) => AnyDuringMigration) {
|
||||
setOnChange(onchangeFn: (p1: Abstract) => void) {
|
||||
if (onchangeFn && typeof onchangeFn !== 'function') {
|
||||
throw Error('onchange must be a function.');
|
||||
}
|
||||
@@ -1066,11 +1083,12 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* @returns List of variable ids.
|
||||
*/
|
||||
getVars(): string[] {
|
||||
const vars = [];
|
||||
const vars: string[] = [];
|
||||
for (let i = 0, input; input = this.inputList[i]; i++) {
|
||||
for (let j = 0, field; field = input.fieldRow[j]; j++) {
|
||||
if (field.referencesVariables()) {
|
||||
vars.push(field.getValue());
|
||||
// NOTE: This only applies to `FieldVariable`, a `Field<string>`
|
||||
vars.push(field.getValue() as string);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1190,9 +1208,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
'connection.');
|
||||
}
|
||||
this.previousConnection.dispose();
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Connection'.
|
||||
this.previousConnection = null as AnyDuringMigration;
|
||||
this.previousConnection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1222,9 +1238,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
'connection.');
|
||||
}
|
||||
this.nextConnection.dispose();
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Connection'.
|
||||
this.nextConnection = null as AnyDuringMigration;
|
||||
this.nextConnection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1253,9 +1267,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
'Must disconnect output value before removing connection.');
|
||||
}
|
||||
this.outputConnection.dispose();
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'Connection'.
|
||||
this.outputConnection = null as AnyDuringMigration;
|
||||
this.outputConnection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1386,21 +1398,52 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
* Create a human-readable text representation of this block and any children.
|
||||
*
|
||||
* @param opt_maxLength Truncate the string to this length.
|
||||
* @param opt_emptyToken The placeholder string used to denote an empty field.
|
||||
* @param opt_emptyToken The placeholder string used to denote an empty input.
|
||||
* If not specified, '?' is used.
|
||||
* @returns Text of block.
|
||||
*/
|
||||
toString(opt_maxLength?: number, opt_emptyToken?: string): string {
|
||||
const tokens = this.toTokens(opt_emptyToken);
|
||||
|
||||
// Run through our tokens array and simplify expression to remove
|
||||
// parentheses around single field blocks.
|
||||
// E.g. ['repeat', '(', '10', ')', 'times', 'do', '?']
|
||||
for (let i = 2; i < tokens.length; i++) {
|
||||
if (tokens[i - 2] === '(' && tokens[i] === ')') {
|
||||
tokens[i - 2] = tokens[i - 1];
|
||||
tokens.splice(i - 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the text array, removing the spaces around added parentheses.
|
||||
let prev = '';
|
||||
let text: string = tokens.reduce((acc, curr) => {
|
||||
const val = acc + ((prev === '(' || curr === ')') ? '' : ' ') + curr;
|
||||
prev = curr[curr.length - 1];
|
||||
return val;
|
||||
}, '');
|
||||
|
||||
text = text.trim() || '???';
|
||||
if (opt_maxLength) {
|
||||
// TODO: Improve truncation so that text from this block is given
|
||||
// priority. E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not
|
||||
// "1+2+3+4+5...". E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
|
||||
if (text.length > opt_maxLength) {
|
||||
text = text.substring(0, opt_maxLength - 3) + '...';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this block into string tokens.
|
||||
*
|
||||
* @param emptyToken The token to use in place of an empty input.
|
||||
* Defaults to '?'.
|
||||
* @returns The array of string tokens representing this block.
|
||||
*/
|
||||
private toTokens(emptyToken = '?'): string[] {
|
||||
const tokens = [];
|
||||
const emptyFieldPlaceholder = opt_emptyToken || '?';
|
||||
|
||||
// Temporarily set flag to navigate to all fields.
|
||||
const prevNavigateFields = ASTNode.NAVIGATE_ALL_FIELDS;
|
||||
ASTNode.NAVIGATE_ALL_FIELDS = true;
|
||||
|
||||
let node = ASTNode.createBlockNode(this);
|
||||
const rootNode = node;
|
||||
|
||||
/**
|
||||
* Whether or not to add parentheses around an input.
|
||||
*
|
||||
@@ -1416,84 +1459,26 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
(checks.indexOf('Boolean') !== -1 || checks.indexOf('Number') !== -1);
|
||||
}
|
||||
|
||||
/** Check that we haven't circled back to the original root node. */
|
||||
function checkRoot() {
|
||||
if (node && node.getType() === rootNode?.getType() &&
|
||||
node.getLocation() === rootNode?.getLocation()) {
|
||||
node = null;
|
||||
for (const input of this.inputList) {
|
||||
if (input.name == constants.COLLAPSED_INPUT_NAME) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse the AST building up our text string.
|
||||
while (node) {
|
||||
switch (node.getType()) {
|
||||
case ASTNode.types.INPUT: {
|
||||
const connection = node.getLocation() as Connection;
|
||||
if (!node.in()) {
|
||||
tokens.push(emptyFieldPlaceholder);
|
||||
} else if (shouldAddParentheses(connection)) {
|
||||
tokens.push('(');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ASTNode.types.FIELD: {
|
||||
const field = node.getLocation() as Field;
|
||||
if (field.name !== constants.COLLAPSED_FIELD_NAME) {
|
||||
tokens.push(field.getText());
|
||||
}
|
||||
break;
|
||||
}
|
||||
for (const field of input.fieldRow) {
|
||||
tokens.push(field.getText());
|
||||
}
|
||||
|
||||
const current = node;
|
||||
node = current.in() || current.next();
|
||||
if (!node) {
|
||||
// Can't go in or next, keep going out until we can go next.
|
||||
node = current.out();
|
||||
checkRoot();
|
||||
while (node && !node.next()) {
|
||||
node = node.out();
|
||||
checkRoot();
|
||||
// If we hit an input on the way up, possibly close out parentheses.
|
||||
if (node && node.getType() === ASTNode.types.INPUT &&
|
||||
shouldAddParentheses(node.getLocation() as Connection)) {
|
||||
tokens.push(')');
|
||||
}
|
||||
}
|
||||
if (node) {
|
||||
node = node.next();
|
||||
if (input.connection) {
|
||||
const child = input.connection.targetBlock();
|
||||
if (child) {
|
||||
const shouldAddParens = shouldAddParentheses(input.connection);
|
||||
if (shouldAddParens) tokens.push('(');
|
||||
tokens.push(...child.toTokens(emptyToken));
|
||||
if (shouldAddParens) tokens.push(')');
|
||||
} else {
|
||||
tokens.push(emptyToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore state of NAVIGATE_ALL_FIELDS.
|
||||
ASTNode.NAVIGATE_ALL_FIELDS = prevNavigateFields;
|
||||
|
||||
// Run through our text array and simplify expression to remove parentheses
|
||||
// around single field blocks.
|
||||
// E.g. ['repeat', '(', '10', ')', 'times', 'do', '?']
|
||||
for (let i = 2; i < tokens.length; i++) {
|
||||
if (tokens[i - 2] === '(' && tokens[i] === ')') {
|
||||
tokens[i - 2] = tokens[i - 1];
|
||||
tokens.splice(i - 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Join the text array, removing spaces around added parentheses.
|
||||
let text: string = tokens.reduce(function(acc, value) {
|
||||
return acc + (acc.substr(-1) === '(' || value === ')' ? '' : ' ') + value;
|
||||
}, '');
|
||||
|
||||
text = text.trim() || '???';
|
||||
if (opt_maxLength) {
|
||||
// TODO: Improve truncation so that text from this block is given
|
||||
// priority. E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not
|
||||
// "1+2+3+4+5...". E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
|
||||
if (text.length > opt_maxLength) {
|
||||
text = text.substring(0, opt_maxLength - 3) + '...';
|
||||
}
|
||||
}
|
||||
return text;
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1933,10 +1918,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
if (type === inputTypes.STATEMENT) {
|
||||
this.statementInputCount++;
|
||||
}
|
||||
// AnyDuringMigration because: Argument of type 'Connection | null' is not
|
||||
// assignable to parameter of type 'Connection'.
|
||||
const input =
|
||||
new Input(type, name, this, (connection as AnyDuringMigration));
|
||||
const input = new Input(type, name, this, connection);
|
||||
// Append input to list.
|
||||
this.inputList.push(input);
|
||||
return input;
|
||||
@@ -2080,9 +2062,7 @@ export class Block implements IASTNodeLocation, IDeletable {
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
|
||||
this, 'comment', null, this.commentModel.text, text));
|
||||
this.commentModel.text = text;
|
||||
// AnyDuringMigration because: Type 'string | null' is not assignable to
|
||||
// type 'string | Comment'.
|
||||
this.comment = text as AnyDuringMigration; // For backwards compatibility.
|
||||
this.comment = text; // For backwards compatibility.
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Methods animating a block on connection and disconnection.
|
||||
*
|
||||
* @namespace Blockly.blockAnimations
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.blockAnimations');
|
||||
|
||||
@@ -28,18 +23,20 @@ interface CloneRect {
|
||||
/** PID of disconnect UI animation. There can only be one at a time. */
|
||||
let disconnectPid: ReturnType<typeof setTimeout>|null = null;
|
||||
|
||||
/** SVG group of wobbling block. There can only be one at a time. */
|
||||
let disconnectGroup: SVGElement|null = null;
|
||||
/** The wobbling block. There can only be one at a time. */
|
||||
let wobblingBlock: BlockSvg|null = null;
|
||||
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, animation) when disposing of a block.
|
||||
*
|
||||
* @param block The block being disposed of.
|
||||
* @alias Blockly.blockAnimations.disposeUiEffect
|
||||
* @internal
|
||||
*/
|
||||
export function disposeUiEffect(block: BlockSvg) {
|
||||
// Disposing is going to take so long the animation won't play anyway.
|
||||
if (block.getDescendants(false).length > 100) return;
|
||||
|
||||
const workspace = block.workspace;
|
||||
const svgGroup = block.getSvgRoot();
|
||||
workspace.getAudioManager().play('delete');
|
||||
@@ -88,7 +85,6 @@ function disposeUiStep(
|
||||
* Play some UI effects (sound, ripple) after a connection has been established.
|
||||
*
|
||||
* @param block The block being connected.
|
||||
* @alias Blockly.blockAnimations.connectionUiEffect
|
||||
* @internal
|
||||
*/
|
||||
export function connectionUiEffect(block: BlockSvg) {
|
||||
@@ -135,8 +131,8 @@ function connectionUiStep(ripple: SVGElement, start: Date, scale: number) {
|
||||
if (percent > 1) {
|
||||
dom.removeNode(ripple);
|
||||
} else {
|
||||
ripple.setAttribute('r', (percent * 25 * scale).toString());
|
||||
ripple.style.opacity = (1 - percent).toString();
|
||||
ripple.setAttribute('r', String(percent * 25 * scale));
|
||||
ripple.style.opacity = String(1 - percent);
|
||||
disconnectPid = setTimeout(connectionUiStep, 10, ripple, start, scale);
|
||||
}
|
||||
}
|
||||
@@ -145,7 +141,6 @@ function connectionUiStep(ripple: SVGElement, start: Date, scale: number) {
|
||||
* Play some UI effects (sound, animation) when disconnecting a block.
|
||||
*
|
||||
* @param block The block being disconnected.
|
||||
* @alias Blockly.blockAnimations.disconnectUiEffect
|
||||
* @internal
|
||||
*/
|
||||
export function disconnectUiEffect(block: BlockSvg) {
|
||||
@@ -163,18 +158,18 @@ export function disconnectUiEffect(block: BlockSvg) {
|
||||
magnitude *= -1;
|
||||
}
|
||||
// Start the animation.
|
||||
disconnectGroup = block.getSvgRoot();
|
||||
disconnectUiStep(disconnectGroup, magnitude, new Date());
|
||||
wobblingBlock = block;
|
||||
disconnectUiStep(block, magnitude, new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate a brief wiggle of a disconnected block.
|
||||
*
|
||||
* @param group SVG element to animate.
|
||||
* @param block Block to animate.
|
||||
* @param magnitude Maximum degrees skew (reversed for RTL).
|
||||
* @param start Date of animation's start.
|
||||
*/
|
||||
function disconnectUiStep(group: SVGElement, magnitude: number, start: Date) {
|
||||
function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) {
|
||||
const DURATION = 200; // Milliseconds.
|
||||
const WIGGLES = 3; // Half oscillations.
|
||||
|
||||
@@ -186,29 +181,25 @@ function disconnectUiStep(group: SVGElement, magnitude: number, start: Date) {
|
||||
const val = Math.round(
|
||||
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
|
||||
skew = `skewX(${val})`;
|
||||
disconnectPid = setTimeout(disconnectUiStep, 10, group, magnitude, start);
|
||||
disconnectPid = setTimeout(disconnectUiStep, 10, block, magnitude, start);
|
||||
}
|
||||
(group as AnyDuringMigration).skew_ = skew;
|
||||
group.setAttribute(
|
||||
'transform',
|
||||
(group as AnyDuringMigration).translate_ +
|
||||
(group as AnyDuringMigration).skew_);
|
||||
|
||||
block.getSvgRoot().setAttribute(
|
||||
'transform', `${block.getTranslation()} ${skew}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the disconnect UI animation immediately.
|
||||
*
|
||||
* @alias Blockly.blockAnimations.disconnectUiStop
|
||||
* @internal
|
||||
*/
|
||||
export function disconnectUiStop() {
|
||||
if (disconnectGroup) {
|
||||
if (disconnectPid) {
|
||||
clearTimeout(disconnectPid);
|
||||
}
|
||||
const group = disconnectGroup;
|
||||
(group as AnyDuringMigration).skew_ = '';
|
||||
group.setAttribute('transform', (group as AnyDuringMigration).translate_);
|
||||
disconnectGroup = null;
|
||||
if (!wobblingBlock) return;
|
||||
if (disconnectPid) {
|
||||
clearTimeout(disconnectPid);
|
||||
disconnectPid = null;
|
||||
}
|
||||
wobblingBlock.getSvgRoot().setAttribute(
|
||||
'transform', wobblingBlock.getTranslation());
|
||||
wobblingBlock = null;
|
||||
}
|
||||
|
||||
@@ -34,8 +34,6 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
/**
|
||||
* Class for a block dragger. It moves blocks around the workspace when they
|
||||
* are being dragged by a mouse or touch.
|
||||
*
|
||||
* @alias Blockly.BlockDragger
|
||||
*/
|
||||
export class BlockDragger implements IBlockDragger {
|
||||
/** The top block in the stack that is being dragged. */
|
||||
@@ -241,7 +239,7 @@ export class BlockDragger implements IBlockDragger {
|
||||
// These are expensive and don't need to be done if we're deleting.
|
||||
this.draggingBlock_.setDragging(false);
|
||||
if (delta) { // !preventMove
|
||||
this.updateBlockAfterMove_(delta);
|
||||
this.updateBlockAfterMove_();
|
||||
} else {
|
||||
// Blocks dragged directly from a flyout may need to be bumped into
|
||||
// bounds.
|
||||
@@ -294,18 +292,14 @@ export class BlockDragger implements IBlockDragger {
|
||||
|
||||
/**
|
||||
* Updates the necessary information to place a block at a certain location.
|
||||
*
|
||||
* @param delta The change in location from where the block started the drag
|
||||
* to where it ended the drag.
|
||||
*/
|
||||
protected updateBlockAfterMove_(delta: Coordinate) {
|
||||
this.draggingBlock_.moveConnections(delta.x, delta.y);
|
||||
protected updateBlockAfterMove_() {
|
||||
this.fireMoveEvent_();
|
||||
if (this.draggedConnectionManager_.wouldConnectBlock()) {
|
||||
// Applying connections also rerenders the relevant blocks.
|
||||
this.draggedConnectionManager_.applyConnections();
|
||||
} else {
|
||||
this.draggingBlock_.render();
|
||||
this.draggingBlock_.queueRender();
|
||||
}
|
||||
this.draggingBlock_.scheduleSnapAndBump();
|
||||
}
|
||||
|
||||
@@ -56,13 +56,12 @@ import * as svgMath from './utils/svg_math.js';
|
||||
import {Warning} from './warning.js';
|
||||
import type {Workspace} from './workspace.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import {queueRender} from './render_management.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block's SVG representation.
|
||||
* Not normally called directly, workspace.newBlock() is preferred.
|
||||
*
|
||||
* @alias Blockly.BlockSvg
|
||||
*/
|
||||
export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
IBoundedElement, ICopyable,
|
||||
@@ -79,12 +78,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* the block.
|
||||
*/
|
||||
static readonly COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';
|
||||
override decompose?: ((p1: Workspace) => BlockSvg);
|
||||
override decompose?: (p1: Workspace) => BlockSvg;
|
||||
// override compose?: ((p1: BlockSvg) => void)|null;
|
||||
saveConnections?: ((p1: BlockSvg) => AnyDuringMigration);
|
||||
saveConnections?: (p1: BlockSvg) => void;
|
||||
customContextMenu?:
|
||||
((p1: Array<ContextMenuOption|LegacyContextMenuOption>) =>
|
||||
AnyDuringMigration)|null;
|
||||
(p1: Array<ContextMenuOption|LegacyContextMenuOption>) => void;
|
||||
|
||||
/**
|
||||
* An property used internally to reference the block's rendering debugger.
|
||||
@@ -125,6 +123,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
/** @internal */
|
||||
pathObject: IPathObject;
|
||||
override rendered = false;
|
||||
private visuallyDisabled = false;
|
||||
|
||||
/**
|
||||
* Is this block currently rendering? Used to stop recursive render calls
|
||||
@@ -144,6 +143,24 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
override previousConnection!: RenderedConnection;
|
||||
private readonly useDragSurface_: boolean;
|
||||
|
||||
private translation = '';
|
||||
|
||||
/**
|
||||
* The ID of the setTimeout callback for bumping neighbours, or 0 if no bump
|
||||
* is currently scheduled.
|
||||
*/
|
||||
private bumpNeighboursPid = 0;
|
||||
|
||||
/**
|
||||
* The location of the top left of this block (in workspace coordinates)
|
||||
* relative to either its parent block, or the workspace origin if it has no
|
||||
* parent.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
relativeCoords = new Coordinate(0, 0);
|
||||
|
||||
|
||||
/**
|
||||
* @param workspace The block's workspace.
|
||||
* @param prototypeName Name of the language object containing type-specific
|
||||
@@ -154,36 +171,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
constructor(workspace: WorkspaceSvg, prototypeName: string, opt_id?: string) {
|
||||
super(workspace, prototypeName, opt_id);
|
||||
this.workspace = workspace;
|
||||
|
||||
/**
|
||||
* An optional method called when a mutator dialog is first opened.
|
||||
* This function must create and initialize a top-level block for the
|
||||
* mutator dialog, and return it. This function should also populate this
|
||||
* top-level block with any sub-blocks which are appropriate. This method
|
||||
* must also be coupled with defining a `compose` method for the default
|
||||
* mutation dialog button and UI to appear.
|
||||
*/
|
||||
this.decompose = this.decompose;
|
||||
|
||||
/**
|
||||
* An optional method called when a mutator dialog saves its content.
|
||||
* This function is called to modify the original block according to new
|
||||
* settings. This method must also be coupled with defining a `decompose`
|
||||
* method for the default mutation dialog button and UI to appear.
|
||||
*/
|
||||
this.compose = this.compose;
|
||||
|
||||
/**
|
||||
* An optional method called by the default mutator UI which gives the block
|
||||
* a chance to save information about what child blocks are connected to
|
||||
* what mutated connections.
|
||||
*/
|
||||
this.saveConnections = this.saveConnections;
|
||||
|
||||
/** An optional method for defining custom block context menu items. */
|
||||
this.customContextMenu = this.customContextMenu;
|
||||
this.svgGroup_ = dom.createSvgElement(Svg.G, {});
|
||||
(this.svgGroup_ as AnyDuringMigration).translate_ = '';
|
||||
|
||||
/** A block style object. */
|
||||
this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
|
||||
@@ -199,7 +187,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
this.useDragSurface_ = !!workspace.getBlockDragSurface();
|
||||
|
||||
const svgPath = this.pathObject.svgPath;
|
||||
(svgPath as AnyDuringMigration).tooltip = this;
|
||||
(svgPath as any).tooltip = this;
|
||||
Tooltip.bindMouseEvents(svgPath);
|
||||
|
||||
// Expose this block's ID on its top-level SVG group.
|
||||
@@ -348,9 +336,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
const oldXY = this.getRelativeToSurfaceXY();
|
||||
if (newParent) {
|
||||
(newParent as BlockSvg).getSvgRoot().appendChild(svgRoot);
|
||||
const newXY = this.getRelativeToSurfaceXY();
|
||||
// Move the connections to match the child's new position.
|
||||
this.moveConnections(newXY.x - oldXY.x, newXY.y - oldXY.y);
|
||||
} else if (oldParent) {
|
||||
// If we are losing a parent, we want to move our DOM element to the
|
||||
// root of the workspace.
|
||||
@@ -435,8 +420,18 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* @param y The y coordinate of the translation in workspace units.
|
||||
*/
|
||||
translate(x: number, y: number) {
|
||||
this.getSvgRoot().setAttribute(
|
||||
'transform', 'translate(' + x + ',' + y + ')');
|
||||
this.translation = `translate(${x}, ${y})`;
|
||||
this.relativeCoords = new Coordinate(x, y);
|
||||
this.getSvgRoot().setAttribute('transform', this.getTranslation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SVG translation of this block.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
getTranslation(): string {
|
||||
return this.translation;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,13 +501,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
this.workspace.getBlockDragSurface()!.translateSurface(
|
||||
newLoc.x, newLoc.y);
|
||||
} else {
|
||||
(this.svgGroup_ as AnyDuringMigration).translate_ =
|
||||
'translate(' + newLoc.x + ',' + newLoc.y + ')';
|
||||
(this.svgGroup_ as AnyDuringMigration)
|
||||
.setAttribute(
|
||||
'transform',
|
||||
(this.svgGroup_ as AnyDuringMigration).translate_ +
|
||||
(this.svgGroup_ as AnyDuringMigration).skew_);
|
||||
this.translate(newLoc.x, newLoc.y);
|
||||
this.getSvgRoot().setAttribute('transform', this.getTranslation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,13 +588,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
return;
|
||||
}
|
||||
super.setCollapsed(collapsed);
|
||||
if (!collapsed) {
|
||||
this.updateCollapsed_();
|
||||
} else if (this.rendered) {
|
||||
this.render();
|
||||
// Don't bump neighbours. Users like to store collapsed functions together
|
||||
// and bumping makes them go out of alignment.
|
||||
}
|
||||
this.updateCollapsed_();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -708,11 +692,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
if (this.workspace.options.readOnly || !this.contextMenu) {
|
||||
return null;
|
||||
}
|
||||
// AnyDuringMigration because: Argument of type '{ block: this; }' is not
|
||||
// assignable to parameter of type 'Scope'.
|
||||
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
|
||||
ContextMenuRegistry.ScopeType.BLOCK,
|
||||
{block: this} as AnyDuringMigration);
|
||||
ContextMenuRegistry.ScopeType.BLOCK, {block: this});
|
||||
|
||||
// Allow the block to add or modify menuOptions.
|
||||
if (this.customContextMenu) {
|
||||
@@ -775,9 +756,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
*/
|
||||
setDragging(adding: boolean) {
|
||||
if (adding) {
|
||||
const group = this.getSvgRoot();
|
||||
(group as AnyDuringMigration).translate_ = '';
|
||||
(group as AnyDuringMigration).skew_ = '';
|
||||
this.translation = '';
|
||||
common.draggingConnections.push(...this.getConnections_(true));
|
||||
dom.addClass(this.svgGroup_, 'blocklyDragging');
|
||||
} else {
|
||||
@@ -862,59 +841,39 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
override dispose(healStack?: boolean, animate?: boolean) {
|
||||
if (this.isDeadOrDying()) {
|
||||
return;
|
||||
}
|
||||
if (this.isDeadOrDying()) return;
|
||||
|
||||
Tooltip.dispose();
|
||||
Tooltip.unbindMouseEvents(this.pathObject.svgPath);
|
||||
dom.startTextWidthCache();
|
||||
// Save the block's workspace temporarily so we can resize the
|
||||
// contents once the block is disposed.
|
||||
const blockWorkspace = this.workspace;
|
||||
// If this block is being dragged, unlink the mouse events.
|
||||
if (common.getSelected() === this) {
|
||||
this.unselect();
|
||||
this.workspace.cancelCurrentGesture();
|
||||
}
|
||||
// If this block has a context menu open, close it.
|
||||
if (ContextMenu.getCurrentBlock() === this) {
|
||||
ContextMenu.hide();
|
||||
}
|
||||
ContextMenu.hide();
|
||||
|
||||
if (animate && this.rendered) {
|
||||
this.unplug(healStack);
|
||||
blockAnimations.disposeUiEffect(this);
|
||||
}
|
||||
// Stop rerendering.
|
||||
this.rendered = false;
|
||||
|
||||
// Clear pending warnings.
|
||||
for (const n of this.warningTextDb.values()) {
|
||||
clearTimeout(n);
|
||||
}
|
||||
this.warningTextDb.clear();
|
||||
|
||||
const icons = this.getIcons();
|
||||
for (let i = 0; i < icons.length; i++) {
|
||||
icons[i].dispose();
|
||||
}
|
||||
|
||||
// Just deleting this block from the DOM would result in a memory leak as
|
||||
// well as corruption of the connection database. Therefore we must
|
||||
// methodically step through the blocks and carefully disassemble them.
|
||||
if (common.getSelected() === this) {
|
||||
common.setSelected(null);
|
||||
}
|
||||
|
||||
super.dispose(!!healStack);
|
||||
|
||||
dom.removeNode(this.svgGroup_);
|
||||
blockWorkspace.resizeContents();
|
||||
// Sever JavaScript to DOM connections.
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGGElement'.
|
||||
this.svgGroup_ = null as AnyDuringMigration;
|
||||
dom.stopTextWidthCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of this block without doing things required by the top block.
|
||||
* E.g. does trigger UI effects, remove nodes, etc.
|
||||
*/
|
||||
override disposeInternal() {
|
||||
if (this.isDeadOrDying()) return;
|
||||
super.disposeInternal();
|
||||
|
||||
this.rendered = false;
|
||||
|
||||
if (common.getSelected() === this) {
|
||||
this.unselect();
|
||||
this.workspace.cancelCurrentGesture();
|
||||
}
|
||||
|
||||
[...this.warningTextDb.values()].forEach((n) => clearTimeout(n));
|
||||
this.warningTextDb.clear();
|
||||
|
||||
this.getIcons().forEach((i) => i.dispose());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -987,15 +946,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* @internal
|
||||
*/
|
||||
updateDisabled() {
|
||||
const children = (this.getChildren(false));
|
||||
const disabled = !this.isEnabled() || this.getInheritedDisabled();
|
||||
if (this.visuallyDisabled === disabled) return;
|
||||
this.applyColour();
|
||||
if (this.isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0, child; child = children[i]; i++) {
|
||||
if (child.rendered) {
|
||||
child.updateDisabled();
|
||||
}
|
||||
this.visuallyDisabled = disabled;
|
||||
for (const child of this.getChildren(false)) {
|
||||
child.updateDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,8 +971,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* @param text The text, or null to delete.
|
||||
*/
|
||||
override setCommentText(text: string|null) {
|
||||
// AnyDuringMigration because: Property 'get' does not exist on type
|
||||
// '(name: string) => void'.
|
||||
if (this.commentModel.text === text) {
|
||||
return;
|
||||
}
|
||||
@@ -1291,7 +1245,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
super.setPreviousStatement(newBoolean, opt_check);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
}
|
||||
@@ -1308,7 +1262,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
super.setNextStatement(newBoolean, opt_check);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
}
|
||||
@@ -1324,7 +1278,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
super.setOutput(newBoolean, opt_check);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
}
|
||||
@@ -1338,7 +1292,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
super.setInputsInline(newBoolean);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
}
|
||||
@@ -1356,7 +1310,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
const removed = super.removeInput(name, opt_quiet);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
// Removing an input will cause the block to change shape.
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
@@ -1374,7 +1328,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
super.moveNumberedInputBefore(inputIndex, refIndex);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
// Moving an input will cause the block to change shape.
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
@@ -1392,7 +1346,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
const input = super.appendInput_(type, name);
|
||||
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
this.queueRender();
|
||||
// Adding an input will cause the block to change shape.
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
@@ -1450,7 +1404,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @param all If true, return all connections even hidden ones.
|
||||
* Otherwise, for a non-rendered block return an empty list, and for a
|
||||
* collapsed block don't return inputs connections.
|
||||
* collapsed block don't return inputs connections.
|
||||
* @returns Array of connections.
|
||||
* @internal
|
||||
*/
|
||||
@@ -1542,7 +1496,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
* up on screen, because that creates confusion for end-users.
|
||||
*/
|
||||
override bumpNeighbours() {
|
||||
this.getRootBlock().bumpNeighboursInternal();
|
||||
if (this.bumpNeighboursPid) return;
|
||||
const group = eventUtils.getGroup();
|
||||
|
||||
this.bumpNeighboursPid = setTimeout(() => {
|
||||
const oldGroup = eventUtils.getGroup();
|
||||
eventUtils.setGroup(group);
|
||||
this.getRootBlock().bumpNeighboursInternal();
|
||||
eventUtils.setGroup(oldGroup);
|
||||
this.bumpNeighboursPid = 0;
|
||||
}, config.bumpDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1597,11 +1560,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
eventUtils.setGroup(false);
|
||||
}, config.bumpDelay / 2);
|
||||
|
||||
setTimeout(() => {
|
||||
eventUtils.setGroup(group);
|
||||
this.bumpNeighbours();
|
||||
eventUtils.setGroup(false);
|
||||
}, config.bumpDelay);
|
||||
this.bumpNeighbours();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1650,7 +1609,17 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
|
||||
/**
|
||||
* Lays out and reflows a block based on its contents and settings.
|
||||
* Triggers a rerender after a delay to allow for batching.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
queueRender() {
|
||||
queueRender(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately lays out and reflows a block based on its contents and
|
||||
* settings.
|
||||
*
|
||||
* @param opt_bubble If false, just render this block.
|
||||
* If true, also render block's parent, grandparent, etc. Defaults to true.
|
||||
@@ -1668,7 +1637,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
this.updateCollapsed_();
|
||||
}
|
||||
this.workspace.getRenderer().render(this);
|
||||
this.updateConnectionLocations_();
|
||||
this.updateConnectionLocations();
|
||||
|
||||
if (opt_bubble !== false) {
|
||||
const parentBlock = this.getParent();
|
||||
@@ -1687,6 +1656,43 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders this block in a way that's compatible with the more efficient
|
||||
* render management system.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
renderEfficiently() {
|
||||
this.rendered = true;
|
||||
dom.startTextWidthCache();
|
||||
|
||||
if (this.isCollapsed()) {
|
||||
this.updateCollapsed_();
|
||||
}
|
||||
this.workspace.getRenderer().render(this);
|
||||
this.tightenChildrenEfficiently();
|
||||
|
||||
dom.stopTextWidthCache();
|
||||
this.updateMarkers_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tightens all children of this block so they are snuggly rendered against
|
||||
* their parent connections.
|
||||
*
|
||||
* Does not update connection locations, so that they can be updated more
|
||||
* efficiently by the render management system.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
tightenChildrenEfficiently() {
|
||||
for (const input of this.inputList) {
|
||||
const conn = input.connection as RenderedConnection;
|
||||
if (conn) conn.tightenEfficiently();
|
||||
}
|
||||
if (this.nextConnection) this.nextConnection.tightenEfficiently();
|
||||
}
|
||||
|
||||
/** Redraw any attached marker or cursor svgs if needed. */
|
||||
protected updateMarkers_() {
|
||||
if (this.workspace.keyboardAccessibilityMode && this.pathObject.cursorSvg) {
|
||||
@@ -1696,14 +1702,21 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
|
||||
// TODO(#4592): Update all markers on the block.
|
||||
this.workspace.getMarker(MarkerManager.LOCAL_MARKER)!.draw();
|
||||
}
|
||||
for (const input of this.inputList) {
|
||||
for (const field of input.fieldRow) {
|
||||
field.updateMarkers_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all of the connections on this block with the new locations
|
||||
* calculated during rendering. Also move all of the connected blocks based
|
||||
* on the new connection locations.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
private updateConnectionLocations_() {
|
||||
updateConnectionLocations() {
|
||||
const blockTL = this.getRelativeToSurfaceXY();
|
||||
// Don't tighten previous or output connections because they are inferior
|
||||
// connections.
|
||||
|
||||
123
core/blockly.ts
123
core/blockly.ts
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* The top level namespace used to access the Blockly library.
|
||||
*
|
||||
* @namespace Blockly
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly');
|
||||
|
||||
@@ -53,19 +48,19 @@ import {DragTarget} from './drag_target.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import * as Events from './events/events.js';
|
||||
import * as Extensions from './extensions.js';
|
||||
import {Field, FieldValidator} from './field.js';
|
||||
import {FieldAngle, FieldAngleValidator} from './field_angle.js';
|
||||
import {FieldCheckbox, FieldCheckboxValidator} from './field_checkbox.js';
|
||||
import {FieldColour, FieldColourValidator} from './field_colour.js';
|
||||
import {FieldDropdown, FieldDropdownValidator, MenuGenerator, MenuGeneratorFunction, MenuOption} from './field_dropdown.js';
|
||||
import {FieldImage} from './field_image.js';
|
||||
import {FieldLabel} from './field_label.js';
|
||||
import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
|
||||
import {FieldAngle, FieldAngleConfig, FieldAngleFromJsonConfig, FieldAngleValidator} from './field_angle.js';
|
||||
import {FieldCheckbox, FieldCheckboxConfig, FieldCheckboxFromJsonConfig, FieldCheckboxValidator} from './field_checkbox.js';
|
||||
import {FieldColour, FieldColourConfig, FieldColourFromJsonConfig, FieldColourValidator} from './field_colour.js';
|
||||
import {FieldDropdown, FieldDropdownConfig, FieldDropdownFromJsonConfig, FieldDropdownValidator, MenuGenerator, MenuGeneratorFunction, MenuOption} from './field_dropdown.js';
|
||||
import {FieldImage, FieldImageConfig, FieldImageFromJsonConfig} from './field_image.js';
|
||||
import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js';
|
||||
import {FieldLabelSerializable} from './field_label_serializable.js';
|
||||
import {FieldMultilineInput, FieldMultilineInputValidator} from './field_multilineinput.js';
|
||||
import {FieldNumber, FieldNumberValidator} from './field_number.js';
|
||||
import {FieldMultilineInput, FieldMultilineInputConfig, FieldMultilineInputFromJsonConfig, FieldMultilineInputValidator} from './field_multilineinput.js';
|
||||
import {FieldNumber, FieldNumberConfig, FieldNumberFromJsonConfig, FieldNumberValidator} from './field_number.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInput, FieldTextInputValidator} from './field_textinput.js';
|
||||
import {FieldVariable, FieldVariableValidator} from './field_variable.js';
|
||||
import {FieldTextInput, FieldTextInputConfig, FieldTextInputFromJsonConfig, FieldTextInputValidator} from './field_textinput.js';
|
||||
import {FieldVariable, FieldVariableConfig, FieldVariableFromJsonConfig, FieldVariableValidator} from './field_variable.js';
|
||||
import {Flyout} from './flyout_base.js';
|
||||
import {FlyoutButton} from './flyout_button.js';
|
||||
import {HorizontalFlyout} from './flyout_horizontal.js';
|
||||
@@ -99,6 +94,7 @@ import {IFlyout} from './interfaces/i_flyout.js';
|
||||
import {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js';
|
||||
import {IMetricsManager} from './interfaces/i_metrics_manager.js';
|
||||
import {IMovable} from './interfaces/i_movable.js';
|
||||
import {IObservable, isObservable} from './interfaces/i_observable.js';
|
||||
import {IPositionable} from './interfaces/i_positionable.js';
|
||||
import {IRegistrable} from './interfaces/i_registrable.js';
|
||||
import {ISelectable} from './interfaces/i_selectable.js';
|
||||
@@ -106,6 +102,7 @@ import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.js'
|
||||
import {IStyleable} from './interfaces/i_styleable.js';
|
||||
import {IToolbox} from './interfaces/i_toolbox.js';
|
||||
import {IToolboxItem} from './interfaces/i_toolbox_item.js';
|
||||
import {IVariableBackedParameterModel, isVariableBackedParameterModel} from './interfaces/i_variable_backed_parameter_model.js';
|
||||
import * as internalConstants from './internal_constants.js';
|
||||
import {ASTNode} from './keyboard_nav/ast_node.js';
|
||||
import {BasicCursor} from './keyboard_nav/basic_cursor.js';
|
||||
@@ -176,7 +173,6 @@ import {ZoomControls} from './zoom_controls.js';
|
||||
* compiler to override this constant.
|
||||
*
|
||||
* @define {string}
|
||||
* @alias Blockly.VERSION
|
||||
*/
|
||||
export const VERSION = 'uncompiled';
|
||||
|
||||
@@ -194,19 +190,16 @@ export const VERSION = 'uncompiled';
|
||||
|
||||
/**
|
||||
* @see Blockly.Input.Align.LEFT
|
||||
* @alias Blockly.ALIGN_LEFT
|
||||
*/
|
||||
export const ALIGN_LEFT = Align.LEFT;
|
||||
|
||||
/**
|
||||
* @see Blockly.Input.Align.CENTRE
|
||||
* @alias Blockly.ALIGN_CENTRE
|
||||
*/
|
||||
export const ALIGN_CENTRE = Align.CENTRE;
|
||||
|
||||
/**
|
||||
* @see Blockly.Input.Align.RIGHT
|
||||
* @alias Blockly.ALIGN_RIGHT
|
||||
*/
|
||||
export const ALIGN_RIGHT = Align.RIGHT;
|
||||
/*
|
||||
@@ -215,31 +208,26 @@ export const ALIGN_RIGHT = Align.RIGHT;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.INPUT_VALUE
|
||||
* @alias Blockly.INPUT_VALUE
|
||||
*/
|
||||
export const INPUT_VALUE = ConnectionType.INPUT_VALUE;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.OUTPUT_VALUE
|
||||
* @alias Blockly.OUTPUT_VALUE
|
||||
*/
|
||||
export const OUTPUT_VALUE = ConnectionType.OUTPUT_VALUE;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.NEXT_STATEMENT
|
||||
* @alias Blockly.NEXT_STATEMENT
|
||||
*/
|
||||
export const NEXT_STATEMENT = ConnectionType.NEXT_STATEMENT;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.PREVIOUS_STATEMENT
|
||||
* @alias Blockly.PREVIOUS_STATEMENT
|
||||
*/
|
||||
export const PREVIOUS_STATEMENT = ConnectionType.PREVIOUS_STATEMENT;
|
||||
|
||||
/**
|
||||
* @see inputTypes.DUMMY_INPUT
|
||||
* @alias Blockly.DUMMY_INPUT
|
||||
*/
|
||||
export const DUMMY_INPUT = inputTypes.DUMMY;
|
||||
|
||||
@@ -247,25 +235,21 @@ export const DUMMY_INPUT = inputTypes.DUMMY;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.TOP
|
||||
* @alias Blockly.TOOLBOX_AT_TOP
|
||||
*/
|
||||
export const TOOLBOX_AT_TOP = toolbox.Position.TOP;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.BOTTOM
|
||||
* @alias Blockly.TOOLBOX_AT_BOTTOM
|
||||
*/
|
||||
export const TOOLBOX_AT_BOTTOM = toolbox.Position.BOTTOM;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.LEFT
|
||||
* @alias Blockly.TOOLBOX_AT_LEFT
|
||||
*/
|
||||
export const TOOLBOX_AT_LEFT = toolbox.Position.LEFT;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.RIGHT
|
||||
* @alias Blockly.TOOLBOX_AT_RIGHT
|
||||
*/
|
||||
export const TOOLBOX_AT_RIGHT = toolbox.Position.RIGHT;
|
||||
|
||||
@@ -282,7 +266,6 @@ export const TOOLBOX_AT_RIGHT = toolbox.Position.RIGHT;
|
||||
*
|
||||
* @param workspace Any workspace in the SVG.
|
||||
* @see Blockly.common.svgResize
|
||||
* @alias Blockly.svgResize
|
||||
*/
|
||||
export const svgResize = common.svgResize;
|
||||
|
||||
@@ -291,7 +274,6 @@ export const svgResize = common.svgResize;
|
||||
*
|
||||
* @param opt_onlyClosePopups Whether only popups should be closed.
|
||||
* @see Blockly.WorkspaceSvg.hideChaff
|
||||
* @alias Blockly.hideChaff
|
||||
*/
|
||||
export function hideChaff(opt_onlyClosePopups?: boolean) {
|
||||
(common.getMainWorkspace() as WorkspaceSvg).hideChaff(opt_onlyClosePopups);
|
||||
@@ -303,14 +285,11 @@ export function hideChaff(opt_onlyClosePopups?: boolean) {
|
||||
* Blockly instances on a page.
|
||||
*
|
||||
* @see Blockly.common.getMainWorkspace
|
||||
* @alias Blockly.getMainWorkspace
|
||||
*/
|
||||
export const getMainWorkspace = common.getMainWorkspace;
|
||||
|
||||
/**
|
||||
* Returns the currently selected copyable object.
|
||||
*
|
||||
* @alias Blockly.common.getSelected
|
||||
*/
|
||||
export const getSelected = common.getSelected;
|
||||
|
||||
@@ -320,7 +299,6 @@ export const getSelected = common.getSelected;
|
||||
*
|
||||
* @param jsonArray An array of JSON block definitions.
|
||||
* @see Blockly.common.defineBlocksWithJsonArray
|
||||
* @alias Blockly.defineBlocksWithJsonArray
|
||||
*/
|
||||
export const defineBlocksWithJsonArray = common.defineBlocksWithJsonArray;
|
||||
|
||||
@@ -332,7 +310,6 @@ export const defineBlocksWithJsonArray = common.defineBlocksWithJsonArray;
|
||||
*
|
||||
* @param container The container element.
|
||||
* @see Blockly.common.setParentContainer
|
||||
* @alias Blockly.setParentContainer
|
||||
*/
|
||||
export const setParentContainer = common.setParentContainer;
|
||||
|
||||
@@ -343,7 +320,6 @@ export const setParentContainer = common.setParentContainer;
|
||||
* @param workspace The workspace to resize.
|
||||
* @deprecated Use **workspace.resizeContents** instead.
|
||||
* @see Blockly.WorkspaceSvg.resizeContents
|
||||
* @alias Blockly.resizeSvgContents
|
||||
*/
|
||||
function resizeSvgContentsLocal(workspace: WorkspaceSvg) {
|
||||
deprecation.warn(
|
||||
@@ -359,7 +335,6 @@ export const resizeSvgContents = resizeSvgContentsLocal;
|
||||
* @param toCopy Block or Workspace Comment to be copied.
|
||||
* @deprecated Use **Blockly.clipboard.copy** instead.
|
||||
* @see Blockly.clipboard.copy
|
||||
* @alias Blockly.copy
|
||||
*/
|
||||
export function copy(toCopy: ICopyable) {
|
||||
deprecation.warn(
|
||||
@@ -374,7 +349,6 @@ export function copy(toCopy: ICopyable) {
|
||||
* @returns True if the paste was successful, false otherwise.
|
||||
* @deprecated Use **Blockly.clipboard.paste** instead.
|
||||
* @see Blockly.clipboard.paste
|
||||
* @alias Blockly.paste
|
||||
*/
|
||||
export function paste(): boolean {
|
||||
deprecation.warn(
|
||||
@@ -389,7 +363,6 @@ export function paste(): boolean {
|
||||
* @param toDuplicate Block or Workspace Comment to be copied.
|
||||
* @deprecated Use **Blockly.clipboard.duplicate** instead.
|
||||
* @see Blockly.clipboard.duplicate
|
||||
* @alias Blockly.duplicate
|
||||
*/
|
||||
export function duplicate(toDuplicate: ICopyable) {
|
||||
deprecation.warn(
|
||||
@@ -405,7 +378,6 @@ export function duplicate(toDuplicate: ICopyable) {
|
||||
* @returns True if number, false otherwise.
|
||||
* @deprecated Use **Blockly.utils.string.isNumber** instead.
|
||||
* @see Blockly.utils.string.isNumber
|
||||
* @alias Blockly.isNumber
|
||||
*/
|
||||
export function isNumber(str: string): boolean {
|
||||
deprecation.warn(
|
||||
@@ -421,7 +393,6 @@ export function isNumber(str: string): boolean {
|
||||
* @returns RGB code, e.g. '#5ba65b'.
|
||||
* @deprecated Use **Blockly.utils.colour.hueToHex** instead.
|
||||
* @see Blockly.utils.colour.hueToHex
|
||||
* @alias Blockly.hueToHex
|
||||
*/
|
||||
export function hueToHex(hue: number): string {
|
||||
deprecation.warn(
|
||||
@@ -443,7 +414,6 @@ export function hueToHex(hue: number): string {
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @deprecated Use **Blockly.browserEvents.bind** instead.
|
||||
* @see Blockly.browserEvents.bind
|
||||
* @alias Blockly.bindEvent_
|
||||
*/
|
||||
export function bindEvent_(
|
||||
node: EventTarget, name: string, thisObject: Object|null,
|
||||
@@ -462,7 +432,6 @@ export function bindEvent_(
|
||||
* @returns The function call.
|
||||
* @deprecated Use **Blockly.browserEvents.unbind** instead.
|
||||
* @see browserEvents.unbind
|
||||
* @alias Blockly.unbindEvent_
|
||||
*/
|
||||
export function unbindEvent_(bindData: browserEvents.Data): Function {
|
||||
deprecation.warn(
|
||||
@@ -488,7 +457,6 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @deprecated Use **Blockly.browserEvents.conditionalBind** instead.
|
||||
* @see browserEvents.conditionalBind
|
||||
* @alias Blockly.bindEventWithChecks_
|
||||
*/
|
||||
export function bindEventWithChecks_(
|
||||
node: EventTarget, name: string, thisObject: Object|null, func: Function,
|
||||
@@ -514,8 +482,6 @@ export const COLLAPSED_FIELD_NAME = constants.COLLAPSED_FIELD_NAME;
|
||||
* 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.
|
||||
*
|
||||
* @alias Blockly.VARIABLE_CATEGORY_NAME
|
||||
*/
|
||||
export const VARIABLE_CATEGORY_NAME: string = Variables.CATEGORY_NAME;
|
||||
|
||||
@@ -523,8 +489,6 @@ export const VARIABLE_CATEGORY_NAME: string = Variables.CATEGORY_NAME;
|
||||
* 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.
|
||||
*
|
||||
* @alias Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME
|
||||
*/
|
||||
export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
|
||||
VariablesDynamic.CATEGORY_NAME;
|
||||
@@ -532,8 +496,6 @@ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
|
||||
* 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.
|
||||
*
|
||||
* @alias Blockly.PROCEDURE_CATEGORY_NAME
|
||||
*/
|
||||
export const PROCEDURE_CATEGORY_NAME: string = Procedures.CATEGORY_NAME;
|
||||
|
||||
@@ -643,24 +605,61 @@ export {Cursor};
|
||||
export {DeleteArea};
|
||||
export {DragTarget};
|
||||
export const DropDownDiv = dropDownDiv;
|
||||
export {Field, FieldValidator};
|
||||
export {FieldAngle, FieldAngleValidator};
|
||||
export {FieldCheckbox, FieldCheckboxValidator};
|
||||
export {FieldColour, FieldColourValidator};
|
||||
export {Field, FieldConfig, FieldValidator, UnattachedFieldError};
|
||||
export {
|
||||
FieldAngle,
|
||||
FieldAngleConfig,
|
||||
FieldAngleFromJsonConfig,
|
||||
FieldAngleValidator,
|
||||
};
|
||||
export {
|
||||
FieldCheckbox,
|
||||
FieldCheckboxConfig,
|
||||
FieldCheckboxFromJsonConfig,
|
||||
FieldCheckboxValidator,
|
||||
};
|
||||
export {
|
||||
FieldColour,
|
||||
FieldColourConfig,
|
||||
FieldColourFromJsonConfig,
|
||||
FieldColourValidator,
|
||||
};
|
||||
export {
|
||||
FieldDropdown,
|
||||
FieldDropdownConfig,
|
||||
FieldDropdownFromJsonConfig,
|
||||
FieldDropdownValidator,
|
||||
MenuGenerator,
|
||||
MenuGeneratorFunction,
|
||||
MenuOption,
|
||||
};
|
||||
export {FieldImage};
|
||||
export {FieldLabel};
|
||||
export {FieldImage, FieldImageConfig, FieldImageFromJsonConfig};
|
||||
export {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig};
|
||||
export {FieldLabelSerializable};
|
||||
export {FieldMultilineInput, FieldMultilineInputValidator};
|
||||
export {FieldNumber, FieldNumberValidator};
|
||||
export {FieldTextInput, FieldTextInputValidator};
|
||||
export {FieldVariable, FieldVariableValidator};
|
||||
export {
|
||||
FieldMultilineInput,
|
||||
FieldMultilineInputConfig,
|
||||
FieldMultilineInputFromJsonConfig,
|
||||
FieldMultilineInputValidator,
|
||||
};
|
||||
export {
|
||||
FieldNumber,
|
||||
FieldNumberConfig,
|
||||
FieldNumberFromJsonConfig,
|
||||
FieldNumberValidator,
|
||||
};
|
||||
export {
|
||||
FieldTextInput,
|
||||
FieldTextInputConfig,
|
||||
FieldTextInputFromJsonConfig,
|
||||
FieldTextInputValidator,
|
||||
};
|
||||
export {
|
||||
FieldVariable,
|
||||
FieldVariableConfig,
|
||||
FieldVariableFromJsonConfig,
|
||||
FieldVariableValidator,
|
||||
};
|
||||
export {Flyout};
|
||||
export {FlyoutButton};
|
||||
export {FlyoutMetricsManager};
|
||||
@@ -693,6 +692,7 @@ export {IMetricsManager};
|
||||
export {IMovable};
|
||||
export {Input};
|
||||
export {InsertionMarkerManager};
|
||||
export {IObservable, isObservable};
|
||||
export {IPositionable};
|
||||
export {IRegistrable};
|
||||
export {ISelectable};
|
||||
@@ -700,6 +700,7 @@ export {ISelectableToolboxItem};
|
||||
export {IStyleable};
|
||||
export {IToolbox};
|
||||
export {IToolboxItem};
|
||||
export {IVariableBackedParameterModel, isVariableBackedParameterModel};
|
||||
export {Marker};
|
||||
export {MarkerManager};
|
||||
export {Menu};
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Object that defines user-specified options for the workspace.
|
||||
*
|
||||
* @namespace Blockly.BlocklyOptions
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.BlocklyOptions');
|
||||
|
||||
@@ -19,8 +14,6 @@ import type {ToolboxDefinition} from './utils/toolbox.js';
|
||||
|
||||
/**
|
||||
* Blockly options.
|
||||
*
|
||||
* @alias Blockly.BlocklyOptions
|
||||
*/
|
||||
export interface BlocklyOptions {
|
||||
collapse?: boolean;
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* A mapping of block type names to block prototype objects.
|
||||
*
|
||||
* @namespace Blockly.blocks
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.blocks');
|
||||
|
||||
@@ -21,7 +16,5 @@ export type BlockDefinition = AnyDuringMigration;
|
||||
|
||||
/**
|
||||
* A mapping of block type names to block prototype objects.
|
||||
*
|
||||
* @alias Blockly.blocks.Blocks
|
||||
*/
|
||||
export const Blocks: {[key: string]: BlockDefinition} = Object.create(null);
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Browser event handling.
|
||||
*
|
||||
* @namespace Blockly.browserEvents
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.browserEvents');
|
||||
|
||||
@@ -20,8 +15,6 @@ import * as userAgent from './utils/useragent.js';
|
||||
/**
|
||||
* Blockly opaque event data used to unbind events when using
|
||||
* `bind` and `conditionalBind`.
|
||||
*
|
||||
* @alias Blockly.browserEvents.Data
|
||||
*/
|
||||
export type Data = [EventTarget, string, (e: Event) => void][];
|
||||
|
||||
@@ -54,7 +47,6 @@ const PAGE_MODE_MULTIPLIER = 125;
|
||||
* simultaneous touches. False by default.
|
||||
* @param opt_noPreventDefault No-op, deprecated and will be removed in v10.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @alias Blockly.browserEvents.conditionalBind
|
||||
*/
|
||||
export function conditionalBind(
|
||||
node: EventTarget, name: string, thisObject: Object|null, func: Function,
|
||||
@@ -105,7 +97,6 @@ export function conditionalBind(
|
||||
* @param thisObject The value of 'this' in the function.
|
||||
* @param func Function to call when event is triggered.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @alias Blockly.browserEvents.bind
|
||||
*/
|
||||
export function bind(
|
||||
node: EventTarget, name: string, thisObject: Object|null,
|
||||
@@ -142,7 +133,6 @@ export function bind(
|
||||
* @param bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @returns The function call.
|
||||
* @alias Blockly.browserEvents.unbind
|
||||
*/
|
||||
export function unbind(bindData: Data): (e: Event) => void {
|
||||
// Accessing an element of the last property of the array is unsafe if the
|
||||
@@ -150,10 +140,7 @@ export function unbind(bindData: Data): (e: Event) => void {
|
||||
// should only pass Data from bind or conditionalBind.
|
||||
const callback = bindData[bindData.length - 1][2];
|
||||
while (bindData.length) {
|
||||
const bindDatum = bindData.pop();
|
||||
const node = bindDatum![0];
|
||||
const name = bindDatum![1];
|
||||
const func = bindDatum![2];
|
||||
const [node, name, func] = bindData.pop()!;
|
||||
node.removeEventListener(name, func, false);
|
||||
}
|
||||
return callback;
|
||||
@@ -164,7 +151,6 @@ export function unbind(bindData: Data): (e: Event) => void {
|
||||
*
|
||||
* @param e An event.
|
||||
* @returns True if text input.
|
||||
* @alias Blockly.browserEvents.isTargetInput
|
||||
*/
|
||||
export function isTargetInput(e: Event): boolean {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
@@ -194,7 +180,6 @@ export function isTargetInput(e: Event): boolean {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @returns True if right-click.
|
||||
* @alias Blockly.browserEvents.isRightButton
|
||||
*/
|
||||
export function isRightButton(e: MouseEvent): boolean {
|
||||
if (e.ctrlKey && userAgent.MAC) {
|
||||
@@ -213,7 +198,6 @@ export function isRightButton(e: MouseEvent): boolean {
|
||||
* @param svg SVG element.
|
||||
* @param matrix Inverted screen CTM to use.
|
||||
* @returns Object with .x and .y properties.
|
||||
* @alias Blockly.browserEvents.mouseToSvg
|
||||
*/
|
||||
export function mouseToSvg(
|
||||
e: MouseEvent, svg: SVGSVGElement, matrix: SVGMatrix|null): SVGPoint {
|
||||
@@ -232,7 +216,6 @@ export function mouseToSvg(
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @returns Scroll delta object with .x and .y properties.
|
||||
* @alias Blockly.browserEvents.getScrollDeltaPixels
|
||||
*/
|
||||
export function getScrollDeltaPixels(e: WheelEvent): {x: number, y: number} {
|
||||
switch (e.deltaMode) {
|
||||
|
||||
@@ -30,8 +30,6 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
/**
|
||||
* Class for UI bubble.
|
||||
*
|
||||
* @alias Blockly.Bubble
|
||||
*/
|
||||
export class Bubble implements IBubble {
|
||||
/** Width of the border around the bubble. */
|
||||
@@ -220,27 +218,26 @@ export class Bubble implements IBubble {
|
||||
'blocklyResizeSE',
|
||||
},
|
||||
this.bubbleGroup);
|
||||
const resizeSize = 2 * Bubble.BORDER_WIDTH;
|
||||
const size = 2 * Bubble.BORDER_WIDTH;
|
||||
dom.createSvgElement(
|
||||
Svg.POLYGON,
|
||||
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
||||
Svg.POLYGON, {'points': `0,${size} ${size},${size} ${size},0`},
|
||||
this.resizeGroup);
|
||||
dom.createSvgElement(
|
||||
Svg.LINE, {
|
||||
'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize / 3,
|
||||
'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1,
|
||||
'y2': resizeSize / 3,
|
||||
'x1': size / 3,
|
||||
'y1': size - 1,
|
||||
'x2': size - 1,
|
||||
'y2': size / 3,
|
||||
},
|
||||
this.resizeGroup);
|
||||
dom.createSvgElement(
|
||||
Svg.LINE, {
|
||||
'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize * 2 / 3,
|
||||
'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1,
|
||||
'y2': resizeSize * 2 / 3,
|
||||
'x1': size * 2 / 3,
|
||||
'y1': size - 1,
|
||||
'x2': size - 1,
|
||||
'y2': size * 2 / 3,
|
||||
},
|
||||
this.resizeGroup);
|
||||
} else {
|
||||
@@ -662,8 +659,8 @@ export class Bubble implements IBubble {
|
||||
height = Math.max(height, doubleBorderWidth + 20);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.bubbleBack?.setAttribute('width', width.toString());
|
||||
this.bubbleBack?.setAttribute('height', height.toString());
|
||||
this.bubbleBack?.setAttribute('width', `${width}`);
|
||||
this.bubbleBack?.setAttribute('height', `${height}`);
|
||||
if (this.resizeGroup) {
|
||||
if (this.workspace_.RTL) {
|
||||
// Mirror the resize group.
|
||||
@@ -903,8 +900,7 @@ export class Bubble implements IBubble {
|
||||
textElement = paragraphElement.childNodes[i] as SVGTSpanElement;
|
||||
i++) {
|
||||
textElement.setAttribute('text-anchor', 'end');
|
||||
textElement.setAttribute(
|
||||
'x', (maxWidth + Bubble.BORDER_WIDTH).toString());
|
||||
textElement.setAttribute('x', String(maxWidth + Bubble.BORDER_WIDTH));
|
||||
}
|
||||
}
|
||||
return bubble;
|
||||
|
||||
@@ -28,8 +28,6 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
* 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.
|
||||
*
|
||||
* @alias Blockly.BubbleDragger
|
||||
*/
|
||||
export class BubbleDragger {
|
||||
/** Which drag target the mouse pointer is over, if any. */
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utilities for bumping objects back into worksapce bounds.
|
||||
*
|
||||
* @namespace Blockly.bumpObjects
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.bumpObjects');
|
||||
|
||||
@@ -35,7 +30,6 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
* in workspace coordinates.
|
||||
* @param object The object to bump.
|
||||
* @returns True if block was bumped.
|
||||
* @alias Blockly.bumpObjects.bumpIntoBounds
|
||||
*/
|
||||
function bumpObjectIntoBounds(
|
||||
workspace: WorkspaceSvg, scrollMetrics: ContainerRegion,
|
||||
@@ -87,7 +81,6 @@ export const bumpIntoBounds = bumpObjectIntoBounds;
|
||||
*
|
||||
* @param workspace The workspace to handle.
|
||||
* @returns The event handler.
|
||||
* @alias Blockly.bumpObjects.bumpIntoBoundsHandler
|
||||
*/
|
||||
export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
|
||||
(p1: Abstract) => void {
|
||||
@@ -107,7 +100,7 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
|
||||
return;
|
||||
}
|
||||
// Handle undo.
|
||||
const oldGroup = eventUtils.getGroup();
|
||||
const existingGroup = eventUtils.getGroup() || false;
|
||||
eventUtils.setGroup(e.group);
|
||||
|
||||
const wasBumped = bumpObjectIntoBounds(
|
||||
@@ -118,9 +111,7 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
|
||||
'Moved object in bounds but there was no' +
|
||||
' event group. This may break undo.');
|
||||
}
|
||||
if (oldGroup !== null) {
|
||||
eventUtils.setGroup(oldGroup);
|
||||
}
|
||||
eventUtils.setGroup(existingGroup);
|
||||
} else if (e.type === eventUtils.VIEWPORT_CHANGE) {
|
||||
const viewportEvent = (e as ViewportChange);
|
||||
if (viewportEvent.scale && viewportEvent.oldScale &&
|
||||
@@ -167,7 +158,6 @@ function extractObjectFromEvent(
|
||||
* Bumps the top objects in the given workspace into bounds.
|
||||
*
|
||||
* @param workspace The workspace.
|
||||
* @alias Blockly.bumpObjects.bumpTopObjectsIntoBounds
|
||||
*/
|
||||
export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg) {
|
||||
const metricsManager = workspace.getMetricsManager();
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Blockly's internal clipboard for managing copy-paste.
|
||||
*
|
||||
* @namespace Blockly.clipboard
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.clipboard');
|
||||
|
||||
@@ -22,7 +17,6 @@ let copyData: CopyData|null = null;
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
*
|
||||
* @param toCopy Block or Workspace Comment to be copied.
|
||||
* @alias Blockly.clipboard.copy
|
||||
* @internal
|
||||
*/
|
||||
export function copy(toCopy: ICopyable) {
|
||||
@@ -40,7 +34,6 @@ function copyInternal(toCopy: ICopyable) {
|
||||
* Paste a block or workspace comment on to the main workspace.
|
||||
*
|
||||
* @returns The pasted thing if the paste was successful, null otherwise.
|
||||
* @alias Blockly.clipboard.paste
|
||||
* @internal
|
||||
*/
|
||||
export function paste(): ICopyable|null {
|
||||
@@ -66,7 +59,6 @@ export function paste(): ICopyable|null {
|
||||
* @param toDuplicate Block or Workspace Comment to be duplicated.
|
||||
* @returns The block or workspace comment that was duplicated, or null if the
|
||||
* duplication failed.
|
||||
* @alias Blockly.clipboard.duplicate
|
||||
* @internal
|
||||
*/
|
||||
export function duplicate(toDuplicate: ICopyable): ICopyable|null {
|
||||
|
||||
@@ -32,8 +32,6 @@ import {Svg} from './utils/svg.js';
|
||||
|
||||
/**
|
||||
* Class for a comment.
|
||||
*
|
||||
* @alias Blockly.Comment
|
||||
*/
|
||||
export class Comment extends Icon {
|
||||
private readonly model: CommentModel;
|
||||
@@ -44,17 +42,12 @@ export class Comment extends Icon {
|
||||
*/
|
||||
private cachedText: string|null = '';
|
||||
|
||||
/** Mouse up event data. */
|
||||
private onMouseUpWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/** Wheel event data. */
|
||||
private onWheelWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/** Change event data. */
|
||||
private onChangeWrapper: browserEvents.Data|null = null;
|
||||
|
||||
/** Input event data. */
|
||||
private onInputWrapper: browserEvents.Data|null = null;
|
||||
/**
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/**
|
||||
* The SVG element that contains the text edit area, or null if not created.
|
||||
@@ -149,14 +142,14 @@ export class Comment extends Icon {
|
||||
body.appendChild(textarea);
|
||||
this.foreignObject!.appendChild(body);
|
||||
|
||||
this.onMouseUpWrapper = browserEvents.conditionalBind(
|
||||
textarea, 'focus', this, this.startEdit, true);
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
textarea, 'focus', this, this.startEdit, true));
|
||||
// Don't zoom with mousewheel.
|
||||
this.onWheelWrapper = browserEvents.conditionalBind(
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
textarea, 'wheel', this, function(e: Event) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
this.onChangeWrapper = browserEvents.conditionalBind(
|
||||
}));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
textarea, 'change', this,
|
||||
/**
|
||||
* @param _e Unused event parameter.
|
||||
@@ -167,15 +160,15 @@ export class Comment extends Icon {
|
||||
this.getBlock(), 'comment', null, this.cachedText,
|
||||
this.model.text));
|
||||
}
|
||||
});
|
||||
this.onInputWrapper = browserEvents.conditionalBind(
|
||||
}));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
textarea, 'input', this,
|
||||
/**
|
||||
* @param _e Unused event parameter.
|
||||
*/
|
||||
function(this: Comment, _e: Event) {
|
||||
this.model.text = textarea.value;
|
||||
});
|
||||
}));
|
||||
|
||||
setTimeout(textarea.focus.bind(textarea), 0);
|
||||
|
||||
@@ -279,22 +272,10 @@ export class Comment extends Icon {
|
||||
* Dispose of the bubble.
|
||||
*/
|
||||
private disposeBubble() {
|
||||
if (this.onMouseUpWrapper) {
|
||||
browserEvents.unbind(this.onMouseUpWrapper);
|
||||
this.onMouseUpWrapper = null;
|
||||
}
|
||||
if (this.onWheelWrapper) {
|
||||
browserEvents.unbind(this.onWheelWrapper);
|
||||
this.onWheelWrapper = null;
|
||||
}
|
||||
if (this.onChangeWrapper) {
|
||||
browserEvents.unbind(this.onChangeWrapper);
|
||||
this.onChangeWrapper = null;
|
||||
}
|
||||
if (this.onInputWrapper) {
|
||||
browserEvents.unbind(this.onInputWrapper);
|
||||
this.onInputWrapper = null;
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
this.boundEvents.length = 0;
|
||||
if (this.bubble_) {
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common functions used both internally and externally, but which
|
||||
* must not be at the top level to avoid circular dependencies.
|
||||
*
|
||||
* @namespace Blockly.common
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.common');
|
||||
|
||||
@@ -79,7 +73,6 @@ let mainWorkspace: Workspace;
|
||||
* page.
|
||||
*
|
||||
* @returns The main workspace.
|
||||
* @alias Blockly.common.getMainWorkspace
|
||||
*/
|
||||
export function getMainWorkspace(): Workspace {
|
||||
return mainWorkspace;
|
||||
@@ -89,7 +82,6 @@ export function getMainWorkspace(): Workspace {
|
||||
* Sets last used main workspace.
|
||||
*
|
||||
* @param workspace The most recently used top level workspace.
|
||||
* @alias Blockly.common.setMainWorkspace
|
||||
*/
|
||||
export function setMainWorkspace(workspace: Workspace) {
|
||||
mainWorkspace = workspace;
|
||||
@@ -102,8 +94,6 @@ let selected: ICopyable|null = null;
|
||||
|
||||
/**
|
||||
* Returns the currently selected copyable object.
|
||||
*
|
||||
* @alias Blockly.common.getSelected
|
||||
*/
|
||||
export function getSelected(): ICopyable|null {
|
||||
return selected;
|
||||
@@ -115,7 +105,6 @@ export function getSelected(): ICopyable|null {
|
||||
* programmatically select a block, use `BlockSvg#select`.
|
||||
*
|
||||
* @param newSelection The newly selected block.
|
||||
* @alias Blockly.common.setSelected
|
||||
* @internal
|
||||
*/
|
||||
export function setSelected(newSelection: ICopyable|null) {
|
||||
@@ -132,7 +121,6 @@ let parentContainer: Element|null;
|
||||
* Tooltip.
|
||||
*
|
||||
* @returns The parent container.
|
||||
* @alias Blockly.common.getParentContainer
|
||||
*/
|
||||
export function getParentContainer(): Element|null {
|
||||
return parentContainer;
|
||||
@@ -145,7 +133,6 @@ export function getParentContainer(): Element|null {
|
||||
* This method is a NOP if called after the first `Blockly.inject`.
|
||||
*
|
||||
* @param newParent The container element.
|
||||
* @alias Blockly.common.setParentContainer
|
||||
*/
|
||||
export function setParentContainer(newParent: Element) {
|
||||
parentContainer = newParent;
|
||||
@@ -159,7 +146,6 @@ export function setParentContainer(newParent: Element) {
|
||||
* Record the height/width of the SVG image.
|
||||
*
|
||||
* @param workspace Any workspace in the SVG.
|
||||
* @alias Blockly.common.svgResize
|
||||
*/
|
||||
export function svgResize(workspace: WorkspaceSvg) {
|
||||
let mainWorkspace = workspace;
|
||||
@@ -201,7 +187,6 @@ export const draggingConnections: Connection[] = [];
|
||||
* statements (blocks that are not inside a value or statement input
|
||||
* of the block).
|
||||
* @returns Map of types to type counts for descendants of the bock.
|
||||
* @alias Blockly.common.getBlockTypeCounts
|
||||
*/
|
||||
export function getBlockTypeCounts(
|
||||
block: Block, opt_stripFollowing?: boolean): {[key: string]: number} {
|
||||
@@ -243,7 +228,6 @@ function jsonInitFactory(jsonDef: AnyDuringMigration): () => void {
|
||||
* by the Blockly Developer Tools.
|
||||
*
|
||||
* @param jsonArray An array of JSON block definitions.
|
||||
* @alias Blockly.common.defineBlocksWithJsonArray
|
||||
*/
|
||||
export function defineBlocksWithJsonArray(jsonArray: AnyDuringMigration[]) {
|
||||
TEST_ONLY.defineBlocksWithJsonArrayInternal(jsonArray);
|
||||
@@ -263,7 +247,6 @@ function defineBlocksWithJsonArrayInternal(jsonArray: AnyDuringMigration[]) {
|
||||
* @param jsonArray An array of JSON block definitions.
|
||||
* @returns A map of the block
|
||||
* definitions created.
|
||||
* @alias Blockly.common.defineBlocksWithJsonArray
|
||||
*/
|
||||
export function createBlockDefinitionsFromJsonArray(
|
||||
jsonArray: AnyDuringMigration[]): {[key: string]: BlockDefinition} {
|
||||
@@ -292,7 +275,6 @@ export function createBlockDefinitionsFromJsonArray(
|
||||
*
|
||||
* @param blocks A map of block
|
||||
* type names to block definitions.
|
||||
* @alias Blockly.common.defineBlocks
|
||||
*/
|
||||
export function defineBlocks(blocks: {[key: string]: BlockDefinition}) {
|
||||
// Iterate over own enumerable properties.
|
||||
|
||||
@@ -43,8 +43,6 @@ class Capability<_T> {
|
||||
|
||||
/**
|
||||
* Manager for all items registered with the workspace.
|
||||
*
|
||||
* @alias Blockly.ComponentManager
|
||||
*/
|
||||
export class ComponentManager {
|
||||
static Capability = Capability;
|
||||
@@ -120,7 +118,7 @@ export class ComponentManager {
|
||||
'Plugin "' + id + 'already has capability "' + capability + '"');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
this.componentData.get(id)?.capabilities.push(capability);
|
||||
this.capabilityToComponentIds.get(capability)?.push(id);
|
||||
}
|
||||
@@ -143,7 +141,7 @@ export class ComponentManager {
|
||||
'" to remove');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
arrayUtils.removeElem(this.componentData.get(id)!.capabilities, capability);
|
||||
arrayUtils.removeElem(this.capabilityToComponentIds.get(capability)!, id);
|
||||
}
|
||||
@@ -156,7 +154,7 @@ export class ComponentManager {
|
||||
* @returns Whether the component has the capability.
|
||||
*/
|
||||
hasCapability<T>(id: string, capability: string|Capability<T>): boolean {
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
return this.componentData.has(id) &&
|
||||
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1;
|
||||
}
|
||||
@@ -180,7 +178,7 @@ export class ComponentManager {
|
||||
*/
|
||||
getComponents<T extends IComponent>(
|
||||
capability: string|Capability<T>, sorted: boolean): T[] {
|
||||
capability = String(capability).toLowerCase();
|
||||
capability = `${capability}`.toLowerCase();
|
||||
const componentIds = this.capabilityToComponentIds.get(capability);
|
||||
if (!componentIds) {
|
||||
return [];
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* All the values that we expect developers to be able to change
|
||||
* before injecting Blockly. Changing these values during run time is not
|
||||
* generally recommended.
|
||||
*
|
||||
* @namespace Blockly.config
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.config');
|
||||
|
||||
@@ -39,7 +32,6 @@ export const config: Config = {
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag starts.
|
||||
*
|
||||
* @alias Blockly.config.dragRadius
|
||||
*/
|
||||
dragRadius: 5,
|
||||
/**
|
||||
@@ -47,20 +39,17 @@ export const config: Config = {
|
||||
* flyout. Because the drag-intention is determined when this is reached, it
|
||||
* is larger than dragRadius so that the drag-direction is clearer.
|
||||
*
|
||||
* @alias Blockly.config.flyoutDragRadius
|
||||
*/
|
||||
flyoutDragRadius: 10,
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together.
|
||||
*
|
||||
* @alias Blockly.config.snapRadius
|
||||
*/
|
||||
snapRadius: DEFAULT_SNAP_RADIUS,
|
||||
/**
|
||||
* Maximum misalignment between connections for them to snap together.
|
||||
* This should be the same as the snap radius.
|
||||
*
|
||||
* @alias Blockly.config.connectingSnapRadius
|
||||
*/
|
||||
connectingSnapRadius: DEFAULT_SNAP_RADIUS,
|
||||
/**
|
||||
@@ -69,13 +58,11 @@ export const config: Config = {
|
||||
* this much closer to the matching connection on the block than it actually
|
||||
* is.
|
||||
*
|
||||
* @alias Blockly.config.currentConnectionPreference
|
||||
*/
|
||||
currentConnectionPreference: 8,
|
||||
/**
|
||||
* Delay in ms between trigger and bumping unconnected block out of alignment.
|
||||
*
|
||||
* @alias Blockly.config.bumpDelay
|
||||
*/
|
||||
bumpDelay: 250,
|
||||
};
|
||||
|
||||
@@ -25,8 +25,6 @@ import * as Xml from './xml.js';
|
||||
|
||||
/**
|
||||
* Class for a connection between blocks.
|
||||
*
|
||||
* @alias Blockly.Connection
|
||||
*/
|
||||
export class Connection implements IASTNodeLocationWithBlock {
|
||||
/** Constants for checking whether two connections are compatible. */
|
||||
@@ -95,7 +93,7 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
|
||||
// Make sure the childConnection is available.
|
||||
if (childConnection.isConnected()) {
|
||||
childConnection.disconnect();
|
||||
childConnection.disconnectInternal(false);
|
||||
}
|
||||
|
||||
// Make sure the parentConnection is available.
|
||||
@@ -106,7 +104,7 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
if (target!.isShadow()) {
|
||||
target!.dispose(false);
|
||||
} else {
|
||||
this.disconnect();
|
||||
this.disconnectInternal();
|
||||
orphan = target;
|
||||
}
|
||||
this.applyShadowState_(shadowState);
|
||||
@@ -129,8 +127,9 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
if (orphan) {
|
||||
const orphanConnection = this.type === INPUT ? orphan.outputConnection :
|
||||
orphan.previousConnection;
|
||||
if (!orphanConnection) return;
|
||||
const connection = Connection.getConnectionForOrphanedConnection(
|
||||
childBlock, (orphanConnection));
|
||||
childBlock, orphanConnection);
|
||||
if (connection) {
|
||||
orphanConnection.connect(connection);
|
||||
} else {
|
||||
@@ -151,7 +150,7 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
this.setShadowStateInternal_();
|
||||
|
||||
const targetBlock = this.targetBlock();
|
||||
if (targetBlock) {
|
||||
if (targetBlock && !targetBlock.isDeadOrDying()) {
|
||||
// Disconnect the attached normal block.
|
||||
targetBlock.unplug();
|
||||
}
|
||||
@@ -224,8 +223,8 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
|
||||
const checker = this.getConnectionChecker();
|
||||
if (checker.canConnect(this, otherConnection, false)) {
|
||||
const eventGroup = eventUtils.getGroup();
|
||||
if (!eventGroup) {
|
||||
const existingGroup = eventUtils.getGroup();
|
||||
if (!existingGroup) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
// Determine which block is superior (higher in the source stack).
|
||||
@@ -236,75 +235,83 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
// Inferior block.
|
||||
otherConnection.connect_(this);
|
||||
}
|
||||
if (!eventGroup) {
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
eventUtils.setGroup(existingGroup);
|
||||
}
|
||||
|
||||
return this.isConnected();
|
||||
}
|
||||
|
||||
/** Disconnect this connection. */
|
||||
/**
|
||||
* Disconnect this connection.
|
||||
*/
|
||||
disconnect() {
|
||||
const otherConnection = this.targetConnection;
|
||||
if (!otherConnection) {
|
||||
throw Error('Source connection not connected.');
|
||||
}
|
||||
if (otherConnection.targetConnection !== this) {
|
||||
throw Error('Target connection not connected to source connection.');
|
||||
}
|
||||
let parentBlock;
|
||||
let childBlock;
|
||||
let parentConnection;
|
||||
if (this.isSuperior()) {
|
||||
// Superior block.
|
||||
parentBlock = this.sourceBlock_;
|
||||
childBlock = otherConnection.getSourceBlock();
|
||||
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
||||
parentConnection = this;
|
||||
} else {
|
||||
// Inferior block.
|
||||
parentBlock = otherConnection.getSourceBlock();
|
||||
childBlock = this.sourceBlock_;
|
||||
parentConnection = otherConnection;
|
||||
}
|
||||
|
||||
const eventGroup = eventUtils.getGroup();
|
||||
if (!eventGroup) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
this.disconnectInternal_(parentBlock, childBlock);
|
||||
if (!childBlock.isShadow()) {
|
||||
// If we were disconnecting a shadow, no need to spawn a new one.
|
||||
parentConnection.respawnShadow_();
|
||||
}
|
||||
if (!eventGroup) {
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
this.disconnectInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect two blocks that are connected by this connection.
|
||||
*
|
||||
* @param parentBlock The superior block.
|
||||
* @param childBlock The inferior block.
|
||||
* @param setParent Whether to set the parent of the disconnected block or
|
||||
* not, defaults to true.
|
||||
* If you do not set the parent, ensure that a subsequent action does,
|
||||
* otherwise the view and model will be out of sync.
|
||||
*/
|
||||
protected disconnectInternal_(parentBlock: Block, childBlock: Block) {
|
||||
protected disconnectInternal(setParent = true) {
|
||||
const {parentConnection, childConnection} =
|
||||
this.getParentAndChildConnections();
|
||||
if (!parentConnection || !childConnection) {
|
||||
throw Error('Source connection not connected.');
|
||||
}
|
||||
|
||||
const existingGroup = eventUtils.getGroup();
|
||||
if (!existingGroup) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
|
||||
let event;
|
||||
if (eventUtils.isEnabled()) {
|
||||
event =
|
||||
new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock) as BlockMove;
|
||||
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
|
||||
childConnection.getSourceBlock()) as BlockMove;
|
||||
}
|
||||
const otherConnection = this.targetConnection;
|
||||
if (otherConnection) {
|
||||
otherConnection.targetConnection = null;
|
||||
}
|
||||
this.targetConnection = null;
|
||||
childBlock.setParent(null);
|
||||
if (setParent) childConnection.getSourceBlock().setParent(null);
|
||||
if (event) {
|
||||
event.recordNew();
|
||||
eventUtils.fire(event);
|
||||
}
|
||||
|
||||
if (!childConnection.getSourceBlock().isShadow()) {
|
||||
// If we were disconnecting a shadow, no need to spawn a new one.
|
||||
parentConnection.respawnShadow_();
|
||||
}
|
||||
|
||||
eventUtils.setGroup(existingGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent connection (superior) and child connection (inferior)
|
||||
* given this connection and the connection it is connected to.
|
||||
*
|
||||
* @returns The parent connection and child connection, given this connection
|
||||
* and the connection it is connected to.
|
||||
*/
|
||||
protected getParentAndChildConnections():
|
||||
{parentConnection?: Connection, childConnection?: Connection} {
|
||||
if (!this.targetConnection) return {};
|
||||
if (this.isSuperior()) {
|
||||
return {
|
||||
parentConnection: this,
|
||||
childConnection: this.targetConnection,
|
||||
};
|
||||
}
|
||||
return {
|
||||
parentConnection: this.targetConnection,
|
||||
childConnection: this,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -545,6 +552,7 @@ export class Connection implements IASTNodeLocationWithBlock {
|
||||
}
|
||||
} else if (target.isShadow()) {
|
||||
target.dispose(false);
|
||||
if (this.getSourceBlock().isDeadOrDying()) return;
|
||||
this.respawnShadow_();
|
||||
if (this.targetBlock() && this.targetBlock()!.isShadow()) {
|
||||
this.serializeShadow_(this.targetBlock());
|
||||
@@ -678,11 +686,11 @@ function getSingleConnection(block: Block, orphanBlock: Block): Connection|
|
||||
null {
|
||||
let foundConnection = null;
|
||||
const output = orphanBlock.outputConnection;
|
||||
const typeChecker = output.getConnectionChecker();
|
||||
const typeChecker = output?.getConnectionChecker();
|
||||
|
||||
for (let i = 0, input; input = block.inputList[i]; i++) {
|
||||
const connection = input.connection;
|
||||
if (connection && typeChecker.canConnect(output, connection, false)) {
|
||||
if (connection && typeChecker?.canConnect(output, connection, false)) {
|
||||
if (foundConnection) {
|
||||
return null; // More than one connection.
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ import type {RenderedConnection} from './rendered_connection.js';
|
||||
|
||||
/**
|
||||
* Class for connection type checking logic.
|
||||
*
|
||||
* @alias Blockly.ConnectionChecker
|
||||
*/
|
||||
export class ConnectionChecker implements IConnectionChecker {
|
||||
/**
|
||||
@@ -203,7 +201,7 @@ export class ConnectionChecker implements IConnectionChecker {
|
||||
/**
|
||||
* Check whether this connection can be made by dragging.
|
||||
*
|
||||
* @param a Connection to compare.
|
||||
* @param a Connection to compare (on the block that's being dragged).
|
||||
* @param b Connection to compare against.
|
||||
* @param distance The maximum allowable distance between connections.
|
||||
* @returns True if the connection is allowed during a drag.
|
||||
@@ -250,6 +248,12 @@ export class ConnectionChecker implements IConnectionChecker {
|
||||
!b.targetBlock()!.isShadow() && b.targetBlock()!.nextConnection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't offer to splice into a stack where the connected block is
|
||||
// immovable.
|
||||
if (b.targetBlock() && !b.targetBlock()!.isMovable()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -24,8 +24,6 @@ import type {Coordinate} from './utils/coordinate.js';
|
||||
* 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.
|
||||
*
|
||||
* @alias Blockly.ConnectionDB
|
||||
*/
|
||||
export class ConnectionDB {
|
||||
/** Array of connections sorted by y position in workspace units. */
|
||||
|
||||
@@ -4,19 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* An enum for the possible types of connections.
|
||||
*
|
||||
* @namespace Blockly.ConnectionType
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ConnectionType');
|
||||
|
||||
|
||||
/**
|
||||
* Enum for the type of a connection or input.
|
||||
*
|
||||
* @alias Blockly.ConnectionType
|
||||
*/
|
||||
export enum ConnectionType {
|
||||
// A right-facing value input. E.g. 'set item to' or 'return'.
|
||||
|
||||
@@ -4,25 +4,16 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Blockly constants.
|
||||
*
|
||||
* @namespace Blockly.constants
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.constants');
|
||||
|
||||
|
||||
/**
|
||||
* The language-neutral ID given to the collapsed input.
|
||||
*
|
||||
* @alias Blockly.constants.COLLAPSED_INPUT_NAME
|
||||
*/
|
||||
export const COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
|
||||
|
||||
/**
|
||||
* The language-neutral ID given to the collapsed field.
|
||||
*
|
||||
* @alias Blockly.constants.COLLAPSED_FIELD_NAME
|
||||
*/
|
||||
export const COLLAPSED_FIELD_NAME = '_TEMP_COLLAPSED_FIELD';
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Functionality for the right-click context menus.
|
||||
*
|
||||
* @namespace Blockly.ContextMenu
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ContextMenu');
|
||||
|
||||
@@ -44,7 +39,6 @@ const dummyOwner = {};
|
||||
* Gets the block the context menu is currently attached to.
|
||||
*
|
||||
* @returns The block the context menu is attached to.
|
||||
* @alias Blockly.ContextMenu.getCurrentBlock
|
||||
*/
|
||||
export function getCurrentBlock(): Block|null {
|
||||
return currentBlock;
|
||||
@@ -54,7 +48,6 @@ export function getCurrentBlock(): Block|null {
|
||||
* Sets the block the context menu is currently attached to.
|
||||
*
|
||||
* @param block The block the context menu is attached to.
|
||||
* @alias Blockly.ContextMenu.setCurrentBlock
|
||||
*/
|
||||
export function setCurrentBlock(block: Block|null) {
|
||||
currentBlock = block;
|
||||
@@ -71,7 +64,6 @@ let menu_: Menu|null = null;
|
||||
* @param e Mouse event.
|
||||
* @param options Array of menu options.
|
||||
* @param rtl True if RTL, false if LTR.
|
||||
* @alias Blockly.ContextMenu.show
|
||||
*/
|
||||
export function show(
|
||||
e: Event, options: (ContextMenuOption|LegacyContextMenuOption)[],
|
||||
@@ -120,11 +112,15 @@ function populate_(
|
||||
if (option.enabled) {
|
||||
const actionHandler = function() {
|
||||
hide();
|
||||
// If .scope does not exist on the option, then the callback will not
|
||||
// be expecting a scope parameter, so there should be no problems. Just
|
||||
// assume it is a ContextMenuOption and we'll pass undefined if it's
|
||||
// not.
|
||||
option.callback((option as ContextMenuOption).scope);
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => {
|
||||
// If .scope does not exist on the option, then the callback
|
||||
// will not be expecting a scope parameter, so there should be
|
||||
// no problems. Just assume it is a ContextMenuOption and we'll
|
||||
// pass undefined if it's not.
|
||||
option.callback((option as ContextMenuOption).scope);
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
menuItem.onAction(actionHandler, {});
|
||||
}
|
||||
@@ -200,8 +196,6 @@ function haltPropagation(e: Event) {
|
||||
|
||||
/**
|
||||
* Hide the context menu.
|
||||
*
|
||||
* @alias Blockly.ContextMenu.hide
|
||||
*/
|
||||
export function hide() {
|
||||
WidgetDiv.hideIfOwner(dummyOwner);
|
||||
@@ -210,8 +204,6 @@ export function hide() {
|
||||
|
||||
/**
|
||||
* Dispose of the menu.
|
||||
*
|
||||
* @alias Blockly.ContextMenu.dispose
|
||||
*/
|
||||
export function dispose() {
|
||||
if (menu_) {
|
||||
@@ -227,7 +219,6 @@ export function dispose() {
|
||||
* @param block Original block.
|
||||
* @param xml XML representation of new block.
|
||||
* @returns Function that creates a block.
|
||||
* @alias Blockly.ContextMenu.callbackFactory
|
||||
*/
|
||||
export function callbackFactory(block: Block, xml: Element): Function {
|
||||
return () => {
|
||||
@@ -263,7 +254,6 @@ export function callbackFactory(block: Block, xml: Element): Function {
|
||||
* right-click originated.
|
||||
* @returns A menu option,
|
||||
* containing text, enabled, and a callback.
|
||||
* @alias Blockly.ContextMenu.commentDeleteOption
|
||||
* @internal
|
||||
*/
|
||||
export function commentDeleteOption(comment: WorkspaceCommentSvg):
|
||||
@@ -287,7 +277,6 @@ export function commentDeleteOption(comment: WorkspaceCommentSvg):
|
||||
* right-click originated.
|
||||
* @returns A menu option,
|
||||
* containing text, enabled, and a callback.
|
||||
* @alias Blockly.ContextMenu.commentDuplicateOption
|
||||
* @internal
|
||||
*/
|
||||
export function commentDuplicateOption(comment: WorkspaceCommentSvg):
|
||||
@@ -311,7 +300,6 @@ export function commentDuplicateOption(comment: WorkspaceCommentSvg):
|
||||
* @returns A menu option, containing text, enabled, and a callback.
|
||||
* @suppress {strictModuleDepCheck,checkTypes} Suppress checks while workspace
|
||||
* comments are not bundled in.
|
||||
* @alias Blockly.ContextMenu.workspaceCommentOption
|
||||
* @internal
|
||||
*/
|
||||
export function workspaceCommentOption(
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers default context menu items.
|
||||
*
|
||||
* @namespace Blockly.ContextMenuItems
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ContextMenuItems');
|
||||
|
||||
@@ -20,14 +15,11 @@ import * as Events from './events/events.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import {inputTypes} from './input_types.js';
|
||||
import {Msg} from './msg.js';
|
||||
import * as idGenerator from './utils/idgenerator.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* Option to undo previous action.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerUndo
|
||||
*/
|
||||
export function registerUndo() {
|
||||
const undoOption: RegistryItem = {
|
||||
@@ -52,8 +44,6 @@ export function registerUndo() {
|
||||
|
||||
/**
|
||||
* Option to redo previous action.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerRedo
|
||||
*/
|
||||
export function registerRedo() {
|
||||
const redoOption: RegistryItem = {
|
||||
@@ -78,8 +68,6 @@ export function registerRedo() {
|
||||
|
||||
/**
|
||||
* Option to clean up blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerCleanup
|
||||
*/
|
||||
export function registerCleanup() {
|
||||
const cleanOption: RegistryItem = {
|
||||
@@ -135,8 +123,6 @@ function toggleOption_(shouldCollapse: boolean, topBlocks: BlockSvg[]) {
|
||||
|
||||
/**
|
||||
* Option to collapse all blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerCollapse
|
||||
*/
|
||||
export function registerCollapse() {
|
||||
const collapseOption: RegistryItem = {
|
||||
@@ -171,8 +157,6 @@ export function registerCollapse() {
|
||||
|
||||
/**
|
||||
* Option to expand all blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerExpand
|
||||
*/
|
||||
export function registerExpand() {
|
||||
const expandOption: RegistryItem = {
|
||||
@@ -240,13 +224,18 @@ function getDeletableBlocks_(workspace: WorkspaceSvg): BlockSvg[] {
|
||||
/**
|
||||
* Deletes the given blocks. Used to delete all blocks in the workspace.
|
||||
*
|
||||
* @param deleteList list of blocks to delete.
|
||||
* @param eventGroup event group ID with which all delete events should be
|
||||
* associated.
|
||||
* @param deleteList List of blocks to delete.
|
||||
* @param eventGroup Event group ID with which all delete events should be
|
||||
* associated. If not specified, create a new group.
|
||||
*/
|
||||
function deleteNext_(deleteList: BlockSvg[], eventGroup: string) {
|
||||
function deleteNext_(deleteList: BlockSvg[], eventGroup?: string) {
|
||||
const DELAY = 10;
|
||||
eventUtils.setGroup(eventGroup);
|
||||
if (eventGroup) {
|
||||
eventUtils.setGroup(eventGroup);
|
||||
} else {
|
||||
eventUtils.setGroup(true);
|
||||
eventGroup = eventUtils.getGroup();
|
||||
}
|
||||
const block = deleteList.shift();
|
||||
if (block) {
|
||||
if (!block.isDeadOrDying()) {
|
||||
@@ -261,8 +250,6 @@ function deleteNext_(deleteList: BlockSvg[], eventGroup: string) {
|
||||
|
||||
/**
|
||||
* Option to delete all blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDeleteAll
|
||||
*/
|
||||
export function registerDeleteAll() {
|
||||
const deleteOption: RegistryItem = {
|
||||
@@ -273,10 +260,8 @@ export function registerDeleteAll() {
|
||||
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
|
||||
if (deletableBlocksLength === 1) {
|
||||
return Msg['DELETE_BLOCK'];
|
||||
} else {
|
||||
return Msg['DELETE_X_BLOCKS'].replace(
|
||||
'%1', String(deletableBlocksLength));
|
||||
}
|
||||
return Msg['DELETE_X_BLOCKS'].replace('%1', `${deletableBlocksLength}`);
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (!scope.workspace) {
|
||||
@@ -291,16 +276,15 @@ export function registerDeleteAll() {
|
||||
}
|
||||
scope.workspace.cancelCurrentGesture();
|
||||
const deletableBlocks = getDeletableBlocks_(scope.workspace);
|
||||
const eventGroup = idGenerator.genUid();
|
||||
if (deletableBlocks.length < 2) {
|
||||
deleteNext_(deletableBlocks, eventGroup);
|
||||
deleteNext_(deletableBlocks);
|
||||
} else {
|
||||
dialog.confirm(
|
||||
Msg['DELETE_ALL_BLOCKS'].replace(
|
||||
'%1', String(deletableBlocks.length)),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
deleteNext_(deletableBlocks, eventGroup);
|
||||
deleteNext_(deletableBlocks);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -323,8 +307,6 @@ function registerWorkspaceOptions_() {
|
||||
|
||||
/**
|
||||
* Option to duplicate a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDuplicate
|
||||
*/
|
||||
export function registerDuplicate() {
|
||||
const duplicateOption: RegistryItem = {
|
||||
@@ -355,8 +337,6 @@ export function registerDuplicate() {
|
||||
|
||||
/**
|
||||
* Option to add or remove block-level comment.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerComment
|
||||
*/
|
||||
export function registerComment() {
|
||||
const commentOption: RegistryItem = {
|
||||
@@ -393,8 +373,6 @@ export function registerComment() {
|
||||
|
||||
/**
|
||||
* Option to inline variables.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerInline
|
||||
*/
|
||||
export function registerInline() {
|
||||
const inlineOption: RegistryItem = {
|
||||
@@ -428,8 +406,6 @@ export function registerInline() {
|
||||
|
||||
/**
|
||||
* Option to collapse or expand a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerCollapseExpandBlock
|
||||
*/
|
||||
export function registerCollapseExpandBlock() {
|
||||
const collapseExpandOption: RegistryItem = {
|
||||
@@ -457,8 +433,6 @@ export function registerCollapseExpandBlock() {
|
||||
|
||||
/**
|
||||
* Option to disable or enable a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDisable
|
||||
*/
|
||||
export function registerDisable() {
|
||||
const disableOption: RegistryItem = {
|
||||
@@ -479,14 +453,12 @@ export function registerDisable() {
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
const block = scope.block;
|
||||
const group = eventUtils.getGroup();
|
||||
if (!group) {
|
||||
const existingGroup = eventUtils.getGroup();
|
||||
if (!existingGroup) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
block!.setEnabled(!block!.isEnabled());
|
||||
if (!group) {
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
eventUtils.setGroup(existingGroup);
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDisable',
|
||||
@@ -497,8 +469,6 @@ export function registerDisable() {
|
||||
|
||||
/**
|
||||
* Option to delete a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDelete
|
||||
*/
|
||||
export function registerDelete() {
|
||||
const deleteOption: RegistryItem = {
|
||||
@@ -513,7 +483,7 @@ export function registerDelete() {
|
||||
}
|
||||
return descendantCount === 1 ?
|
||||
Msg['DELETE_BLOCK'] :
|
||||
Msg['DELETE_X_BLOCKS'].replace('%1', String(descendantCount));
|
||||
Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`);
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {
|
||||
@@ -535,8 +505,6 @@ export function registerDelete() {
|
||||
|
||||
/**
|
||||
* Option to open help for a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerHelp
|
||||
*/
|
||||
export function registerHelp() {
|
||||
const helpOption: RegistryItem = {
|
||||
@@ -577,7 +545,6 @@ function registerBlockOptions_() {
|
||||
* Registers all default context menu items. This should be called once per
|
||||
* instance of ContextMenuRegistry.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDefaultOptions
|
||||
* @internal
|
||||
*/
|
||||
export function registerDefaultOptions() {
|
||||
|
||||
@@ -20,8 +20,6 @@ import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
* Class for the registry of context menu items. This is intended to be a
|
||||
* singleton. You should not create a new instance, and only access this class
|
||||
* from ContextMenuRegistry.registry.
|
||||
*
|
||||
* @alias Blockly.ContextMenuRegistry
|
||||
*/
|
||||
export class ContextMenuRegistry {
|
||||
static registry: ContextMenuRegistry;
|
||||
|
||||
32
core/css.ts
32
core/css.ts
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Inject Blockly's CSS synchronously.
|
||||
*
|
||||
* @namespace Blockly.Css
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Css');
|
||||
|
||||
@@ -21,7 +16,6 @@ let injected = false;
|
||||
* components such as fields and the toolbox to store separate CSS.
|
||||
*
|
||||
* @param cssContent Multiline CSS string or an array of single lines of CSS.
|
||||
* @alias Blockly.Css.register
|
||||
*/
|
||||
export function register(cssContent: string) {
|
||||
if (injected) {
|
||||
@@ -40,7 +34,6 @@ export function register(cssContent: string) {
|
||||
* @param hasCss If false, don't inject CSS (providing CSS becomes the
|
||||
* document's responsibility).
|
||||
* @param pathToMedia Path from page to the Blockly media directory.
|
||||
* @alias Blockly.Css.inject
|
||||
*/
|
||||
export function inject(hasCss: boolean, pathToMedia: string) {
|
||||
// Only inject the CSS once.
|
||||
@@ -67,8 +60,6 @@ export function inject(hasCss: boolean, pathToMedia: string) {
|
||||
|
||||
/**
|
||||
* The CSS content for Blockly.
|
||||
*
|
||||
* @alias Blockly.Css.content
|
||||
*/
|
||||
let content = `
|
||||
.blocklySvg {
|
||||
@@ -235,16 +226,11 @@ let content = `
|
||||
}
|
||||
|
||||
.blocklyDraggable {
|
||||
/* backup for browsers (e.g. IE11) that don't support grab */
|
||||
cursor: url("<<<PATH>>>/handopen.cur"), auto;
|
||||
cursor: grab;
|
||||
cursor: -webkit-grab;
|
||||
}
|
||||
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
.blocklyDragging {
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
cursor: url("<<<PATH>>>/handclosed.cur"), auto;
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
@@ -252,8 +238,6 @@ let content = `
|
||||
/* Changes cursor on mouse down. Not effective in Firefox because of
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=771241 */
|
||||
.blocklyDraggable:active {
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
cursor: url("<<<PATH>>>/handclosed.cur"), auto;
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
@@ -262,8 +246,6 @@ let content = `
|
||||
ahead of block during a drag. This way the cursor is still a closed hand.
|
||||
*/
|
||||
.blocklyBlockDragSurface .blocklyDraggable {
|
||||
/* backup for browsers (e.g. IE11) that don't support grabbing */
|
||||
cursor: url("<<<PATH>>>/handclosed.cur"), auto;
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
@@ -373,11 +355,15 @@ let content = `
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Edge and IE introduce a close icon when the input value is longer than a
|
||||
certain length. This affects our sizing calculations of the text input.
|
||||
Hiding the close icon to avoid that. */
|
||||
.blocklyHtmlInput::-ms-clear {
|
||||
display: none;
|
||||
/* Remove the increase and decrease arrows on the field number editor */
|
||||
input.blocklyHtmlInput[type=number]::-webkit-inner-spin-button,
|
||||
input.blocklyHtmlInput[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.blocklyMainBackground {
|
||||
|
||||
@@ -22,8 +22,6 @@ import type {IDraggable} from './interfaces/i_draggable.js';
|
||||
/**
|
||||
* Abstract class for a component that can delete a block or bubble that is
|
||||
* dropped on top of it.
|
||||
*
|
||||
* @alias Blockly.DeleteArea
|
||||
*/
|
||||
export class DeleteArea extends DragTarget implements IDeleteArea {
|
||||
/**
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wrapper functions around JS functions for showing alert/confirmation dialogs.
|
||||
*
|
||||
* @namespace Blockly.dialog
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.dialog');
|
||||
|
||||
@@ -37,7 +32,6 @@ let promptImplementation = function(
|
||||
*
|
||||
* @param message The message to display to the user.
|
||||
* @param opt_callback The callback when the alert is dismissed.
|
||||
* @alias Blockly.dialog.alert
|
||||
*/
|
||||
export function alert(message: string, opt_callback?: () => void) {
|
||||
alertImplementation(message, opt_callback);
|
||||
@@ -48,7 +42,6 @@ export function alert(message: string, opt_callback?: () => void) {
|
||||
*
|
||||
* @param alertFunction The function to be run.
|
||||
* @see Blockly.dialog.alert
|
||||
* @alias Blockly.dialog.setAlert
|
||||
*/
|
||||
export function setAlert(alertFunction: (p1: string, p2?: () => void) => void) {
|
||||
alertImplementation = alertFunction;
|
||||
@@ -60,7 +53,6 @@ export function setAlert(alertFunction: (p1: string, p2?: () => void) => void) {
|
||||
*
|
||||
* @param message The message to display to the user.
|
||||
* @param callback The callback for handling user response.
|
||||
* @alias Blockly.dialog.confirm
|
||||
*/
|
||||
export function confirm(message: string, callback: (p1: boolean) => void) {
|
||||
TEST_ONLY.confirmInternal(message, callback);
|
||||
@@ -79,7 +71,6 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) {
|
||||
*
|
||||
* @param confirmFunction The function to be run.
|
||||
* @see Blockly.dialog.confirm
|
||||
* @alias Blockly.dialog.setConfirm
|
||||
*/
|
||||
export function setConfirm(
|
||||
confirmFunction: (p1: string, p2: (p1: boolean) => void) => void) {
|
||||
@@ -95,7 +86,6 @@ export function setConfirm(
|
||||
* @param message The message to display to the user.
|
||||
* @param defaultValue The value to initialize the prompt with.
|
||||
* @param callback The callback for handling user response.
|
||||
* @alias Blockly.dialog.prompt
|
||||
*/
|
||||
export function prompt(
|
||||
message: string, defaultValue: string,
|
||||
@@ -108,7 +98,6 @@ export function prompt(
|
||||
*
|
||||
* @param promptFunction The function to be run.
|
||||
* @see Blockly.dialog.prompt
|
||||
* @alias Blockly.dialog.setPrompt
|
||||
*/
|
||||
export function setPrompt(
|
||||
promptFunction: (p1: string, p2: string, p3: (p1: string|null) => void) =>
|
||||
|
||||
@@ -21,8 +21,6 @@ import type {Rect} from './utils/rect.js';
|
||||
/**
|
||||
* Abstract class for a component with custom behaviour when a block or bubble
|
||||
* is dragged over or dropped on top of it.
|
||||
*
|
||||
* @alias Blockly.DragTarget
|
||||
*/
|
||||
export class DragTarget implements IDragTarget {
|
||||
/**
|
||||
|
||||
@@ -4,16 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Events fired as a result of actions in Blockly's editor.
|
||||
*
|
||||
* @namespace Blockly.Events
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events');
|
||||
|
||||
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
import {Abstract, AbstractEventJson} from './events_abstract.js';
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import {BlockChange, BlockChangeJson} from './events_block_change.js';
|
||||
import {BlockCreate, BlockCreateJson} from './events_block_create.js';
|
||||
@@ -28,16 +23,6 @@ import {CommentCreate, CommentCreateJson} from './events_comment_create.js';
|
||||
import {CommentDelete} from './events_comment_delete.js';
|
||||
import {CommentMove, CommentMoveJson} from './events_comment_move.js';
|
||||
import {MarkerMove, MarkerMoveJson} from './events_marker_move.js';
|
||||
import {ProcedureBase} from './events_procedure_base.js';
|
||||
import {ProcedureChangeReturn} from './events_procedure_change_return.js';
|
||||
import {ProcedureCreate} from './events_procedure_create.js';
|
||||
import {ProcedureDelete} from './events_procedure_delete.js';
|
||||
import {ProcedureEnable} from './events_procedure_enable.js';
|
||||
import {ProcedureRename} from './events_procedure_rename.js';
|
||||
import {ProcedureParameterBase} from './events_procedure_parameter_base.js';
|
||||
import {ProcedureParameterCreate} from './events_procedure_parameter_create.js';
|
||||
import {ProcedureParameterDelete} from './events_procedure_parameter_delete.js';
|
||||
import {ProcedureParameterRename} from './events_procedure_parameter_rename.js';
|
||||
import {Selected, SelectedJson} from './events_selected.js';
|
||||
import {ThemeChange, ThemeChangeJson} from './events_theme_change.js';
|
||||
import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js';
|
||||
@@ -54,7 +39,7 @@ import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js';
|
||||
|
||||
|
||||
// Events.
|
||||
export const Abstract = AbstractEvent;
|
||||
export {Abstract};
|
||||
export {AbstractEventJson};
|
||||
export {BubbleOpen};
|
||||
export {BubbleOpenJson};
|
||||
@@ -87,16 +72,6 @@ export {FinishedLoading};
|
||||
export {FinishedLoadingJson};
|
||||
export {MarkerMove};
|
||||
export {MarkerMoveJson};
|
||||
export {ProcedureBase};
|
||||
export {ProcedureChangeReturn};
|
||||
export {ProcedureCreate};
|
||||
export {ProcedureDelete};
|
||||
export {ProcedureEnable};
|
||||
export {ProcedureRename};
|
||||
export {ProcedureParameterBase};
|
||||
export {ProcedureParameterCreate};
|
||||
export {ProcedureParameterDelete};
|
||||
export {ProcedureParameterRename};
|
||||
export {Selected};
|
||||
export {SelectedJson};
|
||||
export {ThemeChange};
|
||||
|
||||
@@ -22,8 +22,6 @@ import * as eventUtils from './utils.js';
|
||||
|
||||
/**
|
||||
* Abstract class for an event.
|
||||
*
|
||||
* @alias Blockly.Events.Abstract
|
||||
*/
|
||||
export abstract class Abstract {
|
||||
/**
|
||||
@@ -34,7 +32,16 @@ export abstract class Abstract {
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
workspaceId?: string = undefined;
|
||||
|
||||
/**
|
||||
* An ID for the group of events this block is associated with.
|
||||
*
|
||||
* Groups define events that should be treated as an single action from the
|
||||
* user's perspective, and should be undone together.
|
||||
*/
|
||||
group: string;
|
||||
|
||||
/** Whether this event is undoable or not. */
|
||||
recordUndo: boolean;
|
||||
|
||||
/** Whether or not the event is a UI event. */
|
||||
@@ -43,16 +50,8 @@ export abstract class Abstract {
|
||||
/** Type of this event. */
|
||||
type = '';
|
||||
|
||||
/** @alias Blockly.Events.Abstract */
|
||||
constructor() {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
|
||||
/** Sets whether the event should be added to the undo stack. */
|
||||
this.recordUndo = eventUtils.getRecordUndo();
|
||||
}
|
||||
|
||||
@@ -74,6 +73,9 @@ export abstract class Abstract {
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
fromJson(json: AbstractEventJson) {
|
||||
deprecation.warn(
|
||||
'Blockly.Events.Abstract.prototype.fromJson', 'version 9', 'version 10',
|
||||
'Blockly.Events.fromJson');
|
||||
this.isBlank = false;
|
||||
this.group = json['group'] || '';
|
||||
}
|
||||
@@ -89,9 +91,6 @@ export abstract class Abstract {
|
||||
*/
|
||||
static fromJson(json: AbstractEventJson, workspace: Workspace, event: any):
|
||||
Abstract {
|
||||
deprecation.warn(
|
||||
'Blockly.Events.Abstract.prototype.fromJson', 'version 9', 'version 10',
|
||||
'Blockly.Events.fromJson');
|
||||
event.isBlank = false;
|
||||
event.group = json['group'] || '';
|
||||
event.workspaceId = workspace.id;
|
||||
|
||||
@@ -20,12 +20,12 @@ import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a block event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockBase
|
||||
* Abstract class for any event related to blocks.
|
||||
*/
|
||||
export class BlockBase extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
|
||||
/** The ID of the block associated with this event. */
|
||||
blockId?: string;
|
||||
|
||||
/**
|
||||
@@ -38,10 +38,7 @@ export class BlockBase extends AbstractEvent {
|
||||
|
||||
if (!opt_block) return;
|
||||
|
||||
/** The block ID for the block this event pertains to */
|
||||
this.blockId = opt_block.id;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = opt_block.workspace.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {Block} from '../block.js';
|
||||
import type {BlockSvg} from '../block_svg.js';
|
||||
import * as deprecation from '../utils/deprecation.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as utilsXml from '../utils/xml.js';
|
||||
import {Workspace} from '../workspace.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
@@ -24,15 +25,24 @@ import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockChange
|
||||
* Notifies listeners when some element of a block has changed (e.g.
|
||||
* field values, comments, etc).
|
||||
*/
|
||||
export class BlockChange extends BlockBase {
|
||||
override type = eventUtils.BLOCK_CHANGE;
|
||||
/**
|
||||
* The element that changed; one of 'field', 'comment', 'collapsed',
|
||||
* 'disabled', 'inline', or 'mutation'
|
||||
*/
|
||||
element?: string;
|
||||
|
||||
/** The name of the field that changed, if this is a change to a field. */
|
||||
name?: string;
|
||||
|
||||
/** The original value of the element. */
|
||||
oldValue: unknown;
|
||||
|
||||
/** The new value of the element. */
|
||||
newValue: unknown;
|
||||
|
||||
/**
|
||||
@@ -173,7 +183,8 @@ export class BlockChange extends BlockBase {
|
||||
if (block.loadExtraState) {
|
||||
block.loadExtraState(JSON.parse(value as string || '{}'));
|
||||
} else if (block.domToMutation) {
|
||||
block.domToMutation(Xml.textToDom(value as string || '<mutation/>'));
|
||||
block.domToMutation(
|
||||
utilsXml.textToDom(value as string || '<mutation/>'));
|
||||
}
|
||||
eventUtils.fire(
|
||||
new BlockChange(block, 'mutation', null, oldState, value));
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {Block} from '../block.js';
|
||||
import * as deprecation from '../utils/deprecation.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as blocks from '../serialization/blocks.js';
|
||||
import * as utilsXml from '../utils/xml.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
@@ -24,16 +25,21 @@ import {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockCreate
|
||||
* Notifies listeners when a block (or connected stack of blocks) is
|
||||
* created.
|
||||
*/
|
||||
export class BlockCreate extends BlockBase {
|
||||
override type = eventUtils.BLOCK_CREATE;
|
||||
|
||||
/** The XML representation of the created block(s). */
|
||||
xml?: Element|DocumentFragment;
|
||||
ids?: string[];
|
||||
|
||||
/** The JSON respresentation of the created block(s). */
|
||||
json?: blocks.State;
|
||||
|
||||
/** All of the IDs of created blocks. */
|
||||
ids?: string[];
|
||||
|
||||
/** @param opt_block The created block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
@@ -50,7 +56,6 @@ export class BlockCreate extends BlockBase {
|
||||
this.xml = Xml.blockToDomWithXY(opt_block);
|
||||
this.ids = eventUtils.getDescendantIds(opt_block);
|
||||
|
||||
/** JSON representation of the block that was just created. */
|
||||
this.json = blocks.save(opt_block, {addCoordinates: true}) as blocks.State;
|
||||
}
|
||||
|
||||
@@ -95,7 +100,7 @@ export class BlockCreate extends BlockBase {
|
||||
'Blockly.Events.BlockCreate.prototype.fromJson', 'version 9',
|
||||
'version 10', 'Blockly.Events.fromJson');
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
this.xml = utilsXml.textToDom(json['xml']);
|
||||
this.ids = json['ids'];
|
||||
this.json = json['json'] as blocks.State;
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
@@ -117,7 +122,7 @@ export class BlockCreate extends BlockBase {
|
||||
const newEvent =
|
||||
super.fromJson(json, workspace, event ?? new BlockCreate()) as
|
||||
BlockCreate;
|
||||
newEvent.xml = Xml.textToDom(json['xml']);
|
||||
newEvent.xml = utilsXml.textToDom(json['xml']);
|
||||
newEvent.ids = json['ids'];
|
||||
newEvent.json = json['json'] as blocks.State;
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {Block} from '../block.js';
|
||||
import * as deprecation from '../utils/deprecation.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as blocks from '../serialization/blocks.js';
|
||||
import * as utilsXml from '../utils/xml.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
@@ -24,15 +25,22 @@ import {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block deletion event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockDelete
|
||||
* Notifies listeners when a block (or connected stack of blocks) is
|
||||
* deleted.
|
||||
*/
|
||||
export class BlockDelete extends BlockBase {
|
||||
/** The XML representation of the deleted block(s). */
|
||||
oldXml?: Element|DocumentFragment;
|
||||
ids?: string[];
|
||||
wasShadow?: boolean;
|
||||
|
||||
/** The JSON respresentation of the deleted block(s). */
|
||||
oldJson?: blocks.State;
|
||||
|
||||
/** All of the IDs of deleted blocks. */
|
||||
ids?: string[];
|
||||
|
||||
/** True if the deleted block was a shadow block, false otherwise. */
|
||||
wasShadow?: boolean;
|
||||
|
||||
override type = eventUtils.BLOCK_DELETE;
|
||||
|
||||
/** @param opt_block The deleted block. Undefined for a blank event. */
|
||||
@@ -53,11 +61,7 @@ export class BlockDelete extends BlockBase {
|
||||
|
||||
this.oldXml = Xml.blockToDomWithXY(opt_block);
|
||||
this.ids = eventUtils.getDescendantIds(opt_block);
|
||||
|
||||
/** Was the block that was just deleted a shadow? */
|
||||
this.wasShadow = opt_block.isShadow();
|
||||
|
||||
/** JSON representation of the block that was just deleted. */
|
||||
this.oldJson =
|
||||
blocks.save(opt_block, {addCoordinates: true}) as blocks.State;
|
||||
}
|
||||
@@ -109,7 +113,7 @@ export class BlockDelete extends BlockBase {
|
||||
'Blockly.Events.BlockDelete.prototype.fromJson', 'version 9',
|
||||
'version 10', 'Blockly.Events.fromJson');
|
||||
super.fromJson(json);
|
||||
this.oldXml = Xml.textToDom(json['oldXml']);
|
||||
this.oldXml = utilsXml.textToDom(json['oldXml']);
|
||||
this.ids = json['ids'];
|
||||
this.wasShadow =
|
||||
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
|
||||
@@ -133,7 +137,7 @@ export class BlockDelete extends BlockBase {
|
||||
const newEvent =
|
||||
super.fromJson(json, workspace, event ?? new BlockDelete()) as
|
||||
BlockDelete;
|
||||
newEvent.oldXml = Xml.textToDom(json['oldXml']);
|
||||
newEvent.oldXml = utilsXml.textToDom(json['oldXml']);
|
||||
newEvent.ids = json['ids'];
|
||||
newEvent.wasShadow =
|
||||
json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow';
|
||||
|
||||
@@ -22,14 +22,21 @@ import {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block drag event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockDrag
|
||||
* Notifies listeners when a block is being manually dragged/dropped.
|
||||
*/
|
||||
export class BlockDrag extends UiBase {
|
||||
/** The ID of the top-level block being dragged. */
|
||||
blockId?: string;
|
||||
|
||||
/** True if this is the start of a drag, false if this is the end of one. */
|
||||
isStart?: boolean;
|
||||
|
||||
/**
|
||||
* A list of all of the blocks (i.e. all descendants of the block associated
|
||||
* with the block ID) being dragged.
|
||||
*/
|
||||
blocks?: Block[];
|
||||
|
||||
override type = eventUtils.BLOCK_DRAG;
|
||||
|
||||
/**
|
||||
@@ -46,11 +53,7 @@ export class BlockDrag extends UiBase {
|
||||
if (!opt_block) return;
|
||||
|
||||
this.blockId = opt_block.id;
|
||||
|
||||
/** Whether this is the start of a block drag. */
|
||||
this.isStart = opt_isStart;
|
||||
|
||||
/** The blocks affected by this drag event. */
|
||||
this.blocks = opt_blocks;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,18 +30,40 @@ interface BlockLocation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for a block move event. Created before the move.
|
||||
*
|
||||
* @alias Blockly.Events.BlockMove
|
||||
* Notifies listeners when a block is moved. This could be from one
|
||||
* connection to another, or from one location on the workspace to another.
|
||||
*/
|
||||
export class BlockMove extends BlockBase {
|
||||
override type = eventUtils.BLOCK_MOVE;
|
||||
|
||||
/** The ID of the old parent block. Undefined if it was a top-level block. */
|
||||
oldParentId?: string;
|
||||
|
||||
/**
|
||||
* The name of the old input. Undefined if it was a top-level block or the
|
||||
* parent's next block.
|
||||
*/
|
||||
oldInputName?: string;
|
||||
|
||||
/**
|
||||
* The old X and Y workspace coordinates of the block if it was a top level
|
||||
* block. Undefined if it was not a top level block.
|
||||
*/
|
||||
oldCoordinate?: Coordinate;
|
||||
|
||||
/** The ID of the new parent block. Undefined if it is a top-level block. */
|
||||
newParentId?: string;
|
||||
|
||||
/**
|
||||
* The name of the new input. Undefined if it is a top-level block or the
|
||||
* parent's next block.
|
||||
*/
|
||||
newInputName?: string;
|
||||
|
||||
/**
|
||||
* The new X and Y workspace coordinates of the block if it is a top level
|
||||
* block. Undefined if it is not a top level block.
|
||||
*/
|
||||
newCoordinate?: Coordinate;
|
||||
|
||||
/** @param opt_block The moved block. Undefined for a blank event. */
|
||||
@@ -239,7 +261,7 @@ export class BlockMove extends BlockBase {
|
||||
blockConnection = block.previousConnection;
|
||||
}
|
||||
let parentConnection;
|
||||
const connectionType = blockConnection.type;
|
||||
const connectionType = blockConnection?.type;
|
||||
if (inputName) {
|
||||
const input = parentBlock!.getInput(inputName);
|
||||
if (input) {
|
||||
@@ -248,7 +270,7 @@ export class BlockMove extends BlockBase {
|
||||
} else if (connectionType === ConnectionType.PREVIOUS_STATEMENT) {
|
||||
parentConnection = parentBlock!.nextConnection;
|
||||
}
|
||||
if (parentConnection) {
|
||||
if (parentConnection && blockConnection) {
|
||||
blockConnection.connect(parentConnection);
|
||||
} else {
|
||||
console.warn('Can\'t connect to non-existent input: ' + inputName);
|
||||
|
||||
@@ -23,13 +23,17 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
/**
|
||||
* Class for a bubble open event.
|
||||
*
|
||||
* @alias Blockly.Events.BubbleOpen
|
||||
*/
|
||||
export class BubbleOpen extends UiBase {
|
||||
/** The ID of the block the bubble is attached to. */
|
||||
blockId?: string;
|
||||
|
||||
/** True if the bubble is opening, false if closing. */
|
||||
isOpen?: boolean;
|
||||
|
||||
/** The type of bubble; one of 'mutator', 'comment', or 'warning'. */
|
||||
bubbleType?: BubbleType;
|
||||
|
||||
override type = eventUtils.BUBBLE_OPEN;
|
||||
|
||||
/**
|
||||
@@ -46,11 +50,7 @@ export class BubbleOpen extends UiBase {
|
||||
if (!opt_block) return;
|
||||
|
||||
this.blockId = opt_block.id;
|
||||
|
||||
/** Whether the bubble is opening (false if closing). */
|
||||
this.isOpen = opt_isOpen;
|
||||
|
||||
/** The type of bubble. One of 'mutator', 'comment', or 'warning'. */
|
||||
this.bubbleType = opt_bubbleType;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,16 @@ import {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a click event.
|
||||
*
|
||||
* @alias Blockly.Events.Click
|
||||
* Notifies listeners that ome blockly element was clicked.
|
||||
*/
|
||||
export class Click extends UiBase {
|
||||
/** The ID of the block that was clicked, if a block was clicked. */
|
||||
blockId?: string;
|
||||
|
||||
/**
|
||||
* The type of element that was clicked; one of 'block', 'workspace',
|
||||
* or 'zoom_controls'.
|
||||
*/
|
||||
targetType?: ClickTarget;
|
||||
override type = eventUtils.CLICK;
|
||||
|
||||
@@ -51,8 +55,6 @@ export class Click extends UiBase {
|
||||
super(workspaceId);
|
||||
|
||||
this.blockId = opt_block ? opt_block.id : undefined;
|
||||
|
||||
/** The type of element targeted by this click event. */
|
||||
this.targetType = opt_targetType;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
/**
|
||||
* Abstract class for a comment event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentBase
|
||||
*/
|
||||
export class CommentBase extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
|
||||
/** The ID of the comment that this event references. */
|
||||
commentId?: string;
|
||||
|
||||
/**
|
||||
@@ -44,20 +44,9 @@ export class CommentBase extends AbstractEvent {
|
||||
|
||||
if (!opt_comment) return;
|
||||
|
||||
/** The ID of the comment this event pertains to. */
|
||||
this.commentId = opt_comment.id;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = opt_comment.workspace.id;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
|
||||
/** Sets whether the event should be added to the undo stack. */
|
||||
this.recordUndo = eventUtils.getRecordUndo();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,16 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment change event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentChange
|
||||
* Notifies listeners that the contents of a workspace comment has changed.
|
||||
*/
|
||||
export class CommentChange extends CommentBase {
|
||||
override type = eventUtils.COMMENT_CHANGE;
|
||||
|
||||
// TODO(#6774): We should remove underscores.
|
||||
/** The previous contents of the comment. */
|
||||
oldContents_?: string;
|
||||
|
||||
/** The new contents of the comment. */
|
||||
newContents_?: string;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ goog.declareModuleId('Blockly.Events.CommentCreate');
|
||||
import * as deprecation from '../utils/deprecation.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
import * as utilsXml from '../utils/xml.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
@@ -23,13 +24,12 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment creation event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentCreate
|
||||
* Notifies listeners that a workspace comment was created.
|
||||
*/
|
||||
export class CommentCreate extends CommentBase {
|
||||
override type = eventUtils.COMMENT_CREATE;
|
||||
|
||||
/** The XML representation of the created workspace comment. */
|
||||
xml?: Element|DocumentFragment;
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ export class CommentCreate extends CommentBase {
|
||||
'Blockly.Events.CommentCreate.prototype.fromJson', 'version 9',
|
||||
'version 10', 'Blockly.Events.fromJson');
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
this.xml = utilsXml.textToDom(json['xml']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ export class CommentCreate extends CommentBase {
|
||||
const newEvent =
|
||||
super.fromJson(json, workspace, event ?? new CommentCreate()) as
|
||||
CommentCreate;
|
||||
newEvent.xml = Xml.textToDom(json['xml']);
|
||||
newEvent.xml = utilsXml.textToDom(json['xml']);
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,17 +17,18 @@ import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
import * as utilsXml from '../utils/xml.js';
|
||||
import * as Xml from '../xml.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment deletion event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentDelete
|
||||
* Notifies listeners that a workspace comment has been deleted.
|
||||
*/
|
||||
export class CommentDelete extends CommentBase {
|
||||
override type = eventUtils.COMMENT_DELETE;
|
||||
|
||||
/** The XML representation of the deleted workspace comment. */
|
||||
xml?: Element;
|
||||
|
||||
/**
|
||||
@@ -83,7 +84,7 @@ export class CommentDelete extends CommentBase {
|
||||
const newEvent =
|
||||
super.fromJson(json, workspace, event ?? new CommentDelete()) as
|
||||
CommentDelete;
|
||||
newEvent.xml = Xml.textToDom(json['xml']);
|
||||
newEvent.xml = utilsXml.textToDom(json['xml']);
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,19 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment move event. Created before the move.
|
||||
*
|
||||
* @alias Blockly.Events.CommentMove
|
||||
* Notifies listeners that a workspace comment has moved.
|
||||
*/
|
||||
export class CommentMove extends CommentBase {
|
||||
override type = eventUtils.COMMENT_MOVE;
|
||||
|
||||
/** The comment that is being moved. */
|
||||
comment_?: WorkspaceComment;
|
||||
|
||||
// TODO(#6774): We should remove underscores.
|
||||
/** The location of the comment before the move, in workspace coordinates. */
|
||||
oldCoordinate_?: Coordinate;
|
||||
/** The location after the move, in workspace coordinates. */
|
||||
|
||||
/** The location of the comment after the move, in workspace coordinates. */
|
||||
newCoordinate_?: Coordinate;
|
||||
|
||||
/**
|
||||
@@ -45,13 +49,8 @@ export class CommentMove extends CommentBase {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
/**
|
||||
* The comment that is being moved.
|
||||
*/
|
||||
this.comment_ = opt_comment;
|
||||
|
||||
/** The location before the move, in workspace coordinates. */
|
||||
this.oldCoordinate_ = opt_comment.getXY();
|
||||
this.oldCoordinate_ = opt_comment.getRelativeToSurfaceXY();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +68,7 @@ export class CommentMove extends CommentBase {
|
||||
'The comment is undefined. Pass a comment to ' +
|
||||
'the constructor if you want to use the record functionality');
|
||||
}
|
||||
this.newCoordinate_ = this.comment_.getXY();
|
||||
this.newCoordinate_ = this.comment_.getRelativeToSurfaceXY();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +178,7 @@ export class CommentMove extends CommentBase {
|
||||
'or call fromJson');
|
||||
}
|
||||
// TODO: Check if the comment is being dragged, and give up if so.
|
||||
const current = comment.getXY();
|
||||
const current = comment.getRelativeToSurfaceXY();
|
||||
comment.moveBy(target.x - current.x, target.y - current.y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,26 @@ import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a marker move event.
|
||||
*
|
||||
* @alias Blockly.Events.MarkerMove
|
||||
* Notifies listeners that a marker (used for keyboard navigation) has
|
||||
* moved.
|
||||
*/
|
||||
export class MarkerMove extends UiBase {
|
||||
/** The ID of the block the marker is now on, if any. */
|
||||
blockId?: string;
|
||||
|
||||
/** The old node the marker used to be on, if any. */
|
||||
oldNode?: ASTNode;
|
||||
|
||||
/** The new node the marker is now on. */
|
||||
newNode?: ASTNode;
|
||||
|
||||
/**
|
||||
* True if this is a cursor event, false otherwise.
|
||||
* For information about cursors vs markers see {@link
|
||||
* https://blocklycodelabs.dev/codelabs/keyboard-navigation/index.html?index=..%2F..index#1}.
|
||||
*/
|
||||
isCursor?: boolean;
|
||||
|
||||
override type = eventUtils.MARKER_MOVE;
|
||||
|
||||
/**
|
||||
@@ -54,16 +65,9 @@ export class MarkerMove extends UiBase {
|
||||
}
|
||||
super(workspaceId);
|
||||
|
||||
/** The block identifier for this event. */
|
||||
this.blockId = opt_block?.id;
|
||||
|
||||
/** The old node the marker used to be on. */
|
||||
this.oldNode = opt_oldNode || undefined;
|
||||
|
||||
/** The new node the marker is now on. */
|
||||
this.newNode = opt_newNode;
|
||||
|
||||
/** Whether this is a cursor event. */
|
||||
this.isCursor = isCursor;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* The base event for an event associated with a procedure.
|
||||
*/
|
||||
export abstract class ProcedureBase extends AbstractEvent {
|
||||
isBlank = false;
|
||||
|
||||
constructor(workspace: Workspace, public readonly model: IProcedureModel) {
|
||||
super();
|
||||
this.workspaceId = workspace.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureBaseJson {
|
||||
const json = super.toJson() as ProcedureBaseJson;
|
||||
json['procedureId'] = this.model.getId();
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureBaseJson extends AbstractEventJson {
|
||||
procedureId: string;
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a procedure's return type/status changing.
|
||||
*/
|
||||
export class ProcedureChangeReturn extends ProcedureBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_CHANGE_RETURN;
|
||||
|
||||
/** The new type(s) the procedure's return has been set to. */
|
||||
private newTypes: string[]|null;
|
||||
|
||||
/**
|
||||
* @param oldTypes The type(s) the procedure's return was set to before it
|
||||
* changed.
|
||||
*/
|
||||
constructor(
|
||||
workpace: Workspace, model: IProcedureModel,
|
||||
public readonly oldTypes: string[]|null) {
|
||||
super(workpace, model);
|
||||
|
||||
this.newTypes = model.getReturnTypes();
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const procedureModel =
|
||||
this.getEventWorkspace_().getProcedureMap().get(this.model.getId());
|
||||
if (!procedureModel) {
|
||||
throw new Error(
|
||||
'Cannot change the type of a procedure that does not exist ' +
|
||||
'in the procedure map');
|
||||
}
|
||||
if (forward) {
|
||||
procedureModel.setReturnTypes(this.newTypes);
|
||||
} else {
|
||||
procedureModel.setReturnTypes(this.oldTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureChangeReturnJson {
|
||||
const json = super.toJson() as ProcedureChangeReturnJson;
|
||||
json['oldTypes'] = this.oldTypes;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureChangeReturnJson, workspace: Workspace):
|
||||
ProcedureChangeReturn {
|
||||
const model = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
'Cannot deserialize procedure change return event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
return new ProcedureChangeReturn(workspace, model, json['oldTypes']);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureChangeReturnJson extends ProcedureBaseJson {
|
||||
oldTypes: string[]|null;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_CHANGE_RETURN,
|
||||
ProcedureChangeReturn);
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import {ObservableParameterModel, ObservableProcedureModel} from '../procedures.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {loadProcedure, saveProcedure, State as ProcedureState} from '../serialization/procedures.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a procedure data model being created.
|
||||
*/
|
||||
export class ProcedureCreate extends ProcedureBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_CREATE;
|
||||
|
||||
constructor(workspace: Workspace, model: IProcedureModel) {
|
||||
super(workspace, model);
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
const procedureMap = workspace.getProcedureMap();
|
||||
const procedureModel = procedureMap.get(this.model.getId());
|
||||
if (forward) {
|
||||
if (procedureModel) return;
|
||||
// TODO: This should add the model to the map instead of creating a dupe.
|
||||
procedureMap.add(new ObservableProcedureModel(
|
||||
workspace, this.model.getName(), this.model.getId()));
|
||||
} else {
|
||||
if (!procedureModel) return;
|
||||
procedureMap.delete(this.model.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureCreateJson {
|
||||
const json = super.toJson() as ProcedureCreateJson;
|
||||
json['model'] = saveProcedure(this.model);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureCreateJson, workspace: Workspace):
|
||||
ProcedureCreate {
|
||||
return new ProcedureCreate(
|
||||
workspace,
|
||||
loadProcedure(
|
||||
ObservableProcedureModel, ObservableParameterModel, json['model'],
|
||||
workspace));
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureCreateJson extends ProcedureBaseJson {
|
||||
model: ProcedureState,
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_CREATE, ProcedureCreate);
|
||||
@@ -1,71 +0,0 @@
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import {ObservableProcedureModel} from '../procedures.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a procedure data model being deleted.
|
||||
*/
|
||||
export class ProcedureDelete extends ProcedureBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_DELETE;
|
||||
|
||||
constructor(workspace: Workspace, model: IProcedureModel) {
|
||||
super(workspace, model);
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
const procedureMap = workspace.getProcedureMap();
|
||||
const procedureModel = procedureMap.get(this.model.getId());
|
||||
if (forward) {
|
||||
if (!procedureModel) return;
|
||||
procedureMap.delete(this.model.getId());
|
||||
} else {
|
||||
if (procedureModel) return;
|
||||
procedureMap.add(new ObservableProcedureModel(
|
||||
workspace, this.model.getName(), this.model.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureDeleteJson {
|
||||
return super.toJson() as ProcedureDeleteJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureDeleteJson, workspace: Workspace):
|
||||
ProcedureDelete {
|
||||
const model = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
'Cannot deserialize procedure delete event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
return new ProcedureDelete(workspace, model);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureDeleteJson extends ProcedureBaseJson {}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_DELETE, ProcedureDelete);
|
||||
@@ -1,76 +0,0 @@
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
/**
|
||||
* Represents a procedure data model being enabled or disabled.
|
||||
*/
|
||||
export class ProcedureEnable extends ProcedureBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_ENABLE;
|
||||
|
||||
private oldState: boolean;
|
||||
private newState: boolean;
|
||||
|
||||
constructor(workspace: Workspace, model: IProcedureModel) {
|
||||
super(workspace, model);
|
||||
|
||||
this.oldState = !model.getEnabled();
|
||||
this.newState = model.getEnabled();
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const procedureModel =
|
||||
this.getEventWorkspace_().getProcedureMap().get(this.model.getId());
|
||||
if (!procedureModel) {
|
||||
throw new Error(
|
||||
'Cannot change the enabled state of a procedure that does not ' +
|
||||
'exist in the procedure map');
|
||||
}
|
||||
if (forward) {
|
||||
procedureModel.setEnabled(this.newState);
|
||||
} else {
|
||||
procedureModel.setEnabled(this.oldState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureEnableJson {
|
||||
return super.toJson() as ProcedureEnableJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureEnableJson, workspace: Workspace):
|
||||
ProcedureEnable {
|
||||
const model = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
'Cannot deserialize procedure enable event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
return new ProcedureEnable(workspace, model);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureEnableJson extends ProcedureBaseJson {}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_ENABLE, ProcedureEnable);
|
||||
@@ -1,39 +0,0 @@
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
|
||||
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
|
||||
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
/**
|
||||
* The base event for an event associated with a procedure parameter.
|
||||
*/
|
||||
export abstract class ProcedureParameterBase extends ProcedureBase {
|
||||
constructor(
|
||||
workspace: Workspace, model: IProcedureModel,
|
||||
public readonly parameter: IParameterModel) {
|
||||
super(workspace, model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureParameterBaseJson {
|
||||
const json = super.toJson() as ProcedureParameterBaseJson;
|
||||
json['parameterId'] = this.model.getId();
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureParameterBaseJson extends ProcedureBaseJson {
|
||||
parameterId: string,
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import {ObservableParameterModel} from '../procedures/observable_parameter_model.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {loadParameter, ParameterState, saveParameter} from '../serialization/procedures.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureParameterBase, ProcedureParameterBaseJson} from './events_procedure_parameter_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a parameter being added to a procedure.
|
||||
*/
|
||||
export class ProcedureParameterCreate extends ProcedureParameterBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_PARAMETER_CREATE;
|
||||
|
||||
/**
|
||||
* @param parameter The parameter model that was just added to the procedure.
|
||||
* @param index The index the parameter was inserted at.
|
||||
*/
|
||||
constructor(
|
||||
workspace: Workspace, procedure: IProcedureModel,
|
||||
parameter: IParameterModel, public readonly index: number) {
|
||||
super(workspace, procedure, parameter);
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
const procedureMap = workspace.getProcedureMap();
|
||||
const procedureModel = procedureMap.get(this.model.getId());
|
||||
if (!procedureModel) {
|
||||
throw new Error(
|
||||
'Cannot add a parameter to a procedure that does not exist ' +
|
||||
'in the procedure map');
|
||||
}
|
||||
const parameterModel = procedureModel.getParameter(this.index);
|
||||
if (forward) {
|
||||
if (this.parameterMatches(parameterModel)) return;
|
||||
// TODO: This should just add the parameter instead of creating a dupe.
|
||||
procedureModel.insertParameter(
|
||||
new ObservableParameterModel(
|
||||
workspace, this.parameter.getName(), this.parameter.getId()),
|
||||
this.index);
|
||||
} else {
|
||||
if (!this.parameterMatches(parameterModel)) return;
|
||||
procedureModel.deleteParameter(this.index);
|
||||
}
|
||||
}
|
||||
|
||||
parameterMatches(param: IParameterModel) {
|
||||
return param && param.getId() === this.parameter.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureParameterCreateJson {
|
||||
const json = super.toJson() as ProcedureParameterCreateJson;
|
||||
json['parameter'] = saveParameter(this.parameter);
|
||||
json['index'] = this.index;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureParameterCreateJson, workspace: Workspace):
|
||||
ProcedureParameterCreate {
|
||||
const procedure = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!procedure) {
|
||||
throw new Error(
|
||||
'Cannot deserialize parameter create event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
return new ProcedureParameterCreate(
|
||||
workspace, procedure,
|
||||
loadParameter(ObservableParameterModel, json['parameter'], workspace),
|
||||
json['index']);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureParameterCreateJson extends
|
||||
ProcedureParameterBaseJson {
|
||||
parameter: ParameterState, index: number,
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_PARAMETER_CREATE,
|
||||
ProcedureParameterCreate);
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import {ObservableParameterModel} from '../procedures/observable_parameter_model.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureParameterBase, ProcedureParameterBaseJson} from './events_procedure_parameter_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
/**
|
||||
* Represents a parameter being removed from a procedure.
|
||||
*/
|
||||
export class ProcedureParameterDelete extends ProcedureParameterBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_PARAMETER_DELETE;
|
||||
|
||||
/**
|
||||
* @param parameter The parameter model that was just removed from the
|
||||
* procedure.
|
||||
* @param index The index the parameter was at before it was removed.
|
||||
*/
|
||||
constructor(
|
||||
workspace: Workspace, procedure: IProcedureModel,
|
||||
parameter: IParameterModel, public readonly index: number) {
|
||||
super(workspace, procedure, parameter);
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
const procedureMap = workspace.getProcedureMap();
|
||||
const procedureModel = procedureMap.get(this.model.getId());
|
||||
if (!procedureModel) {
|
||||
throw new Error(
|
||||
'Cannot add a parameter to a procedure that does not exist ' +
|
||||
'in the procedure map');
|
||||
}
|
||||
const parameterModel = procedureModel.getParameter(this.index);
|
||||
if (forward) {
|
||||
if (!this.parameterMatches(parameterModel)) return;
|
||||
procedureModel.deleteParameter(this.index);
|
||||
} else {
|
||||
if (this.parameterMatches(parameterModel)) return;
|
||||
// TODO: this should just insert the model instead of creating a dupe.
|
||||
procedureModel.insertParameter(
|
||||
new ObservableParameterModel(
|
||||
workspace, this.parameter.getName(), this.parameter.getId()),
|
||||
this.index);
|
||||
}
|
||||
}
|
||||
|
||||
parameterMatches(param: IParameterModel) {
|
||||
return param && param.getId() === this.parameter.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureParameterDeleteJson {
|
||||
const json = super.toJson() as ProcedureParameterDeleteJson;
|
||||
json['index'] = this.index;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureParameterDeleteJson, workspace: Workspace):
|
||||
ProcedureParameterDelete {
|
||||
const model = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
'Cannot deserialize procedure delete event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
const param = model.getParameter(json['index']);
|
||||
return new ProcedureParameterDelete(workspace, model, param, json['index']);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureParameterDeleteJson extends
|
||||
ProcedureParameterBaseJson {
|
||||
index: number;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_PARAMETER_DELETE,
|
||||
ProcedureParameterDelete);
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IParameterModel} from '../interfaces/i_parameter_model.js';
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureParameterBase, ProcedureParameterBaseJson} from './events_procedure_parameter_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
/**
|
||||
* Represents a parameter of a procedure being renamed.
|
||||
*/
|
||||
export class ProcedureParameterRename extends ProcedureParameterBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_PARAMETER_RENAME;
|
||||
private readonly newName: string;
|
||||
|
||||
constructor(
|
||||
workspace: Workspace, procedure: IProcedureModel,
|
||||
parameter: IParameterModel, public readonly oldName: string) {
|
||||
super(workspace, procedure, parameter);
|
||||
|
||||
this.newName = parameter.getName();
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const parameterModel = findMatchingParameter(
|
||||
this.getEventWorkspace_(), this.model.getId(), this.parameter.getId());
|
||||
if (!parameterModel) {
|
||||
throw new Error(
|
||||
'Cannot rename a parameter that does not exist ' +
|
||||
'in the procedure map');
|
||||
}
|
||||
if (forward) {
|
||||
parameterModel.setName(this.newName);
|
||||
} else {
|
||||
parameterModel.setName(this.oldName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureParameterRenameJson {
|
||||
const json = super.toJson() as ProcedureParameterRenameJson;
|
||||
json['oldName'] = this.oldName;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureParameterRenameJson, workspace: Workspace):
|
||||
ProcedureParameterRename {
|
||||
const model = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
'Cannot deserialize procedure delete event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
const param = findMatchingParameter(
|
||||
workspace, json['procedureId'], json['parameterId']);
|
||||
if (!param) {
|
||||
throw new Error(
|
||||
'Cannot deserialize parameter rename event because the ' +
|
||||
'target parameter does not exist');
|
||||
}
|
||||
return new ProcedureParameterRename(
|
||||
workspace, model, param, json['oldName']);
|
||||
}
|
||||
}
|
||||
|
||||
function findMatchingParameter(
|
||||
workspace: Workspace, modelId: string, paramId: string): IParameterModel|
|
||||
undefined {
|
||||
const procedureModel = workspace.getProcedureMap().get(modelId);
|
||||
if (!procedureModel) {
|
||||
throw new Error(
|
||||
'Cannot rename the parameter of a procedure that does not exist ' +
|
||||
'in the procedure map');
|
||||
}
|
||||
return procedureModel.getParameters().find((p) => p.getId() === paramId);
|
||||
}
|
||||
|
||||
|
||||
export interface ProcedureParameterRenameJson extends
|
||||
ProcedureParameterBaseJson {
|
||||
oldName: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_PARAMETER_RENAME,
|
||||
ProcedureParameterRename);
|
||||
@@ -1,78 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {IProcedureModel} from '../interfaces/i_procedure_model.js';
|
||||
import * as registry from '../registry.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import {ProcedureBase, ProcedureBaseJson} from './events_procedure_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
/**
|
||||
* Represents a procedure being renamed.
|
||||
*/
|
||||
export class ProcedureRename extends ProcedureBase {
|
||||
/** A string used to check the type of the event. */
|
||||
type = eventUtils.PROCEDURE_RENAME;
|
||||
private newName: string;
|
||||
|
||||
constructor(
|
||||
workspace: Workspace, model: IProcedureModel,
|
||||
public readonly oldName: string) {
|
||||
super(workspace, model);
|
||||
|
||||
this.newName = model.getName();
|
||||
}
|
||||
|
||||
run(forward: boolean) {
|
||||
const procedureModel =
|
||||
this.getEventWorkspace_().getProcedureMap().get(this.model.getId());
|
||||
if (!procedureModel) {
|
||||
throw new Error(
|
||||
'Cannot change the type of a procedure that does not exist ' +
|
||||
'in the procedure map');
|
||||
}
|
||||
if (forward) {
|
||||
procedureModel.setName(this.newName);
|
||||
} else {
|
||||
procedureModel.setName(this.oldName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): ProcedureRenameJson {
|
||||
const json = super.toJson() as ProcedureRenameJson;
|
||||
json['oldName'] = this.oldName;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the JSON event.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
static fromJson(json: ProcedureRenameJson, workspace: Workspace):
|
||||
ProcedureRename {
|
||||
const model = workspace.getProcedureMap().get(json['procedureId']);
|
||||
if (!model) {
|
||||
throw new Error(
|
||||
'Cannot deserialize procedure rename event because the ' +
|
||||
'target procedure does not exist');
|
||||
}
|
||||
return new ProcedureRename(workspace, model, json['oldName']);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProcedureRenameJson extends ProcedureBaseJson {
|
||||
oldName: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.PROCEDURE_RENAME, ProcedureRename);
|
||||
@@ -23,12 +23,18 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
/**
|
||||
* Class for a selected event.
|
||||
*
|
||||
* @alias Blockly.Events.Selected
|
||||
* Notifies listeners that a new element has been selected.
|
||||
*/
|
||||
export class Selected extends UiBase {
|
||||
/** The id of the last selected selectable element. */
|
||||
oldElementId?: string;
|
||||
|
||||
/**
|
||||
* The id of the newly selected selectable element,
|
||||
* or undefined if unselected.
|
||||
*/
|
||||
newElementId?: string;
|
||||
|
||||
override type = eventUtils.SELECTED;
|
||||
|
||||
/**
|
||||
@@ -44,10 +50,7 @@ export class Selected extends UiBase {
|
||||
opt_workspaceId?: string) {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/** The id of the last selected element. */
|
||||
this.oldElementId = opt_oldElementId ?? undefined;
|
||||
|
||||
/** The id of the selected element. */
|
||||
this.newElementId = opt_newElementId ?? undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,12 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a theme change event.
|
||||
*
|
||||
* @alias Blockly.Events.ThemeChange
|
||||
* Notifies listeners that the workspace theme has changed.
|
||||
*/
|
||||
export class ThemeChange extends UiBase {
|
||||
/** The name of the new theme that has been set. */
|
||||
themeName?: string;
|
||||
|
||||
override type = eventUtils.THEME_CHANGE;
|
||||
|
||||
/**
|
||||
@@ -36,8 +36,6 @@ export class ThemeChange extends UiBase {
|
||||
*/
|
||||
constructor(opt_themeName?: string, opt_workspaceId?: string) {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/** The theme name. */
|
||||
this.themeName = opt_themeName;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,15 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a toolbox item select event.
|
||||
*
|
||||
* @alias Blockly.Events.ToolboxItemSelect
|
||||
* Notifies listeners that a toolbox item has been selected.
|
||||
*/
|
||||
export class ToolboxItemSelect extends UiBase {
|
||||
/** The previously selected toolbox item. */
|
||||
oldItem?: string;
|
||||
|
||||
/** The newly selected toolbox item. */
|
||||
newItem?: string;
|
||||
|
||||
override type = eventUtils.TOOLBOX_ITEM_SELECT;
|
||||
|
||||
/**
|
||||
@@ -42,11 +44,7 @@ export class ToolboxItemSelect extends UiBase {
|
||||
opt_oldItem?: string|null, opt_newItem?: string|null,
|
||||
opt_workspaceId?: string) {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/** The previously selected toolbox item. */
|
||||
this.oldItem = opt_oldItem ?? undefined;
|
||||
|
||||
/** The newly selected toolbox item. */
|
||||
this.newItem = opt_newItem ?? undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a trashcan open event.
|
||||
*
|
||||
* @alias Blockly.Events.TrashcanOpen
|
||||
* Notifies listeners when the trashcan is opening or closing.
|
||||
*/
|
||||
export class TrashcanOpen extends UiBase {
|
||||
/**
|
||||
* True if the trashcan is currently opening (previously closed).
|
||||
* False if it is currently closing (previously open).
|
||||
*/
|
||||
isOpen?: boolean;
|
||||
override type = eventUtils.TRASHCAN_OPEN;
|
||||
|
||||
@@ -38,8 +40,6 @@ export class TrashcanOpen extends UiBase {
|
||||
*/
|
||||
constructor(opt_isOpen?: boolean, opt_workspaceId?: string) {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/** Whether the trashcan flyout is opening (false if closing). */
|
||||
this.isOpen = opt_isOpen;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import * as eventUtils from './utils.js';
|
||||
* Class for a UI event.
|
||||
*
|
||||
* @deprecated December 2020. Instead use a more specific UI event.
|
||||
* @alias Blockly.Events.Ui
|
||||
*/
|
||||
export class Ui extends UiBase {
|
||||
blockId: AnyDuringMigration;
|
||||
|
||||
@@ -22,8 +22,6 @@ import {Abstract as AbstractEvent} from './events_abstract.js';
|
||||
* editing to work (e.g. scrolling the workspace, zooming, opening toolbox
|
||||
* categories).
|
||||
* UI events do not undo or redo.
|
||||
*
|
||||
* @alias Blockly.Events.UiBase
|
||||
*/
|
||||
export class UiBase extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
|
||||
@@ -21,11 +21,10 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
/**
|
||||
* Abstract class for a variable event.
|
||||
*
|
||||
* @alias Blockly.Events.VarBase
|
||||
*/
|
||||
export class VarBase extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
/** The ID of the variable this event references. */
|
||||
varId?: string;
|
||||
|
||||
/**
|
||||
@@ -37,10 +36,7 @@ export class VarBase extends AbstractEvent {
|
||||
this.isBlank = typeof opt_variable === 'undefined';
|
||||
if (!opt_variable) return;
|
||||
|
||||
/** The variable id for the variable this event pertains to. */
|
||||
this.varId = opt_variable.getId();
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = opt_variable.workspace.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,15 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable creation event.
|
||||
*
|
||||
* @alias Blockly.Events.VarCreate
|
||||
* Notifies listeners that a variable model has been created.
|
||||
*/
|
||||
export class VarCreate extends VarBase {
|
||||
override type = eventUtils.VAR_CREATE;
|
||||
|
||||
/** The type of the variable that was created. */
|
||||
varType?: string;
|
||||
|
||||
/** The name of the variable that was created. */
|
||||
varName?: string;
|
||||
|
||||
/**
|
||||
@@ -51,7 +53,7 @@ export class VarCreate extends VarBase {
|
||||
*/
|
||||
override toJson(): VarCreateJson {
|
||||
const json = super.toJson() as VarCreateJson;
|
||||
if (!this.varType) {
|
||||
if (this.varType === undefined) {
|
||||
throw new Error(
|
||||
'The var type is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes for all types of variable events.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.VarDelete');
|
||||
|
||||
@@ -22,13 +17,15 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable deletion event.
|
||||
* Notifies listeners that a variable model has been deleted.
|
||||
*
|
||||
* @alias Blockly.Events.VarDelete
|
||||
* @class
|
||||
*/
|
||||
export class VarDelete extends VarBase {
|
||||
override type = eventUtils.VAR_DELETE;
|
||||
/** The type of the variable that was deleted. */
|
||||
varType?: string;
|
||||
/** The name of the variable that was deleted. */
|
||||
varName?: string;
|
||||
|
||||
/**
|
||||
@@ -51,7 +48,7 @@ export class VarDelete extends VarBase {
|
||||
*/
|
||||
override toJson(): VarDeleteJson {
|
||||
const json = super.toJson() as VarDeleteJson;
|
||||
if (!this.varType) {
|
||||
if (this.varType === undefined) {
|
||||
throw new Error(
|
||||
'The var type is undefined. Either pass a variable to ' +
|
||||
'the constructor, or call fromJson');
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for a variable rename event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.VarRename');
|
||||
|
||||
@@ -22,13 +17,17 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable rename event.
|
||||
* Notifies listeners that a variable model was renamed.
|
||||
*
|
||||
* @alias Blockly.Events.VarRename
|
||||
* @class
|
||||
*/
|
||||
export class VarRename extends VarBase {
|
||||
override type = eventUtils.VAR_RENAME;
|
||||
|
||||
/** The previous name of the variable. */
|
||||
oldName?: string;
|
||||
|
||||
/** The new name of the variable. */
|
||||
newName?: string;
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,15 +21,30 @@ import type {Workspace} from '../workspace.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a viewport change event.
|
||||
* Notifies listeners that the workspace surface's position or scale has
|
||||
* changed.
|
||||
*
|
||||
* @alias Blockly.Events.ViewportChange
|
||||
* Does not notify when the workspace itself resizes.
|
||||
*/
|
||||
export class ViewportChange extends UiBase {
|
||||
/**
|
||||
* Top edge of the visible portion of the workspace, relative to the
|
||||
* workspace origin.
|
||||
*/
|
||||
viewTop?: number;
|
||||
|
||||
/**
|
||||
* The left edge of the visible portion of the workspace, relative to
|
||||
* the workspace origin.
|
||||
*/
|
||||
viewLeft?: number;
|
||||
|
||||
/** The scale of the workpace. */
|
||||
scale?: number;
|
||||
|
||||
/** The previous scale of the workspace. */
|
||||
oldScale?: number;
|
||||
|
||||
override type = eventUtils.VIEWPORT_CHANGE;
|
||||
|
||||
/**
|
||||
@@ -48,22 +63,9 @@ export class ViewportChange extends UiBase {
|
||||
opt_workspaceId?: string, opt_oldScale?: number) {
|
||||
super(opt_workspaceId);
|
||||
|
||||
/**
|
||||
* Top-edge of the visible portion of the workspace, relative to the
|
||||
* workspace origin.
|
||||
*/
|
||||
this.viewTop = opt_top;
|
||||
|
||||
/**
|
||||
* Left-edge of the visible portion of the workspace, relative to the
|
||||
* workspace origin.
|
||||
*/
|
||||
this.viewLeft = opt_left;
|
||||
|
||||
/** The scale of the workspace. */
|
||||
this.scale = opt_scale;
|
||||
|
||||
/** The old scale of the workspace. */
|
||||
this.oldScale = opt_oldScale;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper methods for events that are fired as a result of
|
||||
* actions in Blockly's editor.
|
||||
*
|
||||
* @namespace Blockly.Events.utils
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.utils');
|
||||
|
||||
@@ -39,7 +33,6 @@ let recordUndo = true;
|
||||
* Sets whether events should be added to the undo stack.
|
||||
*
|
||||
* @param newValue True if events should be added to the undo stack.
|
||||
* @alias Blockly.Events.utils.setRecordUndo
|
||||
*/
|
||||
export function setRecordUndo(newValue: boolean) {
|
||||
recordUndo = newValue;
|
||||
@@ -49,7 +42,6 @@ export function setRecordUndo(newValue: boolean) {
|
||||
* Returns whether or not events will be added to the undo stack.
|
||||
*
|
||||
* @returns True if events will be added to the undo stack.
|
||||
* @alias Blockly.Events.utils.getRecordUndo
|
||||
*/
|
||||
export function getRecordUndo(): boolean {
|
||||
return recordUndo;
|
||||
@@ -60,218 +52,140 @@ let disabled = 0;
|
||||
|
||||
/**
|
||||
* Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
|
||||
*
|
||||
* @alias Blockly.Events.utils.CREATE
|
||||
*/
|
||||
export const CREATE = 'create';
|
||||
|
||||
/**
|
||||
* Name of event that creates a block.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BLOCK_CREATE
|
||||
*/
|
||||
export const BLOCK_CREATE = CREATE;
|
||||
|
||||
/**
|
||||
* Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
|
||||
*
|
||||
* @alias Blockly.Events.utils.DELETE
|
||||
*/
|
||||
export const DELETE = 'delete';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a block.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BLOCK_DELETE
|
||||
*/
|
||||
export const BLOCK_DELETE = DELETE;
|
||||
|
||||
/**
|
||||
* Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
|
||||
*
|
||||
* @alias Blockly.Events.utils.CHANGE
|
||||
*/
|
||||
export const CHANGE = 'change';
|
||||
|
||||
/**
|
||||
* Name of event that changes a block.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BLOCK_CHANGE
|
||||
*/
|
||||
export const BLOCK_CHANGE = CHANGE;
|
||||
|
||||
/**
|
||||
* Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
|
||||
*
|
||||
* @alias Blockly.Events.utils.MOVE
|
||||
*/
|
||||
export const MOVE = 'move';
|
||||
|
||||
/**
|
||||
* Name of event that moves a block.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BLOCK_MOVE
|
||||
*/
|
||||
export const BLOCK_MOVE = MOVE;
|
||||
|
||||
/**
|
||||
* Name of event that creates a variable.
|
||||
*
|
||||
* @alias Blockly.Events.utils.VAR_CREATE
|
||||
*/
|
||||
export const VAR_CREATE = 'var_create';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a variable.
|
||||
*
|
||||
* @alias Blockly.Events.utils.VAR_DELETE
|
||||
*/
|
||||
export const VAR_DELETE = 'var_delete';
|
||||
|
||||
/**
|
||||
* Name of event that renames a variable.
|
||||
*
|
||||
* @alias Blockly.Events.utils.VAR_RENAME
|
||||
*/
|
||||
export const VAR_RENAME = 'var_rename';
|
||||
|
||||
/**
|
||||
* Name of generic event that records a UI change.
|
||||
*
|
||||
* @alias Blockly.Events.utils.UI
|
||||
*/
|
||||
export const UI = 'ui';
|
||||
|
||||
/**
|
||||
* Name of event that record a block drags a block.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BLOCK_DRAG
|
||||
*/
|
||||
export const BLOCK_DRAG = 'drag';
|
||||
|
||||
/**
|
||||
* Name of event that records a change in selected element.
|
||||
*
|
||||
* @alias Blockly.Events.utils.SELECTED
|
||||
*/
|
||||
export const SELECTED = 'selected';
|
||||
|
||||
/**
|
||||
* Name of event that records a click.
|
||||
*
|
||||
* @alias Blockly.Events.utils.CLICK
|
||||
*/
|
||||
export const CLICK = 'click';
|
||||
|
||||
/**
|
||||
* Name of event that records a marker move.
|
||||
*
|
||||
* @alias Blockly.Events.utils.MARKER_MOVE
|
||||
*/
|
||||
export const MARKER_MOVE = 'marker_move';
|
||||
|
||||
/**
|
||||
* Name of event that records a bubble open.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BUBBLE_OPEN
|
||||
*/
|
||||
export const BUBBLE_OPEN = 'bubble_open';
|
||||
|
||||
/**
|
||||
* Name of event that records a trashcan open.
|
||||
*
|
||||
* @alias Blockly.Events.utils.TRASHCAN_OPEN
|
||||
*/
|
||||
export const TRASHCAN_OPEN = 'trashcan_open';
|
||||
|
||||
/**
|
||||
* Name of event that records a toolbox item select.
|
||||
*
|
||||
* @alias Blockly.Events.utils.TOOLBOX_ITEM_SELECT
|
||||
*/
|
||||
export const TOOLBOX_ITEM_SELECT = 'toolbox_item_select';
|
||||
|
||||
/**
|
||||
* Name of event that records a theme change.
|
||||
*
|
||||
* @alias Blockly.Events.utils.THEME_CHANGE
|
||||
*/
|
||||
export const THEME_CHANGE = 'theme_change';
|
||||
|
||||
/**
|
||||
* Name of event that records a viewport change.
|
||||
*
|
||||
* @alias Blockly.Events.utils.VIEWPORT_CHANGE
|
||||
*/
|
||||
export const VIEWPORT_CHANGE = 'viewport_change';
|
||||
|
||||
/**
|
||||
* Name of event that creates a comment.
|
||||
*
|
||||
* @alias Blockly.Events.utils.COMMENT_CREATE
|
||||
*/
|
||||
export const COMMENT_CREATE = 'comment_create';
|
||||
|
||||
/**
|
||||
* Name of event that deletes a comment.
|
||||
*
|
||||
* @alias Blockly.Events.utils.COMMENT_DELETE
|
||||
*/
|
||||
export const COMMENT_DELETE = 'comment_delete';
|
||||
|
||||
/**
|
||||
* Name of event that changes a comment.
|
||||
*
|
||||
* @alias Blockly.Events.utils.COMMENT_CHANGE
|
||||
*/
|
||||
export const COMMENT_CHANGE = 'comment_change';
|
||||
|
||||
/**
|
||||
* Name of event that moves a comment.
|
||||
*
|
||||
* @alias Blockly.Events.utils.COMMENT_MOVE
|
||||
*/
|
||||
export const COMMENT_MOVE = 'comment_move';
|
||||
|
||||
/**
|
||||
* Name of event that records a workspace load.
|
||||
*
|
||||
* @alias Blockly.Events.utils.FINISHED_LOADING
|
||||
*/
|
||||
export const FINISHED_LOADING = 'finished_loading';
|
||||
|
||||
/** Name of event that creates a procedure model. */
|
||||
export const PROCEDURE_CREATE = 'procedure_create';
|
||||
|
||||
/** Name of event that deletes a procedure model. */
|
||||
export const PROCEDURE_DELETE = 'procedure_delete';
|
||||
|
||||
/** Name of event that renames a procedure model. */
|
||||
export const PROCEDURE_RENAME = 'procedure_rename';
|
||||
|
||||
/** Name of event that enables/disables a procedure model. */
|
||||
export const PROCEDURE_ENABLE = 'procedure_enable';
|
||||
|
||||
/** Name of event that changes the returntype of a procedure model. */
|
||||
export const PROCEDURE_CHANGE_RETURN = 'procedure_change_return';
|
||||
|
||||
/** Name of event that creates a procedure parameter. */
|
||||
export const PROCEDURE_PARAMETER_CREATE = 'procedure_parameter_create';
|
||||
|
||||
/** Name of event that deletes a procedure parameter. */
|
||||
export const PROCEDURE_PARAMETER_DELETE = 'procedure_parameter_delete';
|
||||
|
||||
/** Name of event that renames a procedure parameter. */
|
||||
export const PROCEDURE_PARAMETER_RENAME = 'procedure_parameter_rename';
|
||||
|
||||
/**
|
||||
* Type of events that cause objects to be bumped back into the visible
|
||||
* portion of the workspace.
|
||||
*
|
||||
* Not to be confused with bumping so that disconnected connections do not
|
||||
* appear connected.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BumpEvent
|
||||
*/
|
||||
export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove;
|
||||
|
||||
@@ -281,8 +195,6 @@ export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove;
|
||||
*
|
||||
* Not to be confused with bumping so that disconnected connections do not
|
||||
* appear connected.
|
||||
*
|
||||
* @alias Blockly.Events.utils.BUMP_EVENTS
|
||||
*/
|
||||
export const BUMP_EVENTS: string[] =
|
||||
[BLOCK_CREATE, BLOCK_MOVE, COMMENT_CREATE, COMMENT_MOVE];
|
||||
@@ -294,7 +206,6 @@ const FIRE_QUEUE: Abstract[] = [];
|
||||
* Create a custom event and fire it.
|
||||
*
|
||||
* @param event Custom data for event.
|
||||
* @alias Blockly.Events.utils.fire
|
||||
*/
|
||||
export function fire(event: Abstract) {
|
||||
TEST_ONLY.fireInternal(event);
|
||||
@@ -309,7 +220,17 @@ function fireInternal(event: Abstract) {
|
||||
}
|
||||
if (!FIRE_QUEUE.length) {
|
||||
// First event added; schedule a firing of the event queue.
|
||||
setTimeout(fireNow, 0);
|
||||
try {
|
||||
// If we are in a browser context, we want to make sure that the event
|
||||
// fires after blocks have been rerendered this frame.
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(fireNow, 0);
|
||||
});
|
||||
} catch (e) {
|
||||
// Otherwise we just want to delay so events can be coallesced.
|
||||
// requestAnimationFrame will error triggering this.
|
||||
setTimeout(fireNow, 0);
|
||||
}
|
||||
}
|
||||
FIRE_QUEUE.push(event);
|
||||
}
|
||||
@@ -336,7 +257,6 @@ function fireNow() {
|
||||
* @param queueIn Array of events.
|
||||
* @param forward True if forward (redo), false if backward (undo).
|
||||
* @returns Array of filtered events.
|
||||
* @alias Blockly.Events.utils.filter
|
||||
*/
|
||||
export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
|
||||
let queue = queueIn.slice();
|
||||
@@ -419,8 +339,6 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
|
||||
/**
|
||||
* Modify pending undo events so that when they are fired they don't land
|
||||
* in the undo stack. Called by Workspace.clearUndo.
|
||||
*
|
||||
* @alias Blockly.Events.utils.clearPendingUndo
|
||||
*/
|
||||
export function clearPendingUndo() {
|
||||
for (let i = 0, event; event = FIRE_QUEUE[i]; i++) {
|
||||
@@ -430,8 +348,6 @@ export function clearPendingUndo() {
|
||||
|
||||
/**
|
||||
* Stop sending events. Every call to this function MUST also call enable.
|
||||
*
|
||||
* @alias Blockly.Events.utils.disable
|
||||
*/
|
||||
export function disable() {
|
||||
disabled++;
|
||||
@@ -440,8 +356,6 @@ export function disable() {
|
||||
/**
|
||||
* Start sending events. Unless events were already disabled when the
|
||||
* corresponding call to disable was made.
|
||||
*
|
||||
* @alias Blockly.Events.utils.enable
|
||||
*/
|
||||
export function enable() {
|
||||
disabled--;
|
||||
@@ -451,7 +365,6 @@ export function enable() {
|
||||
* Returns whether events may be fired or not.
|
||||
*
|
||||
* @returns True if enabled.
|
||||
* @alias Blockly.Events.utils.isEnabled
|
||||
*/
|
||||
export function isEnabled(): boolean {
|
||||
return disabled === 0;
|
||||
@@ -461,7 +374,6 @@ export function isEnabled(): boolean {
|
||||
* Current group.
|
||||
*
|
||||
* @returns ID string.
|
||||
* @alias Blockly.Events.utils.getGroup
|
||||
*/
|
||||
export function getGroup(): string {
|
||||
return group;
|
||||
@@ -472,7 +384,6 @@ export function getGroup(): string {
|
||||
*
|
||||
* @param state True to start new group, false to end group.
|
||||
* String to set group explicitly.
|
||||
* @alias Blockly.Events.utils.setGroup
|
||||
*/
|
||||
export function setGroup(state: boolean|string) {
|
||||
TEST_ONLY.setGroupInternal(state);
|
||||
@@ -494,7 +405,6 @@ function setGroupInternal(state: boolean|string) {
|
||||
*
|
||||
* @param block The root block.
|
||||
* @returns List of block IDs.
|
||||
* @alias Blockly.Events.utils.getDescendantIds
|
||||
* @internal
|
||||
*/
|
||||
export function getDescendantIds(block: Block): string[] {
|
||||
@@ -513,7 +423,6 @@ export function getDescendantIds(block: Block): string[] {
|
||||
* @param workspace Target workspace for event.
|
||||
* @returns The event represented by the JSON.
|
||||
* @throws {Error} if an event type is not found in the registry.
|
||||
* @alias Blockly.Events.utils.fromJson
|
||||
*/
|
||||
export function fromJson(
|
||||
json: AnyDuringMigration, workspace: Workspace): Abstract {
|
||||
@@ -550,7 +459,6 @@ function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract):
|
||||
*
|
||||
* @param eventType The type of the event to get.
|
||||
* @returns The event class with the given type.
|
||||
* @alias Blockly.Events.utils.get
|
||||
*/
|
||||
export function get(eventType: string):
|
||||
(new (...p1: AnyDuringMigration[]) => Abstract) {
|
||||
@@ -568,7 +476,6 @@ export function get(eventType: string):
|
||||
* users don't try to re-enable disabled orphan blocks.
|
||||
*
|
||||
* @param event Custom data for event.
|
||||
* @alias Blockly.Events.utils.disableOrphans
|
||||
*/
|
||||
export function disableOrphans(event: Abstract) {
|
||||
if (event.type === MOVE || event.type === CREATE) {
|
||||
|
||||
@@ -19,12 +19,8 @@ import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a finished loading event.
|
||||
* Used to notify the developer when the workspace has finished loading (i.e
|
||||
* domToWorkspace).
|
||||
* Finished loading events do not record undo or redo.
|
||||
*
|
||||
* @alias Blockly.Events.FinishedLoading
|
||||
* Notifies listeners when the workspace has finished deserializing from
|
||||
* JSON/XML.
|
||||
*/
|
||||
export class FinishedLoading extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
@@ -37,12 +33,10 @@ export class FinishedLoading extends AbstractEvent {
|
||||
*/
|
||||
constructor(opt_workspace?: Workspace) {
|
||||
super();
|
||||
/** Whether or not the event is blank (to be populated by fromJson). */
|
||||
this.isBlank = !!opt_workspace;
|
||||
|
||||
if (!opt_workspace) return;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
this.workspaceId = opt_workspace.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @namespace Blockly.Extensions
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Extensions');
|
||||
|
||||
@@ -36,7 +28,6 @@ export const TEST_ONLY = {allExtensions};
|
||||
* @param 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.
|
||||
* @alias Blockly.Extensions.register
|
||||
*/
|
||||
export function register(name: string, initFn: Function) {
|
||||
if (typeof name !== 'string' || name.trim() === '') {
|
||||
@@ -58,7 +49,6 @@ export function register(name: string, initFn: Function) {
|
||||
* @param mixinObj The values to mix in.
|
||||
* @throws {Error} if the extension name is empty or the extension is already
|
||||
* registered.
|
||||
* @alias Blockly.Extensions.registerMixin
|
||||
*/
|
||||
export function registerMixin(name: string, mixinObj: AnyDuringMigration) {
|
||||
if (!mixinObj || typeof mixinObj !== 'object') {
|
||||
@@ -81,7 +71,6 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) {
|
||||
* @param 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.
|
||||
* @alias Blockly.Extensions.registerMutator
|
||||
*/
|
||||
export function registerMutator(
|
||||
name: string, mixinObj: AnyDuringMigration,
|
||||
@@ -113,7 +102,6 @@ export function registerMutator(
|
||||
* Unregisters the extension registered with the given name.
|
||||
*
|
||||
* @param name The name of the extension to unregister.
|
||||
* @alias Blockly.Extensions.unregister
|
||||
*/
|
||||
export function unregister(name: string) {
|
||||
if (isRegistered(name)) {
|
||||
@@ -129,7 +117,6 @@ export function unregister(name: string) {
|
||||
*
|
||||
* @param name The name of the extension to check for.
|
||||
* @returns True if the extension is registered. False if it is not registered.
|
||||
* @alias Blockly.Extensions.isRegistered
|
||||
*/
|
||||
export function isRegistered(name: string): boolean {
|
||||
return !!allExtensions[name];
|
||||
@@ -143,7 +130,6 @@ export function isRegistered(name: string): boolean {
|
||||
* @param block The block to apply the named extension to.
|
||||
* @param isMutator True if this extension defines a mutator.
|
||||
* @throws {Error} if the extension is not found.
|
||||
* @alias Blockly.Extensions.apply
|
||||
*/
|
||||
export function apply(name: string, block: Block, isMutator: boolean) {
|
||||
const extensionFn = allExtensions[name];
|
||||
@@ -387,7 +373,6 @@ export function runAfterPageLoad(fn: () => void) {
|
||||
* lookup table.
|
||||
* @param lookupTable The table of field values to tooltip text.
|
||||
* @returns The extension function.
|
||||
* @alias Blockly.Extensions.buildTooltipForDropdown
|
||||
*/
|
||||
export function buildTooltipForDropdown(
|
||||
dropdownName: string, lookupTable: {[key: string]: string}): Function {
|
||||
@@ -470,7 +455,6 @@ function checkDropdownOptionsInTable(
|
||||
* placeholder.
|
||||
* @param fieldName The field with the replacement text.
|
||||
* @returns The extension function.
|
||||
* @alias Blockly.Extensions.buildTooltipWithFieldText
|
||||
*/
|
||||
export function buildTooltipWithFieldText(
|
||||
msgTemplate: string, fieldName: string): Function {
|
||||
|
||||
181
core/field.ts
181
core/field.ts
@@ -35,7 +35,6 @@ import type {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import {Rect} from './utils/rect.js';
|
||||
import {Sentinel} from './utils/sentinel.js';
|
||||
import {Size} from './utils/size.js';
|
||||
import * as style from './utils/style.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
@@ -43,14 +42,28 @@ import * as userAgent from './utils/useragent.js';
|
||||
import * as utilsXml from './utils/xml.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import * as Xml from './xml.js';
|
||||
|
||||
export type FieldValidator<T = any> = (value?: T) => T|null|undefined;
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
export type FieldValidator<T = any> = (newValue: T) => T|null|undefined;
|
||||
|
||||
/**
|
||||
* Abstract class for an editable field.
|
||||
*
|
||||
* @alias Blockly.Field
|
||||
* @typeParam T - The value stored on the field.
|
||||
*/
|
||||
export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
IASTNodeLocationWithBlock,
|
||||
@@ -75,7 +88,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* field's value or run configure_, and should allow a subclass to do that
|
||||
* instead.
|
||||
*/
|
||||
static readonly SKIP_SETUP = new Sentinel();
|
||||
static readonly SKIP_SETUP = Symbol('SKIP_SETUP');
|
||||
|
||||
/**
|
||||
* Name of field. Unique within each block.
|
||||
@@ -186,16 +199,16 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* Also accepts Field.SKIP_SETUP if you wish to skip setup (only used by
|
||||
* subclasses that want to handle configuration and setting the field value
|
||||
* after their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a value & returns a validated value, or null to
|
||||
* abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* Refer to the individual field's documentation for a list of properties
|
||||
* this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
value: T|Sentinel, opt_validator?: FieldValidator<T>|null,
|
||||
opt_config?: FieldConfig) {
|
||||
value: T|typeof Field.SKIP_SETUP, validator?: FieldValidator<T>|null,
|
||||
config?: FieldConfig) {
|
||||
/**
|
||||
* A generic value possessed by the field.
|
||||
* Should generally be non-null, only null when the field is created.
|
||||
@@ -207,15 +220,13 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
/** The size of the area rendered by the field. */
|
||||
this.size_ = new Size(0, 0);
|
||||
|
||||
if (value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +383,8 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* @internal
|
||||
*/
|
||||
fromXml(fieldElement: Element) {
|
||||
this.setValue(fieldElement.textContent);
|
||||
// Any because gremlins live here. No touchie!
|
||||
this.setValue(fieldElement.textContent as any);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,7 +396,8 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* @internal
|
||||
*/
|
||||
toXml(fieldElement: Element): Element {
|
||||
fieldElement.textContent = this.getValue();
|
||||
// Any because gremlins live here. No touchie!
|
||||
fieldElement.textContent = this.getValue() as any;
|
||||
return fieldElement;
|
||||
}
|
||||
|
||||
@@ -434,7 +447,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
callingClass.prototype.toXml !== this.toXml) {
|
||||
const elem = utilsXml.createElement('field');
|
||||
elem.setAttribute('name', this.name || '');
|
||||
const text = Xml.domToText(this.toXml(elem));
|
||||
const text = utilsXml.domToText(this.toXml(elem));
|
||||
return text.replace(
|
||||
' xmlns="https://developers.google.com/blockly/xml"', '');
|
||||
}
|
||||
@@ -456,7 +469,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
boolean {
|
||||
if (callingClass.prototype.loadState === this.loadState &&
|
||||
callingClass.prototype.fromXml !== this.fromXml) {
|
||||
this.fromXml(Xml.textToDom(state as string));
|
||||
this.fromXml(utilsXml.textToDom(state as string));
|
||||
return true;
|
||||
}
|
||||
// Either they called this on purpose from their loadState, or they have
|
||||
@@ -472,14 +485,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
dispose() {
|
||||
dropDownDiv.hideIfOwner(this);
|
||||
WidgetDiv.hideIfOwner(this);
|
||||
Tooltip.unbindMouseEvents(this.getClickTarget_());
|
||||
|
||||
if (this.mouseDownWrapper_) {
|
||||
browserEvents.unbind(this.mouseDownWrapper_);
|
||||
if (!this.getSourceBlock()?.isDeadOrDying()) {
|
||||
dom.removeNode(this.fieldGroup_);
|
||||
}
|
||||
|
||||
dom.removeNode(this.fieldGroup_);
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
@@ -619,7 +629,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @returns Validation function, or null.
|
||||
*/
|
||||
getValidator(): Function|null {
|
||||
getValidator(): FieldValidator<T>|null {
|
||||
return this.validator_;
|
||||
}
|
||||
|
||||
@@ -698,14 +708,14 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* Calls showEditor_ when the field is clicked if the field is clickable.
|
||||
* Do not override.
|
||||
*
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @sealed
|
||||
* @internal
|
||||
*/
|
||||
showEditor(opt_e?: Event) {
|
||||
showEditor(e?: Event) {
|
||||
if (this.isClickable()) {
|
||||
this.showEditor_(opt_e);
|
||||
this.showEditor_(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,11 +732,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
/**
|
||||
* Updates the size of the field based on the text.
|
||||
*
|
||||
* @param opt_margin margin to use when positioning the text element.
|
||||
* @param margin margin to use when positioning the text element.
|
||||
*/
|
||||
protected updateSize_(opt_margin?: number) {
|
||||
protected updateSize_(margin?: number) {
|
||||
const constants = this.getConstants();
|
||||
const xOffset = opt_margin !== undefined ? opt_margin :
|
||||
const xOffset = margin !== undefined ? margin :
|
||||
this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING :
|
||||
0;
|
||||
let totalWidth = xOffset * 2;
|
||||
@@ -766,17 +776,17 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
|
||||
this.textElement_.setAttribute(
|
||||
'x',
|
||||
`${
|
||||
String(
|
||||
this.getSourceBlock()?.RTL ?
|
||||
this.size_.width - contentWidth - xOffset :
|
||||
xOffset}`);
|
||||
xOffset));
|
||||
this.textElement_.setAttribute(
|
||||
'y',
|
||||
`${
|
||||
String(
|
||||
constants!.FIELD_TEXT_BASELINE_CENTER ?
|
||||
halfHeight :
|
||||
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
|
||||
constants!.FIELD_TEXT_BASELINE}`);
|
||||
constants!.FIELD_TEXT_BASELINE));
|
||||
}
|
||||
|
||||
/** Position a field's border rect after a size change. */
|
||||
@@ -784,12 +794,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
if (!this.borderRect_) {
|
||||
return;
|
||||
}
|
||||
this.borderRect_.setAttribute('width', `${this.size_.width}`);
|
||||
this.borderRect_.setAttribute('height', `${this.size_.height}`);
|
||||
this.borderRect_.setAttribute('width', String(this.size_.width));
|
||||
this.borderRect_.setAttribute('height', String(this.size_.height));
|
||||
this.borderRect_.setAttribute(
|
||||
'rx', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||
'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
|
||||
this.borderRect_.setAttribute(
|
||||
'ry', `${this.getConstants()!.FIELD_BORDER_RECT_RADIUS}`);
|
||||
'ry', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -943,9 +953,8 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
forceRerender() {
|
||||
this.isDirty_ = true;
|
||||
if (this.sourceBlock_ && this.sourceBlock_.rendered) {
|
||||
(this.sourceBlock_ as BlockSvg).render();
|
||||
(this.sourceBlock_ as BlockSvg).queueRender();
|
||||
(this.sourceBlock_ as BlockSvg).bumpNeighbours();
|
||||
this.updateMarkers_();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,41 +974,37 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
return;
|
||||
}
|
||||
|
||||
let validatedValue = this.doClassValidation_(newValue);
|
||||
// Class validators might accidentally forget to return, we'll ignore that.
|
||||
newValue = this.processValidation_(newValue, validatedValue);
|
||||
if (newValue instanceof Error) {
|
||||
const classValidation = this.doClassValidation_(newValue);
|
||||
const classValue = this.processValidation_(newValue, classValidation);
|
||||
if (classValue instanceof Error) {
|
||||
doLogging && console.log('invalid class validation, return');
|
||||
return;
|
||||
}
|
||||
|
||||
const localValidator = this.getValidator();
|
||||
if (localValidator) {
|
||||
validatedValue = localValidator.call(this, newValue);
|
||||
// Local validators might accidentally forget to return, we'll ignore
|
||||
// that.
|
||||
newValue = this.processValidation_(newValue, validatedValue);
|
||||
if (newValue instanceof Error) {
|
||||
doLogging && console.log('invalid local validation, return');
|
||||
return;
|
||||
}
|
||||
const localValidation = this.getValidator()?.call(this, classValue);
|
||||
const localValue = this.processValidation_(classValue, localValidation);
|
||||
if (localValue instanceof Error) {
|
||||
doLogging && console.log('invalid local validation, return');
|
||||
return;
|
||||
}
|
||||
|
||||
const source = this.sourceBlock_;
|
||||
if (source && source.disposed) {
|
||||
doLogging && console.log('source disposed, return');
|
||||
return;
|
||||
}
|
||||
|
||||
const oldValue = this.getValue();
|
||||
if (oldValue === newValue) {
|
||||
if (oldValue === localValue) {
|
||||
doLogging && console.log('same, doValueUpdate_, return');
|
||||
this.doValueUpdate_(newValue);
|
||||
this.doValueUpdate_(localValue);
|
||||
return;
|
||||
}
|
||||
|
||||
this.doValueUpdate_(newValue);
|
||||
this.doValueUpdate_(localValue);
|
||||
if (source && eventUtils.isEnabled()) {
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
|
||||
source, 'field', this.name || null, oldValue, newValue));
|
||||
source, 'field', this.name || null, oldValue, localValue));
|
||||
}
|
||||
if (this.isDirty_) {
|
||||
this.forceRerender();
|
||||
@@ -1015,8 +1020,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
* @returns New value, or an Error object.
|
||||
*/
|
||||
private processValidation_(
|
||||
newValue: AnyDuringMigration,
|
||||
validatedValue: AnyDuringMigration): AnyDuringMigration {
|
||||
newValue: AnyDuringMigration, validatedValue: T|null|undefined): T|Error {
|
||||
if (validatedValue === null) {
|
||||
this.doValueInvalid_(newValue);
|
||||
if (this.isDirty_) {
|
||||
@@ -1024,10 +1028,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
}
|
||||
return Error();
|
||||
}
|
||||
if (validatedValue !== undefined) {
|
||||
newValue = validatedValue;
|
||||
}
|
||||
return newValue;
|
||||
return validatedValue === undefined ? newValue as T : validatedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1035,23 +1036,39 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @returns Current value.
|
||||
*/
|
||||
getValue(): AnyDuringMigration {
|
||||
getValue(): T|null {
|
||||
return this.value_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate a value. Returns input by default. Can be overridden by
|
||||
* subclasses, see FieldDropdown.
|
||||
* Validate the changes to a field's value before they are set. See
|
||||
* **FieldDropdown** for an example of subclass implementation.
|
||||
*
|
||||
* @param opt_newValue The value to be validated.
|
||||
* @returns The validated value, same as input by default.
|
||||
* **NOTE:** Validation returns one option between `T`, `null`, and
|
||||
* `undefined`. **Field**'s implementation will never return `undefined`, but
|
||||
* it is valid for a subclass to return `undefined` if the new value is
|
||||
* compatible with `T`.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue - The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
protected doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
AnyDuringMigration {
|
||||
if (opt_newValue === null || opt_newValue === undefined) {
|
||||
protected doClassValidation_(newValue: T): T|null|undefined;
|
||||
protected doClassValidation_(newValue?: AnyDuringMigration): T|null;
|
||||
protected doClassValidation_(newValue?: T|AnyDuringMigration): T|null
|
||||
|undefined {
|
||||
if (newValue === null || newValue === undefined) {
|
||||
return null;
|
||||
}
|
||||
return opt_newValue;
|
||||
|
||||
return newValue as T;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1060,7 +1077,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
*
|
||||
* @param newValue The value to be saved.
|
||||
*/
|
||||
protected doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected doValueUpdate_(newValue: T) {
|
||||
this.value_ = newValue;
|
||||
this.isDirty_ = true;
|
||||
}
|
||||
@@ -1086,7 +1103,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
}
|
||||
const gesture = (this.sourceBlock_.workspace as WorkspaceSvg).getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.setStartField(this as Field);
|
||||
gesture.setStartField(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1261,8 +1278,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
|
||||
this.markerSvg_ = markerSvg;
|
||||
}
|
||||
|
||||
/** Redraw any attached marker or cursor svgs if needed. */
|
||||
protected updateMarkers_() {
|
||||
/**
|
||||
* Redraw any attached marker or cursor svgs if needed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
updateMarkers_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
@@ -1288,6 +1309,8 @@ export interface FieldConfig {
|
||||
/**
|
||||
* For use by Field and descendants of Field. Constructors can change
|
||||
* in descendants, though they should contain all of Field's prototype methods.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export type FieldProto = Pick<typeof Field, 'prototype'>;
|
||||
|
||||
|
||||
@@ -20,30 +20,24 @@ import {Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import * as math from './utils/math.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
|
||||
export type FieldAngleValidator = FieldInputValidator<number>;
|
||||
|
||||
/**
|
||||
* Class for an editable angle field.
|
||||
*
|
||||
* @alias Blockly.FieldAngle
|
||||
*/
|
||||
export class FieldAngle extends FieldInput<number> {
|
||||
/**
|
||||
* The default amount to round angles to when using a mouse or keyboard nav
|
||||
* input. Must be a positive integer to support keyboard navigation.
|
||||
*/
|
||||
static readonly ROUND = 15;
|
||||
|
||||
/** Half the width of protractor image. */
|
||||
static readonly HALF = 100 / 2;
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
*/
|
||||
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
||||
|
||||
/**
|
||||
* Default property describing which direction makes an angle field's value
|
||||
* increase. Angle increases clockwise (true) or counterclockwise (false).
|
||||
@@ -64,88 +58,73 @@ export class FieldAngle extends FieldInput<number> {
|
||||
static readonly WRAP = 360;
|
||||
|
||||
/**
|
||||
* Radius of protractor circle. Slightly smaller than protractor size since
|
||||
* otherwise SVG crops off half the border at the edges.
|
||||
* The default amount to round angles to when using a mouse or keyboard nav
|
||||
* input. Must be a positive integer to support keyboard navigation.
|
||||
*/
|
||||
static readonly RADIUS: number = FieldAngle.HALF - 1;
|
||||
static readonly ROUND = 15;
|
||||
|
||||
/**
|
||||
* Whether the angle should increase as the angle picker is moved clockwise
|
||||
* (true) or counterclockwise (false).
|
||||
*/
|
||||
private clockwise_ = FieldAngle.CLOCKWISE;
|
||||
private clockwise = FieldAngle.CLOCKWISE;
|
||||
|
||||
/**
|
||||
* The offset of zero degrees (and all other angles).
|
||||
*/
|
||||
private offset_ = FieldAngle.OFFSET;
|
||||
private offset = FieldAngle.OFFSET;
|
||||
|
||||
/**
|
||||
* The maximum angle to allow before wrapping.
|
||||
*/
|
||||
private wrap_ = FieldAngle.WRAP;
|
||||
private wrap = FieldAngle.WRAP;
|
||||
|
||||
/**
|
||||
* The amount to round angles to when using a mouse or keyboard nav input.
|
||||
*/
|
||||
private round_ = FieldAngle.ROUND;
|
||||
private round = FieldAngle.ROUND;
|
||||
|
||||
/** The angle picker's SVG element. */
|
||||
private editor_: SVGSVGElement|null = null;
|
||||
/**
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/** The angle picker's gauge path depending on the value. */
|
||||
gauge_: SVGPathElement|null = null;
|
||||
/** Dynamic red line pointing at the value's angle. */
|
||||
private line: SVGLineElement|null = null;
|
||||
|
||||
/** The angle picker's line drawn representing the value's angle. */
|
||||
line_: SVGLineElement|null = null;
|
||||
/** Dynamic pink area extending from 0 to the value's angle. */
|
||||
private gauge: SVGPathElement|null = null;
|
||||
|
||||
/** The degree symbol for this field. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGTSpanElement'.
|
||||
protected symbol_: SVGTSpanElement = null as AnyDuringMigration;
|
||||
|
||||
/** Wrapper click event data. */
|
||||
private clickWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Surface click event data. */
|
||||
private clickSurfaceWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Surface mouse move event data. */
|
||||
private moveSurfaceWrapper_: browserEvents.Data|null = null;
|
||||
protected symbol_: SVGTSpanElement|null = null;
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the serializer, non-serializable fields
|
||||
* are not. Editable fields should also be serializable.
|
||||
*/
|
||||
override SERIALIZABLE = true;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a number.
|
||||
* @param value The initial value of the field. Should cast to a number.
|
||||
* Defaults to 0. Also accepts Field.SKIP_SETUP if you wish to skip setup
|
||||
* (only used by subclasses that want to handle configuration and setting
|
||||
* the field value after their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a number & returns a validated number, or null
|
||||
* to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/angle#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|number|Sentinel, opt_validator?: FieldAngleValidator,
|
||||
opt_config?: FieldAngleConfig) {
|
||||
value?: string|number|typeof Field.SKIP_SETUP,
|
||||
validator?: FieldAngleValidator, config?: FieldAngleConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,22 +138,22 @@ export class FieldAngle extends FieldInput<number> {
|
||||
|
||||
switch (config.mode) {
|
||||
case Mode.COMPASS:
|
||||
this.clockwise_ = true;
|
||||
this.offset_ = 90;
|
||||
this.clockwise = true;
|
||||
this.offset = 90;
|
||||
break;
|
||||
case Mode.PROTRACTOR:
|
||||
// This is the default mode, so we could do nothing. But just to
|
||||
// future-proof, we'll set it anyway.
|
||||
this.clockwise_ = false;
|
||||
this.offset_ = 0;
|
||||
this.clockwise = false;
|
||||
this.offset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Allow individual settings to override the mode setting.
|
||||
if (config.clockwise) this.clockwise_ = config.clockwise;
|
||||
if (config.offset) this.offset_ = config.offset;
|
||||
if (config.wrap) this.wrap_ = config.wrap;
|
||||
if (config.round) this.round_ = config.round;
|
||||
if (config.clockwise) this.clockwise = config.clockwise;
|
||||
if (config.offset) this.offset = config.offset;
|
||||
if (config.wrap) this.wrap = config.wrap;
|
||||
if (config.round) this.round = config.round;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,32 +163,32 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*/
|
||||
override initView() {
|
||||
super.initView();
|
||||
// Add the degree symbol to the left of the number, even in RTL (issue
|
||||
// #2380)
|
||||
// Add the degree symbol to the left of the number,
|
||||
// even in RTL (issue #2380).
|
||||
this.symbol_ = dom.createSvgElement(Svg.TSPAN, {});
|
||||
this.symbol_.appendChild(document.createTextNode('°'));
|
||||
this.getTextElement().appendChild(this.symbol_);
|
||||
}
|
||||
|
||||
/** Updates the graph when the field rerenders. */
|
||||
/** Updates the angle when the field rerenders. */
|
||||
protected override render_() {
|
||||
super.render_();
|
||||
this.updateGraph_();
|
||||
this.updateGraph();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and show the angle field's editor.
|
||||
*
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @param e Optional mouse event that triggered the field to open,
|
||||
* or undefined if triggered programmatically.
|
||||
*/
|
||||
protected override showEditor_(opt_e?: Event) {
|
||||
protected override showEditor_(e?: Event) {
|
||||
// Mobile browsers have issues with in-line textareas (focus & keyboards).
|
||||
const noFocus = userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD;
|
||||
super.showEditor_(opt_e, noFocus);
|
||||
super.showEditor_(e, noFocus);
|
||||
|
||||
this.dropdownCreate_();
|
||||
dropDownDiv.getContentDiv().appendChild(this.editor_!);
|
||||
const editor = this.dropdownCreate();
|
||||
dropDownDiv.getContentDiv().appendChild(editor);
|
||||
|
||||
if (this.sourceBlock_ instanceof BlockSvg) {
|
||||
dropDownDiv.setColour(
|
||||
@@ -217,16 +196,17 @@ export class FieldAngle extends FieldInput<number> {
|
||||
this.sourceBlock_.style.colourTertiary);
|
||||
}
|
||||
|
||||
// AnyDuringMigration because: Argument of type 'this' is not assignable to
|
||||
// parameter of type 'Field'.
|
||||
dropDownDiv.showPositionedByField(
|
||||
this as AnyDuringMigration, this.dropdownDispose_.bind(this));
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
|
||||
|
||||
this.updateGraph_();
|
||||
this.updateGraph();
|
||||
}
|
||||
|
||||
/** Create the angle dropdown editor. */
|
||||
private dropdownCreate_() {
|
||||
/**
|
||||
* Creates the angle dropdown editor.
|
||||
*
|
||||
* @returns The newly created slider.
|
||||
*/
|
||||
private dropdownCreate(): SVGSVGElement {
|
||||
const svg = dom.createSvgElement(Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
@@ -244,9 +224,9 @@ export class FieldAngle extends FieldInput<number> {
|
||||
'class': 'blocklyAngleCircle',
|
||||
},
|
||||
svg);
|
||||
this.gauge_ =
|
||||
this.gauge =
|
||||
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
|
||||
this.line_ = dom.createSvgElement(
|
||||
this.line = dom.createSvgElement(
|
||||
Svg.LINE, {
|
||||
'x1': FieldAngle.HALF,
|
||||
'y1': FieldAngle.HALF,
|
||||
@@ -272,38 +252,30 @@ export class FieldAngle extends FieldInput<number> {
|
||||
// The angle picker is different from other fields in that it updates on
|
||||
// mousemove even if it's not in the middle of a drag. In future we may
|
||||
// change this behaviour.
|
||||
this.clickWrapper_ =
|
||||
browserEvents.conditionalBind(svg, 'click', this, this.hide_);
|
||||
this.boundEvents.push(
|
||||
browserEvents.conditionalBind(svg, 'click', this, this.hide));
|
||||
// On touch devices, the picker's value is only updated with a drag. Add
|
||||
// a click handler on the drag surface to update the value if the surface
|
||||
// is clicked.
|
||||
this.clickSurfaceWrapper_ = browserEvents.conditionalBind(
|
||||
circle, 'pointerdown', this, this.onMouseMove_, true);
|
||||
this.moveSurfaceWrapper_ = browserEvents.conditionalBind(
|
||||
circle, 'pointermove', this, this.onMouseMove_, true);
|
||||
this.editor_ = svg;
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
circle, 'pointerdown', this, this.onMouseMove_, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
circle, 'pointermove', this, this.onMouseMove_, true));
|
||||
return svg;
|
||||
}
|
||||
|
||||
/** Disposes of events and DOM-references belonging to the angle editor. */
|
||||
private dropdownDispose_() {
|
||||
if (this.clickWrapper_) {
|
||||
browserEvents.unbind(this.clickWrapper_);
|
||||
this.clickWrapper_ = null;
|
||||
private dropdownDispose() {
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
if (this.clickSurfaceWrapper_) {
|
||||
browserEvents.unbind(this.clickSurfaceWrapper_);
|
||||
this.clickSurfaceWrapper_ = null;
|
||||
}
|
||||
if (this.moveSurfaceWrapper_) {
|
||||
browserEvents.unbind(this.moveSurfaceWrapper_);
|
||||
this.moveSurfaceWrapper_ = null;
|
||||
}
|
||||
this.gauge_ = null;
|
||||
this.line_ = null;
|
||||
this.boundEvents.length = 0;
|
||||
this.gauge = null;
|
||||
this.line = null;
|
||||
}
|
||||
|
||||
/** Hide the editor. */
|
||||
private hide_() {
|
||||
private hide() {
|
||||
dropDownDiv.hideIfOwner(this);
|
||||
WidgetDiv.hide();
|
||||
}
|
||||
@@ -315,7 +287,7 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*/
|
||||
protected onMouseMove_(e: PointerEvent) {
|
||||
// Calculate angle.
|
||||
const bBox = this.gauge_!.ownerSVGElement!.getBoundingClientRect();
|
||||
const bBox = this.gauge!.ownerSVGElement!.getBoundingClientRect();
|
||||
const dx = e.clientX - bBox.left - FieldAngle.HALF;
|
||||
const dy = e.clientY - bBox.top - FieldAngle.HALF;
|
||||
let angle = Math.atan(-dy / dx);
|
||||
@@ -332,13 +304,13 @@ export class FieldAngle extends FieldInput<number> {
|
||||
}
|
||||
|
||||
// Do offsetting.
|
||||
if (this.clockwise_) {
|
||||
angle = this.offset_ + 360 - angle;
|
||||
if (this.clockwise) {
|
||||
angle = this.offset + 360 - angle;
|
||||
} else {
|
||||
angle = 360 - (this.offset_ - angle);
|
||||
angle = 360 - (this.offset - angle);
|
||||
}
|
||||
|
||||
this.displayMouseOrKeyboardValue_(angle);
|
||||
this.displayMouseOrKeyboardValue(angle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,31 +320,31 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*
|
||||
* @param angle New angle.
|
||||
*/
|
||||
private displayMouseOrKeyboardValue_(angle: number) {
|
||||
if (this.round_) {
|
||||
angle = Math.round(angle / this.round_) * this.round_;
|
||||
private displayMouseOrKeyboardValue(angle: number) {
|
||||
if (this.round) {
|
||||
angle = Math.round(angle / this.round) * this.round;
|
||||
}
|
||||
angle = this.wrapValue_(angle);
|
||||
angle = this.wrapValue(angle);
|
||||
if (angle !== this.value_) {
|
||||
this.setEditorValue_(angle);
|
||||
}
|
||||
}
|
||||
|
||||
/** Redraw the graph with the current angle. */
|
||||
private updateGraph_() {
|
||||
if (!this.gauge_) {
|
||||
private updateGraph() {
|
||||
if (!this.gauge || !this.line) {
|
||||
return;
|
||||
}
|
||||
// Always display the input (i.e. getText) even if it is invalid.
|
||||
let angleDegrees = Number(this.getText()) + this.offset_;
|
||||
let angleDegrees = Number(this.getText()) + this.offset;
|
||||
angleDegrees %= 360;
|
||||
let angleRadians = math.toRadians(angleDegrees);
|
||||
const path = ['M ', FieldAngle.HALF, ',', FieldAngle.HALF];
|
||||
let x2 = FieldAngle.HALF;
|
||||
let y2 = FieldAngle.HALF;
|
||||
if (!isNaN(angleRadians)) {
|
||||
const clockwiseFlag = Number(this.clockwise_);
|
||||
const angle1 = math.toRadians(this.offset_);
|
||||
const clockwiseFlag = Number(this.clockwise);
|
||||
const angle1 = math.toRadians(this.offset);
|
||||
const x1 = Math.cos(angle1) * FieldAngle.RADIUS;
|
||||
const y1 = Math.sin(angle1) * -FieldAngle.RADIUS;
|
||||
if (clockwiseFlag) {
|
||||
@@ -390,13 +362,9 @@ export class FieldAngle extends FieldInput<number> {
|
||||
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
|
||||
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
|
||||
}
|
||||
this.gauge_.setAttribute('d', path.join(''));
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.line_!.setAttribute('x2', x2 as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
this.line_!.setAttribute('y2', y2 as AnyDuringMigration);
|
||||
this.gauge.setAttribute('d', path.join(''));
|
||||
this.line.setAttribute('x2', `${x2}`);
|
||||
this.line.setAttribute('y2', `${y2}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,31 +372,35 @@ export class FieldAngle extends FieldInput<number> {
|
||||
*
|
||||
* @param e Keyboard event.
|
||||
*/
|
||||
protected override onHtmlInputKeyDown_(e: Event) {
|
||||
protected override onHtmlInputKeyDown_(e: KeyboardEvent) {
|
||||
super.onHtmlInputKeyDown_(e);
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
const keyboardEvent = e as KeyboardEvent;
|
||||
let multiplier;
|
||||
if (keyboardEvent.keyCode === KeyCodes.LEFT) {
|
||||
// decrement (increment in RTL)
|
||||
multiplier = block.RTL ? 1 : -1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.RIGHT) {
|
||||
// increment (decrement in RTL)
|
||||
multiplier = block.RTL ? -1 : 1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.DOWN) {
|
||||
// decrement
|
||||
multiplier = -1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.UP) {
|
||||
// increment
|
||||
multiplier = 1;
|
||||
let multiplier = 0;
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
// decrement (increment in RTL)
|
||||
multiplier = block.RTL ? 1 : -1;
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
// increment (decrement in RTL)
|
||||
multiplier = block.RTL ? -1 : 1;
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
// decrement
|
||||
multiplier = -1;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
// increment
|
||||
multiplier = 1;
|
||||
break;
|
||||
}
|
||||
if (multiplier) {
|
||||
const value = this.getValue() as number;
|
||||
this.displayMouseOrKeyboardValue_(value + multiplier * this.round_);
|
||||
this.displayMouseOrKeyboardValue(value + multiplier * this.round);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
@@ -437,16 +409,15 @@ export class FieldAngle extends FieldInput<number> {
|
||||
/**
|
||||
* Ensure that the input value is a valid angle.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid angle, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
number|null {
|
||||
const value = Number(opt_newValue);
|
||||
protected override doClassValidation_(newValue?: any): number|null {
|
||||
const value = Number(newValue);
|
||||
if (isNaN(value) || !isFinite(value)) {
|
||||
return null;
|
||||
}
|
||||
return this.wrapValue_(value);
|
||||
return this.wrapValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,12 +426,12 @@ export class FieldAngle extends FieldInput<number> {
|
||||
* @param value The value to wrap.
|
||||
* @returns The wrapped value.
|
||||
*/
|
||||
private wrapValue_(value: number): number {
|
||||
private wrapValue(value: number): number {
|
||||
value %= 360;
|
||||
if (value < 0) {
|
||||
value += 360;
|
||||
}
|
||||
if (value > this.wrap_) {
|
||||
if (value > this.wrap) {
|
||||
value -= 360;
|
||||
}
|
||||
return value;
|
||||
@@ -481,13 +452,20 @@ export class FieldAngle extends FieldInput<number> {
|
||||
}
|
||||
}
|
||||
|
||||
/** CSS for angle field. See css.js for use. */
|
||||
fieldRegistry.register('field_angle', FieldAngle);
|
||||
|
||||
FieldAngle.prototype.DEFAULT_VALUE = 0;
|
||||
|
||||
|
||||
/**
|
||||
* CSS for angle field.
|
||||
*/
|
||||
Css.register(`
|
||||
.blocklyAngleCircle {
|
||||
stroke: #444;
|
||||
stroke-width: 1;
|
||||
fill: #ddd;
|
||||
fill-opacity: .8;
|
||||
fill-opacity: 0.8;
|
||||
}
|
||||
|
||||
.blocklyAngleMarks {
|
||||
@@ -497,7 +475,7 @@ Css.register(`
|
||||
|
||||
.blocklyAngleGauge {
|
||||
fill: #f88;
|
||||
fill-opacity: .8;
|
||||
fill-opacity: 0.8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -509,10 +487,6 @@ Css.register(`
|
||||
}
|
||||
`);
|
||||
|
||||
fieldRegistry.register('field_angle', FieldAngle);
|
||||
|
||||
FieldAngle.prototype.DEFAULT_VALUE = 0;
|
||||
|
||||
/**
|
||||
* The two main modes of the angle field.
|
||||
* Compass specifies:
|
||||
@@ -549,3 +523,20 @@ export interface FieldAngleConfig extends FieldInputConfig {
|
||||
export interface FieldAngleFromJsonConfig extends FieldAngleConfig {
|
||||
angle?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
export type FieldAngleValidator = FieldInputValidator<number>;
|
||||
|
||||
@@ -18,16 +18,14 @@ import './events/events_block_change.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {Field, FieldConfig, FieldValidator} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
|
||||
export type FieldCheckboxValidator = FieldValidator<boolean>;
|
||||
type BoolString = 'TRUE'|'FALSE';
|
||||
type CheckboxBool = BoolString|boolean;
|
||||
|
||||
/**
|
||||
* Class for a checkbox field.
|
||||
*
|
||||
* @alias Blockly.FieldCheckbox
|
||||
*/
|
||||
export class FieldCheckbox extends Field<boolean> {
|
||||
export class FieldCheckbox extends Field<CheckboxBool> {
|
||||
/** Default character for the checkmark. */
|
||||
static readonly CHECK_CHAR = '✓';
|
||||
private checkChar_: string;
|
||||
@@ -42,26 +40,30 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
* Mouse cursor style when over the hotspot that initiates editability.
|
||||
*/
|
||||
override CURSOR = 'default';
|
||||
override value_: AnyDuringMigration;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should either be 'TRUE',
|
||||
* NOTE: The default value is set in `Field`, so maintain that value instead
|
||||
* of overwriting it here or in the constructor.
|
||||
*/
|
||||
override value_: boolean|null = this.value_;
|
||||
|
||||
/**
|
||||
* @param value The initial value of the field. Should either be 'TRUE',
|
||||
* 'FALSE' or a boolean. Defaults to 'FALSE'. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a value ('TRUE' or 'FALSE') & returns a
|
||||
* validated value ('TRUE' or 'FALSE'), or null to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/checkbox#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|boolean|Sentinel,
|
||||
opt_validator?: FieldCheckboxValidator,
|
||||
opt_config?: FieldCheckboxConfig) {
|
||||
value?: CheckboxBool|typeof Field.SKIP_SETUP,
|
||||
validator?: FieldCheckboxValidator, config?: FieldCheckboxConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
/**
|
||||
@@ -70,15 +72,13 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
*/
|
||||
this.checkChar_ = FieldCheckbox.CHECK_CHAR;
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,15 +149,15 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
/**
|
||||
* Ensure that the input value is valid ('TRUE' or 'FALSE').
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid value ('TRUE' or 'FALSE), or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
if (opt_newValue === true || opt_newValue === 'TRUE') {
|
||||
protected override doClassValidation_(newValue?: AnyDuringMigration):
|
||||
BoolString|null {
|
||||
if (newValue === true || newValue === 'TRUE') {
|
||||
return 'TRUE';
|
||||
}
|
||||
if (opt_newValue === false || opt_newValue === 'FALSE') {
|
||||
if (newValue === false || newValue === 'FALSE') {
|
||||
return 'FALSE';
|
||||
}
|
||||
return null;
|
||||
@@ -169,7 +169,7 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is a either 'TRUE' or 'FALSE'.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected override doValueUpdate_(newValue: BoolString) {
|
||||
this.value_ = this.convertValueToBool_(newValue);
|
||||
// Update visual.
|
||||
if (this.textElement_) {
|
||||
@@ -182,7 +182,7 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
*
|
||||
* @returns The value of this field.
|
||||
*/
|
||||
override getValue(): string {
|
||||
override getValue(): BoolString {
|
||||
return this.value_ ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
|
||||
@@ -191,8 +191,8 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
*
|
||||
* @returns The boolean value of this field.
|
||||
*/
|
||||
getValueBoolean(): boolean {
|
||||
return this.value_ as boolean;
|
||||
getValueBoolean(): boolean|null {
|
||||
return this.value_;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,12 +213,9 @@ export class FieldCheckbox extends Field<boolean> {
|
||||
* @param value The value to convert.
|
||||
* @returns The converted value.
|
||||
*/
|
||||
private convertValueToBool_(value: AnyDuringMigration): boolean {
|
||||
if (typeof value === 'string') {
|
||||
return value === 'TRUE';
|
||||
} else {
|
||||
return !!value;
|
||||
}
|
||||
private convertValueToBool_(value: CheckboxBool|null): boolean {
|
||||
if (typeof value === 'string') return value === 'TRUE';
|
||||
return !!value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,3 +250,20 @@ export interface FieldCheckboxConfig extends FieldConfig {
|
||||
export interface FieldCheckboxFromJsonConfig extends FieldCheckboxConfig {
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
export type FieldCheckboxValidator = FieldValidator<CheckboxBool>;
|
||||
|
||||
@@ -25,16 +25,10 @@ import * as fieldRegistry from './field_registry.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import * as colour from './utils/colour.js';
|
||||
import * as idGenerator from './utils/idgenerator.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import {Size} from './utils/size.js';
|
||||
|
||||
export type FieldColourValidator = FieldValidator<string>;
|
||||
|
||||
/**
|
||||
* Class for a colour input field.
|
||||
*
|
||||
* @alias Blockly.FieldColour
|
||||
*/
|
||||
export class FieldColour extends Field<string> {
|
||||
/**
|
||||
@@ -80,25 +74,17 @@ export class FieldColour extends Field<string> {
|
||||
static COLUMNS = 7;
|
||||
|
||||
/** The field's colour picker element. */
|
||||
private picker_: Element|null = null;
|
||||
private picker: HTMLElement|null = null;
|
||||
|
||||
/** Index of the currently highlighted element. */
|
||||
private highlightedIndex_: number|null = null;
|
||||
private highlightedIndex: number|null = null;
|
||||
|
||||
/** Mouse click event data. */
|
||||
private onClickWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse move event data. */
|
||||
private onMouseMoveWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse enter event data. */
|
||||
private onMouseEnterWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse leave event data. */
|
||||
private onMouseLeaveWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Key down event data. */
|
||||
private onKeyDownWrapper_: browserEvents.Data|null = null;
|
||||
/**
|
||||
* Array holding info needed to unbind events.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
*/
|
||||
private boundEvents: browserEvents.Data[] = [];
|
||||
|
||||
/**
|
||||
* Serializable fields are saved by the serializer, non-serializable fields
|
||||
@@ -117,55 +103,46 @@ export class FieldColour extends Field<string> {
|
||||
protected override isDirty_ = false;
|
||||
|
||||
/** Array of colours used by this field. If null, use the global list. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'string[]'.
|
||||
private colours_: string[] = null as AnyDuringMigration;
|
||||
private colours: string[]|null = null;
|
||||
|
||||
/**
|
||||
* Array of colour tooltips used by this field. If null, use the global
|
||||
* list.
|
||||
*/
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'string[]'.
|
||||
private titles_: string[] = null as AnyDuringMigration;
|
||||
private titles: string[]|null = null;
|
||||
|
||||
/**
|
||||
* Number of colour columns used by this field. If 0, use the global
|
||||
* setting. By default use the global constants for columns.
|
||||
*/
|
||||
private columns_ = 0;
|
||||
override size_: AnyDuringMigration;
|
||||
override clickTarget_: AnyDuringMigration;
|
||||
override value_: AnyDuringMigration;
|
||||
private columns = 0;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should be in '#rrggbb'
|
||||
* @param value The initial value of the field. Should be in '#rrggbb'
|
||||
* format. Defaults to the first value in the default colour array. Also
|
||||
* accepts Field.SKIP_SETUP if you wish to skip setup (only used by
|
||||
* subclasses that want to handle configuration and setting the field
|
||||
* value after their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a colour string & returns a validated colour
|
||||
* string ('#rrggbb' format), or null to abort the change.Blockly.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* string ('#rrggbb' format), or null to abort the change.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/colour}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_validator?: FieldColourValidator,
|
||||
opt_config?: FieldColourConfig) {
|
||||
value?: string|typeof Field.SKIP_SETUP, validator?: FieldColourValidator,
|
||||
config?: FieldColourConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,9 +153,9 @@ export class FieldColour extends Field<string> {
|
||||
*/
|
||||
protected override configure_(config: FieldColourConfig) {
|
||||
super.configure_(config);
|
||||
if (config.colourOptions) this.colours_ = config.colourOptions;
|
||||
if (config.colourTitles) this.titles_ = config.colourTitles;
|
||||
if (config.columns) this.columns_ = config.columns;
|
||||
if (config.colourOptions) this.colours = config.colourOptions;
|
||||
if (config.colourTitles) this.titles = config.colourTitles;
|
||||
if (config.columns) this.columns = config.columns;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +175,11 @@ export class FieldColour extends Field<string> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates text field to match the colour/style of the block.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
override applyColour() {
|
||||
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
|
||||
if (this.borderRect_) {
|
||||
@@ -213,15 +195,14 @@ export class FieldColour extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value is a valid colour.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid colour, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
if (typeof opt_newValue !== 'string') {
|
||||
protected override doClassValidation_(newValue?: any): string|null {
|
||||
if (typeof newValue !== 'string') {
|
||||
return null;
|
||||
}
|
||||
return colour.parse(opt_newValue);
|
||||
return colour.parse(newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,15 +211,14 @@ export class FieldColour extends Field<string> {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is a colour in '#rrggbb' format.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected override doValueUpdate_(newValue: string) {
|
||||
this.value_ = newValue;
|
||||
if (this.borderRect_) {
|
||||
this.borderRect_.style.fill = newValue as string;
|
||||
this.borderRect_.style.fill = newValue;
|
||||
} else if (
|
||||
this.sourceBlock_ && this.sourceBlock_.rendered &&
|
||||
this.sourceBlock_ instanceof BlockSvg) {
|
||||
this.sourceBlock_.pathObject.svgPath.setAttribute(
|
||||
'fill', newValue as string);
|
||||
this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue);
|
||||
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
|
||||
}
|
||||
}
|
||||
@@ -262,14 +242,14 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param colours Array of colours for this block, or null to use default
|
||||
* (FieldColour.COLOURS).
|
||||
* @param opt_titles Optional array of colour tooltips, or null to use default
|
||||
* @param titles Optional array of colour tooltips, or null to use default
|
||||
* (FieldColour.TITLES).
|
||||
* @returns Returns itself (for method chaining).
|
||||
*/
|
||||
setColours(colours: string[], opt_titles?: string[]): FieldColour {
|
||||
this.colours_ = colours;
|
||||
if (opt_titles) {
|
||||
this.titles_ = opt_titles;
|
||||
setColours(colours: string[], titles?: string[]): FieldColour {
|
||||
this.colours = colours;
|
||||
if (titles) {
|
||||
this.titles = titles;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -282,23 +262,19 @@ export class FieldColour extends Field<string> {
|
||||
* @returns Returns itself (for method chaining).
|
||||
*/
|
||||
setColumns(columns: number): FieldColour {
|
||||
this.columns_ = columns;
|
||||
this.columns = columns;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Create and show the colour field's editor. */
|
||||
protected override showEditor_() {
|
||||
this.dropdownCreate_();
|
||||
// AnyDuringMigration because: Argument of type 'Element | null' is not
|
||||
// assignable to parameter of type 'Node'.
|
||||
dropDownDiv.getContentDiv().appendChild(this.picker_ as AnyDuringMigration);
|
||||
this.dropdownCreate();
|
||||
dropDownDiv.getContentDiv().appendChild(this.picker!);
|
||||
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose_.bind(this));
|
||||
dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
|
||||
|
||||
// Focus so we can start receiving keyboard events.
|
||||
// AnyDuringMigration because: Property 'focus' does not exist on type
|
||||
// 'Element'.
|
||||
(this.picker_ as AnyDuringMigration)!.focus({preventScroll: true});
|
||||
this.picker!.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -306,7 +282,7 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
private onClick_(e: PointerEvent) {
|
||||
private onClick(e: PointerEvent) {
|
||||
const cell = e.target as Element;
|
||||
const colour = cell && cell.getAttribute('data-colour');
|
||||
if (colour !== null) {
|
||||
@@ -321,31 +297,35 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Keyboard event.
|
||||
*/
|
||||
private onKeyDown_(e: KeyboardEvent) {
|
||||
let handled = false;
|
||||
if (e.keyCode === KeyCodes.UP) {
|
||||
this.moveHighlightBy_(0, -1);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.DOWN) {
|
||||
this.moveHighlightBy_(0, 1);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.LEFT) {
|
||||
this.moveHighlightBy_(-1, 0);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.RIGHT) {
|
||||
this.moveHighlightBy_(1, 0);
|
||||
handled = true;
|
||||
} else if (e.keyCode === KeyCodes.ENTER) {
|
||||
// Select the highlighted colour.
|
||||
const highlighted = this.getHighlighted_();
|
||||
if (highlighted) {
|
||||
const colour = highlighted && highlighted.getAttribute('data-colour');
|
||||
if (colour !== null) {
|
||||
this.setValue(colour);
|
||||
private onKeyDown(e: KeyboardEvent) {
|
||||
let handled = true;
|
||||
let highlighted: HTMLElement|null;
|
||||
switch (e.key) {
|
||||
case 'ArrowUp':
|
||||
this.moveHighlightBy(0, -1);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
this.moveHighlightBy(0, 1);
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
this.moveHighlightBy(-1, 0);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
this.moveHighlightBy(1, 0);
|
||||
break;
|
||||
case 'Enter':
|
||||
// Select the highlighted colour.
|
||||
highlighted = this.getHighlighted();
|
||||
if (highlighted) {
|
||||
const colour = highlighted.getAttribute('data-colour');
|
||||
if (colour !== null) {
|
||||
this.setValue(colour);
|
||||
}
|
||||
}
|
||||
}
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
handled = true;
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
if (handled) {
|
||||
e.stopPropagation();
|
||||
@@ -355,22 +335,22 @@ export class FieldColour extends Field<string> {
|
||||
/**
|
||||
* Move the currently highlighted position by dx and dy.
|
||||
*
|
||||
* @param dx Change of x
|
||||
* @param dy Change of y
|
||||
* @param dx Change of x.
|
||||
* @param dy Change of y.
|
||||
*/
|
||||
private moveHighlightBy_(dx: number, dy: number) {
|
||||
if (!this.highlightedIndex_) {
|
||||
private moveHighlightBy(dx: number, dy: number) {
|
||||
if (!this.highlightedIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const colours = this.colours_ || FieldColour.COLOURS;
|
||||
const columns = this.columns_ || FieldColour.COLUMNS;
|
||||
const colours = this.colours || FieldColour.COLOURS;
|
||||
const columns = this.columns || FieldColour.COLUMNS;
|
||||
|
||||
// Get the current x and y coordinates
|
||||
let x = this.highlightedIndex_ % columns;
|
||||
let y = Math.floor(this.highlightedIndex_ / columns);
|
||||
// Get the current x and y coordinates.
|
||||
let x = this.highlightedIndex % columns;
|
||||
let y = Math.floor(this.highlightedIndex / columns);
|
||||
|
||||
// Add the offset
|
||||
// Add the offset.
|
||||
x += dx;
|
||||
y += dy;
|
||||
|
||||
@@ -405,9 +385,9 @@ export class FieldColour extends Field<string> {
|
||||
}
|
||||
|
||||
// Move the highlight to the new coordinates.
|
||||
const cell = this.picker_!.childNodes[y].childNodes[x] as Element;
|
||||
const cell = this.picker!.childNodes[y].childNodes[x] as Element;
|
||||
const index = y * columns + x;
|
||||
this.setHighlightedCell_(cell, index);
|
||||
this.setHighlightedCell(cell, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -415,30 +395,26 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @param e Mouse event.
|
||||
*/
|
||||
private onMouseMove_(e: PointerEvent) {
|
||||
private onMouseMove(e: PointerEvent) {
|
||||
const cell = e.target as Element;
|
||||
const index = cell && Number(cell.getAttribute('data-index'));
|
||||
if (index !== null && index !== this.highlightedIndex_) {
|
||||
this.setHighlightedCell_(cell, index);
|
||||
if (index !== null && index !== this.highlightedIndex) {
|
||||
this.setHighlightedCell(cell, index);
|
||||
}
|
||||
}
|
||||
|
||||
/** Handle a mouse enter event. Focus the picker. */
|
||||
private onMouseEnter_() {
|
||||
// AnyDuringMigration because: Property 'focus' does not exist on type
|
||||
// 'Element'.
|
||||
(this.picker_ as AnyDuringMigration)!.focus({preventScroll: true});
|
||||
private onMouseEnter() {
|
||||
this.picker?.focus({preventScroll: true});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse leave event. Blur the picker and unhighlight
|
||||
* the currently highlighted colour.
|
||||
*/
|
||||
private onMouseLeave_() {
|
||||
// AnyDuringMigration because: Property 'blur' does not exist on type
|
||||
// 'Element'.
|
||||
(this.picker_ as AnyDuringMigration)!.blur();
|
||||
const highlighted = this.getHighlighted_();
|
||||
private onMouseLeave() {
|
||||
this.picker?.blur();
|
||||
const highlighted = this.getHighlighted();
|
||||
if (highlighted) {
|
||||
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||
}
|
||||
@@ -449,55 +425,53 @@ export class FieldColour extends Field<string> {
|
||||
*
|
||||
* @returns Highlighted item (null if none).
|
||||
*/
|
||||
private getHighlighted_(): HTMLElement|null {
|
||||
if (!this.highlightedIndex_) {
|
||||
private getHighlighted(): HTMLElement|null {
|
||||
if (!this.highlightedIndex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = this.columns_ || FieldColour.COLUMNS;
|
||||
const x = this.highlightedIndex_ % columns;
|
||||
const y = Math.floor(this.highlightedIndex_ / columns);
|
||||
const row = this.picker_!.childNodes[y];
|
||||
const columns = this.columns || FieldColour.COLUMNS;
|
||||
const x = this.highlightedIndex % columns;
|
||||
const y = Math.floor(this.highlightedIndex / columns);
|
||||
const row = this.picker!.childNodes[y];
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
const col = row.childNodes[x] as HTMLElement;
|
||||
return col;
|
||||
return row.childNodes[x] as HTMLElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the currently highlighted cell.
|
||||
*
|
||||
* @param cell the new cell to highlight
|
||||
* @param index the index of the new cell
|
||||
* @param cell The new cell to highlight.
|
||||
* @param index The index of the new cell.
|
||||
*/
|
||||
private setHighlightedCell_(cell: Element, index: number) {
|
||||
private setHighlightedCell(cell: Element, index: number) {
|
||||
// Unhighlight the current item.
|
||||
const highlighted = this.getHighlighted_();
|
||||
const highlighted = this.getHighlighted();
|
||||
if (highlighted) {
|
||||
dom.removeClass(highlighted, 'blocklyColourHighlighted');
|
||||
}
|
||||
// Highlight new item.
|
||||
dom.addClass(cell, 'blocklyColourHighlighted');
|
||||
// Set new highlighted index.
|
||||
this.highlightedIndex_ = index;
|
||||
this.highlightedIndex = index;
|
||||
|
||||
// Update accessibility roles.
|
||||
// AnyDuringMigration because: Argument of type 'string | null' is not
|
||||
// assignable to parameter of type 'string | number | boolean | string[]'.
|
||||
aria.setState(
|
||||
this.picker_ as Element, aria.State.ACTIVEDESCENDANT,
|
||||
cell.getAttribute('id') as AnyDuringMigration);
|
||||
const cellId = cell.getAttribute('id');
|
||||
if (cellId && this.picker) {
|
||||
aria.setState(this.picker, aria.State.ACTIVEDESCENDANT, cellId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a colour picker dropdown editor. */
|
||||
private dropdownCreate_() {
|
||||
const columns = this.columns_ || FieldColour.COLUMNS;
|
||||
const colours = this.colours_ || FieldColour.COLOURS;
|
||||
const titles = this.titles_ || FieldColour.TITLES;
|
||||
private dropdownCreate() {
|
||||
const columns = this.columns || FieldColour.COLUMNS;
|
||||
const colours = this.colours || FieldColour.COLOURS;
|
||||
const titles = this.titles || FieldColour.TITLES;
|
||||
const selectedColour = this.getValue();
|
||||
// Create the palette.
|
||||
const table = (document.createElement('table'));
|
||||
const table = document.createElement('table');
|
||||
table.className = 'blocklyColourTable';
|
||||
table.tabIndex = 0;
|
||||
table.dir = 'ltr';
|
||||
@@ -519,58 +493,40 @@ export class FieldColour extends Field<string> {
|
||||
cell.setAttribute('data-colour', colours[i]);
|
||||
cell.title = titles[i] || colours[i];
|
||||
cell.id = idGenerator.getNextUniqueId();
|
||||
// AnyDuringMigration because: Argument of type 'number' is not
|
||||
// assignable to parameter of type 'string'.
|
||||
cell.setAttribute('data-index', i as AnyDuringMigration);
|
||||
cell.setAttribute('data-index', `${i}`);
|
||||
aria.setRole(cell, aria.Role.GRIDCELL);
|
||||
aria.setState(cell, aria.State.LABEL, colours[i]);
|
||||
aria.setState(cell, aria.State.SELECTED, colours[i] === selectedColour);
|
||||
cell.style.backgroundColor = colours[i];
|
||||
if (colours[i] === selectedColour) {
|
||||
cell.className = 'blocklyColourSelected';
|
||||
this.highlightedIndex_ = i;
|
||||
this.highlightedIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure event handler on the table to listen for any event in a cell.
|
||||
this.onClickWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointerdown', this, this.onClick_, true);
|
||||
this.onMouseMoveWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointermove', this, this.onMouseMove_, true);
|
||||
this.onMouseEnterWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointerenter', this, this.onMouseEnter_, true);
|
||||
this.onMouseLeaveWrapper_ = browserEvents.conditionalBind(
|
||||
table, 'pointerleave', this, this.onMouseLeave_, true);
|
||||
this.onKeyDownWrapper_ =
|
||||
browserEvents.conditionalBind(table, 'keydown', this, this.onKeyDown_);
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointerdown', this, this.onClick, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointermove', this, this.onMouseMove, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointerenter', this, this.onMouseEnter, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'pointerleave', this, this.onMouseLeave, true));
|
||||
this.boundEvents.push(browserEvents.conditionalBind(
|
||||
table, 'keydown', this, this.onKeyDown, false));
|
||||
|
||||
this.picker_ = table;
|
||||
this.picker = table;
|
||||
}
|
||||
|
||||
/** Disposes of events and DOM-references belonging to the colour editor. */
|
||||
private dropdownDispose_() {
|
||||
if (this.onClickWrapper_) {
|
||||
browserEvents.unbind(this.onClickWrapper_);
|
||||
this.onClickWrapper_ = null;
|
||||
private dropdownDispose() {
|
||||
for (const event of this.boundEvents) {
|
||||
browserEvents.unbind(event);
|
||||
}
|
||||
if (this.onMouseMoveWrapper_) {
|
||||
browserEvents.unbind(this.onMouseMoveWrapper_);
|
||||
this.onMouseMoveWrapper_ = null;
|
||||
}
|
||||
if (this.onMouseEnterWrapper_) {
|
||||
browserEvents.unbind(this.onMouseEnterWrapper_);
|
||||
this.onMouseEnterWrapper_ = null;
|
||||
}
|
||||
if (this.onMouseLeaveWrapper_) {
|
||||
browserEvents.unbind(this.onMouseLeaveWrapper_);
|
||||
this.onMouseLeaveWrapper_ = null;
|
||||
}
|
||||
if (this.onKeyDownWrapper_) {
|
||||
browserEvents.unbind(this.onKeyDownWrapper_);
|
||||
this.onKeyDownWrapper_ = null;
|
||||
}
|
||||
this.picker_ = null;
|
||||
this.highlightedIndex_ = null;
|
||||
this.boundEvents.length = 0;
|
||||
this.picker = null;
|
||||
this.highlightedIndex = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -584,14 +540,19 @@ export class FieldColour extends Field<string> {
|
||||
static fromJson(options: FieldColourFromJsonConfig): FieldColour {
|
||||
// `this` might be a subclass of FieldColour if that class doesn't override
|
||||
// the static fromJson method.
|
||||
return new this(options['colour'], undefined, options);
|
||||
return new this(options.colour, undefined, options);
|
||||
}
|
||||
}
|
||||
|
||||
/** The default value for this field. */
|
||||
FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0];
|
||||
|
||||
/** CSS for colour picker. See css.js for use. */
|
||||
fieldRegistry.register('field_colour', FieldColour);
|
||||
|
||||
|
||||
/**
|
||||
* CSS for colour picker.
|
||||
*/
|
||||
Css.register(`
|
||||
.blocklyColourTable {
|
||||
border-collapse: collapse;
|
||||
@@ -601,7 +562,7 @@ Css.register(`
|
||||
}
|
||||
|
||||
.blocklyColourTable>tr>td {
|
||||
border: .5px solid #888;
|
||||
border: 0.5px solid #888;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -612,7 +573,7 @@ Css.register(`
|
||||
|
||||
.blocklyColourTable>tr>td.blocklyColourHighlighted {
|
||||
border-color: #eee;
|
||||
box-shadow: 2px 2px 7px 2px rgba(0,0,0,.3);
|
||||
box-shadow: 2px 2px 7px 2px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -623,8 +584,6 @@ Css.register(`
|
||||
}
|
||||
`);
|
||||
|
||||
fieldRegistry.register('field_colour', FieldColour);
|
||||
|
||||
/**
|
||||
* Config options for the colour field.
|
||||
*/
|
||||
@@ -640,3 +599,20 @@ export interface FieldColourConfig extends FieldConfig {
|
||||
export interface FieldColourFromJsonConfig extends FieldColourConfig {
|
||||
colour?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
export type FieldColourValidator = FieldValidator<string>;
|
||||
|
||||
@@ -20,20 +20,16 @@ import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {Menu} from './menu.js';
|
||||
import {MenuItem} from './menuitem.js';
|
||||
import * as style from './utils/style.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import * as utilsString from './utils/string.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
|
||||
export type FieldDropdownValidator = FieldValidator<string>;
|
||||
|
||||
/**
|
||||
* Class for an editable dropdown field.
|
||||
*
|
||||
* @alias Blockly.FieldDropdown
|
||||
*/
|
||||
export class FieldDropdown extends Field<string> {
|
||||
/** Horizontal distance that a checkmark overhangs the dropdown. */
|
||||
@@ -92,7 +88,7 @@ export class FieldDropdown extends Field<string> {
|
||||
*/
|
||||
override suffixField: string|null = null;
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
private selectedOption_!: Array<string|ImageProperties>;
|
||||
private selectedOption_!: MenuOption;
|
||||
override clickTarget_: SVGElement|null = null;
|
||||
|
||||
/**
|
||||
@@ -101,11 +97,11 @@ export class FieldDropdown extends Field<string> {
|
||||
* if you wish to skip setup (only used by subclasses that want to handle
|
||||
* configuration and setting the field value after their own constructors
|
||||
* have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a language-neutral dropdown option & returns a
|
||||
* validated language-neutral dropdown option, or null to abort the
|
||||
* change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/dropdown#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
@@ -113,19 +109,19 @@ export class FieldDropdown extends Field<string> {
|
||||
*/
|
||||
constructor(
|
||||
menuGenerator: MenuGenerator,
|
||||
opt_validator?: FieldDropdownValidator,
|
||||
opt_config?: FieldConfig,
|
||||
validator?: FieldDropdownValidator,
|
||||
config?: FieldDropdownConfig,
|
||||
);
|
||||
constructor(menuGenerator: Sentinel);
|
||||
constructor(menuGenerator: typeof Field.SKIP_SETUP);
|
||||
constructor(
|
||||
menuGenerator: MenuGenerator|Sentinel,
|
||||
opt_validator?: FieldDropdownValidator,
|
||||
opt_config?: FieldConfig,
|
||||
menuGenerator: MenuGenerator|typeof Field.SKIP_SETUP,
|
||||
validator?: FieldDropdownValidator,
|
||||
config?: FieldDropdownConfig,
|
||||
) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
// If we pass SKIP_SETUP, don't do *anything* with the menu generator.
|
||||
if (!isMenuGenerator(menuGenerator)) return;
|
||||
if (menuGenerator === Field.SKIP_SETUP) return;
|
||||
|
||||
if (Array.isArray(menuGenerator)) {
|
||||
validateOptions(menuGenerator);
|
||||
@@ -143,12 +139,12 @@ export class FieldDropdown extends Field<string> {
|
||||
*/
|
||||
this.selectedOption_ = this.getOptions(false)[0];
|
||||
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
this.setValue(this.selectedOption_[1]);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,17 +244,17 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Create a dropdown menu under the text.
|
||||
*
|
||||
* @param opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
*/
|
||||
protected override showEditor_(opt_e?: MouseEvent) {
|
||||
protected override showEditor_(e?: MouseEvent) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.dropdownCreate_();
|
||||
if (opt_e && typeof opt_e.clientX === 'number') {
|
||||
this.menu_!.openingCoords = new Coordinate(opt_e.clientX, opt_e.clientY);
|
||||
if (e && typeof e.clientX === 'number') {
|
||||
this.menu_!.openingCoords = new Coordinate(e.clientX, e.clientY);
|
||||
} else {
|
||||
this.menu_!.openingCoords = null;
|
||||
}
|
||||
@@ -287,6 +283,9 @@ export class FieldDropdown extends Field<string> {
|
||||
|
||||
if (this.selectedMenuItem_) {
|
||||
this.menu_!.setHighlighted(this.selectedMenuItem_);
|
||||
style.scrollIntoContainerView(
|
||||
this.selectedMenuItem_.getElement()!, dropDownDiv.getContentDiv(),
|
||||
true);
|
||||
}
|
||||
|
||||
this.applyColour();
|
||||
@@ -332,7 +331,7 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Disposes of events and DOM-references belonging to the dropdown editor.
|
||||
*/
|
||||
private dropdownDispose_() {
|
||||
protected dropdownDispose_() {
|
||||
if (this.menu_) {
|
||||
this.menu_.dispose();
|
||||
}
|
||||
@@ -372,20 +371,20 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Return a list of the options for this dropdown.
|
||||
*
|
||||
* @param opt_useCache For dynamic options, whether or not to use the cached
|
||||
* @param useCache For dynamic options, whether or not to use the cached
|
||||
* options or to re-generate them.
|
||||
* @returns A non-empty array of option tuples:
|
||||
* (human-readable text or image, language-neutral name).
|
||||
* @throws {TypeError} If generated options are incorrectly structured.
|
||||
*/
|
||||
getOptions(opt_useCache?: boolean): MenuOption[] {
|
||||
getOptions(useCache?: boolean): MenuOption[] {
|
||||
if (!this.menuGenerator_) {
|
||||
// A subclass improperly skipped setup without defining the menu
|
||||
// generator.
|
||||
throw TypeError('A menu generator was never defined.');
|
||||
}
|
||||
if (Array.isArray(this.menuGenerator_)) return this.menuGenerator_;
|
||||
if (opt_useCache && this.generatedOptions_) return this.generatedOptions_;
|
||||
if (useCache && this.generatedOptions_) return this.generatedOptions_;
|
||||
|
||||
this.generatedOptions_ = this.menuGenerator_();
|
||||
validateOptions(this.generatedOptions_);
|
||||
@@ -395,24 +394,23 @@ export class FieldDropdown extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value is a valid language-neutral option.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid language-neutral option, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: MenuOption[1]): string
|
||||
|null {
|
||||
protected override doClassValidation_(newValue?: string): string|null {
|
||||
const options = this.getOptions(true);
|
||||
const isValueValid = options.some((option) => option[1] === opt_newValue);
|
||||
const isValueValid = options.some((option) => option[1] === newValue);
|
||||
|
||||
if (!isValueValid) {
|
||||
if (this.sourceBlock_) {
|
||||
console.warn(
|
||||
'Cannot set the dropdown\'s value to an unavailable option.' +
|
||||
' Block type: ' + this.sourceBlock_.type +
|
||||
', Field name: ' + this.name + ', Value: ' + opt_newValue);
|
||||
', Field name: ' + this.name + ', Value: ' + newValue);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return opt_newValue as string;
|
||||
return newValue as string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,7 +419,7 @@ export class FieldDropdown extends Field<string> {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is one of the valid dropdown options.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: MenuOption[1]) {
|
||||
protected override doValueUpdate_(newValue: string) {
|
||||
super.doValueUpdate_(newValue);
|
||||
const options = this.getOptions(true);
|
||||
for (let i = 0, option; option = options[i]; i++) {
|
||||
@@ -465,7 +463,7 @@ export class FieldDropdown extends Field<string> {
|
||||
// Show correct element.
|
||||
const option = this.selectedOption_ && this.selectedOption_[0];
|
||||
if (option && typeof option === 'object') {
|
||||
this.renderSelectedImage_((option));
|
||||
this.renderSelectedImage_(option);
|
||||
} else {
|
||||
this.renderSelectedText_();
|
||||
}
|
||||
@@ -486,8 +484,8 @@ export class FieldDropdown extends Field<string> {
|
||||
this.imageElement_!.style.display = '';
|
||||
this.imageElement_!.setAttributeNS(
|
||||
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
||||
this.imageElement_!.setAttribute('height', `${imageJson.height}`);
|
||||
this.imageElement_!.setAttribute('width', `${imageJson.width}`);
|
||||
this.imageElement_!.setAttribute('height', String(imageJson.height));
|
||||
this.imageElement_!.setAttribute('width', String(imageJson.width));
|
||||
|
||||
const imageHeight = Number(imageJson.height);
|
||||
const imageWidth = Number(imageJson.width);
|
||||
@@ -517,14 +515,13 @@ export class FieldDropdown extends Field<string> {
|
||||
let arrowX = 0;
|
||||
if (block.RTL) {
|
||||
const imageX = xPadding + arrowWidth;
|
||||
this.imageElement_!.setAttribute('x', imageX.toString());
|
||||
this.imageElement_!.setAttribute('x', `${imageX}`);
|
||||
} else {
|
||||
arrowX = imageWidth + arrowWidth;
|
||||
this.getTextElement().setAttribute('text-anchor', 'end');
|
||||
this.imageElement_!.setAttribute('x', xPadding.toString());
|
||||
this.imageElement_!.setAttribute('x', `${xPadding}`);
|
||||
}
|
||||
this.imageElement_!.setAttribute(
|
||||
'y', (height / 2 - imageHeight / 2).toString());
|
||||
this.imageElement_!.setAttribute('y', String(height / 2 - imageHeight / 2));
|
||||
|
||||
this.positionTextElement_(arrowX + xPadding, imageWidth + arrowWidth);
|
||||
}
|
||||
@@ -588,8 +585,8 @@ export class FieldDropdown extends Field<string> {
|
||||
|
||||
/**
|
||||
* Use the `getText_` developer hook to override the field's text
|
||||
* representation. Get the selected option text. If the selected option is an
|
||||
* image we return the image alt text.
|
||||
* representation. Get the selected option text. If the selected option is
|
||||
* an image we return the image alt text.
|
||||
*
|
||||
* @returns Selected option text.
|
||||
*/
|
||||
@@ -654,13 +651,35 @@ export type MenuGeneratorFunction = (this: FieldDropdown) => MenuOption[];
|
||||
*/
|
||||
export type MenuGenerator = MenuOption[]|MenuGeneratorFunction;
|
||||
|
||||
/**
|
||||
* Config options for the dropdown field.
|
||||
*/
|
||||
export type FieldDropdownConfig = FieldConfig;
|
||||
|
||||
/**
|
||||
* fromJson config for the dropdown field.
|
||||
*/
|
||||
export interface FieldDropdownFromJsonConfig extends FieldConfig {
|
||||
export interface FieldDropdownFromJsonConfig extends FieldDropdownConfig {
|
||||
options?: MenuOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
export type FieldDropdownValidator = FieldValidator<string>;
|
||||
|
||||
/**
|
||||
* The y offset from the top of the field to the top of the image, if an image
|
||||
* is selected.
|
||||
@@ -670,15 +689,6 @@ const IMAGE_Y_OFFSET = 5;
|
||||
/** The total vertical padding above and below an image. */
|
||||
const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2;
|
||||
|
||||
/**
|
||||
* NOTE: Because Sentinel is an empty class, proving a value is Sentinel does
|
||||
* not resolve in TS that it isn't a MenuGenerator.
|
||||
*/
|
||||
function isMenuGenerator(menuGenerator: MenuGenerator|
|
||||
Sentinel): menuGenerator is MenuGenerator {
|
||||
return menuGenerator !== Field.SKIP_SETUP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factor out common words in statically defined options.
|
||||
* Create prefix and/or suffix labels.
|
||||
|
||||
@@ -16,14 +16,11 @@ import {Field, FieldConfig} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import {Size} from './utils/size.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
|
||||
/**
|
||||
* Class for an image on a block.
|
||||
*
|
||||
* @alias Blockly.FieldImage
|
||||
*/
|
||||
export class FieldImage extends Field<string> {
|
||||
/**
|
||||
@@ -35,12 +32,10 @@ export class FieldImage extends Field<string> {
|
||||
private readonly imageHeight_: number;
|
||||
|
||||
/** The function to be called when this field is clicked. */
|
||||
private clickHandler_: ((p1: FieldImage) => AnyDuringMigration)|null = null;
|
||||
private clickHandler_: ((p1: FieldImage) => void)|null = null;
|
||||
|
||||
/** The rendered field's image element. */
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGImageElement'.
|
||||
private imageElement_: SVGImageElement = null as AnyDuringMigration;
|
||||
private imageElement_: SVGImageElement|null = null;
|
||||
|
||||
/**
|
||||
* Editable fields usually show some sort of UI indicating they are
|
||||
@@ -60,7 +55,6 @@ export class FieldImage extends Field<string> {
|
||||
|
||||
/** Alt text of this image. */
|
||||
private altText_ = '';
|
||||
override value_: AnyDuringMigration;
|
||||
|
||||
/**
|
||||
* @param src The URL of the image.
|
||||
@@ -69,19 +63,19 @@ export class FieldImage extends Field<string> {
|
||||
* after their own constructors have run).
|
||||
* @param width Width of the image.
|
||||
* @param height Height of the image.
|
||||
* @param opt_alt Optional alt text for when block is collapsed.
|
||||
* @param opt_onClick Optional function to be called when the image is
|
||||
* clicked. If opt_onClick is defined, opt_alt must also be defined.
|
||||
* @param opt_flipRtl Whether to flip the icon in RTL.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param alt Optional alt text for when block is collapsed.
|
||||
* @param onClick Optional function to be called when the image is
|
||||
* clicked. If onClick is defined, alt must also be defined.
|
||||
* @param flipRtl Whether to flip the icon in RTL.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/image#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
src: string|Sentinel, width: string|number, height: string|number,
|
||||
opt_alt?: string, opt_onClick?: (p1: FieldImage) => AnyDuringMigration,
|
||||
opt_flipRtl?: boolean, opt_config?: FieldImageConfig) {
|
||||
src: string|typeof Field.SKIP_SETUP, width: string|number,
|
||||
height: string|number, alt?: string, onClick?: (p1: FieldImage) => void,
|
||||
flipRtl?: boolean, config?: FieldImageConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
const imageHeight = Number(parsing.replaceMessageReferences(height));
|
||||
@@ -105,19 +99,17 @@ export class FieldImage extends Field<string> {
|
||||
*/
|
||||
this.imageHeight_ = imageHeight;
|
||||
|
||||
if (typeof opt_onClick === 'function') {
|
||||
this.clickHandler_ = opt_onClick;
|
||||
if (typeof onClick === 'function') {
|
||||
this.clickHandler_ = onClick;
|
||||
}
|
||||
|
||||
if (src === Field.SKIP_SETUP) {
|
||||
return;
|
||||
}
|
||||
if (src === Field.SKIP_SETUP) return;
|
||||
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
} else {
|
||||
this.flipRtl_ = !!opt_flipRtl;
|
||||
this.altText_ = parsing.replaceMessageReferences(opt_alt) || '';
|
||||
this.flipRtl_ = !!flipRtl;
|
||||
this.altText_ = parsing.replaceMessageReferences(alt) || '';
|
||||
}
|
||||
this.setValue(parsing.replaceMessageReferences(src));
|
||||
}
|
||||
@@ -162,15 +154,14 @@ export class FieldImage extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value (the source URL) is a string.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A string, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
if (typeof opt_newValue !== 'string') {
|
||||
protected override doClassValidation_(newValue?: any): string|null {
|
||||
if (typeof newValue !== 'string') {
|
||||
return null;
|
||||
}
|
||||
return opt_newValue;
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,11 +170,11 @@ export class FieldImage extends Field<string> {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is a string.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected override doValueUpdate_(newValue: string) {
|
||||
this.value_ = newValue;
|
||||
if (this.imageElement_) {
|
||||
this.imageElement_.setAttributeNS(
|
||||
dom.XLINK_NS, 'xlink:href', String(this.value_));
|
||||
dom.XLINK_NS, 'xlink:href', this.value_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +218,7 @@ export class FieldImage extends Field<string> {
|
||||
* @param func The function that is called when the image is clicked, or null
|
||||
* to remove.
|
||||
*/
|
||||
setOnClickHandler(func: ((p1: FieldImage) => AnyDuringMigration)|null) {
|
||||
setOnClickHandler(func: ((p1: FieldImage) => void)|null) {
|
||||
this.clickHandler_ = func;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,21 +25,24 @@ import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.
|
||||
import {Msg} from './msg.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
export type InputTypes = string|number;
|
||||
export type FieldInputValidator<T extends InputTypes> = FieldValidator<T>;
|
||||
/**
|
||||
* Supported types for FieldInput subclasses.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
type InputTypes = string|number;
|
||||
|
||||
/**
|
||||
* Class for an editable text field.
|
||||
* Abstract class for an editable input field.
|
||||
*
|
||||
* @alias Blockly.FieldInput
|
||||
* @typeParam T - The value stored on the field.
|
||||
* @internal
|
||||
*/
|
||||
export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
|
||||
/**
|
||||
* Pixel size of input border radius.
|
||||
* Should match blocklyText's border-radius in CSS.
|
||||
@@ -83,38 +86,33 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
|
||||
/** Mouse cursor style when over the hotspot that initiates the editor. */
|
||||
override CURSOR = 'text';
|
||||
override clickTarget_: AnyDuringMigration;
|
||||
override value_: AnyDuringMigration;
|
||||
override isDirty_: AnyDuringMigration;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a string.
|
||||
* @param value The initial value of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_validator A function that is called to validate changes to the
|
||||
* @param validator A function that is called to validate changes to the
|
||||
* field's value. Takes in a string & returns a validated string, or null
|
||||
* to abort the change.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/text-input#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_validator?: FieldInputValidator<T>|null,
|
||||
opt_config?: FieldInputConfig) {
|
||||
value?: string|typeof Field.SKIP_SETUP,
|
||||
validator?: FieldInputValidator<T>|null, config?: FieldInputConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,20 +159,6 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
this.createTextElement_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the input value casts to a valid string.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @returns A valid string, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
AnyDuringMigration {
|
||||
if (opt_newValue === null || opt_newValue === undefined) {
|
||||
return null;
|
||||
}
|
||||
return String(opt_newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by setValue if the text input is not valid. If the field is
|
||||
* currently being edited it reverts value of the field to the previous
|
||||
@@ -207,7 +191,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is a string.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected override doValueUpdate_(newValue: string|T) {
|
||||
this.isDirty_ = true;
|
||||
this.isTextValid_ = true;
|
||||
this.value_ = newValue;
|
||||
@@ -276,14 +260,13 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
* Shows a prompt editor for mobile browsers if the modalInputs option is
|
||||
* enabled.
|
||||
*
|
||||
* @param _opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param _e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @param opt_quietInput True if editor should be created without focus.
|
||||
* @param quietInput True if editor should be created without focus.
|
||||
* Defaults to false.
|
||||
*/
|
||||
protected override showEditor_(_opt_e?: Event, opt_quietInput?: boolean) {
|
||||
protected override showEditor_(_e?: Event, quietInput = false) {
|
||||
this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace;
|
||||
const quietInput = opt_quietInput || false;
|
||||
if (!quietInput && this.workspace_.options.modalInputs &&
|
||||
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
|
||||
this.showPromptEditor_();
|
||||
@@ -372,7 +355,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
div!.style.transition = 'box-shadow 0.25s ease 0s';
|
||||
if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) {
|
||||
div!.style.boxShadow =
|
||||
'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px';
|
||||
'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
|
||||
}
|
||||
}
|
||||
htmlInput.style.borderRadius = borderRadius;
|
||||
@@ -380,7 +363,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
div!.appendChild(htmlInput);
|
||||
|
||||
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
|
||||
htmlInput.setAttribute('data-untyped-default-value', this.value_);
|
||||
htmlInput.setAttribute('data-untyped-default-value', String(this.value_));
|
||||
|
||||
this.resizeEditor_();
|
||||
|
||||
@@ -457,29 +440,19 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
*
|
||||
* @param e Keyboard event.
|
||||
*/
|
||||
protected onHtmlInputKeyDown_(e: Event) {
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
if ((e as AnyDuringMigration).keyCode === KeyCodes.ENTER) {
|
||||
protected onHtmlInputKeyDown_(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.ESC) {
|
||||
} else if (e.key === 'Escape') {
|
||||
this.setValue(
|
||||
this.htmlInput_!.getAttribute('data-untyped-default-value'));
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.TAB) {
|
||||
} else if (e.key === 'Tab') {
|
||||
WidgetDiv.hide();
|
||||
dropDownDiv.hideWithoutAnimation();
|
||||
// AnyDuringMigration because: Property 'shiftKey' does not exist on type
|
||||
// 'Event'. AnyDuringMigration because: Argument of type 'this' is not
|
||||
// assignable to parameter of type 'Field'.
|
||||
(this.sourceBlock_ as BlockSvg)
|
||||
.tab(this as AnyDuringMigration, !(e as AnyDuringMigration).shiftKey);
|
||||
(this.sourceBlock_ as BlockSvg).tab(this, !e.shiftKey);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
@@ -567,7 +540,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
* @returns The text to show on the HTML input.
|
||||
*/
|
||||
protected getEditorText_(value: AnyDuringMigration): string {
|
||||
return String(value);
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -587,7 +560,28 @@ export abstract class FieldInput<T extends InputTypes> extends Field<T> {
|
||||
|
||||
/**
|
||||
* Config options for the input field.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface FieldInputConfig extends FieldConfig {
|
||||
spellcheck?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
* @internal
|
||||
*/
|
||||
export type FieldInputValidator<T extends InputTypes> =
|
||||
FieldValidator<string|T>;
|
||||
|
||||
@@ -17,15 +17,12 @@ import * as dom from './utils/dom.js';
|
||||
import {Field, FieldConfig} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
|
||||
/**
|
||||
* Class for a non-editable, non-serializable text field.
|
||||
*
|
||||
* @alias Blockly.FieldLabel
|
||||
*/
|
||||
export class FieldLabel extends Field<string> {
|
||||
/** The html class name to use for this field. */
|
||||
/** The HTML class name to use for this field. */
|
||||
private class_: string|null = null;
|
||||
|
||||
/**
|
||||
@@ -35,31 +32,29 @@ export class FieldLabel extends Field<string> {
|
||||
override EDITABLE = false;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a string.
|
||||
* @param value The initial value of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_class Optional CSS class for the field's text.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param textClass Optional CSS class for the field's text.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_class?: string,
|
||||
opt_config?: FieldLabelConfig) {
|
||||
value?: string|typeof Field.SKIP_SETUP, textClass?: string,
|
||||
config?: FieldLabelConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
} else {
|
||||
this.class_ = opt_class || null;
|
||||
this.class_ = textClass || null;
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
this.setValue(value);
|
||||
}
|
||||
|
||||
protected override configure_(config: FieldLabelConfig) {
|
||||
@@ -82,15 +77,15 @@ export class FieldLabel extends Field<string> {
|
||||
/**
|
||||
* Ensure that the input value casts to a valid string.
|
||||
*
|
||||
* @param opt_newValue The input value.
|
||||
* @param newValue The input value.
|
||||
* @returns A valid string, or null if invalid.
|
||||
*/
|
||||
protected override doClassValidation_(opt_newValue?: AnyDuringMigration):
|
||||
string|null {
|
||||
if (opt_newValue === null || opt_newValue === undefined) {
|
||||
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|
||||
|null {
|
||||
if (newValue === null || newValue === undefined) {
|
||||
return null;
|
||||
}
|
||||
return String(opt_newValue);
|
||||
return `${newValue}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,8 +20,6 @@ import * as parsing from './utils/parsing.js';
|
||||
|
||||
/**
|
||||
* Class for a non-editable, serializable text field.
|
||||
*
|
||||
* @alias Blockly.FieldLabelSerializable
|
||||
*/
|
||||
export class FieldLabelSerializable extends FieldLabel {
|
||||
/**
|
||||
@@ -38,17 +36,16 @@ export class FieldLabelSerializable extends FieldLabel {
|
||||
override SERIALIZABLE = true;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial value of the field. Should cast to a string.
|
||||
* @param value The initial value of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined.
|
||||
* @param opt_class Optional CSS class for the field's text.
|
||||
* @param opt_config A map of options used to configure the field.
|
||||
* @param textClass Optional CSS class for the field's text.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/label-serializable#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string, opt_class?: string, opt_config?: FieldLabelConfig) {
|
||||
super(String(opt_value ?? ''), opt_class, opt_config);
|
||||
constructor(value?: string, textClass?: string, config?: FieldLabelConfig) {
|
||||
super(String(value ?? ''), textClass, config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,28 +18,20 @@ import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {KeyCodes} from './utils/keycodes.js';
|
||||
import * as parsing from './utils/parsing.js';
|
||||
import type {Sentinel} from './utils/sentinel.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
|
||||
export type FieldMultilineInputValidator = FieldTextInputValidator;
|
||||
|
||||
/**
|
||||
* Class for an editable text area field.
|
||||
*
|
||||
* @alias Blockly.FieldMultilineInput
|
||||
*/
|
||||
export class FieldMultilineInput extends FieldTextInput {
|
||||
/**
|
||||
* The SVG group element that will contain a text element for each text row
|
||||
* when initialized.
|
||||
*/
|
||||
// AnyDuringMigration because: Type 'null' is not assignable to type
|
||||
// 'SVGGElement'.
|
||||
textGroup_: SVGGElement = null as AnyDuringMigration;
|
||||
textGroup: SVGGElement|null = null;
|
||||
|
||||
/**
|
||||
* Defines the maximum number of lines of field.
|
||||
@@ -51,37 +43,41 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
protected isOverflowedY_ = false;
|
||||
|
||||
/**
|
||||
* @param opt_value The initial content of the field. Should cast to a string.
|
||||
* @param value The initial content of the field. Should cast to a string.
|
||||
* Defaults to an empty string if null or undefined. Also accepts
|
||||
* Field.SKIP_SETUP if you wish to skip setup (only used by subclasses
|
||||
* that want to handle configuration and setting the field value after
|
||||
* their own constructors have run).
|
||||
* @param opt_validator An optional function that is called to validate any
|
||||
* @param 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 opt_config A map of options used to configure the field.
|
||||
* @param config A map of options used to configure the field.
|
||||
* See the [field creation documentation]{@link
|
||||
* https://developers.google.com/blockly/guides/create-custom-blocks/fields/built-in-fields/multiline-text-input#creation}
|
||||
* for a list of properties this parameter supports.
|
||||
*/
|
||||
constructor(
|
||||
opt_value?: string|Sentinel, opt_validator?: FieldMultilineInputValidator,
|
||||
opt_config?: FieldMultilineInputConfig) {
|
||||
value?: string|typeof Field.SKIP_SETUP,
|
||||
validator?: FieldMultilineInputValidator,
|
||||
config?: FieldMultilineInputConfig) {
|
||||
super(Field.SKIP_SETUP);
|
||||
|
||||
if (opt_value === Field.SKIP_SETUP) {
|
||||
return;
|
||||
if (value === Field.SKIP_SETUP) return;
|
||||
if (config) {
|
||||
this.configure_(config);
|
||||
}
|
||||
if (opt_config) {
|
||||
this.configure_(opt_config);
|
||||
}
|
||||
this.setValue(opt_value);
|
||||
if (opt_validator) {
|
||||
this.setValidator(opt_validator);
|
||||
this.setValue(value);
|
||||
if (validator) {
|
||||
this.setValidator(validator);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the field based on the given map of options.
|
||||
*
|
||||
* @param config A map of options to configure the field based on.
|
||||
*/
|
||||
protected override configure_(config: FieldMultilineInputConfig) {
|
||||
super.configure_(config);
|
||||
if (config.maxLines) this.setMaxLines(config.maxLines);
|
||||
@@ -118,6 +114,8 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
|
||||
/**
|
||||
* Saves this field's value.
|
||||
* This function only exists for subclasses of FieldMultilineInput which
|
||||
* predate the load/saveState API and only define to/fromXml.
|
||||
*
|
||||
* @returns The state of this field.
|
||||
* @internal
|
||||
@@ -132,6 +130,8 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
|
||||
/**
|
||||
* Sets the field's value based on the given state.
|
||||
* This function only exists for subclasses of FieldMultilineInput which
|
||||
* predate the load/saveState API and only define to/fromXml.
|
||||
*
|
||||
* @param state The state of the variable to assign to this variable field.
|
||||
* @internal
|
||||
@@ -150,7 +150,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
*/
|
||||
override initView() {
|
||||
this.createBorderRect_();
|
||||
this.textGroup_ = dom.createSvgElement(
|
||||
this.textGroup = dom.createSvgElement(
|
||||
Svg.G, {
|
||||
'class': 'blocklyEditableText',
|
||||
},
|
||||
@@ -210,9 +210,11 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
* @param newValue The value to be saved. The default validator guarantees
|
||||
* that this is a string.
|
||||
*/
|
||||
protected override doValueUpdate_(newValue: AnyDuringMigration) {
|
||||
protected override doValueUpdate_(newValue: string) {
|
||||
super.doValueUpdate_(newValue);
|
||||
this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_;
|
||||
if (this.value_ !== null) {
|
||||
this.isOverflowedY_ = this.value_.split('\n').length > this.maxLines_;
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the text of the textElement. */
|
||||
@@ -223,8 +225,9 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
}
|
||||
// Remove all text group children.
|
||||
let currentChild;
|
||||
while (currentChild = this.textGroup_.firstChild) {
|
||||
this.textGroup_.removeChild(currentChild);
|
||||
const textGroup = this.textGroup;
|
||||
while (currentChild = textGroup!.firstChild) {
|
||||
textGroup!.removeChild(currentChild);
|
||||
}
|
||||
|
||||
// Add in text elements into the group.
|
||||
@@ -240,7 +243,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING,
|
||||
'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
|
||||
},
|
||||
this.textGroup_);
|
||||
textGroup);
|
||||
span.appendChild(document.createTextNode(lines[i]));
|
||||
y += lineHeight;
|
||||
}
|
||||
@@ -278,7 +281,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
|
||||
/** Updates the size of the field based on the text. */
|
||||
protected override updateSize_() {
|
||||
const nodes = this.textGroup_.childNodes;
|
||||
const nodes = (this.textGroup as SVGElement).childNodes;
|
||||
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE;
|
||||
const fontWeight = this.getConstants()!.FIELD_TEXT_FONTWEIGHT;
|
||||
const fontFamily = this.getConstants()!.FIELD_TEXT_FONTFAMILY;
|
||||
@@ -300,7 +303,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
// absolute longest line, even if it would be truncated after editing.
|
||||
// Otherwise we would get wrong editor width when there are more
|
||||
// lines than this.maxLines_.
|
||||
const actualEditorLines = this.value_.split('\n');
|
||||
const actualEditorLines = String(this.value_).split('\n');
|
||||
const dummyTextElement = dom.createSvgElement(
|
||||
Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
|
||||
|
||||
@@ -324,13 +327,8 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
if (this.borderRect_) {
|
||||
totalHeight += this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * 2;
|
||||
totalWidth += this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * 2;
|
||||
// AnyDuringMigration because: Argument of type 'number' is not
|
||||
// assignable to parameter of type 'string'.
|
||||
this.borderRect_.setAttribute('width', totalWidth as AnyDuringMigration);
|
||||
// AnyDuringMigration because: Argument of type 'number' is not
|
||||
// assignable to parameter of type 'string'.
|
||||
this.borderRect_.setAttribute(
|
||||
'height', totalHeight as AnyDuringMigration);
|
||||
this.borderRect_.setAttribute('width', `${totalWidth}`);
|
||||
this.borderRect_.setAttribute('height', `${totalHeight}`);
|
||||
}
|
||||
this.size_.width = totalWidth;
|
||||
this.size_.height = totalHeight;
|
||||
@@ -343,13 +341,13 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
* Overrides the default behaviour to force rerender in order to
|
||||
* correct block size, based on editor text.
|
||||
*
|
||||
* @param _opt_e Optional mouse event that triggered the field to open, or
|
||||
* @param e Optional mouse event that triggered the field to open, or
|
||||
* undefined if triggered programmatically.
|
||||
* @param opt_quietInput True if editor should be created without focus.
|
||||
* @param quietInput True if editor should be created without focus.
|
||||
* Defaults to false.
|
||||
*/
|
||||
override showEditor_(_opt_e?: Event, opt_quietInput?: boolean) {
|
||||
super.showEditor_(_opt_e, opt_quietInput);
|
||||
override showEditor_(e?: Event, quietInput?: boolean) {
|
||||
super.showEditor_(e, quietInput);
|
||||
this.forceRerender();
|
||||
}
|
||||
|
||||
@@ -364,10 +362,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
|
||||
const htmlInput = (document.createElement('textarea'));
|
||||
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
|
||||
// AnyDuringMigration because: Argument of type 'boolean' is not assignable
|
||||
// to parameter of type 'string'.
|
||||
htmlInput.setAttribute(
|
||||
'spellcheck', this.spellcheck_ as AnyDuringMigration);
|
||||
htmlInput.setAttribute('spellcheck', String(this.spellcheck_));
|
||||
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
|
||||
div!.style.fontSize = fontSize;
|
||||
htmlInput.style.fontSize = fontSize;
|
||||
@@ -385,7 +380,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
div!.appendChild(htmlInput);
|
||||
|
||||
htmlInput.value = htmlInput.defaultValue = this.getEditorText_(this.value_);
|
||||
htmlInput.setAttribute('data-untyped-default-value', this.value_);
|
||||
htmlInput.setAttribute('data-untyped-default-value', String(this.value_));
|
||||
htmlInput.setAttribute('data-old-value', '');
|
||||
if (userAgent.GECKO) {
|
||||
// In FF, ensure the browser reflows before resizing to avoid issue #2777.
|
||||
@@ -428,10 +423,8 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
*
|
||||
* @param e Keyboard event.
|
||||
*/
|
||||
protected override onHtmlInputKeyDown_(e: Event) {
|
||||
// AnyDuringMigration because: Property 'keyCode' does not exist on type
|
||||
// 'Event'.
|
||||
if ((e as AnyDuringMigration).keyCode !== KeyCodes.ENTER) {
|
||||
protected override onHtmlInputKeyDown_(e: KeyboardEvent) {
|
||||
if (e.key !== 'Enter') {
|
||||
super.onHtmlInputKeyDown_(e);
|
||||
}
|
||||
}
|
||||
@@ -454,7 +447,12 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
}
|
||||
}
|
||||
|
||||
/** CSS for multiline field. See css.js for use. */
|
||||
fieldRegistry.register('field_multilinetext', FieldMultilineInput);
|
||||
|
||||
|
||||
/**
|
||||
* CSS for multiline field.
|
||||
*/
|
||||
Css.register(`
|
||||
.blocklyHtmlTextAreaInput {
|
||||
font-family: monospace;
|
||||
@@ -469,8 +467,6 @@ Css.register(`
|
||||
}
|
||||
`);
|
||||
|
||||
fieldRegistry.register('field_multilinetext', FieldMultilineInput);
|
||||
|
||||
/**
|
||||
* Config options for the multiline input field.
|
||||
*/
|
||||
@@ -485,3 +481,20 @@ export interface FieldMultilineInputFromJsonConfig extends
|
||||
FieldMultilineInputConfig {
|
||||
text?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that is called to validate changes to the field's value before
|
||||
* they are set.
|
||||
*
|
||||
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/fields/validators#return_values}
|
||||
* @param newValue The value to be validated.
|
||||
* @returns One of three instructions for setting the new value: `T`, `null`,
|
||||
* or `undefined`.
|
||||
*
|
||||
* - `T` to set this function's returned value instead of `newValue`.
|
||||
*
|
||||
* - `null` to invoke `doValueInvalid_` and not set a value.
|
||||
*
|
||||
* - `undefined` to set `newValue` as is.
|
||||
*/
|
||||
export type FieldMultilineInputValidator = FieldTextInputValidator;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user