mirror of
https://github.com/google/blockly.git
synced 2026-06-17 00:25:14 +02:00
@@ -17,3 +17,4 @@
|
||||
/externs/*
|
||||
/closure/*
|
||||
/scripts/gulpfiles/*
|
||||
/typings/*
|
||||
|
||||
+81
-3
@@ -39,7 +39,7 @@
|
||||
"strict": ["off"],
|
||||
// Closure style allows redeclarations.
|
||||
"no-redeclare": ["off"],
|
||||
"valid-jsdoc": ["error", {"requireReturn": false}],
|
||||
"valid-jsdoc": ["error"],
|
||||
"no-console": ["off"],
|
||||
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
|
||||
"operator-linebreak": ["error", "after"],
|
||||
@@ -78,11 +78,89 @@
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"Blockly": true,
|
||||
"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"]
|
||||
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ All submissions, including submissions by project members, require review. We
|
||||
use Github pull requests for this purpose.
|
||||
|
||||
### Browser compatibility
|
||||
We care strongly about making Blockly work on all browsers. As of 2017 we
|
||||
support IE 10 and 11, Edge, Chrome, Safari, and Firefox. We will not accept
|
||||
changes that only work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/)
|
||||
We care strongly about making Blockly work on all browsers. As of 2022 we
|
||||
support Edge, Chrome, Safari, and Firefox. We will not accept changes that only
|
||||
work on a subset of those browsers. You can check [caniuse.com](https://caniuse.com/)
|
||||
for compatibility information.
|
||||
|
||||
### The small print
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
|
||||
### Test Coverage
|
||||
|
||||
<!-- TODO: Please do one of the following:
|
||||
- * Create unit tests, and explain here how they cover your changes.
|
||||
- * List steps you used for manual testing, and explain how they cover
|
||||
- your changes.
|
||||
-->
|
||||
<!-- TODO: Please create unit tests, and explain here how they cover
|
||||
your changes, or tell us how you tested it manually. If
|
||||
your changes include browser-specific behaviour, include
|
||||
information about the browser and device that you used for
|
||||
testing. -->
|
||||
|
||||
### Documentation
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ packageName: blockly
|
||||
manifest: true
|
||||
manifestConfig: release-please-config.json
|
||||
manifestFile: .release-please-manifest.json
|
||||
handleGHRelease: true
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
# Checks-out the repository under $GITHUB_WORKSPACE.
|
||||
# When running manually this checks out the master branch.
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare demo files
|
||||
# Install all dependencies, then copy all the files needed for demos.
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
npm run prepareDemos
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: appengine_files
|
||||
path: _deploy/
|
||||
@@ -36,13 +36,13 @@ jobs:
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Download prepared files
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: appengine_files
|
||||
path: _deploy/
|
||||
|
||||
- name: Deploy to App Engine
|
||||
uses: google-github-actions/deploy-appengine@v0.2.0
|
||||
uses: google-github-actions/deploy-appengine@v0.8.0
|
||||
# For parameters see:
|
||||
# https://github.com/google-github-actions/deploy-appengine#inputs
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Assign requested reviewer
|
||||
uses: actions/github-script@v5
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
# TODO (#2114): re-enable osx build.
|
||||
# os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
node-version: [14.x, 16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
ssh://git@github.com/
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
@@ -55,10 +55,10 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ jobs:
|
||||
clang-formatter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: DoozyX/clang-format-lint-action@v0.13
|
||||
- uses: DoozyX/clang-format-lint-action@v0.14
|
||||
with:
|
||||
source: 'core'
|
||||
extensions: 'js,ts'
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
# Add the type: cleanup label
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@a3e7071a34d7e1f219a8a4de9a5e0a34d1ee1293
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
// Note that pull requests are considered issues and "shared"
|
||||
|
||||
@@ -17,12 +17,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check Out Blockly
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: 'develop'
|
||||
|
||||
- name: Use Node.js 16.x
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
run: source ./tests/scripts/update_metadata.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@9825ae65b1cb54b543b938503728b432a0176d29
|
||||
uses: peter-evans/create-pull-request@171dd555b9ab6b18fa02519fdfacbb8bf671e1b4
|
||||
with:
|
||||
commit-message: Update build artifact sizes in check_metadata.sh
|
||||
delete-branch: true
|
||||
|
||||
@@ -8,6 +8,7 @@ build-debug.log
|
||||
*.pyc
|
||||
*.komodoproject
|
||||
/nbproject/private/
|
||||
tsdoc-metadata.json
|
||||
|
||||
tests/compile/main_compressed.js
|
||||
tests/compile/main_compressed.js.map
|
||||
@@ -18,3 +19,4 @@ local_build/local_*_compressed.js
|
||||
chromedriver
|
||||
build/
|
||||
dist/
|
||||
temp/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Blockly [](https://travis-ci.org/google/blockly)
|
||||
# Blockly
|
||||
|
||||
Google's Blockly is a library that adds a visual code editor to web and mobile apps. The Blockly editor uses interlocking, graphical blocks to represent code concepts like variables, logical expressions, loops, and more. It allows users to apply programming principles without having to worry about syntax or the intimidation of a blinking cursor on the command line. All code is free and open source.
|
||||
|
||||
@@ -41,11 +41,9 @@ Want to make Blockly better? We welcome contributions to Blockly in the form of
|
||||
|
||||
## Releases
|
||||
|
||||
The next major release will be during the last week of **March 2022**.
|
||||
|
||||
We release by pushing the latest code to the master branch, followed by updating the npm package, our [docs](https://developers.google.com/blockly), and [demo pages](https://google.github.io/blockly-samples/). We typically release a new version of Blockly once a quarter (every 3 months). If there are breaking bugs, such as a crash when performing a standard action or a rendering issue that makes Blockly unusable, we will cherry-pick fixes to master between releases to fix them. The [releases page](https://github.com/google/blockly/releases) has a list of all releases.
|
||||
|
||||
Releases are tagged by the release date (YYYYMMDD) with a leading major version number and a trailing '.0' in case we ever need a major or patch version (such as [2.20190722.1](https://github.com/google/blockly/tree/2.20190722.1)). Releases that have breaking changes or are otherwise not backwards compatible will have a new major version. Patch versions are reserved for bug-fix patches between scheduled releases.
|
||||
We use [semantic versioning](https://semver.org/). Releases that have breaking changes or are otherwise not backwards compatible will have a new major version. Patch versions are reserved for bug-fix patches between scheduled releases.
|
||||
|
||||
We now have a [beta release on npm](https://www.npmjs.com/package/blockly?activeTab=versions). If you'd like to test the upcoming release, or try out a not-yet-released new API, you can use the beta channel with:
|
||||
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
|
||||
/**
|
||||
* Optionally specifies another JSON config file that this file extends from. This provides a way for
|
||||
* standard settings to be shared across multiple projects.
|
||||
*
|
||||
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
|
||||
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
|
||||
* resolved using NodeJS require().
|
||||
*
|
||||
* SUPPORTED TOKENS: none
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "extends": "./shared/api-extractor-base.json"
|
||||
// "extends": "my-package/include/api-extractor-base.json"
|
||||
|
||||
/**
|
||||
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
|
||||
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting.
|
||||
*
|
||||
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
|
||||
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
|
||||
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
|
||||
* will be reported.
|
||||
*
|
||||
* SUPPORTED TOKENS: <lookup>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "projectFolder": "..",
|
||||
|
||||
/**
|
||||
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
|
||||
* analyzes the symbols exported by this module.
|
||||
*
|
||||
* The file extension must be ".d.ts" and not ".ts".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
*/
|
||||
"mainEntryPointFilePath": "dist/index.d.ts",
|
||||
|
||||
/**
|
||||
* A list of NPM package names whose exports should be treated as part of this package.
|
||||
*
|
||||
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
|
||||
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
|
||||
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
|
||||
* imports library2. To avoid this, we can specify:
|
||||
*
|
||||
* "bundledPackages": [ "library2" ],
|
||||
*
|
||||
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
|
||||
* local files for library1.
|
||||
*/
|
||||
"bundledPackages": [],
|
||||
|
||||
/**
|
||||
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
|
||||
*/
|
||||
"compiler": {
|
||||
/**
|
||||
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* Note: This setting will be ignored if "overrideTsconfig" is used.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
|
||||
*/
|
||||
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
|
||||
/**
|
||||
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
|
||||
* The object must conform to the TypeScript tsconfig schema:
|
||||
*
|
||||
* http://json.schemastore.org/tsconfig
|
||||
*
|
||||
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
|
||||
*
|
||||
* DEFAULT VALUE: no overrideTsconfig section
|
||||
*/
|
||||
// "overrideTsconfig": {
|
||||
// . . .
|
||||
// }
|
||||
/**
|
||||
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
|
||||
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
|
||||
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
|
||||
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "skipLibCheck": true,
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the API report file (*.api.md) will be generated.
|
||||
*/
|
||||
"apiReport": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate an API report.
|
||||
*/
|
||||
"enabled": false,
|
||||
|
||||
/**
|
||||
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
|
||||
* a full file path.
|
||||
*
|
||||
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
|
||||
*
|
||||
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
|
||||
*/
|
||||
// "reportFileName": "<unscopedPackageName>.api.md",
|
||||
|
||||
/**
|
||||
* Specifies the folder where the API report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
|
||||
* e.g. for an API review.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||
*/
|
||||
// "reportFolder": "<projectFolder>/temp/",
|
||||
|
||||
/**
|
||||
* Specifies the folder where the temporary report file is written. The file name portion is determined by
|
||||
* the "reportFileName" setting.
|
||||
*
|
||||
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
|
||||
* If they are different, a production build will fail.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/"
|
||||
*/
|
||||
// "reportTempFolder": "<projectFolder>/temp/"
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the doc model file (*.api.json) will be generated.
|
||||
*/
|
||||
"docModel": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate a doc model file.
|
||||
*/
|
||||
"enabled": true
|
||||
|
||||
/**
|
||||
* The output path for the doc model file. The file extension should be ".api.json".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
*/
|
||||
// "apiJsonFilePath": "<projectFolder>/temp/<unscopedPackageName>.api.json"
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the .d.ts rollup file will be generated.
|
||||
*/
|
||||
"dtsRollup": {
|
||||
/**
|
||||
* (REQUIRED) Whether to generate the .d.ts rollup file.
|
||||
*/
|
||||
"enabled": true,
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
|
||||
* This file will include all declarations that are exported by the main entry point.
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
|
||||
*/
|
||||
"untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>_rollup.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release.
|
||||
* This file will include only declarations that are marked as "@public", "@beta", or "@alpha".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "alphaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-alpha.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
|
||||
* This file will include only declarations that are marked as "@public" or "@beta".
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
|
||||
|
||||
/**
|
||||
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
|
||||
* This file will include only declarations that are marked as "@public".
|
||||
*
|
||||
* If the path is an empty string, then this file will not be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: ""
|
||||
*/
|
||||
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
|
||||
|
||||
/**
|
||||
* When a declaration is trimmed, by default it will be replaced by a code comment such as
|
||||
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
|
||||
* declaration completely.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "omitTrimmingComments": true
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures how the tsdoc-metadata.json file will be generated.
|
||||
*/
|
||||
"tsdocMetadata": {
|
||||
/**
|
||||
* Whether to generate the tsdoc-metadata.json file.
|
||||
*
|
||||
* DEFAULT VALUE: true
|
||||
*/
|
||||
// "enabled": true,
|
||||
/**
|
||||
* Specifies where the TSDoc metadata file should be written.
|
||||
*
|
||||
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
|
||||
* prepend a folder token such as "<projectFolder>".
|
||||
*
|
||||
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
|
||||
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
|
||||
* falls back to "tsdoc-metadata.json" in the package folder.
|
||||
*
|
||||
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
|
||||
* DEFAULT VALUE: "<lookup>"
|
||||
*/
|
||||
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
|
||||
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
|
||||
* To use the OS's default newline kind, specify "os".
|
||||
*
|
||||
* DEFAULT VALUE: "crlf"
|
||||
*/
|
||||
// "newlineKind": "crlf",
|
||||
|
||||
/**
|
||||
* Configures how API Extractor reports error and warning messages produced during analysis.
|
||||
*
|
||||
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
|
||||
*/
|
||||
"messages": {
|
||||
/**
|
||||
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
|
||||
* the input .d.ts files.
|
||||
*
|
||||
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"compilerMessageReporting": {
|
||||
/**
|
||||
* Configures the default routing for messages that don't match an explicit rule in this table.
|
||||
*/
|
||||
"default": {
|
||||
/**
|
||||
* Specifies whether the message should be written to the the tool's output log. Note that
|
||||
* the "addToApiReportFile" property may supersede this option.
|
||||
*
|
||||
* Possible values: "error", "warning", "none"
|
||||
*
|
||||
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
|
||||
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
|
||||
* the "--local" option), the warning is displayed but the build will not fail.
|
||||
*
|
||||
* DEFAULT VALUE: "warning"
|
||||
*/
|
||||
"logLevel": "warning"
|
||||
|
||||
/**
|
||||
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
|
||||
* then the message will be written inside that file; otherwise, the message is instead logged according to
|
||||
* the "logLevel" option.
|
||||
*
|
||||
* DEFAULT VALUE: false
|
||||
*/
|
||||
// "addToApiReportFile": false
|
||||
}
|
||||
|
||||
// "TS2551": {
|
||||
// "logLevel": "warning",
|
||||
// "addToApiReportFile": true
|
||||
// },
|
||||
//
|
||||
// . . .
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures handling of messages reported by API Extractor during its analysis.
|
||||
*
|
||||
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
|
||||
*
|
||||
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
|
||||
*/
|
||||
"extractorMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
},
|
||||
|
||||
// We don't use `@public`, that's just the default.
|
||||
"ae-missing-release-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
|
||||
// Needs investigation.
|
||||
"ae-forgotten-export": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
|
||||
*
|
||||
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
|
||||
*
|
||||
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
|
||||
*/
|
||||
"tsdocMessageReporting": {
|
||||
"default": {
|
||||
"logLevel": "warning"
|
||||
// "addToApiReportFile": false
|
||||
},
|
||||
|
||||
"tsdoc-param-tag-missing-hyphen": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
|
||||
// These two are due to "type-like" tags in JsDoc like
|
||||
// `@suppress {warningName}`. The braces are unexpected in TsDoc.
|
||||
"tsdoc-malformed-inline-tag": {
|
||||
"logLevel": "none"
|
||||
},
|
||||
"tsdoc-escape-right-brace": {
|
||||
"logLevel": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,39 @@ if (loc.match('/apps/puzzle/')) {
|
||||
loc = loc.replace('/apps/', '/demos/');
|
||||
}
|
||||
|
||||
// Demos without saved data were moved to Blockly Samples in 2021.
|
||||
if (loc.match('/demos/fixed/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/fixed-demo/';
|
||||
} else if (loc.match('/demos/resizable/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/resizable-demo/';
|
||||
} else if (loc.match('/demos/toolbox/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/toolbox-demo/';
|
||||
} else if (loc.match('/demos/maxBlocks/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/max-blocks-demo/';
|
||||
} else if (loc.match('/demos/generator/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/generator-demo/';
|
||||
} else if (loc.match('/demos/headless/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/headless-demo/';
|
||||
} else if (loc.match('/demos/interpreter/step-execution')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/interpreter-demo/step-execution.html';
|
||||
} else if (loc.match('/demos/interpreter/async-execution')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/interpreter-demo/async-execution.html';
|
||||
} else if (loc.match('/demos/graph/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/graph-demo/';
|
||||
} else if (loc.match('/demos/rtl/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/rtl-demo/';
|
||||
} else if (loc.match('/demos/custom-dialogs/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/custom-dialogs-demo';
|
||||
} else if (loc.match('/demos/custom-fields/turtle/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/turtle-field-demo/';
|
||||
} else if (loc.match('/demos/custom-fields/pitch/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/pitch-field-demo/';
|
||||
} else if (loc.match('/demos/mirror/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/mirror-demo/';
|
||||
} else if (loc.match('/demos/plane/')) {
|
||||
loc = 'https://google.github.io/blockly-samples/examples/plane-demo/';
|
||||
}
|
||||
|
||||
location = loc;
|
||||
|
||||
</script>
|
||||
|
||||
+1515
-1994
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Bootstrap code to load Blockly in uncompiled mode.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
/* eslint-disable no-var */
|
||||
|
||||
/**
|
||||
* Blockly uncompiled-mode startup code. If running in a browser
|
||||
* loads closure/goog/base.js and tests/deps.js, then (in any case)
|
||||
* requires Blockly.requires.
|
||||
*/
|
||||
(function(globalThis) {
|
||||
/* eslint-disable-next-line no-undef */
|
||||
var IS_NODE_JS = !!(typeof module !== 'undefined' && module.exports);
|
||||
|
||||
if (IS_NODE_JS) {
|
||||
// Load Blockly.
|
||||
goog.require('Blockly.requires');
|
||||
/* eslint-disable no-undef */
|
||||
module.exports = Blockly;
|
||||
} else {
|
||||
var BLOCKLY_DIR = '';
|
||||
// Find name of current directory.
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var re = /(.+)[\\/]blockly_(?:.*)uncompressed\.js$/;
|
||||
for (var script, i = 0; (script = scripts[i]); i++) {
|
||||
var match = re.exec(script.src);
|
||||
if (match) {
|
||||
BLOCKLY_DIR = match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!BLOCKLY_DIR) {
|
||||
alert('Could not detect Blockly\'s directory name.');
|
||||
}
|
||||
// Disable loading of closure/goog/deps.js (which doesn't exist).
|
||||
globalThis.CLOSURE_NO_DEPS = true;
|
||||
// Load Closure Library base.js (the only part of the libary we use,
|
||||
// mainly for goog.require / goog.provide / goog.module).
|
||||
document.write(
|
||||
'<script src="' + BLOCKLY_DIR + '/closure/goog/base.js"></script>');
|
||||
// Load dependency graph info from test/deps.js. To update
|
||||
// deps.js, run `npm run build:deps`.
|
||||
document.write(
|
||||
'<script src="' + BLOCKLY_DIR + '/tests/deps.js"></script>');
|
||||
// Load the rest of Blockly.
|
||||
document.write('<script>goog.require(\'Blockly\');</script>');
|
||||
}
|
||||
/* eslint-disable-next-line no-invalid-this */
|
||||
})(this);
|
||||
+3
-2
@@ -11,7 +11,6 @@
|
||||
'use strict';
|
||||
|
||||
goog.module('Blockly.libraryBlocks');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const colour = goog.require('Blockly.libraryBlocks.colour');
|
||||
const lists = goog.require('Blockly.libraryBlocks.lists');
|
||||
@@ -22,8 +21,10 @@ const procedures = goog.require('Blockly.libraryBlocks.procedures');
|
||||
const texts = goog.require('Blockly.libraryBlocks.texts');
|
||||
const variables = goog.require('Blockly.libraryBlocks.variables');
|
||||
const variablesDynamic = goog.require('Blockly.libraryBlocks.variablesDynamic');
|
||||
// const {BlockDefinition} = goog.requireType('Blockly.blocks');
|
||||
// TODO (6248): Properly import the BlockDefinition type.
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDefinition} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
|
||||
|
||||
exports.colour = colour;
|
||||
|
||||
+3
-1
@@ -11,8 +11,10 @@
|
||||
|
||||
goog.module('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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.FieldColour');
|
||||
|
||||
+106
-34
@@ -13,11 +13,14 @@
|
||||
goog.module('Blockly.libraryBlocks.lists');
|
||||
|
||||
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');
|
||||
// const {BlockDefinition} = goog.requireType('Blockly.blocks');
|
||||
// TODO (6248): Properly import the BlockDefinition type.
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDefinition} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {FieldDropdown} = goog.require('Blockly.FieldDropdown');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
@@ -130,7 +133,7 @@ blocks['lists_create_with'] = {
|
||||
this.itemCount_ = 3;
|
||||
this.updateShape_();
|
||||
this.setOutput(true, 'Array');
|
||||
this.setMutator(new Mutator(['lists_create_with_item']));
|
||||
this.setMutator(new Mutator(['lists_create_with_item'], this));
|
||||
this.setTooltip(Msg['LISTS_CREATE_WITH_TOOLTIP']);
|
||||
},
|
||||
/**
|
||||
@@ -198,10 +201,13 @@ blocks['lists_create_with'] = {
|
||||
let itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
// Count number of inputs.
|
||||
const connections = [];
|
||||
while (itemBlock && !itemBlock.isInsertionMarker()) {
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
connections.push(itemBlock.valueConnection_);
|
||||
itemBlock =
|
||||
itemBlock.nextConnection && itemBlock.nextConnection.targetBlock();
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
}
|
||||
// Disconnect any children that don't belong.
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
@@ -226,10 +232,13 @@ blocks['lists_create_with'] = {
|
||||
let itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
let i = 0;
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
const input = this.getInput('ADD' + i);
|
||||
itemBlock.valueConnection_ = input && input.connection.targetConnection;
|
||||
itemBlock =
|
||||
itemBlock.nextConnection && itemBlock.nextConnection.targetBlock();
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
i++;
|
||||
}
|
||||
},
|
||||
@@ -446,10 +455,34 @@ blocks['lists_getIndex'] = {
|
||||
this.updateAt_(isAt);
|
||||
},
|
||||
|
||||
// This block does not need JSO serialization hooks (saveExtraState and
|
||||
// loadExtraState) because the state of this object is already encoded in the
|
||||
// dropdown values.
|
||||
// XML hooks are kept for backwards compatibility.
|
||||
/**
|
||||
* Returns the state of this block as a JSON serializable object.
|
||||
* Returns null for efficiency if no state is needed (not a statement)
|
||||
* @return {?{isStatement: boolean}} The state of this block, ie whether it's
|
||||
* a statement.
|
||||
*/
|
||||
saveExtraState: function() {
|
||||
if (!this.outputConnection) {
|
||||
return {
|
||||
'isStatement': true,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the given state to this block.
|
||||
* @param {*} state The state to apply to this block, ie whether it's a
|
||||
* statement.
|
||||
*/
|
||||
loadExtraState: function(state) {
|
||||
if (state['isStatement']) {
|
||||
this.updateStatement_(true);
|
||||
} else if (typeof state === 'string') {
|
||||
// backward compatible for json serialised mutations
|
||||
this.domToMutation(Xml.textToDom(state));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch between a value block and a statement block.
|
||||
@@ -498,7 +531,7 @@ blocks['lists_getIndex'] = {
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
* @returns {null|undefined} Null if the field has been replaced;
|
||||
* @return {null|undefined} Null if the field has been replaced;
|
||||
* otherwise undefined.
|
||||
*/
|
||||
function(value) {
|
||||
@@ -618,10 +651,23 @@ blocks['lists_setIndex'] = {
|
||||
this.updateAt_(isAt);
|
||||
},
|
||||
|
||||
// This block does not need JSO serialization hooks (saveExtraState and
|
||||
// loadExtraState) because the state of this object is already encoded in the
|
||||
// dropdown values.
|
||||
// XML hooks are kept for backwards compatibility.
|
||||
/**
|
||||
* Returns the state of this block as a JSON serializable object.
|
||||
* This block does not need to serialize any specific state as it is already
|
||||
* encoded in the dropdown values, but must have an implementation to avoid
|
||||
* the backward compatible XML mutations being serialized.
|
||||
* @return {null} The state of this block.
|
||||
*/
|
||||
saveExtraState: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the given state to this block.
|
||||
* No extra state is needed or expected as it is already encoded in the
|
||||
* dropdown values.
|
||||
*/
|
||||
loadExtraState: function() {},
|
||||
|
||||
/**
|
||||
* Create or delete an input for the numeric index.
|
||||
@@ -648,7 +694,7 @@ blocks['lists_setIndex'] = {
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
* @returns {null|undefined} Null if the field has been replaced;
|
||||
* @return {null|undefined} Null if the field has been replaced;
|
||||
* otherwise undefined.
|
||||
*/
|
||||
function(value) {
|
||||
@@ -730,10 +776,23 @@ blocks['lists_getSublist'] = {
|
||||
this.updateAt_(2, isAt2);
|
||||
},
|
||||
|
||||
// This block does not need JSO serialization hooks (saveExtraState and
|
||||
// loadExtraState) because the state of this object is already encoded in the
|
||||
// dropdown values.
|
||||
// XML hooks are kept for backwards compatibility.
|
||||
/**
|
||||
* Returns the state of this block as a JSON serializable object.
|
||||
* This block does not need to serialize any specific state as it is already
|
||||
* encoded in the dropdown values, but must have an implementation to avoid
|
||||
* the backward compatible XML mutations being serialized.
|
||||
* @return {null} The state of this block.
|
||||
*/
|
||||
saveExtraState: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the given state to this block.
|
||||
* No extra state is needed or expected as it is already encoded in the
|
||||
* dropdown values.
|
||||
*/
|
||||
loadExtraState: function() {},
|
||||
|
||||
/**
|
||||
* Create or delete an input for a numeric index.
|
||||
@@ -763,7 +822,7 @@ blocks['lists_getSublist'] = {
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
* @returns {null|undefined} Null if the field has been replaced;
|
||||
* @return {null|undefined} Null if the field has been replaced;
|
||||
* otherwise undefined.
|
||||
*/
|
||||
function(value) {
|
||||
@@ -799,23 +858,23 @@ blocks['lists_sort'] = {
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
'message0': Msg['LISTS_SORT_TITLE'],
|
||||
'message0': '%{BKY_LISTS_SORT_TITLE}',
|
||||
'args0': [
|
||||
{
|
||||
'type': 'field_dropdown',
|
||||
'name': 'TYPE',
|
||||
'options': [
|
||||
[Msg['LISTS_SORT_TYPE_NUMERIC'], 'NUMERIC'],
|
||||
[Msg['LISTS_SORT_TYPE_TEXT'], 'TEXT'],
|
||||
[Msg['LISTS_SORT_TYPE_IGNORECASE'], 'IGNORE_CASE'],
|
||||
['%{BKY_LISTS_SORT_TYPE_NUMERIC}', 'NUMERIC'],
|
||||
['%{BKY_LISTS_SORT_TYPE_TEXT}', 'TEXT'],
|
||||
['%{BKY_LISTS_SORT_TYPE_IGNORECASE}', 'IGNORE_CASE'],
|
||||
],
|
||||
},
|
||||
{
|
||||
'type': 'field_dropdown',
|
||||
'name': 'DIRECTION',
|
||||
'options': [
|
||||
[Msg['LISTS_SORT_ORDER_ASCENDING'], '1'],
|
||||
[Msg['LISTS_SORT_ORDER_DESCENDING'], '-1'],
|
||||
['%{BKY_LISTS_SORT_ORDER_ASCENDING}', '1'],
|
||||
['%{BKY_LISTS_SORT_ORDER_DESCENDING}', '-1'],
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -826,8 +885,8 @@ blocks['lists_sort'] = {
|
||||
],
|
||||
'output': 'Array',
|
||||
'style': 'list_blocks',
|
||||
'tooltip': Msg['LISTS_SORT_TOOLTIP'],
|
||||
'helpUrl': Msg['LISTS_SORT_HELPURL'],
|
||||
'tooltip': '%{BKY_LISTS_SORT_TOOLTIP}',
|
||||
'helpUrl': '%{BKY_LISTS_SORT_HELPURL}',
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -914,10 +973,23 @@ blocks['lists_split'] = {
|
||||
this.updateType_(xmlElement.getAttribute('mode'));
|
||||
},
|
||||
|
||||
// This block does not need JSO serialization hooks (saveExtraState and
|
||||
// loadExtraState) because the state of this object is already encoded in the
|
||||
// dropdown values.
|
||||
// XML hooks are kept for backwards compatibility.
|
||||
/**
|
||||
* Returns the state of this block as a JSON serializable object.
|
||||
* This block does not need to serialize any specific state as it is already
|
||||
* encoded in the dropdown values, but must have an implementation to avoid
|
||||
* the backward compatible XML mutations being serialized.
|
||||
* @return {null} The state of this block.
|
||||
*/
|
||||
saveExtraState: function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Applies the given state to this block.
|
||||
* No extra state is needed or expected as it is already encoded in the
|
||||
* dropdown values.
|
||||
*/
|
||||
loadExtraState: function() {},
|
||||
};
|
||||
|
||||
// Register provided blocks.
|
||||
|
||||
+14
-6
@@ -19,8 +19,10 @@ const Extensions = goog.require('Blockly.Extensions');
|
||||
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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {Mutator} = goog.require('Blockly.Mutator');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
@@ -394,7 +396,11 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
|
||||
const valueConnections = [null];
|
||||
const statementConnections = [null];
|
||||
let elseStatementConnection = null;
|
||||
while (clauseBlock && !clauseBlock.isInsertionMarker()) {
|
||||
while (clauseBlock) {
|
||||
if (clauseBlock.isInsertionMarker()) {
|
||||
clauseBlock = clauseBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
switch (clauseBlock.type) {
|
||||
case 'controls_if_elseif':
|
||||
this.elseifCount_++;
|
||||
@@ -408,8 +414,7 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
|
||||
default:
|
||||
throw TypeError('Unknown block type: ' + clauseBlock.type);
|
||||
}
|
||||
clauseBlock = clauseBlock.nextConnection &&
|
||||
clauseBlock.nextConnection.targetBlock();
|
||||
clauseBlock = clauseBlock.getNextBlock();
|
||||
}
|
||||
this.updateShape_();
|
||||
// Reconnect any child blocks.
|
||||
@@ -425,6 +430,10 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
|
||||
let clauseBlock = containerBlock.nextConnection.targetBlock();
|
||||
let i = 1;
|
||||
while (clauseBlock) {
|
||||
if (clauseBlock.isInsertionMarker()) {
|
||||
clauseBlock = clauseBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
switch (clauseBlock.type) {
|
||||
case 'controls_if_elseif': {
|
||||
const inputIf = this.getInput('IF' + i);
|
||||
@@ -445,8 +454,7 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
|
||||
default:
|
||||
throw TypeError('Unknown block type: ' + clauseBlock.type);
|
||||
}
|
||||
clauseBlock = clauseBlock.nextConnection &&
|
||||
clauseBlock.nextConnection.targetBlock();
|
||||
clauseBlock = clauseBlock.getNextBlock();
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
||||
+5
-3
@@ -21,8 +21,10 @@ const Variables = goog.require('Blockly.Variables');
|
||||
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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
@@ -293,8 +295,8 @@ Extensions.register(
|
||||
* To add a new loop type add this to your code:
|
||||
*
|
||||
* // If using the Blockly npm package and es6 import syntax:
|
||||
* import {loopTypes} from 'blockly/blocks';
|
||||
* loopTypes.add('custom_loop');
|
||||
* import {loops} from 'blockly/blocks';
|
||||
* loops.loopTypes.add('custom_loop');
|
||||
*
|
||||
* // Else if using Closure Compiler and goog.modules:
|
||||
* const {loopTypes} = goog.require('Blockly.libraryBlocks.loops');
|
||||
|
||||
+3
-1
@@ -19,8 +19,10 @@ 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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.FieldLabel');
|
||||
|
||||
@@ -23,8 +23,10 @@ const xmlUtils = goog.require('Blockly.utils.xml');
|
||||
const {Align} = goog.require('Blockly.Input');
|
||||
/* 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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {config} = goog.require('Blockly.config');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {FieldCheckbox} = goog.require('Blockly.FieldCheckbox');
|
||||
@@ -459,7 +461,7 @@ blocks['procedures_defnoreturn'] = {
|
||||
.appendField(Msg['PROCEDURES_DEFNORETURN_TITLE'])
|
||||
.appendField(nameField, 'NAME')
|
||||
.appendField('', 'PARAMS');
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg']));
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg'], this));
|
||||
if ((this.workspace.options.comments ||
|
||||
(this.workspace.options.parentWorkspace &&
|
||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||
@@ -505,7 +507,7 @@ blocks['procedures_defreturn'] = {
|
||||
this.appendValueInput('RETURN')
|
||||
.setAlign(Align.RIGHT)
|
||||
.appendField(Msg['PROCEDURES_DEFRETURN_RETURN']);
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg']));
|
||||
this.setMutator(new Mutator(['procedures_mutatorarg'], this));
|
||||
if ((this.workspace.options.comments ||
|
||||
(this.workspace.options.parentWorkspace &&
|
||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||
@@ -978,7 +980,7 @@ const PROCEDURE_CALL_COMMON = {
|
||||
Xml.domToWorkspace(xml, this.workspace);
|
||||
Events.setGroup(false);
|
||||
}
|
||||
} else if (event.type === Events.BLOCK_DELETE) {
|
||||
} else if (event.type === Events.BLOCK_DELETE && event.blockId != this.id) {
|
||||
// Look for the case where a procedure definition has been deleted,
|
||||
// leaving this block (a procedure call) orphaned. In this case, delete
|
||||
// the orphan.
|
||||
|
||||
+16
-8
@@ -19,8 +19,10 @@ const xmlUtils = goog.require('Blockly.utils.xml');
|
||||
const {Align} = goog.require('Blockly.Input');
|
||||
/* 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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {FieldDropdown} = goog.require('Blockly.FieldDropdown');
|
||||
const {FieldImage} = goog.require('Blockly.FieldImage');
|
||||
@@ -341,7 +343,7 @@ blocks['text_getSubstring'] = {
|
||||
/**
|
||||
* @param {*} value The input value.
|
||||
* @this {FieldDropdown}
|
||||
* @returns {null|undefined} Null if the field has been replaced;
|
||||
* @return {null|undefined} Null if the field has been replaced;
|
||||
* otherwise undefined.
|
||||
*/
|
||||
function(value) {
|
||||
@@ -781,10 +783,13 @@ const TEXT_JOIN_MUTATOR_MIXIN = {
|
||||
let itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
// Count number of inputs.
|
||||
const connections = [];
|
||||
while (itemBlock && !itemBlock.isInsertionMarker()) {
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
connections.push(itemBlock.valueConnection_);
|
||||
itemBlock =
|
||||
itemBlock.nextConnection && itemBlock.nextConnection.targetBlock();
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
}
|
||||
// Disconnect any children that don't belong.
|
||||
for (let i = 0; i < this.itemCount_; i++) {
|
||||
@@ -809,10 +814,13 @@ const TEXT_JOIN_MUTATOR_MIXIN = {
|
||||
let itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
let i = 0;
|
||||
while (itemBlock) {
|
||||
if (itemBlock.isInsertionMarker()) {
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
continue;
|
||||
}
|
||||
const input = this.getInput('ADD' + i);
|
||||
itemBlock.valueConnection_ = input && input.connection.targetConnection;
|
||||
itemBlock =
|
||||
itemBlock.nextConnection && itemBlock.nextConnection.targetBlock();
|
||||
itemBlock = itemBlock.getNextBlock();
|
||||
i++;
|
||||
}
|
||||
},
|
||||
@@ -856,7 +864,7 @@ const TEXT_JOIN_EXTENSION = function() {
|
||||
this.itemCount_ = 2;
|
||||
this.updateShape_();
|
||||
// Configure the mutator UI.
|
||||
this.setMutator(new Mutator(['text_create_join_item']));
|
||||
this.setMutator(new Mutator(['text_create_join_item'], this));
|
||||
};
|
||||
|
||||
// Update the tooltip of 'text_append' block to reference the variable.
|
||||
|
||||
+3
-1
@@ -18,8 +18,10 @@ const Variables = goog.require('Blockly.Variables');
|
||||
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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
|
||||
@@ -20,8 +20,10 @@ const Variables = goog.require('Blockly.Variables');
|
||||
const xml = 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} = goog.requireType('Blockly.blocks');
|
||||
const BlockDefinition = Object;
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
|
||||
/** @suppress {extraRequire} */
|
||||
|
||||
+189
-171
@@ -12,100 +12,113 @@
|
||||
}
|
||||
}(this, function(__parent__) {
|
||||
var $=__parent__.__namespace__;
|
||||
var module$exports$Blockly$libraryBlocks$variablesDynamic={};
|
||||
module$exports$Blockly$libraryBlocks$variablesDynamic.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"variables_get_dynamic",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_dynamic_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableDynamicSetterGetter"]},{type:"variables_set_dynamic",message0:"%{BKY_VARIABLES_SET}",
|
||||
var module$exports$Blockly$libraryBlocks$variablesDynamic={},module$contents$Blockly$libraryBlocks$variablesDynamic_ContextMenu=$.module$build$src$core$contextmenu,module$contents$Blockly$libraryBlocks$variablesDynamic_Extensions=$.module$build$src$core$extensions,module$contents$Blockly$libraryBlocks$variablesDynamic_Variables=$.module$build$src$core$variables,module$contents$Blockly$libraryBlocks$variablesDynamic_xml=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$variablesDynamic_BlockDefinition=
|
||||
Object,module$contents$Blockly$libraryBlocks$variablesDynamic_Msg=$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$variablesDynamic_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$variablesDynamic_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$variablesDynamic.blocks=module$contents$Blockly$libraryBlocks$variablesDynamic_createBlockDefinitionsFromJsonArray([{type:"variables_get_dynamic",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_dynamic_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableDynamicSetterGetter"]},{type:"variables_set_dynamic",message0:"%{BKY_VARIABLES_SET}",
|
||||
args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_dynamic_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableDynamicSetterGetter"]}]);
|
||||
var module$contents$Blockly$libraryBlocks$variablesDynamic_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("VAR");var c=this.workspace.getVariableById(b).type;if("variables_get_dynamic"===this.type){b="variables_set_dynamic";var d=$.module$exports$Blockly$Msg.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",d=$.module$exports$Blockly$Msg.Msg.VARIABLES_SET_CREATE_GET;var e={enabled:0<this.workspace.remainingCapacity()},
|
||||
f=this.getField("VAR").getText();e.text=d.replace("%1",f);d=(0,$.module$exports$Blockly$utils$xml.createElement)("field");d.setAttribute("name","VAR");d.setAttribute("variabletype",c);d.appendChild((0,$.module$exports$Blockly$utils$xml.createTextNode)(f));c=(0,$.module$exports$Blockly$utils$xml.createElement)("block");c.setAttribute("type",b);c.appendChild(d);e.callback=(0,$.module$exports$Blockly$ContextMenu.callbackFactory)(this,c);a.push(e)}else if("variables_get_dynamic"===this.type||"variables_get_reporter_dynamic"===
|
||||
this.type)b={text:$.module$exports$Blockly$Msg.Msg.RENAME_VARIABLE,enabled:!0,callback:module$contents$Blockly$libraryBlocks$variablesDynamic_renameOptionCallbackFactory(this)},e=this.getField("VAR").getText(),e={text:$.module$exports$Blockly$Msg.Msg.DELETE_VARIABLE.replace("%1",e),enabled:!0,callback:module$contents$Blockly$libraryBlocks$variablesDynamic_deleteOptionCallbackFactory(this)},a.unshift(b),a.unshift(e)},onchange:function(a){a=this.getFieldValue("VAR");a=(0,$.module$exports$Blockly$Variables.getVariable)(this.workspace,
|
||||
a);"variables_get_dynamic"===this.type?this.outputConnection.setCheck(a.type):this.getInput("VALUE").connection.setCheck(a.type)}},module$contents$Blockly$libraryBlocks$variablesDynamic_renameOptionCallbackFactory=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();(0,$.module$exports$Blockly$Variables.renameVariable)(b,c)}},module$contents$Blockly$libraryBlocks$variablesDynamic_deleteOptionCallbackFactory=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();
|
||||
b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};(0,$.module$exports$Blockly$Extensions.registerMixin)("contextMenu_variableDynamicSetterGetter",module$contents$Blockly$libraryBlocks$variablesDynamic_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$variablesDynamic.blocks);var module$exports$Blockly$libraryBlocks$variables={};
|
||||
module$exports$Blockly$libraryBlocks$variables.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",name:"VAR",
|
||||
variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]);
|
||||
var module$contents$Blockly$libraryBlocks$variables_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){if("variables_get"===this.type){var b="variables_set";var c=$.module$exports$Blockly$Msg.Msg.VARIABLES_GET_CREATE_SET}else b="variables_get",c=$.module$exports$Blockly$Msg.Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getField("VAR").getText();d.text=c.replace("%1",e);c=(0,$.module$exports$Blockly$utils$xml.createElement)("field");
|
||||
c.setAttribute("name","VAR");c.appendChild((0,$.module$exports$Blockly$utils$xml.createTextNode)(e));e=(0,$.module$exports$Blockly$utils$xml.createElement)("block");e.setAttribute("type",b);e.appendChild(c);d.callback=(0,$.module$exports$Blockly$ContextMenu.callbackFactory)(this,e);a.push(d)}else if("variables_get"===this.type||"variables_get_reporter"===this.type)b={text:$.module$exports$Blockly$Msg.Msg.RENAME_VARIABLE,enabled:!0,callback:module$contents$Blockly$libraryBlocks$variables_renameOptionCallbackFactory(this)},
|
||||
d=this.getField("VAR").getText(),d={text:$.module$exports$Blockly$Msg.Msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:module$contents$Blockly$libraryBlocks$variables_deleteOptionCallbackFactory(this)},a.unshift(b),a.unshift(d)}},module$contents$Blockly$libraryBlocks$variables_renameOptionCallbackFactory=function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();(0,$.module$exports$Blockly$Variables.renameVariable)(b,c)}},module$contents$Blockly$libraryBlocks$variables_deleteOptionCallbackFactory=
|
||||
function(a){return function(){var b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};(0,$.module$exports$Blockly$Extensions.registerMixin)("contextMenu_variableSetterGetter",module$contents$Blockly$libraryBlocks$variables_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$variables.blocks);var module$exports$Blockly$libraryBlocks$texts={};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]},{type:"text_multiline",message0:"%1 %2",args0:[{type:"field_image",src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpPU2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjmRIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z8873zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5lujnYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiDMYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYXfwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4zwA5X2Z9AYnQrEAAAAASUVORK5CYII=",width:12,
|
||||
height:17,alt:"\u00b6"},{type:"field_multilinetext",name:"TEXT",text:""}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"text_join",message0:"",output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_JOIN_HELPURL}",tooltip:"%{BKY_TEXT_JOIN_TOOLTIP}",mutator:"text_join_mutator"},{type:"text_create_join_container",message0:"%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",args0:[{type:"input_dummy"},
|
||||
var module$contents$Blockly$libraryBlocks$variablesDynamic_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getFieldValue("VAR");var c=this.workspace.getVariableById(b).type;if("variables_get_dynamic"===this.type){b="variables_set_dynamic";var d=module$contents$Blockly$libraryBlocks$variablesDynamic_Msg.VARIABLES_GET_CREATE_SET}else b="variables_get_dynamic",d=module$contents$Blockly$libraryBlocks$variablesDynamic_Msg.VARIABLES_SET_CREATE_GET;
|
||||
var e={enabled:0<this.workspace.remainingCapacity()};const f=this.getField("VAR").getText();e.text=d.replace("%1",f);d=$.module$build$src$core$utils$xml.createElement("field");d.setAttribute("name","VAR");d.setAttribute("variabletype",c);d.appendChild($.module$build$src$core$utils$xml.createTextNode(f));c=$.module$build$src$core$utils$xml.createElement("block");c.setAttribute("type",b);c.appendChild(d);e.callback=$.module$build$src$core$contextmenu.callbackFactory(this,c);a.push(e)}else if("variables_get_dynamic"===
|
||||
this.type||"variables_get_reporter_dynamic"===this.type)b={text:module$contents$Blockly$libraryBlocks$variablesDynamic_Msg.RENAME_VARIABLE,enabled:!0,callback:module$contents$Blockly$libraryBlocks$variablesDynamic_renameOptionCallbackFactory(this)},e=this.getField("VAR").getText(),e={text:module$contents$Blockly$libraryBlocks$variablesDynamic_Msg.DELETE_VARIABLE.replace("%1",e),enabled:!0,callback:module$contents$Blockly$libraryBlocks$variablesDynamic_deleteOptionCallbackFactory(this)},a.unshift(b),
|
||||
a.unshift(e)},onchange:function(a){a=this.getFieldValue("VAR");a=$.module$build$src$core$variables.getVariable(this.workspace,a);"variables_get_dynamic"===this.type?this.outputConnection.setCheck(a.type):this.getInput("VALUE").connection.setCheck(a.type)}},module$contents$Blockly$libraryBlocks$variablesDynamic_renameOptionCallbackFactory=function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();$.module$build$src$core$variables.renameVariable(b,c)}},module$contents$Blockly$libraryBlocks$variablesDynamic_deleteOptionCallbackFactory=
|
||||
function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};$.module$build$src$core$extensions.registerMixin("contextMenu_variableDynamicSetterGetter",module$contents$Blockly$libraryBlocks$variablesDynamic_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);module$contents$Blockly$libraryBlocks$variablesDynamic_defineBlocks(module$exports$Blockly$libraryBlocks$variablesDynamic.blocks);var module$exports$Blockly$libraryBlocks$variables={},module$contents$Blockly$libraryBlocks$variables_ContextMenu=$.module$build$src$core$contextmenu,module$contents$Blockly$libraryBlocks$variables_Extensions=$.module$build$src$core$extensions,module$contents$Blockly$libraryBlocks$variables_Variables=$.module$build$src$core$variables,module$contents$Blockly$libraryBlocks$variables_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$variables_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$variables_Msg=
|
||||
$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$variables_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$variables_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$variables.blocks=module$contents$Blockly$libraryBlocks$variables_createBlockDefinitionsFromJsonArray([{type:"variables_get",message0:"%1",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"}],output:null,style:"variable_blocks",helpUrl:"%{BKY_VARIABLES_GET_HELPURL}",tooltip:"%{BKY_VARIABLES_GET_TOOLTIP}",extensions:["contextMenu_variableSetterGetter"]},{type:"variables_set",message0:"%{BKY_VARIABLES_SET}",args0:[{type:"field_variable",
|
||||
name:"VAR",variable:"%{BKY_VARIABLES_DEFAULT_NAME}"},{type:"input_value",name:"VALUE"}],previousStatement:null,nextStatement:null,style:"variable_blocks",tooltip:"%{BKY_VARIABLES_SET_TOOLTIP}",helpUrl:"%{BKY_VARIABLES_SET_HELPURL}",extensions:["contextMenu_variableSetterGetter"]}]);
|
||||
var module$contents$Blockly$libraryBlocks$variables_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){if("variables_get"===this.type){var b="variables_set";var c=module$contents$Blockly$libraryBlocks$variables_Msg.VARIABLES_GET_CREATE_SET}else b="variables_get",c=module$contents$Blockly$libraryBlocks$variables_Msg.VARIABLES_SET_CREATE_GET;var d={enabled:0<this.workspace.remainingCapacity()},e=this.getField("VAR").getText();d.text=c.replace("%1",e);
|
||||
c=$.module$build$src$core$utils$xml.createElement("field");c.setAttribute("name","VAR");c.appendChild($.module$build$src$core$utils$xml.createTextNode(e));e=$.module$build$src$core$utils$xml.createElement("block");e.setAttribute("type",b);e.appendChild(c);d.callback=$.module$build$src$core$contextmenu.callbackFactory(this,e);a.push(d)}else if("variables_get"===this.type||"variables_get_reporter"===this.type)b={text:module$contents$Blockly$libraryBlocks$variables_Msg.RENAME_VARIABLE,enabled:!0,callback:module$contents$Blockly$libraryBlocks$variables_renameOptionCallbackFactory(this)},
|
||||
d=this.getField("VAR").getText(),d={text:module$contents$Blockly$libraryBlocks$variables_Msg.DELETE_VARIABLE.replace("%1",d),enabled:!0,callback:module$contents$Blockly$libraryBlocks$variables_deleteOptionCallbackFactory(this)},a.unshift(b),a.unshift(d)}},module$contents$Blockly$libraryBlocks$variables_renameOptionCallbackFactory=function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();$.module$build$src$core$variables.renameVariable(b,c)}},module$contents$Blockly$libraryBlocks$variables_deleteOptionCallbackFactory=
|
||||
function(a){return function(){const b=a.workspace,c=a.getField("VAR").getVariable();b.deleteVariableById(c.getId());b.refreshToolboxSelection()}};$.module$build$src$core$extensions.registerMixin("contextMenu_variableSetterGetter",module$contents$Blockly$libraryBlocks$variables_CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);module$contents$Blockly$libraryBlocks$variables_defineBlocks(module$exports$Blockly$libraryBlocks$variables.blocks);var module$exports$Blockly$libraryBlocks$texts={},module$contents$Blockly$libraryBlocks$texts_Extensions=$.module$build$src$core$extensions,module$contents$Blockly$libraryBlocks$texts_Msg=$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$texts_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$texts_Align=$.Align$$module$build$src$core$input,module$contents$Blockly$libraryBlocks$texts_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$texts_ConnectionType=
|
||||
$.module$build$src$core$connection_type.ConnectionType,module$contents$Blockly$libraryBlocks$texts_FieldDropdown=$.module$build$src$core$field_dropdown.FieldDropdown,module$contents$Blockly$libraryBlocks$texts_FieldImage=$.FieldImage$$module$build$src$core$field_image,module$contents$Blockly$libraryBlocks$texts_FieldTextInput=$.FieldTextInput$$module$build$src$core$field_textinput,module$contents$Blockly$libraryBlocks$texts_Mutator=$.Mutator$$module$build$src$core$mutator,module$contents$Blockly$libraryBlocks$texts_createBlockDefinitionsFromJsonArray=
|
||||
$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$texts_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks=module$contents$Blockly$libraryBlocks$texts_createBlockDefinitionsFromJsonArray([{type:"text",message0:"%1",args0:[{type:"field_input",name:"TEXT",text:""}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["text_quotes","parent_tooltip_when_inline"]},{type:"text_multiline",message0:"%1 %2",args0:[{type:"field_image",src:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpPU2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjmRIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z8873zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5lujnYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiDMYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYXfwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4zwA5X2Z9AYnQrEAAAAASUVORK5CYII=",
|
||||
width:12,height:17,alt:"\u00b6"},{type:"field_multilinetext",name:"TEXT",text:""}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_TEXT_HELPURL}",tooltip:"%{BKY_TEXT_TEXT_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"text_join",message0:"",output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_JOIN_HELPURL}",tooltip:"%{BKY_TEXT_JOIN_TOOLTIP}",mutator:"text_join_mutator"},{type:"text_create_join_container",message0:"%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2",args0:[{type:"input_dummy"},
|
||||
{type:"input_statement",name:"STACK"}],style:"text_blocks",tooltip:"%{BKY_TEXT_CREATE_JOIN_TOOLTIP}",enableContextMenu:!1},{type:"text_create_join_item",message0:"%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}",previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:"%{BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}",enableContextMenu:!1},{type:"text_append",message0:"%{BKY_TEXT_APPEND_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:"%{BKY_TEXT_APPEND_VARIABLE}"},{type:"input_value",name:"TEXT"}],
|
||||
previousStatement:null,nextStatement:null,style:"text_blocks",extensions:["text_append_tooltip"]},{type:"text_length",message0:"%{BKY_TEXT_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"text_blocks",tooltip:"%{BKY_TEXT_LENGTH_TOOLTIP}",helpUrl:"%{BKY_TEXT_LENGTH_HELPURL}"},{type:"text_isEmpty",message0:"%{BKY_TEXT_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"text_blocks",tooltip:"%{BKY_TEXT_ISEMPTY_TOOLTIP}",
|
||||
helpUrl:"%{BKY_TEXT_ISEMPTY_HELPURL}"},{type:"text_indexOf",message0:"%{BKY_TEXT_INDEXOF_TITLE}",args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"END",options:[["%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}","FIRST"],["%{BKY_TEXT_INDEXOF_OPERATOR_LAST}","LAST"]]},{type:"input_value",name:"FIND",check:"String"}],output:"Number",style:"text_blocks",helpUrl:"%{BKY_TEXT_INDEXOF_HELPURL}",inputsInline:!0,extensions:["text_indexOf_tooltip"]},{type:"text_charAt",message0:"%{BKY_TEXT_CHARAT_TITLE}",
|
||||
args0:[{type:"input_value",name:"VALUE",check:"String"},{type:"field_dropdown",name:"WHERE",options:[["%{BKY_TEXT_CHARAT_FROM_START}","FROM_START"],["%{BKY_TEXT_CHARAT_FROM_END}","FROM_END"],["%{BKY_TEXT_CHARAT_FIRST}","FIRST"],["%{BKY_TEXT_CHARAT_LAST}","LAST"],["%{BKY_TEXT_CHARAT_RANDOM}","RANDOM"]]}],output:"String",style:"text_blocks",helpUrl:"%{BKY_TEXT_CHARAT_HELPURL}",inputsInline:!0,mutator:"text_charAt_mutator"}]);
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_getSubstring={init:function(){this.WHERE_OPTIONS_1=[[$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],[$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_END_FROM_END,
|
||||
"FROM_END"],[$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_HELPURL);this.setStyle("text_blocks");this.appendValueInput("STRING").setCheck("String").appendField($.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);this.appendDummyInput("AT1");this.appendDummyInput("AT2");$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField($.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_TAIL);
|
||||
this.setInputsInline(!0);this.setOutput(!0,"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip($.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation"),b=this.getInput("AT1").type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;a.setAttribute("at2",
|
||||
b);return a},domToMutation:function(a){var b="true"===a.getAttribute("at1");a="true"===a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),$.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField($.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);2===a&&$.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_TAIL&&
|
||||
(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField($.module$exports$Blockly$Msg.Msg.TEXT_GET_SUBSTRING_TAIL));var c=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(this["WHERE_OPTIONS_"+a],function(d){var e="FROM_START"===d||"FROM_END"===d;if(e!==b){var f=this.getSourceBlock();f.updateAt_(a,e);f.setFieldValue(d,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1===a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1",
|
||||
"AT2"))}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_changeCase={init:function(){var a=[[$.module$exports$Blockly$Msg.Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[$.module$exports$Blockly$Msg.Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[$.module$exports$Blockly$Msg.Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.TEXT_CHANGECASE_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField(new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a),"CASE");
|
||||
this.setOutput(!0,"String");this.setTooltip($.module$exports$Blockly$Msg.Msg.TEXT_CHANGECASE_TOOLTIP)}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_trim={init:function(){var a=[[$.module$exports$Blockly$Msg.Msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[$.module$exports$Blockly$Msg.Msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[$.module$exports$Blockly$Msg.Msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.TEXT_TRIM_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField(new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a),"MODE");
|
||||
this.setOutput(!0,"String");this.setTooltip($.module$exports$Blockly$Msg.Msg.TEXT_TRIM_TOOLTIP)}};module$exports$Blockly$libraryBlocks$texts.blocks.text_print={init:function(){this.jsonInit({message0:$.module$exports$Blockly$Msg.Msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:$.module$exports$Blockly$Msg.Msg.TEXT_PRINT_TOOLTIP,helpUrl:$.module$exports$Blockly$Msg.Msg.TEXT_PRINT_HELPURL})}};
|
||||
var module$contents$Blockly$libraryBlocks$texts_TEXT_PROMPT_COMMON={updateType_:function(a){this.outputConnection.setCheck("NUMBER"===a?"Number":"String")},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("type",this.getFieldValue("TYPE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("type"))}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_prompt_ext=Object.assign({},module$contents$Blockly$libraryBlocks$texts_TEXT_PROMPT_COMMON,{init:function(){var a=[[$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");var b=this;a=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a,function(c){b.updateType_(c)});
|
||||
this.appendValueInput("TEXT").appendField(a,"TYPE");this.setOutput(!0,"String");this.setTooltip(function(){return"TEXT"===b.getFieldValue("TYPE")?$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TOOLTIP_TEXT:$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TOOLTIP_NUMBER})}});
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_prompt=Object.assign({},module$contents$Blockly$libraryBlocks$texts_TEXT_PROMPT_COMMON,{init:function(){this.mixin(module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN);var a=[[$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]],b=this;this.setHelpUrl($.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");a=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a,
|
||||
function(c){b.updateType_(c)});this.appendDummyInput().appendField(a,"TYPE").appendField(this.newQuote_(!0)).appendField(new $.module$exports$Blockly$FieldTextInput.FieldTextInput(""),"TEXT").appendField(this.newQuote_(!1));this.setOutput(!0,"String");this.setTooltip(function(){return"TEXT"===b.getFieldValue("TYPE")?$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TOOLTIP_TEXT:$.module$exports$Blockly$Msg.Msg.TEXT_PROMPT_TOOLTIP_NUMBER})}});
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_count={init:function(){this.jsonInit({message0:$.module$exports$Blockly$Msg.Msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,style:"text_blocks",tooltip:$.module$exports$Blockly$Msg.Msg.TEXT_COUNT_TOOLTIP,helpUrl:$.module$exports$Blockly$Msg.Msg.TEXT_COUNT_HELPURL})}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_replace={init:function(){this.jsonInit({message0:$.module$exports$Blockly$Msg.Msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:$.module$exports$Blockly$Msg.Msg.TEXT_REPLACE_TOOLTIP,helpUrl:$.module$exports$Blockly$Msg.Msg.TEXT_REPLACE_HELPURL})}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_reverse={init:function(){this.jsonInit({message0:$.module$exports$Blockly$Msg.Msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:$.module$exports$Blockly$Msg.Msg.TEXT_REVERSE_TOOLTIP,helpUrl:$.module$exports$Blockly$Msg.Msg.TEXT_REVERSE_HELPURL})}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_getSubstring={init:function(){this.WHERE_OPTIONS_1=[[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_START_FROM_START,"FROM_START"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_START_FROM_END,"FROM_END"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_END_FROM_START,"FROM_START"],
|
||||
[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_END_FROM_END,"FROM_END"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_END_LAST,"LAST"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_HELPURL);this.setStyle("text_blocks");this.appendValueInput("STRING").setCheck("String").appendField(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_INPUT_IN_TEXT);this.appendDummyInput("AT1");this.appendDummyInput("AT2");
|
||||
module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_TAIL&&this.appendDummyInput("TAIL").appendField(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_TAIL);this.setInputsInline(!0);this.setOutput(!0,"String");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_TOOLTIP)},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");var b=this.getInput("AT1").type===
|
||||
$.module$build$src$core$connection_type.ConnectionType.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type===$.module$build$src$core$connection_type.ConnectionType.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){const b="true"===a.getAttribute("at1");a="true"===a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),module$contents$Blockly$libraryBlocks$texts_Msg.ORDINAL_NUMBER_SUFFIX&&
|
||||
this.appendDummyInput("ORDINAL"+a).appendField(module$contents$Blockly$libraryBlocks$texts_Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);2===a&&module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_GET_SUBSTRING_TAIL));const c=new module$contents$Blockly$libraryBlocks$texts_FieldDropdown(this["WHERE_OPTIONS_"+a],function(d){const e="FROM_START"===
|
||||
d||"FROM_END"===d;if(e!==b){const f=this.getSourceBlock();f.updateAt_(a,e);f.setFieldValue(d,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1===a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"))}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_changeCase={init:function(){const a=[[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHANGECASE_OPERATOR_UPPERCASE,"UPPERCASE"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHANGECASE_OPERATOR_LOWERCASE,"LOWERCASE"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHANGECASE_OPERATOR_TITLECASE,"TITLECASE"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHANGECASE_HELPURL);this.setStyle("text_blocks");
|
||||
this.appendValueInput("TEXT").setCheck("String").appendField(new module$contents$Blockly$libraryBlocks$texts_FieldDropdown(a),"CASE");this.setOutput(!0,"String");this.setTooltip(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHANGECASE_TOOLTIP)}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_trim={init:function(){const a=[[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_TRIM_OPERATOR_BOTH,"BOTH"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_TRIM_OPERATOR_LEFT,"LEFT"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_TRIM_OPERATOR_RIGHT,"RIGHT"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_TRIM_HELPURL);this.setStyle("text_blocks");this.appendValueInput("TEXT").setCheck("String").appendField(new module$contents$Blockly$libraryBlocks$texts_FieldDropdown(a),
|
||||
"MODE");this.setOutput(!0,"String");this.setTooltip(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_TRIM_TOOLTIP)}};module$exports$Blockly$libraryBlocks$texts.blocks.text_print={init:function(){this.jsonInit({message0:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PRINT_TITLE,args0:[{type:"input_value",name:"TEXT"}],previousStatement:null,nextStatement:null,style:"text_blocks",tooltip:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PRINT_TOOLTIP,helpUrl:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PRINT_HELPURL})}};
|
||||
var module$contents$Blockly$libraryBlocks$texts_TEXT_PROMPT_COMMON={updateType_:function(a){this.outputConnection.setCheck("NUMBER"===a?"Number":"String")},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("type",this.getFieldValue("TYPE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("type"))}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_prompt_ext=Object.assign({},module$contents$Blockly$libraryBlocks$texts_TEXT_PROMPT_COMMON,{init:function(){var a=[[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_HELPURL);this.setStyle("text_blocks");const b=this;a=new module$contents$Blockly$libraryBlocks$texts_FieldDropdown(a,
|
||||
function(c){b.updateType_(c)});this.appendValueInput("TEXT").appendField(a,"TYPE");this.setOutput(!0,"String");this.setTooltip(function(){return"TEXT"===b.getFieldValue("TYPE")?module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TOOLTIP_TEXT:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TOOLTIP_NUMBER})}});
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_prompt=Object.assign({},module$contents$Blockly$libraryBlocks$texts_TEXT_PROMPT_COMMON,{init:function(){this.mixin(module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN);var a=[[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TYPE_TEXT,"TEXT"],[module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TYPE_NUMBER,"NUMBER"]];const b=this;this.setHelpUrl(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_HELPURL);
|
||||
this.setStyle("text_blocks");a=new module$contents$Blockly$libraryBlocks$texts_FieldDropdown(a,function(c){b.updateType_(c)});this.appendDummyInput().appendField(a,"TYPE").appendField(this.newQuote_(!0)).appendField(new $.FieldTextInput$$module$build$src$core$field_textinput(""),"TEXT").appendField(this.newQuote_(!1));this.setOutput(!0,"String");this.setTooltip(function(){return"TEXT"===b.getFieldValue("TYPE")?module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TOOLTIP_TEXT:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_PROMPT_TOOLTIP_NUMBER})}});
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_count={init:function(){this.jsonInit({message0:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_COUNT_MESSAGE0,args0:[{type:"input_value",name:"SUB",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"Number",inputsInline:!0,style:"text_blocks",tooltip:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_COUNT_TOOLTIP,helpUrl:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_COUNT_HELPURL})}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_replace={init:function(){this.jsonInit({message0:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_REPLACE_MESSAGE0,args0:[{type:"input_value",name:"FROM",check:"String"},{type:"input_value",name:"TO",check:"String"},{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_REPLACE_TOOLTIP,helpUrl:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_REPLACE_HELPURL})}};
|
||||
module$exports$Blockly$libraryBlocks$texts.blocks.text_reverse={init:function(){this.jsonInit({message0:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_REVERSE_MESSAGE0,args0:[{type:"input_value",name:"TEXT",check:"String"}],output:"String",inputsInline:!0,style:"text_blocks",tooltip:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_REVERSE_TOOLTIP,helpUrl:module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_REVERSE_HELPURL})}};
|
||||
var module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN={QUOTE_IMAGE_LEFT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAn0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMfz9AylsaRRgGzvZAAAAAElFTkSuQmCC",QUOTE_IMAGE_RIGHT_DATAURI:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAAqUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhggONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvBO3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5AoslLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==",
|
||||
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(var b=0,c;c=this.inputList[b];b++)for(var d=0,e;e=c.fieldRow[d];d++)if(a===e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new $.module$exports$Blockly$FieldImage.FieldImage(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,
|
||||
a?"\u201c":"\u201d")}},module$contents$Blockly$libraryBlocks$texts_TEXT_QUOTES_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")},module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_MUTATOR_MIXIN={mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},
|
||||
saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){var b=a.newBlock("text_create_join_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b&&!b.isInsertionMarker();)a.push(b.valueConnection_),
|
||||
b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;c&&-1===a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)$.module$exports$Blockly$Mutator.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");for(var b=0;a;){var c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;a=a.nextConnection&&a.nextConnection.targetBlock();
|
||||
b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+a).setAlign($.module$exports$Blockly$Input.Align.RIGHT);0===a&&b.appendField($.module$exports$Blockly$Msg.Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(a=this.itemCount_;this.getInput("ADD"+
|
||||
a);a++)this.removeInput("ADD"+a)}},module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN);this.itemCount_=2;this.updateShape_();this.setMutator(new $.module$exports$Blockly$Mutator.Mutator(["text_create_join_item"]))};(0,$.module$exports$Blockly$Extensions.register)("text_append_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipWithFieldText)("%{BKY_TEXT_APPEND_TOOLTIP}","VAR"));
|
||||
var module$contents$Blockly$libraryBlocks$texts_TEXT_INDEXOF_TOOLTIP_EXTENSION=function(){var a=this;this.setTooltip(function(){return $.module$exports$Blockly$Msg.Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",a.workspace.options.oneBasedIndex?"0":"-1")})},module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_MUTATOR_MIXIN={mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("at",!!this.isAt_);return a},domToMutation:function(a){a="false"!==a.getAttribute("at");
|
||||
this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT",!0);this.removeInput("ORDINAL",!0);a&&(this.appendValueInput("AT").setCheck("Number"),$.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX));$.module$exports$Blockly$Msg.Msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField($.module$exports$Blockly$Msg.Msg.TEXT_CHARAT_TAIL));this.isAt_=a}},module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_EXTENSION=
|
||||
function(){this.getField("WHERE").setValidator(function(b){b="FROM_START"===b||"FROM_END"===b;b!==this.isAt_&&this.getSourceBlock().updateAt_(b)});this.updateAt_(!0);var a=this;this.setTooltip(function(){var b=a.getFieldValue("WHERE"),c=$.module$exports$Blockly$Msg.Msg.TEXT_CHARAT_TOOLTIP;("FROM_START"===b||"FROM_END"===b)&&(b="FROM_START"===b?$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_FROM_START_TOOLTIP:$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_FROM_END_TOOLTIP)&&(c+=" "+b.replace("%1",a.workspace.options.oneBasedIndex?
|
||||
"#1":"#0"));return c})};(0,$.module$exports$Blockly$Extensions.register)("text_indexOf_tooltip",module$contents$Blockly$libraryBlocks$texts_TEXT_INDEXOF_TOOLTIP_EXTENSION);(0,$.module$exports$Blockly$Extensions.register)("text_quotes",module$contents$Blockly$libraryBlocks$texts_TEXT_QUOTES_EXTENSION);(0,$.module$exports$Blockly$Extensions.registerMutator)("text_join_mutator",module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_EXTENSION);
|
||||
(0,$.module$exports$Blockly$Extensions.registerMutator)("text_charAt_mutator",module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_EXTENSION);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$texts.blocks);var module$exports$Blockly$libraryBlocks$procedures={blocks:{}},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_DEF_COMMON={setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK","RETURN")):this.removeInput("STACK",!0),this.hasStatements_=a)},updateParams_:function(){var a="";this.arguments_.length&&(a=$.module$exports$Blockly$Msg.Msg.PROCEDURES_BEFORE_PARAMS+
|
||||
" "+this.arguments_.join(", "));(0,$.module$exports$Blockly$Events.disable)();try{this.setFieldValue(a,"PARAMS")}finally{(0,$.module$exports$Blockly$Events.enable)()}},mutationToDom:function(a){var b=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(var c=0;c<this.argumentVarModels_.length;c++){var d=(0,$.module$exports$Blockly$utils$xml.createElement)("arg"),e=this.argumentVarModels_[c];d.setAttribute("name",e.name);d.setAttribute("varid",
|
||||
e.getId());a&&this.paramIds_&&d.setAttribute("paramId",this.paramIds_[c]);b.appendChild(d)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];this.argumentVarModels_=[];for(var b=0,c;c=a.childNodes[b];b++)if("arg"===c.nodeName.toLowerCase()){var d=c.getAttribute("name");c=c.getAttribute("varid")||c.getAttribute("varId");this.arguments_.push(d);c=(0,$.module$exports$Blockly$Variables.getOrCreateVariablePackage)(this.workspace,c,d,"");null!==
|
||||
c?this.argumentVarModels_.push(c):console.log("Failed to create a variable with name "+d+", ignoring.")}this.updateParams_();(0,$.module$exports$Blockly$Procedures.mutateCallers)(this);this.setStatements_("false"!==a.getAttribute("statements"))},saveExtraState:function(){if(!this.argumentVarModels_.length&&this.hasStatements_)return null;var a=Object.create(null);if(this.argumentVarModels_.length){a.params=[];for(var b=0;b<this.argumentVarModels_.length;b++)a.params.push({name:this.argumentVarModels_[b].name,
|
||||
id:this.argumentVarModels_[b].getId()})}this.hasStatements_||(a.hasStatements=!1);return a},loadExtraState:function(a){this.arguments_=[];this.argumentVarModels_=[];if(a.params)for(var b=0;b<a.params.length;b++){var c=a.params[b];c=(0,$.module$exports$Blockly$Variables.getOrCreateVariablePackage)(this.workspace,c.id,c.name,"");this.arguments_.push(c.name);this.argumentVarModels_.push(c)}this.updateParams_();(0,$.module$exports$Blockly$Procedures.mutateCallers)(this);this.setStatements_(!1===a.hasStatements?
|
||||
!1:!0)},decompose:function(a){var b=(0,$.module$exports$Blockly$utils$xml.createElement)("block");b.setAttribute("type","procedures_mutatorcontainer");var c=(0,$.module$exports$Blockly$utils$xml.createElement)("statement");c.setAttribute("name","STACK");b.appendChild(c);for(var d=0;d<this.arguments_.length;d++){var e=(0,$.module$exports$Blockly$utils$xml.createElement)("block");e.setAttribute("type","procedures_mutatorarg");var f=(0,$.module$exports$Blockly$utils$xml.createElement)("field");f.setAttribute("name",
|
||||
"NAME");var g=(0,$.module$exports$Blockly$utils$xml.createTextNode)(this.arguments_[d]);f.appendChild(g);e.appendChild(f);f=(0,$.module$exports$Blockly$utils$xml.createElement)("next");e.appendChild(f);c.appendChild(e);c=f}a=(0,$.module$exports$Blockly$Xml.domToBlock)(b,a);"procedures_defreturn"===this.type?a.setFieldValue(this.hasStatements_,"STATEMENTS"):a.removeInput("STATEMENT_INPUT");(0,$.module$exports$Blockly$Procedures.mutateCallers)(this);return a},compose:function(a){this.arguments_=[];
|
||||
this.paramIds_=[];this.argumentVarModels_=[];for(var b=a.getInputTargetBlock("STACK");b&&!b.isInsertionMarker();){var c=b.getFieldValue("NAME");this.arguments_.push(c);c=this.workspace.getVariable(c,"");this.argumentVarModels_.push(c);this.paramIds_.push(b.id);b=b.nextConnection&&b.nextConnection.targetBlock()}this.updateParams_();(0,$.module$exports$Blockly$Procedures.mutateCallers)(this);a=a.getFieldValue("STATEMENTS");if(null!==a&&(a="TRUE"===a,this.hasStatements_!==a))if(a)this.setStatements_(!0),
|
||||
$.module$exports$Blockly$Mutator.Mutator.reconnect(this.statementConnection_,this,"STACK"),this.statementConnection_=null;else{a=this.getInput("STACK").connection;if(this.statementConnection_=a.targetConnection)a=a.targetBlock(),a.unplug(),a.bumpNeighbours();this.setStatements_(!1)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},renameVarById:function(a,b){var c=this.workspace.getVariableById(a);if(""===c.type){c=c.name;b=this.workspace.getVariableById(b);
|
||||
for(var d=!1,e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===a&&(this.arguments_[e]=b.name,this.argumentVarModels_[e]=b,d=!0);d&&(this.displayRenamedVar_(c,b.name),(0,$.module$exports$Blockly$Procedures.mutateCallers)(this))}},updateVarName:function(a){for(var b=a.name,c=!1,d,e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===a.getId()&&(d=this.arguments_[e],this.arguments_[e]=b,c=!0);c&&(this.displayRenamedVar_(d,b),(0,$.module$exports$Blockly$Procedures.mutateCallers)(this))},
|
||||
displayRenamedVar_:function(a,b){this.updateParams_();if(this.mutator&&this.mutator.isVisible())for(var c=this.mutator.workspace_.getAllBlocks(!1),d=0,e;e=c[d];d++)"procedures_mutatorarg"===e.type&&$.module$exports$Blockly$Names.Names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")},customContextMenu:function(a){if(!this.isInFlyout){var b={enabled:!0},c=this.getFieldValue("NAME");b.text=$.module$exports$Blockly$Msg.Msg.PROCEDURES_CREATE_DO.replace("%1",c);var d=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");
|
||||
d.setAttribute("name",c);for(c=0;c<this.arguments_.length;c++){var e=(0,$.module$exports$Blockly$utils$xml.createElement)("arg");e.setAttribute("name",this.arguments_[c]);d.appendChild(e)}c=(0,$.module$exports$Blockly$utils$xml.createElement)("block");c.setAttribute("type",this.callType_);c.appendChild(d);b.callback=(0,$.module$exports$Blockly$ContextMenu.callbackFactory)(this,c);a.push(b);if(!this.isCollapsed())for(b=0;b<this.argumentVarModels_.length;b++)d={enabled:!0},c=this.argumentVarModels_[b],
|
||||
d.text=$.module$exports$Blockly$Msg.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c.name),c=(0,$.module$exports$Blockly$Variables.generateVariableFieldDom)(c),e=(0,$.module$exports$Blockly$utils$xml.createElement)("block"),e.setAttribute("type","variables_get"),e.appendChild(c),d.callback=(0,$.module$exports$Blockly$ContextMenu.callbackFactory)(this,e),a.push(d)}}};
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_defnoreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_DEF_COMMON,{init:function(){var a=(0,$.module$exports$Blockly$Procedures.findLegalName)("",this);a=new $.module$exports$Blockly$FieldTextInput.FieldTextInput(a,$.module$exports$Blockly$Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,"NAME").appendField("",
|
||||
"PARAMS");this.setMutator(new $.module$exports$Blockly$Mutator.Mutator(["procedures_mutatorarg"]));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&$.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFNORETURN_COMMENT&&this.setCommentText($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFNORETURN_COMMENT);this.setStyle("procedure_blocks");this.setTooltip($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);this.setHelpUrl($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFNORETURN_HELPURL);
|
||||
this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},callType_:"procedures_callnoreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_defreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_DEF_COMMON,{init:function(){var a=(0,$.module$exports$Blockly$Procedures.findLegalName)("",this);a=new $.module$exports$Blockly$FieldTextInput.FieldTextInput(a,$.module$exports$Blockly$Procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,"NAME").appendField("",
|
||||
"PARAMS");this.appendValueInput("RETURN").setAlign($.module$exports$Blockly$Input.Align.RIGHT).appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new $.module$exports$Blockly$Mutator.Mutator(["procedures_mutatorarg"]));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&$.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_COMMENT&&this.setCommentText($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_COMMENT);
|
||||
this.setStyle("procedure_blocks");this.setTooltip($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_TOOLTIP);this.setHelpUrl($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!0]},callType_:"procedures_callreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_mutatorcontainer={init:function(){this.appendDummyInput().appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_ALLOW_STATEMENTS).appendField(new $.module$exports$Blockly$FieldCheckbox.FieldCheckbox("TRUE"),"STATEMENTS");this.setStyle("procedure_blocks");this.setTooltip($.module$exports$Blockly$Msg.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
|
||||
this.contextMenu=!1}};
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_mutatorarg={init:function(){var a=new $.module$exports$Blockly$FieldTextInput.FieldTextInput($.module$exports$Blockly$Procedures.DEFAULT_ARG,this.validator_);a.oldShowEditorFn_=a.showEditor_;a.showEditor_=function(){this.createdVariables_=[];this.oldShowEditorFn_()};this.appendDummyInput().appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);
|
||||
this.setStyle("procedure_blocks");this.setTooltip($.module$exports$Blockly$Msg.Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1;a.onFinishEditing_=this.deleteIntermediateVars_;a.createdVariables_=[];a.onFinishEditing_("x")},validator_:function(a){var b=this.getSourceBlock(),c=$.module$exports$Blockly$Mutator.Mutator.findParentWs(b.workspace);a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,"");if(!a)return null;for(var d=(b.workspace.targetWorkspace||b.workspace).getAllBlocks(!1),e=a.toLowerCase(),
|
||||
f=0;f<d.length;f++)if(d[f].id!==this.getSourceBlock().id){var g=d[f].getFieldValue("NAME");if(g&&g.toLowerCase()===e)return null}if(b.isInFlyout)return a;(b=c.getVariable(a,""))&&b.name!==a&&c.renameVariableById(b.getId(),a);b||(b=c.createVariable(a,""))&&this.createdVariables_&&this.createdVariables_.push(b);return a},deleteIntermediateVars_:function(a){var b=$.module$exports$Blockly$Mutator.Mutator.findParentWs(this.getSourceBlock().workspace);if(b)for(var c=0;c<this.createdVariables_.length;c++){var d=
|
||||
this.createdVariables_[c];d.name!==a&&b.deleteVariableById(d.getId())}}};
|
||||
var module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_CALL_COMMON={getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){$.module$exports$Blockly$Names.Names.equals(a,this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?$.module$exports$Blockly$Msg.Msg.PROCEDURES_CALLRETURN_TOOLTIP:$.module$exports$Blockly$Msg.Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,b){var c=
|
||||
(0,$.module$exports$Blockly$Procedures.getDefinition)(this.getProcedureCall(),this.workspace),d=c&&c.mutator&&c.mutator.isVisible();d?this.setCollapsed(!1):(this.quarkConnections_={},this.quarkIds_=null);if(a.join("\n")===this.arguments_.join("\n"))this.quarkIds_=b;else{if(b.length!==a.length)throw RangeError("paramNames and paramIds must be the same length.");this.quarkIds_||(this.quarkConnections_={},this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(var e=0;e<this.arguments_.length;e++){var f=
|
||||
this.getInput("ARG"+e);f&&(f=f.connection.targetConnection,this.quarkConnections_[this.quarkIds_[e]]=f,d&&f&&-1===b.indexOf(this.quarkIds_[e])&&(f.disconnect(),f.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(a=0;a<this.arguments_.length;a++)d=(0,$.module$exports$Blockly$Variables.getOrCreateVariablePackage)(this.workspace,null,this.arguments_[a],""),this.argumentVarModels_.push(d);this.updateShape_();if(this.quarkIds_=b)for(b=0;b<this.arguments_.length;b++)a=
|
||||
this.quarkIds_[b],a in this.quarkConnections_&&($.module$exports$Blockly$Mutator.Mutator.reconnect(this.quarkConnections_[a],this,"ARG"+b)||delete this.quarkConnections_[a]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){(0,$.module$exports$Blockly$Events.disable)();try{b.setValue(this.arguments_[a])}finally{(0,$.module$exports$Blockly$Events.enable)()}}else b=new $.module$exports$Blockly$FieldLabel.FieldLabel(this.arguments_[a]),
|
||||
this.appendValueInput("ARG"+a).setAlign($.module$exports$Blockly$Input.Align.RIGHT).appendField(b,"ARGNAME"+a).init()}for(a=this.arguments_.length;this.getInput("ARG"+a);a++)this.removeInput("ARG"+a);if(a=this.getInput("TOPROW"))this.arguments_.length?this.getField("WITH")||(a.appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"),a.init()):this.getField("WITH")&&a.removeField("WITH")},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");
|
||||
a.setAttribute("name",this.getProcedureCall());for(var b=0;b<this.arguments_.length;b++){var c=(0,$.module$exports$Blockly$utils$xml.createElement)("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];for(var c=[],d=0,e;e=a.childNodes[d];d++)"arg"===e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,
|
||||
c)},saveExtraState:function(){var a=Object.create(null);a.name=this.getProcedureCall();this.arguments_.length&&(a.params=this.arguments_);return a},loadExtraState:function(a){this.renameProcedure(this.getProcedureCall(),a.name);if(a=a.params){var b=[];b.length=a.length;b.fill(null);this.setProcedureParameters_(a,b)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&!this.workspace.isFlyout&&a.recordUndo)if(a.type===
|
||||
$.module$exports$Blockly$Events.BLOCK_CREATE&&-1!==a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=(0,$.module$exports$Blockly$Procedures.getDefinition)(b,this.workspace);!b||b.type===this.defType_&&JSON.stringify(b.getVars())===JSON.stringify(this.arguments_)||(b=null);if(!b){(0,$.module$exports$Blockly$Events.setGroup)(a.group);a=(0,$.module$exports$Blockly$utils$xml.createElement)("xml");b=(0,$.module$exports$Blockly$utils$xml.createElement)("block");b.setAttribute("type",this.defType_);
|
||||
var c=this.getRelativeToSurfaceXY(),d=c.y+2*$.module$exports$Blockly$config.config.snapRadius;b.setAttribute("x",c.x+$.module$exports$Blockly$config.config.snapRadius*(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=(0,$.module$exports$Blockly$utils$xml.createElement)("field");c.setAttribute("name","NAME");d=this.getProcedureCall();var e=(0,$.module$exports$Blockly$Procedures.findLegalName)(d,this);d!==e&&this.renameProcedure(d,e);c.appendChild((0,$.module$exports$Blockly$utils$xml.createTextNode)(d));
|
||||
b.appendChild(c);a.appendChild(b);(0,$.module$exports$Blockly$Xml.domToWorkspace)(a,this.workspace);(0,$.module$exports$Blockly$Events.setGroup)(!1)}}else a.type===$.module$exports$Blockly$Events.BLOCK_DELETE?(b=this.getProcedureCall(),(0,$.module$exports$Blockly$Procedures.getDefinition)(b,this.workspace)||((0,$.module$exports$Blockly$Events.setGroup)(a.group),this.dispose(!0),(0,$.module$exports$Blockly$Events.setGroup)(!1))):a.type===$.module$exports$Blockly$Events.CHANGE&&"disabled"===a.element&&
|
||||
(b=this.getProcedureCall(),(b=(0,$.module$exports$Blockly$Procedures.getDefinition)(b,this.workspace))&&b.id===a.blockId&&((b=(0,$.module$exports$Blockly$Events.getGroup)())&&console.log("Saw an existing group while responding to a definition change"),(0,$.module$exports$Blockly$Events.setGroup)(a.group),a.newValue?(this.previousEnabledState_=this.isEnabled(),this.setEnabled(!1)):this.setEnabled(this.previousEnabledState_),(0,$.module$exports$Blockly$Events.setGroup)(b)))},customContextMenu:function(a){if(this.workspace.isMovable()){var b=
|
||||
{enabled:!0};b.text=$.module$exports$Blockly$Msg.Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){var e=(0,$.module$exports$Blockly$Procedures.getDefinition)(c,d);e&&(d.centerOnBlock(e.id),e.select())};a.push(b)}}};
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_callnoreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_CALL_COMMON,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setHelpUrl($.module$exports$Blockly$Msg.Msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null;
|
||||
this.previousEnabledState_=!0},defType_:"procedures_defnoreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_callreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_CALL_COMMON,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setStyle("procedure_blocks");this.setHelpUrl($.module$exports$Blockly$Msg.Msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null;this.previousEnabledState_=!0},defType_:"procedures_defreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_ifreturn={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip($.module$exports$Blockly$Msg.Msg.PROCEDURES_IFRETURN_TOOLTIP);
|
||||
this.setHelpUrl($.module$exports$Blockly$Msg.Msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("value",Number(this.hasReturnValue_));return a},domToMutation:function(a){this.hasReturnValue_="1"===a.getAttribute("value");this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_RETURN))},onchange:function(a){if(!(this.workspace.isDragging&&
|
||||
this.workspace.isDragging()||a.type!==$.module$exports$Blockly$Events.BLOCK_MOVE)){var b=!1,c=this;do{if(-1!==this.FUNCTION_TYPES.indexOf(c.type)){b=!0;break}c=c.getSurroundParent()}while(c);b?("procedures_defnoreturn"===c.type&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!1):"procedures_defreturn"!==c.type||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField($.module$exports$Blockly$Msg.Msg.PROCEDURES_DEFRETURN_RETURN),
|
||||
this.hasReturnValue_=!0),this.setWarningText(null)):this.setWarningText($.module$exports$Blockly$Msg.Msg.PROCEDURES_IFRETURN_WARNING);this.isInFlyout||(c=(0,$.module$exports$Blockly$Events.getGroup)(),(0,$.module$exports$Blockly$Events.setGroup)(a.group),this.setEnabled(b),(0,$.module$exports$Blockly$Events.setGroup)(c))}},FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$procedures.blocks);var module$exports$Blockly$libraryBlocks$math={};
|
||||
module$exports$Blockly$libraryBlocks$math.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"math_number",message0:"%1",args0:[{type:"field_number",name:"NUM",value:0}],output:"Number",helpUrl:"%{BKY_MATH_NUMBER_HELPURL}",style:"math_blocks",tooltip:"%{BKY_MATH_NUMBER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"math_arithmetic",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Number"},{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ADDITION_SYMBOL}",
|
||||
QUOTE_IMAGE_WIDTH:12,QUOTE_IMAGE_HEIGHT:12,quoteField_:function(a){for(let b=0,c;c=this.inputList[b];b++)for(let d=0,e;e=c.fieldRow[d];d++)if(a===e.name){c.insertFieldAt(d,this.newQuote_(!0));c.insertFieldAt(d+2,this.newQuote_(!1));return}console.warn('field named "'+a+'" not found in '+this.toDevString())},newQuote_:function(a){a=this.RTL?!a:a;return new $.FieldImage$$module$build$src$core$field_image(a?this.QUOTE_IMAGE_LEFT_DATAURI:this.QUOTE_IMAGE_RIGHT_DATAURI,this.QUOTE_IMAGE_WIDTH,this.QUOTE_IMAGE_HEIGHT,
|
||||
a?"\u201c":"\u201d")}},module$contents$Blockly$libraryBlocks$texts_TEXT_QUOTES_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN);this.quoteField_("TEXT")},module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_MUTATOR_MIXIN={mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},
|
||||
saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){const b=a.newBlock("text_create_join_container");b.initSvg();let c=b.getInput("STACK").connection;for(let d=0;d<this.itemCount_;d++){const e=a.newBlock("text_create_join_item");e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)b.isInsertionMarker()||
|
||||
a.push(b.valueConnection_),b=b.getNextBlock();for(b=0;b<this.itemCount_;b++){const c=this.getInput("ADD"+b).connection.targetConnection;c&&-1===a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)$.Mutator$$module$build$src$core$mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");let b=0;for(;a;){if(a.isInsertionMarker()){a=a.getNextBlock();continue}const c=this.getInput("ADD"+b);a.valueConnection_=
|
||||
c&&c.connection.targetConnection;a=a.getNextBlock();b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(this.newQuote_(!0)).appendField(this.newQuote_(!1));for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){const b=this.appendValueInput("ADD"+a).setAlign($.Align$$module$build$src$core$input.RIGHT);0===a&&b.appendField(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_JOIN_TITLE_CREATEWITH)}for(a=
|
||||
this.itemCount_;this.getInput("ADD"+a);a++)this.removeInput("ADD"+a)}},module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$texts_QUOTE_IMAGE_MIXIN);this.itemCount_=2;this.updateShape_();this.setMutator(new $.Mutator$$module$build$src$core$mutator(["text_create_join_item"],this))};
|
||||
$.module$build$src$core$extensions.register("text_append_tooltip",$.module$build$src$core$extensions.buildTooltipWithFieldText("%{BKY_TEXT_APPEND_TOOLTIP}","VAR"));
|
||||
var module$contents$Blockly$libraryBlocks$texts_TEXT_INDEXOF_TOOLTIP_EXTENSION=function(){const a=this;this.setTooltip(function(){return module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_INDEXOF_TOOLTIP.replace("%1",a.workspace.options.oneBasedIndex?"0":"-1")})},module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_MUTATOR_MIXIN={mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("at",!!this.isAt_);return a},domToMutation:function(a){a=
|
||||
"false"!==a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT",!0);this.removeInput("ORDINAL",!0);a&&(this.appendValueInput("AT").setCheck("Number"),module$contents$Blockly$libraryBlocks$texts_Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(module$contents$Blockly$libraryBlocks$texts_Msg.ORDINAL_NUMBER_SUFFIX));module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHARAT_TAIL&&(this.removeInput("TAIL",!0),this.appendDummyInput("TAIL").appendField(module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHARAT_TAIL));
|
||||
this.isAt_=a}},module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_EXTENSION=function(){this.getField("WHERE").setValidator(function(b){b="FROM_START"===b||"FROM_END"===b;b!==this.isAt_&&this.getSourceBlock().updateAt_(b)});this.updateAt_(!0);const a=this;this.setTooltip(function(){var b=a.getFieldValue("WHERE");let c=module$contents$Blockly$libraryBlocks$texts_Msg.TEXT_CHARAT_TOOLTIP;("FROM_START"===b||"FROM_END"===b)&&(b="FROM_START"===b?module$contents$Blockly$libraryBlocks$texts_Msg.LISTS_INDEX_FROM_START_TOOLTIP:
|
||||
module$contents$Blockly$libraryBlocks$texts_Msg.LISTS_INDEX_FROM_END_TOOLTIP)&&(c+=" "+b.replace("%1",a.workspace.options.oneBasedIndex?"#1":"#0"));return c})};$.module$build$src$core$extensions.register("text_indexOf_tooltip",module$contents$Blockly$libraryBlocks$texts_TEXT_INDEXOF_TOOLTIP_EXTENSION);$.module$build$src$core$extensions.register("text_quotes",module$contents$Blockly$libraryBlocks$texts_TEXT_QUOTES_EXTENSION);
|
||||
$.module$build$src$core$extensions.registerMutator("text_join_mutator",module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$texts_TEXT_JOIN_EXTENSION);$.module$build$src$core$extensions.registerMutator("text_charAt_mutator",module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$texts_TEXT_CHARAT_EXTENSION);module$contents$Blockly$libraryBlocks$texts_defineBlocks(module$exports$Blockly$libraryBlocks$texts.blocks);var module$exports$Blockly$libraryBlocks$procedures={},module$contents$Blockly$libraryBlocks$procedures_ContextMenu=$.module$build$src$core$contextmenu,module$contents$Blockly$libraryBlocks$procedures_Events=$.module$build$src$core$events$events,module$contents$Blockly$libraryBlocks$procedures_Procedures=$.module$build$src$core$procedures,module$contents$Blockly$libraryBlocks$procedures_Variables=$.module$build$src$core$variables,module$contents$Blockly$libraryBlocks$procedures_Xml=$.module$build$src$core$xml,
|
||||
module$contents$Blockly$libraryBlocks$procedures_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$procedures_Align=$.Align$$module$build$src$core$input,module$contents$Blockly$libraryBlocks$procedures_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$procedures_config=$.config$$module$build$src$core$config,module$contents$Blockly$libraryBlocks$procedures_FieldCheckbox=$.FieldCheckbox$$module$build$src$core$field_checkbox,module$contents$Blockly$libraryBlocks$procedures_FieldLabel=
|
||||
$.FieldLabel$$module$build$src$core$field_label,module$contents$Blockly$libraryBlocks$procedures_FieldTextInput=$.FieldTextInput$$module$build$src$core$field_textinput,module$contents$Blockly$libraryBlocks$procedures_Msg=$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$procedures_Mutator=$.Mutator$$module$build$src$core$mutator,module$contents$Blockly$libraryBlocks$procedures_Names=$.module$build$src$core$names.Names,module$contents$Blockly$libraryBlocks$procedures_defineBlocks=
|
||||
$.module$build$src$core$common.defineBlocks;module$exports$Blockly$libraryBlocks$procedures.blocks={};
|
||||
var module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_DEF_COMMON={setStatements_:function(a){this.hasStatements_!==a&&(a?(this.appendStatementInput("STACK").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFNORETURN_DO),this.getInput("RETURN")&&this.moveInputBefore("STACK","RETURN")):this.removeInput("STACK",!0),this.hasStatements_=a)},updateParams_:function(){let a="";this.arguments_.length&&(a=module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_BEFORE_PARAMS+
|
||||
" "+this.arguments_.join(", "));$.module$build$src$core$events$events.disable();try{this.setFieldValue(a,"PARAMS")}finally{$.module$build$src$core$events$events.enable()}},mutationToDom:function(a){const b=$.module$build$src$core$utils$xml.createElement("mutation");a&&b.setAttribute("name",this.getFieldValue("NAME"));for(let c=0;c<this.argumentVarModels_.length;c++){const d=$.module$build$src$core$utils$xml.createElement("arg"),e=this.argumentVarModels_[c];d.setAttribute("name",e.name);d.setAttribute("varid",
|
||||
e.getId());a&&this.paramIds_&&d.setAttribute("paramId",this.paramIds_[c]);b.appendChild(d)}this.hasStatements_||b.setAttribute("statements","false");return b},domToMutation:function(a){this.arguments_=[];this.argumentVarModels_=[];for(let c=0,d;d=a.childNodes[c];c++)if("arg"===d.nodeName.toLowerCase()){const e=d.getAttribute("name");var b=d.getAttribute("varid")||d.getAttribute("varId");this.arguments_.push(e);b=$.module$build$src$core$variables.getOrCreateVariablePackage(this.workspace,b,e,"");null!==
|
||||
b?this.argumentVarModels_.push(b):console.log("Failed to create a variable with name "+e+", ignoring.")}this.updateParams_();$.module$build$src$core$procedures.mutateCallers(this);this.setStatements_("false"!==a.getAttribute("statements"))},saveExtraState:function(){if(!this.argumentVarModels_.length&&this.hasStatements_)return null;const a=Object.create(null);if(this.argumentVarModels_.length){a.params=[];for(let b=0;b<this.argumentVarModels_.length;b++)a.params.push({name:this.argumentVarModels_[b].name,
|
||||
id:this.argumentVarModels_[b].getId()})}this.hasStatements_||(a.hasStatements=!1);return a},loadExtraState:function(a){this.arguments_=[];this.argumentVarModels_=[];if(a.params)for(let c=0;c<a.params.length;c++){var b=a.params[c];b=$.module$build$src$core$variables.getOrCreateVariablePackage(this.workspace,b.id,b.name,"");this.arguments_.push(b.name);this.argumentVarModels_.push(b)}this.updateParams_();$.module$build$src$core$procedures.mutateCallers(this);this.setStatements_(!1===a.hasStatements?
|
||||
!1:!0)},decompose:function(a){const b=$.module$build$src$core$utils$xml.createElement("block");b.setAttribute("type","procedures_mutatorcontainer");var c=$.module$build$src$core$utils$xml.createElement("statement");c.setAttribute("name","STACK");b.appendChild(c);for(let e=0;e<this.arguments_.length;e++){const f=$.module$build$src$core$utils$xml.createElement("block");f.setAttribute("type","procedures_mutatorarg");var d=$.module$build$src$core$utils$xml.createElement("field");d.setAttribute("name",
|
||||
"NAME");const g=$.module$build$src$core$utils$xml.createTextNode(this.arguments_[e]);d.appendChild(g);f.appendChild(d);d=$.module$build$src$core$utils$xml.createElement("next");f.appendChild(d);c.appendChild(f);c=d}a=$.module$build$src$core$xml.domToBlock(b,a);"procedures_defreturn"===this.type?a.setFieldValue(this.hasStatements_,"STATEMENTS"):a.removeInput("STATEMENT_INPUT");$.module$build$src$core$procedures.mutateCallers(this);return a},compose:function(a){this.arguments_=[];this.paramIds_=[];
|
||||
this.argumentVarModels_=[];let b=a.getInputTargetBlock("STACK");for(;b&&!b.isInsertionMarker();){var c=b.getFieldValue("NAME");this.arguments_.push(c);c=this.workspace.getVariable(c,"");this.argumentVarModels_.push(c);this.paramIds_.push(b.id);b=b.nextConnection&&b.nextConnection.targetBlock()}this.updateParams_();$.module$build$src$core$procedures.mutateCallers(this);a=a.getFieldValue("STATEMENTS");if(null!==a&&(a="TRUE"===a,this.hasStatements_!==a))if(a)this.setStatements_(!0),$.Mutator$$module$build$src$core$mutator.reconnect(this.statementConnection_,
|
||||
this,"STACK"),this.statementConnection_=null;else{a=this.getInput("STACK").connection;if(this.statementConnection_=a.targetConnection)a=a.targetBlock(),a.unplug(),a.bumpNeighbours();this.setStatements_(!1)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},renameVarById:function(a,b){var c=this.workspace.getVariableById(a);if(""===c.type){c=c.name;b=this.workspace.getVariableById(b);var d=!1;for(let e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===
|
||||
a&&(this.arguments_[e]=b.name,this.argumentVarModels_[e]=b,d=!0);d&&(this.displayRenamedVar_(c,b.name),$.module$build$src$core$procedures.mutateCallers(this))}},updateVarName:function(a){const b=a.name;let c=!1,d;for(let e=0;e<this.argumentVarModels_.length;e++)this.argumentVarModels_[e].getId()===a.getId()&&(d=this.arguments_[e],this.arguments_[e]=b,c=!0);c&&(this.displayRenamedVar_(d,b),$.module$build$src$core$procedures.mutateCallers(this))},displayRenamedVar_:function(a,b){this.updateParams_();
|
||||
if(this.mutator&&this.mutator.isVisible()){const c=this.mutator.workspace_.getAllBlocks(!1);for(let d=0,e;e=c[d];d++)"procedures_mutatorarg"===e.type&&$.module$build$src$core$names.Names.equals(a,e.getFieldValue("NAME"))&&e.setFieldValue(b,"NAME")}},customContextMenu:function(a){if(!this.isInFlyout){var b={enabled:!0},c=this.getFieldValue("NAME");b.text=module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_CREATE_DO.replace("%1",c);var d=$.module$build$src$core$utils$xml.createElement("mutation");
|
||||
d.setAttribute("name",c);for(c=0;c<this.arguments_.length;c++){var e=$.module$build$src$core$utils$xml.createElement("arg");e.setAttribute("name",this.arguments_[c]);d.appendChild(e)}c=$.module$build$src$core$utils$xml.createElement("block");c.setAttribute("type",this.callType_);c.appendChild(d);b.callback=$.module$build$src$core$contextmenu.callbackFactory(this,c);a.push(b);if(!this.isCollapsed())for(b=0;b<this.argumentVarModels_.length;b++)d={enabled:!0},c=this.argumentVarModels_[b],d.text=module$contents$Blockly$libraryBlocks$procedures_Msg.VARIABLES_SET_CREATE_GET.replace("%1",
|
||||
c.name),c=$.module$build$src$core$variables.generateVariableFieldDom(c),e=$.module$build$src$core$utils$xml.createElement("block"),e.setAttribute("type","variables_get"),e.appendChild(c),d.callback=$.module$build$src$core$contextmenu.callbackFactory(this,e),a.push(d)}}};
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_defnoreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_DEF_COMMON,{init:function(){var a=$.module$build$src$core$procedures.findLegalName("",this);a=new $.FieldTextInput$$module$build$src$core$field_textinput(a,$.module$build$src$core$procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFNORETURN_TITLE).appendField(a,
|
||||
"NAME").appendField("","PARAMS");this.setMutator(new $.Mutator$$module$build$src$core$mutator(["procedures_mutatorarg"],this));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFNORETURN_COMMENT&&this.setCommentText(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFNORETURN_COMMENT);this.setStyle("procedure_blocks");this.setTooltip(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
|
||||
this.setHelpUrl(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,!1]},callType_:"procedures_callnoreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_defreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_DEF_COMMON,{init:function(){var a=$.module$build$src$core$procedures.findLegalName("",this);a=new $.FieldTextInput$$module$build$src$core$field_textinput(a,$.module$build$src$core$procedures.rename);a.setSpellcheck(!1);this.appendDummyInput().appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_TITLE).appendField(a,
|
||||
"NAME").appendField("","PARAMS");this.appendValueInput("RETURN").setAlign($.Align$$module$build$src$core$input.RIGHT).appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_RETURN);this.setMutator(new $.Mutator$$module$build$src$core$mutator(["procedures_mutatorarg"],this));(this.workspace.options.comments||this.workspace.options.parentWorkspace&&this.workspace.options.parentWorkspace.options.comments)&&module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_COMMENT&&
|
||||
this.setCommentText(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_COMMENT);this.setStyle("procedure_blocks");this.setTooltip(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_TOOLTIP);this.setHelpUrl(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.setStatements_(!0);this.statementConnection_=null},getProcedureDef:function(){return[this.getFieldValue("NAME"),this.arguments_,
|
||||
!0]},callType_:"procedures_callreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_mutatorcontainer={init:function(){this.appendDummyInput().appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_MUTATORCONTAINER_TITLE);this.appendStatementInput("STACK");this.appendDummyInput("STATEMENT_INPUT").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_ALLOW_STATEMENTS).appendField(new $.FieldCheckbox$$module$build$src$core$field_checkbox("TRUE"),"STATEMENTS");this.setStyle("procedure_blocks");
|
||||
this.setTooltip(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);this.contextMenu=!1}};
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_mutatorarg={init:function(){const a=new $.FieldTextInput$$module$build$src$core$field_textinput($.module$build$src$core$procedures.DEFAULT_ARG,this.validator_);a.oldShowEditorFn_=a.showEditor_;a.showEditor_=function(){this.createdVariables_=[];this.oldShowEditorFn_()};this.appendDummyInput().appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_MUTATORARG_TITLE).appendField(a,"NAME");this.setPreviousStatement(!0);
|
||||
this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_MUTATORARG_TOOLTIP);this.contextMenu=!1;a.onFinishEditing_=this.deleteIntermediateVars_;a.createdVariables_=[];a.onFinishEditing_("x")},validator_:function(a){var b=this.getSourceBlock();const c=$.Mutator$$module$build$src$core$mutator.findParentWs(b.workspace);a=a.replace(/[\s\xa0]+/g," ").replace(/^ | $/g,"");if(!a)return null;const d=(b.workspace.targetWorkspace||
|
||||
b.workspace).getAllBlocks(!1),e=a.toLowerCase();for(let f=0;f<d.length;f++){if(d[f].id===this.getSourceBlock().id)continue;const g=d[f].getFieldValue("NAME");if(g&&g.toLowerCase()===e)return null}if(b.isInFlyout)return a;(b=c.getVariable(a,""))&&b.name!==a&&c.renameVariableById(b.getId(),a);b||(b=c.createVariable(a,""))&&this.createdVariables_&&this.createdVariables_.push(b);return a},deleteIntermediateVars_:function(a){const b=$.Mutator$$module$build$src$core$mutator.findParentWs(this.getSourceBlock().workspace);
|
||||
if(b)for(let c=0;c<this.createdVariables_.length;c++){const d=this.createdVariables_[c];d.name!==a&&b.deleteVariableById(d.getId())}}};
|
||||
var module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_CALL_COMMON={getProcedureCall:function(){return this.getFieldValue("NAME")},renameProcedure:function(a,b){$.module$build$src$core$names.Names.equals(a,this.getProcedureCall())&&(this.setFieldValue(b,"NAME"),this.setTooltip((this.outputConnection?module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_CALLRETURN_TOOLTIP:module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_CALLNORETURN_TOOLTIP).replace("%1",b)))},setProcedureParameters_:function(a,
|
||||
b){var c=$.module$build$src$core$procedures.getDefinition(this.getProcedureCall(),this.workspace),d=c&&c.mutator&&c.mutator.isVisible();d?this.setCollapsed(!1):(this.quarkConnections_={},this.quarkIds_=null);if(a.join("\n")===this.arguments_.join("\n"))this.quarkIds_=b;else{if(b.length!==a.length)throw RangeError("paramNames and paramIds must be the same length.");this.quarkIds_||(this.quarkConnections_={},this.quarkIds_=[]);c=this.rendered;this.rendered=!1;for(let f=0;f<this.arguments_.length;f++){var e=
|
||||
this.getInput("ARG"+f);e&&(e=e.connection.targetConnection,this.quarkConnections_[this.quarkIds_[f]]=e,d&&e&&-1===b.indexOf(this.quarkIds_[f])&&(e.disconnect(),e.getSourceBlock().bumpNeighbours()))}this.arguments_=[].concat(a);this.argumentVarModels_=[];for(a=0;a<this.arguments_.length;a++)d=$.module$build$src$core$variables.getOrCreateVariablePackage(this.workspace,null,this.arguments_[a],""),this.argumentVarModels_.push(d);this.updateShape_();if(this.quarkIds_=b)for(b=0;b<this.arguments_.length;b++)a=
|
||||
this.quarkIds_[b],a in this.quarkConnections_&&($.Mutator$$module$build$src$core$mutator.reconnect(this.quarkConnections_[a],this,"ARG"+b)||delete this.quarkConnections_[a]);(this.rendered=c)&&this.render()}},updateShape_:function(){for(var a=0;a<this.arguments_.length;a++){var b=this.getField("ARGNAME"+a);if(b){$.module$build$src$core$events$events.disable();try{b.setValue(this.arguments_[a])}finally{$.module$build$src$core$events$events.enable()}}else b=new $.FieldLabel$$module$build$src$core$field_label(this.arguments_[a]),
|
||||
this.appendValueInput("ARG"+a).setAlign($.Align$$module$build$src$core$input.RIGHT).appendField(b,"ARGNAME"+a).init()}for(a=this.arguments_.length;this.getInput("ARG"+a);a++)this.removeInput("ARG"+a);if(a=this.getInput("TOPROW"))this.arguments_.length?this.getField("WITH")||(a.appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_CALL_BEFORE_PARAMS,"WITH"),a.init()):this.getField("WITH")&&a.removeField("WITH")},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");
|
||||
a.setAttribute("name",this.getProcedureCall());for(let b=0;b<this.arguments_.length;b++){const c=$.module$build$src$core$utils$xml.createElement("arg");c.setAttribute("name",this.arguments_[b]);a.appendChild(c)}return a},domToMutation:function(a){var b=a.getAttribute("name");this.renameProcedure(this.getProcedureCall(),b);b=[];const c=[];for(let d=0,e;e=a.childNodes[d];d++)"arg"===e.nodeName.toLowerCase()&&(b.push(e.getAttribute("name")),c.push(e.getAttribute("paramId")));this.setProcedureParameters_(b,
|
||||
c)},saveExtraState:function(){const a=Object.create(null);a.name=this.getProcedureCall();this.arguments_.length&&(a.params=this.arguments_);return a},loadExtraState:function(a){this.renameProcedure(this.getProcedureCall(),a.name);if(a=a.params){const b=[];b.length=a.length;b.fill(null);this.setProcedureParameters_(a,b)}},getVars:function(){return this.arguments_},getVarModels:function(){return this.argumentVarModels_},onchange:function(a){if(this.workspace&&!this.workspace.isFlyout&&a.recordUndo)if(a.type===
|
||||
$.module$build$src$core$events$events.BLOCK_CREATE&&-1!==a.ids.indexOf(this.id)){var b=this.getProcedureCall();b=$.module$build$src$core$procedures.getDefinition(b,this.workspace);!b||b.type===this.defType_&&JSON.stringify(b.getVars())===JSON.stringify(this.arguments_)||(b=null);if(!b){$.module$build$src$core$events$events.setGroup(a.group);a=$.module$build$src$core$utils$xml.createElement("xml");b=$.module$build$src$core$utils$xml.createElement("block");b.setAttribute("type",this.defType_);var c=
|
||||
this.getRelativeToSurfaceXY(),d=c.y+2*$.config$$module$build$src$core$config.snapRadius;b.setAttribute("x",c.x+$.config$$module$build$src$core$config.snapRadius*(this.RTL?-1:1));b.setAttribute("y",d);c=this.mutationToDom();b.appendChild(c);c=$.module$build$src$core$utils$xml.createElement("field");c.setAttribute("name","NAME");d=this.getProcedureCall();const e=$.module$build$src$core$procedures.findLegalName(d,this);d!==e&&this.renameProcedure(d,e);c.appendChild($.module$build$src$core$utils$xml.createTextNode(d));
|
||||
b.appendChild(c);a.appendChild(b);$.module$build$src$core$xml.domToWorkspace(a,this.workspace);$.module$build$src$core$events$events.setGroup(!1)}}else a.type===$.module$build$src$core$events$events.BLOCK_DELETE&&a.blockId!=this.id?(b=this.getProcedureCall(),$.module$build$src$core$procedures.getDefinition(b,this.workspace)||($.module$build$src$core$events$events.setGroup(a.group),this.dispose(!0),$.module$build$src$core$events$events.setGroup(!1))):a.type===$.module$build$src$core$events$events.CHANGE&&
|
||||
"disabled"===a.element&&(b=this.getProcedureCall(),(b=$.module$build$src$core$procedures.getDefinition(b,this.workspace))&&b.id===a.blockId&&((b=$.module$build$src$core$events$events.getGroup())&&console.log("Saw an existing group while responding to a definition change"),$.module$build$src$core$events$events.setGroup(a.group),a.newValue?(this.previousEnabledState_=this.isEnabled(),this.setEnabled(!1)):this.setEnabled(this.previousEnabledState_),$.module$build$src$core$events$events.setGroup(b)))},
|
||||
customContextMenu:function(a){if(this.workspace.isMovable()){var b={enabled:!0};b.text=module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_HIGHLIGHT_DEF;var c=this.getProcedureCall(),d=this.workspace;b.callback=function(){const e=$.module$build$src$core$procedures.getDefinition(c,d);e&&(d.centerOnBlock(e.id),e.select())};a.push(b)}}};
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_callnoreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_CALL_COMMON,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setHelpUrl(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_CALLNORETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};
|
||||
this.quarkIds_=null;this.previousEnabledState_=!0},defType_:"procedures_defnoreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_callreturn=Object.assign({},module$contents$Blockly$libraryBlocks$procedures_PROCEDURE_CALL_COMMON,{init:function(){this.appendDummyInput("TOPROW").appendField("","NAME");this.setOutput(!0);this.setStyle("procedure_blocks");this.setHelpUrl(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_CALLRETURN_HELPURL);this.arguments_=[];this.argumentVarModels_=[];this.quarkConnections_={};this.quarkIds_=null;this.previousEnabledState_=
|
||||
!0},defType_:"procedures_defreturn"});
|
||||
module$exports$Blockly$libraryBlocks$procedures.blocks.procedures_ifreturn={init:function(){this.appendValueInput("CONDITION").setCheck("Boolean").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.CONTROLS_IF_MSG_IF);this.appendValueInput("VALUE").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_RETURN);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setStyle("procedure_blocks");this.setTooltip(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_IFRETURN_TOOLTIP);
|
||||
this.setHelpUrl(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_IFRETURN_HELPURL);this.hasReturnValue_=!0},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("value",Number(this.hasReturnValue_));return a},domToMutation:function(a){this.hasReturnValue_="1"===a.getAttribute("value");this.hasReturnValue_||(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_RETURN))},
|
||||
onchange:function(a){if(!(this.workspace.isDragging&&this.workspace.isDragging()||a.type!==$.module$build$src$core$events$events.BLOCK_MOVE)){var b=!1,c=this;do{if(-1!==this.FUNCTION_TYPES.indexOf(c.type)){b=!0;break}c=c.getSurroundParent()}while(c);b?("procedures_defnoreturn"===c.type&&this.hasReturnValue_?(this.removeInput("VALUE"),this.appendDummyInput("VALUE").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!1):"procedures_defreturn"!==
|
||||
c.type||this.hasReturnValue_||(this.removeInput("VALUE"),this.appendValueInput("VALUE").appendField(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_DEFRETURN_RETURN),this.hasReturnValue_=!0),this.setWarningText(null)):this.setWarningText(module$contents$Blockly$libraryBlocks$procedures_Msg.PROCEDURES_IFRETURN_WARNING);this.isInFlyout||(c=$.module$build$src$core$events$events.getGroup(),$.module$build$src$core$events$events.setGroup(a.group),this.setEnabled(b),$.module$build$src$core$events$events.setGroup(c))}},
|
||||
FUNCTION_TYPES:["procedures_defnoreturn","procedures_defreturn"]};module$contents$Blockly$libraryBlocks$procedures_defineBlocks(module$exports$Blockly$libraryBlocks$procedures.blocks);var module$exports$Blockly$libraryBlocks$math={},module$contents$Blockly$libraryBlocks$math_Extensions=$.module$build$src$core$extensions,module$contents$Blockly$libraryBlocks$math_FieldDropdown=$.module$build$src$core$field_dropdown,module$contents$Blockly$libraryBlocks$math_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$math_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$math_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,
|
||||
module$contents$Blockly$libraryBlocks$math_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$math.blocks=module$contents$Blockly$libraryBlocks$math_createBlockDefinitionsFromJsonArray([{type:"math_number",message0:"%1",args0:[{type:"field_number",name:"NUM",value:0}],output:"Number",helpUrl:"%{BKY_MATH_NUMBER_HELPURL}",style:"math_blocks",tooltip:"%{BKY_MATH_NUMBER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"math_arithmetic",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Number"},{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_ADDITION_SYMBOL}",
|
||||
"ADD"],["%{BKY_MATH_SUBTRACTION_SYMBOL}","MINUS"],["%{BKY_MATH_MULTIPLICATION_SYMBOL}","MULTIPLY"],["%{BKY_MATH_DIVISION_SYMBOL}","DIVIDE"],["%{BKY_MATH_POWER_SYMBOL}","POWER"]]},{type:"input_value",name:"B",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_ARITHMETIC_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_single",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_SINGLE_OP_ROOT}","ROOT"],["%{BKY_MATH_SINGLE_OP_ABSOLUTE}",
|
||||
"ABS"],["-","NEG"],["ln","LN"],["log10","LOG10"],["e^","EXP"],["10^","POW10"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_SINGLE_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_trig",message0:"%1 %2",args0:[{type:"field_dropdown",name:"OP",options:[["%{BKY_MATH_TRIG_SIN}","SIN"],["%{BKY_MATH_TRIG_COS}","COS"],["%{BKY_MATH_TRIG_TAN}","TAN"],["%{BKY_MATH_TRIG_ASIN}","ASIN"],["%{BKY_MATH_TRIG_ACOS}","ACOS"],["%{BKY_MATH_TRIG_ATAN}",
|
||||
"ATAN"]]},{type:"input_value",name:"NUM",check:"Number"}],output:"Number",style:"math_blocks",helpUrl:"%{BKY_MATH_TRIG_HELPURL}",extensions:["math_op_tooltip"]},{type:"math_constant",message0:"%1",args0:[{type:"field_dropdown",name:"CONSTANT",options:[["\u03c0","PI"],["e","E"],["\u03c6","GOLDEN_RATIO"],["sqrt(2)","SQRT2"],["sqrt(\u00bd)","SQRT1_2"],["\u221e","INFINITY"]]}],output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_CONSTANT_TOOLTIP}",helpUrl:"%{BKY_MATH_CONSTANT_HELPURL}"},{type:"math_number_property",
|
||||
@@ -118,94 +131,99 @@ args0:[{type:"input_value",name:"FROM",check:"Number"},{type:"input_value",name:
|
||||
name:"X",check:"Number"},{type:"input_value",name:"Y",check:"Number"}],inputsInline:!0,output:"Number",style:"math_blocks",tooltip:"%{BKY_MATH_ATAN2_TOOLTIP}",helpUrl:"%{BKY_MATH_ATAN2_HELPURL}"}]);
|
||||
var module$contents$Blockly$libraryBlocks$math_TOOLTIPS_BY_OP={ADD:"%{BKY_MATH_ARITHMETIC_TOOLTIP_ADD}",MINUS:"%{BKY_MATH_ARITHMETIC_TOOLTIP_MINUS}",MULTIPLY:"%{BKY_MATH_ARITHMETIC_TOOLTIP_MULTIPLY}",DIVIDE:"%{BKY_MATH_ARITHMETIC_TOOLTIP_DIVIDE}",POWER:"%{BKY_MATH_ARITHMETIC_TOOLTIP_POWER}",ROOT:"%{BKY_MATH_SINGLE_TOOLTIP_ROOT}",ABS:"%{BKY_MATH_SINGLE_TOOLTIP_ABS}",NEG:"%{BKY_MATH_SINGLE_TOOLTIP_NEG}",LN:"%{BKY_MATH_SINGLE_TOOLTIP_LN}",LOG10:"%{BKY_MATH_SINGLE_TOOLTIP_LOG10}",EXP:"%{BKY_MATH_SINGLE_TOOLTIP_EXP}",
|
||||
POW10:"%{BKY_MATH_SINGLE_TOOLTIP_POW10}",SIN:"%{BKY_MATH_TRIG_TOOLTIP_SIN}",COS:"%{BKY_MATH_TRIG_TOOLTIP_COS}",TAN:"%{BKY_MATH_TRIG_TOOLTIP_TAN}",ASIN:"%{BKY_MATH_TRIG_TOOLTIP_ASIN}",ACOS:"%{BKY_MATH_TRIG_TOOLTIP_ACOS}",ATAN:"%{BKY_MATH_TRIG_TOOLTIP_ATAN}",SUM:"%{BKY_MATH_ONLIST_TOOLTIP_SUM}",MIN:"%{BKY_MATH_ONLIST_TOOLTIP_MIN}",MAX:"%{BKY_MATH_ONLIST_TOOLTIP_MAX}",AVERAGE:"%{BKY_MATH_ONLIST_TOOLTIP_AVERAGE}",MEDIAN:"%{BKY_MATH_ONLIST_TOOLTIP_MEDIAN}",MODE:"%{BKY_MATH_ONLIST_TOOLTIP_MODE}",STD_DEV:"%{BKY_MATH_ONLIST_TOOLTIP_STD_DEV}",
|
||||
RANDOM:"%{BKY_MATH_ONLIST_TOOLTIP_RANDOM}"};(0,$.module$exports$Blockly$Extensions.register)("math_op_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("OP",module$contents$Blockly$libraryBlocks$math_TOOLTIPS_BY_OP));
|
||||
var module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLEBY_MUTATOR_MIXIN={mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation"),b="DIVISIBLE_BY"===this.getFieldValue("PROPERTY");a.setAttribute("divisor_input",b);return a},domToMutation:function(a){a="true"===a.getAttribute("divisor_input");this.updateShape_(a)},updateShape_:function(a){var b=this.getInput("DIVISOR");a?b||this.appendValueInput("DIVISOR").setCheck("Number"):b&&this.removeInput("DIVISOR")}},
|
||||
module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLE_MUTATOR_EXTENSION=function(){this.getField("PROPERTY").setValidator(function(a){a="DIVISIBLE_BY"===a;this.getSourceBlock().updateShape_(a)})};(0,$.module$exports$Blockly$Extensions.registerMutator)("math_is_divisibleby_mutator",module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLEBY_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLE_MUTATOR_EXTENSION);
|
||||
(0,$.module$exports$Blockly$Extensions.register)("math_change_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipWithFieldText)("%{BKY_MATH_CHANGE_TOOLTIP}","VAR"));
|
||||
var module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_MIXIN={updateType_:function(a){"MODE"===a?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("op"))}},module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_EXTENSION=function(){this.getField("OP").setValidator(function(a){this.updateType_(a)}.bind(this))};
|
||||
(0,$.module$exports$Blockly$Extensions.registerMutator)("math_modes_of_list_mutator",module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_EXTENSION);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$math.blocks);var module$exports$Blockly$libraryBlocks$loops={};
|
||||
module$exports$Blockly$libraryBlocks$loops.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"controls_repeat_ext",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"input_value",name:"TIMES",check:"Number"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat",
|
||||
RANDOM:"%{BKY_MATH_ONLIST_TOOLTIP_RANDOM}"};$.module$build$src$core$extensions.register("math_op_tooltip",$.module$build$src$core$extensions.buildTooltipForDropdown("OP",module$contents$Blockly$libraryBlocks$math_TOOLTIPS_BY_OP));
|
||||
var module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLEBY_MUTATOR_MIXIN={mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation"),b="DIVISIBLE_BY"===this.getFieldValue("PROPERTY");a.setAttribute("divisor_input",b);return a},domToMutation:function(a){a="true"===a.getAttribute("divisor_input");this.updateShape_(a)},updateShape_:function(a){const b=this.getInput("DIVISOR");a?b||this.appendValueInput("DIVISOR").setCheck("Number"):b&&this.removeInput("DIVISOR")}},
|
||||
module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLE_MUTATOR_EXTENSION=function(){this.getField("PROPERTY").setValidator(function(a){a="DIVISIBLE_BY"===a;this.getSourceBlock().updateShape_(a)})};$.module$build$src$core$extensions.registerMutator("math_is_divisibleby_mutator",module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLEBY_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$math_IS_DIVISIBLE_MUTATOR_EXTENSION);
|
||||
$.module$build$src$core$extensions.register("math_change_tooltip",$.module$build$src$core$extensions.buildTooltipWithFieldText("%{BKY_MATH_CHANGE_TOOLTIP}","VAR"));
|
||||
var module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_MIXIN={updateType_:function(a){"MODE"===a?this.outputConnection.setCheck("Array"):this.outputConnection.setCheck("Number")},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("op",this.getFieldValue("OP"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("op"))}},module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_EXTENSION=function(){this.getField("OP").setValidator(function(a){this.updateType_(a)}.bind(this))};
|
||||
$.module$build$src$core$extensions.registerMutator("math_modes_of_list_mutator",module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_MIXIN,module$contents$Blockly$libraryBlocks$math_LIST_MODES_MUTATOR_EXTENSION);module$contents$Blockly$libraryBlocks$math_defineBlocks(module$exports$Blockly$libraryBlocks$math.blocks);var module$exports$Blockly$libraryBlocks$loops={},module$contents$Blockly$libraryBlocks$loops_ContextMenu=$.module$build$src$core$contextmenu,module$contents$Blockly$libraryBlocks$loops_Events=$.module$build$src$core$events$events,module$contents$Blockly$libraryBlocks$loops_Extensions=$.module$build$src$core$extensions,module$contents$Blockly$libraryBlocks$loops_Variables=$.module$build$src$core$variables,module$contents$Blockly$libraryBlocks$loops_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$loops_BlockDefinition=
|
||||
Object,module$contents$Blockly$libraryBlocks$loops_Msg=$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$loops_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$loops_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$loops.blocks=module$contents$Blockly$libraryBlocks$loops_createBlockDefinitionsFromJsonArray([{type:"controls_repeat_ext",message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"input_value",name:"TIMES",check:"Number"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_repeat",
|
||||
message0:"%{BKY_CONTROLS_REPEAT_TITLE}",args0:[{type:"field_number",name:"TIMES",value:10,min:0,precision:1}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",tooltip:"%{BKY_CONTROLS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_CONTROLS_REPEAT_HELPURL}"},{type:"controls_whileUntil",message0:"%1 %2",args0:[{type:"field_dropdown",name:"MODE",options:[["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_WHILE}","WHILE"],["%{BKY_CONTROLS_WHILEUNTIL_OPERATOR_UNTIL}",
|
||||
"UNTIL"]]},{type:"input_value",name:"BOOL",check:"Boolean"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_WHILEUNTIL_HELPURL}",extensions:["controls_whileUntil_tooltip"]},{type:"controls_for",message0:"%{BKY_CONTROLS_FOR_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",name:"FROM",check:"Number",align:"RIGHT"},{type:"input_value",name:"TO",
|
||||
check:"Number",align:"RIGHT"},{type:"input_value",name:"BY",check:"Number",align:"RIGHT"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],inputsInline:!0,previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOR_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_for_tooltip"]},{type:"controls_forEach",message0:"%{BKY_CONTROLS_FOREACH_TITLE}",args0:[{type:"field_variable",name:"VAR",variable:null},{type:"input_value",
|
||||
name:"LIST",check:"Array"}],message1:"%{BKY_CONTROLS_REPEAT_INPUT_DO} %1",args1:[{type:"input_statement",name:"DO"}],previousStatement:null,nextStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FOREACH_HELPURL}",extensions:["contextMenu_newGetVariableBlock","controls_forEach_tooltip"]},{type:"controls_flow_statements",message0:"%1",args0:[{type:"field_dropdown",name:"FLOW",options:[["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK}","BREAK"],["%{BKY_CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE}",
|
||||
"CONTINUE"]]}],previousStatement:null,style:"loop_blocks",helpUrl:"%{BKY_CONTROLS_FLOW_STATEMENTS_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_flow_tooltip","controls_flow_in_loop_check"]}]);var module$contents$Blockly$libraryBlocks$loops_WHILE_UNTIL_TOOLTIPS={WHILE:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}",UNTIL:"%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}"};
|
||||
(0,$.module$exports$Blockly$Extensions.register)("controls_whileUntil_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("MODE",module$contents$Blockly$libraryBlocks$loops_WHILE_UNTIL_TOOLTIPS));var module$contents$Blockly$libraryBlocks$loops_BREAK_CONTINUE_TOOLTIPS={BREAK:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}",CONTINUE:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}"};
|
||||
(0,$.module$exports$Blockly$Extensions.register)("controls_flow_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("FLOW",module$contents$Blockly$libraryBlocks$loops_BREAK_CONTINUE_TOOLTIPS));
|
||||
var module$contents$Blockly$libraryBlocks$loops_CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR").getVariable(),c=b.name;if(!this.isCollapsed()&&null!==c){var d={enabled:!0};d.text=$.module$exports$Blockly$Msg.Msg.VARIABLES_SET_CREATE_GET.replace("%1",c);b=(0,$.module$exports$Blockly$Variables.generateVariableFieldDom)(b);c=(0,$.module$exports$Blockly$utils$xml.createElement)("block");c.setAttribute("type","variables_get");
|
||||
c.appendChild(b);d.callback=(0,$.module$exports$Blockly$ContextMenu.callbackFactory)(this,c);a.push(d)}}}};(0,$.module$exports$Blockly$Extensions.registerMixin)("contextMenu_newGetVariableBlock",module$contents$Blockly$libraryBlocks$loops_CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN);(0,$.module$exports$Blockly$Extensions.register)("controls_for_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipWithFieldText)("%{BKY_CONTROLS_FOR_TOOLTIP}","VAR"));
|
||||
(0,$.module$exports$Blockly$Extensions.register)("controls_forEach_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipWithFieldText)("%{BKY_CONTROLS_FOREACH_TOOLTIP}","VAR"));module$exports$Blockly$libraryBlocks$loops.loopTypes=new Set(["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]);
|
||||
var module$contents$Blockly$libraryBlocks$loops_CONTROL_FLOW_IN_LOOP_CHECK_MIXIN={getSurroundLoop:function(){var a=this;do{if(module$exports$Blockly$libraryBlocks$loops.loopTypes.has(a.type))return a;a=a.getSurroundParent()}while(a);return null},onchange:function(a){if(this.workspace.isDragging&&!this.workspace.isDragging()&&a.type===$.module$exports$Blockly$Events.BLOCK_MOVE){var b=this.getSurroundLoop(this);this.setWarningText(b?null:$.module$exports$Blockly$Msg.Msg.CONTROLS_FLOW_STATEMENTS_WARNING);
|
||||
if(!this.isInFlyout){var c=(0,$.module$exports$Blockly$Events.getGroup)();(0,$.module$exports$Blockly$Events.setGroup)(a.group);this.setEnabled(b);(0,$.module$exports$Blockly$Events.setGroup)(c)}}}};(0,$.module$exports$Blockly$Extensions.registerMixin)("controls_flow_in_loop_check",module$contents$Blockly$libraryBlocks$loops_CONTROL_FLOW_IN_LOOP_CHECK_MIXIN);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$loops.blocks);var module$exports$Blockly$libraryBlocks$logic={};
|
||||
module$exports$Blockly$libraryBlocks$logic.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"logic_boolean",message0:"%1",args0:[{type:"field_dropdown",name:"BOOL",options:[["%{BKY_LOGIC_BOOLEAN_TRUE}","TRUE"],["%{BKY_LOGIC_BOOLEAN_FALSE}","FALSE"]]}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_BOOLEAN_TOOLTIP}",helpUrl:"%{BKY_LOGIC_BOOLEAN_HELPURL}"},{type:"controls_if",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",
|
||||
check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],previousStatement:null,nextStatement:null,style:"logic_blocks",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,mutator:"controls_if_mutator",extensions:["controls_if_tooltip"]},{type:"controls_ifelse",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],message2:"%{BKY_CONTROLS_IF_MSG_ELSE} %1",
|
||||
args2:[{type:"input_statement",name:"ELSE"}],previousStatement:null,nextStatement:null,style:"logic_blocks",tooltip:"%{BKYCONTROLS_IF_TOOLTIP_2}",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_if_tooltip"]},{type:"logic_compare",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A"},{type:"field_dropdown",name:"OP",options:[["=","EQ"],["\u2260","NEQ"],["\u200f<","LT"],["\u200f\u2264","LTE"],["\u200f>","GT"],["\u200f\u2265","GTE"]]},{type:"input_value",name:"B"}],
|
||||
inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_COMPARE_HELPURL}",extensions:["logic_compare","logic_op_tooltip"]},{type:"logic_operation",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Boolean"},{type:"field_dropdown",name:"OP",options:[["%{BKY_LOGIC_OPERATION_AND}","AND"],["%{BKY_LOGIC_OPERATION_OR}","OR"]]},{type:"input_value",name:"B",check:"Boolean"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_OPERATION_HELPURL}",extensions:["logic_op_tooltip"]},
|
||||
{type:"logic_negate",message0:"%{BKY_LOGIC_NEGATE_TITLE}",args0:[{type:"input_value",name:"BOOL",check:"Boolean"}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_NEGATE_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NEGATE_HELPURL}"},{type:"logic_null",message0:"%{BKY_LOGIC_NULL}",output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_NULL_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NULL_HELPURL}"},{type:"logic_ternary",message0:"%{BKY_LOGIC_TERNARY_CONDITION} %1",args0:[{type:"input_value",name:"IF",check:"Boolean"}],
|
||||
message1:"%{BKY_LOGIC_TERNARY_IF_TRUE} %1",args1:[{type:"input_value",name:"THEN"}],message2:"%{BKY_LOGIC_TERNARY_IF_FALSE} %1",args2:[{type:"input_value",name:"ELSE"}],output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_TERNARY_TOOLTIP}",helpUrl:"%{BKY_LOGIC_TERNARY_HELPURL}",extensions:["logic_ternary"]},{type:"controls_if_if",message0:"%{BKY_CONTROLS_IF_IF_TITLE_IF}",nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_IF_TOOLTIP}"},{type:"controls_if_elseif",
|
||||
message0:"%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}",previousStatement:null,nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}"},{type:"controls_if_else",message0:"%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}",previousStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]);
|
||||
var module$contents$Blockly$libraryBlocks$logic_TOOLTIPS_BY_OP={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"};
|
||||
(0,$.module$exports$Blockly$Extensions.register)("logic_op_tooltip",(0,$.module$exports$Blockly$Extensions.buildTooltipForDropdown)("OP",module$contents$Blockly$libraryBlocks$logic_TOOLTIPS_BY_OP));
|
||||
var module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_MUTATOR_MIXIN={elseifCount_:0,elseCount_:0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");this.elseifCount_&&a.setAttribute("elseif",this.elseifCount_);this.elseCount_&&a.setAttribute("else",1);return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||
|
||||
0;this.rebuildShape_()},saveExtraState:function(){if(!this.elseifCount_&&!this.elseCount_)return null;var a=Object.create(null);this.elseifCount_&&(a.elseIfCount=this.elseifCount_);this.elseCount_&&(a.hasElse=!0);return a},loadExtraState:function(a){this.elseifCount_=a.elseIfCount||0;this.elseCount_=a.hasElse?1:0;this.updateShape_()},decompose:function(a){var b=a.newBlock("controls_if_if");b.initSvg();for(var c=b.nextConnection,d=1;d<=this.elseifCount_;d++){var e=a.newBlock("controls_if_elseif");
|
||||
e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;for(var b=[null],c=[null],d=null;a&&!a.isInsertionMarker();){switch(a.type){case "controls_if_elseif":this.elseifCount_++;b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_;
|
||||
break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();for(var b=1;a;){switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b),d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":c=this.getInput("ELSE");
|
||||
a.statementConnection_=c&&c.connection.targetConnection;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.nextConnection&&a.nextConnection.targetBlock()}},rebuildShape_:function(){var a=[null],b=[null],c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(var d=1;this.getInput("IF"+d);d++){var e=this.getInput("IF"+d),f=this.getInput("DO"+d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection)}this.updateShape_();this.reconnectChildBlocks_(a,
|
||||
b,c)},updateShape_:function(){this.getInput("ELSE")&&this.removeInput("ELSE");for(var a=1;this.getInput("IF"+a);a++)this.removeInput("IF"+a),this.removeInput("DO"+a);for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").appendField($.module$exports$Blockly$Msg.Msg.CONTROLS_IF_MSG_ELSE)},
|
||||
reconnectChildBlocks_:function(a,b,c){for(var d=1;d<=this.elseifCount_;d++)$.module$exports$Blockly$Mutator.Mutator.reconnect(a[d],this,"IF"+d),$.module$exports$Blockly$Mutator.Mutator.reconnect(b[d],this,"DO"+d);$.module$exports$Blockly$Mutator.Mutator.reconnect(c,this,"ELSE")}};(0,$.module$exports$Blockly$Extensions.registerMutator)("controls_if_mutator",module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_MUTATOR_MIXIN,null,["controls_if_elseif","controls_if_else"]);
|
||||
var module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_4}else return $.module$exports$Blockly$Msg.Msg.CONTROLS_IF_TOOLTIP_1;
|
||||
return""}.bind(this))};(0,$.module$exports$Blockly$Extensions.register)("controls_if_tooltip",module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION);
|
||||
var module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A"),c=this.getInputTargetBlock("B");b&&c&&!this.workspace.connectionChecker.doTypeChecks(b.outputConnection,c.outputConnection)&&((0,$.module$exports$Blockly$Events.setGroup)(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),b=this.prevBlocks_[1],
|
||||
b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),this.bumpNeighbours(),(0,$.module$exports$Blockly$Events.setGroup)(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}},module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN)};
|
||||
(0,$.module$exports$Blockly$Extensions.register)("logic_compare",module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_EXTENSION);
|
||||
var module$contents$Blockly$libraryBlocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN={prevParentConnection_:null,onchange:function(a){var b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(var e=0;2>e;e++){var f=1===e?b:c;f&&!f.workspace.connectionChecker.doTypeChecks(f.outputConnection,d)&&((0,$.module$exports$Blockly$Events.setGroup)(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):(f.unplug(),
|
||||
f.bumpNeighbours()),(0,$.module$exports$Blockly$Events.setGroup)(!1))}this.prevParentConnection_=d}};(0,$.module$exports$Blockly$Extensions.registerMixin)("logic_ternary",module$contents$Blockly$libraryBlocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$logic.blocks);var module$exports$Blockly$libraryBlocks$lists={};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"lists_create_empty",message0:"%{BKY_LISTS_CREATE_EMPTY_TITLE}",output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_CREATE_EMPTY_HELPURL}"},{type:"lists_repeat",message0:"%{BKY_LISTS_REPEAT_TITLE}",args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_REPEAT_TOOLTIP}",
|
||||
helpUrl:"%{BKY_LISTS_REPEAT_HELPURL}"},{type:"lists_reverse",message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"},
|
||||
{type:"lists_length",message0:"%{BKY_LISTS_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]);
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with={init:function(){this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new $.module$exports$Blockly$Mutator.Mutator(["lists_create_with_item"]));this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");
|
||||
a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){var b=a.newBlock("lists_create_with_container");b.initSvg();for(var c=b.getInput("STACK").connection,d=0;d<this.itemCount_;d++){var e=a.newBlock("lists_create_with_item");e.initSvg();c.connect(e.previousConnection);
|
||||
c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b&&!b.isInsertionMarker();)a.push(b.valueConnection_),b=b.nextConnection&&b.nextConnection.targetBlock();for(b=0;b<this.itemCount_;b++){var c=this.getInput("ADD"+b).connection.targetConnection;c&&-1===a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)$.module$exports$Blockly$Mutator.Mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=
|
||||
a.getInputTargetBlock("STACK");for(var b=0;a;){var c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;a=a.nextConnection&&a.nextConnection.targetBlock();b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_EMPTY_TITLE);for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){var b=this.appendValueInput("ADD"+
|
||||
a).setAlign($.module$exports$Blockly$Input.Align.RIGHT);0===a&&b.appendField($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_INPUT_WITH)}for(a=this.itemCount_;this.getInput("ADD"+a);a++)this.removeInput("ADD"+a)}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with_container={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);this.appendStatementInput("STACK");this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);this.contextMenu=!1}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with_item={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_ITEM_TITLE);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);this.contextMenu=!1}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_indexOf={init:function(){var a=[[$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_OF_FIRST,"FIRST"],[$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_OF_LAST,"LAST"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_INDEX_OF_HELPURL);this.setStyle("list_blocks");this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("Array").appendField($.module$exports$Blockly$Msg.Msg.LISTS_INDEX_OF_INPUT_IN_LIST);this.appendValueInput("FIND").appendField(new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a),
|
||||
"END");this.setInputsInline(!0);var b=this;this.setTooltip(function(){return $.module$exports$Blockly$Msg.Msg.LISTS_INDEX_OF_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"0":"-1")})}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_getIndex={init:function(){var a=[[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_GET,"GET"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_GET_REMOVE,"GET_REMOVE"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_REMOVE,"REMOVE"]];this.WHERE_OPTIONS=[[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_FIRST,
|
||||
"FIRST"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_LAST,"LAST"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_HELPURL);this.setStyle("list_blocks");a=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a,function(c){c="REMOVE"===c;this.getSourceBlock().updateStatement_(c)});this.appendValueInput("VALUE").setCheck("Array").appendField($.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_INPUT_IN_LIST);this.appendDummyInput().appendField(a,
|
||||
"MODE").appendField("","SPACE");this.appendDummyInput("AT");$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TAIL&&this.appendDummyInput("TAIL").appendField($.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TAIL);this.setInputsInline(!0);this.setOutput(!0);this.updateAt_(!0);var b=this;this.setTooltip(function(){var c=b.getFieldValue("MODE"),d=b.getFieldValue("WHERE"),e="";switch(c+" "+d){case "GET FROM_START":case "GET FROM_END":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;
|
||||
break;case "GET FIRST":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;break;case "GET LAST":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;break;case "GET RANDOM":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;break;case "GET_REMOVE FROM_START":case "GET_REMOVE FROM_END":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;break;case "GET_REMOVE FIRST":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;
|
||||
break;case "GET_REMOVE LAST":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;break;case "GET_REMOVE RANDOM":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;break;case "REMOVE FROM_START":case "REMOVE FROM_END":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;break;case "REMOVE FIRST":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;break;case "REMOVE LAST":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;
|
||||
break;case "REMOVE RANDOM":e=$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM}if("FROM_START"===d||"FROM_END"===d)e+=" "+("FROM_START"===d?$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_FROM_START_TOOLTIP:$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return e})},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("statement",!this.outputConnection);
|
||||
var b=this.getInput("AT").type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){var b="true"===a.getAttribute("statement");this.updateStatement_(b);a="false"!==a.getAttribute("at");this.updateAt_(a)},updateStatement_:function(a){a!==!this.outputConnection&&(this.unplug(!0,!0),a?(this.setOutput(!1),this.setPreviousStatement(!0),this.setNextStatement(!0)):(this.setPreviousStatement(!1),this.setNextStatement(!1),this.setOutput(!0)))},
|
||||
updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),$.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(this.WHERE_OPTIONS,function(c){var d="FROM_START"===c||"FROM_END"===c;if(d!==a){var e=this.getSourceBlock();e.updateAt_(d);e.setFieldValue(c,
|
||||
"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE");$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_setIndex={init:function(){var a=[[$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_SET,"SET"],[$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_INSERT,"INSERT"]];this.WHERE_OPTIONS=[[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_FIRST,"FIRST"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_LAST,
|
||||
"LAST"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField($.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_INPUT_IN_LIST);this.appendDummyInput().appendField(new $.module$exports$Blockly$FieldDropdown.FieldDropdown(a),"MODE").appendField("","SPACE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField($.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_INPUT_TO);
|
||||
this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);var b=this;this.setTooltip(function(){var c=b.getFieldValue("MODE"),d=b.getFieldValue("WHERE"),e="";switch(c+" "+d){case "SET FROM_START":case "SET FROM_END":e=$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;break;case "SET FIRST":e=$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;break;case "SET LAST":e=
|
||||
$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;break;case "SET RANDOM":e=$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;break;case "INSERT FROM_START":case "INSERT FROM_END":e=$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;break;case "INSERT FIRST":e=$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;break;case "INSERT LAST":e=$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;break;case "INSERT RANDOM":e=
|
||||
$.module$exports$Blockly$Msg.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM}if("FROM_START"===d||"FROM_END"===d)e+=" "+$.module$exports$Blockly$Msg.Msg.LISTS_INDEX_FROM_START_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return e})},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation"),b=this.getInput("AT").type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a=
|
||||
"false"!==a.getAttribute("at");this.updateAt_(a)},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),$.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField($.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");var b=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(this.WHERE_OPTIONS,function(c){var d="FROM_START"===c||"FROM_END"===c;if(d!==
|
||||
a){var e=this.getSourceBlock();e.updateAt_(d);e.setFieldValue(c,"WHERE");return null}});this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&this.moveInputBefore("ORDINAL","TO");this.getInput("AT").appendField(b,"WHERE")}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_getSublist={init:function(){this.WHERE_OPTIONS_1=[[$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_END_FROM_END,
|
||||
"FROM_END"],[$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField($.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);this.appendDummyInput("AT1");this.appendDummyInput("AT2");$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_TAIL&&this.appendDummyInput("TAIL").appendField($.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_TAIL);
|
||||
this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip($.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation"),b=this.getInput("AT1").type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;a.setAttribute("at1",b);b=this.getInput("AT2").type===$.module$exports$Blockly$ConnectionType.ConnectionType.INPUT_VALUE;a.setAttribute("at2",
|
||||
b);return a},domToMutation:function(a){var b="true"===a.getAttribute("at1");a="true"===a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),$.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL"+a).appendField($.module$exports$Blockly$Msg.Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);var c=new $.module$exports$Blockly$FieldDropdown.FieldDropdown(this["WHERE_OPTIONS_"+
|
||||
a],function(d){var e="FROM_START"===d||"FROM_END"===d;if(e!==b){var f=this.getSourceBlock();f.updateAt_(a,e);f.setFieldValue(d,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1===a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&this.moveInputBefore("ORDINAL1","AT2"));$.module$exports$Blockly$Msg.Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_sort={init:function(){this.jsonInit({message0:$.module$exports$Blockly$Msg.Msg.LISTS_SORT_TITLE,args0:[{type:"field_dropdown",name:"TYPE",options:[[$.module$exports$Blockly$Msg.Msg.LISTS_SORT_TYPE_NUMERIC,"NUMERIC"],[$.module$exports$Blockly$Msg.Msg.LISTS_SORT_TYPE_TEXT,"TEXT"],[$.module$exports$Blockly$Msg.Msg.LISTS_SORT_TYPE_IGNORECASE,"IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[[$.module$exports$Blockly$Msg.Msg.LISTS_SORT_ORDER_ASCENDING,
|
||||
"1"],[$.module$exports$Blockly$Msg.Msg.LISTS_SORT_ORDER_DESCENDING,"-1"]]},{type:"input_value",name:"LIST",check:"Array"}],output:"Array",style:"list_blocks",tooltip:$.module$exports$Blockly$Msg.Msg.LISTS_SORT_TOOLTIP,helpUrl:$.module$exports$Blockly$Msg.Msg.LISTS_SORT_HELPURL})}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_split={init:function(){var a=this,b=new $.module$exports$Blockly$FieldDropdown.FieldDropdown([[$.module$exports$Blockly$Msg.Msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[$.module$exports$Blockly$Msg.Msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]],function(c){a.updateType_(c)});this.setHelpUrl($.module$exports$Blockly$Msg.Msg.LISTS_SPLIT_HELPURL);this.setStyle("list_blocks");this.appendValueInput("INPUT").setCheck("String").appendField(b,"MODE");this.appendValueInput("DELIM").setCheck("String").appendField($.module$exports$Blockly$Msg.Msg.LISTS_SPLIT_WITH_DELIMITER);
|
||||
this.setInputsInline(!0);this.setOutput(!0,"Array");this.setTooltip(function(){var c=a.getFieldValue("MODE");if("SPLIT"===c)return $.module$exports$Blockly$Msg.Msg.LISTS_SPLIT_TOOLTIP_SPLIT;if("JOIN"===c)return $.module$exports$Blockly$Msg.Msg.LISTS_SPLIT_TOOLTIP_JOIN;throw Error("Unknown mode: "+c);})},updateType_:function(a){if(this.getFieldValue("MODE")!==a){var b=this.getInput("INPUT").connection;b.setShadowDom(null);var c=b.targetBlock();c&&(b.disconnect(),c.isShadow()?c.dispose():this.bumpNeighbours())}"SPLIT"===
|
||||
a?(this.outputConnection.setCheck("Array"),this.getInput("INPUT").setCheck("String")):(this.outputConnection.setCheck("String"),this.getInput("INPUT").setCheck("Array"))},mutationToDom:function(){var a=(0,$.module$exports$Blockly$utils$xml.createElement)("mutation");a.setAttribute("mode",this.getFieldValue("MODE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("mode"))}};(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$lists.blocks);var module$exports$Blockly$libraryBlocks$colour={};
|
||||
module$exports$Blockly$libraryBlocks$colour.blocks=(0,$.module$exports$Blockly$common.createBlockDefinitionsFromJsonArray)([{type:"colour_picker",message0:"%1",args0:[{type:"field_colour",name:"COLOUR",colour:"#ff0000"}],output:"Colour",helpUrl:"%{BKY_COLOUR_PICKER_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_PICKER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"colour_random",message0:"%{BKY_COLOUR_RANDOM_TITLE}",output:"Colour",helpUrl:"%{BKY_COLOUR_RANDOM_HELPURL}",style:"colour_blocks",
|
||||
tooltip:"%{BKY_COLOUR_RANDOM_TOOLTIP}"},{type:"colour_rgb",message0:"%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3",args0:[{type:"input_value",name:"RED",check:"Number",align:"RIGHT"},{type:"input_value",name:"GREEN",check:"Number",align:"RIGHT"},{type:"input_value",name:"BLUE",check:"Number",align:"RIGHT"}],output:"Colour",helpUrl:"%{BKY_COLOUR_RGB_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_RGB_TOOLTIP}"},{type:"colour_blend",message0:"%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} %1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3",
|
||||
args0:[{type:"input_value",name:"COLOUR1",check:"Colour",align:"RIGHT"},{type:"input_value",name:"COLOUR2",check:"Colour",align:"RIGHT"},{type:"input_value",name:"RATIO",check:"Number",align:"RIGHT"}],output:"Colour",helpUrl:"%{BKY_COLOUR_BLEND_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_BLEND_TOOLTIP}"}]);(0,$.module$exports$Blockly$common.defineBlocks)(module$exports$Blockly$libraryBlocks$colour.blocks);$.Blockly.libraryBlocks={};$.Blockly.libraryBlocks.colour=module$exports$Blockly$libraryBlocks$colour;$.Blockly.libraryBlocks.lists=module$exports$Blockly$libraryBlocks$lists;$.Blockly.libraryBlocks.logic=module$exports$Blockly$libraryBlocks$logic;$.Blockly.libraryBlocks.loops=module$exports$Blockly$libraryBlocks$loops;$.Blockly.libraryBlocks.math=module$exports$Blockly$libraryBlocks$math;$.Blockly.libraryBlocks.procedures=module$exports$Blockly$libraryBlocks$procedures;
|
||||
$.Blockly.libraryBlocks.texts=module$exports$Blockly$libraryBlocks$texts;$.Blockly.libraryBlocks.variables=module$exports$Blockly$libraryBlocks$variables;$.Blockly.libraryBlocks.variablesDynamic=module$exports$Blockly$libraryBlocks$variablesDynamic;
|
||||
var module$contents$Blockly$libraryBlocks_blocks=Object.assign({},module$exports$Blockly$libraryBlocks$colour.blocks,module$exports$Blockly$libraryBlocks$lists.blocks,module$exports$Blockly$libraryBlocks$logic.blocks,module$exports$Blockly$libraryBlocks$loops.blocks,module$exports$Blockly$libraryBlocks$math.blocks,module$exports$Blockly$libraryBlocks$procedures.blocks,module$exports$Blockly$libraryBlocks$variables.blocks,module$exports$Blockly$libraryBlocks$variablesDynamic.blocks);
|
||||
$.Blockly.libraryBlocks.blocks=module$contents$Blockly$libraryBlocks_blocks;
|
||||
$.Blockly.libraryBlocks.__namespace__=$;
|
||||
return $.Blockly.libraryBlocks;
|
||||
$.module$build$src$core$extensions.register("controls_whileUntil_tooltip",$.module$build$src$core$extensions.buildTooltipForDropdown("MODE",module$contents$Blockly$libraryBlocks$loops_WHILE_UNTIL_TOOLTIPS));var module$contents$Blockly$libraryBlocks$loops_BREAK_CONTINUE_TOOLTIPS={BREAK:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}",CONTINUE:"%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE}"};
|
||||
$.module$build$src$core$extensions.register("controls_flow_tooltip",$.module$build$src$core$extensions.buildTooltipForDropdown("FLOW",module$contents$Blockly$libraryBlocks$loops_BREAK_CONTINUE_TOOLTIPS));
|
||||
var module$contents$Blockly$libraryBlocks$loops_CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN={customContextMenu:function(a){if(!this.isInFlyout){var b=this.getField("VAR").getVariable(),c=b.name;if(!this.isCollapsed()&&null!==c){const d={enabled:!0};d.text=module$contents$Blockly$libraryBlocks$loops_Msg.VARIABLES_SET_CREATE_GET.replace("%1",c);b=$.module$build$src$core$variables.generateVariableFieldDom(b);c=$.module$build$src$core$utils$xml.createElement("block");c.setAttribute("type","variables_get");
|
||||
c.appendChild(b);d.callback=$.module$build$src$core$contextmenu.callbackFactory(this,c);a.push(d)}}}};$.module$build$src$core$extensions.registerMixin("contextMenu_newGetVariableBlock",module$contents$Blockly$libraryBlocks$loops_CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN);$.module$build$src$core$extensions.register("controls_for_tooltip",$.module$build$src$core$extensions.buildTooltipWithFieldText("%{BKY_CONTROLS_FOR_TOOLTIP}","VAR"));
|
||||
$.module$build$src$core$extensions.register("controls_forEach_tooltip",$.module$build$src$core$extensions.buildTooltipWithFieldText("%{BKY_CONTROLS_FOREACH_TOOLTIP}","VAR"));module$exports$Blockly$libraryBlocks$loops.loopTypes=new Set(["controls_repeat","controls_repeat_ext","controls_forEach","controls_for","controls_whileUntil"]);
|
||||
var module$contents$Blockly$libraryBlocks$loops_CONTROL_FLOW_IN_LOOP_CHECK_MIXIN={getSurroundLoop:function(){let a=this;do{if(module$exports$Blockly$libraryBlocks$loops.loopTypes.has(a.type))return a;a=a.getSurroundParent()}while(a);return null},onchange:function(a){if(this.workspace.isDragging&&!this.workspace.isDragging()&&a.type===$.module$build$src$core$events$events.BLOCK_MOVE){var b=this.getSurroundLoop(this);this.setWarningText(b?null:module$contents$Blockly$libraryBlocks$loops_Msg.CONTROLS_FLOW_STATEMENTS_WARNING);
|
||||
if(!this.isInFlyout){const c=$.module$build$src$core$events$events.getGroup();$.module$build$src$core$events$events.setGroup(a.group);this.setEnabled(b);$.module$build$src$core$events$events.setGroup(c)}}}};$.module$build$src$core$extensions.registerMixin("controls_flow_in_loop_check",module$contents$Blockly$libraryBlocks$loops_CONTROL_FLOW_IN_LOOP_CHECK_MIXIN);module$contents$Blockly$libraryBlocks$loops_defineBlocks(module$exports$Blockly$libraryBlocks$loops.blocks);var module$exports$Blockly$libraryBlocks$logic={},module$contents$Blockly$libraryBlocks$logic_Events=$.module$build$src$core$events$events,module$contents$Blockly$libraryBlocks$logic_Extensions=$.module$build$src$core$extensions,module$contents$Blockly$libraryBlocks$logic_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$logic_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$logic_Msg=$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$logic_Mutator=
|
||||
$.Mutator$$module$build$src$core$mutator,module$contents$Blockly$libraryBlocks$logic_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$logic_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$logic.blocks=module$contents$Blockly$libraryBlocks$logic_createBlockDefinitionsFromJsonArray([{type:"logic_boolean",message0:"%1",args0:[{type:"field_dropdown",name:"BOOL",options:[["%{BKY_LOGIC_BOOLEAN_TRUE}","TRUE"],["%{BKY_LOGIC_BOOLEAN_FALSE}","FALSE"]]}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_BOOLEAN_TOOLTIP}",helpUrl:"%{BKY_LOGIC_BOOLEAN_HELPURL}"},{type:"controls_if",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",
|
||||
name:"IF0",check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],previousStatement:null,nextStatement:null,style:"logic_blocks",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,mutator:"controls_if_mutator",extensions:["controls_if_tooltip"]},{type:"controls_ifelse",message0:"%{BKY_CONTROLS_IF_MSG_IF} %1",args0:[{type:"input_value",name:"IF0",check:"Boolean"}],message1:"%{BKY_CONTROLS_IF_MSG_THEN} %1",args1:[{type:"input_statement",name:"DO0"}],
|
||||
message2:"%{BKY_CONTROLS_IF_MSG_ELSE} %1",args2:[{type:"input_statement",name:"ELSE"}],previousStatement:null,nextStatement:null,style:"logic_blocks",tooltip:"%{BKYCONTROLS_IF_TOOLTIP_2}",helpUrl:"%{BKY_CONTROLS_IF_HELPURL}",suppressPrefixSuffix:!0,extensions:["controls_if_tooltip"]},{type:"logic_compare",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A"},{type:"field_dropdown",name:"OP",options:[["=","EQ"],["\u2260","NEQ"],["\u200f<","LT"],["\u200f\u2264","LTE"],["\u200f>","GT"],["\u200f\u2265",
|
||||
"GTE"]]},{type:"input_value",name:"B"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_COMPARE_HELPURL}",extensions:["logic_compare","logic_op_tooltip"]},{type:"logic_operation",message0:"%1 %2 %3",args0:[{type:"input_value",name:"A",check:"Boolean"},{type:"field_dropdown",name:"OP",options:[["%{BKY_LOGIC_OPERATION_AND}","AND"],["%{BKY_LOGIC_OPERATION_OR}","OR"]]},{type:"input_value",name:"B",check:"Boolean"}],inputsInline:!0,output:"Boolean",style:"logic_blocks",helpUrl:"%{BKY_LOGIC_OPERATION_HELPURL}",
|
||||
extensions:["logic_op_tooltip"]},{type:"logic_negate",message0:"%{BKY_LOGIC_NEGATE_TITLE}",args0:[{type:"input_value",name:"BOOL",check:"Boolean"}],output:"Boolean",style:"logic_blocks",tooltip:"%{BKY_LOGIC_NEGATE_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NEGATE_HELPURL}"},{type:"logic_null",message0:"%{BKY_LOGIC_NULL}",output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_NULL_TOOLTIP}",helpUrl:"%{BKY_LOGIC_NULL_HELPURL}"},{type:"logic_ternary",message0:"%{BKY_LOGIC_TERNARY_CONDITION} %1",args0:[{type:"input_value",
|
||||
name:"IF",check:"Boolean"}],message1:"%{BKY_LOGIC_TERNARY_IF_TRUE} %1",args1:[{type:"input_value",name:"THEN"}],message2:"%{BKY_LOGIC_TERNARY_IF_FALSE} %1",args2:[{type:"input_value",name:"ELSE"}],output:null,style:"logic_blocks",tooltip:"%{BKY_LOGIC_TERNARY_TOOLTIP}",helpUrl:"%{BKY_LOGIC_TERNARY_HELPURL}",extensions:["logic_ternary"]},{type:"controls_if_if",message0:"%{BKY_CONTROLS_IF_IF_TITLE_IF}",nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_IF_TOOLTIP}"},
|
||||
{type:"controls_if_elseif",message0:"%{BKY_CONTROLS_IF_ELSEIF_TITLE_ELSEIF}",previousStatement:null,nextStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSEIF_TOOLTIP}"},{type:"controls_if_else",message0:"%{BKY_CONTROLS_IF_ELSE_TITLE_ELSE}",previousStatement:null,enableContextMenu:!1,style:"logic_blocks",tooltip:"%{BKY_CONTROLS_IF_ELSE_TOOLTIP}"}]);
|
||||
var module$contents$Blockly$libraryBlocks$logic_TOOLTIPS_BY_OP={EQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_EQ}",NEQ:"%{BKY_LOGIC_COMPARE_TOOLTIP_NEQ}",LT:"%{BKY_LOGIC_COMPARE_TOOLTIP_LT}",LTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_LTE}",GT:"%{BKY_LOGIC_COMPARE_TOOLTIP_GT}",GTE:"%{BKY_LOGIC_COMPARE_TOOLTIP_GTE}",AND:"%{BKY_LOGIC_OPERATION_TOOLTIP_AND}",OR:"%{BKY_LOGIC_OPERATION_TOOLTIP_OR}"};$.module$build$src$core$extensions.register("logic_op_tooltip",$.module$build$src$core$extensions.buildTooltipForDropdown("OP",module$contents$Blockly$libraryBlocks$logic_TOOLTIPS_BY_OP));
|
||||
var module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_MUTATOR_MIXIN={elseifCount_:0,elseCount_:0,mutationToDom:function(){if(!this.elseifCount_&&!this.elseCount_)return null;const a=$.module$build$src$core$utils$xml.createElement("mutation");this.elseifCount_&&a.setAttribute("elseif",this.elseifCount_);this.elseCount_&&a.setAttribute("else",1);return a},domToMutation:function(a){this.elseifCount_=parseInt(a.getAttribute("elseif"),10)||0;this.elseCount_=parseInt(a.getAttribute("else"),10)||0;
|
||||
this.rebuildShape_()},saveExtraState:function(){if(!this.elseifCount_&&!this.elseCount_)return null;const a=Object.create(null);this.elseifCount_&&(a.elseIfCount=this.elseifCount_);this.elseCount_&&(a.hasElse=!0);return a},loadExtraState:function(a){this.elseifCount_=a.elseIfCount||0;this.elseCount_=a.hasElse?1:0;this.updateShape_()},decompose:function(a){const b=a.newBlock("controls_if_if");b.initSvg();let c=b.nextConnection;for(let d=1;d<=this.elseifCount_;d++){const e=a.newBlock("controls_if_elseif");
|
||||
e.initSvg();c.connect(e.previousConnection);c=e.nextConnection}this.elseCount_&&(a=a.newBlock("controls_if_else"),a.initSvg(),c.connect(a.previousConnection));return b},compose:function(a){a=a.nextConnection.targetBlock();this.elseCount_=this.elseifCount_=0;const b=[null],c=[null];let d=null;for(;a;){if(!a.isInsertionMarker())switch(a.type){case "controls_if_elseif":this.elseifCount_++;b.push(a.valueConnection_);c.push(a.statementConnection_);break;case "controls_if_else":this.elseCount_++;d=a.statementConnection_;
|
||||
break;default:throw TypeError("Unknown block type: "+a.type);}a=a.getNextBlock()}this.updateShape_();this.reconnectChildBlocks_(b,c,d)},saveConnections:function(a){a=a.nextConnection.targetBlock();let b=1;for(;a;){if(!a.isInsertionMarker())switch(a.type){case "controls_if_elseif":var c=this.getInput("IF"+b);const d=this.getInput("DO"+b);a.valueConnection_=c&&c.connection.targetConnection;a.statementConnection_=d&&d.connection.targetConnection;b++;break;case "controls_if_else":c=this.getInput("ELSE");
|
||||
a.statementConnection_=c&&c.connection.targetConnection;break;default:throw TypeError("Unknown block type: "+a.type);}a=a.getNextBlock()}},rebuildShape_:function(){const a=[null],b=[null];let c=null;this.getInput("ELSE")&&(c=this.getInput("ELSE").connection.targetConnection);for(let d=1;this.getInput("IF"+d);d++){const e=this.getInput("IF"+d),f=this.getInput("DO"+d);a.push(e.connection.targetConnection);b.push(f.connection.targetConnection)}this.updateShape_();this.reconnectChildBlocks_(a,b,c)},updateShape_:function(){this.getInput("ELSE")&&
|
||||
this.removeInput("ELSE");for(var a=1;this.getInput("IF"+a);a++)this.removeInput("IF"+a),this.removeInput("DO"+a);for(a=1;a<=this.elseifCount_;a++)this.appendValueInput("IF"+a).setCheck("Boolean").appendField(module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_MSG_ELSEIF),this.appendStatementInput("DO"+a).appendField(module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_MSG_THEN);this.elseCount_&&this.appendStatementInput("ELSE").appendField(module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_MSG_ELSE)},
|
||||
reconnectChildBlocks_:function(a,b,c){for(let d=1;d<=this.elseifCount_;d++)$.Mutator$$module$build$src$core$mutator.reconnect(a[d],this,"IF"+d),$.Mutator$$module$build$src$core$mutator.reconnect(b[d],this,"DO"+d);$.Mutator$$module$build$src$core$mutator.reconnect(c,this,"ELSE")}};$.module$build$src$core$extensions.registerMutator("controls_if_mutator",module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_MUTATOR_MIXIN,null,["controls_if_elseif","controls_if_else"]);
|
||||
var module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION=function(){this.setTooltip(function(){if(this.elseifCount_||this.elseCount_){if(!this.elseifCount_&&this.elseCount_)return module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_TOOLTIP_2;if(this.elseifCount_&&!this.elseCount_)return module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_TOOLTIP_3;if(this.elseifCount_&&this.elseCount_)return module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_TOOLTIP_4}else return module$contents$Blockly$libraryBlocks$logic_Msg.CONTROLS_IF_TOOLTIP_1;
|
||||
return""}.bind(this))};$.module$build$src$core$extensions.register("controls_if_tooltip",module$contents$Blockly$libraryBlocks$logic_CONTROLS_IF_TOOLTIP_EXTENSION);
|
||||
var module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN={onchange:function(a){this.prevBlocks_||(this.prevBlocks_=[null,null]);var b=this.getInputTargetBlock("A");const c=this.getInputTargetBlock("B");b&&c&&!this.workspace.connectionChecker.doTypeChecks(b.outputConnection,c.outputConnection)&&($.module$build$src$core$events$events.setGroup(a.group),a=this.prevBlocks_[0],a!==b&&(b.unplug(),!a||a.isDisposed()||a.isShadow()||this.getInput("A").connection.connect(a.outputConnection)),
|
||||
b=this.prevBlocks_[1],b!==c&&(c.unplug(),!b||b.isDisposed()||b.isShadow()||this.getInput("B").connection.connect(b.outputConnection)),this.bumpNeighbours(),$.module$build$src$core$events$events.setGroup(!1));this.prevBlocks_[0]=this.getInputTargetBlock("A");this.prevBlocks_[1]=this.getInputTargetBlock("B")}},module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_EXTENSION=function(){this.mixin(module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_ONCHANGE_MIXIN)};
|
||||
$.module$build$src$core$extensions.register("logic_compare",module$contents$Blockly$libraryBlocks$logic_LOGIC_COMPARE_EXTENSION);
|
||||
var module$contents$Blockly$libraryBlocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN={prevParentConnection_:null,onchange:function(a){const b=this.getInputTargetBlock("THEN"),c=this.getInputTargetBlock("ELSE"),d=this.outputConnection.targetConnection;if((b||c)&&d)for(let e=0;2>e;e++){const f=1===e?b:c;f&&!f.workspace.connectionChecker.doTypeChecks(f.outputConnection,d)&&($.module$build$src$core$events$events.setGroup(a.group),d===this.prevParentConnection_?(this.unplug(),d.getSourceBlock().bumpNeighbours()):
|
||||
(f.unplug(),f.bumpNeighbours()),$.module$build$src$core$events$events.setGroup(!1))}this.prevParentConnection_=d}};$.module$build$src$core$extensions.registerMixin("logic_ternary",module$contents$Blockly$libraryBlocks$logic_LOGIC_TERNARY_ONCHANGE_MIXIN);module$contents$Blockly$libraryBlocks$logic_defineBlocks(module$exports$Blockly$libraryBlocks$logic.blocks);var module$exports$Blockly$libraryBlocks$lists={},module$contents$Blockly$libraryBlocks$lists_xmlUtils=$.module$build$src$core$utils$xml,module$contents$Blockly$libraryBlocks$lists_Xml=$.module$build$src$core$xml,module$contents$Blockly$libraryBlocks$lists_Align=$.Align$$module$build$src$core$input,module$contents$Blockly$libraryBlocks$lists_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$lists_ConnectionType=$.module$build$src$core$connection_type.ConnectionType,module$contents$Blockly$libraryBlocks$lists_FieldDropdown=
|
||||
$.module$build$src$core$field_dropdown.FieldDropdown,module$contents$Blockly$libraryBlocks$lists_Msg=$.module$build$src$core$msg.Msg,module$contents$Blockly$libraryBlocks$lists_Mutator=$.Mutator$$module$build$src$core$mutator,module$contents$Blockly$libraryBlocks$lists_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$lists_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks=module$contents$Blockly$libraryBlocks$lists_createBlockDefinitionsFromJsonArray([{type:"lists_create_empty",message0:"%{BKY_LISTS_CREATE_EMPTY_TITLE}",output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_CREATE_EMPTY_TOOLTIP}",helpUrl:"%{BKY_LISTS_CREATE_EMPTY_HELPURL}"},{type:"lists_repeat",message0:"%{BKY_LISTS_REPEAT_TITLE}",args0:[{type:"input_value",name:"ITEM"},{type:"input_value",name:"NUM",check:"Number"}],output:"Array",style:"list_blocks",
|
||||
tooltip:"%{BKY_LISTS_REPEAT_TOOLTIP}",helpUrl:"%{BKY_LISTS_REPEAT_HELPURL}"},{type:"lists_reverse",message0:"%{BKY_LISTS_REVERSE_MESSAGE0}",args0:[{type:"input_value",name:"LIST",check:"Array"}],output:"Array",inputsInline:!0,style:"list_blocks",tooltip:"%{BKY_LISTS_REVERSE_TOOLTIP}",helpUrl:"%{BKY_LISTS_REVERSE_HELPURL}"},{type:"lists_isEmpty",message0:"%{BKY_LISTS_ISEMPTY_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Boolean",style:"list_blocks",tooltip:"%{BKY_LISTS_ISEMPTY_TOOLTIP}",
|
||||
helpUrl:"%{BKY_LISTS_ISEMPTY_HELPURL}"},{type:"lists_length",message0:"%{BKY_LISTS_LENGTH_TITLE}",args0:[{type:"input_value",name:"VALUE",check:["String","Array"]}],output:"Number",style:"list_blocks",tooltip:"%{BKY_LISTS_LENGTH_TOOLTIP}",helpUrl:"%{BKY_LISTS_LENGTH_HELPURL}"}]);
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with={init:function(){this.setHelpUrl(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_HELPURL);this.setStyle("list_blocks");this.itemCount_=3;this.updateShape_();this.setOutput(!0,"Array");this.setMutator(new $.Mutator$$module$build$src$core$mutator(["lists_create_with_item"],this));this.setTooltip(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_TOOLTIP)},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");
|
||||
a.setAttribute("items",this.itemCount_);return a},domToMutation:function(a){this.itemCount_=parseInt(a.getAttribute("items"),10);this.updateShape_()},saveExtraState:function(){return{itemCount:this.itemCount_}},loadExtraState:function(a){this.itemCount_=a.itemCount;this.updateShape_()},decompose:function(a){const b=a.newBlock("lists_create_with_container");b.initSvg();let c=b.getInput("STACK").connection;for(let d=0;d<this.itemCount_;d++){const e=a.newBlock("lists_create_with_item");e.initSvg();c.connect(e.previousConnection);
|
||||
c=e.nextConnection}return b},compose:function(a){var b=a.getInputTargetBlock("STACK");for(a=[];b;)b.isInsertionMarker()||a.push(b.valueConnection_),b=b.getNextBlock();for(b=0;b<this.itemCount_;b++){const c=this.getInput("ADD"+b).connection.targetConnection;c&&-1===a.indexOf(c)&&c.disconnect()}this.itemCount_=a.length;this.updateShape_();for(b=0;b<this.itemCount_;b++)$.Mutator$$module$build$src$core$mutator.reconnect(a[b],this,"ADD"+b)},saveConnections:function(a){a=a.getInputTargetBlock("STACK");
|
||||
let b=0;for(;a;){if(a.isInsertionMarker()){a=a.getNextBlock();continue}const c=this.getInput("ADD"+b);a.valueConnection_=c&&c.connection.targetConnection;a=a.getNextBlock();b++}},updateShape_:function(){this.itemCount_&&this.getInput("EMPTY")?this.removeInput("EMPTY"):this.itemCount_||this.getInput("EMPTY")||this.appendDummyInput("EMPTY").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_EMPTY_TITLE);for(var a=0;a<this.itemCount_;a++)if(!this.getInput("ADD"+a)){const b=this.appendValueInput("ADD"+
|
||||
a).setAlign($.Align$$module$build$src$core$input.RIGHT);0===a&&b.appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_INPUT_WITH)}for(a=this.itemCount_;this.getInput("ADD"+a);a++)this.removeInput("ADD"+a)}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with_container={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);this.appendStatementInput("STACK");this.setTooltip(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);this.contextMenu=!1}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_create_with_item={init:function(){this.setStyle("list_blocks");this.appendDummyInput().appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_ITEM_TITLE);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);this.contextMenu=!1}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_indexOf={init:function(){const a=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_OF_FIRST,"FIRST"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_OF_LAST,"LAST"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_OF_HELPURL);this.setStyle("list_blocks");this.setOutput(!0,"Number");this.appendValueInput("VALUE").setCheck("Array").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_OF_INPUT_IN_LIST);
|
||||
this.appendValueInput("FIND").appendField(new module$contents$Blockly$libraryBlocks$lists_FieldDropdown(a),"END");this.setInputsInline(!0);const b=this;this.setTooltip(function(){return module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_OF_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?"0":"-1")})}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_getIndex={init:function(){var a=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_GET,"GET"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_GET_REMOVE,"GET_REMOVE"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_REMOVE,"REMOVE"]];this.WHERE_OPTIONS=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_FROM_END,
|
||||
"FROM_END"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_FIRST,"FIRST"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_LAST,"LAST"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_HELPURL);this.setStyle("list_blocks");a=new module$contents$Blockly$libraryBlocks$lists_FieldDropdown(a,function(c){c="REMOVE"===c;this.getSourceBlock().updateStatement_(c)});
|
||||
this.appendValueInput("VALUE").setCheck("Array").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_INPUT_IN_LIST);this.appendDummyInput().appendField(a,"MODE").appendField("","SPACE");this.appendDummyInput("AT");module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TAIL&&this.appendDummyInput("TAIL").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TAIL);this.setInputsInline(!0);this.setOutput(!0);this.updateAt_(!0);const b=this;this.setTooltip(function(){const c=
|
||||
b.getFieldValue("MODE"),d=b.getFieldValue("WHERE");let e="";switch(c+" "+d){case "GET FROM_START":case "GET FROM_END":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;break;case "GET FIRST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;break;case "GET LAST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;break;case "GET RANDOM":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;
|
||||
break;case "GET_REMOVE FROM_START":case "GET_REMOVE FROM_END":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;break;case "GET_REMOVE FIRST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;break;case "GET_REMOVE LAST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;break;case "GET_REMOVE RANDOM":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;
|
||||
break;case "REMOVE FROM_START":case "REMOVE FROM_END":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;break;case "REMOVE FIRST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;break;case "REMOVE LAST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;break;case "REMOVE RANDOM":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM}if("FROM_START"===d||"FROM_END"===
|
||||
d)e+=" "+("FROM_START"===d?module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_FROM_START_TOOLTIP:module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_FROM_END_TOOLTIP).replace("%1",b.workspace.options.oneBasedIndex?"#1":"#0");return e})},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("statement",!this.outputConnection);const b=this.getInput("AT").type===$.module$build$src$core$connection_type.ConnectionType.INPUT_VALUE;
|
||||
a.setAttribute("at",b);return a},domToMutation:function(a){const b="true"===a.getAttribute("statement");this.updateStatement_(b);a="false"!==a.getAttribute("at");this.updateAt_(a)},saveExtraState:function(){return this.outputConnection?null:{isStatement:!0}},loadExtraState:function(a){a.isStatement?this.updateStatement_(!0):"string"===typeof a&&this.domToMutation($.module$build$src$core$xml.textToDom(a))},updateStatement_:function(a){a!==!this.outputConnection&&(this.unplug(!0,!0),a?(this.setOutput(!1),
|
||||
this.setPreviousStatement(!0),this.setNextStatement(!0)):(this.setPreviousStatement(!1),this.setNextStatement(!1),this.setOutput(!0)))},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),module$contents$Blockly$libraryBlocks$lists_Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");const b=new module$contents$Blockly$libraryBlocks$lists_FieldDropdown(this.WHERE_OPTIONS,
|
||||
function(c){const d="FROM_START"===c||"FROM_END"===c;if(d!==a){const e=this.getSourceBlock();e.updateAt_(d);e.setFieldValue(c,"WHERE");return null}});this.getInput("AT").appendField(b,"WHERE");module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_TAIL&&this.moveInputBefore("TAIL",null)}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_setIndex={init:function(){const a=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_SET,"SET"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_INSERT,"INSERT"]];this.WHERE_OPTIONS=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_FROM_START,"FROM_START"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_FROM_END,"FROM_END"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_FIRST,
|
||||
"FIRST"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_LAST,"LAST"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_INDEX_RANDOM,"RANDOM"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_INPUT_IN_LIST);this.appendDummyInput().appendField(new module$contents$Blockly$libraryBlocks$lists_FieldDropdown(a),
|
||||
"MODE").appendField("","SPACE");this.appendDummyInput("AT");this.appendValueInput("TO").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_INPUT_TO);this.setInputsInline(!0);this.setPreviousStatement(!0);this.setNextStatement(!0);this.setTooltip(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP);this.updateAt_(!0);const b=this;this.setTooltip(function(){const c=b.getFieldValue("MODE"),d=b.getFieldValue("WHERE");let e="";switch(c+" "+d){case "SET FROM_START":case "SET FROM_END":e=
|
||||
module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;break;case "SET FIRST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;break;case "SET LAST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;break;case "SET RANDOM":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;break;case "INSERT FROM_START":case "INSERT FROM_END":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;
|
||||
break;case "INSERT FIRST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;break;case "INSERT LAST":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;break;case "INSERT RANDOM":e=module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM}if("FROM_START"===d||"FROM_END"===d)e+=" "+module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_INDEX_FROM_START_TOOLTIP.replace("%1",b.workspace.options.oneBasedIndex?
|
||||
"#1":"#0");return e})},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation"),b=this.getInput("AT").type===$.module$build$src$core$connection_type.ConnectionType.INPUT_VALUE;a.setAttribute("at",b);return a},domToMutation:function(a){a="false"!==a.getAttribute("at");this.updateAt_(a)},saveExtraState:function(){return null},loadExtraState:function(){},updateAt_:function(a){this.removeInput("AT");this.removeInput("ORDINAL",!0);a?(this.appendValueInput("AT").setCheck("Number"),
|
||||
module$contents$Blockly$libraryBlocks$lists_Msg.ORDINAL_NUMBER_SUFFIX&&this.appendDummyInput("ORDINAL").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT");const b=new module$contents$Blockly$libraryBlocks$lists_FieldDropdown(this.WHERE_OPTIONS,function(c){const d="FROM_START"===c||"FROM_END"===c;if(d!==a){const e=this.getSourceBlock();e.updateAt_(d);e.setFieldValue(c,"WHERE");return null}});this.moveInputBefore("AT","TO");this.getInput("ORDINAL")&&
|
||||
this.moveInputBefore("ORDINAL","TO");this.getInput("AT").appendField(b,"WHERE")}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_getSublist={init:function(){this.WHERE_OPTIONS_1=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_START_FROM_START,"FROM_START"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_START_FROM_END,"FROM_END"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_START_FIRST,"FIRST"]];this.WHERE_OPTIONS_2=[[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_END_FROM_START,"FROM_START"],
|
||||
[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_END_FROM_END,"FROM_END"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_END_LAST,"LAST"]];this.setHelpUrl(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_HELPURL);this.setStyle("list_blocks");this.appendValueInput("LIST").setCheck("Array").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);this.appendDummyInput("AT1");this.appendDummyInput("AT2");module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_TAIL&&
|
||||
this.appendDummyInput("TAIL").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_TAIL);this.setInputsInline(!0);this.setOutput(!0,"Array");this.updateAt_(1,!0);this.updateAt_(2,!0);this.setTooltip(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_TOOLTIP)},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");var b=this.getInput("AT1").type===$.module$build$src$core$connection_type.ConnectionType.INPUT_VALUE;a.setAttribute("at1",
|
||||
b);b=this.getInput("AT2").type===$.module$build$src$core$connection_type.ConnectionType.INPUT_VALUE;a.setAttribute("at2",b);return a},domToMutation:function(a){const b="true"===a.getAttribute("at1");a="true"===a.getAttribute("at2");this.updateAt_(1,b);this.updateAt_(2,a)},saveExtraState:function(){return null},loadExtraState:function(){},updateAt_:function(a,b){this.removeInput("AT"+a);this.removeInput("ORDINAL"+a,!0);b?(this.appendValueInput("AT"+a).setCheck("Number"),module$contents$Blockly$libraryBlocks$lists_Msg.ORDINAL_NUMBER_SUFFIX&&
|
||||
this.appendDummyInput("ORDINAL"+a).appendField(module$contents$Blockly$libraryBlocks$lists_Msg.ORDINAL_NUMBER_SUFFIX)):this.appendDummyInput("AT"+a);const c=new module$contents$Blockly$libraryBlocks$lists_FieldDropdown(this["WHERE_OPTIONS_"+a],function(d){const e="FROM_START"===d||"FROM_END"===d;if(e!==b){const f=this.getSourceBlock();f.updateAt_(a,e);f.setFieldValue(d,"WHERE"+a);return null}});this.getInput("AT"+a).appendField(c,"WHERE"+a);1===a&&(this.moveInputBefore("AT1","AT2"),this.getInput("ORDINAL1")&&
|
||||
this.moveInputBefore("ORDINAL1","AT2"));module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_GET_SUBLIST_TAIL&&this.moveInputBefore("TAIL",null)}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_sort={init:function(){this.jsonInit({message0:"%{BKY_LISTS_SORT_TITLE}",args0:[{type:"field_dropdown",name:"TYPE",options:[["%{BKY_LISTS_SORT_TYPE_NUMERIC}","NUMERIC"],["%{BKY_LISTS_SORT_TYPE_TEXT}","TEXT"],["%{BKY_LISTS_SORT_TYPE_IGNORECASE}","IGNORE_CASE"]]},{type:"field_dropdown",name:"DIRECTION",options:[["%{BKY_LISTS_SORT_ORDER_ASCENDING}","1"],["%{BKY_LISTS_SORT_ORDER_DESCENDING}","-1"]]},{type:"input_value",name:"LIST",check:"Array"}],
|
||||
output:"Array",style:"list_blocks",tooltip:"%{BKY_LISTS_SORT_TOOLTIP}",helpUrl:"%{BKY_LISTS_SORT_HELPURL}"})}};
|
||||
module$exports$Blockly$libraryBlocks$lists.blocks.lists_split={init:function(){const a=this,b=new module$contents$Blockly$libraryBlocks$lists_FieldDropdown([[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SPLIT_LIST_FROM_TEXT,"SPLIT"],[module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SPLIT_TEXT_FROM_LIST,"JOIN"]],function(c){a.updateType_(c)});this.setHelpUrl(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SPLIT_HELPURL);this.setStyle("list_blocks");this.appendValueInput("INPUT").setCheck("String").appendField(b,
|
||||
"MODE");this.appendValueInput("DELIM").setCheck("String").appendField(module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SPLIT_WITH_DELIMITER);this.setInputsInline(!0);this.setOutput(!0,"Array");this.setTooltip(function(){const c=a.getFieldValue("MODE");if("SPLIT"===c)return module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SPLIT_TOOLTIP_SPLIT;if("JOIN"===c)return module$contents$Blockly$libraryBlocks$lists_Msg.LISTS_SPLIT_TOOLTIP_JOIN;throw Error("Unknown mode: "+c);})},updateType_:function(a){if(this.getFieldValue("MODE")!==
|
||||
a){const b=this.getInput("INPUT").connection;b.setShadowDom(null);const c=b.targetBlock();c&&(b.disconnect(),c.isShadow()?c.dispose():this.bumpNeighbours())}"SPLIT"===a?(this.outputConnection.setCheck("Array"),this.getInput("INPUT").setCheck("String")):(this.outputConnection.setCheck("String"),this.getInput("INPUT").setCheck("Array"))},mutationToDom:function(){const a=$.module$build$src$core$utils$xml.createElement("mutation");a.setAttribute("mode",this.getFieldValue("MODE"));return a},domToMutation:function(a){this.updateType_(a.getAttribute("mode"))},
|
||||
saveExtraState:function(){return null},loadExtraState:function(){}};module$contents$Blockly$libraryBlocks$lists_defineBlocks(module$exports$Blockly$libraryBlocks$lists.blocks);var module$exports$Blockly$libraryBlocks$colour={},module$contents$Blockly$libraryBlocks$colour_BlockDefinition=Object,module$contents$Blockly$libraryBlocks$colour_createBlockDefinitionsFromJsonArray=$.module$build$src$core$common.createBlockDefinitionsFromJsonArray,module$contents$Blockly$libraryBlocks$colour_defineBlocks=$.module$build$src$core$common.defineBlocks;
|
||||
module$exports$Blockly$libraryBlocks$colour.blocks=module$contents$Blockly$libraryBlocks$colour_createBlockDefinitionsFromJsonArray([{type:"colour_picker",message0:"%1",args0:[{type:"field_colour",name:"COLOUR",colour:"#ff0000"}],output:"Colour",helpUrl:"%{BKY_COLOUR_PICKER_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_PICKER_TOOLTIP}",extensions:["parent_tooltip_when_inline"]},{type:"colour_random",message0:"%{BKY_COLOUR_RANDOM_TITLE}",output:"Colour",helpUrl:"%{BKY_COLOUR_RANDOM_HELPURL}",
|
||||
style:"colour_blocks",tooltip:"%{BKY_COLOUR_RANDOM_TOOLTIP}"},{type:"colour_rgb",message0:"%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3",args0:[{type:"input_value",name:"RED",check:"Number",align:"RIGHT"},{type:"input_value",name:"GREEN",check:"Number",align:"RIGHT"},{type:"input_value",name:"BLUE",check:"Number",align:"RIGHT"}],output:"Colour",helpUrl:"%{BKY_COLOUR_RGB_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_RGB_TOOLTIP}"},{type:"colour_blend",
|
||||
message0:"%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} %1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3",args0:[{type:"input_value",name:"COLOUR1",check:"Colour",align:"RIGHT"},{type:"input_value",name:"COLOUR2",check:"Colour",align:"RIGHT"},{type:"input_value",name:"RATIO",check:"Number",align:"RIGHT"}],output:"Colour",helpUrl:"%{BKY_COLOUR_BLEND_HELPURL}",style:"colour_blocks",tooltip:"%{BKY_COLOUR_BLEND_TOOLTIP}"}]);module$contents$Blockly$libraryBlocks$colour_defineBlocks(module$exports$Blockly$libraryBlocks$colour.blocks);var module$exports$Blockly$libraryBlocks={},module$contents$Blockly$libraryBlocks_BlockDefinition=Object;module$exports$Blockly$libraryBlocks.colour=module$exports$Blockly$libraryBlocks$colour;module$exports$Blockly$libraryBlocks.lists=module$exports$Blockly$libraryBlocks$lists;module$exports$Blockly$libraryBlocks.logic=module$exports$Blockly$libraryBlocks$logic;module$exports$Blockly$libraryBlocks.loops=module$exports$Blockly$libraryBlocks$loops;module$exports$Blockly$libraryBlocks.math=module$exports$Blockly$libraryBlocks$math;
|
||||
module$exports$Blockly$libraryBlocks.procedures=module$exports$Blockly$libraryBlocks$procedures;module$exports$Blockly$libraryBlocks.texts=module$exports$Blockly$libraryBlocks$texts;module$exports$Blockly$libraryBlocks.variables=module$exports$Blockly$libraryBlocks$variables;module$exports$Blockly$libraryBlocks.variablesDynamic=module$exports$Blockly$libraryBlocks$variablesDynamic;
|
||||
module$exports$Blockly$libraryBlocks.blocks=Object.assign({},module$exports$Blockly$libraryBlocks$colour.blocks,module$exports$Blockly$libraryBlocks$lists.blocks,module$exports$Blockly$libraryBlocks$logic.blocks,module$exports$Blockly$libraryBlocks$loops.blocks,module$exports$Blockly$libraryBlocks$math.blocks,module$exports$Blockly$libraryBlocks$procedures.blocks,module$exports$Blockly$libraryBlocks$variables.blocks,module$exports$Blockly$libraryBlocks$variablesDynamic.blocks);
|
||||
module$exports$Blockly$libraryBlocks.__namespace__=$;
|
||||
return module$exports$Blockly$libraryBlocks;
|
||||
}));
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+77
-56
@@ -256,15 +256,15 @@ goog.LOCALE = goog.define('goog.LOCALE', 'en'); // default to en
|
||||
|
||||
|
||||
/**
|
||||
* This method is intended to be used for bookkeeping purposes. We would
|
||||
* like to distinguish uses of goog.LOCALE used for code stripping purposes
|
||||
* and uses of goog.LOCALE for other uses (such as URL parameters).
|
||||
* Same as `goog.LOCALE`, which should be used instead.
|
||||
*
|
||||
* This allows us to ban direct uses of goog.LOCALE and to ensure that all
|
||||
* code has been transformed to our new localization build scheme.
|
||||
* Using this method just makes it harder for closure-compiler to optimize
|
||||
* your locale-specific code, since it has to take the extra step of inlining
|
||||
* this function to discover and remove code that is not used for the target
|
||||
* locale.
|
||||
*
|
||||
* @return {string}
|
||||
*
|
||||
* @deprecated use `goog.LOCALE`
|
||||
*/
|
||||
goog.getLocale = function() {
|
||||
return goog.LOCALE;
|
||||
@@ -617,8 +617,8 @@ 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.');
|
||||
}
|
||||
if (namespace in goog.loadedModules_) {
|
||||
throw new Error(
|
||||
@@ -917,14 +917,6 @@ goog.global.CLOSURE_NO_DEPS;
|
||||
goog.global.CLOSURE_IMPORT_SCRIPT;
|
||||
|
||||
|
||||
/**
|
||||
* Null function used for default values of callbacks, etc.
|
||||
* @return {void} Nothing.
|
||||
* @deprecated use '()=>{}' or 'function(){}' instead.
|
||||
*/
|
||||
goog.nullFunction = function() {};
|
||||
|
||||
|
||||
/**
|
||||
* When defining a class Foo with an abstract method bar(), you can do:
|
||||
* Foo.prototype.bar = goog.abstractMethod
|
||||
@@ -1567,34 +1559,6 @@ goog.partial = function(fn, var_args) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Copies all the members of a source object to a target object. This method
|
||||
* does not work on all browsers for all objects that contain keys such as
|
||||
* toString or hasOwnProperty. Use goog.object.extend for this purpose.
|
||||
*
|
||||
* NOTE: Some have advocated for the use of goog.mixin to setup classes
|
||||
* with multiple inheritence (traits, mixins, etc). However, as it simply
|
||||
* uses "for in", this is not compatible with ES6 classes whose methods are
|
||||
* non-enumerable. Changing this, would break cases where non-enumerable
|
||||
* properties are not expected.
|
||||
*
|
||||
* @param {Object} target Target.
|
||||
* @param {Object} source Source.
|
||||
* @deprecated Prefer Object.assign
|
||||
*/
|
||||
goog.mixin = function(target, source) {
|
||||
for (var x in source) {
|
||||
target[x] = source[x];
|
||||
}
|
||||
|
||||
// For IE7 or lower, the for-in-loop does not contain any properties that are
|
||||
// not enumerable on the prototype object (for example, isPrototypeOf from
|
||||
// Object.prototype) but also it will not include 'replace' on objects that
|
||||
// extend String and change 'replace' (not that it is common for anyone to
|
||||
// extend anything except Object).
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} An integer value representing the number of milliseconds
|
||||
* between midnight, January 1, 1970 and the current time.
|
||||
@@ -1773,6 +1737,71 @@ if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
|
||||
goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options bag type for `goog.getMsg()` third argument.
|
||||
*
|
||||
* It is important to note that these options need to be known at compile time,
|
||||
* so they must always be provided to `goog.getMsg()` as an actual object
|
||||
* literal in the function call. Otherwise, closure-compiler will report an
|
||||
* error.
|
||||
* @record
|
||||
*/
|
||||
goog.GetMsgOptions = function() {};
|
||||
|
||||
/**
|
||||
* If `true`, escape '<' in the message string to '<'.
|
||||
*
|
||||
* Used by Closure Templates where the generated code size and performance is
|
||||
* critical which is why {@link goog.html.SafeHtmlFormatter} is not used.
|
||||
* The value must be literal `true` or `false`.
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
goog.GetMsgOptions.prototype.html;
|
||||
|
||||
/**
|
||||
* If `true`, unescape common html entities: >, <, ', " and
|
||||
* &.
|
||||
*
|
||||
* Used for messages not in HTML context, such as with the `textContent`
|
||||
* property.
|
||||
* The value must be literal `true` or `false`.
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
goog.GetMsgOptions.prototype.unescapeHtmlEntities;
|
||||
|
||||
/**
|
||||
* Associates placeholder names with strings showing how their values are
|
||||
* obtained.
|
||||
*
|
||||
* This field is intended for use in automatically generated JS code.
|
||||
* Human-written code should use meaningful placeholder names instead.
|
||||
*
|
||||
* closure-compiler uses this as the contents of the `<ph>` tag in the
|
||||
* XMB file it generates or defaults to `-` for historical reasons.
|
||||
*
|
||||
* Must be an object literal.
|
||||
* Ignored at runtime.
|
||||
* Keys are placeholder names.
|
||||
* Values are string literals indicating how the value is obtained.
|
||||
* Typically this is a snippet of source code.
|
||||
* @type {!Object<string, string>|undefined}
|
||||
*/
|
||||
goog.GetMsgOptions.prototype.original_code;
|
||||
|
||||
/**
|
||||
* Associates placeholder names with example values.
|
||||
*
|
||||
* closure-compiler uses this as the contents of the `<ex>` tag in the
|
||||
* XMB file it generates or defaults to `-` for historical reasons.
|
||||
*
|
||||
* Must be an object literal.
|
||||
* Ignored at runtime.
|
||||
* Keys are placeholder names.
|
||||
* Values are string literals containing example placeholder values.
|
||||
* (e.g. "George McFly" for a name placeholder)
|
||||
* @type {!Object<string, string>|undefined}
|
||||
*/
|
||||
goog.GetMsgOptions.prototype.example;
|
||||
|
||||
/**
|
||||
* Gets a localized message.
|
||||
@@ -1791,16 +1820,8 @@ if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
|
||||
* produce SafeHtml.
|
||||
*
|
||||
* @param {string} str Translatable string, places holders in the form {$foo}.
|
||||
* @param {Object<string, string>=} opt_values Maps place holder name to value.
|
||||
* @param {{html: (boolean|undefined),
|
||||
* unescapeHtmlEntities: (boolean|undefined)}=} opt_options Options:
|
||||
* html: Escape '<' in str to '<'. Used by Closure Templates where the
|
||||
* generated code size and performance is critical which is why {@link
|
||||
* goog.html.SafeHtmlFormatter} is not used. The value must be literal true
|
||||
* or false.
|
||||
* unescapeHtmlEntities: Unescape common html entities: >, <, ',
|
||||
* " and &. Used for messages not in HTML context, such as with
|
||||
* `textContent` property.
|
||||
* @param {!Object<string, string>=} opt_values Maps place holder name to value.
|
||||
* @param {!goog.GetMsgOptions=} opt_options see `goog.GetMsgOptions`
|
||||
* @return {string} message with placeholders filled.
|
||||
*/
|
||||
goog.getMsg = function(str, opt_values, opt_options) {
|
||||
@@ -2266,8 +2287,8 @@ if (!COMPILED && goog.DEPENDENCIES_ENABLED) {
|
||||
var src = script.src;
|
||||
var qmark = src.lastIndexOf('?');
|
||||
var l = qmark == -1 ? src.length : qmark;
|
||||
if (src.substr(l - 7, 7) == 'base.js') {
|
||||
goog.basePath = src.substr(0, l - 7);
|
||||
if (src.slice(l - 7, l) == 'base.js') {
|
||||
goog.basePath = src.slice(0, l - 7);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
+44
-41
@@ -36,50 +36,53 @@
|
||||
* goog.require calls.
|
||||
*/
|
||||
|
||||
export const global = goog.global;
|
||||
export const require = goog.require;
|
||||
export const define = goog.define;
|
||||
export const DEBUG = goog.DEBUG;
|
||||
export const LOCALE = goog.LOCALE;
|
||||
export const TRUSTED_SITE = goog.TRUSTED_SITE;
|
||||
export const DISALLOW_TEST_ONLY_CODE = goog.DISALLOW_TEST_ONLY_CODE;
|
||||
export const getGoogModule = goog.module.get;
|
||||
export const setTestOnly = goog.setTestOnly;
|
||||
export const forwardDeclare = goog.forwardDeclare;
|
||||
export const getObjectByName = goog.getObjectByName;
|
||||
export const basePath = goog.basePath;
|
||||
export const addSingletonGetter = goog.addSingletonGetter;
|
||||
export const typeOf = goog.typeOf;
|
||||
export const isArrayLike = goog.isArrayLike;
|
||||
export const isDateLike = goog.isDateLike;
|
||||
export const isObject = goog.isObject;
|
||||
export const getUid = goog.getUid;
|
||||
export const hasUid = goog.hasUid;
|
||||
export const removeUid = goog.removeUid;
|
||||
export const mixin = goog.mixin;
|
||||
export const now = Date.now;
|
||||
export const globalEval = goog.globalEval;
|
||||
export const getCssName = goog.getCssName;
|
||||
export const setCssNameMapping = goog.setCssNameMapping;
|
||||
export const getMsg = goog.getMsg;
|
||||
export const getMsgWithFallback = goog.getMsgWithFallback;
|
||||
export const exportSymbol = goog.exportSymbol;
|
||||
export const exportProperty = goog.exportProperty;
|
||||
export const nullFunction = goog.nullFunction;
|
||||
export const abstractMethod = goog.abstractMethod;
|
||||
export const cloneObject = goog.cloneObject;
|
||||
export const bind = goog.bind;
|
||||
export const partial = goog.partial;
|
||||
export const inherits = goog.inherits;
|
||||
export const scope = goog.scope;
|
||||
export const defineClass = goog.defineClass;
|
||||
export const declareModuleId = goog.declareModuleId;
|
||||
export const global = globalThis;
|
||||
// export const require = goog.require;
|
||||
// export const define = goog.define;
|
||||
// export const DEBUG = goog.DEBUG;
|
||||
// export const LOCALE = goog.LOCALE;
|
||||
// export const TRUSTED_SITE = goog.TRUSTED_SITE;
|
||||
// export const DISALLOW_TEST_ONLY_CODE = goog.DISALLOW_TEST_ONLY_CODE;
|
||||
// export const getGoogModule = goog.module.get;
|
||||
// export const setTestOnly = goog.setTestOnly;
|
||||
// export const forwardDeclare = goog.forwardDeclare;
|
||||
// export const getObjectByName = goog.getObjectByName;
|
||||
// export const basePath = goog.basePath;
|
||||
// export const addSingletonGetter = goog.addSingletonGetter;
|
||||
// export const typeOf = goog.typeOf;
|
||||
// export const isArrayLike = goog.isArrayLike;
|
||||
// export const isDateLike = goog.isDateLike;
|
||||
// export const isObject = goog.isObject;
|
||||
// export const getUid = goog.getUid;
|
||||
// export const hasUid = goog.hasUid;
|
||||
// export const removeUid = goog.removeUid;
|
||||
// export const now = Date.now;
|
||||
// export const globalEval = goog.globalEval;
|
||||
// export const getCssName = goog.getCssName;
|
||||
// export const setCssNameMapping = goog.setCssNameMapping;
|
||||
// export const getMsg = goog.getMsg;
|
||||
// export const getMsgWithFallback = goog.getMsgWithFallback;
|
||||
// export const exportSymbol = goog.exportSymbol;
|
||||
// export const exportProperty = goog.exportProperty;
|
||||
// export const abstractMethod = goog.abstractMethod;
|
||||
// export const cloneObject = goog.cloneObject;
|
||||
// export const bind = goog.bind;
|
||||
// export const partial = goog.partial;
|
||||
// export const inherits = goog.inherits;
|
||||
// export const scope = goog.scope;
|
||||
// export const defineClass = goog.defineClass;
|
||||
export const declareModuleId = function(namespace) {
|
||||
if (window.goog && window.goog.declareModuleId) {
|
||||
window.goog.declareModuleId.call(this, namespace);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Export select properties of module. Do not export the function itself or
|
||||
// goog.module.declareLegacyNamespace.
|
||||
export const module = {
|
||||
get: goog.module.get,
|
||||
};
|
||||
// export const module = {
|
||||
// get: goog.module.get,
|
||||
// };
|
||||
|
||||
// Omissions include:
|
||||
// goog.ENABLE_DEBUG_LOADER - define only used in base.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line
|
||||
type AnyDuringMigration = any;
|
||||
+733
-742
File diff suppressed because it is too large
Load Diff
@@ -4,93 +4,94 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods animating a block on connection and disconnection.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Methods animating a block on connection and disconnection.
|
||||
*
|
||||
* @namespace Blockly.blockAnimations
|
||||
*/
|
||||
goog.module('Blockly.blockAnimations');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.blockAnimations');
|
||||
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
const {Svg} = goog.require('Blockly.utils.Svg');
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* PID of disconnect UI animation. There can only be one at a time.
|
||||
* @type {number}
|
||||
*/
|
||||
let disconnectPid = 0;
|
||||
/** A bounding box for a cloned block. */
|
||||
interface CloneRect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
/**
|
||||
* SVG group of wobbling block. There can only be one at a time.
|
||||
* @type {Element}
|
||||
*/
|
||||
let disconnectGroup = null;
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, animation) when disposing of a block.
|
||||
* @param {!BlockSvg} block The block being disposed of.
|
||||
*
|
||||
* @param block The block being disposed of.
|
||||
* @alias Blockly.blockAnimations.disposeUiEffect
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const disposeUiEffect = function(block) {
|
||||
export function disposeUiEffect(block: BlockSvg) {
|
||||
const workspace = block.workspace;
|
||||
const svgGroup = block.getSvgRoot();
|
||||
workspace.getAudioManager().play('delete');
|
||||
|
||||
const xy = workspace.getSvgXY(svgGroup);
|
||||
// Deeply clone the current block.
|
||||
const clone = svgGroup.cloneNode(true);
|
||||
clone.translateX_ = xy.x;
|
||||
clone.translateY_ = xy.y;
|
||||
const clone: SVGGElement = svgGroup.cloneNode(true) as SVGGElement;
|
||||
clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
|
||||
workspace.getParentSvg().appendChild(clone);
|
||||
clone.bBox_ = clone.getBBox();
|
||||
// Start the animation.
|
||||
disposeUiStep(clone, workspace.RTL, new Date, workspace.scale);
|
||||
};
|
||||
exports.disposeUiEffect = disposeUiEffect;
|
||||
|
||||
const cloneRect =
|
||||
{'x': xy.x, 'y': xy.y, 'width': block.width, 'height': block.height};
|
||||
disposeUiStep(clone, cloneRect, workspace.RTL, new Date(), workspace.scale);
|
||||
}
|
||||
/**
|
||||
* Animate a cloned block and eventually dispose of it.
|
||||
* This is a class method, not an instance method since the original block has
|
||||
* been destroyed and is no longer accessible.
|
||||
* @param {!Element} clone SVG element to animate and dispose of.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @param {!Date} start Date of animation's start.
|
||||
* @param {number} workspaceScale Scale of workspace.
|
||||
*
|
||||
* @param clone SVG element to animate and dispose of.
|
||||
* @param rect Starting rect of the clone.
|
||||
* @param rtl True if RTL, false if LTR.
|
||||
* @param start Date of animation's start.
|
||||
* @param workspaceScale Scale of workspace.
|
||||
*/
|
||||
const disposeUiStep = function(clone, rtl, start, workspaceScale) {
|
||||
const ms = new Date - start;
|
||||
function disposeUiStep(
|
||||
clone: Element, rect: CloneRect, rtl: boolean, start: Date,
|
||||
workspaceScale: number) {
|
||||
const ms = new Date().getTime() - start.getTime();
|
||||
const percent = ms / 150;
|
||||
if (percent > 1) {
|
||||
dom.removeNode(clone);
|
||||
} else {
|
||||
const x = clone.translateX_ +
|
||||
(rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
|
||||
const y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
|
||||
const x =
|
||||
rect.x + (rtl ? -1 : 1) * rect.width * workspaceScale / 2 * percent;
|
||||
const y = rect.y + rect.height * workspaceScale * percent;
|
||||
const scale = (1 - percent) * workspaceScale;
|
||||
clone.setAttribute(
|
||||
'transform',
|
||||
'translate(' + x + ',' + y + ')' +
|
||||
' scale(' + scale + ')');
|
||||
setTimeout(disposeUiStep, 10, clone, rtl, start, workspaceScale);
|
||||
setTimeout(disposeUiStep, 10, clone, rect, rtl, start, workspaceScale);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, ripple) after a connection has been established.
|
||||
* @param {!BlockSvg} block The block being connected.
|
||||
*
|
||||
* @param block The block being connected.
|
||||
* @alias Blockly.blockAnimations.connectionUiEffect
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const connectionUiEffect = function(block) {
|
||||
export function connectionUiEffect(block: BlockSvg) {
|
||||
const workspace = block.workspace;
|
||||
const scale = workspace.scale;
|
||||
workspace.getAudioManager().play('click');
|
||||
@@ -118,35 +119,37 @@ const connectionUiEffect = function(block) {
|
||||
},
|
||||
workspace.getParentSvg());
|
||||
// Start the animation.
|
||||
connectionUiStep(ripple, new Date, scale);
|
||||
};
|
||||
exports.connectionUiEffect = connectionUiEffect;
|
||||
connectionUiStep(ripple, new Date(), scale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand a ripple around a connection.
|
||||
* @param {!SVGElement} ripple Element to animate.
|
||||
* @param {!Date} start Date of animation's start.
|
||||
* @param {number} scale Scale of workspace.
|
||||
*
|
||||
* @param ripple Element to animate.
|
||||
* @param start Date of animation's start.
|
||||
* @param scale Scale of workspace.
|
||||
*/
|
||||
const connectionUiStep = function(ripple, start, scale) {
|
||||
const ms = new Date - start;
|
||||
function connectionUiStep(ripple: SVGElement, start: Date, scale: number) {
|
||||
const ms = new Date().getTime() - start.getTime();
|
||||
const percent = ms / 150;
|
||||
if (percent > 1) {
|
||||
dom.removeNode(ripple);
|
||||
} else {
|
||||
ripple.setAttribute('r', percent * 25 * scale);
|
||||
ripple.style.opacity = 1 - percent;
|
||||
ripple.setAttribute('r', (percent * 25 * scale).toString());
|
||||
ripple.style.opacity = (1 - percent).toString();
|
||||
disconnectPid = setTimeout(connectionUiStep, 10, ripple, start, scale);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Play some UI effects (sound, animation) when disconnecting a block.
|
||||
* @param {!BlockSvg} block The block being disconnected.
|
||||
*
|
||||
* @param block The block being disconnected.
|
||||
* @alias Blockly.blockAnimations.disconnectUiEffect
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const disconnectUiEffect = function(block) {
|
||||
export function disconnectUiEffect(block: BlockSvg) {
|
||||
disconnectUiStop();
|
||||
block.workspace.getAudioManager().play('disconnect');
|
||||
if (block.workspace.scale < 1) {
|
||||
return; // Too small to care about visual effects.
|
||||
@@ -160,47 +163,46 @@ const disconnectUiEffect = function(block) {
|
||||
magnitude *= -1;
|
||||
}
|
||||
// Start the animation.
|
||||
disconnectUiStep(block.getSvgRoot(), magnitude, new Date);
|
||||
};
|
||||
exports.disconnectUiEffect = disconnectUiEffect;
|
||||
disconnectGroup = block.getSvgRoot();
|
||||
disconnectUiStep(disconnectGroup, magnitude, new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate a brief wiggle of a disconnected block.
|
||||
* @param {!SVGElement} group SVG element to animate.
|
||||
* @param {number} magnitude Maximum degrees skew (reversed for RTL).
|
||||
* @param {!Date} start Date of animation's start.
|
||||
*
|
||||
* @param group SVG element to animate.
|
||||
* @param magnitude Maximum degrees skew (reversed for RTL).
|
||||
* @param start Date of animation's start.
|
||||
*/
|
||||
const disconnectUiStep = function(group, magnitude, start) {
|
||||
function disconnectUiStep(group: SVGElement, magnitude: number, start: Date) {
|
||||
const DURATION = 200; // Milliseconds.
|
||||
const WIGGLES = 3; // Half oscillations.
|
||||
|
||||
const ms = new Date - start;
|
||||
const ms = new Date().getTime() - start.getTime();
|
||||
const percent = ms / DURATION;
|
||||
|
||||
if (percent > 1) {
|
||||
group.skew_ = '';
|
||||
} else {
|
||||
const skew = Math.round(
|
||||
let skew = '';
|
||||
if (percent <= 1) {
|
||||
const val = Math.round(
|
||||
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
|
||||
group.skew_ = 'skewX(' + skew + ')';
|
||||
disconnectGroup = group;
|
||||
skew = `skewX(${val})`;
|
||||
disconnectPid = setTimeout(disconnectUiStep, 10, group, magnitude, start);
|
||||
}
|
||||
group.setAttribute('transform', group.translate_ + group.skew_);
|
||||
};
|
||||
group.setAttribute('transform', skew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the disconnect UI animation immediately.
|
||||
*
|
||||
* @alias Blockly.blockAnimations.disconnectUiStop
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const disconnectUiStop = function() {
|
||||
export function disconnectUiStop() {
|
||||
if (disconnectGroup) {
|
||||
clearTimeout(disconnectPid);
|
||||
const group = disconnectGroup;
|
||||
group.skew_ = '';
|
||||
group.setAttribute('transform', group.translate_);
|
||||
if (disconnectPid) {
|
||||
clearTimeout(disconnectPid);
|
||||
}
|
||||
disconnectGroup.setAttribute('transform', '');
|
||||
disconnectGroup = null;
|
||||
}
|
||||
};
|
||||
exports.disconnectUiStop = disconnectUiStop;
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A class that manages a surface for dragging blocks. When a
|
||||
* block drag is started, we move the block (and children) to a separate DOM
|
||||
* element that we move around using translate3d. At the end of the drag, the
|
||||
* blocks are put back in into the SVG they came from. This helps
|
||||
* performance by avoiding repainting the entire SVG on every mouse move
|
||||
* while dragging blocks.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A class that manages a surface for dragging blocks. When a
|
||||
* block drag is started, we move the block (and children) to a separate DOM
|
||||
* element that we move around using translate3d. At the end of the drag, the
|
||||
* blocks are put back in into the SVG they came from. This helps
|
||||
* performance by avoiding repainting the entire SVG on every mouse move
|
||||
* while dragging blocks.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.BlockDragSurfaceSvg');
|
||||
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const svgMath = goog.require('Blockly.utils.svgMath');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
const {Svg} = goog.require('Blockly.utils.Svg');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a drag surface for the currently dragged block. This is a separate
|
||||
* SVG that contains only the currently moving block, or nothing.
|
||||
* @alias Blockly.BlockDragSurfaceSvg
|
||||
*/
|
||||
const BlockDragSurfaceSvg = class {
|
||||
/**
|
||||
* @param {!Element} container Containing element.
|
||||
*/
|
||||
constructor(container) {
|
||||
/**
|
||||
* The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom.
|
||||
* @type {?SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.SVG_ = null;
|
||||
|
||||
/**
|
||||
* This is where blocks live while they are being dragged if the drag
|
||||
* surface is enabled.
|
||||
* @type {?SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.dragGroup_ = null;
|
||||
|
||||
/**
|
||||
* Containing HTML element; parent of the workspace and the drag surface.
|
||||
* @type {!Element}
|
||||
* @private
|
||||
*/
|
||||
this.container_ = container;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.scale_ = 1;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the drag surface.
|
||||
* This translation is in pixel units, because the scale is applied to the
|
||||
* drag group rather than the top-level SVG.
|
||||
* @type {?Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.surfaceXY_ = null;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the child drag surface in pixel
|
||||
* units. Since the child drag surface tracks the translation of the
|
||||
* workspace this is ultimately the translation of the workspace.
|
||||
* @type {!Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.childSurfaceXY_ = new Coordinate(0, 0);
|
||||
|
||||
this.createDom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
createDom() {
|
||||
if (this.SVG_) {
|
||||
return; // Already created.
|
||||
}
|
||||
this.SVG_ = dom.createSvgElement(
|
||||
Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
'xmlns:xlink': dom.XLINK_NS,
|
||||
'version': '1.1',
|
||||
'class': 'blocklyBlockDragSurface',
|
||||
},
|
||||
this.container_);
|
||||
this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.SVG_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SVG blocks on the drag surface's group and show the surface.
|
||||
* Only one block group should be on the drag surface at a time.
|
||||
* @param {!SVGElement} blocks Block or group of blocks to place on the drag
|
||||
* surface.
|
||||
*/
|
||||
setBlocksAndShow(blocks) {
|
||||
if (this.dragGroup_.childNodes.length) {
|
||||
throw Error('Already dragging a block.');
|
||||
}
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.SVG_.style.display = 'block';
|
||||
this.surfaceXY_ = new Coordinate(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to the given position, to
|
||||
* keep in sync with the workspace.
|
||||
* @param {number} x X translation in pixel coordinates.
|
||||
* @param {number} y Y translation in pixel coordinates.
|
||||
* @param {number} scale Scale of the group.
|
||||
*/
|
||||
translateAndScaleGroup(x, y, scale) {
|
||||
this.scale_ = scale;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
const fixedX = x.toFixed(0);
|
||||
const fixedY = y.toFixed(0);
|
||||
|
||||
this.childSurfaceXY_.x = parseInt(fixedX, 10);
|
||||
this.childSurfaceXY_.y = parseInt(fixedY, 10);
|
||||
|
||||
this.dragGroup_.setAttribute(
|
||||
'transform',
|
||||
'translate(' + fixedX + ',' + fixedY + ') scale(' + scale + ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the drag surface's SVG based on its internal state.
|
||||
* @private
|
||||
*/
|
||||
translateSurfaceInternal_() {
|
||||
let x = this.surfaceXY_.x;
|
||||
let y = this.surfaceXY_.y;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.SVG_.style.display = 'block';
|
||||
|
||||
dom.setCssTransform(this.SVG_, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the entire surface by a relative offset.
|
||||
* @param {number} deltaX Horizontal offset in pixel units.
|
||||
* @param {number} deltaY Vertical offset in pixel units.
|
||||
*/
|
||||
translateBy(deltaX, deltaY) {
|
||||
const x = this.surfaceXY_.x + deltaX;
|
||||
const y = this.surfaceXY_.y + deltaY;
|
||||
this.surfaceXY_ = new Coordinate(x, y);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
* so that the browser avoids repainting the SVG.
|
||||
* Because of this, the drag coordinates must be adjusted by scale.
|
||||
* @param {number} x X translation for the entire surface.
|
||||
* @param {number} y Y translation for the entire surface.
|
||||
*/
|
||||
translateSurface(x, y) {
|
||||
this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
* @return {!Coordinate} Current translation of the surface.
|
||||
*/
|
||||
getSurfaceTranslation() {
|
||||
const xy = svgMath.getRelativeXY(/** @type {!SVGElement} */ (this.SVG_));
|
||||
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a reference to the drag group (primarily for
|
||||
* BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {?SVGElement} Drag surface group element.
|
||||
*/
|
||||
getGroup() {
|
||||
return this.dragGroup_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SVG drag surface.
|
||||
* @returns {?SVGElement} The SVG drag surface.
|
||||
*/
|
||||
getSvgRoot() {
|
||||
return this.SVG_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current blocks on the drag surface, if any (primarily
|
||||
* for BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {?Element} Drag surface block DOM element, or null if no blocks
|
||||
* exist.
|
||||
*/
|
||||
getCurrentBlock() {
|
||||
return /** @type {Element} */ (this.dragGroup_.firstChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation of the child block surface
|
||||
* This surface is in charge of keeping track of how much the workspace has
|
||||
* moved.
|
||||
* @return {!Coordinate} The amount the workspace has been moved.
|
||||
*/
|
||||
getWsTranslation() {
|
||||
// Returning a copy so the coordinate can not be changed outside this class.
|
||||
return this.childSurfaceXY_.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided
|
||||
* element.
|
||||
* If the block is being deleted it doesn't need to go back to the original
|
||||
* surface, since it would be removed immediately during dispose.
|
||||
* @param {Element=} opt_newSurface Surface the dragging blocks should be
|
||||
* moved to, or null if the blocks should be removed from this surface
|
||||
* without being moved to a different surface.
|
||||
*/
|
||||
clearAndHide(opt_newSurface) {
|
||||
const currentBlockElement = this.getCurrentBlock();
|
||||
if (currentBlockElement) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
opt_newSurface.appendChild(currentBlockElement);
|
||||
} else {
|
||||
this.dragGroup_.removeChild(currentBlockElement);
|
||||
}
|
||||
}
|
||||
this.SVG_.style.display = 'none';
|
||||
if (this.dragGroup_.childNodes.length) {
|
||||
throw Error('Drag group was not cleared.');
|
||||
}
|
||||
this.surfaceXY_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
exports.BlockDragSurfaceSvg = BlockDragSurfaceSvg;
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* A class that manages a surface for dragging blocks. When a
|
||||
* block drag is started, we move the block (and children) to a separate DOM
|
||||
* element that we move around using translate3d. At the end of the drag, the
|
||||
* blocks are put back in into the SVG they came from. This helps
|
||||
* performance by avoiding repainting the entire SVG on every mouse move
|
||||
* while dragging blocks.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.BlockDragSurfaceSvg');
|
||||
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a drag surface for the currently dragged block. This is a separate
|
||||
* SVG that contains only the currently moving block, or nothing.
|
||||
*
|
||||
* @alias Blockly.BlockDragSurfaceSvg
|
||||
*/
|
||||
export class BlockDragSurfaceSvg {
|
||||
/** The SVG drag surface. Set once by BlockDragSurfaceSvg.createDom. */
|
||||
private svg_: SVGElement;
|
||||
|
||||
/**
|
||||
* This is where blocks live while they are being dragged if the drag
|
||||
* surface is enabled.
|
||||
*/
|
||||
private dragGroup_: SVGElement;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
*/
|
||||
private scale_ = 1;
|
||||
|
||||
/**
|
||||
* Cached value for the translation of the drag surface.
|
||||
* This translation is in pixel units, because the scale is applied to the
|
||||
* drag group rather than the top-level SVG.
|
||||
*/
|
||||
private surfaceXY_: Coordinate = new Coordinate(0, 0);
|
||||
private readonly childSurfaceXY_: Coordinate;
|
||||
|
||||
/** @param container Containing element. */
|
||||
constructor(private readonly container: Element) {
|
||||
/**
|
||||
* Cached value for the translation of the child drag surface in pixel
|
||||
* units. Since the child drag surface tracks the translation of the
|
||||
* workspace this is ultimately the translation of the workspace.
|
||||
*/
|
||||
this.childSurfaceXY_ = new Coordinate(0, 0);
|
||||
|
||||
this.svg_ = dom.createSvgElement(
|
||||
Svg.SVG, {
|
||||
'xmlns': dom.SVG_NS,
|
||||
'xmlns:html': dom.HTML_NS,
|
||||
'xmlns:xlink': dom.XLINK_NS,
|
||||
'version': '1.1',
|
||||
'class': 'blocklyBlockDragSurface',
|
||||
},
|
||||
this.container);
|
||||
this.dragGroup_ = dom.createSvgElement(Svg.G, {}, this.svg_ as SVGElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*
|
||||
* @deprecated The DOM is automatically created by the constructor.
|
||||
*/
|
||||
createDom() {
|
||||
// No alternative provided, because now the dom is just automatically
|
||||
// created in the constructor now.
|
||||
deprecation.warn('BlockDragSurfaceSvg createDom', 'June 2022', 'June 2023');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SVG blocks on the drag surface's group and show the surface.
|
||||
* Only one block group should be on the drag surface at a time.
|
||||
*
|
||||
* @param blocks Block or group of blocks to place on the drag surface.
|
||||
*/
|
||||
setBlocksAndShow(blocks: SVGElement) {
|
||||
if (this.dragGroup_.childNodes.length) {
|
||||
throw Error('Already dragging a block.');
|
||||
}
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.svg_.style.display = 'block';
|
||||
this.surfaceXY_ = new Coordinate(0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to the given position, to
|
||||
* keep in sync with the workspace.
|
||||
*
|
||||
* @param x X translation in pixel coordinates.
|
||||
* @param y Y translation in pixel coordinates.
|
||||
* @param scale Scale of the group.
|
||||
*/
|
||||
translateAndScaleGroup(x: number, y: number, scale: number) {
|
||||
this.scale_ = scale;
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
const roundX = Math.round(x);
|
||||
const roundY = Math.round(y);
|
||||
this.childSurfaceXY_.x = roundX;
|
||||
this.childSurfaceXY_.y = roundY;
|
||||
this.dragGroup_!.setAttribute(
|
||||
'transform',
|
||||
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the drag surface's SVG based on its internal state.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
translateSurfaceInternal_() {
|
||||
let x = this.surfaceXY_!.x;
|
||||
let y = this.surfaceXY_!.y;
|
||||
// Make sure the svg exists on a pixel boundary so that it is not fuzzy.
|
||||
x = Math.round(x);
|
||||
y = Math.round(y);
|
||||
this.svg_.style.display = 'block';
|
||||
dom.setCssTransform(this.svg_, 'translate3d(' + x + 'px, ' + y + 'px, 0)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates the entire surface by a relative offset.
|
||||
*
|
||||
* @param deltaX Horizontal offset in pixel units.
|
||||
* @param deltaY Vertical offset in pixel units.
|
||||
*/
|
||||
translateBy(deltaX: number, deltaY: number) {
|
||||
const x = this.surfaceXY_.x + deltaX;
|
||||
const y = this.surfaceXY_.y + deltaY;
|
||||
this.surfaceXY_ = new Coordinate(x, y);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
* so that the browser avoids repainting the SVG.
|
||||
* Because of this, the drag coordinates must be adjusted by scale.
|
||||
*
|
||||
* @param x X translation for the entire surface.
|
||||
* @param y Y translation for the entire surface.
|
||||
*/
|
||||
translateSurface(x: number, y: number) {
|
||||
this.surfaceXY_ = new Coordinate(x * this.scale_, y * this.scale_);
|
||||
this.translateSurfaceInternal_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
*
|
||||
* @returns Current translation of the surface.
|
||||
*/
|
||||
getSurfaceTranslation(): Coordinate {
|
||||
const xy = svgMath.getRelativeXY(this.svg_ as SVGElement);
|
||||
return new Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a reference to the drag group (primarily for
|
||||
* BlockSvg.getRelativeToSurfaceXY).
|
||||
*
|
||||
* @returns Drag surface group element.
|
||||
*/
|
||||
getGroup(): SVGElement|null {
|
||||
return this.dragGroup_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SVG drag surface.
|
||||
*
|
||||
* @returns The SVG drag surface.
|
||||
*/
|
||||
getSvgRoot(): SVGElement|null {
|
||||
return this.svg_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current blocks on the drag surface, if any (primarily
|
||||
* for BlockSvg.getRelativeToSurfaceXY).
|
||||
*
|
||||
* @returns Drag surface block DOM element, or null if no blocks exist.
|
||||
*/
|
||||
getCurrentBlock(): Element|null {
|
||||
return this.dragGroup_.firstChild as Element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation of the child block surface
|
||||
* This surface is in charge of keeping track of how much the workspace has
|
||||
* moved.
|
||||
*
|
||||
* @returns The amount the workspace has been moved.
|
||||
*/
|
||||
getWsTranslation(): Coordinate {
|
||||
// Returning a copy so the coordinate can not be changed outside this class.
|
||||
return this.childSurfaceXY_.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided
|
||||
* element.
|
||||
* If the block is being deleted it doesn't need to go back to the original
|
||||
* surface, since it would be removed immediately during dispose.
|
||||
*
|
||||
* @param opt_newSurface Surface the dragging blocks should be moved to, or
|
||||
* null if the blocks should be removed from this surface without being
|
||||
* moved to a different surface.
|
||||
*/
|
||||
clearAndHide(opt_newSurface?: Element) {
|
||||
const currentBlockElement = this.getCurrentBlock();
|
||||
if (currentBlockElement) {
|
||||
if (opt_newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
opt_newSurface.appendChild(currentBlockElement);
|
||||
} else {
|
||||
this.dragGroup_.removeChild(currentBlockElement);
|
||||
}
|
||||
}
|
||||
this.svg_.style.display = 'none';
|
||||
if (this.dragGroup_.childNodes.length) {
|
||||
throw Error('Drag group was not cleared.');
|
||||
}
|
||||
this.surfaceXY_ = new Coordinate(0, 0);
|
||||
}
|
||||
}
|
||||
@@ -4,94 +4,71 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a block visually.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Methods for dragging a block visually.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.BlockDragger');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.BlockDragger');
|
||||
|
||||
const blockAnimation = goog.require('Blockly.blockAnimations');
|
||||
const bumpObjects = goog.require('Blockly.bumpObjects');
|
||||
const common = goog.require('Blockly.common');
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockMove} = goog.requireType('Blockly.Events.BlockMove');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IBlockDragger} = goog.require('Blockly.IBlockDragger');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDragTarget} = goog.requireType('Blockly.IDragTarget');
|
||||
const {InsertionMarkerManager} = goog.require('Blockly.InsertionMarkerManager');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockDrag');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockMove');
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_block_drag.js';
|
||||
|
||||
import * as blockAnimation from './block_animations.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as bumpObjects from './bump_objects.js';
|
||||
import * as common from './common.js';
|
||||
import type {BlockMove} from './events/events_block_move.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import type {Icon} from './icon.js';
|
||||
import {InsertionMarkerManager} from './insertion_marker_manager.js';
|
||||
import type {IBlockDragger} from './interfaces/i_block_dragger.js';
|
||||
import type {IDragTarget} from './interfaces/i_drag_target.js';
|
||||
import * as registry from './registry.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
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.
|
||||
* @implements {IBlockDragger}
|
||||
*
|
||||
* @alias Blockly.BlockDragger
|
||||
*/
|
||||
const BlockDragger = class {
|
||||
export class BlockDragger implements IBlockDragger {
|
||||
/** The top block in the stack that is being dragged. */
|
||||
protected draggingBlock_: BlockSvg;
|
||||
protected draggedConnectionManager_: InsertionMarkerManager;
|
||||
|
||||
/** The workspace on which the block is being dragged. */
|
||||
protected workspace_: WorkspaceSvg;
|
||||
|
||||
/** Which drag area the mouse pointer is over, if any. */
|
||||
private dragTarget_: IDragTarget|null = null;
|
||||
|
||||
/** Whether the block would be deleted if dropped immediately. */
|
||||
protected wouldDeleteBlock_ = false;
|
||||
protected startXY_: Coordinate;
|
||||
protected dragIconData_: IconPositionData[];
|
||||
|
||||
/**
|
||||
* @param {!BlockSvg} block The block to drag.
|
||||
* @param {!WorkspaceSvg} workspace The workspace to drag on.
|
||||
* @param block The block to drag.
|
||||
* @param workspace The workspace to drag on.
|
||||
*/
|
||||
constructor(block, workspace) {
|
||||
/**
|
||||
* The top block in the stack that is being dragged.
|
||||
* @type {!BlockSvg}
|
||||
* @protected
|
||||
*/
|
||||
constructor(block: BlockSvg, workspace: WorkspaceSvg) {
|
||||
this.draggingBlock_ = block;
|
||||
|
||||
/**
|
||||
* The workspace on which the block is being dragged.
|
||||
* @type {!WorkspaceSvg}
|
||||
* @protected
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Object that keeps track of connections on dragged blocks.
|
||||
* @type {!InsertionMarkerManager}
|
||||
* @protected
|
||||
*/
|
||||
/** Object that keeps track of connections on dragged blocks. */
|
||||
this.draggedConnectionManager_ =
|
||||
new InsertionMarkerManager(this.draggingBlock_);
|
||||
|
||||
/**
|
||||
* Which drag area the mouse pointer is over, if any.
|
||||
* @type {?IDragTarget}
|
||||
* @private
|
||||
*/
|
||||
this.dragTarget_ = null;
|
||||
|
||||
/**
|
||||
* Whether the block would be deleted if dropped immediately.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.wouldDeleteBlock_ = false;
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* The location of the top left corner of the dragging block at the
|
||||
* beginning of the drag in workspace coordinates.
|
||||
* @type {!Coordinate}
|
||||
* @protected
|
||||
*/
|
||||
this.startXY_ = this.draggingBlock_.getRelativeToSurfaceXY();
|
||||
|
||||
@@ -99,15 +76,14 @@ const BlockDragger = class {
|
||||
* A list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @type {Array<!Object>}
|
||||
* @protected
|
||||
*/
|
||||
this.dragIconData_ = initIconData(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
dispose() {
|
||||
this.dragIconData_.length = 0;
|
||||
@@ -119,13 +95,12 @@ const BlockDragger = class {
|
||||
|
||||
/**
|
||||
* Start dragging a block. This includes moving it to the drag surface.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @param {boolean} healStack Whether or not to heal the stack after
|
||||
* disconnecting.
|
||||
* @public
|
||||
*
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at mouse down, in pixel units.
|
||||
* @param healStack Whether or not to heal the stack after disconnecting.
|
||||
*/
|
||||
startDrag(currentDragDeltaXY, healStack) {
|
||||
startDrag(currentDragDeltaXY: Coordinate, healStack: boolean) {
|
||||
if (!eventUtils.getGroup()) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
@@ -157,27 +132,26 @@ const BlockDragger = class {
|
||||
|
||||
/**
|
||||
* Whether or not we should disconnect the block when a drag is started.
|
||||
* @param {boolean} healStack Whether or not to heal the stack after
|
||||
* disconnecting.
|
||||
* @return {boolean} True to disconnect the block, false otherwise.
|
||||
* @protected
|
||||
*
|
||||
* @param healStack Whether or not to heal the stack after disconnecting.
|
||||
* @returns True to disconnect the block, false otherwise.
|
||||
*/
|
||||
shouldDisconnect_(healStack) {
|
||||
protected shouldDisconnect_(healStack: boolean): boolean {
|
||||
return !!(
|
||||
this.draggingBlock_.getParent() ||
|
||||
(healStack && this.draggingBlock_.nextConnection &&
|
||||
this.draggingBlock_.nextConnection.targetBlock()));
|
||||
healStack && this.draggingBlock_.nextConnection &&
|
||||
this.draggingBlock_.nextConnection.targetBlock());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the block and moves it to a new location.
|
||||
* @param {boolean} healStack Whether or not to heal the stack after
|
||||
* disconnecting.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at mouse down, in pixel units.
|
||||
* @protected
|
||||
*
|
||||
* @param healStack Whether or not to heal the stack after disconnecting.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at mouse down, in pixel units.
|
||||
*/
|
||||
disconnectBlock_(healStack, currentDragDeltaXY) {
|
||||
protected disconnectBlock_(
|
||||
healStack: boolean, currentDragDeltaXY: Coordinate) {
|
||||
this.draggingBlock_.unplug(healStack);
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
@@ -187,11 +161,8 @@ const BlockDragger = class {
|
||||
this.draggedConnectionManager_.updateAvailableConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a UI event at the start of a block drag.
|
||||
* @protected
|
||||
*/
|
||||
fireDragStartEvent_() {
|
||||
/** Fire a UI event at the start of a block drag. */
|
||||
protected fireDragStartEvent_() {
|
||||
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
|
||||
this.draggingBlock_, true, this.draggingBlock_.getDescendants(false));
|
||||
eventUtils.fire(event);
|
||||
@@ -200,12 +171,12 @@ const BlockDragger = class {
|
||||
/**
|
||||
* Execute a step of block dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
* @param {!Event} e The most recent move event.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @public
|
||||
*
|
||||
* @param e The most recent move event.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
*/
|
||||
drag(e, currentDragDeltaXY) {
|
||||
drag(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
this.draggingBlock_.moveDuringDrag(newLoc);
|
||||
@@ -233,12 +204,12 @@ const BlockDragger = class {
|
||||
|
||||
/**
|
||||
* Finish a block drag and put the block back on the workspace.
|
||||
* @param {!Event} e The mouseup/touchend event.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @public
|
||||
*
|
||||
* @param e The mouseup/touchend event.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
*/
|
||||
endDrag(e, currentDragDeltaXY) {
|
||||
endDrag(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
// Make sure internal state is fresh.
|
||||
this.drag(e, currentDragDeltaXY);
|
||||
this.dragIconData_ = [];
|
||||
@@ -250,10 +221,8 @@ const BlockDragger = class {
|
||||
|
||||
const preventMove = !!this.dragTarget_ &&
|
||||
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
|
||||
/** @type {Coordinate} */
|
||||
let newLoc;
|
||||
/** @type {Coordinate} */
|
||||
let delta;
|
||||
let newLoc: Coordinate;
|
||||
let delta: Coordinate|null = null;
|
||||
if (preventMove) {
|
||||
newLoc = this.startXY_;
|
||||
} else {
|
||||
@@ -289,29 +258,30 @@ const BlockDragger = class {
|
||||
|
||||
/**
|
||||
* Calculates the drag delta and new location values after a block is dragged.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the start of the drag, in pixel units.
|
||||
* @return {{delta: !Coordinate, newLocation:
|
||||
* !Coordinate}} New location after drag. delta is in
|
||||
* workspace units. newLocation is the new coordinate where the block
|
||||
* should end up.
|
||||
* @protected
|
||||
*
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the start of
|
||||
* the drag, in pixel units.
|
||||
* @returns New location after drag. delta is in workspace units. newLocation
|
||||
* is the new coordinate where the block should end up.
|
||||
*/
|
||||
getNewLocationAfterDrag_(currentDragDeltaXY) {
|
||||
const newValues = {};
|
||||
newValues.delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
newValues.newLocation = Coordinate.sum(this.startXY_, newValues.delta);
|
||||
return newValues;
|
||||
protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate):
|
||||
{delta: Coordinate, newLocation: Coordinate} {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLocation = Coordinate.sum(this.startXY_, delta);
|
||||
return {
|
||||
delta,
|
||||
newLocation,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* May delete the dragging block, if allowed. If `this.wouldDeleteBlock_` is
|
||||
* not true, the block will not be deleted. This should be called at the end
|
||||
* of a block drag.
|
||||
* @return {boolean} True if the block was deleted.
|
||||
* @protected
|
||||
*
|
||||
* @returns True if the block was deleted.
|
||||
*/
|
||||
maybeDeleteBlock_() {
|
||||
protected maybeDeleteBlock_(): boolean {
|
||||
if (this.wouldDeleteBlock_) {
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
@@ -324,11 +294,11 @@ const BlockDragger = class {
|
||||
|
||||
/**
|
||||
* Updates the necessary information to place a block at a certain location.
|
||||
* @param {!Coordinate} delta The change in location from where
|
||||
* the block started the drag to where it ended the drag.
|
||||
* @protected
|
||||
*
|
||||
* @param delta The change in location from where the block started the drag
|
||||
* to where it ended the drag.
|
||||
*/
|
||||
updateBlockAfterMove_(delta) {
|
||||
protected updateBlockAfterMove_(delta: Coordinate) {
|
||||
this.draggingBlock_.moveConnections(delta.x, delta.y);
|
||||
this.fireMoveEvent_();
|
||||
if (this.draggedConnectionManager_.wouldConnectBlock()) {
|
||||
@@ -340,11 +310,8 @@ const BlockDragger = class {
|
||||
this.draggingBlock_.scheduleSnapAndBump();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a UI event at the end of a block drag.
|
||||
* @protected
|
||||
*/
|
||||
fireDragEndEvent_() {
|
||||
/** Fire a UI event at the end of a block drag. */
|
||||
protected fireDragEndEvent_() {
|
||||
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
|
||||
this.draggingBlock_, false, this.draggingBlock_.getDescendants(false));
|
||||
eventUtils.fire(event);
|
||||
@@ -354,32 +321,39 @@ const BlockDragger = class {
|
||||
* Adds or removes the style of the cursor for the toolbox.
|
||||
* This is what changes the cursor to display an x when a deletable block is
|
||||
* held over the toolbox.
|
||||
* @param {boolean} isEnd True if we are at the end of a drag, false
|
||||
* otherwise.
|
||||
* @protected
|
||||
*
|
||||
* @param isEnd True if we are at the end of a drag, false otherwise.
|
||||
*/
|
||||
updateToolboxStyle_(isEnd) {
|
||||
protected updateToolboxStyle_(isEnd: boolean) {
|
||||
const toolbox = this.workspace_.getToolbox();
|
||||
|
||||
if (toolbox) {
|
||||
const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
|
||||
'blocklyToolboxGrab';
|
||||
|
||||
if (isEnd && typeof toolbox.removeStyle === 'function') {
|
||||
toolbox.removeStyle(style);
|
||||
} else if (!isEnd && typeof toolbox.addStyle === 'function') {
|
||||
toolbox.addStyle(style);
|
||||
// AnyDuringMigration because: Property 'removeStyle' does not exist on
|
||||
// type 'IToolbox'.
|
||||
if (isEnd &&
|
||||
typeof (toolbox as AnyDuringMigration).removeStyle === 'function') {
|
||||
// AnyDuringMigration because: Property 'removeStyle' does not exist on
|
||||
// type 'IToolbox'.
|
||||
(toolbox as AnyDuringMigration).removeStyle(style);
|
||||
// AnyDuringMigration because: Property 'addStyle' does not exist on
|
||||
// type 'IToolbox'.
|
||||
} else if (
|
||||
!isEnd &&
|
||||
typeof (toolbox as AnyDuringMigration).addStyle === 'function') {
|
||||
// AnyDuringMigration because: Property 'addStyle' does not exist on
|
||||
// type 'IToolbox'.
|
||||
(toolbox as AnyDuringMigration).addStyle(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a move event at the end of a block drag.
|
||||
* @protected
|
||||
*/
|
||||
fireMoveEvent_() {
|
||||
const event = /** @type {!BlockMove} */
|
||||
(new (eventUtils.get(eventUtils.BLOCK_MOVE))(this.draggingBlock_));
|
||||
/** Fire a move event at the end of a block drag. */
|
||||
protected fireMoveEvent_() {
|
||||
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
|
||||
this.draggingBlock_) as BlockMove;
|
||||
event.oldCoordinate = this.startXY_;
|
||||
event.recordNew();
|
||||
eventUtils.fire(event);
|
||||
@@ -388,9 +362,8 @@ const BlockDragger = class {
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging block would be deleted if released immediately.
|
||||
* @protected
|
||||
*/
|
||||
updateCursorDuringBlockDrag_() {
|
||||
protected updateCursorDuringBlockDrag_() {
|
||||
this.draggingBlock_.setDeleteStyle(this.wouldDeleteBlock_);
|
||||
}
|
||||
|
||||
@@ -399,13 +372,11 @@ const BlockDragger = class {
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
* @param {!Coordinate} pixelCoord A coordinate with x and y
|
||||
* values in CSS pixel units.
|
||||
* @return {!Coordinate} The input coordinate divided by the
|
||||
* workspace scale.
|
||||
* @protected
|
||||
*
|
||||
* @param pixelCoord A coordinate with x and y values in CSS pixel units.
|
||||
* @returns The input coordinate divided by the workspace scale.
|
||||
*/
|
||||
pixelsToWorkspaceUnits_(pixelCoord) {
|
||||
protected pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
|
||||
const result = new Coordinate(
|
||||
pixelCoord.x / this.workspace_.scale,
|
||||
pixelCoord.y / this.workspace_.scale);
|
||||
@@ -413,7 +384,7 @@ const BlockDragger = class {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same
|
||||
// as the scale on the parent workspace. Fix that for dragging.
|
||||
const mainScale = this.workspace_.options.parentWorkspace.scale;
|
||||
const mainScale = this.workspace_.options.parentWorkspace!.scale;
|
||||
result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
@@ -421,11 +392,11 @@ const BlockDragger = class {
|
||||
|
||||
/**
|
||||
* Move all of the icons connected to this drag.
|
||||
* @param {!Coordinate} dxy How far to move the icons from their
|
||||
* original positions, in workspace units.
|
||||
* @protected
|
||||
*
|
||||
* @param dxy How far to move the icons from their original positions, in
|
||||
* workspace units.
|
||||
*/
|
||||
dragIcons_(dxy) {
|
||||
protected dragIcons_(dxy: Coordinate) {
|
||||
// Moving icons moves their associated bubbles.
|
||||
for (let i = 0; i < this.dragIconData_.length; i++) {
|
||||
const data = this.dragIconData_[i];
|
||||
@@ -436,11 +407,10 @@ const BlockDragger = class {
|
||||
/**
|
||||
* Get a list of the insertion markers that currently exist. Drags have 0, 1,
|
||||
* or 2 insertion markers.
|
||||
* @return {!Array<!BlockSvg>} A possibly empty list of insertion
|
||||
* marker blocks.
|
||||
* @public
|
||||
*
|
||||
* @returns A possibly empty list of insertion marker blocks.
|
||||
*/
|
||||
getInsertionMarkers() {
|
||||
getInsertionMarkers(): BlockSvg[] {
|
||||
// No insertion markers with the old style of dragged connection managers.
|
||||
if (this.draggedConnectionManager_ &&
|
||||
this.draggedConnectionManager_.getInsertionMarkers) {
|
||||
@@ -448,37 +418,42 @@ const BlockDragger = class {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Data about the position of a given icon. */
|
||||
export interface IconPositionData {
|
||||
location: Coordinate;
|
||||
icon: Icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a list of all of the icons (comment, warning, and mutator) that are
|
||||
* on this block and its descendants. Moving an icon moves the bubble that
|
||||
* extends from it if that bubble is open.
|
||||
* @param {!BlockSvg} block The root block that is being dragged.
|
||||
* @return {!Array<!Object>} The list of all icons and their locations.
|
||||
*
|
||||
* @param block The root block that is being dragged.
|
||||
* @returns The list of all icons and their locations.
|
||||
*/
|
||||
const initIconData = function(block) {
|
||||
function initIconData(block: BlockSvg): IconPositionData[] {
|
||||
// Build a list of icons that need to be moved and where they started.
|
||||
const dragIconData = [];
|
||||
const descendants =
|
||||
/** @type {!Array<!BlockSvg>} */ (block.getDescendants(false));
|
||||
const descendants = (block.getDescendants(false));
|
||||
|
||||
for (let i = 0, descendant; (descendant = descendants[i]); i++) {
|
||||
for (let i = 0, descendant; descendant = descendants[i]; i++) {
|
||||
const icons = descendant.getIcons();
|
||||
for (let j = 0; j < icons.length; j++) {
|
||||
const data = {
|
||||
// Coordinate with x and y properties (workspace
|
||||
// coordinates).
|
||||
location: icons[j].getIconLocation(),
|
||||
// Blockly.Icon
|
||||
location: icons[j].getIconLocation(), // Blockly.Icon
|
||||
icon: icons[j],
|
||||
};
|
||||
dragIconData.push(data);
|
||||
}
|
||||
}
|
||||
return dragIconData;
|
||||
};
|
||||
// AnyDuringMigration because: Type '{ location: Coordinate | null; icon:
|
||||
// Icon; }[]' is not assignable to type 'IconPositionData[]'.
|
||||
return dragIconData as AnyDuringMigration;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.BLOCK_DRAGGER, registry.DEFAULT, BlockDragger);
|
||||
|
||||
exports.BlockDragger = BlockDragger;
|
||||
File diff suppressed because it is too large
Load Diff
-890
@@ -1,890 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2011 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The top level namespace used to access the Blockly library.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The top level namespace used to access the Blockly library.
|
||||
* @namespace Blockly
|
||||
*/
|
||||
goog.module('Blockly');
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
const ContextMenu = goog.require('Blockly.ContextMenu');
|
||||
const ContextMenuItems = goog.require('Blockly.ContextMenuItems');
|
||||
const Css = goog.require('Blockly.Css');
|
||||
const Events = goog.require('Blockly.Events');
|
||||
const Extensions = goog.require('Blockly.Extensions');
|
||||
const Procedures = goog.require('Blockly.Procedures');
|
||||
const ShortcutItems = goog.require('Blockly.ShortcutItems');
|
||||
const Themes = goog.require('Blockly.Themes');
|
||||
const Tooltip = goog.require('Blockly.Tooltip');
|
||||
const Touch = goog.require('Blockly.Touch');
|
||||
const Variables = goog.require('Blockly.Variables');
|
||||
const VariablesDynamic = goog.require('Blockly.VariablesDynamic');
|
||||
const WidgetDiv = goog.require('Blockly.WidgetDiv');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const blockAnimations = goog.require('Blockly.blockAnimations');
|
||||
const blockRendering = goog.require('Blockly.blockRendering');
|
||||
const browserEvents = goog.require('Blockly.browserEvents');
|
||||
const bumpObjects = goog.require('Blockly.bumpObjects');
|
||||
const clipboard = goog.require('Blockly.clipboard');
|
||||
const colour = goog.require('Blockly.utils.colour');
|
||||
const common = goog.require('Blockly.common');
|
||||
const constants = goog.require('Blockly.constants');
|
||||
const deprecation = goog.require('Blockly.utils.deprecation');
|
||||
const dialog = goog.require('Blockly.dialog');
|
||||
const dropDownDiv = goog.require('Blockly.dropDownDiv');
|
||||
const fieldRegistry = goog.require('Blockly.fieldRegistry');
|
||||
const geras = goog.require('Blockly.geras');
|
||||
const internalConstants = goog.require('Blockly.internalConstants');
|
||||
const minimalist = goog.require('Blockly.minimalist');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const serializationBlocks = goog.require('Blockly.serialization.blocks');
|
||||
const serializationExceptions = goog.require('Blockly.serialization.exceptions');
|
||||
const serializationPriorities = goog.require('Blockly.serialization.priorities');
|
||||
const serializationRegistry = goog.require('Blockly.serialization.registry');
|
||||
const serializationVariables = goog.require('Blockly.serialization.variables');
|
||||
const serializationWorkspaces = goog.require('Blockly.serialization.workspaces');
|
||||
const svgMath = goog.require('Blockly.utils.svgMath');
|
||||
const thrasos = goog.require('Blockly.thrasos');
|
||||
const toolbox = goog.require('Blockly.utils.toolbox');
|
||||
const uiPosition = goog.require('Blockly.uiPosition');
|
||||
const utils = goog.require('Blockly.utils');
|
||||
const zelos = goog.require('Blockly.zelos');
|
||||
const {Align, Input} = goog.require('Blockly.Input');
|
||||
const {ASTNode} = goog.require('Blockly.ASTNode');
|
||||
const {BasicCursor} = goog.require('Blockly.BasicCursor');
|
||||
const {BlockDragSurfaceSvg} = goog.require('Blockly.BlockDragSurfaceSvg');
|
||||
const {BlockDragger} = goog.require('Blockly.BlockDragger');
|
||||
const {BlockSvg} = goog.require('Blockly.BlockSvg');
|
||||
const {BlocklyOptions} = goog.require('Blockly.BlocklyOptions');
|
||||
const {Blocks} = goog.require('Blockly.blocks');
|
||||
const {Block} = goog.require('Blockly.Block');
|
||||
const {BubbleDragger} = goog.require('Blockly.BubbleDragger');
|
||||
const {Bubble} = goog.require('Blockly.Bubble');
|
||||
const {CollapsibleToolboxCategory} = goog.require('Blockly.CollapsibleToolboxCategory');
|
||||
const {Comment} = goog.require('Blockly.Comment');
|
||||
const {ComponentManager} = goog.require('Blockly.ComponentManager');
|
||||
const {config} = goog.require('Blockly.config');
|
||||
const {ConnectionChecker} = goog.require('Blockly.ConnectionChecker');
|
||||
const {ConnectionDB} = goog.require('Blockly.ConnectionDB');
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {Connection} = goog.require('Blockly.Connection');
|
||||
const {ContextMenuRegistry} = goog.require('Blockly.ContextMenuRegistry');
|
||||
const {Cursor} = goog.require('Blockly.Cursor');
|
||||
const {DeleteArea} = goog.require('Blockly.DeleteArea');
|
||||
const {DragTarget} = goog.require('Blockly.DragTarget');
|
||||
const {FieldAngle} = goog.require('Blockly.FieldAngle');
|
||||
const {FieldCheckbox} = goog.require('Blockly.FieldCheckbox');
|
||||
const {FieldColour} = goog.require('Blockly.FieldColour');
|
||||
const {FieldDropdown} = goog.require('Blockly.FieldDropdown');
|
||||
const {FieldImage} = goog.require('Blockly.FieldImage');
|
||||
const {FieldLabelSerializable} = goog.require('Blockly.FieldLabelSerializable');
|
||||
const {FieldLabel} = goog.require('Blockly.FieldLabel');
|
||||
const {FieldMultilineInput} = goog.require('Blockly.FieldMultilineInput');
|
||||
const {FieldNumber} = goog.require('Blockly.FieldNumber');
|
||||
const {FieldTextInput} = goog.require('Blockly.FieldTextInput');
|
||||
const {FieldVariable} = goog.require('Blockly.FieldVariable');
|
||||
const {Field} = goog.require('Blockly.Field');
|
||||
const {FlyoutButton} = goog.require('Blockly.FlyoutButton');
|
||||
const {FlyoutMetricsManager} = goog.require('Blockly.FlyoutMetricsManager');
|
||||
const {Flyout} = goog.require('Blockly.Flyout');
|
||||
const {Generator} = goog.require('Blockly.Generator');
|
||||
const {Gesture} = goog.require('Blockly.Gesture');
|
||||
const {Grid} = goog.require('Blockly.Grid');
|
||||
const {HorizontalFlyout} = goog.require('Blockly.HorizontalFlyout');
|
||||
const {IASTNodeLocationSvg} = goog.require('Blockly.IASTNodeLocationSvg');
|
||||
const {IASTNodeLocationWithBlock} = goog.require('Blockly.IASTNodeLocationWithBlock');
|
||||
const {IASTNodeLocation} = goog.require('Blockly.IASTNodeLocation');
|
||||
const {IAutoHideable} = goog.require('Blockly.IAutoHideable');
|
||||
const {IBlockDragger} = goog.require('Blockly.IBlockDragger');
|
||||
const {IBoundedElement} = goog.require('Blockly.IBoundedElement');
|
||||
const {IBubble} = goog.require('Blockly.IBubble');
|
||||
const {ICollapsibleToolboxItem} = goog.require('Blockly.ICollapsibleToolboxItem');
|
||||
const {IComponent} = goog.require('Blockly.IComponent');
|
||||
const {IConnectionChecker} = goog.require('Blockly.IConnectionChecker');
|
||||
const {IContextMenu} = goog.require('Blockly.IContextMenu');
|
||||
const {ICopyable} = goog.require('Blockly.ICopyable');
|
||||
const {IDeletable} = goog.require('Blockly.IDeletable');
|
||||
const {IDeleteArea} = goog.require('Blockly.IDeleteArea');
|
||||
const {IDragTarget} = goog.require('Blockly.IDragTarget');
|
||||
const {IDraggable} = goog.require('Blockly.IDraggable');
|
||||
const {IFlyout} = goog.require('Blockly.IFlyout');
|
||||
const {IKeyboardAccessible} = goog.require('Blockly.IKeyboardAccessible');
|
||||
const {IMetricsManager} = goog.require('Blockly.IMetricsManager');
|
||||
const {IMovable} = goog.require('Blockly.IMovable');
|
||||
const {IPositionable} = goog.require('Blockly.IPositionable');
|
||||
const {IRegistrableField} = goog.require('Blockly.IRegistrableField');
|
||||
const {IRegistrable} = goog.require('Blockly.IRegistrable');
|
||||
const {ISelectableToolboxItem} = goog.require('Blockly.ISelectableToolboxItem');
|
||||
const {ISelectable} = goog.require('Blockly.ISelectable');
|
||||
const {ISerializer} = goog.require('Blockly.serialization.ISerializer');
|
||||
const {IStyleable} = goog.require('Blockly.IStyleable');
|
||||
const {IToolboxItem} = goog.require('Blockly.IToolboxItem');
|
||||
const {IToolbox} = goog.require('Blockly.IToolbox');
|
||||
const {Icon} = goog.require('Blockly.Icon');
|
||||
const {InsertionMarkerManager} = goog.require('Blockly.InsertionMarkerManager');
|
||||
const {Marker} = goog.require('Blockly.Marker');
|
||||
const {MarkerManager} = goog.require('Blockly.MarkerManager');
|
||||
const {MenuItem} = goog.require('Blockly.MenuItem');
|
||||
const {Menu} = goog.require('Blockly.Menu');
|
||||
const {MetricsManager} = goog.require('Blockly.MetricsManager');
|
||||
const {Mutator} = goog.require('Blockly.Mutator');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {Names} = goog.require('Blockly.Names');
|
||||
const {Options} = goog.require('Blockly.Options');
|
||||
const {RenderedConnection} = goog.require('Blockly.RenderedConnection');
|
||||
const {ScrollbarPair} = goog.require('Blockly.ScrollbarPair');
|
||||
const {Scrollbar} = goog.require('Blockly.Scrollbar');
|
||||
const {ShortcutRegistry} = goog.require('Blockly.ShortcutRegistry');
|
||||
const {TabNavigateCursor} = goog.require('Blockly.TabNavigateCursor');
|
||||
const {ThemeManager} = goog.require('Blockly.ThemeManager');
|
||||
const {Theme} = goog.require('Blockly.Theme');
|
||||
const {ToolboxCategory} = goog.require('Blockly.ToolboxCategory');
|
||||
const {ToolboxItem} = goog.require('Blockly.ToolboxItem');
|
||||
const {ToolboxSeparator} = goog.require('Blockly.ToolboxSeparator');
|
||||
const {Toolbox} = goog.require('Blockly.Toolbox');
|
||||
const {TouchGesture} = goog.require('Blockly.TouchGesture');
|
||||
const {Trashcan} = goog.require('Blockly.Trashcan');
|
||||
const {VariableMap} = goog.require('Blockly.VariableMap');
|
||||
const {VariableModel} = goog.require('Blockly.VariableModel');
|
||||
const {VerticalFlyout} = goog.require('Blockly.VerticalFlyout');
|
||||
const {Warning} = goog.require('Blockly.Warning');
|
||||
const {WorkspaceAudio} = goog.require('Blockly.WorkspaceAudio');
|
||||
const {WorkspaceCommentSvg} = goog.require('Blockly.WorkspaceCommentSvg');
|
||||
const {WorkspaceComment} = goog.require('Blockly.WorkspaceComment');
|
||||
const {WorkspaceDragSurfaceSvg} = goog.require('Blockly.WorkspaceDragSurfaceSvg');
|
||||
const {WorkspaceDragger} = goog.require('Blockly.WorkspaceDragger');
|
||||
const {WorkspaceSvg, resizeSvgContents} = goog.require('Blockly.WorkspaceSvg');
|
||||
const {Workspace} = goog.require('Blockly.Workspace');
|
||||
const {ZoomControls} = goog.require('Blockly.ZoomControls');
|
||||
const {globalThis} = goog.require('Blockly.utils.global');
|
||||
const {inject} = goog.require('Blockly.inject');
|
||||
const {inputTypes} = goog.require('Blockly.inputTypes');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockCreate');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.FinishedLoading');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.Ui');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.UiBase');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.VarCreate');
|
||||
|
||||
|
||||
/**
|
||||
* Blockly core version.
|
||||
* This constant is overridden by the build script (npm run build) to the value
|
||||
* of the version in package.json. This is done by the Closure Compiler in the
|
||||
* buildCompressed gulp task.
|
||||
* For local builds, you can pass --define='Blockly.VERSION=X.Y.Z' to the
|
||||
* compiler to override this constant.
|
||||
* @define {string}
|
||||
* @alias Blockly.VERSION
|
||||
*/
|
||||
exports.VERSION = 'uncompiled';
|
||||
|
||||
/*
|
||||
* Top-level functions and properties on the Blockly namespace.
|
||||
* These are used only in external code. Do not reference these
|
||||
* from internal code as importing from this file can cause circular
|
||||
* dependencies. Do not add new functions here. There is probably a better
|
||||
* namespace to put new functions on.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Aliases for input alignments used in block defintions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see Blockly.Input.Align.LEFT
|
||||
* @alias Blockly.ALIGN_LEFT
|
||||
*/
|
||||
exports.ALIGN_LEFT = Align.LEFT;
|
||||
|
||||
/**
|
||||
* @see Blockly.Input.Align.CENTRE
|
||||
* @alias Blockly.ALIGN_CENTRE
|
||||
*/
|
||||
exports.ALIGN_CENTRE = Align.CENTRE;
|
||||
|
||||
/**
|
||||
* @see Blockly.Input.Align.RIGHT
|
||||
* @alias Blockly.ALIGN_RIGHT
|
||||
*/
|
||||
exports.ALIGN_RIGHT = Align.RIGHT;
|
||||
|
||||
/*
|
||||
* Aliases for constants used for connection and input types.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see ConnectionType.INPUT_VALUE
|
||||
* @alias Blockly.INPUT_VALUE
|
||||
*/
|
||||
exports.INPUT_VALUE = ConnectionType.INPUT_VALUE;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.OUTPUT_VALUE
|
||||
* @alias Blockly.OUTPUT_VALUE
|
||||
*/
|
||||
exports.OUTPUT_VALUE = ConnectionType.OUTPUT_VALUE;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.NEXT_STATEMENT
|
||||
* @alias Blockly.NEXT_STATEMENT
|
||||
*/
|
||||
exports.NEXT_STATEMENT = ConnectionType.NEXT_STATEMENT;
|
||||
|
||||
/**
|
||||
* @see ConnectionType.PREVIOUS_STATEMENT
|
||||
* @alias Blockly.PREVIOUS_STATEMENT
|
||||
*/
|
||||
exports.PREVIOUS_STATEMENT = ConnectionType.PREVIOUS_STATEMENT;
|
||||
|
||||
/**
|
||||
* @see inputTypes.DUMMY_INPUT
|
||||
* @alias Blockly.DUMMY_INPUT
|
||||
*/
|
||||
exports.DUMMY_INPUT = inputTypes.DUMMY;
|
||||
|
||||
/**
|
||||
* Aliases for toolbox positions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.TOP
|
||||
* @alias Blockly.TOOLBOX_AT_TOP
|
||||
*/
|
||||
exports.TOOLBOX_AT_TOP = toolbox.Position.TOP;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.BOTTOM
|
||||
* @alias Blockly.TOOLBOX_AT_BOTTOM
|
||||
*/
|
||||
exports.TOOLBOX_AT_BOTTOM = toolbox.Position.BOTTOM;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.LEFT
|
||||
* @alias Blockly.TOOLBOX_AT_LEFT
|
||||
*/
|
||||
exports.TOOLBOX_AT_LEFT = toolbox.Position.LEFT;
|
||||
|
||||
/**
|
||||
* @see toolbox.Position.RIGHT
|
||||
* @alias Blockly.TOOLBOX_AT_RIGHT
|
||||
*/
|
||||
exports.TOOLBOX_AT_RIGHT = toolbox.Position.RIGHT;
|
||||
|
||||
/*
|
||||
* Other aliased functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Size the SVG image to completely fill its container. Call this when the view
|
||||
* actually changes sizes (e.g. on a window resize/device orientation change).
|
||||
* See workspace.resizeContents to resize the workspace when the contents
|
||||
* change (e.g. when a block is added or removed).
|
||||
* Record the height/width of the SVG image.
|
||||
* @param {!WorkspaceSvg} workspace Any workspace in the SVG.
|
||||
* @see Blockly.common.svgResize
|
||||
* @alias Blockly.svgResize
|
||||
*/
|
||||
exports.svgResize = common.svgResize;
|
||||
|
||||
/**
|
||||
* Close tooltips, context menus, dropdown selections, etc.
|
||||
* @param {boolean=} opt_onlyClosePopups Whether only popups should be closed.
|
||||
* @see Blockly.WorkspaceSvg.hideChaff
|
||||
* @alias Blockly.hideChaff
|
||||
*/
|
||||
const hideChaff = function(opt_onlyClosePopups) {
|
||||
/** @type {!WorkspaceSvg} */ (common.getMainWorkspace())
|
||||
.hideChaff(opt_onlyClosePopups);
|
||||
};
|
||||
exports.hideChaff = hideChaff;
|
||||
|
||||
/**
|
||||
* Returns the main workspace. Returns the last used main workspace (based on
|
||||
* focus). Try not to use this function, particularly if there are multiple
|
||||
* Blockly instances on a page.
|
||||
* @return {!Workspace} The main workspace.
|
||||
* @see Blockly.common.getMainWorkspace
|
||||
* @alias Blockly.getMainWorkspace
|
||||
*/
|
||||
exports.getMainWorkspace = common.getMainWorkspace;
|
||||
|
||||
/**
|
||||
* Define blocks from an array of JSON block definitions, as might be generated
|
||||
* by the Blockly Developer Tools.
|
||||
* @param {!Array<!Object>} jsonArray An array of JSON block definitions.
|
||||
* @see Blockly.common.defineBlocksWithJsonArray
|
||||
* @alias Blockly.defineBlocksWithJsonArray
|
||||
*/
|
||||
exports.defineBlocksWithJsonArray = common.defineBlocksWithJsonArray;
|
||||
|
||||
/**
|
||||
* Set the parent container. This is the container element that the WidgetDiv,
|
||||
* dropDownDiv, and Tooltip are rendered into the first time `Blockly.inject`
|
||||
* is called.
|
||||
* This method is a NOP if called after the first ``Blockly.inject``.
|
||||
* @param {!Element} container The container element.
|
||||
* @see Blockly.common.setParentContainer
|
||||
* @alias Blockly.setParentContainer
|
||||
*/
|
||||
exports.setParentContainer = common.setParentContainer;
|
||||
|
||||
/*
|
||||
* Aliased functions and properties that used to be on the Blockly namespace.
|
||||
* Everything in this section is deprecated. Both external and internal code
|
||||
* should avoid using these functions and use the designated replacements.
|
||||
* Anything in this section may be removed in a future version of Blockly.
|
||||
*/
|
||||
|
||||
// Add accessors for properties on Blockly that have now been deprecated.
|
||||
Object.defineProperties(exports, {
|
||||
/**
|
||||
* Wrapper to window.alert() that app developers may override to
|
||||
* provide alternatives to the modal browser window.
|
||||
* @name Blockly.alert
|
||||
* @type {!function(string, function()=)}
|
||||
* @deprecated Use Blockly.dialog.alert / .setAlert() instead.
|
||||
* (December 2021)
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
alert: {
|
||||
set: function(newAlert) {
|
||||
deprecation.warn('Blockly.alert', 'December 2021', 'December 2022');
|
||||
dialog.setAlert(newAlert);
|
||||
},
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.alert', 'December 2021', 'December 2022',
|
||||
'Blockly.dialog.alert()');
|
||||
return dialog.alert;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Wrapper to window.confirm() that app developers may override to
|
||||
* provide alternatives to the modal browser window.
|
||||
* @name Blockly.confirm
|
||||
* @type {!function(string, function()=)}
|
||||
* @deprecated Use Blockly.dialog.confirm / .setConfirm() instead.
|
||||
* (December 2021)
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
confirm: {
|
||||
set: function(newConfirm) {
|
||||
deprecation.warn('Blockly.confirm', 'December 2021', 'December 2022');
|
||||
dialog.setConfirm(newConfirm);
|
||||
},
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.confirm', 'December 2021', 'December 2022',
|
||||
'Blockly.dialog.confirm()');
|
||||
return dialog.confirm;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The main workspace most recently used.
|
||||
* Set by Blockly.WorkspaceSvg.prototype.markFocused
|
||||
* @name Blockly.mainWorkspace
|
||||
* @type {Workspace}
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
mainWorkspace: {
|
||||
set: function(x) {
|
||||
common.setMainWorkspace(x);
|
||||
},
|
||||
get: function() {
|
||||
return common.getMainWorkspace();
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Wrapper to window.prompt() that app developers may override to
|
||||
* provide alternatives to the modal browser window. Built-in
|
||||
* browser prompts are often used for better text input experience
|
||||
* on mobile device. We strongly recommend testing mobile when
|
||||
* overriding this.
|
||||
* @name Blockly.prompt
|
||||
* @type {!function(string, string, function()=)}
|
||||
* @deprecated Use Blockly.dialog.prompt / .setPrompt() instead.
|
||||
* (December 2021)
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
prompt: {
|
||||
set: function(newPrompt) {
|
||||
deprecation.warn('Blockly.prompt', 'December 2021', 'December 2022');
|
||||
dialog.setPrompt(newPrompt);
|
||||
},
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.prompt', 'December 2021', 'December 2022',
|
||||
'Blockly.dialog.prompt()');
|
||||
return dialog.prompt;
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Currently selected block.
|
||||
* @name Blockly.selected
|
||||
* @type {?ICopyable}
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
selected: {
|
||||
get: function() {
|
||||
return common.getSelected();
|
||||
},
|
||||
set: function(newSelection) {
|
||||
common.setSelected(newSelection);
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The richness of block colours, regardless of the hue.
|
||||
* Must be in the range of 0 (inclusive) to 1 (exclusive).
|
||||
* @name Blockly.HSV_SATURATION
|
||||
* @type {number}
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
HSV_SATURATION: {
|
||||
get: function() {
|
||||
return utils.colour.getHsvSaturation();
|
||||
},
|
||||
set: function(newValue) {
|
||||
utils.colour.setHsvSaturation(newValue);
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The intensity of block colours, regardless of the hue.
|
||||
* Must be in the range of 0 (inclusive) to 1 (exclusive).
|
||||
* @name Blockly.HSV_VALUE
|
||||
* @type {number}
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
HSV_VALUE: {
|
||||
get: function() {
|
||||
return utils.colour.getHsvValue();
|
||||
},
|
||||
set: function(newValue) {
|
||||
utils.colour.setHsvValue(newValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the dimensions of the specified SVG image.
|
||||
* @param {!SVGElement} svg SVG image.
|
||||
* @return {!Size} Contains width and height properties.
|
||||
* @deprecated Use workspace.setCachedParentSvgSize. (2021 March 5)
|
||||
* @see Blockly.WorkspaceSvg.setCachedParentSvgSize
|
||||
* @alias Blockly.svgSize
|
||||
*/
|
||||
exports.svgSize = svgMath.svgSize;
|
||||
|
||||
/**
|
||||
* Size the workspace when the contents change. This also updates
|
||||
* scrollbars accordingly.
|
||||
* @param {!WorkspaceSvg} workspace The workspace to resize.
|
||||
* @deprecated Use workspace.resizeContents. (2021 December)
|
||||
* @see Blockly.WorkspaceSvg.resizeContents
|
||||
* @alias Blockly.resizeSvgContents
|
||||
*/
|
||||
const resizeSvgContentsLocal = function(workspace) {
|
||||
deprecation.warn(
|
||||
'Blockly.resizeSvgContents', 'December 2021', 'December 2022',
|
||||
'Blockly.WorkspaceSvg.resizeSvgContents');
|
||||
resizeSvgContents(workspace);
|
||||
};
|
||||
exports.resizeSvgContents = resizeSvgContentsLocal;
|
||||
|
||||
/**
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
* @param {!ICopyable} toCopy Block or Workspace Comment to be copied.
|
||||
* @deprecated Use Blockly.clipboard.copy(). (2021 December)
|
||||
* @see Blockly.clipboard.copy
|
||||
* @alias Blockly.copy
|
||||
*/
|
||||
const copy = function(toCopy) {
|
||||
deprecation.warn(
|
||||
'Blockly.copy', 'December 2021', 'December 2022',
|
||||
'Blockly.clipboard.copy');
|
||||
clipboard.copy(toCopy);
|
||||
};
|
||||
exports.copy = copy;
|
||||
|
||||
/**
|
||||
* Paste a block or workspace comment on to the main workspace.
|
||||
* @return {boolean} True if the paste was successful, false otherwise.
|
||||
* @deprecated Use Blockly.clipboard.paste(). (2021 December)
|
||||
* @see Blockly.clipboard.paste
|
||||
* @alias Blockly.paste
|
||||
*/
|
||||
const paste = function() {
|
||||
deprecation.warn(
|
||||
'Blockly.paste', 'December 2021', 'December 2022',
|
||||
'Blockly.clipboard.paste');
|
||||
return !!clipboard.paste();
|
||||
};
|
||||
exports.paste = paste;
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
* @param {!ICopyable} toDuplicate Block or Workspace Comment to be
|
||||
* copied.
|
||||
* @deprecated Use Blockly.clipboard.duplicate(). (2021 December)
|
||||
* @see Blockly.clipboard.duplicate
|
||||
* @alias Blockly.duplicate
|
||||
*/
|
||||
const duplicate = function(toDuplicate) {
|
||||
deprecation.warn(
|
||||
'Blockly.duplicate', 'December 2021', 'December 2022',
|
||||
'Blockly.clipboard.duplicate');
|
||||
clipboard.duplicate(toDuplicate);
|
||||
};
|
||||
exports.duplicate = duplicate;
|
||||
|
||||
/**
|
||||
* Is the given string a number (includes negative and decimals).
|
||||
* @param {string} str Input string.
|
||||
* @return {boolean} True if number, false otherwise.
|
||||
* @deprecated Use Blockly.utils.string.isNumber(str). (2021 December)
|
||||
* @see Blockly.utils.string.isNumber
|
||||
* @alias Blockly.isNumber
|
||||
*/
|
||||
const isNumber = function(str) {
|
||||
deprecation.warn(
|
||||
'Blockly.isNumber', 'December 2021', 'December 2022',
|
||||
'Blockly.utils.string.isNumber');
|
||||
return utils.string.isNumber(str);
|
||||
};
|
||||
exports.isNumber = isNumber;
|
||||
|
||||
/**
|
||||
* Convert a hue (HSV model) into an RGB hex triplet.
|
||||
* @param {number} hue Hue on a colour wheel (0-360).
|
||||
* @return {string} RGB code, e.g. '#5ba65b'.
|
||||
* @deprecated Use Blockly.utils.colour.hueToHex(). (2021 December)
|
||||
* @see Blockly.utils.colour.hueToHex
|
||||
* @alias Blockly.hueToHex
|
||||
*/
|
||||
const hueToHex = function(hue) {
|
||||
deprecation.warn(
|
||||
'Blockly.hueToHex', 'December 2021', 'December 2022',
|
||||
'Blockly.utils.colour.hueToHex');
|
||||
return colour.hueToHex(hue);
|
||||
};
|
||||
exports.hueToHex = hueToHex;
|
||||
|
||||
/**
|
||||
* Bind an event handler that should be called regardless of whether it is part
|
||||
* of the active touch stream.
|
||||
* Use this for events that are not part of a multi-part gesture (e.g.
|
||||
* mouseover for tooltips).
|
||||
* @param {!EventTarget} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {?Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @return {!browserEvents.Data} Opaque data that can be passed to
|
||||
* unbindEvent_.
|
||||
* @deprecated Use Blockly.browserEvents.bind(). (December 2021)
|
||||
* @see Blockly.browserEvents.bind
|
||||
* @alias Blockly.bindEvent_
|
||||
*/
|
||||
const bindEvent_ = function(node, name, thisObject, func) {
|
||||
deprecation.warn(
|
||||
'Blockly.bindEvent_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.bind');
|
||||
return browserEvents.bind(node, name, thisObject, func);
|
||||
};
|
||||
exports.bindEvent_ = bindEvent_;
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
* @param {!browserEvents.Data} bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @return {!Function} The function call.
|
||||
* @deprecated Use Blockly.browserEvents.unbind(). (December 2021)
|
||||
* @see browserEvents.unbind
|
||||
* @alias Blockly.unbindEvent_
|
||||
*/
|
||||
const unbindEvent_ = function(bindData) {
|
||||
deprecation.warn(
|
||||
'Blockly.unbindEvent_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.unbind');
|
||||
return browserEvents.unbind(bindData);
|
||||
};
|
||||
exports.unbindEvent_ = unbindEvent_;
|
||||
|
||||
/**
|
||||
* Bind an event handler that can be ignored if it is not part of the active
|
||||
* touch stream.
|
||||
* Use this for events that either start or continue a multi-part gesture (e.g.
|
||||
* mousedown or mousemove, which may be part of a drag or click).
|
||||
* @param {!EventTarget} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {?Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @param {boolean=} opt_noCaptureIdentifier True if triggering on this event
|
||||
* should not block execution of other event handlers on this touch or
|
||||
* other simultaneous touches. False by default.
|
||||
* @param {boolean=} opt_noPreventDefault True if triggering on this event
|
||||
* should prevent the default handler. False by default. If
|
||||
* opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be
|
||||
* provided.
|
||||
* @return {!browserEvents.Data} Opaque data that can be passed to
|
||||
* unbindEvent_.
|
||||
* @deprecated Use Blockly.browserEvents.conditionalBind(). (December 2021)
|
||||
* @see browserEvents.conditionalBind
|
||||
* @alias Blockly.bindEventWithChecks_
|
||||
*/
|
||||
const bindEventWithChecks_ = function(
|
||||
node, name, thisObject, func, opt_noCaptureIdentifier,
|
||||
opt_noPreventDefault) {
|
||||
deprecation.warn(
|
||||
'Blockly.bindEventWithChecks_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.conditionalBind');
|
||||
return browserEvents.conditionalBind(
|
||||
node, name, thisObject, func, opt_noCaptureIdentifier,
|
||||
opt_noPreventDefault);
|
||||
};
|
||||
exports.bindEventWithChecks_ = bindEventWithChecks_;
|
||||
|
||||
// Aliases to allow external code to access these values for legacy reasons.
|
||||
exports.COLLAPSE_CHARS = internalConstants.COLLAPSE_CHARS;
|
||||
exports.DRAG_STACK = internalConstants.DRAG_STACK;
|
||||
exports.OPPOSITE_TYPE = internalConstants.OPPOSITE_TYPE;
|
||||
exports.RENAME_VARIABLE_ID = internalConstants.RENAME_VARIABLE_ID;
|
||||
exports.DELETE_VARIABLE_ID = internalConstants.DELETE_VARIABLE_ID;
|
||||
exports.COLLAPSED_INPUT_NAME = constants.COLLAPSED_INPUT_NAME;
|
||||
exports.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.
|
||||
* @const {string}
|
||||
* @alias Blockly.VARIABLE_CATEGORY_NAME
|
||||
*/
|
||||
exports.VARIABLE_CATEGORY_NAME = 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.
|
||||
* @const {string}
|
||||
* @alias Blockly.VARIABLE_DYNAMIC_CATEGORY_NAME
|
||||
*/
|
||||
exports.VARIABLE_DYNAMIC_CATEGORY_NAME = VariablesDynamic.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
|
||||
* procedure blocks.
|
||||
* @const {string}
|
||||
* @alias Blockly.PROCEDURE_CATEGORY_NAME
|
||||
*/
|
||||
exports.PROCEDURE_CATEGORY_NAME = Procedures.CATEGORY_NAME;
|
||||
|
||||
// Re-export submodules that no longer declareLegacyNamespace.
|
||||
exports.ASTNode = ASTNode;
|
||||
exports.BasicCursor = BasicCursor;
|
||||
exports.Block = Block;
|
||||
exports.BlocklyOptions = BlocklyOptions;
|
||||
exports.BlockDragger = BlockDragger;
|
||||
exports.BlockDragSurfaceSvg = BlockDragSurfaceSvg;
|
||||
exports.BlockSvg = BlockSvg;
|
||||
exports.Blocks = Blocks;
|
||||
exports.Bubble = Bubble;
|
||||
exports.BubbleDragger = BubbleDragger;
|
||||
exports.CollapsibleToolboxCategory = CollapsibleToolboxCategory;
|
||||
exports.Comment = Comment;
|
||||
exports.ComponentManager = ComponentManager;
|
||||
exports.Connection = Connection;
|
||||
exports.ConnectionType = ConnectionType;
|
||||
exports.ConnectionChecker = ConnectionChecker;
|
||||
exports.ConnectionDB = ConnectionDB;
|
||||
exports.ContextMenu = ContextMenu;
|
||||
exports.ContextMenuItems = ContextMenuItems;
|
||||
exports.ContextMenuRegistry = ContextMenuRegistry;
|
||||
exports.Css = Css;
|
||||
exports.Cursor = Cursor;
|
||||
exports.DeleteArea = DeleteArea;
|
||||
exports.DragTarget = DragTarget;
|
||||
exports.DropDownDiv = dropDownDiv;
|
||||
exports.Events = Events;
|
||||
exports.Extensions = Extensions;
|
||||
exports.Field = Field;
|
||||
exports.FieldAngle = FieldAngle;
|
||||
exports.FieldCheckbox = FieldCheckbox;
|
||||
exports.FieldColour = FieldColour;
|
||||
exports.FieldDropdown = FieldDropdown;
|
||||
exports.FieldImage = FieldImage;
|
||||
exports.FieldLabel = FieldLabel;
|
||||
exports.FieldLabelSerializable = FieldLabelSerializable;
|
||||
exports.FieldMultilineInput = FieldMultilineInput;
|
||||
exports.FieldNumber = FieldNumber;
|
||||
exports.FieldTextInput = FieldTextInput;
|
||||
exports.FieldVariable = FieldVariable;
|
||||
exports.Flyout = Flyout;
|
||||
exports.FlyoutButton = FlyoutButton;
|
||||
exports.FlyoutMetricsManager = FlyoutMetricsManager;
|
||||
exports.Generator = Generator;
|
||||
exports.Gesture = Gesture;
|
||||
exports.Grid = Grid;
|
||||
exports.HorizontalFlyout = HorizontalFlyout;
|
||||
exports.IASTNodeLocation = IASTNodeLocation;
|
||||
exports.IASTNodeLocationSvg = IASTNodeLocationSvg;
|
||||
exports.IASTNodeLocationWithBlock = IASTNodeLocationWithBlock;
|
||||
exports.IAutoHideable = IAutoHideable;
|
||||
exports.IBlockDragger = IBlockDragger;
|
||||
exports.IBoundedElement = IBoundedElement;
|
||||
exports.IBubble = IBubble;
|
||||
exports.ICollapsibleToolboxItem = ICollapsibleToolboxItem;
|
||||
exports.IComponent = IComponent;
|
||||
exports.IConnectionChecker = IConnectionChecker;
|
||||
exports.IContextMenu = IContextMenu;
|
||||
exports.Icon = Icon;
|
||||
exports.ICopyable = ICopyable;
|
||||
exports.IDeletable = IDeletable;
|
||||
exports.IDeleteArea = IDeleteArea;
|
||||
exports.IDragTarget = IDragTarget;
|
||||
exports.IDraggable = IDraggable;
|
||||
exports.IFlyout = IFlyout;
|
||||
exports.IKeyboardAccessible = IKeyboardAccessible;
|
||||
exports.IMetricsManager = IMetricsManager;
|
||||
exports.IMovable = IMovable;
|
||||
exports.Input = Input;
|
||||
exports.InsertionMarkerManager = InsertionMarkerManager;
|
||||
exports.IPositionable = IPositionable;
|
||||
exports.IRegistrable = IRegistrable;
|
||||
exports.IRegistrableField = IRegistrableField;
|
||||
exports.ISelectable = ISelectable;
|
||||
exports.ISelectableToolboxItem = ISelectableToolboxItem;
|
||||
exports.IStyleable = IStyleable;
|
||||
exports.IToolbox = IToolbox;
|
||||
exports.IToolboxItem = IToolboxItem;
|
||||
exports.Marker = Marker;
|
||||
exports.MarkerManager = MarkerManager;
|
||||
exports.Menu = Menu;
|
||||
exports.MenuItem = MenuItem;
|
||||
exports.MetricsManager = MetricsManager;
|
||||
exports.Mutator = Mutator;
|
||||
exports.Msg = Msg;
|
||||
exports.Names = Names;
|
||||
exports.Options = Options;
|
||||
exports.Procedures = Procedures;
|
||||
exports.RenderedConnection = RenderedConnection;
|
||||
exports.Scrollbar = Scrollbar;
|
||||
exports.ScrollbarPair = ScrollbarPair;
|
||||
exports.ShortcutItems = ShortcutItems;
|
||||
exports.ShortcutRegistry = ShortcutRegistry;
|
||||
exports.TabNavigateCursor = TabNavigateCursor;
|
||||
exports.Theme = Theme;
|
||||
exports.Themes = Themes;
|
||||
exports.ThemeManager = ThemeManager;
|
||||
exports.Toolbox = Toolbox;
|
||||
exports.ToolboxCategory = ToolboxCategory;
|
||||
exports.ToolboxItem = ToolboxItem;
|
||||
exports.ToolboxSeparator = ToolboxSeparator;
|
||||
exports.Tooltip = Tooltip;
|
||||
exports.Touch = Touch;
|
||||
exports.TouchGesture = TouchGesture;
|
||||
exports.Trashcan = Trashcan;
|
||||
exports.VariableMap = VariableMap;
|
||||
exports.VariableModel = VariableModel;
|
||||
exports.Variables = Variables;
|
||||
exports.VariablesDynamic = VariablesDynamic;
|
||||
exports.VerticalFlyout = VerticalFlyout;
|
||||
exports.Warning = Warning;
|
||||
exports.WidgetDiv = WidgetDiv;
|
||||
exports.Workspace = Workspace;
|
||||
exports.WorkspaceAudio = WorkspaceAudio;
|
||||
exports.WorkspaceComment = WorkspaceComment;
|
||||
exports.WorkspaceCommentSvg = WorkspaceCommentSvg;
|
||||
exports.WorkspaceDragSurfaceSvg = WorkspaceDragSurfaceSvg;
|
||||
exports.WorkspaceDragger = WorkspaceDragger;
|
||||
exports.WorkspaceSvg = WorkspaceSvg;
|
||||
exports.Xml = Xml;
|
||||
exports.ZoomControls = ZoomControls;
|
||||
exports.blockAnimations = blockAnimations;
|
||||
exports.blockRendering = blockRendering;
|
||||
exports.browserEvents = browserEvents;
|
||||
exports.bumpObjects = bumpObjects;
|
||||
exports.clipboard = clipboard;
|
||||
exports.common = common;
|
||||
exports.config = config;
|
||||
/** @deprecated Use Blockly.ConnectionType instead. */
|
||||
exports.connectionTypes = ConnectionType;
|
||||
exports.constants = constants;
|
||||
exports.dialog = dialog;
|
||||
exports.fieldRegistry = fieldRegistry;
|
||||
exports.geras = geras;
|
||||
exports.inject = inject;
|
||||
exports.inputTypes = inputTypes;
|
||||
exports.minimalist = minimalist;
|
||||
exports.registry = registry;
|
||||
exports.serialization = {
|
||||
blocks: serializationBlocks,
|
||||
exceptions: serializationExceptions,
|
||||
priorities: serializationPriorities,
|
||||
registry: serializationRegistry,
|
||||
variables: serializationVariables,
|
||||
workspaces: serializationWorkspaces,
|
||||
ISerializer: ISerializer,
|
||||
};
|
||||
exports.thrasos = thrasos;
|
||||
exports.uiPosition = uiPosition;
|
||||
exports.utils = utils;
|
||||
exports.zelos = zelos;
|
||||
|
||||
// If Blockly is compiled with ADVANCED_COMPILATION and/or loaded as a
|
||||
// CJS or ES module there will not be a Blockly global variable
|
||||
// created. This can cause problems because a very common way of
|
||||
// loading translations is to use a <script> tag to load one of
|
||||
// msg/js/*.js, which consists of lines like:
|
||||
//
|
||||
// Blockly.Msg["ADD_COMMENT"] = "Add Comment";
|
||||
// Blockly.Msg["CLEAN_UP"] = "Clean up Blocks";
|
||||
//
|
||||
// This obviously only works if Blockly.Msg is the Msg export from the
|
||||
// Blockly.Msg module - so make sure it is, but only if there is not
|
||||
// yet a Blockly global variable.
|
||||
if (!('Blockly' in globalThis)) {
|
||||
globalThis['Blockly'] = {'Msg': Msg};
|
||||
}
|
||||
|
||||
// Temporary hack to copy accessor properties from exports to the
|
||||
// global Blockly object as the routine to copy exports in
|
||||
// goog.exportPath_ (see closure/goog/base.js) invoked by
|
||||
// declareLegacyNamespace only copies normal data properties, not
|
||||
// accessors. This can be removed once all remaining calls to
|
||||
// declareLegacyNamspace have been removed.
|
||||
//
|
||||
// This is only needed in uncompiled mode (see
|
||||
// google/blockly-samples#902); in compiled mode the exports object is
|
||||
// already the value of globalThis['Blockly'].
|
||||
//
|
||||
// Note that this code will still attempt to redefine accessors on a
|
||||
// previously-imported copy of the Blockly library if both are
|
||||
// imported in uncompiled mode. This will fail with TypeError as the
|
||||
// accessors are nonconfigurable (which is good, as otherwise one
|
||||
// accessors on one copy would call get/set functions on the other
|
||||
// copy!)
|
||||
/* eslint-disable-next-line no-undef */
|
||||
if (!COMPILED && typeof globalThis['Blockly'] === 'object' &&
|
||||
globalThis['Blockly'] !== exports) {
|
||||
const descriptors = Object.getOwnPropertyDescriptors(exports);
|
||||
const accessors = {};
|
||||
for (const key in descriptors) {
|
||||
if (descriptors[key].get || descriptors[key].set) {
|
||||
accessors[key] = descriptors[key];
|
||||
}
|
||||
}
|
||||
Object.defineProperties(globalThis['Blockly'], accessors);
|
||||
}
|
||||
+754
@@ -0,0 +1,754 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2011 Google LLC
|
||||
* 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');
|
||||
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_block_create.js';
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/workspace_events.js';
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_ui.js';
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_ui_base.js';
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_var_create.js';
|
||||
|
||||
import {Block} from './block.js';
|
||||
import * as blockAnimations from './block_animations.js';
|
||||
import {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import {BlockDragger} from './block_dragger.js';
|
||||
import {BlockSvg} from './block_svg.js';
|
||||
import {BlocklyOptions} from './blockly_options.js';
|
||||
import {Blocks} from './blocks.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import {Bubble} from './bubble.js';
|
||||
import {BubbleDragger} from './bubble_dragger.js';
|
||||
import * as bumpObjects from './bump_objects.js';
|
||||
import * as clipboard from './clipboard.js';
|
||||
import {Comment} from './comment.js';
|
||||
import * as common from './common.js';
|
||||
import {ComponentManager} from './component_manager.js';
|
||||
import {config} from './config.js';
|
||||
import {Connection} from './connection.js';
|
||||
import {ConnectionChecker} from './connection_checker.js';
|
||||
import {ConnectionDB} from './connection_db.js';
|
||||
import {ConnectionType} from './connection_type.js';
|
||||
import * as ContextMenu from './contextmenu.js';
|
||||
import * as ContextMenuItems from './contextmenu_items.js';
|
||||
import {ContextMenuRegistry} from './contextmenu_registry.js';
|
||||
import * as Css from './css.js';
|
||||
import {DeleteArea} from './delete_area.js';
|
||||
import * as dialog from './dialog.js';
|
||||
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} from './field.js';
|
||||
import {FieldAngle} from './field_angle.js';
|
||||
import {FieldCheckbox} from './field_checkbox.js';
|
||||
import {FieldColour} from './field_colour.js';
|
||||
import {FieldDropdown} from './field_dropdown.js';
|
||||
import {FieldImage} from './field_image.js';
|
||||
import {FieldLabel} from './field_label.js';
|
||||
import {FieldLabelSerializable} from './field_label_serializable.js';
|
||||
import {FieldMultilineInput} from './field_multilineinput.js';
|
||||
import {FieldNumber} from './field_number.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInput} from './field_textinput.js';
|
||||
import {FieldVariable} from './field_variable.js';
|
||||
import {Flyout} from './flyout_base.js';
|
||||
import {FlyoutButton} from './flyout_button.js';
|
||||
import {HorizontalFlyout} from './flyout_horizontal.js';
|
||||
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
|
||||
import {VerticalFlyout} from './flyout_vertical.js';
|
||||
import {Generator} from './generator.js';
|
||||
import {Gesture} from './gesture.js';
|
||||
import {Grid} from './grid.js';
|
||||
import {Icon} from './icon.js';
|
||||
import {inject} from './inject.js';
|
||||
import {Align, Input} from './input.js';
|
||||
import {inputTypes} from './input_types.js';
|
||||
import {InsertionMarkerManager} from './insertion_marker_manager.js';
|
||||
import {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
|
||||
import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
|
||||
import {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js';
|
||||
import {IAutoHideable} from './interfaces/i_autohideable.js';
|
||||
import {IBlockDragger} from './interfaces/i_block_dragger.js';
|
||||
import {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||
import {IBubble} from './interfaces/i_bubble.js';
|
||||
import {ICollapsibleToolboxItem} from './interfaces/i_collapsible_toolbox_item.js';
|
||||
import {IComponent} from './interfaces/i_component.js';
|
||||
import {IConnectionChecker} from './interfaces/i_connection_checker.js';
|
||||
import {IContextMenu} from './interfaces/i_contextmenu.js';
|
||||
import {ICopyable} from './interfaces/i_copyable.js';
|
||||
import {IDeletable} from './interfaces/i_deletable.js';
|
||||
import {IDeleteArea} from './interfaces/i_delete_area.js';
|
||||
import {IDragTarget} from './interfaces/i_drag_target.js';
|
||||
import {IDraggable} from './interfaces/i_draggable.js';
|
||||
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 {IPositionable} from './interfaces/i_positionable.js';
|
||||
import {IRegistrable} from './interfaces/i_registrable.js';
|
||||
import {IRegistrableField} from './interfaces/i_registrable_field.js';
|
||||
import {ISelectable} from './interfaces/i_selectable.js';
|
||||
import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.js';
|
||||
import {ISerializer as SerializerInterface} from './interfaces/i_serializer.js';
|
||||
import {IStyleable} from './interfaces/i_styleable.js';
|
||||
import {IToolbox} from './interfaces/i_toolbox.js';
|
||||
import {IToolboxItem} from './interfaces/i_toolbox_item.js';
|
||||
import * as internalConstants from './internal_constants.js';
|
||||
import {ASTNode} from './keyboard_nav/ast_node.js';
|
||||
import {BasicCursor} from './keyboard_nav/basic_cursor.js';
|
||||
import {Cursor} from './keyboard_nav/cursor.js';
|
||||
import {Marker} from './keyboard_nav/marker.js';
|
||||
import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js';
|
||||
import {MarkerManager} from './marker_manager.js';
|
||||
import {Menu} from './menu.js';
|
||||
import {MenuItem} from './menuitem.js';
|
||||
import {MetricsManager} from './metrics_manager.js';
|
||||
import {Msg, setLocale} from './msg.js';
|
||||
import {Mutator} from './mutator.js';
|
||||
import {Names} from './names.js';
|
||||
import {Options} from './options.js';
|
||||
import * as uiPosition from './positionable_helpers.js';
|
||||
import * as Procedures from './procedures.js';
|
||||
import * as registry from './registry.js';
|
||||
import {RenderedConnection} from './rendered_connection.js';
|
||||
import * as blockRendering from './renderers/common/block_rendering.js';
|
||||
import * as constants from './constants.js';
|
||||
import * as geras from './renderers/geras/geras.js';
|
||||
import * as minimalist from './renderers/minimalist/minimalist.js';
|
||||
import * as thrasos from './renderers/thrasos/thrasos.js';
|
||||
import * as zelos from './renderers/zelos/zelos.js';
|
||||
import {Scrollbar} from './scrollbar.js';
|
||||
import {ScrollbarPair} from './scrollbar_pair.js';
|
||||
import * as serializationBlocks from './serialization/blocks.js';
|
||||
import * as serializationExceptions from './serialization/exceptions.js';
|
||||
import * as serializationPriorities from './serialization/priorities.js';
|
||||
import * as serializationRegistry from './serialization/registry.js';
|
||||
import * as serializationVariables from './serialization/variables.js';
|
||||
import * as serializationWorkspaces from './serialization/workspaces.js';
|
||||
import * as ShortcutItems from './shortcut_items.js';
|
||||
import {ShortcutRegistry} from './shortcut_registry.js';
|
||||
import {Theme} from './theme.js';
|
||||
import * as Themes from './theme/themes.js';
|
||||
import {ThemeManager} from './theme_manager.js';
|
||||
import {ToolboxCategory} from './toolbox/category.js';
|
||||
import {CollapsibleToolboxCategory} from './toolbox/collapsible_category.js';
|
||||
import {ToolboxSeparator} from './toolbox/separator.js';
|
||||
import {Toolbox} from './toolbox/toolbox.js';
|
||||
import {ToolboxItem} from './toolbox/toolbox_item.js';
|
||||
import * as Tooltip from './tooltip.js';
|
||||
import * as Touch from './touch.js';
|
||||
import {TouchGesture} from './touch_gesture.js';
|
||||
import {Trashcan} from './trashcan.js';
|
||||
import * as utils from './utils.js';
|
||||
import * as colour from './utils/colour.js';
|
||||
import * as deprecation from './utils/deprecation.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
import * as toolbox from './utils/toolbox.js';
|
||||
import {VariableMap} from './variable_map.js';
|
||||
import {VariableModel} from './variable_model.js';
|
||||
import * as Variables from './variables.js';
|
||||
import * as VariablesDynamic from './variables_dynamic.js';
|
||||
import {Warning} from './warning.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
import {Workspace} from './workspace.js';
|
||||
import {WorkspaceAudio} from './workspace_audio.js';
|
||||
import {WorkspaceComment} from './workspace_comment.js';
|
||||
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
||||
import {WorkspaceDragSurfaceSvg} from './workspace_drag_surface_svg.js';
|
||||
import {WorkspaceDragger} from './workspace_dragger.js';
|
||||
import {resizeSvgContents as realResizeSvgContents, WorkspaceSvg} from './workspace_svg.js';
|
||||
import * as Xml from './xml.js';
|
||||
import {ZoomControls} from './zoom_controls.js';
|
||||
|
||||
|
||||
/**
|
||||
* Blockly core version.
|
||||
* This constant is overridden by the build script (npm run build) to the value
|
||||
* of the version in package.json. This is done by the Closure Compiler in the
|
||||
* buildCompressed gulp task.
|
||||
* For local builds, you can pass --define='Blockly.VERSION=X.Y.Z' to the
|
||||
* compiler to override this constant.
|
||||
*
|
||||
* @define {string}
|
||||
* @alias Blockly.VERSION
|
||||
*/
|
||||
export const VERSION = 'uncompiled';
|
||||
|
||||
/*
|
||||
* Top-level functions and properties on the Blockly namespace.
|
||||
* These are used only in external code. Do not reference these
|
||||
* from internal code as importing from this file can cause circular
|
||||
* dependencies. Do not add new functions here. There is probably a better
|
||||
* namespace to put new functions on.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Aliases for input alignments used in block defintions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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;
|
||||
/*
|
||||
* Aliases for constants used for connection and input types.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/** Aliases for toolbox positions. */
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/*
|
||||
* Other aliased functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Size the SVG image to completely fill its container. Call this when the view
|
||||
* actually changes sizes (e.g. on a window resize/device orientation change).
|
||||
* See workspace.resizeContents to resize the workspace when the contents
|
||||
* change (e.g. when a block is added or removed).
|
||||
* Record the height/width of the SVG image.
|
||||
*
|
||||
* @param workspace Any workspace in the SVG.
|
||||
* @see Blockly.common.svgResize
|
||||
* @alias Blockly.svgResize
|
||||
*/
|
||||
export const svgResize = common.svgResize;
|
||||
|
||||
/**
|
||||
* Close tooltips, context menus, dropdown selections, etc.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main workspace. Returns the last used main workspace (based on
|
||||
* focus). Try not to use this function, particularly if there are multiple
|
||||
* Blockly instances on a page.
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* Define blocks from an array of JSON block definitions, as might be generated
|
||||
* by the Blockly Developer Tools.
|
||||
*
|
||||
* @param jsonArray An array of JSON block definitions.
|
||||
* @see Blockly.common.defineBlocksWithJsonArray
|
||||
* @alias Blockly.defineBlocksWithJsonArray
|
||||
*/
|
||||
export const defineBlocksWithJsonArray = common.defineBlocksWithJsonArray;
|
||||
|
||||
/**
|
||||
* Set the parent container. This is the container element that the WidgetDiv,
|
||||
* dropDownDiv, and Tooltip are rendered into the first time `Blockly.inject`
|
||||
* is called.
|
||||
* This method is a NOP if called after the first `Blockly.inject`.
|
||||
*
|
||||
* @param container The container element.
|
||||
* @see Blockly.common.setParentContainer
|
||||
* @alias Blockly.setParentContainer
|
||||
*/
|
||||
export const setParentContainer = common.setParentContainer;
|
||||
|
||||
/**
|
||||
* Size the workspace when the contents change. This also updates
|
||||
* scrollbars accordingly.
|
||||
*
|
||||
* @param workspace The workspace to resize.
|
||||
* @deprecated Use **workspace.resizeContents** instead.
|
||||
* @see Blockly.WorkspaceSvg.resizeContents
|
||||
* @alias Blockly.resizeSvgContents
|
||||
*/
|
||||
function resizeSvgContentsLocal(workspace: WorkspaceSvg) {
|
||||
deprecation.warn(
|
||||
'Blockly.resizeSvgContents', 'December 2021', 'December 2022',
|
||||
'Blockly.WorkspaceSvg.resizeSvgContents');
|
||||
realResizeSvgContents(workspace);
|
||||
}
|
||||
export const resizeSvgContents = resizeSvgContentsLocal;
|
||||
|
||||
/**
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
*
|
||||
* @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(
|
||||
'Blockly.copy', 'December 2021', 'December 2022',
|
||||
'Blockly.clipboard.copy');
|
||||
clipboard.copy(toCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a block or workspace comment on to the main workspace.
|
||||
*
|
||||
* @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(
|
||||
'Blockly.paste', 'December 2021', 'December 2022',
|
||||
'Blockly.clipboard.paste');
|
||||
return !!clipboard.paste();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
*
|
||||
* @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(
|
||||
'Blockly.duplicate', 'December 2021', 'December 2022',
|
||||
'Blockly.clipboard.duplicate');
|
||||
clipboard.duplicate(toDuplicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given string a number (includes negative and decimals).
|
||||
*
|
||||
* @param str Input string.
|
||||
* @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(
|
||||
'Blockly.isNumber', 'December 2021', 'December 2022',
|
||||
'Blockly.utils.string.isNumber');
|
||||
return utils.string.isNumber(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a hue (HSV model) into an RGB hex triplet.
|
||||
*
|
||||
* @param hue Hue on a colour wheel (0-360).
|
||||
* @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(
|
||||
'Blockly.hueToHex', 'December 2021', 'December 2022',
|
||||
'Blockly.utils.colour.hueToHex');
|
||||
return colour.hueToHex(hue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind an event handler that should be called regardless of whether it is part
|
||||
* of the active touch stream.
|
||||
* Use this for events that are not part of a multi-part gesture (e.g.
|
||||
* mouseover for tooltips).
|
||||
*
|
||||
* @param node Node upon which to listen.
|
||||
* @param name Event name to listen to (e.g. 'mousedown').
|
||||
* @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_.
|
||||
* @deprecated Use **Blockly.browserEvents.bind** instead.
|
||||
* @see Blockly.browserEvents.bind
|
||||
* @alias Blockly.bindEvent_
|
||||
*/
|
||||
export function bindEvent_(
|
||||
node: EventTarget, name: string, thisObject: Object|null,
|
||||
func: Function): browserEvents.Data {
|
||||
deprecation.warn(
|
||||
'Blockly.bindEvent_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.bind');
|
||||
return browserEvents.bind(node, name, thisObject, func);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
*
|
||||
* @param bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @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(
|
||||
'Blockly.unbindEvent_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.unbind');
|
||||
return browserEvents.unbind(bindData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind an event handler that can be ignored if it is not part of the active
|
||||
* touch stream.
|
||||
* Use this for events that either start or continue a multi-part gesture (e.g.
|
||||
* mousedown or mousemove, which may be part of a drag or click).
|
||||
*
|
||||
* @param node Node upon which to listen.
|
||||
* @param name Event name to listen to (e.g. 'mousedown').
|
||||
* @param thisObject The value of 'this' in the function.
|
||||
* @param func Function to call when event is triggered.
|
||||
* @param opt_noCaptureIdentifier True if triggering on this event should not
|
||||
* block execution of other event handlers on this touch or other
|
||||
* simultaneous touches. False by default.
|
||||
* @param opt_noPreventDefault True if triggering on this event should prevent
|
||||
* the default handler. False by default. If opt_noPreventDefault is
|
||||
* provided, opt_noCaptureIdentifier must also be provided.
|
||||
* @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,
|
||||
opt_noCaptureIdentifier?: boolean,
|
||||
opt_noPreventDefault?: boolean): browserEvents.Data {
|
||||
deprecation.warn(
|
||||
'Blockly.bindEventWithChecks_', 'December 2021', 'December 2022',
|
||||
'Blockly.browserEvents.conditionalBind');
|
||||
return browserEvents.conditionalBind(
|
||||
node, name, thisObject, func, opt_noCaptureIdentifier,
|
||||
opt_noPreventDefault);
|
||||
}
|
||||
|
||||
// Aliases to allow external code to access these values for legacy reasons.
|
||||
export const COLLAPSE_CHARS = internalConstants.COLLAPSE_CHARS;
|
||||
export const DRAG_STACK = internalConstants.DRAG_STACK;
|
||||
export const OPPOSITE_TYPE = internalConstants.OPPOSITE_TYPE;
|
||||
export const RENAME_VARIABLE_ID = internalConstants.RENAME_VARIABLE_ID;
|
||||
export const DELETE_VARIABLE_ID = internalConstants.DELETE_VARIABLE_ID;
|
||||
export const COLLAPSED_INPUT_NAME = constants.COLLAPSED_INPUT_NAME;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
// Context for why we need to monkey-patch in these functions (internal):
|
||||
// https://docs.google.com/document/d/1MbO0LEA-pAyx1ErGLJnyUqTLrcYTo-5zga9qplnxeXo/edit?usp=sharing&resourcekey=0-5h_32-i-dHwHjf_9KYEVKg
|
||||
|
||||
// clang-format off
|
||||
Workspace.prototype.newBlock =
|
||||
function(prototypeName: string, opt_id?: string): Block {
|
||||
return new Block(this, prototypeName, opt_id);
|
||||
};
|
||||
|
||||
WorkspaceSvg.prototype.newBlock =
|
||||
function(prototypeName: string, opt_id?: string): BlockSvg {
|
||||
return new BlockSvg(this, prototypeName, opt_id);
|
||||
};
|
||||
|
||||
WorkspaceSvg.newTrashcan = function(workspace: WorkspaceSvg): Trashcan {
|
||||
return new Trashcan(workspace);
|
||||
};
|
||||
|
||||
WorkspaceCommentSvg.prototype.showContextMenu =
|
||||
function(this: WorkspaceCommentSvg, e: Event) {
|
||||
if (this.workspace.options.readOnly) {
|
||||
return;
|
||||
}
|
||||
const menuOptions = [];
|
||||
|
||||
if (this.isDeletable() && this.isMovable()) {
|
||||
menuOptions.push(ContextMenu.commentDuplicateOption(this));
|
||||
menuOptions.push(ContextMenu.commentDeleteOption(this));
|
||||
}
|
||||
|
||||
ContextMenu.show(e, menuOptions, this.RTL);
|
||||
};
|
||||
|
||||
Mutator.prototype.newWorkspaceSvg =
|
||||
function(options: Options): WorkspaceSvg {
|
||||
return new WorkspaceSvg(options);
|
||||
};
|
||||
|
||||
Names.prototype.populateProcedures =
|
||||
function(this: Names, workspace: Workspace) {
|
||||
const procedures = Procedures.allProcedures(workspace);
|
||||
// Flatten the return vs no-return procedure lists.
|
||||
const flattenedProcedures: AnyDuringMigration[][] =
|
||||
procedures[0].concat(procedures[1]);
|
||||
for (let i = 0; i < flattenedProcedures.length; i++) {
|
||||
this.getName(flattenedProcedures[i][0], Names.NameType.PROCEDURE);
|
||||
}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
// Re-export submodules that no longer declareLegacyNamespace.
|
||||
export {browserEvents};
|
||||
export {ContextMenu};
|
||||
export {ContextMenuItems};
|
||||
export {Css};
|
||||
export {Events};
|
||||
export {Extensions};
|
||||
export {Procedures};
|
||||
export {ShortcutItems};
|
||||
export {Themes};
|
||||
export {Tooltip};
|
||||
export {Touch};
|
||||
export {Variables};
|
||||
export {VariablesDynamic};
|
||||
export {WidgetDiv};
|
||||
export {Xml};
|
||||
export {blockAnimations};
|
||||
export {blockRendering};
|
||||
export {bumpObjects};
|
||||
export {clipboard};
|
||||
export {common};
|
||||
export {constants};
|
||||
export {dialog};
|
||||
export {fieldRegistry};
|
||||
export {geras};
|
||||
export {minimalist};
|
||||
export {registry};
|
||||
export {thrasos};
|
||||
export {uiPosition};
|
||||
export {utils};
|
||||
export {zelos};
|
||||
export {ASTNode};
|
||||
export {BasicCursor};
|
||||
export {Block};
|
||||
export {BlocklyOptions};
|
||||
export {BlockDragger};
|
||||
export {BlockDragSurfaceSvg};
|
||||
export {BlockSvg};
|
||||
export {Blocks};
|
||||
export {Bubble};
|
||||
export {BubbleDragger};
|
||||
export {CollapsibleToolboxCategory};
|
||||
export {Comment};
|
||||
export {ComponentManager};
|
||||
export {Connection};
|
||||
export {ConnectionType};
|
||||
export {ConnectionChecker};
|
||||
export {ConnectionDB};
|
||||
export {ContextMenuRegistry};
|
||||
export {Cursor};
|
||||
export {DeleteArea};
|
||||
export {DragTarget};
|
||||
export const DropDownDiv = dropDownDiv;
|
||||
export {Field};
|
||||
export {FieldAngle};
|
||||
export {FieldCheckbox};
|
||||
export {FieldColour};
|
||||
export {FieldDropdown};
|
||||
export {FieldImage};
|
||||
export {FieldLabel};
|
||||
export {FieldLabelSerializable};
|
||||
export {FieldMultilineInput};
|
||||
export {FieldNumber};
|
||||
export {FieldTextInput};
|
||||
export {FieldVariable};
|
||||
export {Flyout};
|
||||
export {FlyoutButton};
|
||||
export {FlyoutMetricsManager};
|
||||
export {Generator};
|
||||
export {Gesture};
|
||||
export {Grid};
|
||||
export {HorizontalFlyout};
|
||||
export {IASTNodeLocation};
|
||||
export {IASTNodeLocationSvg};
|
||||
export {IASTNodeLocationWithBlock};
|
||||
export {IAutoHideable};
|
||||
export {IBlockDragger};
|
||||
export {IBoundedElement};
|
||||
export {IBubble};
|
||||
export {ICollapsibleToolboxItem};
|
||||
export {IComponent};
|
||||
export {IConnectionChecker};
|
||||
export {IContextMenu};
|
||||
export {Icon};
|
||||
export {ICopyable};
|
||||
export {IDeletable};
|
||||
export {IDeleteArea};
|
||||
export {IDragTarget};
|
||||
export {IDraggable};
|
||||
export {IFlyout};
|
||||
export {IKeyboardAccessible};
|
||||
export {IMetricsManager};
|
||||
export {IMovable};
|
||||
export {Input};
|
||||
export {InsertionMarkerManager};
|
||||
export {IPositionable};
|
||||
export {IRegistrable};
|
||||
export {IRegistrableField};
|
||||
export {ISelectable};
|
||||
export {ISelectableToolboxItem};
|
||||
export {IStyleable};
|
||||
export {IToolbox};
|
||||
export {IToolboxItem};
|
||||
export {Marker};
|
||||
export {MarkerManager};
|
||||
export {Menu};
|
||||
export {MenuItem};
|
||||
export {MetricsManager};
|
||||
export {Mutator};
|
||||
export {Msg, setLocale};
|
||||
export {Names};
|
||||
export {Options};
|
||||
export {RenderedConnection};
|
||||
export {Scrollbar};
|
||||
export {ScrollbarPair};
|
||||
export {ShortcutRegistry};
|
||||
export {TabNavigateCursor};
|
||||
export {Theme};
|
||||
export {ThemeManager};
|
||||
export {Toolbox};
|
||||
export {ToolboxCategory};
|
||||
export {ToolboxItem};
|
||||
export {ToolboxSeparator};
|
||||
export {TouchGesture};
|
||||
export {Trashcan};
|
||||
export {VariableMap};
|
||||
export {VariableModel};
|
||||
export {VerticalFlyout};
|
||||
export {Warning};
|
||||
export {Workspace};
|
||||
export {WorkspaceAudio};
|
||||
export {WorkspaceComment};
|
||||
export {WorkspaceCommentSvg};
|
||||
export {WorkspaceDragSurfaceSvg};
|
||||
export {WorkspaceDragger};
|
||||
export {WorkspaceSvg};
|
||||
export {ZoomControls};
|
||||
export {config};
|
||||
/** @deprecated Use Blockly.ConnectionType instead. */
|
||||
export const connectionTypes = ConnectionType;
|
||||
export {inject};
|
||||
export {inputTypes};
|
||||
export namespace serialization {
|
||||
export const blocks = serializationBlocks;
|
||||
export const exceptions = serializationExceptions;
|
||||
export const priorities = serializationPriorities;
|
||||
export const registry = serializationRegistry;
|
||||
export const variables = serializationVariables;
|
||||
export const workspaces = serializationWorkspaces;
|
||||
export type ISerializer = SerializerInterface;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object that defines user-specified options for the workspace.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Object that defines user-specified options for the workspace.
|
||||
* @namespace Blockly.BlocklyOptions
|
||||
*/
|
||||
goog.module('Blockly.BlocklyOptions');
|
||||
|
||||
|
||||
/**
|
||||
* Blockly options.
|
||||
* This interface is further described in
|
||||
* `typings/parts/blockly-interfaces.d.ts`.
|
||||
* @interface
|
||||
* @alias Blockly.BlocklyOptions
|
||||
*/
|
||||
const BlocklyOptions = function() {};
|
||||
|
||||
exports.BlocklyOptions = BlocklyOptions;
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* 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');
|
||||
|
||||
import type {Theme, ITheme} from './theme.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import type {ToolboxDefinition} from './utils/toolbox.js';
|
||||
|
||||
|
||||
/**
|
||||
* Blockly options.
|
||||
*
|
||||
* @alias Blockly.BlocklyOptions
|
||||
*/
|
||||
export interface BlocklyOptions {
|
||||
collapse?: boolean;
|
||||
comments?: boolean;
|
||||
css?: boolean;
|
||||
disable?: boolean;
|
||||
grid?: GridOptions;
|
||||
horizontalLayout?: boolean;
|
||||
maxBlocks?: number;
|
||||
maxInstances?: {[blockType: string]: number};
|
||||
media?: string;
|
||||
move?: MoveOptions;
|
||||
oneBasedIndex?: boolean;
|
||||
readOnly?: boolean;
|
||||
renderer?: string;
|
||||
rendererOverrides?: {[rendererConstant: string]: any};
|
||||
rtl?: boolean;
|
||||
scrollbars?: ScrollbarOptions|boolean;
|
||||
sounds?: boolean;
|
||||
theme?: Theme|string|ITheme;
|
||||
toolbox?: string|ToolboxDefinition|Element;
|
||||
toolboxPosition?: string;
|
||||
trashcan?: boolean;
|
||||
maxTrashcanContents?: number;
|
||||
plugins?: {[key: string]: (new(...p1: any[]) => any)|string};
|
||||
zoom?: ZoomOptions;
|
||||
parentWorkspace?: WorkspaceSvg;
|
||||
}
|
||||
|
||||
export interface GridOptions {
|
||||
colour?: string;
|
||||
length?: number;
|
||||
snap?: boolean;
|
||||
spacing?: number;
|
||||
}
|
||||
|
||||
export interface MoveOptions {
|
||||
drag?: boolean;
|
||||
scrollbars?: boolean|ScrollbarOptions;
|
||||
wheel?: boolean;
|
||||
}
|
||||
|
||||
export interface ScrollbarOptions {
|
||||
horizontal?: boolean;
|
||||
vertical?: boolean;
|
||||
}
|
||||
|
||||
export interface ZoomOptions {
|
||||
controls?: boolean;
|
||||
maxScale?: number;
|
||||
minScale?: number;
|
||||
pinch?: boolean;
|
||||
scaleSpeed?: number;
|
||||
startScale?: number;
|
||||
wheel?: boolean;
|
||||
}
|
||||
@@ -4,30 +4,24 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A mapping of block type names to block prototype objects.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A mapping of block type names to block prototype objects.
|
||||
*
|
||||
* @namespace Blockly.blocks
|
||||
*/
|
||||
goog.module('Blockly.blocks');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.blocks');
|
||||
|
||||
|
||||
/**
|
||||
* A block definition. For now this very lose, but it can potentially
|
||||
* be refined e.g. by replacing this typedef with a class definition.
|
||||
* @typedef {!Object}
|
||||
*/
|
||||
let BlockDefinition;
|
||||
exports.BlockDefinition = BlockDefinition;
|
||||
export type BlockDefinition = AnyDuringMigration;
|
||||
|
||||
/**
|
||||
* A mapping of block type names to block prototype objects.
|
||||
* @type {!Object<string,!BlockDefinition>}
|
||||
*
|
||||
* @alias Blockly.blocks.Blocks
|
||||
*/
|
||||
const Blocks = Object.create(null);
|
||||
exports.Blocks = Blocks;
|
||||
export const Blocks: {[key: string]: BlockDefinition} = Object.create(null);
|
||||
@@ -4,37 +4,30 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Browser event handling.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Browser event handling.
|
||||
*
|
||||
* @namespace Blockly.browserEvents
|
||||
*/
|
||||
goog.module('Blockly.browserEvents');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.browserEvents');
|
||||
|
||||
const Touch = goog.require('Blockly.Touch');
|
||||
const userAgent = goog.require('Blockly.utils.userAgent');
|
||||
const {globalThis} = goog.require('Blockly.utils.global');
|
||||
import * as Touch from './touch.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
|
||||
|
||||
/**
|
||||
* Blockly opaque event data used to unbind events when using
|
||||
* `bind` and `conditionalBind`.
|
||||
* @typedef {!Array<!Array>}
|
||||
*
|
||||
* @alias Blockly.browserEvents.Data
|
||||
*/
|
||||
let Data;
|
||||
exports.Data = Data;
|
||||
export type Data = [EventTarget, string, (e: Event) => void][];
|
||||
|
||||
/**
|
||||
* The multiplier for scroll wheel deltas using the line delta mode.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
|
||||
* for more information on deltaMode.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const LINE_MODE_MULTIPLIER = 40;
|
||||
|
||||
@@ -42,8 +35,6 @@ const LINE_MODE_MULTIPLIER = 40;
|
||||
* The multiplier for scroll wheel deltas using the page delta mode.
|
||||
* See https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/deltaMode
|
||||
* for more information on deltaMode.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const PAGE_MODE_MULTIPLIER = 125;
|
||||
|
||||
@@ -52,26 +43,29 @@ const PAGE_MODE_MULTIPLIER = 125;
|
||||
* touch stream.
|
||||
* Use this for events that either start or continue a multi-part gesture (e.g.
|
||||
* mousedown or mousemove, which may be part of a drag or click).
|
||||
* @param {!EventTarget} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {?Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @param {boolean=} opt_noCaptureIdentifier True if triggering on this event
|
||||
* should not block execution of other event handlers on this touch or
|
||||
* other simultaneous touches. False by default.
|
||||
* @param {boolean=} opt_noPreventDefault True if triggering on this event
|
||||
* should prevent the default handler. False by default. If
|
||||
* opt_noPreventDefault is provided, opt_noCaptureIdentifier must also be
|
||||
* provided.
|
||||
* @return {!Data} Opaque data that can be passed to
|
||||
* unbindEvent_.
|
||||
*
|
||||
* @param node Node upon which to listen.
|
||||
* @param name Event name to listen to (e.g. 'mousedown').
|
||||
* @param thisObject The value of 'this' in the function.
|
||||
* @param func Function to call when event is triggered.
|
||||
* @param opt_noCaptureIdentifier True if triggering on this event should not
|
||||
* block execution of other event handlers on this touch or other
|
||||
* simultaneous touches. False by default.
|
||||
* @param opt_noPreventDefault True if triggering on this event should prevent
|
||||
* the default handler. False by default. If opt_noPreventDefault is
|
||||
* provided, opt_noCaptureIdentifier must also be provided.
|
||||
* @returns Opaque data that can be passed to unbindEvent_.
|
||||
* @alias Blockly.browserEvents.conditionalBind
|
||||
*/
|
||||
const conditionalBind = function(
|
||||
node, name, thisObject, func, opt_noCaptureIdentifier,
|
||||
opt_noPreventDefault) {
|
||||
export function conditionalBind(
|
||||
node: EventTarget, name: string, thisObject: Object|null, func: Function,
|
||||
opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean): Data {
|
||||
let handled = false;
|
||||
const wrapFunc = function(e) {
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
function wrapFunc(e: Event) {
|
||||
const captureIdentifier = !opt_noCaptureIdentifier;
|
||||
// Handle each touch point separately. If the event was a mouse event, this
|
||||
// will hand back an array with one element, which we're fine handling.
|
||||
@@ -89,10 +83,10 @@ const conditionalBind = function(
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const bindData = [];
|
||||
if (globalThis['PointerEvent'] && (name in Touch.TOUCH_MAP)) {
|
||||
const bindData: Data = [];
|
||||
if (globalThis['PointerEvent'] && name in Touch.TOUCH_MAP) {
|
||||
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
|
||||
const type = Touch.TOUCH_MAP[name][i];
|
||||
node.addEventListener(type, wrapFunc, false);
|
||||
@@ -104,7 +98,7 @@ const conditionalBind = function(
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Touch.TOUCH_MAP) {
|
||||
const touchWrapFunc = function(e) {
|
||||
const touchWrapFunc = (e: Event) => {
|
||||
wrapFunc(e);
|
||||
// Calling preventDefault stops the browser from scrolling/zooming the
|
||||
// page.
|
||||
@@ -121,34 +115,38 @@ const conditionalBind = function(
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
exports.conditionalBind = conditionalBind;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind an event handler that should be called regardless of whether it is part
|
||||
* of the active touch stream.
|
||||
* Use this for events that are not part of a multi-part gesture (e.g.
|
||||
* mouseover for tooltips).
|
||||
* @param {!EventTarget} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {?Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @return {!Data} Opaque data that can be passed to
|
||||
* unbindEvent_.
|
||||
*
|
||||
* @param node Node upon which to listen.
|
||||
* @param name Event name to listen to (e.g. 'mousedown').
|
||||
* @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
|
||||
*/
|
||||
const bind = function(node, name, thisObject, func) {
|
||||
const wrapFunc = function(e) {
|
||||
export function bind(
|
||||
node: EventTarget, name: string, thisObject: Object|null,
|
||||
func: Function): Data {
|
||||
/**
|
||||
*
|
||||
* @param e
|
||||
*/
|
||||
function wrapFunc(e: Event) {
|
||||
if (thisObject) {
|
||||
func.call(thisObject, e);
|
||||
} else {
|
||||
func(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const bindData = [];
|
||||
if (globalThis['PointerEvent'] && (name in Touch.TOUCH_MAP)) {
|
||||
const bindData: Data = [];
|
||||
if (globalThis['PointerEvent'] && name in Touch.TOUCH_MAP) {
|
||||
for (let i = 0; i < Touch.TOUCH_MAP[name].length; i++) {
|
||||
const type = Touch.TOUCH_MAP[name][i];
|
||||
node.addEventListener(type, wrapFunc, false);
|
||||
@@ -160,13 +158,17 @@ const bind = function(node, name, thisObject, func) {
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Touch.TOUCH_MAP) {
|
||||
const touchWrapFunc = function(e) {
|
||||
const touchWrapFunc = (e: Event) => {
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches && e.changedTouches.length === 1) {
|
||||
if (e instanceof TouchEvent && e.changedTouches &&
|
||||
e.changedTouches.length === 1) {
|
||||
// Map the touch event's properties to the event.
|
||||
const touchPoint = e.changedTouches[0];
|
||||
e.clientX = touchPoint.clientX;
|
||||
e.clientY = touchPoint.clientY;
|
||||
// TODO (6311): We are trying to make a touch event look like a mouse
|
||||
// event, which is not allowed, because it requires adding more
|
||||
// properties to the event. How do we want to deal with this?
|
||||
(e as AnyDuringMigration).clientX = touchPoint.clientX;
|
||||
(e as AnyDuringMigration).clientY = touchPoint.clientY;
|
||||
}
|
||||
wrapFunc(e);
|
||||
|
||||
@@ -181,90 +183,107 @@ const bind = function(node, name, thisObject, func) {
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
exports.bind = bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
* @param {!Data} bindData Opaque data from bindEvent_.
|
||||
*
|
||||
* @param bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @return {!Function} The function call.
|
||||
* @returns The function call.
|
||||
* @alias Blockly.browserEvents.unbind
|
||||
*/
|
||||
const unbind = function(bindData) {
|
||||
let func;
|
||||
export function unbind(bindData: Data): (e: Event) => void {
|
||||
// Accessing an element of the last property of the array is unsafe if the
|
||||
// bindData is an empty array. But that should never happen because developers
|
||||
// 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];
|
||||
func = bindDatum[2];
|
||||
const node = bindDatum![0];
|
||||
const name = bindDatum![1];
|
||||
const func = bindDatum![2];
|
||||
node.removeEventListener(name, func, false);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
exports.unbind = unbind;
|
||||
return callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this event is targeting a text input widget?
|
||||
* @param {!Event} e An event.
|
||||
* @return {boolean} True if text input.
|
||||
*
|
||||
* @param e An event.
|
||||
* @returns True if text input.
|
||||
* @alias Blockly.browserEvents.isTargetInput
|
||||
*/
|
||||
const isTargetInput = function(e) {
|
||||
return e.target.type === 'textarea' || e.target.type === 'text' ||
|
||||
e.target.type === 'number' || e.target.type === 'email' ||
|
||||
e.target.type === 'password' || e.target.type === 'search' ||
|
||||
e.target.type === 'tel' || e.target.type === 'url' ||
|
||||
e.target.isContentEditable ||
|
||||
(e.target.dataset && e.target.dataset.isTextInput === 'true');
|
||||
};
|
||||
exports.isTargetInput = isTargetInput;
|
||||
export function isTargetInput(e: Event): boolean {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
if (e.target.isContentEditable ||
|
||||
e.target.getAttribute('data-is-text-input') === 'true') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
const target = e.target;
|
||||
return target.type === 'text' || target.type === 'number' ||
|
||||
target.type === 'email' || target.type === 'password' ||
|
||||
target.type === 'search' || target.type === 'tel' ||
|
||||
target.type === 'url';
|
||||
}
|
||||
|
||||
if (e.target instanceof HTMLTextAreaElement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true this event is a right-click.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @return {boolean} True if right-click.
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @returns True if right-click.
|
||||
* @alias Blockly.browserEvents.isRightButton
|
||||
*/
|
||||
const isRightButton = function(e) {
|
||||
export function isRightButton(e: MouseEvent): boolean {
|
||||
if (e.ctrlKey && userAgent.MAC) {
|
||||
// Control-clicking on Mac OS X is treated as a right-click.
|
||||
// WebKit on Mac OS X fails to change button to 2 (but Gecko does).
|
||||
return true;
|
||||
}
|
||||
return e.button === 2;
|
||||
};
|
||||
exports.isRightButton = isRightButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the converted coordinates of the given mouse event.
|
||||
* The origin (0,0) is the top-left corner of the Blockly SVG.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @param {!Element} svg SVG element.
|
||||
* @param {?SVGMatrix} matrix Inverted screen CTM to use.
|
||||
* @return {!SVGPoint} Object with .x and .y properties.
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @param svg SVG element.
|
||||
* @param matrix Inverted screen CTM to use.
|
||||
* @returns Object with .x and .y properties.
|
||||
* @alias Blockly.browserEvents.mouseToSvg
|
||||
*/
|
||||
const mouseToSvg = function(e, svg, matrix) {
|
||||
export function mouseToSvg(
|
||||
e: MouseEvent, svg: SVGSVGElement, matrix: SVGMatrix|null): SVGPoint {
|
||||
const svgPoint = svg.createSVGPoint();
|
||||
svgPoint.x = e.clientX;
|
||||
svgPoint.y = e.clientY;
|
||||
|
||||
if (!matrix) {
|
||||
matrix = svg.getScreenCTM().inverse();
|
||||
matrix = svg.getScreenCTM()!.inverse();
|
||||
}
|
||||
return svgPoint.matrixTransform(matrix);
|
||||
};
|
||||
exports.mouseToSvg = mouseToSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the scroll delta of a mouse event in pixel units.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @return {{x: number, y: number}} Scroll delta object with .x and .y
|
||||
* properties.
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @returns Scroll delta object with .x and .y properties.
|
||||
* @alias Blockly.browserEvents.getScrollDeltaPixels
|
||||
*/
|
||||
const getScrollDeltaPixels = function(e) {
|
||||
export function getScrollDeltaPixels(e: WheelEvent): {x: number, y: number} {
|
||||
switch (e.deltaMode) {
|
||||
case 0x00: // Pixel mode.
|
||||
default:
|
||||
@@ -280,5 +299,4 @@ const getScrollDeltaPixels = function(e) {
|
||||
y: e.deltaY * PAGE_MODE_MULTIPLIER,
|
||||
};
|
||||
}
|
||||
};
|
||||
exports.getScrollDeltaPixels = getScrollDeltaPixels;
|
||||
}
|
||||
+300
-373
@@ -4,178 +4,146 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a UI bubble.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Object representing a UI bubble.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Bubble');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Bubble');
|
||||
|
||||
const Touch = goog.require('Blockly.Touch');
|
||||
const browserEvents = goog.require('Blockly.browserEvents');
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const math = goog.require('Blockly.utils.math');
|
||||
const userAgent = goog.require('Blockly.utils.userAgent');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDragSurfaceSvg} = goog.requireType('Blockly.BlockDragSurfaceSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IBubble} = goog.require('Blockly.IBubble');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {MetricsManager} = goog.requireType('Blockly.MetricsManager');
|
||||
const {Scrollbar} = goog.require('Blockly.Scrollbar');
|
||||
const {Size} = goog.require('Blockly.utils.Size');
|
||||
const {Svg} = goog.require('Blockly.utils.Svg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Workspace');
|
||||
import type {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import type {IBubble} from './interfaces/i_bubble.js';
|
||||
import type {ContainerRegion} from './metrics_manager.js';
|
||||
import {Scrollbar} from './scrollbar.js';
|
||||
import * as Touch from './touch.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as math from './utils/math.js';
|
||||
import {Size} from './utils/size.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
import * as userAgent from './utils/useragent.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for UI bubble.
|
||||
* @implements {IBubble}
|
||||
*
|
||||
* @alias Blockly.Bubble
|
||||
*/
|
||||
const Bubble = class {
|
||||
export class Bubble implements IBubble {
|
||||
/** Width of the border around the bubble. */
|
||||
static BORDER_WIDTH = 6;
|
||||
|
||||
/**
|
||||
* @param {!WorkspaceSvg} workspace The workspace on which to draw the
|
||||
* bubble.
|
||||
* @param {!Element} content SVG content for the bubble.
|
||||
* @param {!Element} shape SVG element to avoid eclipsing.
|
||||
* @param {!Coordinate} anchorXY Absolute position of bubble's
|
||||
* anchor point.
|
||||
* @param {?number} bubbleWidth Width of bubble, or null if not resizable.
|
||||
* @param {?number} bubbleHeight Height of bubble, or null if not resizable.
|
||||
* @struct
|
||||
* Determines the thickness of the base of the arrow in relation to the size
|
||||
* of the bubble. Higher numbers result in thinner arrows.
|
||||
*/
|
||||
constructor(workspace, content, shape, anchorXY, bubbleWidth, bubbleHeight) {
|
||||
static ARROW_THICKNESS = 5;
|
||||
|
||||
/** The number of degrees that the arrow bends counter-clockwise. */
|
||||
static ARROW_ANGLE = 20;
|
||||
|
||||
/**
|
||||
* The sharpness of the arrow's bend. Higher numbers result in smoother
|
||||
* arrows.
|
||||
*/
|
||||
static ARROW_BEND = 4;
|
||||
|
||||
/** Distance between arrow point and anchor point. */
|
||||
static ANCHOR_RADIUS = 8;
|
||||
|
||||
/** Mouse up event data. */
|
||||
private static onMouseUpWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse move event data. */
|
||||
private static onMouseMoveWrapper_: browserEvents.Data|null = null;
|
||||
workspace_: WorkspaceSvg;
|
||||
content_: SVGElement;
|
||||
shape_: SVGElement;
|
||||
|
||||
/** Flag to stop incremental rendering during construction. */
|
||||
private readonly rendered_: boolean;
|
||||
|
||||
/** The SVG group containing all parts of the bubble. */
|
||||
private bubbleGroup_: SVGGElement|null = null;
|
||||
|
||||
/**
|
||||
* The SVG path for the arrow from the bubble to the icon on the block.
|
||||
*/
|
||||
private bubbleArrow_: SVGPathElement|null = null;
|
||||
|
||||
/** The SVG rect for the main body of the bubble. */
|
||||
private bubbleBack_: SVGRectElement|null = null;
|
||||
|
||||
/** The SVG group for the resize hash marks on some bubbles. */
|
||||
private resizeGroup_: SVGGElement|null = null;
|
||||
|
||||
/** Absolute coordinate of anchor point, in workspace coordinates. */
|
||||
private anchorXY_!: Coordinate;
|
||||
|
||||
/**
|
||||
* Relative X coordinate of bubble with respect to the anchor's centre,
|
||||
* in workspace units.
|
||||
* In RTL mode the initial value is negated.
|
||||
*/
|
||||
private relativeLeft_ = 0;
|
||||
|
||||
/**
|
||||
* Relative Y coordinate of bubble with respect to the anchor's centre, in
|
||||
* workspace units.
|
||||
*/
|
||||
private relativeTop_ = 0;
|
||||
|
||||
/** Width of bubble, in workspace units. */
|
||||
private width_ = 0;
|
||||
|
||||
/** Height of bubble, in workspace units. */
|
||||
private height_ = 0;
|
||||
|
||||
/** Automatically position and reposition the bubble. */
|
||||
private autoLayout_ = true;
|
||||
|
||||
/** Method to call on resize of bubble. */
|
||||
private resizeCallback_: (() => void)|null = null;
|
||||
|
||||
/** Method to call on move of bubble. */
|
||||
private moveCallback_: (() => void)|null = null;
|
||||
|
||||
/** Mouse down on bubbleBack_ event data. */
|
||||
private onMouseDownBubbleWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/** Mouse down on resizeGroup_ event data. */
|
||||
private onMouseDownResizeWrapper_: browserEvents.Data|null = null;
|
||||
|
||||
/**
|
||||
* Describes whether this bubble has been disposed of (nodes and event
|
||||
* listeners removed from the page) or not.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
disposed = false;
|
||||
private arrow_radians_: number;
|
||||
|
||||
/**
|
||||
* @param workspace The workspace on which to draw the bubble.
|
||||
* @param content SVG content for the bubble.
|
||||
* @param shape SVG element to avoid eclipsing.
|
||||
* @param anchorXY Absolute position of bubble's anchor point.
|
||||
* @param bubbleWidth Width of bubble, or null if not resizable.
|
||||
* @param bubbleHeight Height of bubble, or null if not resizable.
|
||||
*/
|
||||
constructor(
|
||||
workspace: WorkspaceSvg, content: SVGElement, shape: SVGElement,
|
||||
anchorXY: Coordinate, bubbleWidth: number|null,
|
||||
bubbleHeight: number|null) {
|
||||
this.rendered_ = false;
|
||||
this.workspace_ = workspace;
|
||||
this.content_ = content;
|
||||
this.shape_ = shape;
|
||||
|
||||
/**
|
||||
* Flag to stop incremental rendering during construction.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.rendered_ = false;
|
||||
|
||||
/**
|
||||
* The SVG group containing all parts of the bubble.
|
||||
* @type {SVGGElement}
|
||||
* @private
|
||||
*/
|
||||
this.bubbleGroup_ = null;
|
||||
|
||||
/**
|
||||
* The SVG path for the arrow from the bubble to the icon on the block.
|
||||
* @type {SVGPathElement}
|
||||
* @private
|
||||
*/
|
||||
this.bubbleArrow_ = null;
|
||||
|
||||
/**
|
||||
* The SVG rect for the main body of the bubble.
|
||||
* @type {SVGRectElement}
|
||||
* @private
|
||||
*/
|
||||
this.bubbleBack_ = null;
|
||||
|
||||
/**
|
||||
* The SVG group for the resize hash marks on some bubbles.
|
||||
* @type {SVGGElement}
|
||||
* @private
|
||||
*/
|
||||
this.resizeGroup_ = null;
|
||||
|
||||
/**
|
||||
* Absolute coordinate of anchor point, in workspace coordinates.
|
||||
* @type {Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.anchorXY_ = null;
|
||||
|
||||
/**
|
||||
* Relative X coordinate of bubble with respect to the anchor's centre,
|
||||
* in workspace units.
|
||||
* In RTL mode the initial value is negated.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.relativeLeft_ = 0;
|
||||
|
||||
/**
|
||||
* Relative Y coordinate of bubble with respect to the anchor's centre, in
|
||||
* workspace units.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.relativeTop_ = 0;
|
||||
|
||||
/**
|
||||
* Width of bubble, in workspace units.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.width_ = 0;
|
||||
|
||||
/**
|
||||
* Height of bubble, in workspace units.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.height_ = 0;
|
||||
|
||||
/**
|
||||
* Automatically position and reposition the bubble.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.autoLayout_ = true;
|
||||
|
||||
/**
|
||||
* Method to call on resize of bubble.
|
||||
* @type {?function()}
|
||||
* @private
|
||||
*/
|
||||
this.resizeCallback_ = null;
|
||||
|
||||
/**
|
||||
* Method to call on move of bubble.
|
||||
* @type {?function()}
|
||||
* @private
|
||||
*/
|
||||
this.moveCallback_ = null;
|
||||
|
||||
/**
|
||||
* Mouse down on bubbleBack_ event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
this.onMouseDownBubbleWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Mouse down on resizeGroup_ event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
this.onMouseDownResizeWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Describes whether this bubble has been disposed of (nodes and event
|
||||
* listeners removed from the page) or not.
|
||||
* @type {boolean}
|
||||
* @package
|
||||
*/
|
||||
this.disposed = false;
|
||||
|
||||
let angle = Bubble.ARROW_ANGLE;
|
||||
if (this.workspace_.RTL) {
|
||||
angle = -angle;
|
||||
@@ -188,7 +156,7 @@ const Bubble = class {
|
||||
|
||||
this.setAnchorLocation(anchorXY);
|
||||
if (!bubbleWidth || !bubbleHeight) {
|
||||
const bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
|
||||
const bBox = (this.content_ as SVGGraphicsElement).getBBox();
|
||||
bubbleWidth = bBox.width + 2 * Bubble.BORDER_WIDTH;
|
||||
bubbleHeight = bBox.height + 2 * Bubble.BORDER_WIDTH;
|
||||
}
|
||||
@@ -202,32 +170,33 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Create the bubble's DOM.
|
||||
* @param {!Element} content SVG content for the bubble.
|
||||
* @param {boolean} hasResize Add diagonal resize gripper if true.
|
||||
* @return {!SVGElement} The bubble's SVG group.
|
||||
* @private
|
||||
*
|
||||
* @param content SVG content for the bubble.
|
||||
* @param hasResize Add diagonal resize gripper if true.
|
||||
* @returns The bubble's SVG group.
|
||||
*/
|
||||
createDom_(content, hasResize) {
|
||||
private createDom_(content: Element, hasResize: boolean): SVGElement {
|
||||
/* Create the bubble. Here's the markup that will be generated:
|
||||
<g>
|
||||
<g filter="url(#blocklyEmbossFilter837493)">
|
||||
<path d="... Z" />
|
||||
<rect class="blocklyDraggable" rx="8" ry="8" width="180" height="180"/>
|
||||
</g>
|
||||
<g transform="translate(165, 165)" class="blocklyResizeSE">
|
||||
<polygon points="0,15 15,15 15,0"/>
|
||||
<line class="blocklyResizeLine" x1="5" y1="14" x2="14" y2="5"/>
|
||||
<line class="blocklyResizeLine" x1="10" y1="14" x2="14" y2="10"/>
|
||||
</g>
|
||||
[...content goes here...]
|
||||
</g>
|
||||
*/
|
||||
this.bubbleGroup_ = dom.createSvgElement(Svg.G, {}, null);
|
||||
let filter = {
|
||||
<g>
|
||||
<g filter="url(#blocklyEmbossFilter837493)">
|
||||
<path d="... Z" />
|
||||
<rect class="blocklyDraggable" rx="8" ry="8" width="180"
|
||||
height="180"/>
|
||||
</g>
|
||||
<g transform="translate(165, 165)" class="blocklyResizeSE">
|
||||
<polygon points="0,15 15,15 15,0"/>
|
||||
<line class="blocklyResizeLine" x1="5" y1="14" x2="14" y2="5"/>
|
||||
<line class="blocklyResizeLine" x1="10" y1="14" x2="14" y2="10"/>
|
||||
</g>
|
||||
[...content goes here...]
|
||||
</g>
|
||||
*/
|
||||
this.bubbleGroup_ = dom.createSvgElement(Svg.G, {});
|
||||
let filter: {filter?: string} = {
|
||||
'filter': 'url(#' +
|
||||
this.workspace_.getRenderer().getConstants().embossFilterId + ')',
|
||||
};
|
||||
if (userAgent.JAVA_FX) {
|
||||
if (userAgent.JavaFx) {
|
||||
// Multiple reports that JavaFX can't handle filters.
|
||||
// https://github.com/google/blockly/issues/99
|
||||
filter = {};
|
||||
@@ -291,28 +260,28 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Return the root node of the bubble's SVG group.
|
||||
* @return {!SVGElement} The root SVG node of the bubble's group.
|
||||
*
|
||||
* @returns The root SVG node of the bubble's group.
|
||||
*/
|
||||
getSvgRoot() {
|
||||
return /** @type {!SVGElement} */ (this.bubbleGroup_);
|
||||
getSvgRoot(): SVGElement {
|
||||
return this.bubbleGroup_ as SVGElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the block's ID on the bubble's top-level SVG group.
|
||||
* @param {string} id ID of block.
|
||||
*
|
||||
* @param id ID of block.
|
||||
*/
|
||||
setSvgId(id) {
|
||||
if (this.bubbleGroup_.dataset) {
|
||||
this.bubbleGroup_.dataset['blockId'] = id;
|
||||
}
|
||||
setSvgId(id: string) {
|
||||
this.bubbleGroup_?.setAttribute('data-block-id', id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's border.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
*/
|
||||
bubbleMouseDown_(e) {
|
||||
private bubbleMouseDown_(e: Event) {
|
||||
const gesture = this.workspace_.getGesture(e);
|
||||
if (gesture) {
|
||||
gesture.handleBubbleStart(e, this);
|
||||
@@ -321,38 +290,38 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Show the context menu for this bubble.
|
||||
* @param {!Event} _e Mouse event.
|
||||
* @package
|
||||
*
|
||||
* @param _e Mouse event.
|
||||
* @internal
|
||||
*/
|
||||
showContextMenu(_e) {
|
||||
// NOP on bubbles, but used by the bubble dragger to pass events to
|
||||
// workspace comments.
|
||||
}
|
||||
showContextMenu(_e: Event) {}
|
||||
// NOP on bubbles, but used by the bubble dragger to pass events to
|
||||
// workspace comments.
|
||||
|
||||
/**
|
||||
* Get whether this bubble is deletable or not.
|
||||
* @return {boolean} True if deletable.
|
||||
* @package
|
||||
*
|
||||
* @returns True if deletable.
|
||||
* @internal
|
||||
*/
|
||||
isDeletable() {
|
||||
isDeletable(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the style of this bubble when it is dragged over a delete area.
|
||||
* @param {boolean} _enable True if the bubble is about to be deleted, false
|
||||
* otherwise.
|
||||
*
|
||||
* @param _enable True if the bubble is about to be deleted, false otherwise.
|
||||
*/
|
||||
setDeleteStyle(_enable) {
|
||||
// NOP if bubble is not deletable.
|
||||
}
|
||||
setDeleteStyle(_enable: boolean) {}
|
||||
// NOP if bubble is not deletable.
|
||||
|
||||
/**
|
||||
* Handle a mouse-down on bubble's resize corner.
|
||||
* @param {!Event} e Mouse down event.
|
||||
* @private
|
||||
*
|
||||
* @param e Mouse down event.
|
||||
*/
|
||||
resizeMouseDown_(e) {
|
||||
private resizeMouseDown_(e: MouseEvent) {
|
||||
this.promote();
|
||||
Bubble.unbindDragEvents_();
|
||||
if (browserEvents.isRightButton(e)) {
|
||||
@@ -377,10 +346,10 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Resize this bubble to follow the mouse.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @private
|
||||
*
|
||||
* @param e Mouse move event.
|
||||
*/
|
||||
resizeMouseMove_(e) {
|
||||
private resizeMouseMove_(e: MouseEvent) {
|
||||
this.autoLayout_ = false;
|
||||
const newXY = this.workspace_.moveDrag(e);
|
||||
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
|
||||
@@ -392,29 +361,32 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Register a function as a callback event for when the bubble is resized.
|
||||
* @param {!Function} callback The function to call on resize.
|
||||
*
|
||||
* @param callback The function to call on resize.
|
||||
*/
|
||||
registerResizeEvent(callback) {
|
||||
registerResizeEvent(callback: () => void) {
|
||||
this.resizeCallback_ = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a function as a callback event for when the bubble is moved.
|
||||
* @param {!Function} callback The function to call on move.
|
||||
*
|
||||
* @param callback The function to call on move.
|
||||
*/
|
||||
registerMoveEvent(callback) {
|
||||
registerMoveEvent(callback: () => void) {
|
||||
this.moveCallback_ = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this bubble to the top of the stack.
|
||||
* @return {boolean} Whether or not the bubble has been moved.
|
||||
* @package
|
||||
*
|
||||
* @returns Whether or not the bubble has been moved.
|
||||
* @internal
|
||||
*/
|
||||
promote() {
|
||||
const svgGroup = this.bubbleGroup_.parentNode;
|
||||
if (svgGroup.lastChild !== this.bubbleGroup_) {
|
||||
svgGroup.appendChild(this.bubbleGroup_);
|
||||
promote(): boolean {
|
||||
const svgGroup = this.bubbleGroup_?.parentNode;
|
||||
if (svgGroup?.lastChild !== this.bubbleGroup_ && this.bubbleGroup_) {
|
||||
svgGroup?.appendChild(this.bubbleGroup_);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -423,32 +395,31 @@ const Bubble = class {
|
||||
/**
|
||||
* Notification that the anchor has moved.
|
||||
* Update the arrow and bubble accordingly.
|
||||
* @param {!Coordinate} xy Absolute location.
|
||||
*
|
||||
* @param xy Absolute location.
|
||||
*/
|
||||
setAnchorLocation(xy) {
|
||||
setAnchorLocation(xy: Coordinate) {
|
||||
this.anchorXY_ = xy;
|
||||
if (this.rendered_) {
|
||||
this.positionBubble_();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the bubble so that it does not fall off-screen.
|
||||
* @private
|
||||
*/
|
||||
layoutBubble_() {
|
||||
/** Position the bubble so that it does not fall off-screen. */
|
||||
private layoutBubble_() {
|
||||
// Get the metrics in workspace units.
|
||||
const viewMetrics =
|
||||
this.workspace_.getMetricsManager().getViewMetrics(true);
|
||||
|
||||
const optimalLeft = this.getOptimalRelativeLeft_(viewMetrics);
|
||||
const optimalTop = this.getOptimalRelativeTop_(viewMetrics);
|
||||
const bbox = this.shape_.getBBox();
|
||||
const bbox = (this.shape_ as SVGGraphicsElement).getBBox();
|
||||
|
||||
const topPosition = {
|
||||
x: optimalLeft,
|
||||
y: -this.height_ -
|
||||
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT,
|
||||
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as
|
||||
number,
|
||||
};
|
||||
const startPosition = {x: -this.width_ - 30, y: optimalTop};
|
||||
const endPosition = {x: bbox.width, y: optimalTop};
|
||||
@@ -495,19 +466,20 @@ const Bubble = class {
|
||||
/**
|
||||
* Calculate the what percentage of the bubble overlaps with the visible
|
||||
* workspace (what percentage of the bubble is visible).
|
||||
* @param {!{x: number, y: number}} relativeMin The position of the top-left
|
||||
* corner of the bubble relative to the anchor point.
|
||||
* @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics
|
||||
* of the workspace the bubble will appear in.
|
||||
* @return {number} The percentage of the bubble that is visible.
|
||||
* @private
|
||||
*
|
||||
* @param relativeMin The position of the top-left corner of the bubble
|
||||
* relative to the anchor point.
|
||||
* @param viewMetrics The view metrics of the workspace the bubble will appear
|
||||
* in.
|
||||
* @returns The percentage of the bubble that is visible.
|
||||
*/
|
||||
getOverlap_(relativeMin, viewMetrics) {
|
||||
private getOverlap_(
|
||||
relativeMin: {x: number, y: number},
|
||||
viewMetrics: ContainerRegion): number {
|
||||
// The position of the top-left corner of the bubble in workspace units.
|
||||
const bubbleMin = {
|
||||
x: this.workspace_.RTL ?
|
||||
(this.anchorXY_.x - relativeMin.x - this.width_) :
|
||||
(relativeMin.x + this.anchorXY_.x),
|
||||
x: this.workspace_.RTL ? this.anchorXY_.x - relativeMin.x - this.width_ :
|
||||
relativeMin.x + this.anchorXY_.x,
|
||||
y: relativeMin.y + this.anchorXY_.y,
|
||||
};
|
||||
// The position of the bottom-right corner of the bubble in workspace units.
|
||||
@@ -536,20 +508,20 @@ const Bubble = class {
|
||||
return Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
1, (overlapWidth * overlapHeight) / (this.width_ * this.height_)));
|
||||
1, overlapWidth * overlapHeight / (this.width_ * this.height_)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate what the optimal horizontal position of the top-left corner of
|
||||
* the bubble is (relative to the anchor point) so that the most area of the
|
||||
* bubble is shown.
|
||||
* @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics
|
||||
* of the workspace the bubble will appear in.
|
||||
* @return {number} The optimal horizontal position of the top-left corner
|
||||
* of the bubble.
|
||||
* @private
|
||||
*
|
||||
* @param viewMetrics The view metrics of the workspace the bubble will appear
|
||||
* in.
|
||||
* @returns The optimal horizontal position of the top-left corner of the
|
||||
* bubble.
|
||||
*/
|
||||
getOptimalRelativeLeft_(viewMetrics) {
|
||||
private getOptimalRelativeLeft_(viewMetrics: ContainerRegion): number {
|
||||
let relativeLeft = -this.width_ / 4;
|
||||
|
||||
// No amount of sliding left or right will give us a better overlap.
|
||||
@@ -565,7 +537,7 @@ const Bubble = class {
|
||||
const workspaceRight = viewMetrics.left + viewMetrics.width;
|
||||
const workspaceLeft = viewMetrics.left +
|
||||
// Thickness in workspace units.
|
||||
(Scrollbar.scrollbarThickness / this.workspace_.scale);
|
||||
Scrollbar.scrollbarThickness / this.workspace_.scale;
|
||||
|
||||
if (bubbleLeft < workspaceLeft) {
|
||||
// Slide the bubble right until it is onscreen.
|
||||
@@ -581,7 +553,7 @@ const Bubble = class {
|
||||
const workspaceLeft = viewMetrics.left;
|
||||
const workspaceRight = viewMetrics.left + viewMetrics.width -
|
||||
// Thickness in workspace units.
|
||||
(Scrollbar.scrollbarThickness / this.workspace_.scale);
|
||||
Scrollbar.scrollbarThickness / this.workspace_.scale;
|
||||
|
||||
if (bubbleLeft < workspaceLeft) {
|
||||
// Slide the bubble right until it is onscreen.
|
||||
@@ -599,13 +571,13 @@ const Bubble = class {
|
||||
* Calculate what the optimal vertical position of the top-left corner of
|
||||
* the bubble is (relative to the anchor point) so that the most area of the
|
||||
* bubble is shown.
|
||||
* @param {!MetricsManager.ContainerRegion} viewMetrics The view metrics
|
||||
* of the workspace the bubble will appear in.
|
||||
* @return {number} The optimal vertical position of the top-left corner
|
||||
* of the bubble.
|
||||
* @private
|
||||
*
|
||||
* @param viewMetrics The view metrics of the workspace the bubble will appear
|
||||
* in.
|
||||
* @returns The optimal vertical position of the top-left corner of the
|
||||
* bubble.
|
||||
*/
|
||||
getOptimalRelativeTop_(viewMetrics) {
|
||||
private getOptimalRelativeTop_(viewMetrics: ContainerRegion): number {
|
||||
let relativeTop = -this.height_ / 4;
|
||||
|
||||
// No amount of sliding up or down will give us a better overlap.
|
||||
@@ -616,9 +588,9 @@ const Bubble = class {
|
||||
const bubbleTop = this.anchorXY_.y + relativeTop;
|
||||
const bubbleBottom = bubbleTop + this.height_;
|
||||
const workspaceTop = viewMetrics.top;
|
||||
const workspaceBottom = viewMetrics.top + viewMetrics.height -
|
||||
// Thickness in workspace units.
|
||||
(Scrollbar.scrollbarThickness / this.workspace_.scale);
|
||||
const workspaceBottom = viewMetrics.top +
|
||||
viewMetrics.height - // Thickness in workspace units.
|
||||
Scrollbar.scrollbarThickness / this.workspace_.scale;
|
||||
|
||||
const anchorY = this.anchorXY_.y;
|
||||
if (bubbleTop < workspaceTop) {
|
||||
@@ -632,11 +604,8 @@ const Bubble = class {
|
||||
return relativeTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the bubble to a location relative to the anchor's centre.
|
||||
* @private
|
||||
*/
|
||||
positionBubble_() {
|
||||
/** Move the bubble to a location relative to the anchor's centre. */
|
||||
private positionBubble_() {
|
||||
let left = this.anchorXY_.x;
|
||||
if (this.workspace_.RTL) {
|
||||
left -= this.relativeLeft_ + this.width_;
|
||||
@@ -649,21 +618,23 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Move the bubble group to the specified location in workspace coordinates.
|
||||
* @param {number} x The x position to move to.
|
||||
* @param {number} y The y position to move to.
|
||||
* @package
|
||||
*
|
||||
* @param x The x position to move to.
|
||||
* @param y The y position to move to.
|
||||
* @internal
|
||||
*/
|
||||
moveTo(x, y) {
|
||||
this.bubbleGroup_.setAttribute(
|
||||
moveTo(x: number, y: number) {
|
||||
this.bubbleGroup_?.setAttribute(
|
||||
'transform', 'translate(' + x + ',' + y + ')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a move callback if one exists at the end of a drag.
|
||||
* @param {boolean} adding True if adding, false if removing.
|
||||
* @package
|
||||
*
|
||||
* @param adding True if adding, false if removing.
|
||||
* @internal
|
||||
*/
|
||||
setDragging(adding) {
|
||||
setDragging(adding: boolean) {
|
||||
if (!adding && this.moveCallback_) {
|
||||
this.moveCallback_();
|
||||
}
|
||||
@@ -671,26 +642,28 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Get the dimensions of this bubble.
|
||||
* @return {!Size} The height and width of the bubble.
|
||||
*
|
||||
* @returns The height and width of the bubble.
|
||||
*/
|
||||
getBubbleSize() {
|
||||
getBubbleSize(): Size {
|
||||
return new Size(this.width_, this.height_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Size this bubble.
|
||||
* @param {number} width Width of the bubble.
|
||||
* @param {number} height Height of the bubble.
|
||||
*
|
||||
* @param width Width of the bubble.
|
||||
* @param height Height of the bubble.
|
||||
*/
|
||||
setBubbleSize(width, height) {
|
||||
setBubbleSize(width: number, height: number) {
|
||||
const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH;
|
||||
// Minimum size of a bubble.
|
||||
width = Math.max(width, doubleBorderWidth + 45);
|
||||
height = Math.max(height, doubleBorderWidth + 20);
|
||||
this.width_ = width;
|
||||
this.height_ = height;
|
||||
this.bubbleBack_.setAttribute('width', width);
|
||||
this.bubbleBack_.setAttribute('height', height);
|
||||
this.bubbleBack_?.setAttribute('width', width.toString());
|
||||
this.bubbleBack_?.setAttribute('height', height.toString());
|
||||
if (this.resizeGroup_) {
|
||||
if (this.workspace_.RTL) {
|
||||
// Mirror the resize group.
|
||||
@@ -718,11 +691,8 @@ const Bubble = class {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the arrow between the bubble and the origin.
|
||||
* @private
|
||||
*/
|
||||
renderArrow_() {
|
||||
/** Draw the arrow between the bubble and the origin. */
|
||||
private renderArrow_() {
|
||||
const steps = [];
|
||||
// Find the relative coordinates of the center of the bubble.
|
||||
const relBubbleX = this.width_ / 2;
|
||||
@@ -788,21 +758,20 @@ const Bubble = class {
|
||||
',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2);
|
||||
}
|
||||
steps.push('z');
|
||||
this.bubbleArrow_.setAttribute('d', steps.join(' '));
|
||||
this.bubbleArrow_?.setAttribute('d', steps.join(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the colour of a bubble.
|
||||
* @param {string} hexColour Hex code of colour.
|
||||
*
|
||||
* @param hexColour Hex code of colour.
|
||||
*/
|
||||
setColour(hexColour) {
|
||||
this.bubbleBack_.setAttribute('fill', hexColour);
|
||||
this.bubbleArrow_.setAttribute('fill', hexColour);
|
||||
setColour(hexColour: string) {
|
||||
this.bubbleBack_?.setAttribute('fill', hexColour);
|
||||
this.bubbleArrow_?.setAttribute('fill', hexColour);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this bubble.
|
||||
*/
|
||||
/** Dispose of this bubble. */
|
||||
dispose() {
|
||||
if (this.onMouseDownBubbleWrapper_) {
|
||||
browserEvents.unbind(this.onMouseDownBubbleWrapper_);
|
||||
@@ -818,13 +787,13 @@ const Bubble = class {
|
||||
/**
|
||||
* Move this bubble during a drag, taking into account whether or not there is
|
||||
* a drag surface.
|
||||
* @param {BlockDragSurfaceSvg} dragSurface The surface that carries
|
||||
* rendered items during a drag, or null if no drag surface is in use.
|
||||
* @param {!Coordinate} newLoc The location to translate to, in
|
||||
* workspace coordinates.
|
||||
* @package
|
||||
*
|
||||
* @param dragSurface The surface that carries rendered items during a drag,
|
||||
* or null if no drag surface is in use.
|
||||
* @param newLoc The location to translate to, in workspace coordinates.
|
||||
* @internal
|
||||
*/
|
||||
moveDuringDrag(dragSurface, newLoc) {
|
||||
moveDuringDrag(dragSurface: BlockDragSurfaceSvg, newLoc: Coordinate) {
|
||||
if (dragSurface) {
|
||||
dragSurface.translateSurface(newLoc.x, newLoc.y);
|
||||
} else {
|
||||
@@ -842,9 +811,10 @@ const Bubble = class {
|
||||
/**
|
||||
* Return the coordinates of the top-left corner of this bubble's body
|
||||
* relative to the drawing surface's origin (0,0), in workspace units.
|
||||
* @return {!Coordinate} Object with .x and .y properties.
|
||||
*
|
||||
* @returns Object with .x and .y properties.
|
||||
*/
|
||||
getRelativeToSurfaceXY() {
|
||||
getRelativeToSurfaceXY(): Coordinate {
|
||||
return new Coordinate(
|
||||
this.workspace_.RTL ?
|
||||
-this.relativeLeft_ + this.anchorXY_.x - this.width_ :
|
||||
@@ -856,19 +826,16 @@ const Bubble = class {
|
||||
* Set whether auto-layout of this bubble is enabled. The first time a bubble
|
||||
* is shown it positions itself to not cover any blocks. Once a user has
|
||||
* dragged it to reposition, it renders where the user put it.
|
||||
* @param {boolean} enable True if auto-layout should be enabled, false
|
||||
* otherwise.
|
||||
* @package
|
||||
*
|
||||
* @param enable True if auto-layout should be enabled, false otherwise.
|
||||
* @internal
|
||||
*/
|
||||
setAutoLayout(enable) {
|
||||
setAutoLayout(enable: boolean) {
|
||||
this.autoLayout_ = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop binding to the global mouseup and mousemove events.
|
||||
* @private
|
||||
*/
|
||||
static unbindDragEvents_() {
|
||||
/** Stop binding to the global mouseup and mousemove events. */
|
||||
private static unbindDragEvents_() {
|
||||
if (Bubble.onMouseUpWrapper_) {
|
||||
browserEvents.unbind(Bubble.onMouseUpWrapper_);
|
||||
Bubble.onMouseUpWrapper_ = null;
|
||||
@@ -881,27 +848,26 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Handle a mouse-up event while dragging a bubble's border or resize handle.
|
||||
* @param {!Event} _e Mouse up event.
|
||||
* @private
|
||||
*
|
||||
* @param _e Mouse up event.
|
||||
*/
|
||||
static bubbleMouseUp_(_e) {
|
||||
private static bubbleMouseUp_(_e: MouseEvent) {
|
||||
Touch.clearTouchIdentifier();
|
||||
Bubble.unbindDragEvents_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the text for a non editable bubble.
|
||||
* @param {string} text The text to display.
|
||||
* @return {!SVGTextElement} The top-level node of the text.
|
||||
* @package
|
||||
*
|
||||
* @param text The text to display.
|
||||
* @returns The top-level node of the text.
|
||||
* @internal
|
||||
*/
|
||||
static textToDom(text) {
|
||||
const paragraph = dom.createSvgElement(
|
||||
Svg.TEXT, {
|
||||
'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents',
|
||||
'y': Bubble.BORDER_WIDTH,
|
||||
},
|
||||
null);
|
||||
static textToDom(text: string): SVGTextElement {
|
||||
const paragraph = dom.createSvgElement(Svg.TEXT, {
|
||||
'class': 'blocklyText blocklyBubbleText blocklyNoPointerEvents',
|
||||
'y': Bubble.BORDER_WIDTH,
|
||||
});
|
||||
const lines = text.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const tspanElement = dom.createSvgElement(
|
||||
@@ -914,18 +880,19 @@ const Bubble = class {
|
||||
|
||||
/**
|
||||
* Creates a bubble that can not be edited.
|
||||
* @param {!SVGTextElement} paragraphElement The text element for the non
|
||||
* editable bubble.
|
||||
* @param {!BlockSvg} block The block that the bubble is attached to.
|
||||
* @param {!Coordinate} iconXY The coordinate of the icon.
|
||||
* @return {!Bubble} The non editable bubble.
|
||||
* @package
|
||||
*
|
||||
* @param paragraphElement The text element for the non editable bubble.
|
||||
* @param block The block that the bubble is attached to.
|
||||
* @param iconXY The coordinate of the icon.
|
||||
* @returns The non editable bubble.
|
||||
* @internal
|
||||
*/
|
||||
static createNonEditableBubble(paragraphElement, block, iconXY) {
|
||||
static createNonEditableBubble(
|
||||
paragraphElement: SVGTextElement, block: BlockSvg,
|
||||
iconXY: Coordinate): Bubble {
|
||||
const bubble = new Bubble(
|
||||
/** @type {!WorkspaceSvg} */ (block.workspace), paragraphElement,
|
||||
block.pathObject.svgPath,
|
||||
/** @type {!Coordinate} */ (iconXY), null, null);
|
||||
block.workspace!, paragraphElement, block.pathObject.svgPath, (iconXY),
|
||||
null, null);
|
||||
// Expose this bubble's block's ID on its top-level SVG group.
|
||||
bubble.setSvgId(block.id);
|
||||
if (block.RTL) {
|
||||
@@ -933,53 +900,13 @@ const Bubble = class {
|
||||
// This cannot be done until the bubble is rendered on screen.
|
||||
const maxWidth = paragraphElement.getBBox().width;
|
||||
for (let i = 0, textElement;
|
||||
(textElement = paragraphElement.childNodes[i]); i++) {
|
||||
textElement = paragraphElement.childNodes[i] as SVGTSpanElement;
|
||||
i++) {
|
||||
textElement.setAttribute('text-anchor', 'end');
|
||||
textElement.setAttribute('x', maxWidth + Bubble.BORDER_WIDTH);
|
||||
textElement.setAttribute(
|
||||
'x', (maxWidth + Bubble.BORDER_WIDTH).toString());
|
||||
}
|
||||
}
|
||||
return bubble;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Width of the border around the bubble.
|
||||
*/
|
||||
Bubble.BORDER_WIDTH = 6;
|
||||
|
||||
/**
|
||||
* Determines the thickness of the base of the arrow in relation to the size
|
||||
* of the bubble. Higher numbers result in thinner arrows.
|
||||
*/
|
||||
Bubble.ARROW_THICKNESS = 5;
|
||||
|
||||
/**
|
||||
* The number of degrees that the arrow bends counter-clockwise.
|
||||
*/
|
||||
Bubble.ARROW_ANGLE = 20;
|
||||
|
||||
/**
|
||||
* The sharpness of the arrow's bend. Higher numbers result in smoother arrows.
|
||||
*/
|
||||
Bubble.ARROW_BEND = 4;
|
||||
|
||||
/**
|
||||
* Distance between arrow point and anchor point.
|
||||
*/
|
||||
Bubble.ANCHOR_RADIUS = 8;
|
||||
|
||||
/**
|
||||
* Mouse up event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
Bubble.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Mouse move event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
Bubble.onMouseMoveWrapper_ = null;
|
||||
|
||||
exports.Bubble = Bubble;
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Methods for dragging a bubble visually.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Methods for dragging a bubble visually.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.BubbleDragger');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const svgMath = goog.require('Blockly.utils.svgMath');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDragSurfaceSvg} = goog.requireType('Blockly.BlockDragSurfaceSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentMove} = goog.requireType('Blockly.Events.CommentMove');
|
||||
const {ComponentManager} = goog.require('Blockly.ComponentManager');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IBubble} = goog.requireType('Blockly.IBubble');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDeleteArea} = goog.requireType('Blockly.IDeleteArea');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDragTarget} = goog.requireType('Blockly.IDragTarget');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceCommentSvg} = goog.requireType('Blockly.WorkspaceCommentSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Bubble');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.CommentMove');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.constants');
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
const BubbleDragger = class {
|
||||
/**
|
||||
* @param {!IBubble} bubble The item on the bubble canvas to drag.
|
||||
* @param {!WorkspaceSvg} workspace The workspace to drag on.
|
||||
*/
|
||||
constructor(bubble, workspace) {
|
||||
/**
|
||||
* The item on the bubble canvas that is being dragged.
|
||||
* @type {!IBubble}
|
||||
* @private
|
||||
*/
|
||||
this.draggingBubble_ = bubble;
|
||||
|
||||
/**
|
||||
* The workspace on which the bubble is being dragged.
|
||||
* @type {!WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Which drag target the mouse pointer is over, if any.
|
||||
* @type {?IDragTarget}
|
||||
* @private
|
||||
*/
|
||||
this.dragTarget_ = null;
|
||||
|
||||
/**
|
||||
* Whether the bubble would be deleted if dropped immediately.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.wouldDeleteBubble_ = false;
|
||||
|
||||
/**
|
||||
* The location of the top left corner of the dragging bubble's body at the
|
||||
* beginning of the drag, in workspace coordinates.
|
||||
* @type {!Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.startXY_ = this.draggingBubble_.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* The drag surface to move bubbles to during a drag, or null if none should
|
||||
* be used. Block dragging and bubble dragging use the same surface.
|
||||
* @type {BlockDragSurfaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.dragSurface_ =
|
||||
svgMath.is3dSupported() && !!workspace.getBlockDragSurface() ?
|
||||
workspace.getBlockDragSurface() :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sever all links from this object.
|
||||
* @package
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
dispose() {
|
||||
this.draggingBubble_ = null;
|
||||
this.workspace_ = null;
|
||||
this.dragSurface_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start dragging a bubble. This includes moving it to the drag surface.
|
||||
* @package
|
||||
*/
|
||||
startBubbleDrag() {
|
||||
if (!eventUtils.getGroup()) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
this.draggingBubble_.setAutoLayout(false);
|
||||
if (this.dragSurface_) {
|
||||
this.moveToDragSurface_();
|
||||
}
|
||||
|
||||
this.draggingBubble_.setDragging && this.draggingBubble_.setDragging(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a step of bubble dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
* @param {!Event} e The most recent move event.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
dragBubble(e, currentDragDeltaXY) {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
this.draggingBubble_.moveDuringDrag(this.dragSurface_, newLoc);
|
||||
|
||||
const oldDragTarget = this.dragTarget_;
|
||||
this.dragTarget_ = this.workspace_.getDragTarget(e);
|
||||
|
||||
const oldWouldDeleteBubble = this.wouldDeleteBubble_;
|
||||
this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_);
|
||||
if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) {
|
||||
// Prevent unnecessary add/remove class calls.
|
||||
this.updateCursorDuringBubbleDrag_();
|
||||
}
|
||||
|
||||
// Call drag enter/exit/over after wouldDeleteBlock is called in
|
||||
// shouldDelete_
|
||||
if (this.dragTarget_ !== oldDragTarget) {
|
||||
oldDragTarget && oldDragTarget.onDragExit(this.draggingBubble_);
|
||||
this.dragTarget_ && this.dragTarget_.onDragEnter(this.draggingBubble_);
|
||||
}
|
||||
this.dragTarget_ && this.dragTarget_.onDragOver(this.draggingBubble_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether ending the drag would delete the bubble.
|
||||
* @param {?IDragTarget} dragTarget The drag target that the bubblee is
|
||||
* currently over.
|
||||
* @return {boolean} Whether dropping the bubble immediately would delete the
|
||||
* block.
|
||||
* @private
|
||||
*/
|
||||
shouldDelete_(dragTarget) {
|
||||
if (dragTarget) {
|
||||
const componentManager = this.workspace_.getComponentManager();
|
||||
const isDeleteArea = componentManager.hasCapability(
|
||||
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
|
||||
if (isDeleteArea) {
|
||||
return (/** @type {!IDeleteArea} */ (dragTarget))
|
||||
.wouldDelete(this.draggingBubble_, false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging bubble would be deleted if released immediately.
|
||||
* @private
|
||||
*/
|
||||
updateCursorDuringBubbleDrag_() {
|
||||
this.draggingBubble_.setDeleteStyle(this.wouldDeleteBubble_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish a bubble drag and put the bubble back on the workspace.
|
||||
* @param {!Event} e The mouseup/touchend event.
|
||||
* @param {!Coordinate} currentDragDeltaXY How far the pointer has
|
||||
* moved from the position at the start of the drag, in pixel units.
|
||||
* @package
|
||||
*/
|
||||
endBubbleDrag(e, currentDragDeltaXY) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBubble(e, currentDragDeltaXY);
|
||||
|
||||
const preventMove = this.dragTarget_ &&
|
||||
this.dragTarget_.shouldPreventMove(this.draggingBubble_);
|
||||
let newLoc;
|
||||
if (preventMove) {
|
||||
newLoc = this.startXY_;
|
||||
} else {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
}
|
||||
// Move the bubble to its final location.
|
||||
this.draggingBubble_.moveTo(newLoc.x, newLoc.y);
|
||||
|
||||
if (this.dragTarget_) {
|
||||
this.dragTarget_.onDrop(this.draggingBubble_);
|
||||
}
|
||||
|
||||
if (this.wouldDeleteBubble_) {
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.draggingBubble_.dispose(false, true);
|
||||
} else {
|
||||
// Put everything back onto the bubble canvas.
|
||||
if (this.dragSurface_) {
|
||||
this.dragSurface_.clearAndHide(this.workspace_.getBubbleCanvas());
|
||||
}
|
||||
if (this.draggingBubble_.setDragging) {
|
||||
this.draggingBubble_.setDragging(false);
|
||||
}
|
||||
this.fireMoveEvent_();
|
||||
}
|
||||
this.workspace_.setResizesEnabled(true);
|
||||
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a move event at the end of a bubble drag.
|
||||
* @private
|
||||
*/
|
||||
fireMoveEvent_() {
|
||||
if (this.draggingBubble_.isComment) {
|
||||
// TODO (adodson): Resolve build errors when requiring
|
||||
// WorkspaceCommentSvg.
|
||||
const event = /** @type {!CommentMove} */
|
||||
(new (eventUtils.get(eventUtils.COMMENT_MOVE))(
|
||||
/** @type {!WorkspaceCommentSvg} */ (this.draggingBubble_)));
|
||||
event.setOldCoordinate(this.startXY_);
|
||||
event.recordNew();
|
||||
eventUtils.fire(event);
|
||||
}
|
||||
// TODO (fenichel): move events for comments.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a coordinate object from pixels to workspace units, including a
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
* @param {!Coordinate} pixelCoord A coordinate with x and y
|
||||
* values in CSS pixel units.
|
||||
* @return {!Coordinate} The input coordinate divided by the
|
||||
* workspace scale.
|
||||
* @private
|
||||
*/
|
||||
pixelsToWorkspaceUnits_(pixelCoord) {
|
||||
const result = new Coordinate(
|
||||
pixelCoord.x / this.workspace_.scale,
|
||||
pixelCoord.y / this.workspace_.scale);
|
||||
if (this.workspace_.isMutator) {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same
|
||||
// as the scale on the parent workspace. Fix that for dragging.
|
||||
const mainScale = this.workspace_.options.parentWorkspace.scale;
|
||||
result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the bubble onto the drag surface at the beginning of a drag. Move the
|
||||
* drag surface to preserve the apparent location of the bubble.
|
||||
* @private
|
||||
*/
|
||||
moveToDragSurface_() {
|
||||
this.draggingBubble_.moveTo(0, 0);
|
||||
this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y);
|
||||
// Execute the move on the top-level SVG component.
|
||||
this.dragSurface_.setBlocksAndShow(this.draggingBubble_.getSvgRoot());
|
||||
}
|
||||
};
|
||||
|
||||
exports.BubbleDragger = BubbleDragger;
|
||||
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Methods for dragging a bubble visually.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.BubbleDragger');
|
||||
|
||||
import type {BlockDragSurfaceSvg} from './block_drag_surface.js';
|
||||
import {ComponentManager} from './component_manager.js';
|
||||
import type {CommentMove} from './events/events_comment_move.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import type {IBubble} from './interfaces/i_bubble.js';
|
||||
import type {IDeleteArea} from './interfaces/i_delete_area.js';
|
||||
import type {IDragTarget} from './interfaces/i_drag_target.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
||||
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. */
|
||||
private dragTarget_: IDragTarget|null = null;
|
||||
|
||||
/** Whether the bubble would be deleted if dropped immediately. */
|
||||
private wouldDeleteBubble_ = false;
|
||||
private readonly startXY_: Coordinate;
|
||||
private dragSurface_: BlockDragSurfaceSvg|null;
|
||||
|
||||
/**
|
||||
* @param bubble The item on the bubble canvas to drag.
|
||||
* @param workspace The workspace to drag on.
|
||||
*/
|
||||
constructor(private bubble: IBubble, private workspace: WorkspaceSvg) {
|
||||
/**
|
||||
* The location of the top left corner of the dragging bubble's body at the
|
||||
* beginning of the drag, in workspace coordinates.
|
||||
*/
|
||||
this.startXY_ = this.bubble.getRelativeToSurfaceXY();
|
||||
|
||||
/**
|
||||
* The drag surface to move bubbles to during a drag, or null if none should
|
||||
* be used. Block dragging and bubble dragging use the same surface.
|
||||
*/
|
||||
this.dragSurface_ = workspace.getBlockDragSurface();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start dragging a bubble. This includes moving it to the drag surface.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
startBubbleDrag() {
|
||||
if (!eventUtils.getGroup()) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
|
||||
this.workspace.setResizesEnabled(false);
|
||||
this.bubble.setAutoLayout(false);
|
||||
if (this.dragSurface_) {
|
||||
this.bubble.moveTo(0, 0);
|
||||
this.dragSurface_.translateSurface(this.startXY_.x, this.startXY_.y);
|
||||
// Execute the move on the top-level SVG component.
|
||||
this.dragSurface_.setBlocksAndShow(this.bubble.getSvgRoot());
|
||||
}
|
||||
|
||||
this.bubble.setDragging && this.bubble.setDragging(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a step of bubble dragging, based on the given event. Update the
|
||||
* display accordingly.
|
||||
*
|
||||
* @param e The most recent move event.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
* @internal
|
||||
*/
|
||||
dragBubble(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
const newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
this.bubble.moveDuringDrag(this.dragSurface_, newLoc);
|
||||
|
||||
const oldDragTarget = this.dragTarget_;
|
||||
this.dragTarget_ = this.workspace.getDragTarget(e);
|
||||
|
||||
const oldWouldDeleteBubble = this.wouldDeleteBubble_;
|
||||
this.wouldDeleteBubble_ = this.shouldDelete_(this.dragTarget_);
|
||||
if (oldWouldDeleteBubble !== this.wouldDeleteBubble_) {
|
||||
// Prevent unnecessary add/remove class calls.
|
||||
this.updateCursorDuringBubbleDrag_();
|
||||
}
|
||||
// Call drag enter/exit/over after wouldDeleteBlock is called in
|
||||
// shouldDelete_
|
||||
if (this.dragTarget_ !== oldDragTarget) {
|
||||
oldDragTarget && oldDragTarget.onDragExit(this.bubble);
|
||||
this.dragTarget_ && this.dragTarget_.onDragEnter(this.bubble);
|
||||
}
|
||||
this.dragTarget_ && this.dragTarget_.onDragOver(this.bubble);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether ending the drag would delete the bubble.
|
||||
*
|
||||
* @param dragTarget The drag target that the bubblee is currently over.
|
||||
* @returns Whether dropping the bubble immediately would delete the block.
|
||||
*/
|
||||
private shouldDelete_(dragTarget: IDragTarget|null): boolean {
|
||||
if (dragTarget) {
|
||||
const componentManager = this.workspace.getComponentManager();
|
||||
const isDeleteArea = componentManager.hasCapability(
|
||||
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
|
||||
if (isDeleteArea) {
|
||||
return (dragTarget as IDeleteArea).wouldDelete(this.bubble, false);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cursor (and possibly the trash can lid) to reflect whether the
|
||||
* dragging bubble would be deleted if released immediately.
|
||||
*/
|
||||
private updateCursorDuringBubbleDrag_() {
|
||||
this.bubble.setDeleteStyle(this.wouldDeleteBubble_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish a bubble drag and put the bubble back on the workspace.
|
||||
*
|
||||
* @param e The mouseup/touchend event.
|
||||
* @param currentDragDeltaXY How far the pointer has moved from the position
|
||||
* at the start of the drag, in pixel units.
|
||||
* @internal
|
||||
*/
|
||||
endBubbleDrag(e: Event, currentDragDeltaXY: Coordinate) {
|
||||
// Make sure internal state is fresh.
|
||||
this.dragBubble(e, currentDragDeltaXY);
|
||||
|
||||
const preventMove =
|
||||
this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble);
|
||||
let newLoc;
|
||||
if (preventMove) {
|
||||
newLoc = this.startXY_;
|
||||
} else {
|
||||
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
|
||||
newLoc = Coordinate.sum(this.startXY_, delta);
|
||||
}
|
||||
// Move the bubble to its final location.
|
||||
this.bubble.moveTo(newLoc.x, newLoc.y);
|
||||
|
||||
if (this.dragTarget_) {
|
||||
this.dragTarget_.onDrop(this.bubble);
|
||||
}
|
||||
|
||||
if (this.wouldDeleteBubble_) {
|
||||
// Fire a move event, so we know where to go back to for an undo.
|
||||
this.fireMoveEvent_();
|
||||
this.bubble.dispose();
|
||||
} else {
|
||||
// Put everything back onto the bubble canvas.
|
||||
if (this.dragSurface_) {
|
||||
this.dragSurface_.clearAndHide(this.workspace.getBubbleCanvas());
|
||||
}
|
||||
if (this.bubble.setDragging) {
|
||||
this.bubble.setDragging(false);
|
||||
}
|
||||
this.fireMoveEvent_();
|
||||
}
|
||||
this.workspace.setResizesEnabled(true);
|
||||
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
|
||||
/** Fire a move event at the end of a bubble drag. */
|
||||
private fireMoveEvent_() {
|
||||
if (this.bubble instanceof WorkspaceCommentSvg) {
|
||||
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
|
||||
this.bubble) as CommentMove;
|
||||
event.setOldCoordinate(this.startXY_);
|
||||
event.recordNew();
|
||||
eventUtils.fire(event);
|
||||
}
|
||||
// TODO (fenichel): move events for comments.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a coordinate object from pixels to workspace units, including a
|
||||
* correction for mutator workspaces.
|
||||
* This function does not consider differing origins. It simply scales the
|
||||
* input's x and y values.
|
||||
*
|
||||
* @param pixelCoord A coordinate with x and y values in CSS pixel units.
|
||||
* @returns The input coordinate divided by the workspace scale.
|
||||
*/
|
||||
private pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
|
||||
const result = new Coordinate(
|
||||
pixelCoord.x / this.workspace.scale,
|
||||
pixelCoord.y / this.workspace.scale);
|
||||
if (this.workspace.isMutator) {
|
||||
// If we're in a mutator, its scale is always 1, purely because of some
|
||||
// oddities in our rendering optimizations. The actual scale is the same
|
||||
// as the scale on the parent workspace. Fix that for dragging.
|
||||
const mainScale = this.workspace.options.parentWorkspace!.scale;
|
||||
result.scale(1 / mainScale);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -4,45 +4,42 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Utilities for bumping objects back into worksapce bounds.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Utilities for bumping objects back into worksapce bounds.
|
||||
*
|
||||
* @namespace Blockly.bumpObjects
|
||||
*/
|
||||
goog.module('Blockly.bumpObjects');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.bumpObjects');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const mathUtils = goog.require('Blockly.utils.math');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Abstract} = goog.requireType('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IBoundedElement} = goog.requireType('Blockly.IBoundedElement');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {MetricsManager} = goog.requireType('Blockly.MetricsManager');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {ViewportChange} = goog.requireType('Blockly.Events.ViewportChange');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceCommentSvg} = goog.requireType('Blockly.WorkspaceCommentSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import type {Abstract} from './events/events_abstract.js';
|
||||
import type {BlockCreate} from './events/events_block_create.js';
|
||||
import type {BlockMove} from './events/events_block_move.js';
|
||||
import type {CommentCreate} from './events/events_comment_create.js';
|
||||
import type {CommentMove} from './events/events_comment_move.js';
|
||||
import type {ViewportChange} from './events/events_viewport.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||
import type {ContainerRegion} from './metrics_manager.js';
|
||||
import * as mathUtils from './utils/math.js';
|
||||
import type {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* Bumps the given object that has passed out of bounds.
|
||||
* @param {!WorkspaceSvg} workspace The workspace containing the object.
|
||||
* @param {!MetricsManager.ContainerRegion} scrollMetrics Scroll metrics
|
||||
*
|
||||
* @param workspace The workspace containing the object.
|
||||
* @param scrollMetrics Scroll metrics
|
||||
* in workspace coordinates.
|
||||
* @param {!IBoundedElement} object The object to bump.
|
||||
* @return {boolean} True if block was bumped.
|
||||
* @param object The object to bump.
|
||||
* @returns True if block was bumped.
|
||||
* @alias Blockly.bumpObjects.bumpIntoBounds
|
||||
*/
|
||||
const bumpObjectIntoBounds = function(workspace, scrollMetrics, object) {
|
||||
function bumpObjectIntoBounds(
|
||||
workspace: WorkspaceSvg, scrollMetrics: ContainerRegion,
|
||||
object: IBoundedElement): boolean {
|
||||
// Compute new top/left position for object.
|
||||
const objectMetrics = object.getBoundingRectangle();
|
||||
const height = objectMetrics.bottom - objectMetrics.top;
|
||||
@@ -82,27 +79,30 @@ const bumpObjectIntoBounds = function(workspace, scrollMetrics, object) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
exports.bumpIntoBounds = bumpObjectIntoBounds;
|
||||
}
|
||||
export const bumpIntoBounds = bumpObjectIntoBounds;
|
||||
|
||||
/**
|
||||
* Creates a handler for bumping objects when they cross fixed bounds.
|
||||
* @param {!WorkspaceSvg} workspace The workspace to handle.
|
||||
* @return {function(Abstract)} The event handler.
|
||||
*
|
||||
* @param workspace The workspace to handle.
|
||||
* @returns The event handler.
|
||||
* @alias Blockly.bumpObjects.bumpIntoBoundsHandler
|
||||
*/
|
||||
const bumpIntoBoundsHandler = function(workspace) {
|
||||
return function(e) {
|
||||
export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
|
||||
(p1: Abstract) => void {
|
||||
return (e) => {
|
||||
const metricsManager = workspace.getMetricsManager();
|
||||
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventUtils.BUMP_EVENTS.indexOf(e.type) !== -1) {
|
||||
if (eventUtils.BUMP_EVENTS.indexOf(e.type ?? '') !== -1) {
|
||||
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
|
||||
|
||||
// Triggered by move/create event
|
||||
const object = extractObjectFromEvent(workspace, e);
|
||||
const object =
|
||||
extractObjectFromEvent(workspace, e as eventUtils.BumpEvent);
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
@@ -111,8 +111,7 @@ const bumpIntoBoundsHandler = function(workspace) {
|
||||
eventUtils.setGroup(e.group);
|
||||
|
||||
const wasBumped = bumpObjectIntoBounds(
|
||||
workspace, scrollMetricsInWsCoords,
|
||||
/** @type {!IBoundedElement} */ (object));
|
||||
workspace, scrollMetricsInWsCoords, (object as IBoundedElement));
|
||||
|
||||
if (wasBumped && !e.group) {
|
||||
console.warn(
|
||||
@@ -123,49 +122,54 @@ const bumpIntoBoundsHandler = function(workspace) {
|
||||
eventUtils.setGroup(oldGroup);
|
||||
}
|
||||
} else if (e.type === eventUtils.VIEWPORT_CHANGE) {
|
||||
const viewportEvent = /** @type {!ViewportChange} */ (e);
|
||||
if (viewportEvent.scale > viewportEvent.oldScale) {
|
||||
const viewportEvent = (e as ViewportChange);
|
||||
if (viewportEvent.scale && viewportEvent.oldScale &&
|
||||
viewportEvent.scale > viewportEvent.oldScale) {
|
||||
bumpTopObjectsIntoBounds(workspace);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
exports.bumpIntoBoundsHandler = bumpIntoBoundsHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the object from the given event.
|
||||
* @param {!WorkspaceSvg} workspace The workspace the event originated
|
||||
*
|
||||
* @param workspace The workspace the event originated
|
||||
* from.
|
||||
* @param {!eventUtils.BumpEvent} e An event containing an object.
|
||||
* @return {?BlockSvg|?WorkspaceCommentSvg} The extracted
|
||||
* @param e An event containing an object.
|
||||
* @returns The extracted
|
||||
* object.
|
||||
*/
|
||||
const extractObjectFromEvent = function(workspace, e) {
|
||||
function extractObjectFromEvent(
|
||||
workspace: WorkspaceSvg, e: eventUtils.BumpEvent): BlockSvg|null|
|
||||
WorkspaceCommentSvg {
|
||||
let object = null;
|
||||
switch (e.type) {
|
||||
case eventUtils.BLOCK_CREATE:
|
||||
case eventUtils.BLOCK_MOVE:
|
||||
object = workspace.getBlockById(e.blockId);
|
||||
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!);
|
||||
if (object) {
|
||||
object = object.getRootBlock();
|
||||
}
|
||||
break;
|
||||
case eventUtils.COMMENT_CREATE:
|
||||
case eventUtils.COMMENT_MOVE:
|
||||
object = (
|
||||
/** @type {?WorkspaceCommentSvg} */
|
||||
(workspace.getCommentById(e.commentId)));
|
||||
object =
|
||||
workspace.getCommentById((e as CommentCreate | CommentMove).commentId!
|
||||
) as WorkspaceCommentSvg |
|
||||
null;
|
||||
break;
|
||||
}
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Bumps the top objects in the given workspace into bounds.
|
||||
* @param {!WorkspaceSvg} workspace The workspace.
|
||||
*
|
||||
* @param workspace The workspace.
|
||||
* @alias Blockly.bumpObjects.bumpTopObjectsIntoBounds
|
||||
*/
|
||||
const bumpTopObjectsIntoBounds = function(workspace) {
|
||||
export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg) {
|
||||
const metricsManager = workspace.getMetricsManager();
|
||||
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
|
||||
return;
|
||||
@@ -173,8 +177,7 @@ const bumpTopObjectsIntoBounds = function(workspace) {
|
||||
|
||||
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
|
||||
const topBlocks = workspace.getTopBoundedElements();
|
||||
for (let i = 0, block; (block = topBlocks[i]); i++) {
|
||||
for (let i = 0, block; block = topBlocks[i]; i++) {
|
||||
bumpObjectIntoBounds(workspace, scrollMetricsInWsCoords, block);
|
||||
}
|
||||
};
|
||||
exports.bumpTopObjectsIntoBounds = bumpTopObjectsIntoBounds;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Blockly's internal clipboard for managing copy-paste.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Blockly's internal clipboard for managing copy-paste.
|
||||
* @namespace Blockly.clipboard
|
||||
*/
|
||||
goog.module('Blockly.clipboard');
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {ICopyable} = goog.requireType('Blockly.ICopyable');
|
||||
|
||||
|
||||
/**
|
||||
* Metadata about the object that is currently on the clipboard.
|
||||
* @type {?ICopyable.CopyData}
|
||||
*/
|
||||
let copyData = null;
|
||||
|
||||
/**
|
||||
* Copy a block or workspace comment onto the local clipboard.
|
||||
* @param {!ICopyable} toCopy Block or Workspace Comment to be copied.
|
||||
* @alias Blockly.clipboard.copy
|
||||
* @package
|
||||
*/
|
||||
const copy = function(toCopy) {
|
||||
copyData = toCopy.toCopyData();
|
||||
};
|
||||
exports.copy = copy;
|
||||
|
||||
/**
|
||||
* Paste a block or workspace comment on to the main workspace.
|
||||
* @return {!ICopyable|null} The pasted thing if the paste
|
||||
* was successful, null otherwise.
|
||||
* @alias Blockly.clipboard.paste
|
||||
* @package
|
||||
*/
|
||||
const paste = function() {
|
||||
if (!copyData) {
|
||||
return null;
|
||||
}
|
||||
// Pasting always pastes to the main workspace, even if the copy
|
||||
// started in a flyout workspace.
|
||||
let workspace = copyData.source;
|
||||
if (workspace.isFlyout) {
|
||||
workspace = workspace.targetWorkspace;
|
||||
}
|
||||
if (copyData.typeCounts &&
|
||||
workspace.isCapacityAvailable(copyData.typeCounts)) {
|
||||
return workspace.paste(copyData.saveInfo);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
exports.paste = paste;
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
* @param {!ICopyable} toDuplicate Block or Workspace Comment to be
|
||||
* duplicated.
|
||||
* @return {!ICopyable|null} The block or workspace comment that was duplicated,
|
||||
* or null if the duplication failed.
|
||||
* @alias Blockly.clipboard.duplicate
|
||||
* @package
|
||||
*/
|
||||
const duplicate = function(toDuplicate) {
|
||||
const oldCopyData = copyData;
|
||||
copy(toDuplicate);
|
||||
const pastedThing = toDuplicate.workspace.paste(copyData.saveInfo);
|
||||
copyData = oldCopyData;
|
||||
return pastedThing;
|
||||
};
|
||||
exports.duplicate = duplicate;
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* 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');
|
||||
|
||||
import type {CopyData, ICopyable} from './interfaces/i_copyable.js';
|
||||
|
||||
|
||||
/** Metadata about the object that is currently on the clipboard. */
|
||||
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) {
|
||||
TEST_ONLY.copyInternal(toCopy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private version of copy for stubbing in tests.
|
||||
*/
|
||||
function copyInternal(toCopy: ICopyable) {
|
||||
copyData = toCopy.toCopyData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
if (!copyData) {
|
||||
return null;
|
||||
}
|
||||
// Pasting always pastes to the main workspace, even if the copy
|
||||
// started in a flyout workspace.
|
||||
let workspace = copyData.source;
|
||||
if (workspace.isFlyout) {
|
||||
workspace = workspace.targetWorkspace!;
|
||||
}
|
||||
if (copyData.typeCounts &&
|
||||
workspace.isCapacityAvailable(copyData.typeCounts)) {
|
||||
return workspace.paste(copyData.saveInfo);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate this block and its children, or a workspace comment.
|
||||
*
|
||||
* @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 {
|
||||
return TEST_ONLY.duplicateInternal(toDuplicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private version of duplicate for stubbing in tests.
|
||||
*/
|
||||
function duplicateInternal(toDuplicate: ICopyable): ICopyable|null {
|
||||
const oldCopyData = copyData;
|
||||
copy(toDuplicate);
|
||||
const pastedThing =
|
||||
toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null;
|
||||
copyData = oldCopyData;
|
||||
return pastedThing;
|
||||
}
|
||||
|
||||
export const TEST_ONLY = {
|
||||
duplicateInternal,
|
||||
copyInternal,
|
||||
};
|
||||
+137
-193
@@ -4,131 +4,88 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Object representing a code comment.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Object representing a code comment.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Comment');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Comment');
|
||||
|
||||
const Css = goog.require('Blockly.Css');
|
||||
const browserEvents = goog.require('Blockly.browserEvents');
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const userAgent = goog.require('Blockly.utils.userAgent');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
const {Bubble} = goog.require('Blockly.Bubble');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Coordinate} = goog.requireType('Blockly.utils.Coordinate');
|
||||
const {Icon} = goog.require('Blockly.Icon');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Size} = goog.requireType('Blockly.utils.Size');
|
||||
const {Svg} = goog.require('Blockly.utils.Svg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockChange');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BubbleOpen');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Warning');
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_block_change.js';
|
||||
// Unused import preserved for side-effects. Remove if unneeded.
|
||||
import './events/events_bubble_open.js';
|
||||
|
||||
import type {CommentModel} from './block.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import {Bubble} from './bubble.js';
|
||||
import * as Css from './css.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import {Icon} from './icon.js';
|
||||
import type {Coordinate} from './utils/coordinate.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import type {Size} from './utils/size.js';
|
||||
import {Svg} from './utils/svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment.
|
||||
* @extends {Icon}
|
||||
*
|
||||
* @alias Blockly.Comment
|
||||
*/
|
||||
class Comment extends Icon {
|
||||
export class Comment extends Icon {
|
||||
private readonly model_: CommentModel;
|
||||
|
||||
/**
|
||||
* @param {!BlockSvg} block The block associated with this comment.
|
||||
* The model's text value at the start of an edit.
|
||||
* Used to tell if an event should be fired at the end of an edit.
|
||||
*/
|
||||
constructor(block) {
|
||||
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;
|
||||
|
||||
/**
|
||||
* The SVG element that contains the text edit area, or null if not created.
|
||||
*/
|
||||
private foreignObject_: SVGForeignObjectElement|null = null;
|
||||
|
||||
/** The editable text area, or null if not created. */
|
||||
private textarea_: HTMLTextAreaElement|null = null;
|
||||
|
||||
/** The top-level node of the comment text, or null if not created. */
|
||||
private paragraphElement_: SVGTextElement|null = null;
|
||||
|
||||
/** @param block The block associated with this comment. */
|
||||
constructor(block: BlockSvg) {
|
||||
super(block);
|
||||
|
||||
/**
|
||||
* The model for this comment.
|
||||
* @type {!Block.CommentModel}
|
||||
* @private
|
||||
*/
|
||||
/** The model for this comment. */
|
||||
this.model_ = block.commentModel;
|
||||
// If someone creates the comment directly instead of calling
|
||||
// block.setCommentText we want to make sure the text is non-null;
|
||||
this.model_.text = this.model_.text || '';
|
||||
|
||||
/**
|
||||
* The model's text value at the start of an edit.
|
||||
* Used to tell if an event should be fired at the end of an edit.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.cachedText_ = '';
|
||||
|
||||
/**
|
||||
* Mouse up event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
this.onMouseUpWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Wheel event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
this.onWheelWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Change event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
this.onChangeWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Input event data.
|
||||
* @type {?browserEvents.Data}
|
||||
* @private
|
||||
*/
|
||||
this.onInputWrapper_ = null;
|
||||
|
||||
/**
|
||||
* The SVG element that contains the text edit area, or null if not created.
|
||||
* @type {?SVGForeignObjectElement}
|
||||
* @private
|
||||
*/
|
||||
this.foreignObject_ = null;
|
||||
|
||||
/**
|
||||
* The editable text area, or null if not created.
|
||||
* @type {?Element}
|
||||
* @private
|
||||
*/
|
||||
this.textarea_ = null;
|
||||
|
||||
/**
|
||||
* The top-level node of the comment text, or null if not created.
|
||||
* @type {?SVGTextElement}
|
||||
* @private
|
||||
*/
|
||||
this.paragraphElement_ = null;
|
||||
this.model_.text = this.model_.text ?? '';
|
||||
|
||||
this.createIcon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the comment icon.
|
||||
* @param {!Element} group The icon group.
|
||||
* @protected
|
||||
*
|
||||
* @param group The icon group.
|
||||
*/
|
||||
drawIcon_(group) {
|
||||
protected override drawIcon_(group: Element) {
|
||||
// Circle.
|
||||
dom.createSvgElement(
|
||||
Svg.CIRCLE,
|
||||
@@ -157,39 +114,40 @@ class Comment extends Icon {
|
||||
|
||||
/**
|
||||
* Create the editor for the comment's bubble.
|
||||
* @return {!SVGElement} The top-level node of the editor.
|
||||
* @private
|
||||
*
|
||||
* @returns The top-level node of the editor.
|
||||
*/
|
||||
createEditor_() {
|
||||
private createEditor_(): SVGElement {
|
||||
/* Create the editor. Here's the markup that will be generated in
|
||||
* editable mode:
|
||||
<foreignObject x="8" y="8" width="164" height="164">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
|
||||
<textarea xmlns="http://www.w3.org/1999/xhtml"
|
||||
class="blocklyCommentTextarea"
|
||||
style="height: 164px; width: 164px;"></textarea>
|
||||
</body>
|
||||
</foreignObject>
|
||||
* For non-editable mode see Warning.textToDom_.
|
||||
*/
|
||||
* editable mode:
|
||||
<foreignObject x="8" y="8" width="164" height="164">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml"
|
||||
class="blocklyMinimalBody"> <textarea
|
||||
xmlns="http://www.w3.org/1999/xhtml" class="blocklyCommentTextarea"
|
||||
style="height: 164px; width: 164px;"></textarea>
|
||||
</body>
|
||||
</foreignObject>
|
||||
* For non-editable mode see Warning.textToDom_.
|
||||
*/
|
||||
|
||||
this.foreignObject_ = dom.createSvgElement(
|
||||
Svg.FOREIGNOBJECT, {'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH},
|
||||
null);
|
||||
Svg.FOREIGNOBJECT,
|
||||
{'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH});
|
||||
|
||||
const body = document.createElementNS(dom.HTML_NS, 'body');
|
||||
body.setAttribute('xmlns', dom.HTML_NS);
|
||||
body.className = 'blocklyMinimalBody';
|
||||
|
||||
this.textarea_ = document.createElementNS(dom.HTML_NS, 'textarea');
|
||||
this.textarea_ = document.createElementNS(dom.HTML_NS, 'textarea') as
|
||||
HTMLTextAreaElement;
|
||||
const textarea = this.textarea_;
|
||||
textarea.className = 'blocklyCommentTextarea';
|
||||
textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
|
||||
textarea.value = this.model_.text;
|
||||
textarea.setAttribute('dir', this.getBlock().RTL ? 'RTL' : 'LTR');
|
||||
textarea.value = this.model_.text ?? '';
|
||||
this.resizeTextarea_();
|
||||
|
||||
body.appendChild(textarea);
|
||||
this.foreignObject_.appendChild(body);
|
||||
this.foreignObject_!.appendChild(body);
|
||||
|
||||
// Ideally this would be hooked to the focus event for the comment.
|
||||
// However doing so in Firefox swallows the cursor for unknown reasons.
|
||||
@@ -197,30 +155,28 @@ class Comment extends Icon {
|
||||
this.onMouseUpWrapper_ = browserEvents.conditionalBind(
|
||||
textarea, 'mouseup', this, this.startEdit_, true, true);
|
||||
// Don't zoom with mousewheel.
|
||||
this.onWheelWrapper_ =
|
||||
browserEvents.conditionalBind(textarea, 'wheel', this, function(e) {
|
||||
this.onWheelWrapper_ = browserEvents.conditionalBind(
|
||||
textarea, 'wheel', this, function(e: Event) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
this.onChangeWrapper_ = browserEvents.conditionalBind(
|
||||
textarea, 'change', this,
|
||||
/**
|
||||
* @this {Comment}
|
||||
* @param {Event} _e Unused event parameter.
|
||||
* @param _e Unused event parameter.
|
||||
*/
|
||||
function(_e) {
|
||||
function(this: Comment, _e: Event) {
|
||||
if (this.cachedText_ !== this.model_.text) {
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
|
||||
this.block_, 'comment', null, this.cachedText_,
|
||||
this.getBlock(), 'comment', null, this.cachedText_,
|
||||
this.model_.text));
|
||||
}
|
||||
});
|
||||
this.onInputWrapper_ = browserEvents.conditionalBind(
|
||||
textarea, 'input', this,
|
||||
/**
|
||||
* @this {Comment}
|
||||
* @param {Event} _e Unused event parameter.
|
||||
* @param _e Unused event parameter.
|
||||
*/
|
||||
function(_e) {
|
||||
function(this: Comment, _e: Event) {
|
||||
this.model_.text = textarea.value;
|
||||
});
|
||||
|
||||
@@ -229,11 +185,8 @@ class Comment extends Icon {
|
||||
return this.foreignObject_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or remove editability of the comment.
|
||||
* @override
|
||||
*/
|
||||
updateEditable() {
|
||||
/** Add or remove editability of the comment. */
|
||||
override updateEditable() {
|
||||
super.updateEditable();
|
||||
if (this.isVisible()) {
|
||||
// Recreate the bubble with the correct UI.
|
||||
@@ -245,12 +198,12 @@ class Comment extends Icon {
|
||||
/**
|
||||
* Callback function triggered when the bubble has resized.
|
||||
* Resize the text area accordingly.
|
||||
* @private
|
||||
*/
|
||||
onBubbleResize_() {
|
||||
if (!this.isVisible()) {
|
||||
private onBubbleResize_() {
|
||||
if (!this.isVisible() || !this.bubble_) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model_.size = this.bubble_.getBubbleSize();
|
||||
this.resizeTextarea_();
|
||||
}
|
||||
@@ -258,29 +211,29 @@ class Comment extends Icon {
|
||||
/**
|
||||
* Resizes the text area to match the size defined on the model (which is
|
||||
* the size of the bubble).
|
||||
* @private
|
||||
*/
|
||||
resizeTextarea_() {
|
||||
private resizeTextarea_() {
|
||||
const size = this.model_.size;
|
||||
const doubleBorderWidth = 2 * Bubble.BORDER_WIDTH;
|
||||
const widthMinusBorder = size.width - doubleBorderWidth;
|
||||
const heightMinusBorder = size.height - doubleBorderWidth;
|
||||
this.foreignObject_.setAttribute('width', widthMinusBorder);
|
||||
this.foreignObject_.setAttribute('height', heightMinusBorder);
|
||||
this.textarea_.style.width = (widthMinusBorder - 4) + 'px';
|
||||
this.textarea_.style.height = (heightMinusBorder - 4) + 'px';
|
||||
this.foreignObject_!.setAttribute('width', `${widthMinusBorder}`);
|
||||
this.foreignObject_!.setAttribute('height', `${heightMinusBorder}`);
|
||||
this.textarea_!.style.width = widthMinusBorder - 4 + 'px';
|
||||
this.textarea_!.style.height = heightMinusBorder - 4 + 'px';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide the comment bubble.
|
||||
* @param {boolean} visible True if the bubble should be visible.
|
||||
*
|
||||
* @param visible True if the bubble should be visible.
|
||||
*/
|
||||
setVisible(visible) {
|
||||
override setVisible(visible: boolean) {
|
||||
if (visible === this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
|
||||
this.block_, visible, 'comment'));
|
||||
this.getBlock(), visible, 'comment'));
|
||||
this.model_.pinned = visible;
|
||||
if (visible) {
|
||||
this.createBubble_();
|
||||
@@ -289,57 +242,47 @@ class Comment extends Icon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the bubble. Handles deciding if it should be editable or not.
|
||||
* @private
|
||||
*/
|
||||
createBubble_() {
|
||||
if (!this.block_.isEditable() || userAgent.IE) {
|
||||
// MSIE does not support foreignobject; textareas are impossible.
|
||||
// https://docs.microsoft.com/en-us/openspecs/ie_standards/ms-svg/56e6e04c-7c8c-44dd-8100-bd745ee42034
|
||||
// Always treat comments in IE as uneditable.
|
||||
/** Show the bubble. Handles deciding if it should be editable or not. */
|
||||
private createBubble_() {
|
||||
if (!this.getBlock().isEditable()) {
|
||||
this.createNonEditableBubble_();
|
||||
} else {
|
||||
this.createEditableBubble_();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an editable bubble.
|
||||
* @private
|
||||
*/
|
||||
createEditableBubble_() {
|
||||
/** Show an editable bubble. */
|
||||
private createEditableBubble_() {
|
||||
const block = this.getBlock();
|
||||
this.bubble_ = new Bubble(
|
||||
/** @type {!WorkspaceSvg} */ (this.block_.workspace),
|
||||
this.createEditor_(), this.block_.pathObject.svgPath,
|
||||
/** @type {!Coordinate} */ (this.iconXY_), this.model_.size.width,
|
||||
block.workspace, this.createEditor_(), block.pathObject.svgPath,
|
||||
(this.iconXY_ as Coordinate), this.model_.size.width,
|
||||
this.model_.size.height);
|
||||
// Expose this comment's block's ID on its top-level SVG group.
|
||||
this.bubble_.setSvgId(this.block_.id);
|
||||
this.bubble_.setSvgId(block.id);
|
||||
this.bubble_.registerResizeEvent(this.onBubbleResize_.bind(this));
|
||||
this.applyColour();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a non-editable bubble.
|
||||
* @private
|
||||
*
|
||||
* @suppress {checkTypes} Suppress `this` type mismatch.
|
||||
*/
|
||||
createNonEditableBubble_() {
|
||||
private createNonEditableBubble_() {
|
||||
// TODO (#2917): It would be great if the comment could support line breaks.
|
||||
this.paragraphElement_ = Bubble.textToDom(this.block_.getCommentText());
|
||||
this.paragraphElement_ = Bubble.textToDom(this.model_.text ?? '');
|
||||
this.bubble_ = Bubble.createNonEditableBubble(
|
||||
this.paragraphElement_, /** @type {!BlockSvg} */ (this.block_),
|
||||
/** @type {!Coordinate} */ (this.iconXY_));
|
||||
this.paragraphElement_, this.getBlock(), this.iconXY_ as Coordinate);
|
||||
this.applyColour();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of the bubble.
|
||||
* @private
|
||||
*
|
||||
* @suppress {checkTypes} Suppress `this` type mismatch.
|
||||
*/
|
||||
disposeBubble_() {
|
||||
private disposeBubble_() {
|
||||
if (this.onMouseUpWrapper_) {
|
||||
browserEvents.unbind(this.onMouseUpWrapper_);
|
||||
this.onMouseUpWrapper_ = null;
|
||||
@@ -356,8 +299,10 @@ class Comment extends Icon {
|
||||
browserEvents.unbind(this.onInputWrapper_);
|
||||
this.onInputWrapper_ = null;
|
||||
}
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
if (this.bubble_) {
|
||||
this.bubble_.dispose();
|
||||
this.bubble_ = null;
|
||||
}
|
||||
this.textarea_ = null;
|
||||
this.foreignObject_ = null;
|
||||
this.paragraphElement_ = null;
|
||||
@@ -368,14 +313,14 @@ class Comment extends Icon {
|
||||
*
|
||||
* Bring the comment to the top of the stack when clicked on. Also cache the
|
||||
* current text so it can be used to fire a change event.
|
||||
* @param {!Event} _e Mouse up event.
|
||||
* @private
|
||||
*
|
||||
* @param _e Mouse up event.
|
||||
*/
|
||||
startEdit_(_e) {
|
||||
if (this.bubble_.promote()) {
|
||||
private startEdit_(_e: Event) {
|
||||
if (this.bubble_?.promote()) {
|
||||
// Since the act of moving this node within the DOM causes a loss of
|
||||
// focus, we need to reapply the focus.
|
||||
this.textarea_.focus();
|
||||
this.textarea_!.focus();
|
||||
}
|
||||
|
||||
this.cachedText_ = this.model_.text;
|
||||
@@ -383,18 +328,20 @@ class Comment extends Icon {
|
||||
|
||||
/**
|
||||
* Get the dimensions of this comment's bubble.
|
||||
* @return {Size} Object with width and height properties.
|
||||
*
|
||||
* @returns Object with width and height properties.
|
||||
*/
|
||||
getBubbleSize() {
|
||||
getBubbleSize(): Size {
|
||||
return this.model_.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size this comment's bubble.
|
||||
* @param {number} width Width of the bubble.
|
||||
* @param {number} height Height of the bubble.
|
||||
*
|
||||
* @param width Width of the bubble.
|
||||
* @param height Height of the bubble.
|
||||
*/
|
||||
setBubbleSize(width, height) {
|
||||
setBubbleSize(width: number, height: number) {
|
||||
if (this.bubble_) {
|
||||
this.bubble_.setBubbleSize(width, height);
|
||||
} else {
|
||||
@@ -405,15 +352,16 @@ class Comment extends Icon {
|
||||
|
||||
/**
|
||||
* Update the comment's view to match the model.
|
||||
* @package
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
updateText() {
|
||||
if (this.textarea_) {
|
||||
this.textarea_.value = this.model_.text;
|
||||
this.textarea_.value = this.model_.text ?? '';
|
||||
} else if (this.paragraphElement_) {
|
||||
// Non-Editable mode.
|
||||
// TODO (#2917): If 2917 gets added this will probably need to be updated.
|
||||
this.paragraphElement_.firstChild.textContent = this.model_.text;
|
||||
this.paragraphElement_.firstChild!.textContent = this.model_.text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,15 +371,13 @@ class Comment extends Icon {
|
||||
* If you want to receive a comment "delete" event (newValue: null), then this
|
||||
* should not be called directly. Instead call block.setCommentText(null);
|
||||
*/
|
||||
dispose() {
|
||||
this.block_.comment = null;
|
||||
Icon.prototype.dispose.call(this);
|
||||
override dispose() {
|
||||
this.getBlock().comment = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS for block comment. See css.js for use.
|
||||
*/
|
||||
/** CSS for block comment. See css.js for use. */
|
||||
Css.register(`
|
||||
.blocklyCommentTextarea {
|
||||
background-color: #fef49c;
|
||||
@@ -444,5 +390,3 @@ Css.register(`
|
||||
text-overflow: hidden;
|
||||
}
|
||||
`);
|
||||
|
||||
exports.Comment = Comment;
|
||||
+132
-89
@@ -4,120 +4,152 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Common functions used both internally and externally, but which
|
||||
* must not be at the top level to avoid circular dependencies.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Common functions used both internally and externally, but which
|
||||
* must not be at the top level to avoid circular dependencies.
|
||||
*
|
||||
* @namespace Blockly.common
|
||||
*/
|
||||
goog.module('Blockly.common');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.common');
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockDefinition, Blocks} = goog.require('Blockly.blocks');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Connection} = goog.requireType('Blockly.Connection');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {ICopyable} = goog.requireType('Blockly.ICopyable');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
import type {Block} from './block.js';
|
||||
import {BlockDefinition, Blocks} from './blocks.js';
|
||||
import type {Connection} from './connection.js';
|
||||
import type {ICopyable} from './interfaces/i_copyable.js';
|
||||
import type {Workspace} from './workspace.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
/** Database of all workspaces. */
|
||||
const WorkspaceDB_ = Object.create(null);
|
||||
|
||||
|
||||
/**
|
||||
* Find the workspace with the specified ID.
|
||||
*
|
||||
* @param id ID of workspace to find.
|
||||
* @returns The sought after workspace or null if not found.
|
||||
*/
|
||||
export function getWorkspaceById(id: string): Workspace|null {
|
||||
return WorkspaceDB_[id] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all workspaces.
|
||||
*
|
||||
* @returns Array of workspaces.
|
||||
*/
|
||||
export function getAllWorkspaces(): Workspace[] {
|
||||
const workspaces = [];
|
||||
for (const workspaceId in WorkspaceDB_) {
|
||||
workspaces.push(WorkspaceDB_[workspaceId]);
|
||||
}
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a workspace in the workspace db.
|
||||
*
|
||||
* @param workspace
|
||||
*/
|
||||
export function registerWorkspace(workspace: Workspace) {
|
||||
WorkspaceDB_[workspace.id] = workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a workspace from the workspace db.
|
||||
*
|
||||
* @param workspace
|
||||
*/
|
||||
export function unregisterWorkpace(workspace: Workspace) {
|
||||
delete WorkspaceDB_[workspace.id];
|
||||
}
|
||||
|
||||
/**
|
||||
* The main workspace most recently used.
|
||||
* Set by Blockly.WorkspaceSvg.prototype.markFocused
|
||||
* @type {!Workspace}
|
||||
*/
|
||||
let mainWorkspace;
|
||||
let mainWorkspace: Workspace;
|
||||
|
||||
/**
|
||||
* Returns the last used top level workspace (based on focus). Try not to use
|
||||
* this function, particularly if there are multiple Blockly instances on a
|
||||
* page.
|
||||
* @return {!Workspace} The main workspace.
|
||||
*
|
||||
* @returns The main workspace.
|
||||
* @alias Blockly.common.getMainWorkspace
|
||||
*/
|
||||
const getMainWorkspace = function() {
|
||||
export function getMainWorkspace(): Workspace {
|
||||
return mainWorkspace;
|
||||
};
|
||||
exports.getMainWorkspace = getMainWorkspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets last used main workspace.
|
||||
* @param {!Workspace} workspace The most recently used top level workspace.
|
||||
*
|
||||
* @param workspace The most recently used top level workspace.
|
||||
* @alias Blockly.common.setMainWorkspace
|
||||
*/
|
||||
const setMainWorkspace = function(workspace) {
|
||||
export function setMainWorkspace(workspace: Workspace) {
|
||||
mainWorkspace = workspace;
|
||||
};
|
||||
exports.setMainWorkspace = setMainWorkspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently selected block.
|
||||
* @type {?ICopyable}
|
||||
* Currently selected copyable object.
|
||||
*/
|
||||
let selected = null;
|
||||
let selected: ICopyable|null = null;
|
||||
|
||||
/**
|
||||
* Returns the currently selected block.
|
||||
* @return {?ICopyable} The currently selected block.
|
||||
* Returns the currently selected copyable object.
|
||||
*
|
||||
* @alias Blockly.common.getSelected
|
||||
*/
|
||||
const getSelected = function() {
|
||||
export function getSelected(): ICopyable|null {
|
||||
return selected;
|
||||
};
|
||||
exports.getSelected = getSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently selected block. This function does not visually mark the
|
||||
* block as selected or fire the required events. If you wish to
|
||||
* programmatically select a block, use `BlockSvg#select`.
|
||||
* @param {?ICopyable} newSelection The newly selected block.
|
||||
*
|
||||
* @param newSelection The newly selected block.
|
||||
* @alias Blockly.common.setSelected
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const setSelected = function(newSelection) {
|
||||
export function setSelected(newSelection: ICopyable|null) {
|
||||
selected = newSelection;
|
||||
};
|
||||
exports.setSelected = setSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container element in which to render the WidgetDiv, DropDownDiv and Tooltip.
|
||||
* @type {?Element}
|
||||
*/
|
||||
let parentContainer;
|
||||
let parentContainer: Element|null;
|
||||
|
||||
/**
|
||||
* Get the container element in which to render the WidgetDiv, DropDownDiv and\
|
||||
* Get the container element in which to render the WidgetDiv, DropDownDiv and
|
||||
* Tooltip.
|
||||
* @return {?Element} The parent container.
|
||||
*
|
||||
* @returns The parent container.
|
||||
* @alias Blockly.common.getParentContainer
|
||||
*/
|
||||
const getParentContainer = function() {
|
||||
export function getParentContainer(): Element|null {
|
||||
return parentContainer;
|
||||
};
|
||||
exports.getParentContainer = getParentContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent container. This is the container element that the WidgetDiv,
|
||||
* DropDownDiv, and Tooltip are rendered into the first time `Blockly.inject`
|
||||
* is called.
|
||||
* This method is a NOP if called after the first ``Blockly.inject``.
|
||||
* @param {!Element} newParent The container element.
|
||||
* This method is a NOP if called after the first `Blockly.inject`.
|
||||
*
|
||||
* @param newParent The container element.
|
||||
* @alias Blockly.common.setParentContainer
|
||||
*/
|
||||
const setParentContainer = function(newParent) {
|
||||
export function setParentContainer(newParent: Element) {
|
||||
parentContainer = newParent;
|
||||
};
|
||||
exports.setParentContainer = setParentContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Size the SVG image to completely fill its container. Call this when the view
|
||||
@@ -125,21 +157,23 @@ exports.setParentContainer = setParentContainer;
|
||||
* See workspace.resizeContents to resize the workspace when the contents
|
||||
* change (e.g. when a block is added or removed).
|
||||
* Record the height/width of the SVG image.
|
||||
* @param {!WorkspaceSvg} workspace Any workspace in the SVG.
|
||||
*
|
||||
* @param workspace Any workspace in the SVG.
|
||||
* @alias Blockly.common.svgResize
|
||||
*/
|
||||
const svgResize = function(workspace) {
|
||||
export function svgResize(workspace: WorkspaceSvg) {
|
||||
let mainWorkspace = workspace;
|
||||
while (mainWorkspace.options.parentWorkspace) {
|
||||
mainWorkspace = mainWorkspace.options.parentWorkspace;
|
||||
}
|
||||
const svg = mainWorkspace.getParentSvg();
|
||||
const cachedSize = mainWorkspace.getCachedParentSvgSize();
|
||||
const div = svg.parentNode;
|
||||
if (!div) {
|
||||
const div = svg.parentElement;
|
||||
if (!(div instanceof HTMLElement)) {
|
||||
// Workspace deleted, or something.
|
||||
return;
|
||||
}
|
||||
|
||||
const width = div.offsetWidth;
|
||||
const height = div.offsetHeight;
|
||||
if (cachedSize.width !== width) {
|
||||
@@ -151,26 +185,26 @@ const svgResize = function(workspace) {
|
||||
mainWorkspace.setCachedParentSvgSize(null, height);
|
||||
}
|
||||
mainWorkspace.resize();
|
||||
};
|
||||
exports.svgResize = svgResize;
|
||||
}
|
||||
|
||||
/**
|
||||
* All of the connections on blocks that are currently being dragged.
|
||||
* @type {!Array<!Connection>}
|
||||
*/
|
||||
exports.draggingConnections = [];
|
||||
export const draggingConnections: Connection[] = [];
|
||||
|
||||
/**
|
||||
* Get a map of all the block's descendants mapping their type to the number of
|
||||
* children with that type.
|
||||
* @param {!Block} block The block to map.
|
||||
* @param {boolean=} opt_stripFollowing Optionally ignore all following
|
||||
*
|
||||
* @param block The block to map.
|
||||
* @param opt_stripFollowing Optionally ignore all following
|
||||
* statements (blocks that are not inside a value or statement input
|
||||
* of the block).
|
||||
* @return {!Object} Map of types to type counts for descendants of the bock.
|
||||
* @returns Map of types to type counts for descendants of the bock.
|
||||
* @alias Blockly.common.getBlockTypeCounts
|
||||
*/
|
||||
const getBlockTypeCounts = function(block, opt_stripFollowing) {
|
||||
export function getBlockTypeCounts(
|
||||
block: Block, opt_stripFollowing?: boolean): {[key: string]: number} {
|
||||
const typeCountsMap = Object.create(null);
|
||||
const descendants = block.getDescendants(true);
|
||||
if (opt_stripFollowing) {
|
||||
@@ -180,7 +214,7 @@ const getBlockTypeCounts = function(block, opt_stripFollowing) {
|
||||
descendants.splice(index, descendants.length - index);
|
||||
}
|
||||
}
|
||||
for (let i = 0, checkBlock; (checkBlock = descendants[i]); i++) {
|
||||
for (let i = 0, checkBlock; checkBlock = descendants[i]; i++) {
|
||||
if (typeCountsMap[checkBlock.type]) {
|
||||
typeCountsMap[checkBlock.type]++;
|
||||
} else {
|
||||
@@ -188,50 +222,59 @@ const getBlockTypeCounts = function(block, opt_stripFollowing) {
|
||||
}
|
||||
}
|
||||
return typeCountsMap;
|
||||
};
|
||||
exports.getBlockTypeCounts = getBlockTypeCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for defining a block from JSON. The resulting function has
|
||||
* the correct value of jsonDef at the point in code where jsonInit is called.
|
||||
* @param {!Object} jsonDef The JSON definition of a block.
|
||||
* @return {function()} A function that calls jsonInit with the correct value
|
||||
*
|
||||
* @param jsonDef The JSON definition of a block.
|
||||
* @returns A function that calls jsonInit with the correct value
|
||||
* of jsonDef.
|
||||
*/
|
||||
const jsonInitFactory = function(jsonDef) {
|
||||
return /** @this {Block} */ function() {
|
||||
function jsonInitFactory(jsonDef: AnyDuringMigration): () => void {
|
||||
return function(this: Block) {
|
||||
this.jsonInit(jsonDef);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Define blocks from an array of JSON block definitions, as might be generated
|
||||
* by the Blockly Developer Tools.
|
||||
* @param {!Array<!Object>} jsonArray An array of JSON block definitions.
|
||||
*
|
||||
* @param jsonArray An array of JSON block definitions.
|
||||
* @alias Blockly.common.defineBlocksWithJsonArray
|
||||
*/
|
||||
const defineBlocksWithJsonArray = function(jsonArray) {
|
||||
export function defineBlocksWithJsonArray(jsonArray: AnyDuringMigration[]) {
|
||||
TEST_ONLY.defineBlocksWithJsonArrayInternal(jsonArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private version of defineBlocksWithJsonArray for stubbing in tests.
|
||||
*/
|
||||
function defineBlocksWithJsonArrayInternal(jsonArray: AnyDuringMigration[]) {
|
||||
defineBlocks(createBlockDefinitionsFromJsonArray(jsonArray));
|
||||
};
|
||||
exports.defineBlocksWithJsonArray = defineBlocksWithJsonArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define blocks from an array of JSON block definitions, as might be generated
|
||||
* by the Blockly Developer Tools.
|
||||
* @param {!Array<!Object>} jsonArray An array of JSON block definitions.
|
||||
* @return {!Object<string, !BlockDefinition>} A map of the block
|
||||
*
|
||||
* @param jsonArray An array of JSON block definitions.
|
||||
* @returns A map of the block
|
||||
* definitions created.
|
||||
* @alias Blockly.common.defineBlocksWithJsonArray
|
||||
*/
|
||||
const createBlockDefinitionsFromJsonArray = function(jsonArray) {
|
||||
const /** @type {!Object<string,!BlockDefinition>} */ blocks = {};
|
||||
export function createBlockDefinitionsFromJsonArray(
|
||||
jsonArray: AnyDuringMigration[]): {[key: string]: BlockDefinition} {
|
||||
const blocks: {[key: string]: BlockDefinition} = {};
|
||||
for (let i = 0; i < jsonArray.length; i++) {
|
||||
const elem = jsonArray[i];
|
||||
if (!elem) {
|
||||
console.warn(`Block definition #${i} in JSON array is ${elem}. Skipping`);
|
||||
continue;
|
||||
}
|
||||
const type = elem.type;
|
||||
const type = elem['type'];
|
||||
if (!type) {
|
||||
console.warn(
|
||||
`Block definition #${i} in JSON array is missing a type attribute. ` +
|
||||
@@ -241,18 +284,17 @@ const createBlockDefinitionsFromJsonArray = function(jsonArray) {
|
||||
blocks[type] = {init: jsonInitFactory(elem)};
|
||||
}
|
||||
return blocks;
|
||||
};
|
||||
exports.createBlockDefinitionsFromJsonArray =
|
||||
createBlockDefinitionsFromJsonArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the specified block definitions to the block definitions
|
||||
* dictionary (Blockly.Blocks).
|
||||
* @param {!Object<string,!BlockDefinition>} blocks A map of block
|
||||
*
|
||||
* @param blocks A map of block
|
||||
* type names to block definitions.
|
||||
* @alias Blockly.common.defineBlocks
|
||||
*/
|
||||
const defineBlocks = function(blocks) {
|
||||
export function defineBlocks(blocks: {[key: string]: BlockDefinition}) {
|
||||
// Iterate over own enumerable properties.
|
||||
for (const type of Object.keys(blocks)) {
|
||||
const definition = blocks[type];
|
||||
@@ -261,5 +303,6 @@ const defineBlocks = function(blocks) {
|
||||
}
|
||||
Blocks[type] = definition;
|
||||
}
|
||||
};
|
||||
exports.defineBlocks = defineBlocks;
|
||||
}
|
||||
|
||||
export const TEST_ONLY = {defineBlocksWithJsonArrayInternal};
|
||||
@@ -1,263 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Manager for all items registered with the workspace.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Manager for all items registered with the workspace.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.ComponentManager');
|
||||
|
||||
const arrayUtils = goog.require('Blockly.utils.array');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IAutoHideable} = goog.requireType('Blockly.IAutoHideable');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IComponent} = goog.requireType('Blockly.IComponent');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDeleteArea} = goog.requireType('Blockly.IDeleteArea');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDragTarget} = goog.requireType('Blockly.IDragTarget');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IPositionable} = goog.requireType('Blockly.IPositionable');
|
||||
|
||||
|
||||
/**
|
||||
* Manager for all items registered with the workspace.
|
||||
* @alias Blockly.ComponentManager
|
||||
*/
|
||||
class ComponentManager {
|
||||
/**
|
||||
* Creates a new ComponentManager instance.
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* A map of the components registered with the workspace, mapped to id.
|
||||
* @type {!Object<string, !ComponentManager.ComponentDatum>}
|
||||
* @private
|
||||
*/
|
||||
this.componentData_ = Object.create(null);
|
||||
|
||||
/**
|
||||
* A map of capabilities to component IDs.
|
||||
* @type {!Object<string, !Array<string>>}
|
||||
* @private
|
||||
*/
|
||||
this.capabilityToComponentIds_ = Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a component.
|
||||
* @param {!ComponentManager.ComponentDatum} componentInfo The data for
|
||||
* the component to register.
|
||||
* @param {boolean=} opt_allowOverrides True to prevent an error when
|
||||
* overriding an already registered item.
|
||||
*/
|
||||
addComponent(componentInfo, opt_allowOverrides) {
|
||||
// Don't throw an error if opt_allowOverrides is true.
|
||||
const id = componentInfo.component.id;
|
||||
if (!opt_allowOverrides && this.componentData_[id]) {
|
||||
throw Error(
|
||||
'Plugin "' + id + '" with capabilities "' +
|
||||
this.componentData_[id].capabilities + '" already added.');
|
||||
}
|
||||
this.componentData_[id] = componentInfo;
|
||||
const stringCapabilities = [];
|
||||
for (let i = 0; i < componentInfo.capabilities.length; i++) {
|
||||
const capability = String(componentInfo.capabilities[i]).toLowerCase();
|
||||
stringCapabilities.push(capability);
|
||||
if (this.capabilityToComponentIds_[capability] === undefined) {
|
||||
this.capabilityToComponentIds_[capability] = [id];
|
||||
} else {
|
||||
this.capabilityToComponentIds_[capability].push(id);
|
||||
}
|
||||
}
|
||||
this.componentData_[id].capabilities = stringCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a component.
|
||||
* @param {string} id The ID of the component to remove.
|
||||
*/
|
||||
removeComponent(id) {
|
||||
const componentInfo = this.componentData_[id];
|
||||
if (!componentInfo) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < componentInfo.capabilities.length; i++) {
|
||||
const capability = String(componentInfo.capabilities[i]).toLowerCase();
|
||||
arrayUtils.removeElem(this.capabilityToComponentIds_[capability], id);
|
||||
}
|
||||
delete this.componentData_[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a capability to a existing registered component.
|
||||
* @param {string} id The ID of the component to add the capability to.
|
||||
* @param {string|!ComponentManager.Capability<T>} capability The
|
||||
* capability to add.
|
||||
* @template T
|
||||
*/
|
||||
addCapability(id, capability) {
|
||||
if (!this.getComponent(id)) {
|
||||
throw Error(
|
||||
'Cannot add capability, "' + capability + '". Plugin "' + id +
|
||||
'" has not been added to the ComponentManager');
|
||||
}
|
||||
if (this.hasCapability(id, capability)) {
|
||||
console.warn(
|
||||
'Plugin "' + id + 'already has capability "' + capability + '"');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
this.componentData_[id].capabilities.push(capability);
|
||||
this.capabilityToComponentIds_[capability].push(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a capability from an existing registered component.
|
||||
* @param {string} id The ID of the component to remove the capability from.
|
||||
* @param {string|!ComponentManager.Capability<T>} capability The
|
||||
* capability to remove.
|
||||
* @template T
|
||||
*/
|
||||
removeCapability(id, capability) {
|
||||
if (!this.getComponent(id)) {
|
||||
throw Error(
|
||||
'Cannot remove capability, "' + capability + '". Plugin "' + id +
|
||||
'" has not been added to the ComponentManager');
|
||||
}
|
||||
if (!this.hasCapability(id, capability)) {
|
||||
console.warn(
|
||||
'Plugin "' + id + 'doesn\'t have capability "' + capability +
|
||||
'" to remove');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
arrayUtils.removeElem(this.componentData_[id].capabilities, capability);
|
||||
arrayUtils.removeElem(this.capabilityToComponentIds_[capability], id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the component with this id has the specified capability.
|
||||
* @param {string} id The ID of the component to check.
|
||||
* @param {string|!ComponentManager.Capability<T>} capability The
|
||||
* capability to check for.
|
||||
* @return {boolean} Whether the component has the capability.
|
||||
* @template T
|
||||
*/
|
||||
hasCapability(id, capability) {
|
||||
capability = String(capability).toLowerCase();
|
||||
return this.componentData_[id].capabilities.indexOf(capability) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component with the given ID.
|
||||
* @param {string} id The ID of the component to get.
|
||||
* @return {!IComponent|undefined} The component with the given name
|
||||
* or undefined if not found.
|
||||
*/
|
||||
getComponent(id) {
|
||||
return this.componentData_[id] && this.componentData_[id].component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the components with the specified capability.
|
||||
* @param {string|!ComponentManager.Capability<T>
|
||||
* } capability The capability of the component.
|
||||
* @param {boolean} sorted Whether to return list ordered by weights.
|
||||
* @return {!Array<T>} The components that match the specified capability.
|
||||
* @template T
|
||||
*/
|
||||
getComponents(capability, sorted) {
|
||||
capability = String(capability).toLowerCase();
|
||||
const componentIds = this.capabilityToComponentIds_[capability];
|
||||
if (!componentIds) {
|
||||
return [];
|
||||
}
|
||||
const components = [];
|
||||
if (sorted) {
|
||||
const componentDataList = [];
|
||||
const componentData = this.componentData_;
|
||||
componentIds.forEach(function(id) {
|
||||
componentDataList.push(componentData[id]);
|
||||
});
|
||||
componentDataList.sort(function(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
componentDataList.forEach(function(ComponentDatum) {
|
||||
components.push(ComponentDatum.component);
|
||||
});
|
||||
} else {
|
||||
const componentData = this.componentData_;
|
||||
componentIds.forEach(function(id) {
|
||||
components.push(componentData[id].component);
|
||||
});
|
||||
}
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object storing component information.
|
||||
* @typedef {{
|
||||
* component: !IComponent,
|
||||
* capabilities: (
|
||||
* !Array<string|!ComponentManager.Capability<!IComponent>>
|
||||
* ),
|
||||
* weight: number
|
||||
* }}
|
||||
*/
|
||||
ComponentManager.ComponentDatum;
|
||||
|
||||
/**
|
||||
* A name with the capability of the element stored in the generic.
|
||||
* @template T
|
||||
* @alias Blockly.ComponentManager.Capability
|
||||
*/
|
||||
ComponentManager.Capability = class {
|
||||
/**
|
||||
* @param {string} name The name of the component capability.
|
||||
*/
|
||||
constructor(name) {
|
||||
/**
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.name_ = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the capability.
|
||||
* @return {string} The name.
|
||||
* @override
|
||||
*/
|
||||
toString() {
|
||||
return this.name_;
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {!ComponentManager.Capability<!IPositionable>} */
|
||||
ComponentManager.Capability.POSITIONABLE =
|
||||
new ComponentManager.Capability('positionable');
|
||||
|
||||
/** @type {!ComponentManager.Capability<!IDragTarget>} */
|
||||
ComponentManager.Capability.DRAG_TARGET =
|
||||
new ComponentManager.Capability('drag_target');
|
||||
|
||||
/** @type {!ComponentManager.Capability<!IDeleteArea>} */
|
||||
ComponentManager.Capability.DELETE_AREA =
|
||||
new ComponentManager.Capability('delete_area');
|
||||
|
||||
/** @type {!ComponentManager.Capability<!IAutoHideable>} */
|
||||
ComponentManager.Capability.AUTOHIDEABLE =
|
||||
new ComponentManager.Capability('autohideable');
|
||||
|
||||
exports.ComponentManager = ComponentManager;
|
||||
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Manager for all items registered with the workspace.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ComponentManager');
|
||||
|
||||
import type {IAutoHideable} from './interfaces/i_autohideable.js';
|
||||
import type {IComponent} from './interfaces/i_component.js';
|
||||
import type {IDeleteArea} from './interfaces/i_delete_area.js';
|
||||
import type {IDragTarget} from './interfaces/i_drag_target.js';
|
||||
import type {IPositionable} from './interfaces/i_positionable.js';
|
||||
import * as arrayUtils from './utils/array.js';
|
||||
|
||||
|
||||
class Capability<_T> {
|
||||
static POSITIONABLE = new Capability<IPositionable>('positionable');
|
||||
static DRAG_TARGET = new Capability<IDragTarget>('drag_target');
|
||||
static DELETE_AREA = new Capability<IDeleteArea>('delete_area');
|
||||
static AUTOHIDEABLE = new Capability<IAutoHideable>('autohideable');
|
||||
private readonly name_: string;
|
||||
/** @param name The name of the component capability. */
|
||||
constructor(name: string) {
|
||||
this.name_ = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the capability.
|
||||
*
|
||||
* @returns The name.
|
||||
*/
|
||||
toString(): string {
|
||||
return this.name_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manager for all items registered with the workspace.
|
||||
*
|
||||
* @alias Blockly.ComponentManager
|
||||
*/
|
||||
export class ComponentManager {
|
||||
static Capability = Capability;
|
||||
|
||||
/**
|
||||
* A map of the components registered with the workspace, mapped to id.
|
||||
*/
|
||||
private readonly componentData = new Map<string, ComponentDatum>();
|
||||
|
||||
/** A map of capabilities to component IDs. */
|
||||
private readonly capabilityToComponentIds = new Map<string, string[]>();
|
||||
|
||||
/**
|
||||
* Adds a component.
|
||||
*
|
||||
* @param componentInfo The data for the component to register.
|
||||
* @param opt_allowOverrides True to prevent an error when overriding an
|
||||
* already registered item.
|
||||
*/
|
||||
addComponent(componentInfo: ComponentDatum, opt_allowOverrides?: boolean) {
|
||||
// Don't throw an error if opt_allowOverrides is true.
|
||||
const id = componentInfo.component.id;
|
||||
if (!opt_allowOverrides && this.componentData.has(id)) {
|
||||
throw Error(
|
||||
'Plugin "' + id + '" with capabilities "' +
|
||||
this.componentData.get(id)?.capabilities + '" already added.');
|
||||
}
|
||||
this.componentData.set(id, componentInfo);
|
||||
const stringCapabilities = [];
|
||||
for (let i = 0; i < componentInfo.capabilities.length; i++) {
|
||||
const capability = String(componentInfo.capabilities[i]).toLowerCase();
|
||||
stringCapabilities.push(capability);
|
||||
if (!this.capabilityToComponentIds.has(capability)) {
|
||||
this.capabilityToComponentIds.set(capability, [id]);
|
||||
} else {
|
||||
this.capabilityToComponentIds.get(capability)?.push(id);
|
||||
}
|
||||
}
|
||||
this.componentData.get(id)!.capabilities = stringCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a component.
|
||||
*
|
||||
* @param id The ID of the component to remove.
|
||||
*/
|
||||
removeComponent(id: string) {
|
||||
const componentInfo = this.componentData.get(id);
|
||||
if (!componentInfo) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < componentInfo.capabilities.length; i++) {
|
||||
const capability = String(componentInfo.capabilities[i]).toLowerCase();
|
||||
arrayUtils.removeElem(this.capabilityToComponentIds.get(capability)!, id);
|
||||
}
|
||||
this.componentData.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a capability to a existing registered component.
|
||||
*
|
||||
* @param id The ID of the component to add the capability to.
|
||||
* @param capability The capability to add.
|
||||
*/
|
||||
addCapability<T>(id: string, capability: string|Capability<T>) {
|
||||
if (!this.getComponent(id)) {
|
||||
throw Error(
|
||||
'Cannot add capability, "' + capability + '". Plugin "' + id +
|
||||
'" has not been added to the ComponentManager');
|
||||
}
|
||||
if (this.hasCapability(id, capability)) {
|
||||
console.warn(
|
||||
'Plugin "' + id + 'already has capability "' + capability + '"');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
this.componentData.get(id)?.capabilities.push(capability);
|
||||
this.capabilityToComponentIds.get(capability)?.push(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a capability from an existing registered component.
|
||||
*
|
||||
* @param id The ID of the component to remove the capability from.
|
||||
* @param capability The capability to remove.
|
||||
*/
|
||||
removeCapability<T>(id: string, capability: string|Capability<T>) {
|
||||
if (!this.getComponent(id)) {
|
||||
throw Error(
|
||||
'Cannot remove capability, "' + capability + '". Plugin "' + id +
|
||||
'" has not been added to the ComponentManager');
|
||||
}
|
||||
if (!this.hasCapability(id, capability)) {
|
||||
console.warn(
|
||||
'Plugin "' + id + 'doesn\'t have capability "' + capability +
|
||||
'" to remove');
|
||||
return;
|
||||
}
|
||||
capability = String(capability).toLowerCase();
|
||||
arrayUtils.removeElem(this.componentData.get(id)!.capabilities, capability);
|
||||
arrayUtils.removeElem(this.capabilityToComponentIds.get(capability)!, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the component with this id has the specified capability.
|
||||
*
|
||||
* @param id The ID of the component to check.
|
||||
* @param capability The capability to check for.
|
||||
* @returns Whether the component has the capability.
|
||||
*/
|
||||
hasCapability<T>(id: string, capability: string|Capability<T>): boolean {
|
||||
capability = String(capability).toLowerCase();
|
||||
return this.componentData.has(id) &&
|
||||
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the component with the given ID.
|
||||
*
|
||||
* @param id The ID of the component to get.
|
||||
* @returns The component with the given name or undefined if not found.
|
||||
*/
|
||||
getComponent(id: string): IComponent|undefined {
|
||||
return this.componentData.get(id)?.component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the components with the specified capability.
|
||||
*
|
||||
* @param capability The capability of the component.
|
||||
* @param sorted Whether to return list ordered by weights.
|
||||
* @returns The components that match the specified capability.
|
||||
*/
|
||||
getComponents<T extends IComponent>(
|
||||
capability: string|Capability<T>, sorted: boolean): T[] {
|
||||
capability = String(capability).toLowerCase();
|
||||
const componentIds = this.capabilityToComponentIds.get(capability);
|
||||
if (!componentIds) {
|
||||
return [];
|
||||
}
|
||||
const components: T[] = [];
|
||||
if (sorted) {
|
||||
const componentDataList: ComponentDatum[] = [];
|
||||
componentIds.forEach((id) => {
|
||||
componentDataList.push(this.componentData.get(id)!);
|
||||
});
|
||||
componentDataList.sort(function(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
componentDataList.forEach(function(componentDatum) {
|
||||
components.push(componentDatum.component as T);
|
||||
});
|
||||
} else {
|
||||
componentIds.forEach((id) => {
|
||||
components.push(this.componentData.get(id)!.component as T);
|
||||
});
|
||||
}
|
||||
return components;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ComponentManager {
|
||||
/** An object storing component information. */
|
||||
export interface ComponentDatum {
|
||||
component: IComponent;
|
||||
capabilities: Array<string|Capability<IComponent>>;
|
||||
weight: number;
|
||||
}
|
||||
}
|
||||
|
||||
export type ComponentDatum = ComponentManager.ComponentDatum;
|
||||
@@ -4,50 +4,41 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
goog.module('Blockly.config');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.config');
|
||||
|
||||
|
||||
/**
|
||||
* All the values that we expect developers to be able to change
|
||||
* before injecting Blockly.
|
||||
* @typedef {{
|
||||
* dragRadius: number,
|
||||
* flyoutDragRadius: number,
|
||||
* snapRadius: number,
|
||||
* currentConnectionPreference: number,
|
||||
* bumpDelay: number,
|
||||
* connectingSnapRadius: number
|
||||
* }}
|
||||
*/
|
||||
let Config; // eslint-disable-line no-unused-vars
|
||||
interface Config {
|
||||
dragRadius: number;
|
||||
flyoutDragRadius: number;
|
||||
snapRadius: number;
|
||||
currentConnectionPreference: number;
|
||||
bumpDelay: number;
|
||||
connectingSnapRadius: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default snap radius.
|
||||
* @type {number}
|
||||
*/
|
||||
/** Default snap radius. */
|
||||
const DEFAULT_SNAP_RADIUS = 28;
|
||||
|
||||
/**
|
||||
* Object holding all the values on Blockly that we expect developers to be
|
||||
* able to change.
|
||||
* @type {Config}
|
||||
*/
|
||||
const config = {
|
||||
export const config: Config = {
|
||||
/**
|
||||
* Number of pixels the mouse must move before a drag starts.
|
||||
*
|
||||
* @alias Blockly.config.dragRadius
|
||||
*/
|
||||
dragRadius: 5,
|
||||
@@ -55,17 +46,20 @@ const config = {
|
||||
* Number of pixels the mouse must move before a drag/scroll starts from the
|
||||
* flyout. Because the drag-intention is determined when this is reached, it
|
||||
* is larger than 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,
|
||||
@@ -74,14 +68,14 @@ const config = {
|
||||
* to a new connection. The current previewed connection is considered to be
|
||||
* this much closer to the matching connection on the block than it actually
|
||||
* is.
|
||||
*
|
||||
* @alias Blockly.config.currentConnectionPreference
|
||||
*/
|
||||
currentConnectionPreference: 8,
|
||||
/**
|
||||
* Delay in ms between trigger and bumping unconnected block out of alignment.
|
||||
*
|
||||
* @alias Blockly.config.bumpDelay
|
||||
*/
|
||||
bumpDelay: 250,
|
||||
};
|
||||
|
||||
exports.config = config;
|
||||
@@ -4,114 +4,93 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Components for creating connections between blocks.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Components for creating connections between blocks.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Connection');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Connection');
|
||||
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const blocks = goog.require('Blockly.serialization.blocks');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockMove} = goog.requireType('Blockly.Events.BlockMove');
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IASTNodeLocationWithBlock} = goog.require('Blockly.IASTNodeLocationWithBlock');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IConnectionChecker} = goog.requireType('Blockly.IConnectionChecker');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Input} = goog.requireType('Blockly.Input');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockMove');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.constants');
|
||||
import type {Block} from './block.js';
|
||||
import {ConnectionType} from './connection_type.js';
|
||||
import type {BlockMove} from './events/events_block_move.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import type {Input} from './input.js';
|
||||
import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js';
|
||||
import type {IConnectionChecker} from './interfaces/i_connection_checker.js';
|
||||
import * as blocks from './serialization/blocks.js';
|
||||
import * as Xml from './xml.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a connection between blocks.
|
||||
* @implements {IASTNodeLocationWithBlock}
|
||||
*
|
||||
* @alias Blockly.Connection
|
||||
*/
|
||||
class Connection {
|
||||
export class Connection implements IASTNodeLocationWithBlock {
|
||||
/** Constants for checking whether two connections are compatible. */
|
||||
static CAN_CONNECT = 0;
|
||||
static REASON_SELF_CONNECTION = 1;
|
||||
static REASON_WRONG_TYPE = 2;
|
||||
static REASON_TARGET_NULL = 3;
|
||||
static REASON_CHECKS_FAILED = 4;
|
||||
static REASON_DIFFERENT_WORKSPACES = 5;
|
||||
static REASON_SHADOW_PARENT = 6;
|
||||
static REASON_DRAG_CHECKS_FAILED = 7;
|
||||
static REASON_PREVIOUS_AND_OUTPUT = 8;
|
||||
|
||||
protected sourceBlock_: Block;
|
||||
|
||||
/** Connection this connection connects to. Null if not connected. */
|
||||
targetConnection: Connection|null = null;
|
||||
|
||||
/**
|
||||
* @param {!Block} source The block establishing this connection.
|
||||
* @param {number} type The type of the connection.
|
||||
* Has this connection been disposed of?
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
constructor(source, type) {
|
||||
/**
|
||||
* @type {!Block}
|
||||
* @protected
|
||||
*/
|
||||
disposed = false;
|
||||
|
||||
/** List of compatible value types. Null if all types are compatible. */
|
||||
private check_: string[]|null = null;
|
||||
|
||||
/** DOM representation of a shadow block, or null if none. */
|
||||
private shadowDom_: Element|null = null;
|
||||
|
||||
/**
|
||||
* Horizontal location of this connection.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
x = 0;
|
||||
|
||||
/**
|
||||
* Vertical location of this connection.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
y = 0;
|
||||
|
||||
private shadowState_: blocks.State|null = null;
|
||||
|
||||
/**
|
||||
* @param source The block establishing this connection.
|
||||
* @param type The type of the connection.
|
||||
*/
|
||||
constructor(source: Block, public type: number) {
|
||||
this.sourceBlock_ = source;
|
||||
/** @type {number} */
|
||||
this.type = type;
|
||||
|
||||
/**
|
||||
* Connection this connection connects to. Null if not connected.
|
||||
* @type {Connection}
|
||||
*/
|
||||
this.targetConnection = null;
|
||||
|
||||
/**
|
||||
* Has this connection been disposed of?
|
||||
* @type {boolean}
|
||||
* @package
|
||||
*/
|
||||
this.disposed = false;
|
||||
|
||||
/**
|
||||
* List of compatible value types. Null if all types are compatible.
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.check_ = null;
|
||||
|
||||
/**
|
||||
* DOM representation of a shadow block, or null if none.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.shadowDom_ = null;
|
||||
|
||||
/**
|
||||
* Horizontal location of this connection.
|
||||
* @type {number}
|
||||
* @package
|
||||
*/
|
||||
this.x = 0;
|
||||
|
||||
/**
|
||||
* Vertical location of this connection.
|
||||
* @type {number}
|
||||
* @package
|
||||
*/
|
||||
this.y = 0;
|
||||
|
||||
/**
|
||||
* @type {?blocks.State}
|
||||
* @private
|
||||
*/
|
||||
this.shadowState_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect two connections together. This is the connection on the superior
|
||||
* block.
|
||||
* @param {!Connection} childConnection Connection on inferior block.
|
||||
* @protected
|
||||
*
|
||||
* @param childConnection Connection on inferior block.
|
||||
*/
|
||||
connect_(childConnection) {
|
||||
protected connect_(childConnection: Connection) {
|
||||
const INPUT = ConnectionType.INPUT_VALUE;
|
||||
const parentConnection = this;
|
||||
const parentBlock = parentConnection.getSourceBlock();
|
||||
const parentBlock = this.getSourceBlock();
|
||||
const childBlock = childConnection.getSourceBlock();
|
||||
|
||||
// Make sure the childConnection is available.
|
||||
@@ -121,25 +100,25 @@ class Connection {
|
||||
|
||||
// Make sure the parentConnection is available.
|
||||
let orphan;
|
||||
if (parentConnection.isConnected()) {
|
||||
const shadowState = parentConnection.stashShadowState_();
|
||||
const target = parentConnection.targetBlock();
|
||||
if (target.isShadow()) {
|
||||
target.dispose(false);
|
||||
if (this.isConnected()) {
|
||||
const shadowState = this.stashShadowState_();
|
||||
const target = this.targetBlock();
|
||||
if (target!.isShadow()) {
|
||||
target!.dispose(false);
|
||||
} else {
|
||||
parentConnection.disconnect();
|
||||
this.disconnect();
|
||||
orphan = target;
|
||||
}
|
||||
parentConnection.applyShadowState_(shadowState);
|
||||
this.applyShadowState_(shadowState);
|
||||
}
|
||||
|
||||
// Connect the new connection to the parent.
|
||||
let event;
|
||||
if (eventUtils.isEnabled()) {
|
||||
event = /** @type {!BlockMove} */
|
||||
(new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock));
|
||||
event =
|
||||
new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock) as BlockMove;
|
||||
}
|
||||
connectReciprocally(parentConnection, childConnection);
|
||||
connectReciprocally(this, childConnection);
|
||||
childBlock.setParent(parentBlock);
|
||||
if (event) {
|
||||
event.recordNew();
|
||||
@@ -148,22 +127,22 @@ class Connection {
|
||||
|
||||
// Deal with the orphan if it exists.
|
||||
if (orphan) {
|
||||
const orphanConnection = parentConnection.type === INPUT ?
|
||||
orphan.outputConnection :
|
||||
orphan.previousConnection;
|
||||
const orphanConnection = this.type === INPUT ? orphan.outputConnection :
|
||||
orphan.previousConnection;
|
||||
const connection = Connection.getConnectionForOrphanedConnection(
|
||||
childBlock, /** @type {!Connection} */ (orphanConnection));
|
||||
childBlock, (orphanConnection));
|
||||
if (connection) {
|
||||
orphanConnection.connect(connection);
|
||||
} else {
|
||||
orphanConnection.onFailedConnect(parentConnection);
|
||||
orphanConnection.onFailedConnect(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of this connection and deal with connected blocks.
|
||||
* @package
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
dispose() {
|
||||
// isConnected returns true for shadows and non-shadows.
|
||||
@@ -183,57 +162,61 @@ class Connection {
|
||||
|
||||
/**
|
||||
* Get the source block for this connection.
|
||||
* @return {!Block} The source block.
|
||||
*
|
||||
* @returns The source block.
|
||||
*/
|
||||
getSourceBlock() {
|
||||
getSourceBlock(): Block {
|
||||
return this.sourceBlock_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the connection belong to a superior block (higher in the source
|
||||
* stack)?
|
||||
* @return {boolean} True if connection faces down or right.
|
||||
*
|
||||
* @returns True if connection faces down or right.
|
||||
*/
|
||||
isSuperior() {
|
||||
isSuperior(): boolean {
|
||||
return this.type === ConnectionType.INPUT_VALUE ||
|
||||
this.type === ConnectionType.NEXT_STATEMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the connection connected?
|
||||
* @return {boolean} True if connection is connected to another connection.
|
||||
*
|
||||
* @returns True if connection is connected to another connection.
|
||||
*/
|
||||
isConnected() {
|
||||
isConnected(): boolean {
|
||||
return !!this.targetConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the workspace's connection type checker object.
|
||||
* @return {!IConnectionChecker} The connection type checker for the
|
||||
* source block's workspace.
|
||||
* @package
|
||||
*
|
||||
* @returns The connection type checker for the source block's workspace.
|
||||
* @internal
|
||||
*/
|
||||
getConnectionChecker() {
|
||||
getConnectionChecker(): IConnectionChecker {
|
||||
return this.sourceBlock_.workspace.connectionChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an attempted connection fails. NOP by default (i.e. for
|
||||
* headless workspaces).
|
||||
* @param {!Connection} _otherConnection Connection that this connection
|
||||
* failed to connect to.
|
||||
* @package
|
||||
*
|
||||
* @param _otherConnection Connection that this connection failed to connect
|
||||
* to.
|
||||
* @internal
|
||||
*/
|
||||
onFailedConnect(_otherConnection) {
|
||||
// NOP
|
||||
}
|
||||
onFailedConnect(_otherConnection: Connection) {}
|
||||
// NOP
|
||||
|
||||
/**
|
||||
* Connect this connection to another connection.
|
||||
* @param {!Connection} otherConnection Connection to connect to.
|
||||
* @return {boolean} Whether the the blocks are now connected or not.
|
||||
*
|
||||
* @param otherConnection Connection to connect to.
|
||||
* @returns Whether the the blocks are now connected or not.
|
||||
*/
|
||||
connect(otherConnection) {
|
||||
connect(otherConnection: Connection): boolean {
|
||||
if (this.targetConnection === otherConnection) {
|
||||
// Already connected together. NOP.
|
||||
return true;
|
||||
@@ -261,9 +244,7 @@ class Connection {
|
||||
return this.isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect this connection.
|
||||
*/
|
||||
/** Disconnect this connection. */
|
||||
disconnect() {
|
||||
const otherConnection = this.targetConnection;
|
||||
if (!otherConnection) {
|
||||
@@ -279,6 +260,7 @@ class Connection {
|
||||
// Superior block.
|
||||
parentBlock = this.sourceBlock_;
|
||||
childBlock = otherConnection.getSourceBlock();
|
||||
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
|
||||
parentConnection = this;
|
||||
} else {
|
||||
// Inferior block.
|
||||
@@ -303,18 +285,20 @@ class Connection {
|
||||
|
||||
/**
|
||||
* Disconnect two blocks that are connected by this connection.
|
||||
* @param {!Block} parentBlock The superior block.
|
||||
* @param {!Block} childBlock The inferior block.
|
||||
* @protected
|
||||
*
|
||||
* @param parentBlock The superior block.
|
||||
* @param childBlock The inferior block.
|
||||
*/
|
||||
disconnectInternal_(parentBlock, childBlock) {
|
||||
protected disconnectInternal_(parentBlock: Block, childBlock: Block) {
|
||||
let event;
|
||||
if (eventUtils.isEnabled()) {
|
||||
event = /** @type {!BlockMove} */
|
||||
(new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock));
|
||||
event =
|
||||
new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock) as BlockMove;
|
||||
}
|
||||
const otherConnection = this.targetConnection;
|
||||
otherConnection.targetConnection = null;
|
||||
if (otherConnection) {
|
||||
otherConnection.targetConnection = null;
|
||||
}
|
||||
this.targetConnection = null;
|
||||
childBlock.setParent(null);
|
||||
if (event) {
|
||||
@@ -325,49 +309,47 @@ class Connection {
|
||||
|
||||
/**
|
||||
* Respawn the shadow block if there was one connected to the this connection.
|
||||
* @protected
|
||||
*/
|
||||
respawnShadow_() {
|
||||
protected respawnShadow_() {
|
||||
// Have to keep respawnShadow_ for backwards compatibility.
|
||||
this.createShadowBlock_(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block that this connection connects to.
|
||||
* @return {?Block} The connected block or null if none is connected.
|
||||
*
|
||||
* @returns The connected block or null if none is connected.
|
||||
*/
|
||||
targetBlock() {
|
||||
targetBlock(): Block|null {
|
||||
if (this.isConnected()) {
|
||||
return this.targetConnection.getSourceBlock();
|
||||
return this.targetConnection?.getSourceBlock() ?? null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to be called when this connection's compatible types have changed.
|
||||
* @protected
|
||||
*/
|
||||
onCheckChanged_() {
|
||||
protected onCheckChanged_() {
|
||||
// The new value type may not be compatible with the existing connection.
|
||||
if (this.isConnected() &&
|
||||
(!this.targetConnection ||
|
||||
!this.getConnectionChecker().canConnect(
|
||||
this, this.targetConnection, false))) {
|
||||
const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
|
||||
child.unplug();
|
||||
child!.unplug();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a connection's compatibility.
|
||||
* @param {?(string|!Array<string>)} check Compatible value type or list of
|
||||
* value types. Null if all types are compatible.
|
||||
* @return {!Connection} The connection being modified
|
||||
* (to allow chaining).
|
||||
*
|
||||
* @param check Compatible value type or list of value types. Null if all
|
||||
* types are compatible.
|
||||
* @returns The connection being modified (to allow chaining).
|
||||
*/
|
||||
setCheck(check) {
|
||||
setCheck(check: string|string[]|null): Connection {
|
||||
if (check) {
|
||||
// Ensure that check is in an array.
|
||||
if (!Array.isArray(check)) {
|
||||
check = [check];
|
||||
}
|
||||
@@ -381,59 +363,60 @@ class Connection {
|
||||
|
||||
/**
|
||||
* Get a connection's compatibility.
|
||||
* @return {?Array} List of compatible value types.
|
||||
*
|
||||
* @returns List of compatible value types.
|
||||
* Null if all types are compatible.
|
||||
* @public
|
||||
*/
|
||||
getCheck() {
|
||||
getCheck(): string[]|null {
|
||||
return this.check_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the connection's shadow block.
|
||||
* @param {?Element} shadowDom DOM representation of a block or null.
|
||||
*
|
||||
* @param shadowDom DOM representation of a block or null.
|
||||
*/
|
||||
setShadowDom(shadowDom) {
|
||||
this.setShadowStateInternal_({shadowDom: shadowDom});
|
||||
setShadowDom(shadowDom: Element|null) {
|
||||
this.setShadowStateInternal_({shadowDom});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the xml representation of the connection's shadow block.
|
||||
* @param {boolean=} returnCurrent If true, and the shadow block is currently
|
||||
* attached to this connection, this serializes the state of that block
|
||||
* and returns it (so that field values are correct). Otherwise the saved
|
||||
* shadowDom is just returned.
|
||||
* @return {?Element} Shadow DOM representation of a block or null.
|
||||
*
|
||||
* @param returnCurrent If true, and the shadow block is currently attached to
|
||||
* this connection, this serializes the state of that block and returns it
|
||||
* (so that field values are correct). Otherwise the saved shadowDom is
|
||||
* just returned.
|
||||
* @returns Shadow DOM representation of a block or null.
|
||||
*/
|
||||
getShadowDom(returnCurrent) {
|
||||
return (returnCurrent && this.targetBlock().isShadow()) ?
|
||||
/** @type {!Element} */ (Xml.blockToDom(
|
||||
/** @type {!Block} */ (this.targetBlock()))) :
|
||||
getShadowDom(returnCurrent?: boolean): Element|null {
|
||||
return returnCurrent && this.targetBlock()!.isShadow() ?
|
||||
Xml.blockToDom((this.targetBlock() as Block)) as Element :
|
||||
this.shadowDom_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the connection's shadow block.
|
||||
* @param {?blocks.State} shadowState An state represetation of the block or
|
||||
* null.
|
||||
*
|
||||
* @param shadowState An state represetation of the block or null.
|
||||
*/
|
||||
setShadowState(shadowState) {
|
||||
this.setShadowStateInternal_({shadowState: shadowState});
|
||||
setShadowState(shadowState: blocks.State|null) {
|
||||
this.setShadowStateInternal_({shadowState});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the serialized object representation of the connection's shadow
|
||||
* block.
|
||||
* @param {boolean=} returnCurrent If true, and the shadow block is currently
|
||||
* attached to this connection, this serializes the state of that block
|
||||
* and returns it (so that field values are correct). Otherwise the saved
|
||||
* state is just returned.
|
||||
* @return {?blocks.State} Serialized object representation of the block, or
|
||||
* null.
|
||||
*
|
||||
* @param returnCurrent If true, and the shadow block is currently attached to
|
||||
* this connection, this serializes the state of that block and returns it
|
||||
* (so that field values are correct). Otherwise the saved state is just
|
||||
* returned.
|
||||
* @returns Serialized object representation of the block, or null.
|
||||
*/
|
||||
getShadowState(returnCurrent) {
|
||||
if (returnCurrent && this.targetBlock() && this.targetBlock().isShadow()) {
|
||||
return blocks.save(/** @type {!Block} */ (this.targetBlock()));
|
||||
getShadowState(returnCurrent?: boolean): blocks.State|null {
|
||||
if (returnCurrent && this.targetBlock() && this.targetBlock()!.isShadow()) {
|
||||
return blocks.save(this.targetBlock() as Block);
|
||||
}
|
||||
return this.shadowState_;
|
||||
}
|
||||
@@ -444,23 +427,25 @@ class Connection {
|
||||
*
|
||||
* Headless configurations (the default) do not have neighboring connection,
|
||||
* and always return an empty list (the default).
|
||||
* {@link Blockly.RenderedConnection} overrides this behavior with a list
|
||||
* {@link RenderedConnection#neighbours} overrides this behavior with a list
|
||||
* computed from the rendered positioning.
|
||||
* @param {number} _maxLimit The maximum radius to another connection.
|
||||
* @return {!Array<!Connection>} List of connections.
|
||||
* @package
|
||||
*
|
||||
* @param _maxLimit The maximum radius to another connection.
|
||||
* @returns List of connections.
|
||||
* @internal
|
||||
*/
|
||||
neighbours(_maxLimit) {
|
||||
neighbours(_maxLimit: number): Connection[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent input of a connection.
|
||||
* @return {?Input} The input that the connection belongs to or null if
|
||||
* no parent exists.
|
||||
* @package
|
||||
*
|
||||
* @returns The input that the connection belongs to or null if no parent
|
||||
* exists.
|
||||
* @internal
|
||||
*/
|
||||
getParentInput() {
|
||||
getParentInput(): Input|null {
|
||||
let parentInput = null;
|
||||
const inputs = this.sourceBlock_.inputList;
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
@@ -475,9 +460,10 @@ class Connection {
|
||||
/**
|
||||
* This method returns a string describing this Connection in developer terms
|
||||
* (English only). Intended to on be used in console logs and errors.
|
||||
* @return {string} The description.
|
||||
*
|
||||
* @returns The description.
|
||||
*/
|
||||
toString() {
|
||||
toString(): string {
|
||||
const block = this.sourceBlock_;
|
||||
if (!block) {
|
||||
return 'Orphan Connection';
|
||||
@@ -491,7 +477,7 @@ class Connection {
|
||||
msg = 'Next Connection of ';
|
||||
} else {
|
||||
let parentInput = null;
|
||||
for (let i = 0, input; (input = block.inputList[i]); i++) {
|
||||
for (let i = 0, input; input = block.inputList[i]; i++) {
|
||||
if (input.connection === this) {
|
||||
parentInput = input;
|
||||
break;
|
||||
@@ -510,11 +496,11 @@ class Connection {
|
||||
/**
|
||||
* Returns the state of the shadowDom_ and shadowState_ properties, then
|
||||
* temporarily sets those properties to null so no shadow respawns.
|
||||
* @return {{shadowDom: ?Element, shadowState: ?blocks.State}} The state of
|
||||
* both the shadowDom_ and shadowState_ properties.
|
||||
* @private
|
||||
*
|
||||
* @returns The state of both the shadowDom_ and shadowState_ properties.
|
||||
*/
|
||||
stashShadowState_() {
|
||||
private stashShadowState_():
|
||||
{shadowDom: Element|null, shadowState: blocks.State|null} {
|
||||
const shadowDom = this.getShadowDom(true);
|
||||
const shadowState = this.getShadowState(true);
|
||||
// Set to null so it doesn't respawn.
|
||||
@@ -525,23 +511,27 @@ class Connection {
|
||||
|
||||
/**
|
||||
* Reapplies the stashed state of the shadowDom_ and shadowState_ properties.
|
||||
* @param {{shadowDom: ?Element, shadowState: ?blocks.State}} param0 The state
|
||||
* to reapply to the shadowDom_ and shadowState_ properties.
|
||||
* @private
|
||||
*
|
||||
* @param param0 The state to reapply to the shadowDom_ and shadowState_
|
||||
* properties.
|
||||
*/
|
||||
applyShadowState_({shadowDom, shadowState}) {
|
||||
private applyShadowState_({shadowDom, shadowState}: {
|
||||
shadowDom: Element|null,
|
||||
shadowState: blocks.State|null
|
||||
}) {
|
||||
this.shadowDom_ = shadowDom;
|
||||
this.shadowState_ = shadowState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state of the shadow of this connection.
|
||||
* @param {{shadowDom: (?Element|undefined), shadowState:
|
||||
* (?blocks.State|undefined)}=} param0 The state to set the shadow of this
|
||||
* connection to.
|
||||
* @private
|
||||
*
|
||||
* @param param0 The state to set the shadow of this connection to.
|
||||
*/
|
||||
setShadowStateInternal_({shadowDom = null, shadowState = null} = {}) {
|
||||
private setShadowStateInternal_({shadowDom = null, shadowState = null}: {
|
||||
shadowDom?: Element|null,
|
||||
shadowState?: blocks.State|null
|
||||
} = {}) {
|
||||
// One or both of these should always be null.
|
||||
// If neither is null, the shadowState will get priority.
|
||||
this.shadowDom_ = shadowDom;
|
||||
@@ -550,13 +540,13 @@ class Connection {
|
||||
const target = this.targetBlock();
|
||||
if (!target) {
|
||||
this.respawnShadow_();
|
||||
if (this.targetBlock() && this.targetBlock().isShadow()) {
|
||||
if (this.targetBlock() && this.targetBlock()!.isShadow()) {
|
||||
this.serializeShadow_(this.targetBlock());
|
||||
}
|
||||
} else if (target.isShadow()) {
|
||||
target.dispose(false);
|
||||
this.respawnShadow_();
|
||||
if (this.targetBlock() && this.targetBlock().isShadow()) {
|
||||
if (this.targetBlock() && this.targetBlock()!.isShadow()) {
|
||||
this.serializeShadow_(this.targetBlock());
|
||||
}
|
||||
} else {
|
||||
@@ -571,17 +561,17 @@ class Connection {
|
||||
/**
|
||||
* Creates a shadow block based on the current shadowState_ or shadowDom_.
|
||||
* shadowState_ gets priority.
|
||||
* @param {boolean} attemptToConnect Whether to try to connect the shadow
|
||||
* block to this connection or not.
|
||||
* @return {?Block} The shadow block that was created, or null if both the
|
||||
*
|
||||
* @param attemptToConnect Whether to try to connect the shadow block to this
|
||||
* connection or not.
|
||||
* @returns The shadow block that was created, or null if both the
|
||||
* shadowState_ and shadowDom_ are null.
|
||||
* @private
|
||||
*/
|
||||
createShadowBlock_(attemptToConnect) {
|
||||
private createShadowBlock_(attemptToConnect: boolean): Block|null {
|
||||
const parentBlock = this.getSourceBlock();
|
||||
const shadowState = this.getShadowState();
|
||||
const shadowDom = this.getShadowDom();
|
||||
if (!parentBlock.workspace || (!shadowState && !shadowDom)) {
|
||||
if (parentBlock.isDeadOrDying() || !shadowState && !shadowDom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -625,14 +615,14 @@ class Connection {
|
||||
/**
|
||||
* Saves the given shadow block to both the shadowDom_ and shadowState_
|
||||
* properties, in their respective serialized forms.
|
||||
* @param {?Block} shadow The shadow to serialize, or null.
|
||||
* @private
|
||||
*
|
||||
* @param shadow The shadow to serialize, or null.
|
||||
*/
|
||||
serializeShadow_(shadow) {
|
||||
private serializeShadow_(shadow: Block|null) {
|
||||
if (!shadow) {
|
||||
return;
|
||||
}
|
||||
this.shadowDom_ = /** @type {!Element} */ (Xml.blockToDom(shadow));
|
||||
this.shadowDom_ = Xml.blockToDom(shadow) as Element;
|
||||
this.shadowState_ = blocks.save(shadow);
|
||||
}
|
||||
|
||||
@@ -640,13 +630,13 @@ class Connection {
|
||||
* Returns the connection (starting at the startBlock) which will accept
|
||||
* the given connection. This includes compatible connection types and
|
||||
* connection checks.
|
||||
* @param {!Block} startBlock The block on which to start the search.
|
||||
* @param {!Connection} orphanConnection The connection that is looking
|
||||
* for a home.
|
||||
* @return {?Connection} The suitable connection point on the chain of
|
||||
* blocks, or null.
|
||||
*
|
||||
* @param startBlock The block on which to start the search.
|
||||
* @param orphanConnection The connection that is looking for a home.
|
||||
* @returns The suitable connection point on the chain of blocks, or null.
|
||||
*/
|
||||
static getConnectionForOrphanedConnection(startBlock, orphanConnection) {
|
||||
static getConnectionForOrphanedConnection(
|
||||
startBlock: Block, orphanConnection: Connection): Connection|null {
|
||||
if (orphanConnection.type === ConnectionType.OUTPUT_VALUE) {
|
||||
return getConnectionForOrphanedOutput(
|
||||
startBlock, orphanConnection.getSourceBlock());
|
||||
@@ -661,48 +651,36 @@ class Connection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants for checking whether two connections are compatible.
|
||||
*/
|
||||
Connection.CAN_CONNECT = 0;
|
||||
Connection.REASON_SELF_CONNECTION = 1;
|
||||
Connection.REASON_WRONG_TYPE = 2;
|
||||
Connection.REASON_TARGET_NULL = 3;
|
||||
Connection.REASON_CHECKS_FAILED = 4;
|
||||
Connection.REASON_DIFFERENT_WORKSPACES = 5;
|
||||
Connection.REASON_SHADOW_PARENT = 6;
|
||||
Connection.REASON_DRAG_CHECKS_FAILED = 7;
|
||||
Connection.REASON_PREVIOUS_AND_OUTPUT = 8;
|
||||
|
||||
/**
|
||||
* Update two connections to target each other.
|
||||
* @param {Connection} first The first connection to update.
|
||||
* @param {Connection} second The second connection to update.
|
||||
*
|
||||
* @param first The first connection to update.
|
||||
* @param second The second connection to update.
|
||||
*/
|
||||
const connectReciprocally = function(first, second) {
|
||||
function connectReciprocally(first: Connection, second: Connection) {
|
||||
if (!first || !second) {
|
||||
throw Error('Cannot connect null connections.');
|
||||
}
|
||||
first.targetConnection = second;
|
||||
second.targetConnection = first;
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* Returns the single connection on the block that will accept the orphaned
|
||||
* block, if one can be found. If the block has multiple compatible connections
|
||||
* (even if they are filled) this returns null. If the block has no compatible
|
||||
* connections, this returns null.
|
||||
* @param {!Block} block The superior block.
|
||||
* @param {!Block} orphanBlock The inferior block.
|
||||
* @return {?Connection} The suitable connection point on 'block',
|
||||
* or null.
|
||||
*
|
||||
* @param block The superior block.
|
||||
* @param orphanBlock The inferior block.
|
||||
* @returns The suitable connection point on 'block', or null.
|
||||
*/
|
||||
const getSingleConnection = function(block, orphanBlock) {
|
||||
function getSingleConnection(block: Block, orphanBlock: Block): Connection|
|
||||
null {
|
||||
let foundConnection = null;
|
||||
const output = orphanBlock.outputConnection;
|
||||
const typeChecker = output.getConnectionChecker();
|
||||
|
||||
for (let i = 0, input; (input = block.inputList[i]); i++) {
|
||||
for (let i = 0, input; input = block.inputList[i]; i++) {
|
||||
const connection = input.connection;
|
||||
if (connection && typeChecker.canConnect(output, connection, false)) {
|
||||
if (foundConnection) {
|
||||
@@ -712,7 +690,7 @@ const getSingleConnection = function(block, orphanBlock) {
|
||||
}
|
||||
}
|
||||
return foundConnection;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Walks down a row a blocks, at each stage checking if there are any
|
||||
@@ -720,23 +698,20 @@ const getSingleConnection = function(block, orphanBlock) {
|
||||
* are zero or multiple eligible connections, returns null. Otherwise
|
||||
* returns the only input on the last block in the chain.
|
||||
* Terminates early for shadow blocks.
|
||||
* @param {!Block} startBlock The block on which to start the search.
|
||||
* @param {!Block} orphanBlock The block that is looking for a home.
|
||||
* @return {?Connection} The suitable connection point on the chain
|
||||
* of blocks, or null.
|
||||
*
|
||||
* @param startBlock The block on which to start the search.
|
||||
* @param orphanBlock The block that is looking for a home.
|
||||
* @returns The suitable connection point on the chain of blocks, or null.
|
||||
*/
|
||||
const getConnectionForOrphanedOutput = function(startBlock, orphanBlock) {
|
||||
let newBlock = startBlock;
|
||||
function getConnectionForOrphanedOutput(
|
||||
startBlock: Block, orphanBlock: Block): Connection|null {
|
||||
let newBlock: Block|null = startBlock;
|
||||
let connection;
|
||||
while (
|
||||
(connection = getSingleConnection(
|
||||
/** @type {!Block} */ (newBlock), orphanBlock))) {
|
||||
while (connection = getSingleConnection(newBlock, orphanBlock)) {
|
||||
newBlock = connection.targetBlock();
|
||||
if (!newBlock || newBlock.isShadow()) {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Connection = Connection;
|
||||
}
|
||||
@@ -4,49 +4,44 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview An object that encapsulates logic for checking whether a
|
||||
* potential connection is safe and valid.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* An object that encapsulates logic for checking whether a
|
||||
* potential connection is safe and valid.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.ConnectionChecker');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ConnectionChecker');
|
||||
|
||||
const common = goog.require('Blockly.common');
|
||||
const internalConstants = goog.require('Blockly.internalConstants');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {Connection} = goog.require('Blockly.Connection');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IConnectionChecker} = goog.require('Blockly.IConnectionChecker');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection');
|
||||
import * as common from './common.js';
|
||||
import {Connection} from './connection.js';
|
||||
import {ConnectionType} from './connection_type.js';
|
||||
import type {IConnectionChecker} from './interfaces/i_connection_checker.js';
|
||||
import * as internalConstants from './internal_constants.js';
|
||||
import * as registry from './registry.js';
|
||||
import type {RenderedConnection} from './rendered_connection.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for connection type checking logic.
|
||||
* @implements {IConnectionChecker}
|
||||
*
|
||||
* @alias Blockly.ConnectionChecker
|
||||
*/
|
||||
class ConnectionChecker {
|
||||
export class ConnectionChecker implements IConnectionChecker {
|
||||
/**
|
||||
* Check whether the current connection can connect with the target
|
||||
* connection.
|
||||
* @param {Connection} a Connection to check compatibility with.
|
||||
* @param {Connection} b Connection to check compatibility with.
|
||||
* @param {boolean} isDragging True if the connection is being made by
|
||||
* dragging a block.
|
||||
* @param {number=} opt_distance The max allowable distance between the
|
||||
* connections for drag checks.
|
||||
* @return {boolean} Whether the connection is legal.
|
||||
* @public
|
||||
*
|
||||
* @param a Connection to check compatibility with.
|
||||
* @param b Connection to check compatibility with.
|
||||
* @param isDragging True if the connection is being made by dragging a block.
|
||||
* @param opt_distance The max allowable distance between the connections for
|
||||
* drag checks.
|
||||
* @returns Whether the connection is legal.
|
||||
*/
|
||||
canConnect(a, b, isDragging, opt_distance) {
|
||||
canConnect(
|
||||
a: Connection|null, b: Connection|null, isDragging: boolean,
|
||||
opt_distance?: number): boolean {
|
||||
return this.canConnectWithReason(a, b, isDragging, opt_distance) ===
|
||||
Connection.CAN_CONNECT;
|
||||
}
|
||||
@@ -54,33 +49,34 @@ class ConnectionChecker {
|
||||
/**
|
||||
* Checks whether the current connection can connect with the target
|
||||
* connection, and return an error code if there are problems.
|
||||
* @param {Connection} a Connection to check compatibility with.
|
||||
* @param {Connection} b Connection to check compatibility with.
|
||||
* @param {boolean} isDragging True if the connection is being made by
|
||||
* dragging a block.
|
||||
* @param {number=} opt_distance The max allowable distance between the
|
||||
* connections for drag checks.
|
||||
* @return {number} Connection.CAN_CONNECT if the connection is legal,
|
||||
* an error code otherwise.
|
||||
* @public
|
||||
*
|
||||
* @param a Connection to check compatibility with.
|
||||
* @param b Connection to check compatibility with.
|
||||
* @param isDragging True if the connection is being made by dragging a block.
|
||||
* @param opt_distance The max allowable distance between the connections for
|
||||
* drag checks.
|
||||
* @returns Connection.CAN_CONNECT if the connection is legal, an error code
|
||||
* otherwise.
|
||||
*/
|
||||
canConnectWithReason(a, b, isDragging, opt_distance) {
|
||||
canConnectWithReason(
|
||||
a: Connection|null, b: Connection|null, isDragging: boolean,
|
||||
opt_distance?: number): number {
|
||||
const safety = this.doSafetyChecks(a, b);
|
||||
if (safety !== Connection.CAN_CONNECT) {
|
||||
return safety;
|
||||
}
|
||||
|
||||
// If the safety checks passed, both connections are non-null.
|
||||
const connOne = /** @type {!Connection} **/ (a);
|
||||
const connTwo = /** @type {!Connection} **/ (b);
|
||||
const connOne = a!;
|
||||
const connTwo = b!;
|
||||
if (!this.doTypeChecks(connOne, connTwo)) {
|
||||
return Connection.REASON_CHECKS_FAILED;
|
||||
}
|
||||
|
||||
if (isDragging &&
|
||||
!this.doDragChecks(
|
||||
/** @type {!RenderedConnection} **/ (a),
|
||||
/** @type {!RenderedConnection} **/ (b), opt_distance || 0)) {
|
||||
a as RenderedConnection, b as RenderedConnection,
|
||||
opt_distance || 0)) {
|
||||
return Connection.REASON_DRAG_CHECKS_FAILED;
|
||||
}
|
||||
|
||||
@@ -89,14 +85,14 @@ class ConnectionChecker {
|
||||
|
||||
/**
|
||||
* Helper method that translates a connection error code into a string.
|
||||
* @param {number} errorCode The error code.
|
||||
* @param {Connection} a One of the two connections being checked.
|
||||
* @param {Connection} b The second of the two connections being
|
||||
* checked.
|
||||
* @return {string} A developer-readable error string.
|
||||
* @public
|
||||
*
|
||||
* @param errorCode The error code.
|
||||
* @param a One of the two connections being checked.
|
||||
* @param b The second of the two connections being checked.
|
||||
* @returns A developer-readable error string.
|
||||
*/
|
||||
getErrorMessage(errorCode, a, b) {
|
||||
getErrorMessage(errorCode: number, a: Connection|null, b: Connection|null):
|
||||
string {
|
||||
switch (errorCode) {
|
||||
case Connection.REASON_SELF_CONNECTION:
|
||||
return 'Attempted to connect a block to itself.';
|
||||
@@ -108,8 +104,8 @@ class ConnectionChecker {
|
||||
case Connection.REASON_TARGET_NULL:
|
||||
return 'Target connection is null.';
|
||||
case Connection.REASON_CHECKS_FAILED: {
|
||||
const connOne = /** @type {!Connection} **/ (a);
|
||||
const connTwo = /** @type {!Connection} **/ (b);
|
||||
const connOne = a!;
|
||||
const connTwo = b!;
|
||||
let msg = 'Connection checks failed. ';
|
||||
msg += connOne + ' expected ' + connOne.getCheck() + ', found ' +
|
||||
connTwo.getCheck();
|
||||
@@ -129,12 +125,12 @@ class ConnectionChecker {
|
||||
/**
|
||||
* Check that connecting the given connections is safe, meaning that it would
|
||||
* not break any of Blockly's basic assumptions (e.g. no self connections).
|
||||
* @param {Connection} a The first of the connections to check.
|
||||
* @param {Connection} b The second of the connections to check.
|
||||
* @return {number} An enum with the reason this connection is safe or unsafe.
|
||||
* @public
|
||||
*
|
||||
* @param a The first of the connections to check.
|
||||
* @param b The second of the connections to check.
|
||||
* @returns An enum with the reason this connection is safe or unsafe.
|
||||
*/
|
||||
doSafetyChecks(a, b) {
|
||||
doSafetyChecks(a: Connection|null, b: Connection|null): number {
|
||||
if (!a || !b) {
|
||||
return Connection.REASON_TARGET_NULL;
|
||||
}
|
||||
@@ -181,12 +177,12 @@ class ConnectionChecker {
|
||||
* Check whether this connection is compatible with another connection with
|
||||
* respect to the value type system. E.g. square_root("Hello") is not
|
||||
* compatible.
|
||||
* @param {!Connection} a Connection to compare.
|
||||
* @param {!Connection} b Connection to compare against.
|
||||
* @return {boolean} True if the connections share a type.
|
||||
* @public
|
||||
*
|
||||
* @param a Connection to compare.
|
||||
* @param b Connection to compare against.
|
||||
* @returns True if the connections share a type.
|
||||
*/
|
||||
doTypeChecks(a, b) {
|
||||
doTypeChecks(a: Connection, b: Connection): boolean {
|
||||
const checkArrayOne = a.getCheck();
|
||||
const checkArrayTwo = b.getCheck();
|
||||
|
||||
@@ -206,14 +202,14 @@ class ConnectionChecker {
|
||||
|
||||
/**
|
||||
* Check whether this connection can be made by dragging.
|
||||
* @param {!RenderedConnection} a Connection to compare.
|
||||
* @param {!RenderedConnection} b Connection to compare against.
|
||||
* @param {number} distance The maximum allowable distance between
|
||||
* connections.
|
||||
* @return {boolean} True if the connection is allowed during a drag.
|
||||
* @public
|
||||
*
|
||||
* @param a Connection to compare.
|
||||
* @param b Connection to compare against.
|
||||
* @param distance The maximum allowable distance between connections.
|
||||
* @returns True if the connection is allowed during a drag.
|
||||
*/
|
||||
doDragChecks(a, b, distance) {
|
||||
doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number):
|
||||
boolean {
|
||||
if (a.distanceFrom(b) > distance) {
|
||||
return false;
|
||||
}
|
||||
@@ -229,7 +225,7 @@ class ConnectionChecker {
|
||||
case ConnectionType.OUTPUT_VALUE: {
|
||||
// Don't offer to connect an already connected left (male) value plug to
|
||||
// an available right (female) value plug.
|
||||
if ((b.isConnected() && !b.targetBlock().isInsertionMarker()) ||
|
||||
if (b.isConnected() && !b.targetBlock()!.isInsertionMarker() ||
|
||||
a.isConnected()) {
|
||||
return false;
|
||||
}
|
||||
@@ -239,8 +235,8 @@ class ConnectionChecker {
|
||||
// Offering to connect the left (male) of a value block to an already
|
||||
// connected value pair is ok, we'll splice it in.
|
||||
// However, don't offer to splice into an immovable block.
|
||||
if (b.isConnected() && !b.targetBlock().isMovable() &&
|
||||
!b.targetBlock().isShadow()) {
|
||||
if (b.isConnected() && !b.targetBlock()!.isMovable() &&
|
||||
!b.targetBlock()!.isShadow()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -251,7 +247,7 @@ class ConnectionChecker {
|
||||
// is fine. Similarly, replacing a terminal statement with another
|
||||
// terminal statement is allowed.
|
||||
if (b.isConnected() && !a.getSourceBlock().nextConnection &&
|
||||
!b.targetBlock().isShadow() && b.targetBlock().nextConnection) {
|
||||
!b.targetBlock()!.isShadow() && b.targetBlock()!.nextConnection) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
@@ -271,14 +267,13 @@ class ConnectionChecker {
|
||||
|
||||
/**
|
||||
* Helper function for drag checking.
|
||||
* @param {!Connection} a The connection to check, which must be a
|
||||
* statement input or next connection.
|
||||
* @param {!Connection} b A nearby connection to check, which
|
||||
* must be a previous connection.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
* @protected
|
||||
*
|
||||
* @param a The connection to check, which must be a statement input or next
|
||||
* connection.
|
||||
* @param b A nearby connection to check, which must be a previous connection.
|
||||
* @returns True if the connection is allowed, false otherwise.
|
||||
*/
|
||||
canConnectToPrevious_(a, b) {
|
||||
protected canConnectToPrevious_(a: Connection, b: Connection): boolean {
|
||||
if (a.targetConnection) {
|
||||
// This connection is already occupied.
|
||||
// A next connection will never disconnect itself mid-drag.
|
||||
@@ -296,17 +291,15 @@ class ConnectionChecker {
|
||||
|
||||
const targetBlock = b.targetBlock();
|
||||
// If it is connected to a real block, game over.
|
||||
if (!targetBlock.isInsertionMarker()) {
|
||||
if (!targetBlock!.isInsertionMarker()) {
|
||||
return false;
|
||||
}
|
||||
// If it's connected to an insertion marker but that insertion marker
|
||||
// is the first block in a stack, it's still fine. If that insertion
|
||||
// marker is in the middle of a stack, it won't work.
|
||||
return !targetBlock.getPreviousBlock();
|
||||
return !targetBlock!.getPreviousBlock();
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.CONNECTION_CHECKER, registry.DEFAULT, ConnectionChecker);
|
||||
|
||||
exports.ConnectionChecker = ConnectionChecker;
|
||||
@@ -4,68 +4,47 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A database of all the rendered connections that could
|
||||
* possibly be connected to (i.e. not collapsed, etc).
|
||||
* Sorted by y coordinate.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A database of all the rendered connections that could
|
||||
* possibly be connected to (i.e. not collapsed, etc).
|
||||
* Sorted by y coordinate.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.ConnectionDB');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ConnectionDB');
|
||||
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Coordinate} = goog.requireType('Blockly.utils.Coordinate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IConnectionChecker} = goog.requireType('Blockly.IConnectionChecker');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.constants');
|
||||
import {ConnectionType} from './connection_type.js';
|
||||
import type {IConnectionChecker} from './interfaces/i_connection_checker.js';
|
||||
import type {RenderedConnection} from './rendered_connection.js';
|
||||
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
|
||||
*/
|
||||
class ConnectionDB {
|
||||
export class ConnectionDB {
|
||||
/** Array of connections sorted by y position in workspace units. */
|
||||
private readonly connections_: RenderedConnection[] = [];
|
||||
|
||||
/**
|
||||
* @param {!IConnectionChecker} checker The workspace's
|
||||
* connection type checker, used to decide if connections are valid during
|
||||
* a drag.
|
||||
* @param connectionChecker The workspace's connection type checker, used to
|
||||
* decide if connections are valid during a drag.
|
||||
*/
|
||||
constructor(checker) {
|
||||
/**
|
||||
* Array of connections sorted by y position in workspace units.
|
||||
* @type {!Array<!RenderedConnection>}
|
||||
* @private
|
||||
*/
|
||||
this.connections_ = [];
|
||||
/**
|
||||
* The workspace's connection type checker, used to decide if connections
|
||||
* are valid during a drag.
|
||||
* @type {!IConnectionChecker}
|
||||
* @private
|
||||
*/
|
||||
this.connectionChecker_ = checker;
|
||||
}
|
||||
constructor(private readonly connectionChecker: IConnectionChecker) {}
|
||||
|
||||
/**
|
||||
* Add a connection to the database. Should not already exist in the database.
|
||||
* @param {!RenderedConnection} connection The connection to be added.
|
||||
* @param {number} yPos The y position used to decide where to insert the
|
||||
* connection.
|
||||
* @package
|
||||
*
|
||||
* @param connection The connection to be added.
|
||||
* @param yPos The y position used to decide where to insert the connection.
|
||||
* @internal
|
||||
*/
|
||||
addConnection(connection, yPos) {
|
||||
addConnection(connection: RenderedConnection, yPos: number) {
|
||||
const index = this.calculateIndexForYPos_(yPos);
|
||||
this.connections_.splice(index, 0, connection);
|
||||
}
|
||||
@@ -75,14 +54,14 @@ class ConnectionDB {
|
||||
*
|
||||
* Starts by doing a binary search to find the approximate location, then
|
||||
* linearly searches nearby for the exact connection.
|
||||
* @param {!RenderedConnection} conn The connection to find.
|
||||
* @param {number} yPos The y position used to find the index of the
|
||||
* connection.
|
||||
* @return {number} The index of the connection, or -1 if the connection was
|
||||
* not found.
|
||||
* @private
|
||||
*
|
||||
* @param conn The connection to find.
|
||||
* @param yPos The y position used to find the index of the connection.
|
||||
* @returns The index of the connection, or -1 if the connection was not
|
||||
* found.
|
||||
*/
|
||||
findIndexOfConnection_(conn, yPos) {
|
||||
private findIndexOfConnection_(conn: RenderedConnection, yPos: number):
|
||||
number {
|
||||
if (!this.connections_.length) {
|
||||
return -1;
|
||||
}
|
||||
@@ -116,12 +95,11 @@ class ConnectionDB {
|
||||
|
||||
/**
|
||||
* Finds the correct index for the given y position.
|
||||
* @param {number} yPos The y position used to decide where to
|
||||
* insert the connection.
|
||||
* @return {number} The candidate index.
|
||||
* @private
|
||||
*
|
||||
* @param yPos The y position used to decide where to insert the connection.
|
||||
* @returns The candidate index.
|
||||
*/
|
||||
calculateIndexForYPos_(yPos) {
|
||||
private calculateIndexForYPos_(yPos: number): number {
|
||||
if (!this.connections_.length) {
|
||||
return 0;
|
||||
}
|
||||
@@ -143,12 +121,12 @@ class ConnectionDB {
|
||||
|
||||
/**
|
||||
* Remove a connection from the database. Must already exist in DB.
|
||||
* @param {!RenderedConnection} connection The connection to be removed.
|
||||
* @param {number} yPos The y position used to find the index of the
|
||||
* connection.
|
||||
*
|
||||
* @param connection The connection to be removed.
|
||||
* @param yPos The y position used to find the index of the connection.
|
||||
* @throws {Error} If the connection cannot be found in the database.
|
||||
*/
|
||||
removeConnection(connection, yPos) {
|
||||
removeConnection(connection: RenderedConnection, yPos: number) {
|
||||
const index = this.findIndexOfConnection_(connection, yPos);
|
||||
if (index === -1) {
|
||||
throw Error('Unable to find connection in connectionDB.');
|
||||
@@ -159,12 +137,13 @@ class ConnectionDB {
|
||||
/**
|
||||
* Find all nearby connections to the given connection.
|
||||
* Type checking does not apply, since this function is used for bumping.
|
||||
* @param {!RenderedConnection} connection The connection whose
|
||||
* neighbours should be returned.
|
||||
* @param {number} maxRadius The maximum radius to another connection.
|
||||
* @return {!Array<!RenderedConnection>} List of connections.
|
||||
*
|
||||
* @param connection The connection whose neighbours should be returned.
|
||||
* @param maxRadius The maximum radius to another connection.
|
||||
* @returns List of connections.
|
||||
*/
|
||||
getNeighbours(connection, maxRadius) {
|
||||
getNeighbours(connection: RenderedConnection, maxRadius: number):
|
||||
RenderedConnection[] {
|
||||
const db = this.connections_;
|
||||
const currentX = connection.x;
|
||||
const currentY = connection.y;
|
||||
@@ -182,16 +161,17 @@ class ConnectionDB {
|
||||
pointerMid = Math.floor((pointerMin + pointerMax) / 2);
|
||||
}
|
||||
|
||||
const neighbours = [];
|
||||
const neighbours: RenderedConnection[] = [];
|
||||
/**
|
||||
* Computes if the current connection is within the allowed radius of
|
||||
* another connection. This function is a closure and has access to outside
|
||||
* variables.
|
||||
* @param {number} yIndex The other connection's index in the database.
|
||||
* @return {boolean} True if the current connection's vertical distance from
|
||||
* the other connection is less than the allowed radius.
|
||||
*
|
||||
* @param yIndex The other connection's index in the database.
|
||||
* @returns True if the current connection's vertical distance from the
|
||||
* other connection is less than the allowed radius.
|
||||
*/
|
||||
function checkConnection_(yIndex) {
|
||||
function checkConnection_(yIndex: number): boolean {
|
||||
const dx = currentX - db[yIndex].x;
|
||||
const dy = currentY - db[yIndex].y;
|
||||
const r = Math.sqrt(dx * dx + dy * dy);
|
||||
@@ -219,29 +199,30 @@ class ConnectionDB {
|
||||
/**
|
||||
* Is the candidate connection close to the reference connection.
|
||||
* Extremely fast; only looks at Y distance.
|
||||
* @param {number} index Index in database of candidate connection.
|
||||
* @param {number} baseY Reference connection's Y value.
|
||||
* @param {number} maxRadius The maximum radius to another connection.
|
||||
* @return {boolean} True if connection is in range.
|
||||
* @private
|
||||
*
|
||||
* @param index Index in database of candidate connection.
|
||||
* @param baseY Reference connection's Y value.
|
||||
* @param maxRadius The maximum radius to another connection.
|
||||
* @returns True if connection is in range.
|
||||
*/
|
||||
isInYRange_(index, baseY, maxRadius) {
|
||||
return (Math.abs(this.connections_[index].y - baseY) <= maxRadius);
|
||||
private isInYRange_(index: number, baseY: number, maxRadius: number):
|
||||
boolean {
|
||||
return Math.abs(this.connections_[index].y - baseY) <= maxRadius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the closest compatible connection to this connection.
|
||||
* @param {!RenderedConnection} conn The connection searching for a compatible
|
||||
* mate.
|
||||
* @param {number} maxRadius The maximum radius to another connection.
|
||||
* @param {!Coordinate} dxy Offset between this connection's
|
||||
* location in the database and the current location (as a result of
|
||||
* dragging).
|
||||
* @return {!{connection: RenderedConnection, radius: number}}
|
||||
* Contains two properties: 'connection' which is either another
|
||||
*
|
||||
* @param conn The connection searching for a compatible mate.
|
||||
* @param maxRadius The maximum radius to another connection.
|
||||
* @param dxy Offset between this connection's location in the database and
|
||||
* the current location (as a result of dragging).
|
||||
* @returns Contains two properties: 'connection' which is either another
|
||||
* connection or null, and 'radius' which is the distance.
|
||||
*/
|
||||
searchForClosest(conn, maxRadius, dxy) {
|
||||
searchForClosest(
|
||||
conn: RenderedConnection, maxRadius: number,
|
||||
dxy: Coordinate): {connection: RenderedConnection|null, radius: number} {
|
||||
if (!this.connections_.length) {
|
||||
// Don't bother.
|
||||
return {connection: null, radius: maxRadius};
|
||||
@@ -267,7 +248,7 @@ class ConnectionDB {
|
||||
let pointerMin = closestIndex - 1;
|
||||
while (pointerMin >= 0 && this.isInYRange_(pointerMin, conn.y, maxRadius)) {
|
||||
temp = this.connections_[pointerMin];
|
||||
if (this.connectionChecker_.canConnect(conn, temp, true, bestRadius)) {
|
||||
if (this.connectionChecker.canConnect(conn, temp, true, bestRadius)) {
|
||||
bestConnection = temp;
|
||||
bestRadius = temp.distanceFrom(conn);
|
||||
}
|
||||
@@ -278,7 +259,7 @@ class ConnectionDB {
|
||||
while (pointerMax < this.connections_.length &&
|
||||
this.isInYRange_(pointerMax, conn.y, maxRadius)) {
|
||||
temp = this.connections_[pointerMax];
|
||||
if (this.connectionChecker_.canConnect(conn, temp, true, bestRadius)) {
|
||||
if (this.connectionChecker.canConnect(conn, temp, true, bestRadius)) {
|
||||
bestConnection = temp;
|
||||
bestRadius = temp.distanceFrom(conn);
|
||||
}
|
||||
@@ -288,19 +269,18 @@ class ConnectionDB {
|
||||
// Reset the values of x and y.
|
||||
conn.x = baseX;
|
||||
conn.y = baseY;
|
||||
|
||||
// If there were no valid connections, bestConnection will be null.
|
||||
return {connection: bestConnection, radius: bestRadius};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a set of connection DBs for a workspace.
|
||||
* @param {!IConnectionChecker} checker The workspace's
|
||||
* connection checker, used to decide if connections are valid during a
|
||||
* drag.
|
||||
* @return {!Array<!ConnectionDB>} Array of databases.
|
||||
*
|
||||
* @param checker The workspace's connection checker, used to decide if
|
||||
* connections are valid during a drag.
|
||||
* @returns Array of databases.
|
||||
*/
|
||||
static init(checker) {
|
||||
static init(checker: IConnectionChecker): ConnectionDB[] {
|
||||
// Create four databases, one for each connection type.
|
||||
const dbList = [];
|
||||
dbList[ConnectionType.INPUT_VALUE] = new ConnectionDB(checker);
|
||||
@@ -310,5 +290,3 @@ class ConnectionDB {
|
||||
return dbList;
|
||||
}
|
||||
}
|
||||
|
||||
exports.ConnectionDB = ConnectionDB;
|
||||
@@ -4,33 +4,27 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview An enum for the possible types of connections.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* An enum for the possible types of connections.
|
||||
*
|
||||
* @namespace Blockly.ConnectionType
|
||||
*/
|
||||
goog.module('Blockly.ConnectionType');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ConnectionType');
|
||||
|
||||
|
||||
/**
|
||||
* Enum for the type of a connection or input.
|
||||
* @enum {number}
|
||||
*
|
||||
* @alias Blockly.ConnectionType
|
||||
*/
|
||||
const ConnectionType = {
|
||||
export enum ConnectionType {
|
||||
// A right-facing value input. E.g. 'set item to' or 'return'.
|
||||
INPUT_VALUE: 1,
|
||||
INPUT_VALUE = 1,
|
||||
// A left-facing value output. E.g. 'random fraction'.
|
||||
OUTPUT_VALUE: 2,
|
||||
OUTPUT_VALUE,
|
||||
// A down-facing block stack. E.g. 'if-do' or 'else'.
|
||||
NEXT_STATEMENT: 3,
|
||||
NEXT_STATEMENT,
|
||||
// An up-facing block stack. E.g. 'break out of loop'.
|
||||
PREVIOUS_STATEMENT: 4,
|
||||
};
|
||||
|
||||
exports.ConnectionType = ConnectionType;
|
||||
PREVIOUS_STATEMENT
|
||||
}
|
||||
@@ -4,30 +4,25 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Blockly constants.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Blockly constants.
|
||||
*
|
||||
* @namespace Blockly.constants
|
||||
*/
|
||||
goog.module('Blockly.constants');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.constants');
|
||||
|
||||
|
||||
/**
|
||||
* The language-neutral ID given to the collapsed input.
|
||||
* @const {string}
|
||||
*
|
||||
* @alias Blockly.constants.COLLAPSED_INPUT_NAME
|
||||
*/
|
||||
const COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
|
||||
exports.COLLAPSED_INPUT_NAME = COLLAPSED_INPUT_NAME;
|
||||
export const COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
|
||||
|
||||
/**
|
||||
* The language-neutral ID given to the collapsed field.
|
||||
* @const {string}
|
||||
*
|
||||
* @alias Blockly.constants.COLLAPSED_FIELD_NAME
|
||||
*/
|
||||
const COLLAPSED_FIELD_NAME = '_TEMP_COLLAPSED_FIELD';
|
||||
exports.COLLAPSED_FIELD_NAME = COLLAPSED_FIELD_NAME;
|
||||
export const COLLAPSED_FIELD_NAME = '_TEMP_COLLAPSED_FIELD';
|
||||
@@ -4,112 +4,79 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Functionality for the right-click context menus.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Functionality for the right-click context menus.
|
||||
*
|
||||
* @namespace Blockly.ContextMenu
|
||||
*/
|
||||
goog.module('Blockly.ContextMenu');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ContextMenu');
|
||||
|
||||
const WidgetDiv = goog.require('Blockly.WidgetDiv');
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const aria = goog.require('Blockly.utils.aria');
|
||||
const browserEvents = goog.require('Blockly.browserEvents');
|
||||
const clipboard = goog.require('Blockly.clipboard');
|
||||
const deprecation = goog.require('Blockly.utils.deprecation');
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const userAgent = goog.require('Blockly.utils.userAgent');
|
||||
const svgMath = goog.require('Blockly.utils.svgMath');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
const {config} = goog.require('Blockly.config');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
const {MenuItem} = goog.require('Blockly.MenuItem');
|
||||
const {Menu} = goog.require('Blockly.Menu');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
const {Rect} = goog.require('Blockly.utils.Rect');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceCommentSvg} = goog.requireType('Blockly.WorkspaceCommentSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
/** @suppress {extraRequire} */
|
||||
goog.require('Blockly.Events.BlockCreate');
|
||||
import type {Block} from './block.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as clipboard from './clipboard.js';
|
||||
import {config} from './config.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import type {ContextMenuOption, LegacyContextMenuOption} from './contextmenu_registry.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import {Menu} from './menu.js';
|
||||
import {MenuItem} from './menuitem.js';
|
||||
import {Msg} from './msg.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
import {Coordinate} from './utils/coordinate.js';
|
||||
import {Rect} from './utils/rect.js';
|
||||
import * as svgMath from './utils/svg_math.js';
|
||||
import * as WidgetDiv from './widgetdiv.js';
|
||||
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
import * as Xml from './xml.js';
|
||||
|
||||
|
||||
/**
|
||||
* Which block is the context menu attached to?
|
||||
* @type {?Block}
|
||||
*/
|
||||
let currentBlock = null;
|
||||
let currentBlock: Block|null = null;
|
||||
|
||||
const dummyOwner = {};
|
||||
|
||||
/**
|
||||
* Gets the block the context menu is currently attached to.
|
||||
* @return {?Block} The block the context menu is attached to.
|
||||
*
|
||||
* @returns The block the context menu is attached to.
|
||||
* @alias Blockly.ContextMenu.getCurrentBlock
|
||||
*/
|
||||
const getCurrentBlock = function() {
|
||||
export function getCurrentBlock(): Block|null {
|
||||
return currentBlock;
|
||||
};
|
||||
exports.getCurrentBlock = getCurrentBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the block the context menu is currently attached to.
|
||||
* @param {?Block} block The block the context menu is attached to.
|
||||
*
|
||||
* @param block The block the context menu is attached to.
|
||||
* @alias Blockly.ContextMenu.setCurrentBlock
|
||||
*/
|
||||
const setCurrentBlock = function(block) {
|
||||
export function setCurrentBlock(block: Block|null) {
|
||||
currentBlock = block;
|
||||
};
|
||||
exports.setCurrentBlock = setCurrentBlock;
|
||||
|
||||
// Add JS accessors for backwards compatibility.
|
||||
Object.defineProperties(exports, {
|
||||
/**
|
||||
* Which block is the context menu attached to?
|
||||
* @name Blockly.ContextMenu.currentBlock
|
||||
* @type {Block}
|
||||
* @deprecated Use Blockly.Tooltip.getCurrentBlock() /
|
||||
* .setCurrentBlock() instead. (September 2021)
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
currentBlock: {
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.ContextMenu.currentBlock', 'September 2021',
|
||||
'September 2022', 'Blockly.Tooltip.getCurrentBlock()');
|
||||
return getCurrentBlock();
|
||||
},
|
||||
set: function(block) {
|
||||
deprecation.warn(
|
||||
'Blockly.ContextMenu.currentBlock', 'September 2021',
|
||||
'September 2022', 'Blockly.Tooltip.setCurrentBlock(block)');
|
||||
setCurrentBlock(block);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu object.
|
||||
* @type {Menu}
|
||||
*/
|
||||
let menu_ = null;
|
||||
let menu_: Menu|null = null;
|
||||
|
||||
/**
|
||||
* Construct the menu based on the list of options and show the menu.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @param {!Array<!Object>} options Array of menu options.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
*
|
||||
* @param e Mouse event.
|
||||
* @param options Array of menu options.
|
||||
* @param rtl True if RTL, false if LTR.
|
||||
* @alias Blockly.ContextMenu.show
|
||||
*/
|
||||
const show = function(e, options, rtl) {
|
||||
WidgetDiv.show(exports, rtl, dispose);
|
||||
export function show(
|
||||
e: Event, options: (ContextMenuOption|LegacyContextMenuOption)[],
|
||||
rtl: boolean) {
|
||||
WidgetDiv.show(dummyOwner, rtl, dispose);
|
||||
if (!options.length) {
|
||||
hide();
|
||||
return;
|
||||
@@ -124,22 +91,23 @@ const show = function(e, options, rtl) {
|
||||
menu.focus();
|
||||
}, 1);
|
||||
currentBlock = null; // May be set by Blockly.Block.
|
||||
};
|
||||
exports.show = show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the context menu object and populate it with the given options.
|
||||
* @param {!Array<!Object>} options Array of menu options.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @return {!Menu} The menu that will be shown on right click.
|
||||
* @private
|
||||
*
|
||||
* @param options Array of menu options.
|
||||
* @param rtl True if RTL, false if LTR.
|
||||
* @returns The menu that will be shown on right click.
|
||||
*/
|
||||
const populate_ = function(options, rtl) {
|
||||
function populate_(
|
||||
options: (ContextMenuOption|LegacyContextMenuOption)[],
|
||||
rtl: boolean): Menu {
|
||||
/* Here's what one option object looks like:
|
||||
{text: 'Make It So',
|
||||
enabled: true,
|
||||
callback: Blockly.MakeItSo}
|
||||
*/
|
||||
{text: 'Make It So',
|
||||
enabled: true,
|
||||
callback: Blockly.MakeItSo}
|
||||
*/
|
||||
const menu = new Menu();
|
||||
menu.setRole(aria.Role.MENU);
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
@@ -150,35 +118,39 @@ const populate_ = function(options, rtl) {
|
||||
menu.addChild(menuItem);
|
||||
menuItem.setEnabled(option.enabled);
|
||||
if (option.enabled) {
|
||||
const actionHandler = function(_menuItem) {
|
||||
// TODO: Create a type for option that can be used in an @this tag.
|
||||
/* eslint-disable-next-line no-invalid-this */
|
||||
const option = this;
|
||||
const actionHandler = function() {
|
||||
hide();
|
||||
option.callback(option.scope);
|
||||
// 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);
|
||||
};
|
||||
menuItem.onAction(actionHandler, option);
|
||||
menuItem.onAction(actionHandler, {});
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu to the page and position it correctly.
|
||||
* @param {!Menu} menu The menu to add and position.
|
||||
* @param {!Event} e Mouse event for the right click that is making the context
|
||||
*
|
||||
* @param menu The menu to add and position.
|
||||
* @param e Mouse event for the right click that is making the context
|
||||
* menu appear.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
* @private
|
||||
* @param rtl True if RTL, false if LTR.
|
||||
*/
|
||||
const position_ = function(menu, e, rtl) {
|
||||
function position_(menu: Menu, e: Event, rtl: boolean) {
|
||||
// Record windowSize and scrollOffset before adding menu.
|
||||
const viewportBBox = svgMath.getViewportBBox();
|
||||
const mouseEvent = e as MouseEvent;
|
||||
// This one is just a point, but we'll pretend that it's a rect so we can use
|
||||
// some helper functions.
|
||||
const anchorBBox = new Rect(
|
||||
e.clientY + viewportBBox.top, e.clientY + viewportBBox.top,
|
||||
e.clientX + viewportBBox.left, e.clientX + viewportBBox.left);
|
||||
mouseEvent.clientY + viewportBBox.top,
|
||||
mouseEvent.clientY + viewportBBox.top,
|
||||
mouseEvent.clientX + viewportBBox.left,
|
||||
mouseEvent.clientX + viewportBBox.left);
|
||||
|
||||
createWidget_(menu);
|
||||
const menuSize = menu.getSize();
|
||||
@@ -195,77 +167,74 @@ const position_ = function(menu, e, rtl) {
|
||||
// correctly. Otherwise it will cause a page scroll to get the misplaced menu
|
||||
// in view. See issue #1329.
|
||||
menu.focus();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and render the menu widget inside Blockly's widget div.
|
||||
* @param {!Menu} menu The menu to add to the widget div.
|
||||
* @private
|
||||
*
|
||||
* @param menu The menu to add to the widget div.
|
||||
*/
|
||||
const createWidget_ = function(menu) {
|
||||
function createWidget_(menu: Menu) {
|
||||
const div = WidgetDiv.getDiv();
|
||||
if (!div) {
|
||||
throw Error('Attempting to create a context menu when widget div is null');
|
||||
}
|
||||
menu.render(div);
|
||||
const menuDom = menu.getElement();
|
||||
dom.addClass(
|
||||
/** @type {!Element} */ (menuDom), 'blocklyContextMenu');
|
||||
const menuDom = menu.render(div);
|
||||
dom.addClass(menuDom, 'blocklyContextMenu');
|
||||
// Prevent system context menu when right-clicking a Blockly context menu.
|
||||
browserEvents.conditionalBind(
|
||||
/** @type {!EventTarget} */ (menuDom), 'contextmenu', null,
|
||||
haltPropagation);
|
||||
(menuDom as EventTarget), 'contextmenu', null, haltPropagation);
|
||||
// Focus only after the initial render to avoid issue #1329.
|
||||
menu.focus();
|
||||
};
|
||||
|
||||
}
|
||||
/**
|
||||
* Halts the propagation of the event without doing anything else.
|
||||
* @param {!Event} e An event.
|
||||
*
|
||||
* @param e An event.
|
||||
*/
|
||||
const haltPropagation = function(e) {
|
||||
function haltPropagation(e: Event) {
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the context menu.
|
||||
*
|
||||
* @alias Blockly.ContextMenu.hide
|
||||
*/
|
||||
const hide = function() {
|
||||
WidgetDiv.hideIfOwner(exports);
|
||||
export function hide() {
|
||||
WidgetDiv.hideIfOwner(dummyOwner);
|
||||
currentBlock = null;
|
||||
};
|
||||
exports.hide = hide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of the menu.
|
||||
*
|
||||
* @alias Blockly.ContextMenu.dispose
|
||||
*/
|
||||
const dispose = function() {
|
||||
export function dispose() {
|
||||
if (menu_) {
|
||||
menu_.dispose();
|
||||
menu_ = null;
|
||||
}
|
||||
};
|
||||
exports.dispose = dispose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback function that creates and configures a block,
|
||||
* then places the new block next to the original.
|
||||
* @param {!Block} block Original block.
|
||||
* @param {!Element} xml XML representation of new block.
|
||||
* @return {!Function} Function that creates a block.
|
||||
*
|
||||
* @param block Original block.
|
||||
* @param xml XML representation of new block.
|
||||
* @returns Function that creates a block.
|
||||
* @alias Blockly.ContextMenu.callbackFactory
|
||||
*/
|
||||
const callbackFactory = function(block, xml) {
|
||||
return function() {
|
||||
export function callbackFactory(block: Block, xml: Element): Function {
|
||||
return () => {
|
||||
eventUtils.disable();
|
||||
let newBlock;
|
||||
try {
|
||||
newBlock =
|
||||
/** @type {!BlockSvg} */ (Xml.domToBlock(xml, block.workspace));
|
||||
newBlock = Xml.domToBlock(xml, block.workspace!) as BlockSvg;
|
||||
// Move the new block next to the old block.
|
||||
const xy = block.getRelativeToSurfaceXY();
|
||||
if (block.RTL) {
|
||||
@@ -283,20 +252,22 @@ const callbackFactory = function(block, xml) {
|
||||
}
|
||||
newBlock.select();
|
||||
};
|
||||
};
|
||||
exports.callbackFactory = callbackFactory;
|
||||
}
|
||||
|
||||
// Helper functions for creating context menu options.
|
||||
|
||||
/**
|
||||
* Make a context menu option for deleting the current workspace comment.
|
||||
* @param {!WorkspaceCommentSvg} comment The workspace comment where the
|
||||
*
|
||||
* @param comment The workspace comment where the
|
||||
* right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @returns A menu option,
|
||||
* containing text, enabled, and a callback.
|
||||
* @alias Blockly.ContextMenu.commentDeleteOption
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const commentDeleteOption = function(comment) {
|
||||
export function commentDeleteOption(comment: WorkspaceCommentSvg):
|
||||
LegacyContextMenuOption {
|
||||
const deleteOption = {
|
||||
text: Msg['REMOVE_COMMENT'],
|
||||
enabled: true,
|
||||
@@ -307,18 +278,20 @@ const commentDeleteOption = function(comment) {
|
||||
},
|
||||
};
|
||||
return deleteOption;
|
||||
};
|
||||
exports.commentDeleteOption = commentDeleteOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a context menu option for duplicating the current workspace comment.
|
||||
* @param {!WorkspaceCommentSvg} comment The workspace comment where the
|
||||
*
|
||||
* @param comment The workspace comment where the
|
||||
* right-click originated.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @returns A menu option,
|
||||
* containing text, enabled, and a callback.
|
||||
* @alias Blockly.ContextMenu.commentDuplicateOption
|
||||
* @package
|
||||
* @internal
|
||||
*/
|
||||
const commentDuplicateOption = function(comment) {
|
||||
export function commentDuplicateOption(comment: WorkspaceCommentSvg):
|
||||
LegacyContextMenuOption {
|
||||
const duplicateOption = {
|
||||
text: Msg['DUPLICATE_COMMENT'],
|
||||
enabled: true,
|
||||
@@ -327,28 +300,27 @@ const commentDuplicateOption = function(comment) {
|
||||
},
|
||||
};
|
||||
return duplicateOption;
|
||||
};
|
||||
exports.commentDuplicateOption = commentDuplicateOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a context menu option for adding a comment on the workspace.
|
||||
* @param {!WorkspaceSvg} ws The workspace where the right-click
|
||||
*
|
||||
* @param ws The workspace where the right-click
|
||||
* originated.
|
||||
* @param {!Event} e The right-click mouse event.
|
||||
* @return {!Object} A menu option, containing text, enabled, and a callback.
|
||||
* @package
|
||||
* @param e The right-click mouse event.
|
||||
* @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
|
||||
*/
|
||||
const workspaceCommentOption = function(ws, e) {
|
||||
const {WorkspaceCommentSvg} = goog.module.get('Blockly.WorkspaceCommentSvg');
|
||||
if (!WorkspaceCommentSvg) {
|
||||
throw Error('Missing require for Blockly.WorkspaceCommentSvg');
|
||||
}
|
||||
// Helper function to create and position a comment correctly based on the
|
||||
// location of the mouse event.
|
||||
const addWsComment = function() {
|
||||
export function workspaceCommentOption(
|
||||
ws: WorkspaceSvg, e: Event): ContextMenuOption {
|
||||
/**
|
||||
* Helper function to create and position a comment correctly based on the
|
||||
* location of the mouse event.
|
||||
*/
|
||||
function addWsComment() {
|
||||
const comment = new WorkspaceCommentSvg(
|
||||
ws, Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'],
|
||||
WorkspaceCommentSvg.DEFAULT_SIZE, WorkspaceCommentSvg.DEFAULT_SIZE);
|
||||
@@ -360,8 +332,10 @@ const workspaceCommentOption = function(ws, e) {
|
||||
const boundingRect = injectionDiv.getBoundingClientRect();
|
||||
|
||||
// The client coordinates offset by the injection div's upper left corner.
|
||||
const mouseEvent = e as MouseEvent;
|
||||
const clientOffsetPixels = new Coordinate(
|
||||
e.clientX - boundingRect.left, e.clientY - boundingRect.top);
|
||||
mouseEvent.clientX - boundingRect.left,
|
||||
mouseEvent.clientY - boundingRect.top);
|
||||
|
||||
// The offset in pixels between the main workspace's origin and the upper
|
||||
// left corner of the injection div.
|
||||
@@ -382,17 +356,14 @@ const workspaceCommentOption = function(ws, e) {
|
||||
comment.render();
|
||||
comment.select();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const wsCommentOption = {
|
||||
// Foreign objects don't work in IE. Don't let the user create comments
|
||||
// that they won't be able to edit.
|
||||
enabled: !userAgent.IE,
|
||||
};
|
||||
enabled: true,
|
||||
} as ContextMenuOption;
|
||||
wsCommentOption.text = Msg['ADD_COMMENT'];
|
||||
wsCommentOption.callback = function() {
|
||||
addWsComment();
|
||||
};
|
||||
return wsCommentOption;
|
||||
};
|
||||
exports.workspaceCommentOption = workspaceCommentOption;
|
||||
}
|
||||
@@ -1,651 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Registers default context menu items.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Registers default context menu items.
|
||||
* @namespace Blockly.ContextMenuItems
|
||||
*/
|
||||
goog.module('Blockly.ContextMenuItems');
|
||||
|
||||
const Events = goog.require('Blockly.Events');
|
||||
const clipboard = goog.require('Blockly.clipboard');
|
||||
const dialog = goog.require('Blockly.dialog');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const idGenerator = goog.require('Blockly.utils.idGenerator');
|
||||
const userAgent = goog.require('Blockly.utils.userAgent');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
const {ContextMenuRegistry} = goog.require('Blockly.ContextMenuRegistry');
|
||||
const {Msg} = goog.require('Blockly.Msg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
const {inputTypes} = goog.require('Blockly.inputTypes');
|
||||
|
||||
|
||||
/**
|
||||
* Option to undo previous action.
|
||||
* @alias Blockly.ContextMenuItems.registerUndo
|
||||
*/
|
||||
const registerUndo = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const undoOption = {
|
||||
displayText: function() {
|
||||
return Msg['UNDO'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.workspace.getUndoStack().length > 0) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
scope.workspace.undo(false);
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'undoWorkspace',
|
||||
weight: 1,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(undoOption);
|
||||
};
|
||||
exports.registerUndo = registerUndo;
|
||||
|
||||
/**
|
||||
* Option to redo previous action.
|
||||
* @alias Blockly.ContextMenuItems.registerRedo
|
||||
*/
|
||||
const registerRedo = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const redoOption = {
|
||||
displayText: function() {
|
||||
return Msg['REDO'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.workspace.getRedoStack().length > 0) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
scope.workspace.undo(true);
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'redoWorkspace',
|
||||
weight: 2,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(redoOption);
|
||||
};
|
||||
exports.registerRedo = registerRedo;
|
||||
|
||||
/**
|
||||
* Option to clean up blocks.
|
||||
* @alias Blockly.ContextMenuItems.registerCleanup
|
||||
*/
|
||||
const registerCleanup = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const cleanOption = {
|
||||
displayText: function() {
|
||||
return Msg['CLEAN_UP'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.workspace.isMovable()) {
|
||||
if (scope.workspace.getTopBlocks(false).length > 1) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
scope.workspace.cleanUp();
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'cleanWorkspace',
|
||||
weight: 3,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(cleanOption);
|
||||
};
|
||||
exports.registerCleanup = registerCleanup;
|
||||
|
||||
/**
|
||||
* Creates a callback to collapse or expand top blocks.
|
||||
* @param {boolean} shouldCollapse Whether a block should collapse.
|
||||
* @param {!Array<BlockSvg>} topBlocks Top blocks in the workspace.
|
||||
* @private
|
||||
*/
|
||||
const toggleOption_ = function(shouldCollapse, topBlocks) {
|
||||
const DELAY = 10;
|
||||
let ms = 0;
|
||||
let timeoutCounter = 0;
|
||||
const timeoutFn = function(block) {
|
||||
timeoutCounter--;
|
||||
block.setCollapsed(shouldCollapse);
|
||||
if (timeoutCounter === 0) {
|
||||
Events.setGroup(false);
|
||||
}
|
||||
};
|
||||
Events.setGroup(true);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
let block = topBlocks[i];
|
||||
while (block) {
|
||||
timeoutCounter++;
|
||||
setTimeout(timeoutFn.bind(null, block), ms);
|
||||
block = block.getNextBlock();
|
||||
ms += DELAY;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Option to collapse all blocks.
|
||||
* @alias Blockly.ContextMenuItems.registerCollapse
|
||||
*/
|
||||
const registerCollapse = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const collapseOption = {
|
||||
displayText: function() {
|
||||
return Msg['COLLAPSE_ALL'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.workspace.options.collapse) {
|
||||
const topBlocks = scope.workspace.getTopBlocks(false);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
let block = topBlocks[i];
|
||||
while (block) {
|
||||
if (!block.isCollapsed()) {
|
||||
return 'enabled';
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
toggleOption_(true, scope.workspace.getTopBlocks(true));
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'collapseWorkspace',
|
||||
weight: 4,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(collapseOption);
|
||||
};
|
||||
exports.registerCollapse = registerCollapse;
|
||||
|
||||
/**
|
||||
* Option to expand all blocks.
|
||||
* @alias Blockly.ContextMenuItems.registerExpand
|
||||
*/
|
||||
const registerExpand = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const expandOption = {
|
||||
displayText: function() {
|
||||
return Msg['EXPAND_ALL'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.workspace.options.collapse) {
|
||||
const topBlocks = scope.workspace.getTopBlocks(false);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
let block = topBlocks[i];
|
||||
while (block) {
|
||||
if (block.isCollapsed()) {
|
||||
return 'enabled';
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
toggleOption_(false, scope.workspace.getTopBlocks(true));
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'expandWorkspace',
|
||||
weight: 5,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(expandOption);
|
||||
};
|
||||
exports.registerExpand = registerExpand;
|
||||
|
||||
/**
|
||||
* Adds a block and its children to a list of deletable blocks.
|
||||
* @param {!BlockSvg} block to delete.
|
||||
* @param {!Array<!BlockSvg>} deleteList list of blocks that can be deleted.
|
||||
* This will be
|
||||
* modified in place with the given block and its descendants.
|
||||
* @private
|
||||
*/
|
||||
const addDeletableBlocks_ = function(block, deleteList) {
|
||||
if (block.isDeletable()) {
|
||||
Array.prototype.push.apply(deleteList, block.getDescendants(false));
|
||||
} else {
|
||||
const children = block.getChildren(false);
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
addDeletableBlocks_(children[i], deleteList);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a list of blocks that can be deleted in the given workspace.
|
||||
* @param {!WorkspaceSvg} workspace to delete all blocks from.
|
||||
* @return {!Array<!BlockSvg>} list of blocks to delete.
|
||||
* @private
|
||||
*/
|
||||
const getDeletableBlocks_ = function(workspace) {
|
||||
const deleteList = [];
|
||||
const topBlocks = workspace.getTopBlocks(true);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
addDeletableBlocks_(topBlocks[i], deleteList);
|
||||
}
|
||||
return deleteList;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given blocks. Used to delete all blocks in the workspace.
|
||||
* @param {!Array<!BlockSvg>} deleteList list of blocks to delete.
|
||||
* @param {string} eventGroup event group ID with which all delete events should
|
||||
* be associated.
|
||||
* @private
|
||||
*/
|
||||
const deleteNext_ = function(deleteList, eventGroup) {
|
||||
const DELAY = 10;
|
||||
eventUtils.setGroup(eventGroup);
|
||||
const block = deleteList.shift();
|
||||
if (block) {
|
||||
if (block.workspace) {
|
||||
block.dispose(false, true);
|
||||
setTimeout(deleteNext_, DELAY, deleteList, eventGroup);
|
||||
} else {
|
||||
deleteNext_(deleteList, eventGroup);
|
||||
}
|
||||
}
|
||||
eventUtils.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Option to delete all blocks.
|
||||
* @alias Blockly.ContextMenuItems.registerDeleteAll
|
||||
*/
|
||||
const registerDeleteAll = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const deleteOption = {
|
||||
displayText: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (!scope.workspace) {
|
||||
return;
|
||||
}
|
||||
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
|
||||
if (deletableBlocksLength === 1) {
|
||||
return Msg['DELETE_BLOCK'];
|
||||
} else {
|
||||
return Msg['DELETE_X_BLOCKS'].replace(
|
||||
'%1', String(deletableBlocksLength));
|
||||
}
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (!scope.workspace) {
|
||||
return;
|
||||
}
|
||||
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
|
||||
return deletableBlocksLength > 0 ? 'enabled' : 'disabled';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (!scope.workspace) {
|
||||
return;
|
||||
}
|
||||
scope.workspace.cancelCurrentGesture();
|
||||
const deletableBlocks = getDeletableBlocks_(scope.workspace);
|
||||
const eventGroup = idGenerator.genUid();
|
||||
if (deletableBlocks.length < 2) {
|
||||
deleteNext_(deletableBlocks, eventGroup);
|
||||
} else {
|
||||
dialog.confirm(
|
||||
Msg['DELETE_ALL_BLOCKS'].replace(
|
||||
'%1', String(deletableBlocks.length)),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
deleteNext_(deletableBlocks, eventGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'workspaceDelete',
|
||||
weight: 6,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(deleteOption);
|
||||
};
|
||||
exports.registerDeleteAll = registerDeleteAll;
|
||||
|
||||
/**
|
||||
* Registers all workspace-scoped context menu items.
|
||||
* @private
|
||||
*/
|
||||
const registerWorkspaceOptions_ = function() {
|
||||
registerUndo();
|
||||
registerRedo();
|
||||
registerCleanup();
|
||||
registerCollapse();
|
||||
registerExpand();
|
||||
registerDeleteAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Option to duplicate a block.
|
||||
* @alias Blockly.ContextMenuItems.registerDuplicate
|
||||
*/
|
||||
const registerDuplicate = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const duplicateOption = {
|
||||
displayText: function() {
|
||||
return Msg['DUPLICATE_BLOCK'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
if (!block.isInFlyout && block.isDeletable() && block.isMovable()) {
|
||||
if (block.isDuplicatable()) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.block) {
|
||||
clipboard.duplicate(scope.block);
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDuplicate',
|
||||
weight: 1,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(duplicateOption);
|
||||
};
|
||||
exports.registerDuplicate = registerDuplicate;
|
||||
|
||||
/**
|
||||
* Option to add or remove block-level comment.
|
||||
* @alias Blockly.ContextMenuItems.registerComment
|
||||
*/
|
||||
const registerComment = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const commentOption = {
|
||||
displayText: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.block.getCommentIcon()) {
|
||||
// If there's already a comment, option is to remove.
|
||||
return Msg['REMOVE_COMMENT'];
|
||||
}
|
||||
// If there's no comment yet, option is to add.
|
||||
return Msg['ADD_COMMENT'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
// IE doesn't support necessary features for comment editing.
|
||||
if (!userAgent.IE && !block.isInFlyout &&
|
||||
block.workspace.options.comments && !block.isCollapsed() &&
|
||||
block.isEditable()) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
if (block.getCommentIcon()) {
|
||||
block.setCommentText(null);
|
||||
} else {
|
||||
block.setCommentText('');
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockComment',
|
||||
weight: 2,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(commentOption);
|
||||
};
|
||||
exports.registerComment = registerComment;
|
||||
|
||||
/**
|
||||
* Option to inline variables.
|
||||
* @alias Blockly.ContextMenuItems.registerInline
|
||||
*/
|
||||
const registerInline = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const inlineOption = {
|
||||
displayText: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
return (scope.block.getInputsInline()) ? Msg['EXTERNAL_INPUTS'] :
|
||||
Msg['INLINE_INPUTS'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
if (!block.isInFlyout && block.isMovable() && !block.isCollapsed()) {
|
||||
for (let i = 1; i < block.inputList.length; i++) {
|
||||
// Only display this option if there are two value or dummy inputs
|
||||
// next to each other.
|
||||
if (block.inputList[i - 1].type !== inputTypes.STATEMENT &&
|
||||
block.inputList[i].type !== inputTypes.STATEMENT) {
|
||||
return 'enabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
scope.block.setInputsInline(!scope.block.getInputsInline());
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockInline',
|
||||
weight: 3,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(inlineOption);
|
||||
};
|
||||
exports.registerInline = registerInline;
|
||||
|
||||
/**
|
||||
* Option to collapse or expand a block.
|
||||
* @alias Blockly.ContextMenuItems.registerCollapseExpandBlock
|
||||
*/
|
||||
const registerCollapseExpandBlock = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const collapseExpandOption = {
|
||||
displayText: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
return scope.block.isCollapsed() ? Msg['EXPAND_BLOCK'] :
|
||||
Msg['COLLAPSE_BLOCK'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
if (!block.isInFlyout && block.isMovable() &&
|
||||
block.workspace.options.collapse) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
scope.block.setCollapsed(!scope.block.isCollapsed());
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockCollapseExpand',
|
||||
weight: 4,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(collapseExpandOption);
|
||||
};
|
||||
exports.registerCollapseExpandBlock = registerCollapseExpandBlock;
|
||||
|
||||
/**
|
||||
* Option to disable or enable a block.
|
||||
* @alias Blockly.ContextMenuItems.registerDisable
|
||||
*/
|
||||
const registerDisable = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const disableOption = {
|
||||
displayText: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
return (scope.block.isEnabled()) ? Msg['DISABLE_BLOCK'] :
|
||||
Msg['ENABLE_BLOCK'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
if (!block.isInFlyout && block.workspace.options.disable &&
|
||||
block.isEditable()) {
|
||||
if (block.getInheritedDisabled()) {
|
||||
return 'disabled';
|
||||
}
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
const group = eventUtils.getGroup();
|
||||
if (!group) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
block.setEnabled(!block.isEnabled());
|
||||
if (!group) {
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDisable',
|
||||
weight: 5,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(disableOption);
|
||||
};
|
||||
exports.registerDisable = registerDisable;
|
||||
|
||||
/**
|
||||
* Option to delete a block.
|
||||
* @alias Blockly.ContextMenuItems.registerDelete
|
||||
*/
|
||||
const registerDelete = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const deleteOption = {
|
||||
displayText: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
// Count the number of blocks that are nested in this block.
|
||||
let descendantCount = block.getDescendants(false).length;
|
||||
const nextBlock = block.getNextBlock();
|
||||
if (nextBlock) {
|
||||
// Blocks in the current stack would survive this block's deletion.
|
||||
descendantCount -= nextBlock.getDescendants(false).length;
|
||||
}
|
||||
return (descendantCount === 1) ?
|
||||
Msg['DELETE_BLOCK'] :
|
||||
Msg['DELETE_X_BLOCKS'].replace('%1', String(descendantCount));
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (!scope.block.isInFlyout && scope.block.isDeletable()) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
if (scope.block) {
|
||||
scope.block.checkAndDelete();
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDelete',
|
||||
weight: 6,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(deleteOption);
|
||||
};
|
||||
exports.registerDelete = registerDelete;
|
||||
|
||||
/**
|
||||
* Option to open help for a block.
|
||||
* @alias Blockly.ContextMenuItems.registerHelp
|
||||
*/
|
||||
const registerHelp = function() {
|
||||
/** @type {!ContextMenuRegistry.RegistryItem} */
|
||||
const helpOption = {
|
||||
displayText: function() {
|
||||
return Msg['HELP'];
|
||||
},
|
||||
preconditionFn: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
const block = scope.block;
|
||||
const url = (typeof block.helpUrl === 'function') ? block.helpUrl() :
|
||||
block.helpUrl;
|
||||
if (url) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback: function(/** @type {!ContextMenuRegistry.Scope} */
|
||||
scope) {
|
||||
scope.block.showHelp();
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockHelp',
|
||||
weight: 7,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(helpOption);
|
||||
};
|
||||
exports.registerHelp = registerHelp;
|
||||
|
||||
/**
|
||||
* Registers all block-scoped context menu items.
|
||||
* @private
|
||||
*/
|
||||
const registerBlockOptions_ = function() {
|
||||
registerDuplicate();
|
||||
registerComment();
|
||||
registerInline();
|
||||
registerCollapseExpandBlock();
|
||||
registerDisable();
|
||||
registerDelete();
|
||||
registerHelp();
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers all default context menu items. This should be called once per
|
||||
* instance of ContextMenuRegistry.
|
||||
* @package
|
||||
* @alias Blockly.ContextMenuItems.registerDefaultOptions
|
||||
*/
|
||||
const registerDefaultOptions = function() {
|
||||
registerWorkspaceOptions_();
|
||||
registerBlockOptions_();
|
||||
};
|
||||
exports.registerDefaultOptions = registerDefaultOptions;
|
||||
|
||||
registerDefaultOptions();
|
||||
@@ -0,0 +1,588 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* 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');
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as clipboard from './clipboard.js';
|
||||
import {ContextMenuRegistry, RegistryItem, Scope} from './contextmenu_registry.js';
|
||||
import * as dialog from './dialog.js';
|
||||
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 = {
|
||||
displayText() {
|
||||
return Msg['UNDO'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (scope.workspace!.getUndoStack().length > 0) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
scope.workspace!.undo(false);
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'undoWorkspace',
|
||||
weight: 1,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(undoOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to redo previous action.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerRedo
|
||||
*/
|
||||
export function registerRedo() {
|
||||
const redoOption: RegistryItem = {
|
||||
displayText() {
|
||||
return Msg['REDO'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (scope.workspace!.getRedoStack().length > 0) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
scope.workspace!.undo(true);
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'redoWorkspace',
|
||||
weight: 2,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(redoOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to clean up blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerCleanup
|
||||
*/
|
||||
export function registerCleanup() {
|
||||
const cleanOption: RegistryItem = {
|
||||
displayText() {
|
||||
return Msg['CLEAN_UP'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (scope.workspace!.isMovable()) {
|
||||
if (scope.workspace!.getTopBlocks(false).length > 1) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
scope.workspace!.cleanUp();
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'cleanWorkspace',
|
||||
weight: 3,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(cleanOption);
|
||||
}
|
||||
/**
|
||||
* Creates a callback to collapse or expand top blocks.
|
||||
*
|
||||
* @param shouldCollapse Whether a block should collapse.
|
||||
* @param topBlocks Top blocks in the workspace.
|
||||
*/
|
||||
function toggleOption_(shouldCollapse: boolean, topBlocks: BlockSvg[]) {
|
||||
const DELAY = 10;
|
||||
let ms = 0;
|
||||
let timeoutCounter = 0;
|
||||
function timeoutFn(block: BlockSvg) {
|
||||
timeoutCounter--;
|
||||
block.setCollapsed(shouldCollapse);
|
||||
if (timeoutCounter === 0) {
|
||||
Events.setGroup(false);
|
||||
}
|
||||
}
|
||||
Events.setGroup(true);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
let block: BlockSvg|null = topBlocks[i];
|
||||
while (block) {
|
||||
timeoutCounter++;
|
||||
setTimeout(timeoutFn.bind(null, block), ms);
|
||||
block = block.getNextBlock();
|
||||
ms += DELAY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to collapse all blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerCollapse
|
||||
*/
|
||||
export function registerCollapse() {
|
||||
const collapseOption: RegistryItem = {
|
||||
displayText() {
|
||||
return Msg['COLLAPSE_ALL'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (scope.workspace!.options.collapse) {
|
||||
const topBlocks = scope.workspace!.getTopBlocks(false);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
let block: BlockSvg|null = topBlocks[i];
|
||||
while (block) {
|
||||
if (!block.isCollapsed()) {
|
||||
return 'enabled';
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
toggleOption_(true, scope.workspace!.getTopBlocks(true));
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'collapseWorkspace',
|
||||
weight: 4,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(collapseOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to expand all blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerExpand
|
||||
*/
|
||||
export function registerExpand() {
|
||||
const expandOption: RegistryItem = {
|
||||
displayText() {
|
||||
return Msg['EXPAND_ALL'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (scope.workspace!.options.collapse) {
|
||||
const topBlocks = scope.workspace!.getTopBlocks(false);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
let block: BlockSvg|null = topBlocks[i];
|
||||
while (block) {
|
||||
if (block.isCollapsed()) {
|
||||
return 'enabled';
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
toggleOption_(false, scope.workspace!.getTopBlocks(true));
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'expandWorkspace',
|
||||
weight: 5,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(expandOption);
|
||||
}
|
||||
/**
|
||||
* Adds a block and its children to a list of deletable blocks.
|
||||
*
|
||||
* @param block to delete.
|
||||
* @param deleteList list of blocks that can be deleted.
|
||||
* This will be modified in place with the given block and its descendants.
|
||||
*/
|
||||
function addDeletableBlocks_(block: BlockSvg, deleteList: BlockSvg[]) {
|
||||
if (block.isDeletable()) {
|
||||
Array.prototype.push.apply(deleteList, block.getDescendants(false));
|
||||
} else {
|
||||
const children = block.getChildren(false);
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
addDeletableBlocks_(children[i], deleteList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a list of blocks that can be deleted in the given workspace.
|
||||
*
|
||||
* @param workspace to delete all blocks from.
|
||||
* @returns list of blocks to delete.
|
||||
*/
|
||||
function getDeletableBlocks_(workspace: WorkspaceSvg): BlockSvg[] {
|
||||
const deleteList: BlockSvg[] = [];
|
||||
const topBlocks = workspace.getTopBlocks(true);
|
||||
for (let i = 0; i < topBlocks.length; i++) {
|
||||
addDeletableBlocks_(topBlocks[i], deleteList);
|
||||
}
|
||||
return deleteList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function deleteNext_(deleteList: BlockSvg[], eventGroup: string) {
|
||||
const DELAY = 10;
|
||||
eventUtils.setGroup(eventGroup);
|
||||
const block = deleteList.shift();
|
||||
if (block) {
|
||||
if (!block.isDeadOrDying()) {
|
||||
block.dispose(false, true);
|
||||
setTimeout(deleteNext_, DELAY, deleteList, eventGroup);
|
||||
} else {
|
||||
deleteNext_(deleteList, eventGroup);
|
||||
}
|
||||
}
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to delete all blocks.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDeleteAll
|
||||
*/
|
||||
export function registerDeleteAll() {
|
||||
const deleteOption: RegistryItem = {
|
||||
displayText(scope: Scope) {
|
||||
if (!scope.workspace) {
|
||||
return '';
|
||||
}
|
||||
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
|
||||
if (deletableBlocksLength === 1) {
|
||||
return Msg['DELETE_BLOCK'];
|
||||
} else {
|
||||
return Msg['DELETE_X_BLOCKS'].replace(
|
||||
'%1', String(deletableBlocksLength));
|
||||
}
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (!scope.workspace) {
|
||||
return 'disabled';
|
||||
}
|
||||
const deletableBlocksLength = getDeletableBlocks_(scope.workspace).length;
|
||||
return deletableBlocksLength > 0 ? 'enabled' : 'disabled';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
if (!scope.workspace) {
|
||||
return;
|
||||
}
|
||||
scope.workspace.cancelCurrentGesture();
|
||||
const deletableBlocks = getDeletableBlocks_(scope.workspace);
|
||||
const eventGroup = idGenerator.genUid();
|
||||
if (deletableBlocks.length < 2) {
|
||||
deleteNext_(deletableBlocks, eventGroup);
|
||||
} else {
|
||||
dialog.confirm(
|
||||
Msg['DELETE_ALL_BLOCKS'].replace(
|
||||
'%1', String(deletableBlocks.length)),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
deleteNext_(deletableBlocks, eventGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
|
||||
id: 'workspaceDelete',
|
||||
weight: 6,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(deleteOption);
|
||||
}
|
||||
/** Registers all workspace-scoped context menu items. */
|
||||
function registerWorkspaceOptions_() {
|
||||
registerUndo();
|
||||
registerRedo();
|
||||
registerCleanup();
|
||||
registerCollapse();
|
||||
registerExpand();
|
||||
registerDeleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to duplicate a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDuplicate
|
||||
*/
|
||||
export function registerDuplicate() {
|
||||
const duplicateOption: RegistryItem = {
|
||||
displayText() {
|
||||
return Msg['DUPLICATE_BLOCK'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
const block = scope.block;
|
||||
if (!block!.isInFlyout && block!.isDeletable() && block!.isMovable()) {
|
||||
if (block!.isDuplicatable()) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
if (scope.block) {
|
||||
clipboard.duplicate(scope.block);
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDuplicate',
|
||||
weight: 1,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(duplicateOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to add or remove block-level comment.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerComment
|
||||
*/
|
||||
export function registerComment() {
|
||||
const commentOption: RegistryItem = {
|
||||
displayText(scope: Scope) {
|
||||
if (scope.block!.getCommentIcon()) {
|
||||
// If there's already a comment, option is to remove.
|
||||
return Msg['REMOVE_COMMENT'];
|
||||
}
|
||||
// If there's no comment yet, option is to add.
|
||||
return Msg['ADD_COMMENT'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
const block = scope.block;
|
||||
if (!block!.isInFlyout && block!.workspace.options.comments &&
|
||||
!block!.isCollapsed() && block!.isEditable()) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
const block = scope.block;
|
||||
if (block!.getCommentIcon()) {
|
||||
block!.setCommentText(null);
|
||||
} else {
|
||||
block!.setCommentText('');
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockComment',
|
||||
weight: 2,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(commentOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to inline variables.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerInline
|
||||
*/
|
||||
export function registerInline() {
|
||||
const inlineOption: RegistryItem = {
|
||||
displayText(scope: Scope) {
|
||||
return scope.block!.getInputsInline() ? Msg['EXTERNAL_INPUTS'] :
|
||||
Msg['INLINE_INPUTS'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
const block = scope.block;
|
||||
if (!block!.isInFlyout && block!.isMovable() && !block!.isCollapsed()) {
|
||||
for (let i = 1; i < block!.inputList.length; i++) {
|
||||
// Only display this option if there are two value or dummy inputs
|
||||
// next to each other.
|
||||
if (block!.inputList[i - 1].type !== inputTypes.STATEMENT &&
|
||||
block!.inputList[i].type !== inputTypes.STATEMENT) {
|
||||
return 'enabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
scope.block!.setInputsInline(!scope.block!.getInputsInline());
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockInline',
|
||||
weight: 3,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(inlineOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to collapse or expand a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerCollapseExpandBlock
|
||||
*/
|
||||
export function registerCollapseExpandBlock() {
|
||||
const collapseExpandOption: RegistryItem = {
|
||||
displayText(scope: Scope) {
|
||||
return scope.block!.isCollapsed() ? Msg['EXPAND_BLOCK'] :
|
||||
Msg['COLLAPSE_BLOCK'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
const block = scope.block;
|
||||
if (!block!.isInFlyout && block!.isMovable() &&
|
||||
block!.workspace.options.collapse) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
scope.block!.setCollapsed(!scope.block!.isCollapsed());
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockCollapseExpand',
|
||||
weight: 4,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(collapseExpandOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to disable or enable a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDisable
|
||||
*/
|
||||
export function registerDisable() {
|
||||
const disableOption: RegistryItem = {
|
||||
displayText(scope: Scope) {
|
||||
return scope.block!.isEnabled() ? Msg['DISABLE_BLOCK'] :
|
||||
Msg['ENABLE_BLOCK'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
const block = scope.block;
|
||||
if (!block!.isInFlyout && block!.workspace.options.disable &&
|
||||
block!.isEditable()) {
|
||||
if (block!.getInheritedDisabled()) {
|
||||
return 'disabled';
|
||||
}
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
const block = scope.block;
|
||||
const group = eventUtils.getGroup();
|
||||
if (!group) {
|
||||
eventUtils.setGroup(true);
|
||||
}
|
||||
block!.setEnabled(!block!.isEnabled());
|
||||
if (!group) {
|
||||
eventUtils.setGroup(false);
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDisable',
|
||||
weight: 5,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(disableOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to delete a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDelete
|
||||
*/
|
||||
export function registerDelete() {
|
||||
const deleteOption: RegistryItem = {
|
||||
displayText(scope: Scope) {
|
||||
const block = scope.block;
|
||||
// Count the number of blocks that are nested in this block.
|
||||
let descendantCount = block!.getDescendants(false).length;
|
||||
const nextBlock = block!.getNextBlock();
|
||||
if (nextBlock) {
|
||||
// Blocks in the current stack would survive this block's deletion.
|
||||
descendantCount -= nextBlock.getDescendants(false).length;
|
||||
}
|
||||
return descendantCount === 1 ?
|
||||
Msg['DELETE_BLOCK'] :
|
||||
Msg['DELETE_X_BLOCKS'].replace('%1', String(descendantCount));
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
if (scope.block) {
|
||||
scope.block.checkAndDelete();
|
||||
}
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockDelete',
|
||||
weight: 6,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(deleteOption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to open help for a block.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerHelp
|
||||
*/
|
||||
export function registerHelp() {
|
||||
const helpOption: RegistryItem = {
|
||||
displayText() {
|
||||
return Msg['HELP'];
|
||||
},
|
||||
preconditionFn(scope: Scope) {
|
||||
const block = scope.block;
|
||||
const url = typeof block!.helpUrl === 'function' ? block!.helpUrl() :
|
||||
block!.helpUrl;
|
||||
if (url) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'hidden';
|
||||
},
|
||||
callback(scope: Scope) {
|
||||
scope.block!.showHelp();
|
||||
},
|
||||
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
|
||||
id: 'blockHelp',
|
||||
weight: 7,
|
||||
};
|
||||
ContextMenuRegistry.registry.register(helpOption);
|
||||
}
|
||||
|
||||
/** Registers all block-scoped context menu items. */
|
||||
function registerBlockOptions_() {
|
||||
registerDuplicate();
|
||||
registerComment();
|
||||
registerInline();
|
||||
registerCollapseExpandBlock();
|
||||
registerDisable();
|
||||
registerDelete();
|
||||
registerHelp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers all default context menu items. This should be called once per
|
||||
* instance of ContextMenuRegistry.
|
||||
*
|
||||
* @alias Blockly.ContextMenuItems.registerDefaultOptions
|
||||
* @internal
|
||||
*/
|
||||
export function registerDefaultOptions() {
|
||||
registerWorkspaceOptions_();
|
||||
registerBlockOptions_();
|
||||
}
|
||||
|
||||
registerDefaultOptions();
|
||||
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Registry for context menu option items.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Registry for context menu option items.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.ContextMenuRegistry');
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class ContextMenuRegistry {
|
||||
/**
|
||||
* Resets the existing singleton instance of ContextMenuRegistry.
|
||||
*/
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear and recreate the registry.
|
||||
*/
|
||||
reset() {
|
||||
/**
|
||||
* Registry of all registered RegistryItems, keyed by ID.
|
||||
* @type {!Object<string, !ContextMenuRegistry.RegistryItem>}
|
||||
* @private
|
||||
*/
|
||||
this.registry_ = Object.create(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a RegistryItem.
|
||||
* @param {!ContextMenuRegistry.RegistryItem} item Context menu item to
|
||||
* register.
|
||||
* @throws {Error} if an item with the given ID already exists.
|
||||
*/
|
||||
register(item) {
|
||||
if (this.registry_[item.id]) {
|
||||
throw Error('Menu item with ID "' + item.id + '" is already registered.');
|
||||
}
|
||||
this.registry_[item.id] = item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a RegistryItem with the given ID.
|
||||
* @param {string} id The ID of the RegistryItem to remove.
|
||||
* @throws {Error} if an item with the given ID does not exist.
|
||||
*/
|
||||
unregister(id) {
|
||||
if (!this.registry_[id]) {
|
||||
throw new Error('Menu item with ID "' + id + '" not found.');
|
||||
}
|
||||
delete this.registry_[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id The ID of the RegistryItem to get.
|
||||
* @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not
|
||||
* found
|
||||
*/
|
||||
getItem(id) {
|
||||
return this.registry_[id] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the valid context menu options for the given scope type (e.g. block or
|
||||
* workspace) and scope. Blocks are only shown if the preconditionFn shows
|
||||
* they should not be hidden.
|
||||
* @param {!ContextMenuRegistry.ScopeType} scopeType Type of scope where menu
|
||||
* should be shown (e.g. on a block or on a workspace)
|
||||
* @param {!ContextMenuRegistry.Scope} scope Current scope of context menu
|
||||
* (i.e., the exact workspace or block being clicked on)
|
||||
* @return {!Array<!ContextMenuRegistry.ContextMenuOption>} the list of
|
||||
* ContextMenuOptions
|
||||
*/
|
||||
getContextMenuOptions(scopeType, scope) {
|
||||
const menuOptions = [];
|
||||
const registry = this.registry_;
|
||||
Object.keys(registry).forEach(function(id) {
|
||||
const item = registry[id];
|
||||
if (scopeType === item.scopeType) {
|
||||
const precondition = item.preconditionFn(scope);
|
||||
if (precondition !== 'hidden') {
|
||||
const displayText = typeof item.displayText === 'function' ?
|
||||
item.displayText(scope) :
|
||||
item.displayText;
|
||||
/** @type {!ContextMenuRegistry.ContextMenuOption} */
|
||||
const menuOption = {
|
||||
text: displayText,
|
||||
enabled: (precondition === 'enabled'),
|
||||
callback: item.callback,
|
||||
scope: scope,
|
||||
weight: item.weight,
|
||||
};
|
||||
menuOptions.push(menuOption);
|
||||
}
|
||||
}
|
||||
});
|
||||
menuOptions.sort(function(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
return menuOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Where this menu item should be rendered. If the menu item should be rendered
|
||||
* in multiple scopes, e.g. on both a block and a workspace, it should be
|
||||
* registered for each scope.
|
||||
* @enum {string}
|
||||
*/
|
||||
ContextMenuRegistry.ScopeType = {
|
||||
BLOCK: 'block',
|
||||
WORKSPACE: 'workspace',
|
||||
};
|
||||
|
||||
/**
|
||||
* The actual workspace/block where the menu is being rendered. This is passed
|
||||
* to callback and displayText functions that depend on this information.
|
||||
* @typedef {{
|
||||
* block: (BlockSvg|undefined),
|
||||
* workspace: (WorkspaceSvg|undefined)
|
||||
* }}
|
||||
*/
|
||||
ContextMenuRegistry.Scope;
|
||||
|
||||
/**
|
||||
* A menu item as entered in the registry.
|
||||
* @typedef {{
|
||||
* callback: function(!ContextMenuRegistry.Scope),
|
||||
* scopeType: !ContextMenuRegistry.ScopeType,
|
||||
* displayText: ((function(!ContextMenuRegistry.Scope):string)|string),
|
||||
* preconditionFn: function(!ContextMenuRegistry.Scope):string,
|
||||
* weight: number,
|
||||
* id: string
|
||||
* }}
|
||||
*/
|
||||
ContextMenuRegistry.RegistryItem;
|
||||
|
||||
/**
|
||||
* A menu item as presented to contextmenu.js.
|
||||
* @typedef {{
|
||||
* text: string,
|
||||
* enabled: boolean,
|
||||
* callback: function(!ContextMenuRegistry.Scope),
|
||||
* scope: !ContextMenuRegistry.Scope,
|
||||
* weight: number
|
||||
* }}
|
||||
*/
|
||||
ContextMenuRegistry.ContextMenuOption;
|
||||
|
||||
/**
|
||||
* Singleton instance of this class. All interactions with this class should be
|
||||
* done on this object.
|
||||
* @type {!ContextMenuRegistry}
|
||||
*/
|
||||
ContextMenuRegistry.registry = new ContextMenuRegistry();
|
||||
|
||||
exports.ContextMenuRegistry = ContextMenuRegistry;
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registry for context menu option items.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.ContextMenuRegistry');
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
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;
|
||||
/** Registry of all registered RegistryItems, keyed by ID. */
|
||||
private registry_ = new Map<string, RegistryItem>();
|
||||
|
||||
/** Resets the existing singleton instance of ContextMenuRegistry. */
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/** Clear and recreate the registry. */
|
||||
reset() {
|
||||
this.registry_.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a RegistryItem.
|
||||
*
|
||||
* @param item Context menu item to register.
|
||||
* @throws {Error} if an item with the given ID already exists.
|
||||
*/
|
||||
register(item: RegistryItem) {
|
||||
if (this.registry_.has(item.id)) {
|
||||
throw Error('Menu item with ID "' + item.id + '" is already registered.');
|
||||
}
|
||||
this.registry_.set(item.id, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a RegistryItem with the given ID.
|
||||
*
|
||||
* @param id The ID of the RegistryItem to remove.
|
||||
* @throws {Error} if an item with the given ID does not exist.
|
||||
*/
|
||||
unregister(id: string) {
|
||||
if (!this.registry_.has(id)) {
|
||||
throw new Error('Menu item with ID "' + id + '" not found.');
|
||||
}
|
||||
this.registry_.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id The ID of the RegistryItem to get.
|
||||
* @returns RegistryItem or null if not found
|
||||
*/
|
||||
getItem(id: string): RegistryItem|null {
|
||||
return this.registry_.get(id) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the valid context menu options for the given scope type (e.g. block or
|
||||
* workspace) and scope. Blocks are only shown if the preconditionFn shows
|
||||
* they should not be hidden.
|
||||
*
|
||||
* @param scopeType Type of scope where menu should be shown (e.g. on a block
|
||||
* or on a workspace)
|
||||
* @param scope Current scope of context menu (i.e., the exact workspace or
|
||||
* block being clicked on)
|
||||
* @returns the list of ContextMenuOptions
|
||||
*/
|
||||
getContextMenuOptions(scopeType: ScopeType, scope: Scope):
|
||||
ContextMenuOption[] {
|
||||
const menuOptions: ContextMenuOption[] = [];
|
||||
for (const item of this.registry_.values()) {
|
||||
if (scopeType === item.scopeType) {
|
||||
const precondition = item.preconditionFn(scope);
|
||||
if (precondition !== 'hidden') {
|
||||
const displayText = typeof item.displayText === 'function' ?
|
||||
item.displayText(scope) :
|
||||
item.displayText;
|
||||
const menuOption: ContextMenuOption = {
|
||||
text: displayText,
|
||||
enabled: precondition === 'enabled',
|
||||
callback: item.callback,
|
||||
scope,
|
||||
weight: item.weight,
|
||||
};
|
||||
menuOptions.push(menuOption);
|
||||
}
|
||||
}
|
||||
}
|
||||
menuOptions.sort(function(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
return menuOptions;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace ContextMenuRegistry {
|
||||
/**
|
||||
* Where this menu item should be rendered. If the menu item should be
|
||||
* rendered in multiple scopes, e.g. on both a block and a workspace, it
|
||||
* should be registered for each scope.
|
||||
*/
|
||||
export enum ScopeType {
|
||||
BLOCK = 'block',
|
||||
WORKSPACE = 'workspace',
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual workspace/block where the menu is being rendered. This is passed
|
||||
* to callback and displayText functions that depend on this information.
|
||||
*/
|
||||
export interface Scope {
|
||||
block?: BlockSvg;
|
||||
workspace?: WorkspaceSvg;
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu item as entered in the registry.
|
||||
*/
|
||||
export interface RegistryItem {
|
||||
callback: (p1: Scope) => void;
|
||||
scopeType: ScopeType;
|
||||
displayText: ((p1: Scope) => string)|string;
|
||||
preconditionFn: (p1: Scope) => string;
|
||||
weight: number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu item as presented to contextmenu.js.
|
||||
*/
|
||||
export interface ContextMenuOption {
|
||||
text: string;
|
||||
enabled: boolean;
|
||||
callback: (p1: Scope) => void;
|
||||
scope: Scope;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A subset of ContextMenuOption corresponding to what was publicly
|
||||
* documented. ContextMenuOption should be preferred for new code.
|
||||
*/
|
||||
export interface LegacyContextMenuOption {
|
||||
text: string;
|
||||
enabled: boolean;
|
||||
callback: (p1: Scope) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance of this class. All interactions with this class should
|
||||
* be done on this object.
|
||||
*/
|
||||
ContextMenuRegistry.registry = new ContextMenuRegistry();
|
||||
}
|
||||
|
||||
export type ScopeType = ContextMenuRegistry.ScopeType;
|
||||
export const ScopeType = ContextMenuRegistry.ScopeType;
|
||||
export type Scope = ContextMenuRegistry.Scope;
|
||||
export type RegistryItem = ContextMenuRegistry.RegistryItem;
|
||||
export type ContextMenuOption = ContextMenuRegistry.ContextMenuOption;
|
||||
export type LegacyContextMenuOption =
|
||||
ContextMenuRegistry.LegacyContextMenuOption;
|
||||
+18
-37
@@ -4,50 +4,31 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Inject Blockly's CSS synchronously.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Inject Blockly's CSS synchronously.
|
||||
*
|
||||
* @namespace Blockly.Css
|
||||
*/
|
||||
goog.module('Blockly.Css');
|
||||
|
||||
const deprecation = goog.require('Blockly.utils.deprecation');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Css');
|
||||
|
||||
|
||||
/**
|
||||
* Has CSS already been injected?
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
/** Has CSS already been injected? */
|
||||
let injected = false;
|
||||
|
||||
/**
|
||||
* Add some CSS to the blob that will be injected later. Allows optional
|
||||
* components such as fields and the toolbox to store separate CSS.
|
||||
* @param {string|!Array<string>} cssContent Multiline CSS string or an array of
|
||||
* single lines of CSS.
|
||||
*
|
||||
* @param cssContent Multiline CSS string or an array of single lines of CSS.
|
||||
* @alias Blockly.Css.register
|
||||
*/
|
||||
const register = function(cssContent) {
|
||||
export function register(cssContent: string) {
|
||||
if (injected) {
|
||||
throw Error('CSS already injected');
|
||||
}
|
||||
|
||||
if (Array.isArray(cssContent)) {
|
||||
deprecation.warn(
|
||||
'Registering CSS by passing an array of strings', 'September 2021',
|
||||
'September 2022', 'css.register passing a multiline string');
|
||||
content += ('\n' + cssContent.join('\n'));
|
||||
} else {
|
||||
// Add new cssContent in the global content.
|
||||
content += ('\n' + cssContent);
|
||||
}
|
||||
};
|
||||
exports.register = register;
|
||||
content += '\n' + cssContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the CSS into the DOM. This is preferable over using a regular CSS
|
||||
@@ -55,12 +36,13 @@ exports.register = register;
|
||||
* a) It loads synchronously and doesn't force a redraw later.
|
||||
* b) It speeds up loading by not blocking on a separate HTTP transfer.
|
||||
* c) The CSS content may be made dynamic depending on init options.
|
||||
* @param {boolean} hasCss If false, don't inject CSS
|
||||
* (providing CSS becomes the document's responsibility).
|
||||
* @param {string} pathToMedia Path from page to the Blockly media directory.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
const inject = function(hasCss, pathToMedia) {
|
||||
export function inject(hasCss: boolean, pathToMedia: string) {
|
||||
// Only inject the CSS once.
|
||||
if (injected) {
|
||||
return;
|
||||
@@ -81,14 +63,14 @@ const inject = function(hasCss, pathToMedia) {
|
||||
const cssTextNode = document.createTextNode(cssContent);
|
||||
cssNode.appendChild(cssTextNode);
|
||||
document.head.insertBefore(cssNode, document.head.firstChild);
|
||||
};
|
||||
exports.inject = inject;
|
||||
}
|
||||
|
||||
/**
|
||||
* The CSS content for Blockly.
|
||||
*
|
||||
* @alias Blockly.Css.content
|
||||
*/
|
||||
let content = (`
|
||||
let content = `
|
||||
.blocklySvg {
|
||||
background-color: #fff;
|
||||
outline: none;
|
||||
@@ -564,5 +546,4 @@ let content = (`
|
||||
float: right;
|
||||
margin-right: -24px;
|
||||
}
|
||||
`);
|
||||
exports.content = content;
|
||||
`;
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The abstract class for a component that can delete a block or
|
||||
* bubble that is dropped on top of it.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The abstract class for a component that can delete a block or
|
||||
* bubble that is dropped on top of it.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.DeleteArea');
|
||||
|
||||
const {BlockSvg} = goog.require('Blockly.BlockSvg');
|
||||
const {DragTarget} = goog.require('Blockly.DragTarget');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDeleteArea} = goog.require('Blockly.IDeleteArea');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDraggable} = goog.requireType('Blockly.IDraggable');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a component that can delete a block or bubble that is
|
||||
* dropped on top of it.
|
||||
* @extends {DragTarget}
|
||||
* @implements {IDeleteArea}
|
||||
* @alias Blockly.DeleteArea
|
||||
*/
|
||||
class DeleteArea extends DragTarget {
|
||||
/**
|
||||
* Constructor for DeleteArea. Should not be called directly, only by a
|
||||
* subclass.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/**
|
||||
* Whether the last block or bubble dragged over this delete area would be
|
||||
* deleted if dropped on this component.
|
||||
* This property is not updated after the block or bubble is deleted.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.wouldDelete_ = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided block or bubble would be deleted if dropped on
|
||||
* this area.
|
||||
* This method should check if the element is deletable and is always called
|
||||
* before onDragEnter/onDragOver/onDragExit.
|
||||
* @param {!IDraggable} element The block or bubble currently being
|
||||
* dragged.
|
||||
* @param {boolean} couldConnect Whether the element could could connect to
|
||||
* another.
|
||||
* @return {boolean} Whether the element provided would be deleted if dropped
|
||||
* on this area.
|
||||
*/
|
||||
wouldDelete(element, couldConnect) {
|
||||
if (element instanceof BlockSvg) {
|
||||
const block = /** @type {BlockSvg} */ (element);
|
||||
const couldDeleteBlock = !block.getParent() && block.isDeletable();
|
||||
this.updateWouldDelete_(couldDeleteBlock && !couldConnect);
|
||||
} else {
|
||||
this.updateWouldDelete_(element.isDeletable());
|
||||
}
|
||||
return this.wouldDelete_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal wouldDelete_ state.
|
||||
* @param {boolean} wouldDelete The new value for the wouldDelete state.
|
||||
* @protected
|
||||
*/
|
||||
updateWouldDelete_(wouldDelete) {
|
||||
this.wouldDelete_ = wouldDelete;
|
||||
}
|
||||
}
|
||||
|
||||
exports.DeleteArea = DeleteArea;
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* The abstract class for a component that can delete a block or
|
||||
* bubble that is dropped on top of it.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.DeleteArea');
|
||||
|
||||
import {BlockSvg} from './block_svg.js';
|
||||
import {DragTarget} from './drag_target.js';
|
||||
import type {IDeleteArea} from './interfaces/i_delete_area.js';
|
||||
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 {
|
||||
/**
|
||||
* Whether the last block or bubble dragged over this delete area would be
|
||||
* deleted if dropped on this component.
|
||||
* This property is not updated after the block or bubble is deleted.
|
||||
*/
|
||||
protected wouldDelete_ = false;
|
||||
|
||||
/**
|
||||
* The unique id for this component that is used to register with the
|
||||
* ComponentManager.
|
||||
*/
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
override id!: string;
|
||||
|
||||
/**
|
||||
* Constructor for DeleteArea. Should not be called directly, only by a
|
||||
* subclass.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided block or bubble would be deleted if dropped on
|
||||
* this area.
|
||||
* This method should check if the element is deletable and is always called
|
||||
* before onDragEnter/onDragOver/onDragExit.
|
||||
*
|
||||
* @param element The block or bubble currently being dragged.
|
||||
* @param couldConnect Whether the element could could connect to another.
|
||||
* @returns Whether the element provided would be deleted if dropped on this
|
||||
* area.
|
||||
*/
|
||||
wouldDelete(element: IDraggable, couldConnect: boolean): boolean {
|
||||
if (element instanceof BlockSvg) {
|
||||
const block = (element);
|
||||
const couldDeleteBlock = !block.getParent() && block.isDeletable();
|
||||
this.updateWouldDelete_(couldDeleteBlock && !couldConnect);
|
||||
} else {
|
||||
this.updateWouldDelete_(element.isDeletable());
|
||||
}
|
||||
return this.wouldDelete_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal wouldDelete_ state.
|
||||
*
|
||||
* @param wouldDelete The new value for the wouldDelete state.
|
||||
*/
|
||||
protected updateWouldDelete_(wouldDelete: boolean) {
|
||||
this.wouldDelete_ = wouldDelete;
|
||||
}
|
||||
}
|
||||
@@ -4,104 +4,118 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Wrapper functions around JS functions for showing
|
||||
* alert/confirmation dialogs.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Wrapper functions around JS functions for showing alert/confirmation dialogs.
|
||||
*
|
||||
* @namespace Blockly.dialog
|
||||
*/
|
||||
goog.module('Blockly.dialog');
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.dialog');
|
||||
|
||||
let alertImplementation = function(message, opt_callback) {
|
||||
|
||||
let alertImplementation = function(message: string, opt_callback?: () => void) {
|
||||
window.alert(message);
|
||||
if (opt_callback) {
|
||||
opt_callback();
|
||||
}
|
||||
};
|
||||
|
||||
let confirmImplementation = function(message, callback) {
|
||||
let confirmImplementation = function(
|
||||
message: string, callback: (result: boolean) => void) {
|
||||
callback(window.confirm(message));
|
||||
};
|
||||
|
||||
let promptImplementation = function(message, defaultValue, callback) {
|
||||
let promptImplementation = function(
|
||||
message: string, defaultValue: string,
|
||||
callback: (result: string|null) => void) {
|
||||
callback(window.prompt(message, defaultValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper to window.alert() that app developers may override via setAlert to
|
||||
* provide alternatives to the modal browser window.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {function()=} opt_callback The callback when the alert is dismissed.
|
||||
*
|
||||
* @param message The message to display to the user.
|
||||
* @param opt_callback The callback when the alert is dismissed.
|
||||
* @alias Blockly.dialog.alert
|
||||
*/
|
||||
const alert = function(message, opt_callback) {
|
||||
export function alert(message: string, opt_callback?: () => void) {
|
||||
alertImplementation(message, opt_callback);
|
||||
};
|
||||
exports.alert = alert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the function to be run when Blockly.dialog.alert() is called.
|
||||
* @param {!function(string, function()=)} alertFunction The function to be run.
|
||||
*
|
||||
* @param alertFunction The function to be run.
|
||||
* @see Blockly.dialog.alert
|
||||
* @alias Blockly.dialog.setAlert
|
||||
*/
|
||||
const setAlert = function(alertFunction) {
|
||||
export function setAlert(alertFunction: (p1: string, p2?: () => void) => void) {
|
||||
alertImplementation = alertFunction;
|
||||
};
|
||||
exports.setAlert = setAlert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to window.confirm() that app developers may override via setConfirm
|
||||
* to provide alternatives to the modal browser window.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {!function(boolean)} callback The callback for handling user response.
|
||||
*
|
||||
* @param message The message to display to the user.
|
||||
* @param callback The callback for handling user response.
|
||||
* @alias Blockly.dialog.confirm
|
||||
*/
|
||||
const confirm = function(message, callback) {
|
||||
export function confirm(message: string, callback: (p1: boolean) => void) {
|
||||
TEST_ONLY.confirmInternal(message, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private version of confirm for stubbing in tests.
|
||||
*/
|
||||
function confirmInternal(message: string, callback: (p1: boolean) => void) {
|
||||
confirmImplementation(message, callback);
|
||||
};
|
||||
exports.confirm = confirm;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the function to be run when Blockly.dialog.confirm() is called.
|
||||
* @param {!function(string, !function(boolean))} confirmFunction The function
|
||||
* to be run.
|
||||
*
|
||||
* @param confirmFunction The function to be run.
|
||||
* @see Blockly.dialog.confirm
|
||||
* @alias Blockly.dialog.setConfirm
|
||||
*/
|
||||
const setConfirm = function(confirmFunction) {
|
||||
export function setConfirm(
|
||||
confirmFunction: (p1: string, p2: (p1: boolean) => void) => void) {
|
||||
confirmImplementation = confirmFunction;
|
||||
};
|
||||
exports.setConfirm = setConfirm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to window.prompt() that app developers may override via setPrompt to
|
||||
* provide alternatives to the modal browser window. Built-in browser prompts
|
||||
* are often used for better text input experience on mobile device. We strongly
|
||||
* recommend testing mobile when overriding this.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {string} defaultValue The value to initialize the prompt with.
|
||||
* @param {!function(?string)} callback The callback for handling user response.
|
||||
*
|
||||
* @param 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
|
||||
*/
|
||||
const prompt = function(message, defaultValue, callback) {
|
||||
export function prompt(
|
||||
message: string, defaultValue: string,
|
||||
callback: (p1: string|null) => void) {
|
||||
promptImplementation(message, defaultValue, callback);
|
||||
};
|
||||
exports.prompt = prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the function to be run when Blockly.dialog.prompt() is called.
|
||||
* @param {!function(string, string, !function(?string))} promptFunction The
|
||||
* function to be run.
|
||||
*
|
||||
* @param promptFunction The function to be run.
|
||||
* @see Blockly.dialog.prompt
|
||||
* @alias Blockly.dialog.setPrompt
|
||||
*/
|
||||
const setPrompt = function(promptFunction) {
|
||||
export function setPrompt(
|
||||
promptFunction: (p1: string, p2: string, p3: (p1: string|null) => void) =>
|
||||
void) {
|
||||
promptImplementation = promptFunction;
|
||||
}
|
||||
|
||||
export const TEST_ONLY = {
|
||||
confirmInternal,
|
||||
};
|
||||
exports.setPrompt = setPrompt;
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The abstract class for a component with custom behaviour when a
|
||||
* block or bubble is dragged over or dropped on top of it.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* The abstract class for a component with custom behaviour when a
|
||||
* block or bubble is dragged over or dropped on top of it.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.DragTarget');
|
||||
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDragTarget} = goog.require('Blockly.IDragTarget');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {IDraggable} = goog.requireType('Blockly.IDraggable');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Rect} = goog.requireType('Blockly.utils.Rect');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a component with custom behaviour when a block or bubble
|
||||
* is dragged over or dropped on top of it.
|
||||
* @implements {IDragTarget}
|
||||
* @alias Blockly.DragTarget
|
||||
*/
|
||||
class DragTarget {
|
||||
/**
|
||||
* Handles when a cursor with a block or bubble enters this drag target.
|
||||
* @param {!IDraggable} _dragElement The block or bubble currently being
|
||||
* dragged.
|
||||
*/
|
||||
onDragEnter(_dragElement) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles when a cursor with a block or bubble is dragged over this drag
|
||||
* target.
|
||||
* @param {!IDraggable} _dragElement The block or bubble currently being
|
||||
* dragged.
|
||||
*/
|
||||
onDragOver(_dragElement) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles when a cursor with a block or bubble exits this drag target.
|
||||
* @param {!IDraggable} _dragElement The block or bubble currently being
|
||||
* dragged.
|
||||
*/
|
||||
onDragExit(_dragElement) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles when a block or bubble is dropped on this component.
|
||||
* Should not handle delete here.
|
||||
* @param {!IDraggable} _dragElement The block or bubble currently being
|
||||
* dragged.
|
||||
*/
|
||||
onDrop(_dragElement) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bounding rectangle of the drag target area in pixel units
|
||||
* relative to the Blockly injection div.
|
||||
* @return {?Rect} The component's bounding box. Null if drag
|
||||
* target area should be ignored.
|
||||
*/
|
||||
getClientRect() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided block or bubble should not be moved after
|
||||
* being dropped on this component. If true, the element will return to where
|
||||
* it was when the drag started.
|
||||
* @param {!IDraggable} _dragElement The block or bubble currently being
|
||||
* dragged.
|
||||
* @return {boolean} Whether the block or bubble provided should be returned
|
||||
* to drag start.
|
||||
*/
|
||||
shouldPreventMove(_dragElement) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.DragTarget = DragTarget;
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* The abstract class for a component with custom behaviour when a
|
||||
* block or bubble is dragged over or dropped on top of it.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.DragTarget');
|
||||
|
||||
import type {IDragTarget} from './interfaces/i_drag_target.js';
|
||||
import type {IDraggable} from './interfaces/i_draggable.js';
|
||||
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 {
|
||||
/**
|
||||
* The unique id for this component that is used to register with the
|
||||
* ComponentManager.
|
||||
*/
|
||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||
id!: string;
|
||||
|
||||
/**
|
||||
* Constructor for DragTarget. It exists to add the id property and should not
|
||||
* be called directly, only by a subclass.
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Handles when a cursor with a block or bubble enters this drag target.
|
||||
*
|
||||
* @param _dragElement The block or bubble currently being dragged.
|
||||
*/
|
||||
onDragEnter(_dragElement: IDraggable) {}
|
||||
// no-op
|
||||
|
||||
/**
|
||||
* Handles when a cursor with a block or bubble is dragged over this drag
|
||||
* target.
|
||||
*
|
||||
* @param _dragElement The block or bubble currently being dragged.
|
||||
*/
|
||||
onDragOver(_dragElement: IDraggable) {}
|
||||
// no-op
|
||||
|
||||
/**
|
||||
* Handles when a cursor with a block or bubble exits this drag target.
|
||||
*
|
||||
* @param _dragElement The block or bubble currently being dragged.
|
||||
*/
|
||||
onDragExit(_dragElement: IDraggable) {}
|
||||
// no-op
|
||||
/**
|
||||
* Handles when a block or bubble is dropped on this component.
|
||||
* Should not handle delete here.
|
||||
*
|
||||
* @param _dragElement The block or bubble currently being dragged.
|
||||
*/
|
||||
onDrop(_dragElement: IDraggable) {}
|
||||
// no-op
|
||||
|
||||
/**
|
||||
* Returns the bounding rectangle of the drag target area in pixel units
|
||||
* relative to the Blockly injection div.
|
||||
*
|
||||
* @returns The component's bounding box. Null if drag target area should be
|
||||
* ignored.
|
||||
*/
|
||||
getClientRect(): Rect|null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided block or bubble should not be moved after
|
||||
* being dropped on this component. If true, the element will return to where
|
||||
* it was when the drag started.
|
||||
*
|
||||
* @param _dragElement The block or bubble currently being dragged.
|
||||
* @returns Whether the block or bubble provided should be returned to drag
|
||||
* start.
|
||||
*/
|
||||
shouldPreventMove(_dragElement: IDraggable): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,754 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A div that floats on top of the workspace, for drop-down menus.
|
||||
* The drop-down can be kept inside the workspace, animate in/out, etc.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A div that floats on top of the workspace, for drop-down menus.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.dropDownDiv');
|
||||
|
||||
const common = goog.require('Blockly.common');
|
||||
const dom = goog.require('Blockly.utils.dom');
|
||||
const math = goog.require('Blockly.utils.math');
|
||||
const style = goog.require('Blockly.utils.style');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Field} = goog.requireType('Blockly.Field');
|
||||
const {Rect} = goog.require('Blockly.utils.Rect');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Size} = goog.requireType('Blockly.utils.Size');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg');
|
||||
|
||||
|
||||
/**
|
||||
* Arrow size in px. Should match the value in CSS
|
||||
* (need to position pre-render).
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const ARROW_SIZE = 16;
|
||||
exports.ARROW_SIZE = ARROW_SIZE;
|
||||
|
||||
/**
|
||||
* Drop-down border size in px. Should match the value in CSS (need to position
|
||||
* the arrow).
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const BORDER_SIZE = 1;
|
||||
exports.BORDER_SIZE = BORDER_SIZE;
|
||||
|
||||
/**
|
||||
* Amount the arrow must be kept away from the edges of the main drop-down div,
|
||||
* in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const ARROW_HORIZONTAL_PADDING = 12;
|
||||
exports.ARROW_HORIZONTAL_PADDING = ARROW_HORIZONTAL_PADDING;
|
||||
|
||||
/**
|
||||
* Amount drop-downs should be padded away from the source, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const PADDING_Y = 16;
|
||||
exports.PADDING_Y = PADDING_Y;
|
||||
|
||||
/**
|
||||
* Length of animations in seconds.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
const ANIMATION_TIME = 0.25;
|
||||
exports.ANIMATION_TIME = ANIMATION_TIME;
|
||||
|
||||
/**
|
||||
* Timer for animation out, to be cleared if we need to immediately hide
|
||||
* without disrupting new shows.
|
||||
* @type {?number}
|
||||
*/
|
||||
let animateOutTimer = null;
|
||||
|
||||
/**
|
||||
* Callback for when the drop-down is hidden.
|
||||
* @type {?Function}
|
||||
*/
|
||||
let onHide = null;
|
||||
|
||||
/**
|
||||
* A class name representing the current owner's workspace renderer.
|
||||
* @type {string}
|
||||
*/
|
||||
let renderedClassName = '';
|
||||
|
||||
/**
|
||||
* A class name representing the current owner's workspace theme.
|
||||
* @type {string}
|
||||
*/
|
||||
let themeClassName = '';
|
||||
|
||||
/**
|
||||
* The content element.
|
||||
* @type {!HTMLDivElement}
|
||||
*/
|
||||
let div;
|
||||
|
||||
/**
|
||||
* The content element.
|
||||
* @type {!HTMLDivElement}
|
||||
*/
|
||||
let content;
|
||||
|
||||
/**
|
||||
* The arrow element.
|
||||
* @type {!HTMLDivElement}
|
||||
*/
|
||||
let arrow;
|
||||
|
||||
/**
|
||||
* Drop-downs will appear within the bounds of this element if possible.
|
||||
* Set in setBoundsElement.
|
||||
* @type {?Element}
|
||||
*/
|
||||
let boundsElement = null;
|
||||
|
||||
/**
|
||||
* The object currently using the drop-down.
|
||||
* @type {?Object}
|
||||
*/
|
||||
let owner = null;
|
||||
|
||||
/**
|
||||
* Whether the dropdown was positioned to a field or the source block.
|
||||
* @type {?boolean}
|
||||
*/
|
||||
let positionToField = null;
|
||||
|
||||
/**
|
||||
* Dropdown bounds info object used to encapsulate sizing information about a
|
||||
* bounding element (bounding box and width/height).
|
||||
* @typedef {{
|
||||
* top:number,
|
||||
* left:number,
|
||||
* bottom:number,
|
||||
* right:number,
|
||||
* width:number,
|
||||
* height:number
|
||||
* }}
|
||||
*/
|
||||
let BoundsInfo;
|
||||
exports.BoundsInfo = BoundsInfo;
|
||||
|
||||
/**
|
||||
* Dropdown position metrics.
|
||||
* @typedef {{
|
||||
* initialX:number,
|
||||
* initialY:number,
|
||||
* finalX:number,
|
||||
* finalY:number,
|
||||
* arrowX:?number,
|
||||
* arrowY:?number,
|
||||
* arrowAtTop:?boolean,
|
||||
* arrowVisible:boolean
|
||||
* }}
|
||||
*/
|
||||
let PositionMetrics;
|
||||
exports.PositionMetrics = PositionMetrics;
|
||||
|
||||
/**
|
||||
* Create and insert the DOM element for this div.
|
||||
* @package
|
||||
*/
|
||||
const createDom = function() {
|
||||
if (div) {
|
||||
return; // Already created.
|
||||
}
|
||||
div = /** @type {!HTMLDivElement} */ (document.createElement('div'));
|
||||
div.className = 'blocklyDropDownDiv';
|
||||
const parentDiv = common.getParentContainer() || document.body;
|
||||
parentDiv.appendChild(div);
|
||||
|
||||
content = /** @type {!HTMLDivElement} */ (document.createElement('div'));
|
||||
content.className = 'blocklyDropDownContent';
|
||||
div.appendChild(content);
|
||||
|
||||
arrow = /** @type {!HTMLDivElement} */ (document.createElement('div'));
|
||||
arrow.className = 'blocklyDropDownArrow';
|
||||
div.appendChild(arrow);
|
||||
|
||||
div.style.opacity = 0;
|
||||
|
||||
// Transition animation for transform: translate() and opacity.
|
||||
div.style.transition = 'transform ' + ANIMATION_TIME + 's, ' +
|
||||
'opacity ' + ANIMATION_TIME + 's';
|
||||
|
||||
// Handle focusin/out events to add a visual indicator when
|
||||
// a child is focused or blurred.
|
||||
div.addEventListener('focusin', function() {
|
||||
dom.addClass(div, 'blocklyFocused');
|
||||
});
|
||||
div.addEventListener('focusout', function() {
|
||||
dom.removeClass(div, 'blocklyFocused');
|
||||
});
|
||||
};
|
||||
exports.createDom = createDom;
|
||||
|
||||
/**
|
||||
* Set an element to maintain bounds within. Drop-downs will appear
|
||||
* within the box of this element if possible.
|
||||
* @param {?Element} boundsElem Element to bind drop-down to.
|
||||
*/
|
||||
const setBoundsElement = function(boundsElem) {
|
||||
boundsElement = boundsElem;
|
||||
};
|
||||
exports.setBoundsElement = setBoundsElement;
|
||||
|
||||
/**
|
||||
* Provide the div for inserting content into the drop-down.
|
||||
* @return {!Element} Div to populate with content.
|
||||
*/
|
||||
const getContentDiv = function() {
|
||||
return content;
|
||||
};
|
||||
exports.getContentDiv = getContentDiv;
|
||||
|
||||
/**
|
||||
* Clear the content of the drop-down.
|
||||
*/
|
||||
const clearContent = function() {
|
||||
content.textContent = '';
|
||||
content.style.width = '';
|
||||
};
|
||||
exports.clearContent = clearContent;
|
||||
|
||||
/**
|
||||
* Set the colour for the drop-down.
|
||||
* @param {string} backgroundColour Any CSS colour for the background.
|
||||
* @param {string} borderColour Any CSS colour for the border.
|
||||
*/
|
||||
const setColour = function(backgroundColour, borderColour) {
|
||||
div.style.backgroundColor = backgroundColour;
|
||||
div.style.borderColor = borderColour;
|
||||
};
|
||||
exports.setColour = setColour;
|
||||
|
||||
/**
|
||||
* Shortcut to show and place the drop-down with positioning determined
|
||||
* by a particular block. The primary position will be below the block,
|
||||
* and the secondary position above the block. Drop-down will be
|
||||
* constrained to the block's workspace.
|
||||
* @param {!Field} field The field showing the drop-down.
|
||||
* @param {!BlockSvg} block Block to position the drop-down around.
|
||||
* @param {Function=} opt_onHide Optional callback for when the drop-down is
|
||||
* hidden.
|
||||
* @param {number=} opt_secondaryYOffset Optional Y offset for above-block
|
||||
* positioning.
|
||||
* @return {boolean} True if the menu rendered below block; false if above.
|
||||
*/
|
||||
const showPositionedByBlock = function(
|
||||
field, block, opt_onHide, opt_secondaryYOffset) {
|
||||
return showPositionedByRect(
|
||||
getScaledBboxOfBlock(block), field, opt_onHide, opt_secondaryYOffset);
|
||||
};
|
||||
exports.showPositionedByBlock = showPositionedByBlock;
|
||||
|
||||
/**
|
||||
* Shortcut to show and place the drop-down with positioning determined
|
||||
* by a particular field. The primary position will be below the field,
|
||||
* and the secondary position above the field. Drop-down will be
|
||||
* constrained to the block's workspace.
|
||||
* @param {!Field} field The field to position the dropdown against.
|
||||
* @param {Function=} opt_onHide Optional callback for when the drop-down is
|
||||
* hidden.
|
||||
* @param {number=} opt_secondaryYOffset Optional Y offset for above-block
|
||||
* positioning.
|
||||
* @return {boolean} True if the menu rendered below block; false if above.
|
||||
*/
|
||||
const showPositionedByField = function(
|
||||
field, opt_onHide, opt_secondaryYOffset) {
|
||||
positionToField = true;
|
||||
return showPositionedByRect(
|
||||
getScaledBboxOfField(field), field, opt_onHide, opt_secondaryYOffset);
|
||||
};
|
||||
exports.showPositionedByField = showPositionedByField;
|
||||
|
||||
/**
|
||||
* Get the scaled bounding box of a block.
|
||||
* @param {!BlockSvg} block The block.
|
||||
* @return {!Rect} The scaled bounding box of the block.
|
||||
*/
|
||||
const getScaledBboxOfBlock = function(block) {
|
||||
const blockSvg = block.getSvgRoot();
|
||||
const bBox = blockSvg.getBBox();
|
||||
const scale = block.workspace.scale;
|
||||
const scaledHeight = bBox.height * scale;
|
||||
const scaledWidth = bBox.width * scale;
|
||||
const xy = style.getPageOffset(blockSvg);
|
||||
return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the scaled bounding box of a field.
|
||||
* @param {!Field} field The field.
|
||||
* @return {!Rect} The scaled bounding box of the field.
|
||||
*/
|
||||
const getScaledBboxOfField = function(field) {
|
||||
const bBox = field.getScaledBBox();
|
||||
return new Rect(bBox.top, bBox.bottom, bBox.left, bBox.right);
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method to show and place the drop-down with positioning determined
|
||||
* by a scaled bounding box. The primary position will be below the rect,
|
||||
* and the secondary position above the rect. Drop-down will be constrained to
|
||||
* the block's workspace.
|
||||
* @param {!Rect} bBox The scaled bounding box.
|
||||
* @param {!Field} field The field to position the dropdown against.
|
||||
* @param {Function=} opt_onHide Optional callback for when the drop-down is
|
||||
* hidden.
|
||||
* @param {number=} opt_secondaryYOffset Optional Y offset for above-block
|
||||
* positioning.
|
||||
* @return {boolean} True if the menu rendered below block; false if above.
|
||||
*/
|
||||
const showPositionedByRect = function(
|
||||
bBox, field, opt_onHide, opt_secondaryYOffset) {
|
||||
// If we can fit it, render below the block.
|
||||
const primaryX = bBox.left + (bBox.right - bBox.left) / 2;
|
||||
const primaryY = bBox.bottom;
|
||||
// If we can't fit it, render above the entire parent block.
|
||||
const secondaryX = primaryX;
|
||||
let secondaryY = bBox.top;
|
||||
if (opt_secondaryYOffset) {
|
||||
secondaryY += opt_secondaryYOffset;
|
||||
}
|
||||
const sourceBlock = /** @type {!BlockSvg} */ (field.getSourceBlock());
|
||||
// Set bounds to main workspace; show the drop-down.
|
||||
let workspace = sourceBlock.workspace;
|
||||
while (workspace.options.parentWorkspace) {
|
||||
workspace =
|
||||
/** @type {!WorkspaceSvg} */ (workspace.options.parentWorkspace);
|
||||
}
|
||||
setBoundsElement(
|
||||
/** @type {?Element} */ (workspace.getParentSvg().parentNode));
|
||||
return show(
|
||||
field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY,
|
||||
opt_onHide);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show and place the drop-down.
|
||||
* The drop-down is placed with an absolute "origin point" (x, y) - i.e.,
|
||||
* the arrow will point at this origin and box will positioned below or above
|
||||
* it. If we can maintain the container bounds at the primary point, the arrow
|
||||
* will point there, and the container will be positioned below it.
|
||||
* If we can't maintain the container bounds at the primary point, fall-back to
|
||||
* the secondary point and position above.
|
||||
* @param {?Object} newOwner The object showing the drop-down
|
||||
* @param {boolean} rtl Right-to-left (true) or left-to-right (false).
|
||||
* @param {number} primaryX Desired origin point x, in absolute px.
|
||||
* @param {number} primaryY Desired origin point y, in absolute px.
|
||||
* @param {number} secondaryX Secondary/alternative origin point x, in absolute
|
||||
* px.
|
||||
* @param {number} secondaryY Secondary/alternative origin point y, in absolute
|
||||
* px.
|
||||
* @param {Function=} opt_onHide Optional callback for when the drop-down is
|
||||
* hidden.
|
||||
* @return {boolean} True if the menu rendered at the primary origin point.
|
||||
* @package
|
||||
*/
|
||||
const show = function(
|
||||
newOwner, rtl, primaryX, primaryY, secondaryX, secondaryY, opt_onHide) {
|
||||
owner = newOwner;
|
||||
onHide = opt_onHide || null;
|
||||
// Set direction.
|
||||
div.style.direction = rtl ? 'rtl' : 'ltr';
|
||||
|
||||
const mainWorkspace =
|
||||
/** @type {!WorkspaceSvg} */ (common.getMainWorkspace());
|
||||
renderedClassName = mainWorkspace.getRenderer().getClassName();
|
||||
themeClassName = mainWorkspace.getTheme().getClassName();
|
||||
dom.addClass(div, renderedClassName);
|
||||
dom.addClass(div, themeClassName);
|
||||
|
||||
// When we change `translate` multiple times in close succession,
|
||||
// Chrome may choose to wait and apply them all at once.
|
||||
// Since we want the translation to initial X, Y to be immediate,
|
||||
// and the translation to final X, Y to be animated,
|
||||
// we saw problems where both would be applied after animation was turned on,
|
||||
// making the dropdown appear to fly in from (0, 0).
|
||||
// Using both `left`, `top` for the initial translation and then `translate`
|
||||
// for the animated transition to final X, Y is a workaround.
|
||||
|
||||
return positionInternal(primaryX, primaryY, secondaryX, secondaryY);
|
||||
};
|
||||
exports.show = show;
|
||||
|
||||
const internal = {};
|
||||
|
||||
/**
|
||||
* Get sizing info about the bounding element.
|
||||
* @return {!BoundsInfo} An object containing size
|
||||
* information about the bounding element (bounding box and width/height).
|
||||
*/
|
||||
internal.getBoundsInfo = function() {
|
||||
const boundPosition = style.getPageOffset(
|
||||
/** @type {!Element} */ (boundsElement));
|
||||
const boundSize = style.getSize(
|
||||
/** @type {!Element} */ (boundsElement));
|
||||
|
||||
return {
|
||||
left: boundPosition.x,
|
||||
right: boundPosition.x + boundSize.width,
|
||||
top: boundPosition.y,
|
||||
bottom: boundPosition.y + boundSize.height,
|
||||
width: boundSize.width,
|
||||
height: boundSize.height,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to position the drop-down and the arrow, maintaining bounds.
|
||||
* See explanation of origin points in show.
|
||||
* @param {number} primaryX Desired origin point x, in absolute px.
|
||||
* @param {number} primaryY Desired origin point y, in absolute px.
|
||||
* @param {number} secondaryX Secondary/alternative origin point x,
|
||||
* in absolute px.
|
||||
* @param {number} secondaryY Secondary/alternative origin point y,
|
||||
* in absolute px.
|
||||
* @return {!PositionMetrics} Various final metrics,
|
||||
* including rendered positions for drop-down and arrow.
|
||||
*/
|
||||
internal.getPositionMetrics = function(
|
||||
primaryX, primaryY, secondaryX, secondaryY) {
|
||||
const boundsInfo = internal.getBoundsInfo();
|
||||
const divSize = style.getSize(
|
||||
/** @type {!Element} */ (div));
|
||||
|
||||
// Can we fit in-bounds below the target?
|
||||
if (primaryY + divSize.height < boundsInfo.bottom) {
|
||||
return getPositionBelowMetrics(primaryX, primaryY, boundsInfo, divSize);
|
||||
}
|
||||
// Can we fit in-bounds above the target?
|
||||
if (secondaryY - divSize.height > boundsInfo.top) {
|
||||
return getPositionAboveMetrics(secondaryX, secondaryY, boundsInfo, divSize);
|
||||
}
|
||||
// Can we fit outside the workspace bounds (but inside the window) below?
|
||||
if (primaryY + divSize.height < document.documentElement.clientHeight) {
|
||||
return getPositionBelowMetrics(primaryX, primaryY, boundsInfo, divSize);
|
||||
}
|
||||
// Can we fit outside the workspace bounds (but inside the window) above?
|
||||
if (secondaryY - divSize.height > document.documentElement.clientTop) {
|
||||
return getPositionAboveMetrics(secondaryX, secondaryY, boundsInfo, divSize);
|
||||
}
|
||||
|
||||
// Last resort, render at top of page.
|
||||
return getPositionTopOfPageMetrics(primaryX, boundsInfo, divSize);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metrics for positioning the div below the source.
|
||||
* @param {number} primaryX Desired origin point x, in absolute px.
|
||||
* @param {number} primaryY Desired origin point y, in absolute px.
|
||||
* @param {!BoundsInfo} boundsInfo An object containing size
|
||||
* information about the bounding element (bounding box and width/height).
|
||||
* @param {!Size} divSize An object containing information about
|
||||
* the size of the DropDownDiv (width & height).
|
||||
* @return {!PositionMetrics} Various final metrics,
|
||||
* including rendered positions for drop-down and arrow.
|
||||
*/
|
||||
const getPositionBelowMetrics = function(
|
||||
primaryX, primaryY, boundsInfo, divSize) {
|
||||
const xCoords =
|
||||
getPositionX(primaryX, boundsInfo.left, boundsInfo.right, divSize.width);
|
||||
|
||||
const arrowY = -(ARROW_SIZE / 2 + BORDER_SIZE);
|
||||
const finalY = primaryY + PADDING_Y;
|
||||
|
||||
return {
|
||||
initialX: xCoords.divX,
|
||||
initialY: primaryY,
|
||||
finalX: xCoords.divX, // X position remains constant during animation.
|
||||
finalY: finalY,
|
||||
arrowX: xCoords.arrowX,
|
||||
arrowY: arrowY,
|
||||
arrowAtTop: true,
|
||||
arrowVisible: true,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metrics for positioning the div above the source.
|
||||
* @param {number} secondaryX Secondary/alternative origin point x,
|
||||
* in absolute px.
|
||||
* @param {number} secondaryY Secondary/alternative origin point y,
|
||||
* in absolute px.
|
||||
* @param {!BoundsInfo} boundsInfo An object containing size
|
||||
* information about the bounding element (bounding box and width/height).
|
||||
* @param {!Size} divSize An object containing information about
|
||||
* the size of the DropDownDiv (width & height).
|
||||
* @return {!PositionMetrics} Various final metrics,
|
||||
* including rendered positions for drop-down and arrow.
|
||||
*/
|
||||
const getPositionAboveMetrics = function(
|
||||
secondaryX, secondaryY, boundsInfo, divSize) {
|
||||
const xCoords = getPositionX(
|
||||
secondaryX, boundsInfo.left, boundsInfo.right, divSize.width);
|
||||
|
||||
const arrowY = divSize.height - (BORDER_SIZE * 2) - (ARROW_SIZE / 2);
|
||||
const finalY = secondaryY - divSize.height - PADDING_Y;
|
||||
const initialY = secondaryY - divSize.height; // No padding on Y.
|
||||
|
||||
return {
|
||||
initialX: xCoords.divX,
|
||||
initialY: initialY,
|
||||
finalX: xCoords.divX, // X position remains constant during animation.
|
||||
finalY: finalY,
|
||||
arrowX: xCoords.arrowX,
|
||||
arrowY: arrowY,
|
||||
arrowAtTop: false,
|
||||
arrowVisible: true,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metrics for positioning the div at the top of the page.
|
||||
* @param {number} sourceX Desired origin point x, in absolute px.
|
||||
* @param {!BoundsInfo} boundsInfo An object containing size
|
||||
* information about the bounding element (bounding box and width/height).
|
||||
* @param {!Size} divSize An object containing information about
|
||||
* the size of the DropDownDiv (width & height).
|
||||
* @return {!PositionMetrics} Various final metrics,
|
||||
* including rendered positions for drop-down and arrow.
|
||||
*/
|
||||
const getPositionTopOfPageMetrics = function(sourceX, boundsInfo, divSize) {
|
||||
const xCoords =
|
||||
getPositionX(sourceX, boundsInfo.left, boundsInfo.right, divSize.width);
|
||||
|
||||
// No need to provide arrow-specific information because it won't be visible.
|
||||
return {
|
||||
initialX: xCoords.divX,
|
||||
initialY: 0,
|
||||
finalX: xCoords.divX, // X position remains constant during animation.
|
||||
finalY: 0, // Y position remains constant during animation.
|
||||
arrowAtTop: null,
|
||||
arrowX: null,
|
||||
arrowY: null,
|
||||
arrowVisible: false,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the x positions for the left side of the DropDownDiv and the arrow,
|
||||
* accounting for the bounds of the workspace.
|
||||
* @param {number} sourceX Desired origin point x, in absolute px.
|
||||
* @param {number} boundsLeft The left edge of the bounding element, in
|
||||
* absolute px.
|
||||
* @param {number} boundsRight The right edge of the bounding element, in
|
||||
* absolute px.
|
||||
* @param {number} divWidth The width of the div in px.
|
||||
* @return {{divX: number, arrowX: number}} An object containing metrics for
|
||||
* the x positions of the left side of the DropDownDiv and the arrow.
|
||||
* @package
|
||||
*/
|
||||
const getPositionX = function(sourceX, boundsLeft, boundsRight, divWidth) {
|
||||
let divX = sourceX;
|
||||
// Offset the topLeft coord so that the dropdowndiv is centered.
|
||||
divX -= divWidth / 2;
|
||||
// Fit the dropdowndiv within the bounds of the workspace.
|
||||
divX = math.clamp(boundsLeft, divX, boundsRight - divWidth);
|
||||
|
||||
let arrowX = sourceX;
|
||||
// Offset the arrow coord so that the arrow is centered.
|
||||
arrowX -= ARROW_SIZE / 2;
|
||||
// Convert the arrow position to be relative to the top left of the div.
|
||||
let relativeArrowX = arrowX - divX;
|
||||
const horizPadding = ARROW_HORIZONTAL_PADDING;
|
||||
// Clamp the arrow position so that it stays attached to the dropdowndiv.
|
||||
relativeArrowX = math.clamp(
|
||||
horizPadding, relativeArrowX, divWidth - horizPadding - ARROW_SIZE);
|
||||
|
||||
return {arrowX: relativeArrowX, divX: divX};
|
||||
};
|
||||
exports.getPositionX = getPositionX;
|
||||
|
||||
/**
|
||||
* Is the container visible?
|
||||
* @return {boolean} True if visible.
|
||||
*/
|
||||
const isVisible = function() {
|
||||
return !!owner;
|
||||
};
|
||||
exports.isVisible = isVisible;
|
||||
|
||||
/**
|
||||
* Hide the menu only if it is owned by the provided object.
|
||||
* @param {?Object} divOwner Object which must be owning the drop-down to hide.
|
||||
* @param {boolean=} opt_withoutAnimation True if we should hide the dropdown
|
||||
* without animating.
|
||||
* @return {boolean} True if hidden.
|
||||
*/
|
||||
const hideIfOwner = function(divOwner, opt_withoutAnimation) {
|
||||
if (owner === divOwner) {
|
||||
if (opt_withoutAnimation) {
|
||||
hideWithoutAnimation();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
exports.hideIfOwner = hideIfOwner;
|
||||
|
||||
/**
|
||||
* Hide the menu, triggering animation.
|
||||
*/
|
||||
const hide = function() {
|
||||
// Start the animation by setting the translation and fading out.
|
||||
// Reset to (initialX, initialY) - i.e., no translation.
|
||||
div.style.transform = 'translate(0, 0)';
|
||||
div.style.opacity = 0;
|
||||
// Finish animation - reset all values to default.
|
||||
animateOutTimer = setTimeout(function() {
|
||||
hideWithoutAnimation();
|
||||
}, ANIMATION_TIME * 1000);
|
||||
if (onHide) {
|
||||
onHide();
|
||||
onHide = null;
|
||||
}
|
||||
};
|
||||
exports.hide = hide;
|
||||
|
||||
/**
|
||||
* Hide the menu, without animation.
|
||||
*/
|
||||
const hideWithoutAnimation = function() {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (animateOutTimer) {
|
||||
clearTimeout(animateOutTimer);
|
||||
}
|
||||
|
||||
// Reset style properties in case this gets called directly
|
||||
// instead of hide() - see discussion on #2551.
|
||||
div.style.transform = '';
|
||||
div.style.left = '';
|
||||
div.style.top = '';
|
||||
div.style.opacity = 0;
|
||||
div.style.display = 'none';
|
||||
div.style.backgroundColor = '';
|
||||
div.style.borderColor = '';
|
||||
|
||||
if (onHide) {
|
||||
onHide();
|
||||
onHide = null;
|
||||
}
|
||||
clearContent();
|
||||
owner = null;
|
||||
|
||||
if (renderedClassName) {
|
||||
dom.removeClass(div, renderedClassName);
|
||||
renderedClassName = '';
|
||||
}
|
||||
if (themeClassName) {
|
||||
dom.removeClass(div, themeClassName);
|
||||
themeClassName = '';
|
||||
}
|
||||
(/** @type {!WorkspaceSvg} */ (common.getMainWorkspace())).markFocused();
|
||||
};
|
||||
exports.hideWithoutAnimation = hideWithoutAnimation;
|
||||
|
||||
/**
|
||||
* Set the dropdown div's position.
|
||||
* @param {number} primaryX Desired origin point x, in absolute px.
|
||||
* @param {number} primaryY Desired origin point y, in absolute px.
|
||||
* @param {number} secondaryX Secondary/alternative origin point x,
|
||||
* in absolute px.
|
||||
* @param {number} secondaryY Secondary/alternative origin point y,
|
||||
* in absolute px.
|
||||
* @return {boolean} True if the menu rendered at the primary origin point.
|
||||
*/
|
||||
const positionInternal = function(primaryX, primaryY, secondaryX, secondaryY) {
|
||||
const metrics =
|
||||
internal.getPositionMetrics(primaryX, primaryY, secondaryX, secondaryY);
|
||||
|
||||
// Update arrow CSS.
|
||||
if (metrics.arrowVisible) {
|
||||
arrow.style.display = '';
|
||||
arrow.style.transform = 'translate(' + metrics.arrowX + 'px,' +
|
||||
metrics.arrowY + 'px) rotate(45deg)';
|
||||
arrow.setAttribute(
|
||||
'class',
|
||||
metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' :
|
||||
'blocklyDropDownArrow blocklyArrowBottom');
|
||||
} else {
|
||||
arrow.style.display = 'none';
|
||||
}
|
||||
|
||||
const initialX = Math.floor(metrics.initialX);
|
||||
const initialY = Math.floor(metrics.initialY);
|
||||
const finalX = Math.floor(metrics.finalX);
|
||||
const finalY = Math.floor(metrics.finalY);
|
||||
|
||||
// First apply initial translation.
|
||||
div.style.left = initialX + 'px';
|
||||
div.style.top = initialY + 'px';
|
||||
|
||||
// Show the div.
|
||||
div.style.display = 'block';
|
||||
div.style.opacity = 1;
|
||||
// Add final translate, animated through `transition`.
|
||||
// Coordinates are relative to (initialX, initialY),
|
||||
// where the drop-down is absolutely positioned.
|
||||
const dx = finalX - initialX;
|
||||
const dy = finalY - initialY;
|
||||
div.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
|
||||
|
||||
return !!metrics.arrowAtTop;
|
||||
};
|
||||
|
||||
/**
|
||||
* Repositions the dropdownDiv on window resize. If it doesn't know how to
|
||||
* calculate the new position, it will just hide it instead.
|
||||
* @package
|
||||
*/
|
||||
const repositionForWindowResize = function() {
|
||||
// This condition mainly catches the dropdown div when it is being used as a
|
||||
// dropdown. It is important not to close it in this case because on Android,
|
||||
// when a field is focused, the soft keyboard opens triggering a window resize
|
||||
// event and we want the dropdown div to stick around so users can type into
|
||||
// it.
|
||||
if (owner) {
|
||||
const field = /** @type {!Field} */ (owner);
|
||||
const block = /** @type {!BlockSvg} */ (field.getSourceBlock());
|
||||
const bBox = positionToField ? getScaledBboxOfField(field) :
|
||||
getScaledBboxOfBlock(block);
|
||||
// If we can fit it, render below the block.
|
||||
const primaryX = bBox.left + (bBox.right - bBox.left) / 2;
|
||||
const primaryY = bBox.bottom;
|
||||
// If we can't fit it, render above the entire parent block.
|
||||
const secondaryX = primaryX;
|
||||
const secondaryY = bBox.top;
|
||||
positionInternal(primaryX, primaryY, secondaryX, secondaryY);
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
};
|
||||
exports.repositionForWindowResize = repositionForWindowResize;
|
||||
|
||||
exports.TEST_ONLY = internal;
|
||||
@@ -0,0 +1,687 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* A div that floats on top of the workspace, for drop-down menus.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.dropDownDiv');
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as common from './common.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import type {Field} from './field.js';
|
||||
import * as math from './utils/math.js';
|
||||
import {Rect} from './utils/rect.js';
|
||||
import type {Size} from './utils/size.js';
|
||||
import * as style from './utils/style.js';
|
||||
import type {WorkspaceSvg} from './workspace_svg.js';
|
||||
|
||||
|
||||
/**
|
||||
* Arrow size in px. Should match the value in CSS
|
||||
* (need to position pre-render).
|
||||
*/
|
||||
export const ARROW_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Drop-down border size in px. Should match the value in CSS (need to position
|
||||
* the arrow).
|
||||
*/
|
||||
export const BORDER_SIZE = 1;
|
||||
|
||||
/**
|
||||
* Amount the arrow must be kept away from the edges of the main drop-down div,
|
||||
* in px.
|
||||
*/
|
||||
export const ARROW_HORIZONTAL_PADDING = 12;
|
||||
|
||||
/** Amount drop-downs should be padded away from the source, in px. */
|
||||
export const PADDING_Y = 16;
|
||||
|
||||
/** Length of animations in seconds. */
|
||||
export const ANIMATION_TIME = 0.25;
|
||||
|
||||
/**
|
||||
* Timer for animation out, to be cleared if we need to immediately hide
|
||||
* without disrupting new shows.
|
||||
*/
|
||||
let animateOutTimer: ReturnType<typeof setTimeout>|null = null;
|
||||
|
||||
/** Callback for when the drop-down is hidden. */
|
||||
let onHide: Function|null = null;
|
||||
|
||||
/** A class name representing the current owner's workspace renderer. */
|
||||
let renderedClassName = '';
|
||||
|
||||
/** A class name representing the current owner's workspace theme. */
|
||||
let themeClassName = '';
|
||||
|
||||
/** The content element. */
|
||||
let div: HTMLDivElement;
|
||||
|
||||
/** The content element. */
|
||||
let content: HTMLDivElement;
|
||||
|
||||
/** The arrow element. */
|
||||
let arrow: HTMLDivElement;
|
||||
|
||||
/**
|
||||
* Drop-downs will appear within the bounds of this element if possible.
|
||||
* Set in setBoundsElement.
|
||||
*/
|
||||
let boundsElement: Element|null = null;
|
||||
|
||||
/** The object currently using the drop-down. */
|
||||
let owner: Field|null = null;
|
||||
|
||||
/** Whether the dropdown was positioned to a field or the source block. */
|
||||
let positionToField: boolean|null = null;
|
||||
|
||||
/**
|
||||
* Dropdown bounds info object used to encapsulate sizing information about a
|
||||
* bounding element (bounding box and width/height).
|
||||
*/
|
||||
export interface BoundsInfo {
|
||||
top: number;
|
||||
left: number;
|
||||
bottom: number;
|
||||
right: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** Dropdown position metrics. */
|
||||
export interface PositionMetrics {
|
||||
initialX: number;
|
||||
initialY: number;
|
||||
finalX: number;
|
||||
finalY: number;
|
||||
arrowX: number|null;
|
||||
arrowY: number|null;
|
||||
arrowAtTop: boolean|null;
|
||||
arrowVisible: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and insert the DOM element for this div.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function createDom() {
|
||||
if (div) {
|
||||
return; // Already created.
|
||||
}
|
||||
div = document.createElement('div');
|
||||
div.className = 'blocklyDropDownDiv';
|
||||
const parentDiv = common.getParentContainer() || document.body;
|
||||
parentDiv.appendChild(div);
|
||||
|
||||
content = document.createElement('div');
|
||||
content.className = 'blocklyDropDownContent';
|
||||
div.appendChild(content);
|
||||
|
||||
arrow = document.createElement('div');
|
||||
arrow.className = 'blocklyDropDownArrow';
|
||||
div.appendChild(arrow);
|
||||
|
||||
div.style.opacity = '0';
|
||||
// Transition animation for transform: translate() and opacity.
|
||||
div.style.transition = 'transform ' + ANIMATION_TIME + 's, ' +
|
||||
'opacity ' + ANIMATION_TIME + 's';
|
||||
|
||||
// Handle focusin/out events to add a visual indicator when
|
||||
// a child is focused or blurred.
|
||||
div.addEventListener('focusin', function() {
|
||||
dom.addClass(div, 'blocklyFocused');
|
||||
});
|
||||
div.addEventListener('focusout', function() {
|
||||
dom.removeClass(div, 'blocklyFocused');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an element to maintain bounds within. Drop-downs will appear
|
||||
* within the box of this element if possible.
|
||||
*
|
||||
* @param boundsElem Element to bind drop-down to.
|
||||
*/
|
||||
export function setBoundsElement(boundsElem: Element|null) {
|
||||
boundsElement = boundsElem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the div for inserting content into the drop-down.
|
||||
*
|
||||
* @returns Div to populate with content.
|
||||
*/
|
||||
export function getContentDiv(): Element {
|
||||
return content;
|
||||
}
|
||||
|
||||
/** Clear the content of the drop-down. */
|
||||
export function clearContent() {
|
||||
content.textContent = '';
|
||||
content.style.width = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the colour for the drop-down.
|
||||
*
|
||||
* @param backgroundColour Any CSS colour for the background.
|
||||
* @param borderColour Any CSS colour for the border.
|
||||
*/
|
||||
export function setColour(backgroundColour: string, borderColour: string) {
|
||||
div.style.backgroundColor = backgroundColour;
|
||||
div.style.borderColor = borderColour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to show and place the drop-down with positioning determined
|
||||
* by a particular block. The primary position will be below the block,
|
||||
* and the secondary position above the block. Drop-down will be
|
||||
* constrained to the block's workspace.
|
||||
*
|
||||
* @param field The field showing the drop-down.
|
||||
* @param block Block to position the drop-down around.
|
||||
* @param opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @param opt_secondaryYOffset Optional Y offset for above-block positioning.
|
||||
* @returns True if the menu rendered below block; false if above.
|
||||
*/
|
||||
export function showPositionedByBlock(
|
||||
field: Field, block: BlockSvg, opt_onHide?: Function,
|
||||
opt_secondaryYOffset?: number): boolean {
|
||||
return showPositionedByRect(
|
||||
getScaledBboxOfBlock(block), field, opt_onHide, opt_secondaryYOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to show and place the drop-down with positioning determined
|
||||
* by a particular field. The primary position will be below the field,
|
||||
* and the secondary position above the field. Drop-down will be
|
||||
* constrained to the block's workspace.
|
||||
*
|
||||
* @param field The field to position the dropdown against.
|
||||
* @param opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @param opt_secondaryYOffset Optional Y offset for above-block positioning.
|
||||
* @returns True if the menu rendered below block; false if above.
|
||||
*/
|
||||
export function showPositionedByField(
|
||||
field: Field, opt_onHide?: Function,
|
||||
opt_secondaryYOffset?: number): boolean {
|
||||
positionToField = true;
|
||||
return showPositionedByRect(
|
||||
getScaledBboxOfField(field), field, opt_onHide, opt_secondaryYOffset);
|
||||
}
|
||||
/**
|
||||
* Get the scaled bounding box of a block.
|
||||
*
|
||||
* @param block The block.
|
||||
* @returns The scaled bounding box of the block.
|
||||
*/
|
||||
function getScaledBboxOfBlock(block: BlockSvg): Rect {
|
||||
const blockSvg = block.getSvgRoot();
|
||||
const scale = block.workspace.scale;
|
||||
const scaledHeight = block.height * scale;
|
||||
const scaledWidth = block.width * scale;
|
||||
const xy = style.getPageOffset(blockSvg);
|
||||
return new Rect(xy.y, xy.y + scaledHeight, xy.x, xy.x + scaledWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the scaled bounding box of a field.
|
||||
*
|
||||
* @param field The field.
|
||||
* @returns The scaled bounding box of the field.
|
||||
*/
|
||||
function getScaledBboxOfField(field: Field): Rect {
|
||||
const bBox = field.getScaledBBox();
|
||||
return new Rect(bBox.top, bBox.bottom, bBox.left, bBox.right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to show and place the drop-down with positioning determined
|
||||
* by a scaled bounding box. The primary position will be below the rect,
|
||||
* and the secondary position above the rect. Drop-down will be constrained to
|
||||
* the block's workspace.
|
||||
*
|
||||
* @param bBox The scaled bounding box.
|
||||
* @param field The field to position the dropdown against.
|
||||
* @param opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @param opt_secondaryYOffset Optional Y offset for above-block positioning.
|
||||
* @returns True if the menu rendered below block; false if above.
|
||||
*/
|
||||
function showPositionedByRect(
|
||||
bBox: Rect, field: Field, opt_onHide?: Function,
|
||||
opt_secondaryYOffset?: number): boolean {
|
||||
// If we can fit it, render below the block.
|
||||
const primaryX = bBox.left + (bBox.right - bBox.left) / 2;
|
||||
const primaryY = bBox.bottom;
|
||||
// If we can't fit it, render above the entire parent block.
|
||||
const secondaryX = primaryX;
|
||||
let secondaryY = bBox.top;
|
||||
if (opt_secondaryYOffset) {
|
||||
secondaryY += opt_secondaryYOffset;
|
||||
}
|
||||
const sourceBlock = field.getSourceBlock() as BlockSvg;
|
||||
// Set bounds to main workspace; show the drop-down.
|
||||
let workspace = sourceBlock.workspace;
|
||||
while (workspace.options.parentWorkspace) {
|
||||
workspace = workspace.options.parentWorkspace;
|
||||
}
|
||||
setBoundsElement(workspace.getParentSvg().parentNode as Element | null);
|
||||
return show(
|
||||
field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY,
|
||||
opt_onHide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show and place the drop-down.
|
||||
* The drop-down is placed with an absolute "origin point" (x, y) - i.e.,
|
||||
* the arrow will point at this origin and box will positioned below or above
|
||||
* it. If we can maintain the container bounds at the primary point, the arrow
|
||||
* will point there, and the container will be positioned below it.
|
||||
* If we can't maintain the container bounds at the primary point, fall-back to
|
||||
* the secondary point and position above.
|
||||
*
|
||||
* @param newOwner The object showing the drop-down
|
||||
* @param rtl Right-to-left (true) or left-to-right (false).
|
||||
* @param primaryX Desired origin point x, in absolute px.
|
||||
* @param primaryY Desired origin point y, in absolute px.
|
||||
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
||||
* @param secondaryY Secondary/alternative origin point y, in absolute px.
|
||||
* @param opt_onHide Optional callback for when the drop-down is hidden.
|
||||
* @returns True if the menu rendered at the primary origin point.
|
||||
* @internal
|
||||
*/
|
||||
export function show(
|
||||
newOwner: Field, rtl: boolean, primaryX: number, primaryY: number,
|
||||
secondaryX: number, secondaryY: number, opt_onHide?: Function): boolean {
|
||||
owner = newOwner;
|
||||
onHide = opt_onHide || null;
|
||||
// Set direction.
|
||||
div.style.direction = rtl ? 'rtl' : 'ltr';
|
||||
|
||||
const mainWorkspace = common.getMainWorkspace() as WorkspaceSvg;
|
||||
renderedClassName = mainWorkspace.getRenderer().getClassName();
|
||||
themeClassName = mainWorkspace.getTheme().getClassName();
|
||||
if (renderedClassName) {
|
||||
dom.addClass(div, renderedClassName);
|
||||
}
|
||||
if (themeClassName) {
|
||||
dom.addClass(div, themeClassName);
|
||||
}
|
||||
|
||||
// When we change `translate` multiple times in close succession,
|
||||
// Chrome may choose to wait and apply them all at once.
|
||||
// Since we want the translation to initial X, Y to be immediate,
|
||||
// and the translation to final X, Y to be animated,
|
||||
// we saw problems where both would be applied after animation was turned on,
|
||||
// making the dropdown appear to fly in from (0, 0).
|
||||
// Using both `left`, `top` for the initial translation and then `translate`
|
||||
// for the animated transition to final X, Y is a workaround.
|
||||
return positionInternal(primaryX, primaryY, secondaryX, secondaryY);
|
||||
}
|
||||
|
||||
const internal = {
|
||||
/**
|
||||
* Get sizing info about the bounding element.
|
||||
*
|
||||
* @returns An object containing size information about the bounding element
|
||||
* (bounding box and width/height).
|
||||
*/
|
||||
getBoundsInfo: function(): BoundsInfo {
|
||||
const boundPosition = style.getPageOffset(boundsElement as Element);
|
||||
const boundSize = style.getSize(boundsElement as Element);
|
||||
|
||||
return {
|
||||
left: boundPosition.x,
|
||||
right: boundPosition.x + boundSize.width,
|
||||
top: boundPosition.y,
|
||||
bottom: boundPosition.y + boundSize.height,
|
||||
width: boundSize.width,
|
||||
height: boundSize.height,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to position the drop-down and the arrow, maintaining bounds.
|
||||
* See explanation of origin points in show.
|
||||
*
|
||||
* @param primaryX Desired origin point x, in absolute px.
|
||||
* @param primaryY Desired origin point y, in absolute px.
|
||||
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
||||
* @param secondaryY Secondary/alternative origin point y, in absolute px.
|
||||
* @returns Various final metrics, including rendered positions for drop-down
|
||||
* and arrow.
|
||||
*/
|
||||
getPositionMetrics: function(
|
||||
primaryX: number, primaryY: number, secondaryX: number,
|
||||
secondaryY: number): PositionMetrics {
|
||||
const boundsInfo = internal.getBoundsInfo();
|
||||
const divSize = style.getSize(div as Element);
|
||||
|
||||
// Can we fit in-bounds below the target?
|
||||
if (primaryY + divSize.height < boundsInfo.bottom) {
|
||||
return getPositionBelowMetrics(primaryX, primaryY, boundsInfo, divSize);
|
||||
}
|
||||
// Can we fit in-bounds above the target?
|
||||
if (secondaryY - divSize.height > boundsInfo.top) {
|
||||
return getPositionAboveMetrics(
|
||||
secondaryX, secondaryY, boundsInfo, divSize);
|
||||
}
|
||||
// Can we fit outside the workspace bounds (but inside the window)
|
||||
// below?
|
||||
if (primaryY + divSize.height < document.documentElement.clientHeight) {
|
||||
return getPositionBelowMetrics(primaryX, primaryY, boundsInfo, divSize);
|
||||
}
|
||||
// Can we fit outside the workspace bounds (but inside the window)
|
||||
// above?
|
||||
if (secondaryY - divSize.height > document.documentElement.clientTop) {
|
||||
return getPositionAboveMetrics(
|
||||
secondaryX, secondaryY, boundsInfo, divSize);
|
||||
}
|
||||
|
||||
// Last resort, render at top of page.
|
||||
return getPositionTopOfPageMetrics(primaryX, boundsInfo, divSize);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metrics for positioning the div below the source.
|
||||
*
|
||||
* @param primaryX Desired origin point x, in absolute px.
|
||||
* @param primaryY Desired origin point y, in absolute px.
|
||||
* @param boundsInfo An object containing size information about the bounding
|
||||
* element (bounding box and width/height).
|
||||
* @param divSize An object containing information about the size of the
|
||||
* DropDownDiv (width & height).
|
||||
* @returns Various final metrics, including rendered positions for drop-down
|
||||
* and arrow.
|
||||
*/
|
||||
function getPositionBelowMetrics(
|
||||
primaryX: number, primaryY: number, boundsInfo: BoundsInfo,
|
||||
divSize: Size): PositionMetrics {
|
||||
const xCoords =
|
||||
getPositionX(primaryX, boundsInfo.left, boundsInfo.right, divSize.width);
|
||||
|
||||
const arrowY = -(ARROW_SIZE / 2 + BORDER_SIZE);
|
||||
const finalY = primaryY + PADDING_Y;
|
||||
|
||||
return {
|
||||
initialX: xCoords.divX,
|
||||
initialY: primaryY,
|
||||
finalX: xCoords.divX, // X position remains constant during animation.
|
||||
finalY,
|
||||
arrowX: xCoords.arrowX,
|
||||
arrowY,
|
||||
arrowAtTop: true,
|
||||
arrowVisible: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metrics for positioning the div above the source.
|
||||
*
|
||||
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
||||
* @param secondaryY Secondary/alternative origin point y, in absolute px.
|
||||
* @param boundsInfo An object containing size information about the bounding
|
||||
* element (bounding box and width/height).
|
||||
* @param divSize An object containing information about the size of the
|
||||
* DropDownDiv (width & height).
|
||||
* @returns Various final metrics, including rendered positions for drop-down
|
||||
* and arrow.
|
||||
*/
|
||||
function getPositionAboveMetrics(
|
||||
secondaryX: number, secondaryY: number, boundsInfo: BoundsInfo,
|
||||
divSize: Size): PositionMetrics {
|
||||
const xCoords = getPositionX(
|
||||
secondaryX, boundsInfo.left, boundsInfo.right, divSize.width);
|
||||
|
||||
const arrowY = divSize.height - BORDER_SIZE * 2 - ARROW_SIZE / 2;
|
||||
const finalY = secondaryY - divSize.height - PADDING_Y;
|
||||
const initialY = secondaryY - divSize.height; // No padding on Y.
|
||||
|
||||
return {
|
||||
initialX: xCoords.divX,
|
||||
initialY,
|
||||
finalX: xCoords.divX, // X position remains constant during animation.
|
||||
finalY,
|
||||
arrowX: xCoords.arrowX,
|
||||
arrowY,
|
||||
arrowAtTop: false,
|
||||
arrowVisible: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metrics for positioning the div at the top of the page.
|
||||
*
|
||||
* @param sourceX Desired origin point x, in absolute px.
|
||||
* @param boundsInfo An object containing size information about the bounding
|
||||
* element (bounding box and width/height).
|
||||
* @param divSize An object containing information about the size of the
|
||||
* DropDownDiv (width & height).
|
||||
* @returns Various final metrics, including rendered positions for drop-down
|
||||
* and arrow.
|
||||
*/
|
||||
function getPositionTopOfPageMetrics(
|
||||
sourceX: number, boundsInfo: BoundsInfo, divSize: Size): PositionMetrics {
|
||||
const xCoords =
|
||||
getPositionX(sourceX, boundsInfo.left, boundsInfo.right, divSize.width);
|
||||
|
||||
// No need to provide arrow-specific information because it won't be visible.
|
||||
return {
|
||||
initialX: xCoords.divX,
|
||||
initialY: 0,
|
||||
finalX: xCoords.divX, // X position remains constant during animation.
|
||||
finalY: 0, // Y position remains constant during animation.
|
||||
arrowAtTop: null,
|
||||
arrowX: null,
|
||||
arrowY: null,
|
||||
arrowVisible: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x positions for the left side of the DropDownDiv and the arrow,
|
||||
* accounting for the bounds of the workspace.
|
||||
*
|
||||
* @param sourceX Desired origin point x, in absolute px.
|
||||
* @param boundsLeft The left edge of the bounding element, in absolute px.
|
||||
* @param boundsRight The right edge of the bounding element, in absolute px.
|
||||
* @param divWidth The width of the div in px.
|
||||
* @returns An object containing metrics for the x positions of the left side of
|
||||
* the DropDownDiv and the arrow.
|
||||
* @internal
|
||||
*/
|
||||
export function getPositionX(
|
||||
sourceX: number, boundsLeft: number, boundsRight: number,
|
||||
divWidth: number): {divX: number, arrowX: number} {
|
||||
let divX = sourceX;
|
||||
// Offset the topLeft coord so that the dropdowndiv is centered.
|
||||
divX -= divWidth / 2;
|
||||
// Fit the dropdowndiv within the bounds of the workspace.
|
||||
divX = math.clamp(boundsLeft, divX, boundsRight - divWidth);
|
||||
|
||||
let arrowX = sourceX;
|
||||
// Offset the arrow coord so that the arrow is centered.
|
||||
arrowX -= ARROW_SIZE / 2;
|
||||
// Convert the arrow position to be relative to the top left of the div.
|
||||
let relativeArrowX = arrowX - divX;
|
||||
const horizPadding = ARROW_HORIZONTAL_PADDING;
|
||||
// Clamp the arrow position so that it stays attached to the dropdowndiv.
|
||||
relativeArrowX = math.clamp(
|
||||
horizPadding, relativeArrowX, divWidth - horizPadding - ARROW_SIZE);
|
||||
|
||||
return {arrowX: relativeArrowX, divX};
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the container visible?
|
||||
*
|
||||
* @returns True if visible.
|
||||
*/
|
||||
export function isVisible(): boolean {
|
||||
return !!owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the menu only if it is owned by the provided object.
|
||||
*
|
||||
* @param divOwner Object which must be owning the drop-down to hide.
|
||||
* @param opt_withoutAnimation True if we should hide the dropdown without
|
||||
* animating.
|
||||
* @returns True if hidden.
|
||||
*/
|
||||
export function hideIfOwner(
|
||||
divOwner: Field, opt_withoutAnimation?: boolean): boolean {
|
||||
if (owner === divOwner) {
|
||||
if (opt_withoutAnimation) {
|
||||
hideWithoutAnimation();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Hide the menu, triggering animation. */
|
||||
export function hide() {
|
||||
// Start the animation by setting the translation and fading out.
|
||||
// Reset to (initialX, initialY) - i.e., no translation.
|
||||
div.style.transform = 'translate(0, 0)';
|
||||
div.style.opacity = '0';
|
||||
// Finish animation - reset all values to default.
|
||||
animateOutTimer = setTimeout(function() {
|
||||
hideWithoutAnimation();
|
||||
}, ANIMATION_TIME * 1000);
|
||||
if (onHide) {
|
||||
onHide();
|
||||
onHide = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the menu, without animation. */
|
||||
export function hideWithoutAnimation() {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (animateOutTimer) {
|
||||
clearTimeout(animateOutTimer);
|
||||
}
|
||||
|
||||
// Reset style properties in case this gets called directly
|
||||
// instead of hide() - see discussion on #2551.
|
||||
div.style.transform = '';
|
||||
div.style.left = '';
|
||||
div.style.top = '';
|
||||
div.style.opacity = '0';
|
||||
div.style.display = 'none';
|
||||
div.style.backgroundColor = '';
|
||||
div.style.borderColor = '';
|
||||
|
||||
if (onHide) {
|
||||
onHide();
|
||||
onHide = null;
|
||||
}
|
||||
clearContent();
|
||||
owner = null;
|
||||
|
||||
if (renderedClassName) {
|
||||
dom.removeClass(div, renderedClassName);
|
||||
renderedClassName = '';
|
||||
}
|
||||
if (themeClassName) {
|
||||
dom.removeClass(div, themeClassName);
|
||||
themeClassName = '';
|
||||
}
|
||||
(common.getMainWorkspace() as WorkspaceSvg).markFocused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dropdown div's position.
|
||||
*
|
||||
* @param primaryX Desired origin point x, in absolute px.
|
||||
* @param primaryY Desired origin point y, in absolute px.
|
||||
* @param secondaryX Secondary/alternative origin point x, in absolute px.
|
||||
* @param secondaryY Secondary/alternative origin point y, in absolute px.
|
||||
* @returns True if the menu rendered at the primary origin point.
|
||||
*/
|
||||
function positionInternal(
|
||||
primaryX: number, primaryY: number, secondaryX: number,
|
||||
secondaryY: number): boolean {
|
||||
const metrics =
|
||||
internal.getPositionMetrics(primaryX, primaryY, secondaryX, secondaryY);
|
||||
|
||||
// Update arrow CSS.
|
||||
if (metrics.arrowVisible) {
|
||||
arrow.style.display = '';
|
||||
arrow.style.transform = 'translate(' + metrics.arrowX + 'px,' +
|
||||
metrics.arrowY + 'px) rotate(45deg)';
|
||||
arrow.setAttribute(
|
||||
'class',
|
||||
metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' :
|
||||
'blocklyDropDownArrow blocklyArrowBottom');
|
||||
} else {
|
||||
arrow.style.display = 'none';
|
||||
}
|
||||
|
||||
const initialX = Math.floor(metrics.initialX);
|
||||
const initialY = Math.floor(metrics.initialY);
|
||||
const finalX = Math.floor(metrics.finalX);
|
||||
const finalY = Math.floor(metrics.finalY);
|
||||
|
||||
// First apply initial translation.
|
||||
div.style.left = initialX + 'px';
|
||||
div.style.top = initialY + 'px';
|
||||
|
||||
// Show the div.
|
||||
div.style.display = 'block';
|
||||
div.style.opacity = '1';
|
||||
// Add final translate, animated through `transition`.
|
||||
// Coordinates are relative to (initialX, initialY),
|
||||
// where the drop-down is absolutely positioned.
|
||||
const dx = finalX - initialX;
|
||||
const dy = finalY - initialY;
|
||||
div.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
|
||||
|
||||
return !!metrics.arrowAtTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions the dropdownDiv on window resize. If it doesn't know how to
|
||||
* calculate the new position, it will just hide it instead.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function repositionForWindowResize() {
|
||||
// This condition mainly catches the dropdown div when it is being used as a
|
||||
// dropdown. It is important not to close it in this case because on Android,
|
||||
// when a field is focused, the soft keyboard opens triggering a window resize
|
||||
// event and we want the dropdown div to stick around so users can type into
|
||||
// it.
|
||||
if (owner) {
|
||||
const block = owner.getSourceBlock() as BlockSvg;
|
||||
const bBox = positionToField ? getScaledBboxOfField(owner) :
|
||||
getScaledBboxOfBlock(block);
|
||||
// If we can fit it, render below the block.
|
||||
const primaryX = bBox.left + (bBox.right - bBox.left) / 2;
|
||||
const primaryY = bBox.bottom;
|
||||
// If we can't fit it, render above the entire parent block.
|
||||
const secondaryX = primaryX;
|
||||
const secondaryY = bBox.top;
|
||||
positionInternal(primaryX, primaryY, secondaryX, secondaryY);
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
export const TEST_ONLY = internal;
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Events fired as a result of actions in Blockly's editor.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Events fired as a result of actions in Blockly's editor.
|
||||
* @namespace Blockly.Events
|
||||
*/
|
||||
goog.module('Blockly.Events');
|
||||
|
||||
const deprecation = goog.require('Blockly.utils.deprecation');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
|
||||
const {BlockChange} = goog.require('Blockly.Events.BlockChange');
|
||||
const {BlockCreate} = goog.require('Blockly.Events.BlockCreate');
|
||||
const {BlockDelete} = goog.require('Blockly.Events.BlockDelete');
|
||||
const {BlockDrag} = goog.require('Blockly.Events.BlockDrag');
|
||||
const {BlockMove} = goog.require('Blockly.Events.BlockMove');
|
||||
const {BubbleOpen} = goog.require('Blockly.Events.BubbleOpen');
|
||||
const {Click} = goog.require('Blockly.Events.Click');
|
||||
const {CommentBase} = goog.require('Blockly.Events.CommentBase');
|
||||
const {CommentChange} = goog.require('Blockly.Events.CommentChange');
|
||||
const {CommentCreate} = goog.require('Blockly.Events.CommentCreate');
|
||||
const {CommentDelete} = goog.require('Blockly.Events.CommentDelete');
|
||||
const {CommentMove} = goog.require('Blockly.Events.CommentMove');
|
||||
const {FinishedLoading} = goog.require('Blockly.Events.FinishedLoading');
|
||||
const {MarkerMove} = goog.require('Blockly.Events.MarkerMove');
|
||||
const {Selected} = goog.require('Blockly.Events.Selected');
|
||||
const {ThemeChange} = goog.require('Blockly.Events.ThemeChange');
|
||||
const {ToolboxItemSelect} = goog.require('Blockly.Events.ToolboxItemSelect');
|
||||
const {TrashcanOpen} = goog.require('Blockly.Events.TrashcanOpen');
|
||||
const {UiBase} = goog.require('Blockly.Events.UiBase');
|
||||
const {Ui} = goog.require('Blockly.Events.Ui');
|
||||
const {VarBase} = goog.require('Blockly.Events.VarBase');
|
||||
const {VarCreate} = goog.require('Blockly.Events.VarCreate');
|
||||
const {VarDelete} = goog.require('Blockly.Events.VarDelete');
|
||||
const {VarRename} = goog.require('Blockly.Events.VarRename');
|
||||
const {ViewportChange} = goog.require('Blockly.Events.ViewportChange');
|
||||
|
||||
|
||||
// Events.
|
||||
exports.Abstract = AbstractEvent;
|
||||
exports.BubbleOpen = BubbleOpen;
|
||||
exports.BlockBase = BlockBase;
|
||||
exports.BlockChange = BlockChange;
|
||||
exports.BlockCreate = BlockCreate;
|
||||
exports.BlockDelete = BlockDelete;
|
||||
exports.BlockDrag = BlockDrag;
|
||||
exports.BlockMove = BlockMove;
|
||||
exports.Click = Click;
|
||||
exports.CommentBase = CommentBase;
|
||||
exports.CommentChange = CommentChange;
|
||||
exports.CommentCreate = CommentCreate;
|
||||
exports.CommentDelete = CommentDelete;
|
||||
exports.CommentMove = CommentMove;
|
||||
exports.FinishedLoading = FinishedLoading;
|
||||
exports.MarkerMove = MarkerMove;
|
||||
exports.Selected = Selected;
|
||||
exports.ThemeChange = ThemeChange;
|
||||
exports.ToolboxItemSelect = ToolboxItemSelect;
|
||||
exports.TrashcanOpen = TrashcanOpen;
|
||||
exports.Ui = Ui;
|
||||
exports.UiBase = UiBase;
|
||||
exports.VarBase = VarBase;
|
||||
exports.VarCreate = VarCreate;
|
||||
exports.VarDelete = VarDelete;
|
||||
exports.VarRename = VarRename;
|
||||
exports.ViewportChange = ViewportChange;
|
||||
|
||||
// Event types.
|
||||
exports.BLOCK_CHANGE = eventUtils.BLOCK_CHANGE;
|
||||
exports.BLOCK_CREATE = eventUtils.BLOCK_CREATE;
|
||||
exports.BLOCK_DELETE = eventUtils.BLOCK_DELETE;
|
||||
exports.BLOCK_DRAG = eventUtils.BLOCK_DRAG;
|
||||
exports.BLOCK_MOVE = eventUtils.BLOCK_MOVE;
|
||||
exports.BUBBLE_OPEN = eventUtils.BUBBLE_OPEN;
|
||||
exports.BumpEvent = eventUtils.BumpEvent;
|
||||
exports.BUMP_EVENTS = eventUtils.BUMP_EVENTS;
|
||||
exports.CHANGE = eventUtils.CHANGE;
|
||||
exports.CLICK = eventUtils.CLICK;
|
||||
exports.COMMENT_CHANGE = eventUtils.COMMENT_CHANGE;
|
||||
exports.COMMENT_CREATE = eventUtils.COMMENT_CREATE;
|
||||
exports.COMMENT_DELETE = eventUtils.COMMENT_DELETE;
|
||||
exports.COMMENT_MOVE = eventUtils.COMMENT_MOVE;
|
||||
exports.CREATE = eventUtils.CREATE;
|
||||
exports.DELETE = eventUtils.DELETE;
|
||||
exports.FINISHED_LOADING = eventUtils.FINISHED_LOADING;
|
||||
exports.MARKER_MOVE = eventUtils.MARKER_MOVE;
|
||||
exports.MOVE = eventUtils.MOVE;
|
||||
exports.SELECTED = eventUtils.SELECTED;
|
||||
exports.THEME_CHANGE = eventUtils.THEME_CHANGE;
|
||||
exports.TOOLBOX_ITEM_SELECT = eventUtils.TOOLBOX_ITEM_SELECT;
|
||||
exports.TRASHCAN_OPEN = eventUtils.TRASHCAN_OPEN;
|
||||
exports.UI = eventUtils.UI;
|
||||
exports.VAR_CREATE = eventUtils.VAR_CREATE;
|
||||
exports.VAR_DELETE = eventUtils.VAR_DELETE;
|
||||
exports.VAR_RENAME = eventUtils.VAR_RENAME;
|
||||
exports.VIEWPORT_CHANGE = eventUtils.VIEWPORT_CHANGE;
|
||||
|
||||
// Event utils.
|
||||
exports.clearPendingUndo = eventUtils.clearPendingUndo;
|
||||
exports.disable = eventUtils.disable;
|
||||
exports.enable = eventUtils.enable;
|
||||
exports.filter = eventUtils.filter;
|
||||
exports.fire = eventUtils.fire;
|
||||
exports.fromJson = eventUtils.fromJson;
|
||||
exports.getDescendantIds = eventUtils.getDescendantIds;
|
||||
exports.get = eventUtils.get;
|
||||
exports.getGroup = eventUtils.getGroup;
|
||||
exports.getRecordUndo = eventUtils.getRecordUndo;
|
||||
exports.isEnabled = eventUtils.isEnabled;
|
||||
exports.setGroup = eventUtils.setGroup;
|
||||
exports.setRecordUndo = eventUtils.setRecordUndo;
|
||||
exports.disableOrphans = eventUtils.disableOrphans;
|
||||
|
||||
Object.defineProperties(exports, {
|
||||
/**
|
||||
* Sets whether the next event should be added to the undo stack.
|
||||
* @name Blockly.Evenents.recordUndo
|
||||
* @type {boolean}
|
||||
* @deprecated Use Blockly.Events.getRecordUndo() and
|
||||
* .setRecordUndo(). (September 2021)
|
||||
* @suppress {checkTypes}
|
||||
*/
|
||||
recordUndo: {
|
||||
get: function() {
|
||||
deprecation.warn(
|
||||
'Blockly.Events.recordUndo', 'September 2021', 'September 2022',
|
||||
'Blockly.Events.getRecordUndo()');
|
||||
return eventUtils.getRecordUndo();
|
||||
},
|
||||
set: function(record) {
|
||||
deprecation.warn(
|
||||
'Blockly.Events.recordUndo', 'September 2021', 'September 2022',
|
||||
'Blockly.Events.setRecordUndo()');
|
||||
eventUtils.setRecordUndo(record);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016 Google LLC
|
||||
* 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 {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import {BlockChange, BlockChangeJson} from './events_block_change.js';
|
||||
import {BlockCreate, BlockCreateJson} from './events_block_create.js';
|
||||
import {BlockDelete, BlockDeleteJson} from './events_block_delete.js';
|
||||
import {BlockDrag, BlockDragJson} from './events_block_drag.js';
|
||||
import {BlockMove, BlockMoveJson} from './events_block_move.js';
|
||||
import {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js';
|
||||
import {Click, ClickJson, ClickTarget} from './events_click.js';
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import {CommentChange, CommentChangeJson} from './events_comment_change.js';
|
||||
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 {Selected, SelectedJson} from './events_selected.js';
|
||||
import {ThemeChange, ThemeChangeJson} from './events_theme_change.js';
|
||||
import {ToolboxItemSelect, ToolboxItemSelectJson} from './events_toolbox_item_select.js';
|
||||
import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js';
|
||||
import {Ui} from './events_ui.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import {VarBase, VarBaseJson} from './events_var_base.js';
|
||||
import {VarCreate, VarCreateJson} from './events_var_create.js';
|
||||
import {VarDelete, VarDeleteJson} from './events_var_delete.js';
|
||||
import {VarRename, VarRenameJson} from './events_var_rename.js';
|
||||
import {ViewportChange, ViewportChangeJson} from './events_viewport.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js';
|
||||
|
||||
|
||||
// Events.
|
||||
export const Abstract = AbstractEvent;
|
||||
export {AbstractEventJson};
|
||||
export {BubbleOpen};
|
||||
export {BubbleOpenJson};
|
||||
export {BubbleType};
|
||||
export {BlockBase};
|
||||
export {BlockBaseJson};
|
||||
export {BlockChange};
|
||||
export {BlockChangeJson};
|
||||
export {BlockCreate};
|
||||
export {BlockCreateJson};
|
||||
export {BlockDelete};
|
||||
export {BlockDeleteJson};
|
||||
export {BlockDrag};
|
||||
export {BlockDragJson};
|
||||
export {BlockMove};
|
||||
export {BlockMoveJson};
|
||||
export {Click};
|
||||
export {ClickJson};
|
||||
export {ClickTarget};
|
||||
export {CommentBase};
|
||||
export {CommentBaseJson};
|
||||
export {CommentChange};
|
||||
export {CommentChangeJson};
|
||||
export {CommentCreate};
|
||||
export {CommentCreateJson};
|
||||
export {CommentDelete};
|
||||
export {CommentMove};
|
||||
export {CommentMoveJson};
|
||||
export {FinishedLoading};
|
||||
export {FinishedLoadingJson};
|
||||
export {MarkerMove};
|
||||
export {MarkerMoveJson};
|
||||
export {Selected};
|
||||
export {SelectedJson};
|
||||
export {ThemeChange};
|
||||
export {ThemeChangeJson};
|
||||
export {ToolboxItemSelect};
|
||||
export {ToolboxItemSelectJson};
|
||||
export {TrashcanOpen};
|
||||
export {TrashcanOpenJson};
|
||||
export {Ui};
|
||||
export {UiBase};
|
||||
export {VarBase};
|
||||
export {VarBaseJson};
|
||||
export {VarCreate};
|
||||
export {VarCreateJson};
|
||||
export {VarDelete};
|
||||
export {VarDeleteJson};
|
||||
export {VarRename};
|
||||
export {VarRenameJson};
|
||||
export {ViewportChange};
|
||||
export {ViewportChangeJson};
|
||||
|
||||
// Event types.
|
||||
export const BLOCK_CHANGE = eventUtils.BLOCK_CHANGE;
|
||||
export const BLOCK_CREATE = eventUtils.BLOCK_CREATE;
|
||||
export const BLOCK_DELETE = eventUtils.BLOCK_DELETE;
|
||||
export const BLOCK_DRAG = eventUtils.BLOCK_DRAG;
|
||||
export const BLOCK_MOVE = eventUtils.BLOCK_MOVE;
|
||||
export const BUBBLE_OPEN = eventUtils.BUBBLE_OPEN;
|
||||
export type BumpEvent = eventUtils.BumpEvent;
|
||||
export const BUMP_EVENTS = eventUtils.BUMP_EVENTS;
|
||||
export const CHANGE = eventUtils.CHANGE;
|
||||
export const CLICK = eventUtils.CLICK;
|
||||
export const COMMENT_CHANGE = eventUtils.COMMENT_CHANGE;
|
||||
export const COMMENT_CREATE = eventUtils.COMMENT_CREATE;
|
||||
export const COMMENT_DELETE = eventUtils.COMMENT_DELETE;
|
||||
export const COMMENT_MOVE = eventUtils.COMMENT_MOVE;
|
||||
export const CREATE = eventUtils.CREATE;
|
||||
export const DELETE = eventUtils.DELETE;
|
||||
export const FINISHED_LOADING = eventUtils.FINISHED_LOADING;
|
||||
export const MARKER_MOVE = eventUtils.MARKER_MOVE;
|
||||
export const MOVE = eventUtils.MOVE;
|
||||
export const SELECTED = eventUtils.SELECTED;
|
||||
export const THEME_CHANGE = eventUtils.THEME_CHANGE;
|
||||
export const TOOLBOX_ITEM_SELECT = eventUtils.TOOLBOX_ITEM_SELECT;
|
||||
export const TRASHCAN_OPEN = eventUtils.TRASHCAN_OPEN;
|
||||
export const UI = eventUtils.UI;
|
||||
export const VAR_CREATE = eventUtils.VAR_CREATE;
|
||||
export const VAR_DELETE = eventUtils.VAR_DELETE;
|
||||
export const VAR_RENAME = eventUtils.VAR_RENAME;
|
||||
export const VIEWPORT_CHANGE = eventUtils.VIEWPORT_CHANGE;
|
||||
|
||||
// Event utils.
|
||||
export const clearPendingUndo = eventUtils.clearPendingUndo;
|
||||
export const disable = eventUtils.disable;
|
||||
export const enable = eventUtils.enable;
|
||||
export const filter = eventUtils.filter;
|
||||
export const fire = eventUtils.fire;
|
||||
export const fromJson = eventUtils.fromJson;
|
||||
export const getDescendantIds = eventUtils.getDescendantIds;
|
||||
export const get = eventUtils.get;
|
||||
export const getGroup = eventUtils.getGroup;
|
||||
export const getRecordUndo = eventUtils.getRecordUndo;
|
||||
export const isEnabled = eventUtils.isEnabled;
|
||||
export const setGroup = eventUtils.setGroup;
|
||||
export const setRecordUndo = eventUtils.setRecordUndo;
|
||||
export const disableOrphans = eventUtils.disableOrphans;
|
||||
@@ -1,133 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Abstract class for events fired as a result of actions in
|
||||
* Blockly's editor.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Abstract class for events fired as a result of actions in
|
||||
* Blockly's editor.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.Abstract');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Workspace} = goog.requireType('Blockly.Workspace');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for an event.
|
||||
* @abstract
|
||||
* @alias Blockly.Events.Abstract
|
||||
*/
|
||||
class Abstract {
|
||||
/**
|
||||
* @alias Blockly.Events.Abstract
|
||||
*/
|
||||
constructor() {
|
||||
/**
|
||||
* Whether or not the event is blank (to be populated by fromJson).
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.isBlank = null;
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.workspaceId = undefined;
|
||||
|
||||
/**
|
||||
* The event group id for the group this event belongs to. Groups define
|
||||
* events that should be treated as an single action from the user's
|
||||
* perspective, and should be undone together.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = eventUtils.getRecordUndo();
|
||||
|
||||
/**
|
||||
* Whether or not the event is a UI event.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isUiEvent = false;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.type = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = {'type': this.type};
|
||||
if (this.group) {
|
||||
json['group'] = this.group;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
this.isBlank = false;
|
||||
this.group = json['group'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} True if null, false if something changed.
|
||||
*/
|
||||
isNull() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an event.
|
||||
* @param {boolean} _forward True if run forward, false if run backward
|
||||
* (undo).
|
||||
*/
|
||||
run(_forward) {
|
||||
// Defined by subclasses.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workspace the event belongs to.
|
||||
* @return {!Workspace} The workspace the event belongs to.
|
||||
* @throws {Error} if workspace is null.
|
||||
* @protected
|
||||
*/
|
||||
getEventWorkspace_() {
|
||||
let workspace;
|
||||
if (this.workspaceId) {
|
||||
const {Workspace} = goog.module.get('Blockly.Workspace');
|
||||
workspace = Workspace.getById(this.workspaceId);
|
||||
}
|
||||
if (!workspace) {
|
||||
throw Error(
|
||||
'Workspace is null. Event must have been generated from real' +
|
||||
' Blockly events.');
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
|
||||
exports.Abstract = Abstract;
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Abstract class for events fired as a result of actions in
|
||||
* Blockly's editor.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.Abstract');
|
||||
|
||||
import * as common from '../common.js';
|
||||
import type {Workspace} from '../workspace.js';
|
||||
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for an event.
|
||||
*
|
||||
* @alias Blockly.Events.Abstract
|
||||
*/
|
||||
export abstract class Abstract {
|
||||
/** Whether or not the event is blank (to be populated by fromJson). */
|
||||
abstract isBlank: boolean;
|
||||
|
||||
/** The workspace identifier for this event. */
|
||||
workspaceId?: string = undefined;
|
||||
group: string;
|
||||
recordUndo: boolean;
|
||||
|
||||
/** Whether or not the event is a UI event. */
|
||||
isUiEvent = false;
|
||||
|
||||
/** 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson(): AbstractEventJson {
|
||||
return {
|
||||
'type': this.type,
|
||||
'group': this.group,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
fromJson(json: AbstractEventJson) {
|
||||
this.isBlank = false;
|
||||
this.group = json['group'] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
*
|
||||
* @returns True if null, false if something changed.
|
||||
*/
|
||||
isNull(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an event.
|
||||
*
|
||||
* @param _forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(_forward: boolean) {}
|
||||
// Defined by subclasses.
|
||||
|
||||
/**
|
||||
* Get workspace the event belongs to.
|
||||
*
|
||||
* @returns The workspace the event belongs to.
|
||||
* @throws {Error} if workspace is null.
|
||||
* @internal
|
||||
*/
|
||||
getEventWorkspace_(): Workspace {
|
||||
let workspace;
|
||||
if (this.workspaceId) {
|
||||
workspace = common.getWorkspaceById(this.workspaceId);
|
||||
}
|
||||
if (!workspace) {
|
||||
throw Error(
|
||||
'Workspace is null. Event must have been generated from real' +
|
||||
' Blockly events.');
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AbstractEventJson {
|
||||
type: string;
|
||||
group: string;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Base class for all types of block events.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Base class for all types of block events.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockBase');
|
||||
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a block event.
|
||||
* @extends {AbstractEvent}
|
||||
* @alias Blockly.Events.BlockBase
|
||||
*/
|
||||
class BlockBase extends AbstractEvent {
|
||||
/**
|
||||
* @param {!Block=} opt_block The block this event corresponds to.
|
||||
* Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_block) {
|
||||
super();
|
||||
this.isBlank = typeof opt_block === 'undefined';
|
||||
|
||||
/**
|
||||
* The block ID for the block this event pertains to
|
||||
* @type {string}
|
||||
*/
|
||||
this.blockId = this.isBlank ? '' : opt_block.id;
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.workspaceId = this.isBlank ? '' : opt_block.workspace.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
exports.BlockBase = BlockBase;
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for all types of block events.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BlockBase');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a block event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockBase
|
||||
*/
|
||||
export class BlockBase extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
blockId?: string;
|
||||
|
||||
/**
|
||||
* @param opt_block The block this event corresponds to.
|
||||
* Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_block?: Block) {
|
||||
super();
|
||||
this.isBlank = !!opt_block;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): AbstractEventJson {
|
||||
const json = super.toJson() as BlockBaseJson;
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: BlockBaseJson) {
|
||||
super.fromJson(json);
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockBaseJson extends AbstractEventJson {
|
||||
blockId: string;
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for a block change event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockChange');
|
||||
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
* @extends {BlockBase}
|
||||
* @alias Blockly.Events.BlockChange
|
||||
*/
|
||||
class BlockChange extends BlockBase {
|
||||
/**
|
||||
* @param {!Block=} opt_block The changed block. Undefined for a blank
|
||||
* event.
|
||||
* @param {string=} opt_element One of 'field', 'comment', 'disabled', etc.
|
||||
* @param {?string=} opt_name Name of input or field affected, or null.
|
||||
* @param {*=} opt_oldValue Previous value of element.
|
||||
* @param {*=} opt_newValue New value of element.
|
||||
*/
|
||||
constructor(opt_block, opt_element, opt_name, opt_oldValue, opt_newValue) {
|
||||
super(opt_block);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.BLOCK_CHANGE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
this.element = typeof opt_element === 'undefined' ? '' : opt_element;
|
||||
this.name = typeof opt_name === 'undefined' ? '' : opt_name;
|
||||
this.oldValue = typeof opt_oldValue === 'undefined' ? '' : opt_oldValue;
|
||||
this.newValue = typeof opt_newValue === 'undefined' ? '' : opt_newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['element'] = this.element;
|
||||
if (this.name) {
|
||||
json['name'] = this.name;
|
||||
}
|
||||
json['oldValue'] = this.oldValue;
|
||||
json['newValue'] = this.newValue;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.element = json['element'];
|
||||
this.name = json['name'];
|
||||
this.oldValue = json['oldValue'];
|
||||
this.newValue = json['newValue'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
isNull() {
|
||||
return this.oldValue === this.newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn('Can\'t change non-existent block: ' + this.blockId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume the block is rendered so that then we can check.
|
||||
const blockSvg = /** @type {!BlockSvg} */ (block);
|
||||
if (blockSvg.mutator) {
|
||||
// Close the mutator (if open) since we don't want to update it.
|
||||
blockSvg.mutator.setVisible(false);
|
||||
}
|
||||
const value = forward ? this.newValue : this.oldValue;
|
||||
switch (this.element) {
|
||||
case 'field': {
|
||||
const field = block.getField(this.name);
|
||||
if (field) {
|
||||
field.setValue(value);
|
||||
} else {
|
||||
console.warn('Can\'t set non-existent field: ' + this.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'comment':
|
||||
block.setCommentText(/** @type {string} */ (value) || null);
|
||||
break;
|
||||
case 'collapsed':
|
||||
block.setCollapsed(!!value);
|
||||
break;
|
||||
case 'disabled':
|
||||
block.setEnabled(!value);
|
||||
break;
|
||||
case 'inline':
|
||||
block.setInputsInline(!!value);
|
||||
break;
|
||||
case 'mutation': {
|
||||
const oldState = BlockChange.getExtraBlockState_(
|
||||
/** @type {!BlockSvg} */ (block));
|
||||
if (block.loadExtraState) {
|
||||
block.loadExtraState(
|
||||
JSON.parse(/** @type {string} */ (value) || '{}'));
|
||||
} else if (block.domToMutation) {
|
||||
block.domToMutation(
|
||||
Xml.textToDom(/** @type {string} */ (value) || '<mutation/>'));
|
||||
}
|
||||
eventUtils.fire(
|
||||
new BlockChange(block, 'mutation', null, oldState, value));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn('Unknown change type: ' + this.element);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (#5397): Encapsulate this in the BlocklyMutationChange event when
|
||||
// refactoring change events.
|
||||
/**
|
||||
* Returns the extra state of the given block (either as XML or a JSO,
|
||||
* depending on the block's definition).
|
||||
* @param {!BlockSvg} block The block to get the extra state of.
|
||||
* @return {string} A stringified version of the extra state of the given
|
||||
* block.
|
||||
* @package
|
||||
*/
|
||||
static getExtraBlockState_(block) {
|
||||
if (block.saveExtraState) {
|
||||
const state = block.saveExtraState();
|
||||
return state ? JSON.stringify(state) : '';
|
||||
} else if (block.mutationToDom) {
|
||||
const state = block.mutationToDom();
|
||||
return state ? Xml.domToText(state) : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CHANGE, BlockChange);
|
||||
|
||||
exports.BlockChange = BlockChange;
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BlockChange');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import type {BlockSvg} from '../block_svg.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block change event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockChange
|
||||
*/
|
||||
export class BlockChange extends BlockBase {
|
||||
override type = eventUtils.BLOCK_CHANGE;
|
||||
element?: string;
|
||||
name?: string;
|
||||
oldValue: unknown;
|
||||
newValue: unknown;
|
||||
|
||||
/**
|
||||
* @param opt_block The changed block. Undefined for a blank event.
|
||||
* @param opt_element One of 'field', 'comment', 'disabled', etc.
|
||||
* @param opt_name Name of input or field affected, or null.
|
||||
* @param opt_oldValue Previous value of element.
|
||||
* @param opt_newValue New value of element.
|
||||
*/
|
||||
constructor(
|
||||
opt_block?: Block, opt_element?: string, opt_name?: string|null,
|
||||
opt_oldValue?: unknown, opt_newValue?: unknown) {
|
||||
super(opt_block);
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
this.element = opt_element;
|
||||
this.name = opt_name || undefined;
|
||||
this.oldValue = opt_oldValue;
|
||||
this.newValue = opt_newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): BlockChangeJson {
|
||||
const json = super.toJson() as BlockChangeJson;
|
||||
if (!this.element) {
|
||||
throw new Error(
|
||||
'The changed element is undefined. Either pass an ' +
|
||||
'element to the constructor, or call fromJson');
|
||||
}
|
||||
json['element'] = this.element;
|
||||
json['name'] = this.name;
|
||||
json['oldValue'] = this.oldValue;
|
||||
json['newValue'] = this.newValue;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: BlockChangeJson) {
|
||||
super.fromJson(json);
|
||||
this.element = json['element'];
|
||||
this.name = json['name'];
|
||||
this.oldValue = json['oldValue'];
|
||||
this.newValue = json['newValue'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
*
|
||||
* @returns False if something changed.
|
||||
*/
|
||||
override isNull(): boolean {
|
||||
return this.oldValue === this.newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
*
|
||||
* @param forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The associated block is undefined. Either pass a ' +
|
||||
'block to the constructor, or call fromJson');
|
||||
}
|
||||
// Assume the block is rendered so that then we can check.
|
||||
const blockSvg = block as BlockSvg;
|
||||
if (blockSvg.mutator) {
|
||||
// Close the mutator (if open) since we don't want to update it.
|
||||
blockSvg.mutator.setVisible(false);
|
||||
}
|
||||
const value = forward ? this.newValue : this.oldValue;
|
||||
switch (this.element) {
|
||||
case 'field': {
|
||||
const field = block.getField(this.name!);
|
||||
if (field) {
|
||||
field.setValue(value);
|
||||
} else {
|
||||
console.warn('Can\'t set non-existent field: ' + this.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'comment':
|
||||
block.setCommentText(value as string || null);
|
||||
break;
|
||||
case 'collapsed':
|
||||
block.setCollapsed(!!value);
|
||||
break;
|
||||
case 'disabled':
|
||||
block.setEnabled(!value);
|
||||
break;
|
||||
case 'inline':
|
||||
block.setInputsInline(!!value);
|
||||
break;
|
||||
case 'mutation': {
|
||||
const oldState = BlockChange.getExtraBlockState_(block as BlockSvg);
|
||||
if (block.loadExtraState) {
|
||||
block.loadExtraState(JSON.parse(value as string || '{}'));
|
||||
} else if (block.domToMutation) {
|
||||
block.domToMutation(Xml.textToDom(value as string || '<mutation/>'));
|
||||
}
|
||||
eventUtils.fire(
|
||||
new BlockChange(block, 'mutation', null, oldState, value));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn('Unknown change type: ' + this.element);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (#5397): Encapsulate this in the BlocklyMutationChange event when
|
||||
// refactoring change events.
|
||||
/**
|
||||
* Returns the extra state of the given block (either as XML or a JSO,
|
||||
* depending on the block's definition).
|
||||
*
|
||||
* @param block The block to get the extra state of.
|
||||
* @returns A stringified version of the extra state of the given block.
|
||||
* @internal
|
||||
*/
|
||||
static getExtraBlockState_(block: BlockSvg): string {
|
||||
if (block.saveExtraState) {
|
||||
const state = block.saveExtraState();
|
||||
return state ? JSON.stringify(state) : '';
|
||||
} else if (block.mutationToDom) {
|
||||
const state = block.mutationToDom();
|
||||
return state ? Xml.domToText(state) : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockChangeJson extends BlockBaseJson {
|
||||
element: string;
|
||||
name?: string;
|
||||
newValue: unknown;
|
||||
oldValue: unknown;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CHANGE, BlockChange);
|
||||
@@ -1,119 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for a block creation event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockCreate');
|
||||
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const blocks = goog.require('Blockly.serialization.blocks');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
* @extends {BlockBase}
|
||||
* @alias Blockly.Events.BlockCreate
|
||||
*/
|
||||
class BlockCreate extends BlockBase {
|
||||
/**
|
||||
* @param {!Block=} opt_block The created block. Undefined for a blank
|
||||
* event.
|
||||
*/
|
||||
constructor(opt_block) {
|
||||
super(opt_block);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.BLOCK_CREATE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
if (opt_block.isShadow()) {
|
||||
// Moving shadow blocks is handled via disconnection.
|
||||
this.recordUndo = false;
|
||||
}
|
||||
|
||||
this.xml = Xml.blockToDomWithXY(opt_block);
|
||||
this.ids = eventUtils.getDescendantIds(opt_block);
|
||||
|
||||
/**
|
||||
* JSON representation of the block that was just created.
|
||||
* @type {!blocks.State}
|
||||
*/
|
||||
this.json = /** @type {!blocks.State} */ (
|
||||
blocks.save(opt_block, {addCoordinates: true}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['xml'] = Xml.domToText(this.xml);
|
||||
json['ids'] = this.ids;
|
||||
json['json'] = this.json;
|
||||
if (!this.recordUndo) {
|
||||
json['recordUndo'] = this.recordUndo;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
this.ids = json['ids'];
|
||||
this.json = /** @type {!blocks.State} */ (json['json']);
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
this.recordUndo = json['recordUndo'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
blocks.append(this.json, workspace);
|
||||
} else {
|
||||
for (let i = 0; i < this.ids.length; i++) {
|
||||
const id = this.ids[i];
|
||||
const block = workspace.getBlockById(id);
|
||||
if (block) {
|
||||
block.dispose(false);
|
||||
} else if (id === this.blockId) {
|
||||
// Only complain about root-level block.
|
||||
console.warn('Can\'t uncreate non-existent block: ' + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CREATE, BlockCreate);
|
||||
|
||||
exports.BlockCreate = BlockCreate;
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BlockCreate');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as blocks from '../serialization/blocks.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block creation event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockCreate
|
||||
*/
|
||||
export class BlockCreate extends BlockBase {
|
||||
override type = eventUtils.BLOCK_CREATE;
|
||||
xml?: Element|DocumentFragment;
|
||||
ids?: string[];
|
||||
json?: blocks.State;
|
||||
|
||||
/** @param opt_block The created block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
if (opt_block.isShadow()) {
|
||||
// Moving shadow blocks is handled via disconnection.
|
||||
this.recordUndo = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): BlockCreateJson {
|
||||
const json = super.toJson() as BlockCreateJson;
|
||||
if (!this.xml) {
|
||||
throw new Error(
|
||||
'The block XML is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.json) {
|
||||
throw new Error(
|
||||
'The block JSON is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['xml'] = Xml.domToText(this.xml);
|
||||
json['ids'] = this.ids;
|
||||
json['json'] = this.json;
|
||||
if (!this.recordUndo) {
|
||||
json['recordUndo'] = this.recordUndo;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: BlockCreateJson) {
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
this.ids = json['ids'];
|
||||
this.json = json['json'] as blocks.State;
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
this.recordUndo = json['recordUndo'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
*
|
||||
* @param forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.json) {
|
||||
throw new Error(
|
||||
'The block JSON is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
blocks.append(this.json, workspace);
|
||||
} else {
|
||||
for (let i = 0; i < this.ids.length; i++) {
|
||||
const id = this.ids[i];
|
||||
const block = workspace.getBlockById(id);
|
||||
if (block) {
|
||||
block.dispose(false);
|
||||
} else if (id === this.blockId) {
|
||||
// Only complain about root-level block.
|
||||
console.warn('Can\'t uncreate non-existent block: ' + id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockCreateJson extends BlockBaseJson {
|
||||
xml: string;
|
||||
ids: string[];
|
||||
json: object;
|
||||
recordUndo?: boolean;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CREATE, BlockCreate);
|
||||
@@ -1,131 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for a block delete event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for a block delete event.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockDelete');
|
||||
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const blocks = goog.require('Blockly.serialization.blocks');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block deletion event.
|
||||
* @extends {BlockBase}
|
||||
* @alias Blockly.Events.BlockDelete
|
||||
*/
|
||||
class BlockDelete extends BlockBase {
|
||||
/**
|
||||
* @param {!Block=} opt_block The deleted block. Undefined for a blank
|
||||
* event.
|
||||
*/
|
||||
constructor(opt_block) {
|
||||
super(opt_block);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.BLOCK_DELETE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
if (opt_block.getParent()) {
|
||||
throw Error('Connected blocks cannot be deleted.');
|
||||
}
|
||||
if (opt_block.isShadow()) {
|
||||
// Respawning shadow blocks is handled via disconnection.
|
||||
this.recordUndo = false;
|
||||
}
|
||||
|
||||
this.oldXml = Xml.blockToDomWithXY(opt_block);
|
||||
this.ids = eventUtils.getDescendantIds(opt_block);
|
||||
|
||||
/**
|
||||
* Was the block that was just deleted a shadow?
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.wasShadow = opt_block.isShadow();
|
||||
|
||||
/**
|
||||
* JSON representation of the block that was just deleted.
|
||||
* @type {!blocks.State}
|
||||
*/
|
||||
this.oldJson = /** @type {!blocks.State} */ (
|
||||
blocks.save(opt_block, {addCoordinates: true}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['oldXml'] = Xml.domToText(this.oldXml);
|
||||
json['ids'] = this.ids;
|
||||
json['wasShadow'] = this.wasShadow;
|
||||
json['oldJson'] = this.oldJson;
|
||||
if (!this.recordUndo) {
|
||||
json['recordUndo'] = this.recordUndo;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.oldXml = Xml.textToDom(json['oldXml']);
|
||||
this.ids = json['ids'];
|
||||
this.wasShadow =
|
||||
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
|
||||
this.oldJson = /** @type {!blocks.State} */ (json['oldJson']);
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
this.recordUndo = json['recordUndo'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a deletion event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (forward) {
|
||||
for (let i = 0; i < this.ids.length; i++) {
|
||||
const id = this.ids[i];
|
||||
const block = workspace.getBlockById(id);
|
||||
if (block) {
|
||||
block.dispose(false);
|
||||
} else if (id === this.blockId) {
|
||||
// Only complain about root-level block.
|
||||
console.warn('Can\'t delete non-existent block: ' + id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blocks.append(this.oldJson, workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete);
|
||||
|
||||
exports.BlockDelete = BlockDelete;
|
||||
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for a block delete event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BlockDelete');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
import * as blocks from '../serialization/blocks.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block deletion event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockDelete
|
||||
*/
|
||||
export class BlockDelete extends BlockBase {
|
||||
oldXml?: Element|DocumentFragment;
|
||||
ids?: string[];
|
||||
wasShadow?: boolean;
|
||||
oldJson?: blocks.State;
|
||||
override type = eventUtils.BLOCK_DELETE;
|
||||
|
||||
/** @param opt_block The deleted block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
if (opt_block.getParent()) {
|
||||
throw Error('Connected blocks cannot be deleted.');
|
||||
}
|
||||
if (opt_block.isShadow()) {
|
||||
// Respawning shadow blocks is handled via disconnection.
|
||||
this.recordUndo = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): BlockDeleteJson {
|
||||
const json = super.toJson() as BlockDeleteJson;
|
||||
if (!this.oldXml) {
|
||||
throw new Error(
|
||||
'The old block XML is undefined. Either pass a block ' +
|
||||
'to the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (this.wasShadow === undefined) {
|
||||
throw new Error(
|
||||
'Whether the block was a shadow is undefined. Either ' +
|
||||
'pass a block to the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.oldJson) {
|
||||
throw new Error(
|
||||
'The old block JSON is undefined. Either pass a block ' +
|
||||
'to the constructor, or call fromJson');
|
||||
}
|
||||
json['oldXml'] = Xml.domToText(this.oldXml);
|
||||
json['ids'] = this.ids;
|
||||
json['wasShadow'] = this.wasShadow;
|
||||
json['oldJson'] = this.oldJson;
|
||||
if (!this.recordUndo) {
|
||||
json['recordUndo'] = this.recordUndo;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: BlockDeleteJson) {
|
||||
super.fromJson(json);
|
||||
this.oldXml = Xml.textToDom(json['oldXml']);
|
||||
this.ids = json['ids'];
|
||||
this.wasShadow =
|
||||
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
|
||||
this.oldJson = json['oldJson'];
|
||||
if (json['recordUndo'] !== undefined) {
|
||||
this.recordUndo = json['recordUndo'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a deletion event.
|
||||
*
|
||||
* @param forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.ids) {
|
||||
throw new Error(
|
||||
'The block IDs are undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.oldJson) {
|
||||
throw new Error(
|
||||
'The old block JSON is undefined. Either pass a block ' +
|
||||
'to the constructor, or call fromJson');
|
||||
}
|
||||
if (forward) {
|
||||
for (let i = 0; i < this.ids.length; i++) {
|
||||
const id = this.ids[i];
|
||||
const block = workspace.getBlockById(id);
|
||||
if (block) {
|
||||
block.dispose(false);
|
||||
} else if (id === this.blockId) {
|
||||
// Only complain about root-level block.
|
||||
console.warn('Can\'t delete non-existent block: ' + id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blocks.append(this.oldJson, workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockDeleteJson extends BlockBaseJson {
|
||||
oldXml: string;
|
||||
ids: string[];
|
||||
wasShadow: boolean;
|
||||
oldJson: blocks.State;
|
||||
recordUndo?: boolean;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete);
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Events fired as a block drag.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Events fired as a block drag.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockDrag');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
const {UiBase} = goog.require('Blockly.Events.UiBase');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block drag event.
|
||||
* @extends {UiBase}
|
||||
* @alias Blockly.Events.BlockDrag
|
||||
*/
|
||||
class BlockDrag extends UiBase {
|
||||
/**
|
||||
* @param {!Block=} opt_block The top block in the stack that is being
|
||||
* dragged. Undefined for a blank event.
|
||||
* @param {boolean=} opt_isStart Whether this is the start of a block drag.
|
||||
* Undefined for a blank event.
|
||||
* @param {!Array<!Block>=} opt_blocks The blocks affected by this
|
||||
* drag. Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_block, opt_isStart, opt_blocks) {
|
||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||
super(workspaceId);
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
|
||||
/**
|
||||
* Whether this is the start of a block drag.
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
this.isStart = opt_isStart;
|
||||
|
||||
/**
|
||||
* The blocks affected by this drag event.
|
||||
* @type {!Array<!Block>|undefined}
|
||||
*/
|
||||
this.blocks = opt_blocks;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.BLOCK_DRAG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['isStart'] = this.isStart;
|
||||
json['blockId'] = this.blockId;
|
||||
json['blocks'] = this.blocks;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.isStart = json['isStart'];
|
||||
this.blockId = json['blockId'];
|
||||
this.blocks = json['blocks'];
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.BLOCK_DRAG, BlockDrag);
|
||||
|
||||
exports.BlockDrag = BlockDrag;
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Events fired as a block drag.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BlockDrag');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a block drag event.
|
||||
*
|
||||
* @alias Blockly.Events.BlockDrag
|
||||
*/
|
||||
export class BlockDrag extends UiBase {
|
||||
blockId?: string;
|
||||
isStart?: boolean;
|
||||
blocks?: Block[];
|
||||
override type = eventUtils.BLOCK_DRAG;
|
||||
|
||||
/**
|
||||
* @param opt_block The top block in the stack that is being dragged.
|
||||
* Undefined for a blank event.
|
||||
* @param opt_isStart Whether this is the start of a block drag.
|
||||
* Undefined for a blank event.
|
||||
* @param opt_blocks The blocks affected by this drag. Undefined for a blank
|
||||
* event.
|
||||
*/
|
||||
constructor(opt_block?: Block, opt_isStart?: boolean, opt_blocks?: Block[]) {
|
||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||
super(workspaceId);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): BlockDragJson {
|
||||
const json = super.toJson() as BlockDragJson;
|
||||
if (this.isStart === undefined) {
|
||||
throw new Error(
|
||||
'Whether this event is the start of a drag is ' +
|
||||
'undefined. Either pass the value to the constructor, or call ' +
|
||||
'fromJson');
|
||||
}
|
||||
if (this.blockId === undefined) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['isStart'] = this.isStart;
|
||||
json['blockId'] = this.blockId;
|
||||
// TODO: I don't think we should actually apply the blocks array to the JSON
|
||||
// object b/c they have functions and aren't actually serializable.
|
||||
json['blocks'] = this.blocks;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: BlockDragJson) {
|
||||
super.fromJson(json);
|
||||
this.isStart = json['isStart'];
|
||||
this.blockId = json['blockId'];
|
||||
this.blocks = json['blocks'];
|
||||
}
|
||||
}
|
||||
|
||||
export interface BlockDragJson extends AbstractEventJson {
|
||||
isStart: boolean;
|
||||
blockId: string;
|
||||
blocks?: Block[];
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.BLOCK_DRAG, BlockDrag);
|
||||
@@ -4,48 +4,52 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for a block move event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for a block move event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BlockMove');
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BlockMove');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {BlockBase} = goog.require('Blockly.Events.BlockBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
const {ConnectionType} = goog.require('Blockly.ConnectionType');
|
||||
const {Coordinate} = goog.require('Blockly.utils.Coordinate');
|
||||
import type {Block} from '../block.js';
|
||||
import {ConnectionType} from '../connection_type.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
|
||||
import {BlockBase, BlockBaseJson} from './events_block_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
interface BlockLocation {
|
||||
parentId?: string;
|
||||
inputName?: string;
|
||||
coordinate?: Coordinate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for a block move event. Created before the move.
|
||||
* @extends {BlockBase}
|
||||
*
|
||||
* @alias Blockly.Events.BlockMove
|
||||
*/
|
||||
class BlockMove extends BlockBase {
|
||||
/**
|
||||
* @param {!Block=} opt_block The moved block. Undefined for a blank
|
||||
* event.
|
||||
*/
|
||||
constructor(opt_block) {
|
||||
export class BlockMove extends BlockBase {
|
||||
override type = eventUtils.BLOCK_MOVE;
|
||||
oldParentId?: string;
|
||||
oldInputName?: string;
|
||||
oldCoordinate?: Coordinate;
|
||||
|
||||
newParentId?: string;
|
||||
newInputName?: string;
|
||||
newCoordinate?: Coordinate;
|
||||
|
||||
/** @param opt_block The moved block. Undefined for a blank event. */
|
||||
constructor(opt_block?: Block) {
|
||||
super(opt_block);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.BLOCK_MOVE;
|
||||
|
||||
if (!opt_block) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
return;
|
||||
}
|
||||
// Blank event to be populated by fromJson.
|
||||
if (opt_block.isShadow()) {
|
||||
// Moving shadow blocks is handled via disconnection.
|
||||
this.recordUndo = false;
|
||||
@@ -55,27 +59,20 @@ class BlockMove extends BlockBase {
|
||||
this.oldParentId = location.parentId;
|
||||
this.oldInputName = location.inputName;
|
||||
this.oldCoordinate = location.coordinate;
|
||||
|
||||
this.newParentId = null;
|
||||
this.newInputName = null;
|
||||
this.newCoordinate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
if (this.newParentId) {
|
||||
json['newParentId'] = this.newParentId;
|
||||
}
|
||||
if (this.newInputName) {
|
||||
json['newInputName'] = this.newInputName;
|
||||
}
|
||||
override toJson(): BlockMoveJson {
|
||||
const json = super.toJson() as BlockMoveJson;
|
||||
json['newParentId'] = this.newParentId;
|
||||
json['newInputName'] = this.newInputName;
|
||||
if (this.newCoordinate) {
|
||||
json['newCoordinate'] = Math.round(this.newCoordinate.x) + ',' +
|
||||
Math.round(this.newCoordinate.y);
|
||||
json['newCoordinate'] = `${Math.round(this.newCoordinate.x)}, ` +
|
||||
`${Math.round(this.newCoordinate.y)}`;
|
||||
}
|
||||
if (!this.recordUndo) {
|
||||
json['recordUndo'] = this.recordUndo;
|
||||
@@ -85,9 +82,10 @@ class BlockMove extends BlockBase {
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
override fromJson(json: BlockMoveJson) {
|
||||
super.fromJson(json);
|
||||
this.newParentId = json['newParentId'];
|
||||
this.newInputName = json['newInputName'];
|
||||
@@ -100,9 +98,7 @@ class BlockMove extends BlockBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the block's new location. Called after the move.
|
||||
*/
|
||||
/** Record the block's new location. Called after the move. */
|
||||
recordNew() {
|
||||
const location = this.currentLocation_();
|
||||
this.newParentId = location.parentId;
|
||||
@@ -113,13 +109,23 @@ class BlockMove extends BlockBase {
|
||||
/**
|
||||
* Returns the parentId and input if the block is connected,
|
||||
* or the XY location if disconnected.
|
||||
* @return {!Object} Collection of location info.
|
||||
* @private
|
||||
*
|
||||
* @returns Collection of location info.
|
||||
*/
|
||||
currentLocation_() {
|
||||
private currentLocation_(): BlockLocation {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
const location = {};
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The block associated with the block move event ' +
|
||||
'could not be found');
|
||||
}
|
||||
const location = {} as BlockLocation;
|
||||
const parent = block.getParent();
|
||||
if (parent) {
|
||||
location.parentId = parent.id;
|
||||
@@ -135,9 +141,10 @@ class BlockMove extends BlockBase {
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*
|
||||
* @returns False if something changed.
|
||||
*/
|
||||
isNull() {
|
||||
override isNull(): boolean {
|
||||
return this.oldParentId === this.newParentId &&
|
||||
this.oldInputName === this.newInputName &&
|
||||
Coordinate.equals(this.oldCoordinate, this.newCoordinate);
|
||||
@@ -145,10 +152,16 @@ class BlockMove extends BlockBase {
|
||||
|
||||
/**
|
||||
* Run a move event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*
|
||||
* @param forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.blockId) {
|
||||
throw new Error(
|
||||
'The block ID is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const block = workspace.getBlockById(this.blockId);
|
||||
if (!block) {
|
||||
console.warn('Can\'t move non-existent block: ' + this.blockId);
|
||||
@@ -157,7 +170,7 @@ class BlockMove extends BlockBase {
|
||||
const parentId = forward ? this.newParentId : this.oldParentId;
|
||||
const inputName = forward ? this.newInputName : this.oldInputName;
|
||||
const coordinate = forward ? this.newCoordinate : this.oldCoordinate;
|
||||
let parentBlock;
|
||||
let parentBlock: Block|null;
|
||||
if (parentId) {
|
||||
parentBlock = workspace.getBlockById(parentId);
|
||||
if (!parentBlock) {
|
||||
@@ -174,19 +187,18 @@ class BlockMove extends BlockBase {
|
||||
} else {
|
||||
let blockConnection = block.outputConnection;
|
||||
if (!blockConnection ||
|
||||
(block.previousConnection &&
|
||||
block.previousConnection.isConnected())) {
|
||||
block.previousConnection && block.previousConnection.isConnected()) {
|
||||
blockConnection = block.previousConnection;
|
||||
}
|
||||
let parentConnection;
|
||||
const connectionType = blockConnection.type;
|
||||
if (inputName) {
|
||||
const input = parentBlock.getInput(inputName);
|
||||
const input = parentBlock!.getInput(inputName);
|
||||
if (input) {
|
||||
parentConnection = input.connection;
|
||||
}
|
||||
} else if (connectionType === ConnectionType.PREVIOUS_STATEMENT) {
|
||||
parentConnection = parentBlock.nextConnection;
|
||||
parentConnection = parentBlock!.nextConnection;
|
||||
}
|
||||
if (parentConnection) {
|
||||
blockConnection.connect(parentConnection);
|
||||
@@ -197,6 +209,11 @@ class BlockMove extends BlockBase {
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove);
|
||||
export interface BlockMoveJson extends BlockBaseJson {
|
||||
newParentId?: string;
|
||||
newInputName?: string;
|
||||
newCoordinate?: string;
|
||||
recordUndo?: boolean;
|
||||
}
|
||||
|
||||
exports.BlockMove = BlockMove;
|
||||
registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove);
|
||||
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Events fired as a result of bubble open.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Events fired as a result of bubble open.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.BubbleOpen');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {BlockSvg} = goog.requireType('Blockly.BlockSvg');
|
||||
const {UiBase} = goog.require('Blockly.Events.UiBase');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a bubble open event.
|
||||
* @extends {UiBase}
|
||||
* @alias Blockly.Events.BubbleOpen
|
||||
*/
|
||||
class BubbleOpen extends UiBase {
|
||||
/**
|
||||
* @param {BlockSvg} opt_block The associated block. Undefined for a
|
||||
* blank event.
|
||||
* @param {boolean=} opt_isOpen Whether the bubble is opening (false if
|
||||
* closing). Undefined for a blank event.
|
||||
* @param {string=} opt_bubbleType The type of bubble. One of 'mutator',
|
||||
* 'comment'
|
||||
* or 'warning'. Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_block, opt_isOpen, opt_bubbleType) {
|
||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||
super(workspaceId);
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
|
||||
/**
|
||||
* Whether the bubble is opening (false if closing).
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
this.isOpen = opt_isOpen;
|
||||
|
||||
/**
|
||||
* The type of bubble. One of 'mutator', 'comment', or 'warning'.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.bubbleType = opt_bubbleType;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.BUBBLE_OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['isOpen'] = this.isOpen;
|
||||
json['bubbleType'] = this.bubbleType;
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.isOpen = json['isOpen'];
|
||||
this.bubbleType = json['bubbleType'];
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.BUBBLE_OPEN, BubbleOpen);
|
||||
|
||||
exports.BubbleOpen = BubbleOpen;
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Events fired as a result of bubble open.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.BubbleOpen');
|
||||
|
||||
import type {AbstractEventJson} from './events_abstract.js';
|
||||
import type {BlockSvg} from '../block_svg.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a bubble open event.
|
||||
*
|
||||
* @alias Blockly.Events.BubbleOpen
|
||||
*/
|
||||
export class BubbleOpen extends UiBase {
|
||||
blockId?: string;
|
||||
isOpen?: boolean;
|
||||
bubbleType?: BubbleType;
|
||||
override type = eventUtils.BUBBLE_OPEN;
|
||||
|
||||
/**
|
||||
* @param opt_block The associated block. Undefined for a blank event.
|
||||
* @param opt_isOpen Whether the bubble is opening (false if closing).
|
||||
* Undefined for a blank event.
|
||||
* @param opt_bubbleType The type of bubble. One of 'mutator', 'comment' or
|
||||
* 'warning'. Undefined for a blank event.
|
||||
*/
|
||||
constructor(
|
||||
opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: BubbleType) {
|
||||
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
|
||||
super(workspaceId);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): BubbleOpenJson {
|
||||
const json = super.toJson() as BubbleOpenJson;
|
||||
if (this.isOpen === undefined) {
|
||||
throw new Error(
|
||||
'Whether this event is for opening the bubble is ' +
|
||||
'undefined. Either pass the value to the constructor, or call ' +
|
||||
'fromJson');
|
||||
}
|
||||
if (!this.bubbleType) {
|
||||
throw new Error(
|
||||
'The type of bubble is undefined. Either pass the ' +
|
||||
'value to the constructor, or call ' +
|
||||
'fromJson');
|
||||
}
|
||||
json['isOpen'] = this.isOpen;
|
||||
json['bubbleType'] = this.bubbleType;
|
||||
json['blockId'] = this.blockId || '';
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: BubbleOpenJson) {
|
||||
super.fromJson(json);
|
||||
this.isOpen = json['isOpen'];
|
||||
this.bubbleType = json['bubbleType'];
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
export enum BubbleType {
|
||||
MUTATOR = 'mutator',
|
||||
COMMENT = 'comment',
|
||||
WARNING = 'warning',
|
||||
}
|
||||
|
||||
export interface BubbleOpenJson extends AbstractEventJson {
|
||||
isOpen: boolean;
|
||||
bubbleType: BubbleType;
|
||||
blockId: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.BUBBLE_OPEN, BubbleOpen);
|
||||
@@ -1,87 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Events fired as a result of UI click in Blockly's editor.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Events fired as a result of UI click in Blockly's editor.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.Click');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {Block} = goog.requireType('Blockly.Block');
|
||||
const {UiBase} = goog.require('Blockly.Events.UiBase');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a click event.
|
||||
* @extends {UiBase}
|
||||
* @alias Blockly.Events.Click
|
||||
*/
|
||||
class Click extends UiBase {
|
||||
/**
|
||||
* @param {?Block=} opt_block The affected block. Null for click events
|
||||
* that do not have an associated block (i.e. workspace click). Undefined
|
||||
* for a blank event.
|
||||
* @param {?string=} opt_workspaceId The workspace identifier for this event.
|
||||
* Not used if block is passed. Undefined for a blank event.
|
||||
* @param {string=} opt_targetType The type of element targeted by this click
|
||||
* event. Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_block, opt_workspaceId, opt_targetType) {
|
||||
let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
|
||||
if (workspaceId === null) {
|
||||
workspaceId = undefined;
|
||||
}
|
||||
super(workspaceId);
|
||||
this.blockId = opt_block ? opt_block.id : null;
|
||||
|
||||
/**
|
||||
* The type of element targeted by this click event.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
this.targetType = opt_targetType;
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.CLICK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['targetType'] = this.targetType;
|
||||
if (this.blockId) {
|
||||
json['blockId'] = this.blockId;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.targetType = json['targetType'];
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CLICK, Click);
|
||||
|
||||
exports.Click = Click;
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Events fired as a result of UI click in Blockly's editor.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.Click');
|
||||
|
||||
import type {Block} from '../block.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {AbstractEventJson} from './events_abstract.js';
|
||||
|
||||
import {UiBase} from './events_ui_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a click event.
|
||||
*
|
||||
* @alias Blockly.Events.Click
|
||||
*/
|
||||
export class Click extends UiBase {
|
||||
blockId?: string;
|
||||
targetType?: ClickTarget;
|
||||
override type = eventUtils.CLICK;
|
||||
|
||||
/**
|
||||
* @param opt_block The affected block. Null for click events that do not have
|
||||
* an associated block (i.e. workspace click). Undefined for a blank
|
||||
* event.
|
||||
* @param opt_workspaceId The workspace identifier for this event.
|
||||
* Not used if block is passed. Undefined for a blank event.
|
||||
* @param opt_targetType The type of element targeted by this click event.
|
||||
* Undefined for a blank event.
|
||||
*/
|
||||
constructor(
|
||||
opt_block?: Block|null, opt_workspaceId?: string|null,
|
||||
opt_targetType?: ClickTarget) {
|
||||
let workspaceId = opt_block ? opt_block.workspace.id : opt_workspaceId;
|
||||
if (workspaceId === null) {
|
||||
workspaceId = undefined;
|
||||
}
|
||||
super(workspaceId);
|
||||
|
||||
this.blockId = opt_block ? opt_block.id : undefined;
|
||||
|
||||
/** The type of element targeted by this click event. */
|
||||
this.targetType = opt_targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): ClickJson {
|
||||
const json = super.toJson() as ClickJson;
|
||||
if (!this.targetType) {
|
||||
throw new Error(
|
||||
'The click target type is undefined. Either pass a block to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['targetType'] = this.targetType;
|
||||
json['blockId'] = this.blockId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: ClickJson) {
|
||||
super.fromJson(json);
|
||||
this.targetType = json['targetType'];
|
||||
this.blockId = json['blockId'];
|
||||
}
|
||||
}
|
||||
|
||||
export enum ClickTarget {
|
||||
BLOCK = 'block',
|
||||
WORKSPACE = 'workspace',
|
||||
ZOOM_CONTROLS = 'zoom_controls',
|
||||
}
|
||||
|
||||
export interface ClickJson extends AbstractEventJson {
|
||||
targetType: ClickTarget;
|
||||
blockId?: string;
|
||||
}
|
||||
|
||||
registry.register(registry.Type.EVENT, eventUtils.CLICK, Click);
|
||||
@@ -1,121 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Base class for comment events.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Base class for comment events.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.CommentBase');
|
||||
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const utilsXml = goog.require('Blockly.utils.xml');
|
||||
const {Abstract: AbstractEvent} = goog.require('Blockly.Events.Abstract');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentCreate} = goog.requireType('Blockly.Events.CommentCreate');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {CommentDelete} = goog.requireType('Blockly.Events.CommentDelete');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment');
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a comment event.
|
||||
* @extends {AbstractEvent}
|
||||
* @alias Blockly.Events.CommentBase
|
||||
*/
|
||||
class CommentBase extends AbstractEvent {
|
||||
/**
|
||||
* @param {!WorkspaceComment=} opt_comment The comment this event
|
||||
* corresponds to. Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_comment) {
|
||||
super();
|
||||
/**
|
||||
* Whether or not an event is blank.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isBlank = typeof opt_comment === 'undefined';
|
||||
|
||||
/**
|
||||
* The ID of the comment this event pertains to.
|
||||
* @type {string}
|
||||
*/
|
||||
this.commentId = this.isBlank ? '' : opt_comment.id;
|
||||
|
||||
/**
|
||||
* The workspace identifier for this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.workspaceId = this.isBlank ? '' : 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.
|
||||
* @type {string}
|
||||
*/
|
||||
this.group = eventUtils.getGroup();
|
||||
|
||||
/**
|
||||
* Sets whether the event should be added to the undo stack.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.recordUndo = eventUtils.getRecordUndo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
if (this.commentId) {
|
||||
json['commentId'] = this.commentId;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.commentId = json['commentId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for Comment[Create|Delete]
|
||||
* @param {!CommentCreate|!CommentDelete} event
|
||||
* The event to run.
|
||||
* @param {boolean} create if True then Create, if False then Delete
|
||||
*/
|
||||
static CommentCreateDeleteHelper(event, create) {
|
||||
const workspace = event.getEventWorkspace_();
|
||||
if (create) {
|
||||
const xmlElement = utilsXml.createElement('xml');
|
||||
xmlElement.appendChild(event.xml);
|
||||
Xml.domToWorkspace(xmlElement, workspace);
|
||||
} else {
|
||||
const comment = workspace.getCommentById(event.commentId);
|
||||
if (comment) {
|
||||
comment.dispose();
|
||||
} else {
|
||||
// Only complain about root-level block.
|
||||
console.warn(
|
||||
'Can\'t uncreate non-existent comment: ' + event.commentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.CommentBase = CommentBase;
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for comment events.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.CommentBase');
|
||||
|
||||
import * as utilsXml from '../utils/xml.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
|
||||
import type {CommentCreate} from './events_comment_create.js';
|
||||
import type {CommentDelete} from './events_comment_delete.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class for a comment event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentBase
|
||||
*/
|
||||
export class CommentBase extends AbstractEvent {
|
||||
override isBlank = true;
|
||||
commentId?: string;
|
||||
|
||||
/**
|
||||
* @param opt_comment The comment this event corresponds to. Undefined for a
|
||||
* blank event.
|
||||
*/
|
||||
constructor(opt_comment?: WorkspaceComment) {
|
||||
super();
|
||||
/** Whether or not an event is blank. */
|
||||
this.isBlank = !opt_comment;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): CommentBaseJson {
|
||||
const json = super.toJson() as CommentBaseJson;
|
||||
if (!this.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['commentId'] = this.commentId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: CommentBaseJson) {
|
||||
super.fromJson(json);
|
||||
this.commentId = json['commentId'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for Comment[Create|Delete]
|
||||
*
|
||||
* @param event The event to run.
|
||||
* @param create if True then Create, if False then Delete
|
||||
*/
|
||||
static CommentCreateDeleteHelper(
|
||||
event: CommentCreate|CommentDelete, create: boolean) {
|
||||
const workspace = event.getEventWorkspace_();
|
||||
if (create) {
|
||||
const xmlElement = utilsXml.createElement('xml');
|
||||
if (!event.xml) {
|
||||
throw new Error('Ecountered a comment event without proper xml');
|
||||
}
|
||||
xmlElement.appendChild(event.xml);
|
||||
Xml.domToWorkspace(xmlElement, workspace);
|
||||
} else {
|
||||
if (!event.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const comment = workspace.getCommentById(event.commentId);
|
||||
if (comment) {
|
||||
comment.dispose();
|
||||
} else {
|
||||
// Only complain about root-level block.
|
||||
console.warn(
|
||||
'Can\'t uncreate non-existent comment: ' + event.commentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentBaseJson extends AbstractEventJson {
|
||||
commentId: string;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for comment change event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for comment change event.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.CommentChange');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {CommentBase} = goog.require('Blockly.Events.CommentBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment change event.
|
||||
* @extends {CommentBase}
|
||||
* @alias Blockly.Events.CommentChange
|
||||
*/
|
||||
class CommentChange extends CommentBase {
|
||||
/**
|
||||
* @param {!WorkspaceComment=} opt_comment The comment that is being
|
||||
* changed. Undefined for a blank event.
|
||||
* @param {string=} opt_oldContents Previous contents of the comment.
|
||||
* @param {string=} opt_newContents New contents of the comment.
|
||||
*/
|
||||
constructor(opt_comment, opt_oldContents, opt_newContents) {
|
||||
super(opt_comment);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.COMMENT_CHANGE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
this.oldContents_ =
|
||||
typeof opt_oldContents === 'undefined' ? '' : opt_oldContents;
|
||||
this.newContents_ =
|
||||
typeof opt_newContents === 'undefined' ? '' : opt_newContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['oldContents'] = this.oldContents_;
|
||||
json['newContents'] = this.newContents_;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.oldContents_ = json['oldContents'];
|
||||
this.newContents_ = json['newContents'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
* @return {boolean} False if something changed.
|
||||
*/
|
||||
isNull() {
|
||||
return this.oldContents_ === this.newContents_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
const comment = workspace.getCommentById(this.commentId);
|
||||
if (!comment) {
|
||||
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
const contents = forward ? this.newContents_ : this.oldContents_;
|
||||
|
||||
comment.setContent(contents);
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange);
|
||||
|
||||
exports.CommentChange = CommentChange;
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for comment change event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.CommentChange');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment change event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentChange
|
||||
*/
|
||||
export class CommentChange extends CommentBase {
|
||||
override type = eventUtils.COMMENT_CHANGE;
|
||||
oldContents_?: string;
|
||||
newContents_?: string;
|
||||
|
||||
/**
|
||||
* @param opt_comment The comment that is being changed. Undefined for a
|
||||
* blank event.
|
||||
* @param opt_oldContents Previous contents of the comment.
|
||||
* @param opt_newContents New contents of the comment.
|
||||
*/
|
||||
constructor(
|
||||
opt_comment?: WorkspaceComment, opt_oldContents?: string,
|
||||
opt_newContents?: string) {
|
||||
super(opt_comment);
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
this.oldContents_ =
|
||||
typeof opt_oldContents === 'undefined' ? '' : opt_oldContents;
|
||||
this.newContents_ =
|
||||
typeof opt_newContents === 'undefined' ? '' : opt_newContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): CommentChangeJson {
|
||||
const json = super.toJson() as CommentChangeJson;
|
||||
if (!this.oldContents_) {
|
||||
throw new Error(
|
||||
'The old contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
if (!this.newContents_) {
|
||||
throw new Error(
|
||||
'The new contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['oldContents'] = this.oldContents_;
|
||||
json['newContents'] = this.newContents_;
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: CommentChangeJson) {
|
||||
super.fromJson(json);
|
||||
this.oldContents_ = json['oldContents'];
|
||||
this.newContents_ = json['newContents'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this event record any change of state?
|
||||
*
|
||||
* @returns False if something changed.
|
||||
*/
|
||||
override isNull(): boolean {
|
||||
return this.oldContents_ === this.newContents_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a change event.
|
||||
*
|
||||
* @param forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
const workspace = this.getEventWorkspace_();
|
||||
if (!this.commentId) {
|
||||
throw new Error(
|
||||
'The comment ID is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
const comment = workspace.getCommentById(this.commentId);
|
||||
if (!comment) {
|
||||
console.warn('Can\'t change non-existent comment: ' + this.commentId);
|
||||
return;
|
||||
}
|
||||
const contents = forward ? this.newContents_ : this.oldContents_;
|
||||
if (!contents) {
|
||||
if (forward) {
|
||||
throw new Error(
|
||||
'The new contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
throw new Error(
|
||||
'The old contents is undefined. Either pass a value to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
comment.setContent(contents);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentChangeJson extends CommentBaseJson {
|
||||
oldContents: string;
|
||||
newContents: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange);
|
||||
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for comment creation event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for comment creation event.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.CommentCreate');
|
||||
|
||||
const Xml = goog.require('Blockly.Xml');
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {CommentBase} = goog.require('Blockly.Events.CommentBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment creation event.
|
||||
* @extends {CommentBase}
|
||||
* @alias Blockly.Events.CommentCreate
|
||||
*/
|
||||
class CommentCreate extends CommentBase {
|
||||
/**
|
||||
* @param {!WorkspaceComment=} opt_comment The created comment.
|
||||
* Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_comment) {
|
||||
super(opt_comment);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.COMMENT_CREATE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
this.xml = opt_comment.toXmlWithXY();
|
||||
}
|
||||
|
||||
// TODO (#1266): "Full" and "minimal" serialization.
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
json['xml'] = Xml.domToText(this.xml);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
CommentBase.CommentCreateDeleteHelper(this, forward);
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
|
||||
|
||||
exports.CommentCreate = CommentCreate;
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for comment creation event.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
import * as goog from '../../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.Events.CommentCreate');
|
||||
|
||||
import * as registry from '../registry.js';
|
||||
import type {WorkspaceComment} from '../workspace_comment.js';
|
||||
import * as Xml from '../xml.js';
|
||||
|
||||
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
|
||||
import * as eventUtils from './utils.js';
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment creation event.
|
||||
*
|
||||
* @alias Blockly.Events.CommentCreate
|
||||
*/
|
||||
export class CommentCreate extends CommentBase {
|
||||
override type = eventUtils.COMMENT_CREATE;
|
||||
|
||||
xml?: Element|DocumentFragment;
|
||||
|
||||
/**
|
||||
* @param opt_comment The created comment.
|
||||
* Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_comment?: WorkspaceComment) {
|
||||
super(opt_comment);
|
||||
|
||||
if (!opt_comment) {
|
||||
return;
|
||||
}
|
||||
// Blank event to be populated by fromJson.
|
||||
this.xml = opt_comment.toXmlWithXY();
|
||||
}
|
||||
|
||||
// TODO (#1266): "Full" and "minimal" serialization.
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
*
|
||||
* @returns JSON representation.
|
||||
*/
|
||||
override toJson(): CommentCreateJson {
|
||||
const json = super.toJson() as CommentCreateJson;
|
||||
if (!this.xml) {
|
||||
throw new Error(
|
||||
'The comment XML is undefined. Either pass a comment to ' +
|
||||
'the constructor, or call fromJson');
|
||||
}
|
||||
json['xml'] = Xml.domToText(this.xml);
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
*
|
||||
* @param json JSON representation.
|
||||
*/
|
||||
override fromJson(json: CommentCreateJson) {
|
||||
super.fromJson(json);
|
||||
this.xml = Xml.textToDom(json['xml']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
*
|
||||
* @param forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
override run(forward: boolean) {
|
||||
CommentBase.CommentCreateDeleteHelper(this, forward);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CommentCreateJson extends CommentBaseJson {
|
||||
xml: string;
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2018 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Class for comment deletion event.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Class for comment deletion event.
|
||||
* @class
|
||||
*/
|
||||
goog.module('Blockly.Events.CommentDelete');
|
||||
|
||||
const eventUtils = goog.require('Blockly.Events.utils');
|
||||
const registry = goog.require('Blockly.registry');
|
||||
const {CommentBase} = goog.require('Blockly.Events.CommentBase');
|
||||
/* eslint-disable-next-line no-unused-vars */
|
||||
const {WorkspaceComment} = goog.requireType('Blockly.WorkspaceComment');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a comment deletion event.
|
||||
* @extends {CommentBase}
|
||||
* @alias Blockly.Events.CommentDelete
|
||||
*/
|
||||
class CommentDelete extends CommentBase {
|
||||
/**
|
||||
* @param {!WorkspaceComment=} opt_comment The deleted comment.
|
||||
* Undefined for a blank event.
|
||||
*/
|
||||
constructor(opt_comment) {
|
||||
super(opt_comment);
|
||||
|
||||
/**
|
||||
* Type of this event.
|
||||
* @type {string}
|
||||
*/
|
||||
this.type = eventUtils.COMMENT_DELETE;
|
||||
|
||||
if (!opt_comment) {
|
||||
return; // Blank event to be populated by fromJson.
|
||||
}
|
||||
|
||||
this.xml = opt_comment.toXmlWithXY();
|
||||
}
|
||||
|
||||
// TODO (#1266): "Full" and "minimal" serialization.
|
||||
/**
|
||||
* Encode the event as JSON.
|
||||
* @return {!Object} JSON representation.
|
||||
*/
|
||||
toJson() {
|
||||
const json = super.toJson();
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the JSON event.
|
||||
* @param {!Object} json JSON representation.
|
||||
*/
|
||||
fromJson(json) {
|
||||
super.fromJson(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a creation event.
|
||||
* @param {boolean} forward True if run forward, false if run backward (undo).
|
||||
*/
|
||||
run(forward) {
|
||||
CommentBase.CommentCreateDeleteHelper(this, !forward);
|
||||
}
|
||||
}
|
||||
|
||||
registry.register(
|
||||
registry.Type.EVENT, eventUtils.COMMENT_DELETE, CommentDelete);
|
||||
|
||||
exports.CommentDelete = CommentDelete;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user