chore: use prettier instead of clang-format (#7014)

* chore: add and configure prettier

* chore: remove clang-format

* chore: remove clang-format config

* chore: lint additional ts files

* chore: fix lint errors in blocks

* chore: add prettier-ignore where needed

* chore: ignore js blocks when formatting

* chore: fix playground html syntax

* chore: fix yaml spacing from merge

* chore: convert text blocks to use arrow functions

* chore: format everything with prettier

* chore: fix lint unused imports in blocks
This commit is contained in:
Maribeth Bottorff
2023-05-10 16:01:39 -07:00
committed by GitHub
parent af991f5e1b
commit 88ff901a72
425 changed files with 29170 additions and 21169 deletions

View File

@@ -1,3 +0,0 @@
Language: JavaScript
BasedOnStyle: Google
ColumnLimit: 80

View File

@@ -1,7 +1,11 @@
*_compressed*.js
# Build Artifacts
/msg/*
/build/*
/dist/*
/typings/*
/docs/*
# Tests other than mocha unit tests
/tests/blocks/*
/tests/themes/*
/tests/compile/*
@@ -11,10 +15,14 @@
/tests/screenshot/*
/tests/test_runner.js
/tests/workspace_svg/*
# Demos, scripts, misc
/node_modules/*
/generators/*
/demos/*
/appengine/*
/externs/*
/closure/*
/scripts/gulpfiles/*
/typings/*
CHANGELOG.md
PULL_REQUEST_TEMPLATE.md

View File

@@ -1,19 +1,4 @@
const rules = {
'curly': ['error'],
'eol-last': ['error'],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'max-len': [
'error',
{
'code': 100,
'tabWidth': 4,
'ignoreStrings': true,
'ignoreRegExpLiterals': true,
'ignoreUrls': true,
},
],
'no-trailing-spaces': ['error', {'skipBlankLines': true}],
'no-unused-vars': [
'warn',
{
@@ -29,20 +14,12 @@ const rules = {
// Blockly uses single quotes except for JSON blobs, which must use double
// quotes.
'quotes': ['off'],
'semi': ['error', 'always'],
// Blockly doesn't have space before function paren when defining functions.
'space-before-function-paren': ['error', 'never'],
// Blockly doesn't have space before function paren when calling functions.
'func-call-spacing': ['error', 'never'],
'space-infix-ops': ['error'],
// Blockly uses 'use strict' in files.
'strict': ['off'],
// Closure style allows redeclarations.
'no-redeclare': ['off'],
'valid-jsdoc': ['error'],
'no-console': ['off'],
'no-multi-spaces': ['error', {'ignoreEOLComments': true}],
'operator-linebreak': ['error', 'after'],
'spaced-comment': [
'error',
'always',
@@ -61,27 +38,13 @@ const rules = {
'allow': ['^opt_', '^_opt_', '^testOnly_'],
},
],
// Use clang-format for indentation by running `npm run format`.
'indent': ['off'],
// Blockly uses capital letters for some non-constructor namespaces.
// Keep them for legacy reasons.
'new-cap': ['off'],
// Mostly use default rules for brace style, but allow single-line blocks.
'brace-style': ['error', '1tbs', {'allowSingleLine': true}],
// Blockly uses objects as maps, but uses Object.create(null) to
// instantiate them.
'guard-for-in': ['off'],
'prefer-spread': ['off'],
'comma-dangle': [
'error',
{
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'always-multiline',
'exports': 'always-multiline',
'functions': 'ignore',
},
],
};
/**
@@ -92,10 +55,7 @@ const rules = {
function buildTSOverride({files, tsconfig}) {
return {
'files': files,
'plugins': [
'@typescript-eslint/eslint-plugin',
'jsdoc',
],
'plugins': ['@typescript-eslint/eslint-plugin', 'jsdoc'],
'settings': {
'jsdoc': {
'mode': 'typescript',
@@ -111,6 +71,7 @@ function buildTSOverride({files, tsconfig}) {
'extends': [
'plugin:@typescript-eslint/recommended',
'plugin:jsdoc/recommended',
'prettier', // Extend again so that these rules are applied last
],
'rules': {
// TS rules
@@ -130,8 +91,6 @@ function buildTSOverride({files, tsconfig}) {
'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.
@@ -195,21 +154,15 @@ const eslintJSON = {
'goog': true,
'exports': true,
},
'extends': [
'eslint:recommended',
'google',
],
'extends': ['eslint:recommended', 'google', 'prettier'],
// TypeScript-specific config. Uses above rules plus these.
'overrides': [
buildTSOverride({
files: ['./core/**/*.ts', './core/**/*.tsx'],
files: ['./**/*.ts', './**/*.tsx'],
tsconfig: './tsconfig.json',
}),
buildTSOverride({
files: [
'./tests/typescript/**/*.ts',
'./tests/typescript/**/*.tsx',
],
files: ['./tests/typescript/**/*.ts', './tests/typescript/**/*.tsx'],
tsconfig: './tests/typescript/tsconfig.json',
}),
{

View File

@@ -1,6 +1,7 @@
# Contributing to Blockly
Want to contribute? Great!
- First, read this page (including the small print at the end).
- Second, please make pull requests against develop, not master. If your patch
needs to go into master immediately, include a note in your PR.
@@ -8,6 +9,7 @@ Want to contribute? Great!
For more information on style guide and other details, head over to the [Blockly Developers site](https://developers.google.com/blockly/guides/modify/contributing).
### Before you contribute
Before we can use your code, you must sign the
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
(CLA), which you can do online. The CLA is necessary mainly because you own the
@@ -19,22 +21,26 @@ the CLA until after you've submitted your code for review and a member has
approved it, but you must do it before we can put your code into our codebase.
### Larger changes
Before you start working on a larger contribution, you should get in touch with
us first through the issue tracker with your idea so that we can help out and
possibly guide you. Coordinating up front makes it much easier to avoid
frustration later on.
### Code reviews
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 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/)
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
Contributions made by corporations are covered by a different agreement than
the one above, the
[Software Grant and Corporate Contributor License Agreement](https://cla.developers.google.com/about/google-corporate).

View File

@@ -30,7 +30,7 @@ body:
value: |
1.
2.
3.
3.
- type: textarea
id: stack-trace
attributes:

View File

@@ -1,4 +1,3 @@
name: Documentation
description: Report an issue with our documentation
labels: 'issue: docs, issue: triage'

View File

@@ -1,4 +1,3 @@
name: Feature request
description: Suggest an idea for this project
labels: 'issue: feature request, issue: triage'

View File

@@ -5,23 +5,23 @@
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
target-branch: "develop"
- package-ecosystem: 'npm' # See documentation for possible values
directory: '/' # Location of package manifests
target-branch: 'develop'
schedule:
interval: "weekly"
interval: 'weekly'
commit-message:
prefix: "chore(deps)"
prefix: 'chore(deps)'
labels:
- "PR: chore"
- "PR: dependencies"
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/"
target-branch: "develop"
- 'PR: chore'
- 'PR: dependencies'
- package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/'
target-branch: 'develop'
schedule:
interval: "weekly"
interval: 'weekly'
commit-message:
prefix: "chore(deps)"
prefix: 'chore(deps)'
labels:
- "PR: chore"
- "PR: dependencies"
- 'PR: chore'
- 'PR: dependencies'

14
.github/release.yml vendored
View File

@@ -4,7 +4,7 @@ changelog:
exclude:
labels:
- ignore-for-release
- "PR: chore"
- 'PR: chore'
authors:
- dependabot
categories:
@@ -16,17 +16,17 @@ changelog:
- deprecation
- title: New features ✨
labels:
- "PR: feature"
- 'PR: feature'
- title: Bug fixes 🐛
labels:
- "PR: fix"
- 'PR: fix'
- title: Cleanup ♻️
labels:
- "PR: docs"
- "PR: refactor"
- 'PR: docs'
- 'PR: refactor'
- title: Reverted changes ⎌
labels:
- "PR: revert"
- 'PR: revert'
- title: Other changes
labels:
- "*"
- '*'

View File

@@ -16,26 +16,26 @@ jobs:
requested-reviewer:
runs-on: ubuntu-latest
steps:
- name: Assign requested reviewer
uses: actions/github-script@v6
with:
script: |
try {
if (context.payload.pull_request === undefined) {
throw new Error("Can't get pull_request payload. " +
'Check a request reviewer event was triggered.');
- name: Assign requested reviewer
uses: actions/github-script@v6
with:
script: |
try {
if (context.payload.pull_request === undefined) {
throw new Error("Can't get pull_request payload. " +
'Check a request reviewer event was triggered.');
}
const reviewers = context.payload.pull_request.requested_reviewers;
// Assignees takes in a list of logins rather than the
// reviewer object.
const reviewerNames = reviewers.map(reviewer => reviewer.login);
const {number:issue_number} = context.payload.pull_request;
github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
assignees: reviewerNames
});
} catch (error) {
core.setFailed(error.message);
}
const reviewers = context.payload.pull_request.requested_reviewers;
// Assignees takes in a list of logins rather than the
// reviewer object.
const reviewerNames = reviewers.map(reviewer => reviewer.login);
const {number:issue_number} = context.payload.pull_request;
github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
assignees: reviewerNames
});
} catch (error) {
core.setFailed(error.message);
}

View File

@@ -23,65 +23,67 @@ jobs:
# https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
- uses: actions/checkout@v3
with:
persist-credentials: false
- name: Reconfigure git to use HTTP authentication
run: >
git config --global url."https://github.com/".insteadOf
ssh://git@github.com/
- name: Reconfigure git to use HTTP authentication
run: >
git config --global url."https://github.com/".insteadOf
ssh://git@github.com/
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Npm Install
run: npm install
- name: Npm Install
run: npm install
- name: Linux Test Setup
if: runner.os == 'Linux'
run: source ./tests/scripts/setup_linux_env.sh
- name: Linux Test Setup
if: runner.os == 'Linux'
run: source ./tests/scripts/setup_linux_env.sh
- name: MacOS Test Setup
if: runner.os == 'macOS'
run: source ./tests/scripts/setup_osx_env.sh
- name: MacOS Test Setup
if: runner.os == 'macOS'
run: source ./tests/scripts/setup_osx_env.sh
- name: Run
run: npm run test
- name: Run
run: npm run test
env:
CI: true
env:
CI: true
lint:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Npm Install
run: npm install
- name: Npm Install
run: npm install
- name: Lint
run: npm run lint
- name: Lint
run: npm run lint
clang-formatter:
format:
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.16
with:
source: 'core'
extensions: 'js,ts'
# This should be as close as possible to the version that the npm
# package supports. This can be found by running:
# npx clang-format --version.
clangFormatVersion: 15
- name: Use Node.js 20.x
uses: actions/setup-node@v3
with:
node-version: 20.x
- name: Npm Install
run: npm install
- name: Check Format
run: npm run format:check

View File

@@ -10,7 +10,8 @@ jobs:
steps:
- uses: bcoe/conventional-release-labels@v1
with:
type_labels: '{"feat": "PR: feature", "fix": "PR: fix", "breaking": "breaking
type_labels:
'{"feat": "PR: feature", "fix": "PR: fix", "breaking": "breaking
change", "chore": "PR: chore", "docs": "PR: docs", "refactor": "PR:
refactor", "revert": "PR: revert", "deprecate": "deprecation"}'
ignored_types: '[]'

View File

@@ -23,4 +23,4 @@ jobs:
uses: github-actions-up-and-running/pr-comment@f1f8ab2bf00dce6880a369ce08758a60c61d6c0b
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
message: "Thanks for the PR! The develop branch is currently frozen in preparation for the release so it may not be addressed until after release week."
message: 'Thanks for the PR! The develop branch is currently frozen in preparation for the release so it may not be addressed until after release week.'

View File

@@ -12,7 +12,6 @@ on:
jobs:
tag-module-cleanup:
# Add the type: cleanup label
runs-on: ubuntu-latest
steps:

31
.prettierignore Normal file
View File

@@ -0,0 +1,31 @@
# Build Artifacts
/msg/*
/build/*
/dist/*
/typings/*
/docs/*
# Tests other than mocha unit tests
/tests/blocks/*
/tests/themes/*
/tests/compile/*
/tests/jsunit/*
/tests/generators/*
/tests/mocha/webdriver.js
/tests/screenshot/*
/tests/test_runner.js
/tests/workspace_svg/*
# Demos, scripts, misc
/node_modules/*
/generators/*
/demos/*
/appengine/*
/externs/*
/closure/*
/scripts/gulpfiles/*
CHANGELOG.md
PULL_REQUEST_TEMPLATE.md
# Don't bother formatting js blocks since we're getting rid of them
/blocks/*.js

13
.prettierrc.js Normal file
View File

@@ -0,0 +1,13 @@
// This config attempts to match google-style code.
module.exports = {
// Prefer single quotes, but minimize escaping.
singleQuote: true,
// Some properties must be quoted to preserve closure compiler behavior.
// Don't ever change whether properties are quoted.
quoteProps: 'preserve',
// Don't add spaces around braces for object literals.
bracketSpacing: false,
// Put HTML tag closing brackets on same line as last attribute.
bracketSameLine: true,
};

View File

@@ -1,6 +1,6 @@
# 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.
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.
![](https://developers.google.com/blockly/images/sample.png)
@@ -8,13 +8,13 @@ Google's Blockly is a library that adds a visual code editor to web and mobile a
Blockly has many resources for learning how to use the library. Start at our [Google Developers Site](https://developers.google.com/blockly) to read the documentation on how to get started, configure Blockly, and integrate it into your application. The developers site also contains links to:
* [Getting Started article](https://developers.google.com/blockly/guides/get-started/web)
* [Getting Started codelab](https://blocklycodelabs.dev/codelabs/getting-started/index.html#0)
* [More codelabs](https://blocklycodelabs.dev/)
* [Demos and plugins](https://google.github.io/blockly-samples/)
- [Getting Started article](https://developers.google.com/blockly/guides/get-started/web)
- [Getting Started codelab](https://blocklycodelabs.dev/codelabs/getting-started/index.html#0)
- [More codelabs](https://blocklycodelabs.dev/)
- [Demos and plugins](https://google.github.io/blockly-samples/)
Help us focus our development efforts by telling us [what you are doing with
Blockly](https://developers.google.com/blockly/registration). The questionnaire only takes
Blockly](https://developers.google.com/blockly/registration). The questionnaire only takes
a few minutes and will help us better support the Blockly community.
### Installing Blockly
@@ -28,8 +28,9 @@ npm install blockly
For more information on installing and using Blockly, see the [Getting Started article](https://developers.google.com/blockly/guides/get-started/web).
### Getting Help
* [Report a bug](https://developers.google.com/blockly/guides/modify/contribute/write_a_good_issue) or file a feature request on GitHub
* Ask a question, or search others' questions, on our [developer forum](https://groups.google.com/forum/#!forum/blockly). You can also drop by to say hello and show us your prototypes; collectively we have a lot of experience and can offer hints which will save you time. We actively monitor the forums and typically respond to questions within 2 working days.
- [Report a bug](https://developers.google.com/blockly/guides/modify/contribute/write_a_good_issue) or file a feature request on GitHub
- Ask a question, or search others' questions, on our [developer forum](https://groups.google.com/forum/#!forum/blockly). You can also drop by to say hello and show us your prototypes; collectively we have a lot of experience and can offer hints which will save you time. We actively monitor the forums and typically respond to questions within 2 working days.
### blockly-samples
@@ -50,6 +51,7 @@ We now have a [beta release on npm](https://www.npmjs.com/package/blockly?active
```bash
npm install blockly@beta
```
As it is a beta channel, it may be less stable, and the APIs there are subject to change.
### Branches
@@ -82,5 +84,5 @@ We typically triage all bugs within 2 working days, which includes adding any ap
## Good to Know
* Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com)
* We test browsers using [BrowserStack](https://browserstack.com)
- Cross-browser Testing Platform and Open Source <3 Provided by [Sauce Labs](https://saucelabs.com)
- We test browsers using [BrowserStack](https://browserstack.com)

View File

@@ -109,7 +109,7 @@
/**
* (REQUIRED) Whether to generate an API report.
*/
"enabled": false,
"enabled": false
/**
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
@@ -195,7 +195,7 @@
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
*/
"untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>_rollup.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.

View File

@@ -11,11 +11,12 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.libraryBlocks.colour');
import type {BlockDefinition} from '../core/blocks.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_colour.js';
/**
* A dictionary of the block definitions provided by this module.
*/
@@ -52,7 +53,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'colour_rgb',
'message0':
'%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3',
'%{BKY_COLOUR_RGB_TITLE} %{BKY_COLOUR_RGB_RED} %1 %{BKY_COLOUR_RGB_GREEN} %2 %{BKY_COLOUR_RGB_BLUE} %3',
'args0': [
{
'type': 'input_value',
@@ -82,8 +83,9 @@ export const blocks = createBlockDefinitionsFromJsonArray([
// Block for blending two colours together.
{
'type': 'colour_blend',
'message0': '%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} ' +
'%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3',
'message0':
'%{BKY_COLOUR_BLEND_TITLE} %{BKY_COLOUR_BLEND_COLOUR1} ' +
'%1 %{BKY_COLOUR_BLEND_COLOUR2} %2 %{BKY_COLOUR_BLEND_RATIO} %3',
'args0': [
{
'type': 'input_value',

View File

@@ -18,17 +18,17 @@ import {Align} from '../core/inputs/input.js';
import type {Block} from '../core/block.js';
import type {Connection} from '../core/connection.js';
import type {BlockSvg} from '../core/block_svg.js';
import type {BlockDefinition} from '../core/blocks.js';
import {ConnectionType} from '../core/connection_type.js';
import type {FieldDropdown} from '../core/field_dropdown.js';
import {Msg} from '../core/msg.js';
import {Mutator} from '../core/mutator.js';
import type {Workspace} from '../core/workspace.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_dropdown.js';
import {ValueInput} from '../core/inputs/value_input.js';
/**
* A dictionary of the block definitions provided by this module.
*/
@@ -117,9 +117,8 @@ export const blocks = createBlockDefinitionsFromJsonArray([
},
]);
/** Type of a 'lists_create_with' block. */
type CreateWithBlock = Block&ListCreateWithMixin;
type CreateWithBlock = Block & ListCreateWithMixin;
interface ListCreateWithMixin extends ListCreateWithMixinType {
itemCount_: number;
}
@@ -129,22 +128,22 @@ const LISTS_CREATE_WITH = {
/**
* Block for creating a list with any number of elements of any type.
*/
init: function(this: CreateWithBlock) {
init: function (this: CreateWithBlock) {
this.setHelpUrl(Msg['LISTS_CREATE_WITH_HELPURL']);
this.setStyle('list_blocks');
this.itemCount_ = 3;
this.updateShape_();
this.setOutput(true, 'Array');
this.setMutator(new Mutator(
['lists_create_with_item'],
this as unknown as BlockSvg)); // BUG(#6905)
this.setMutator(
new Mutator(['lists_create_with_item'], this as unknown as BlockSvg)
); // BUG(#6905)
this.setTooltip(Msg['LISTS_CREATE_WITH_TOOLTIP']);
},
/**
* Create XML to represent list inputs.
* Backwards compatible serialization implementation.
*/
mutationToDom: function(this: CreateWithBlock): Element {
mutationToDom: function (this: CreateWithBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('items', String(this.itemCount_));
return container;
@@ -155,7 +154,7 @@ const LISTS_CREATE_WITH = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: CreateWithBlock, xmlElement: Element) {
domToMutation: function (this: CreateWithBlock, xmlElement: Element) {
const items = xmlElement.getAttribute('items');
if (!items) throw new TypeError('element did not have items');
this.itemCount_ = parseInt(items, 10);
@@ -166,7 +165,7 @@ const LISTS_CREATE_WITH = {
*
* @return The state of this block, ie the item count.
*/
saveExtraState: function(this: CreateWithBlock): {itemCount: number} {
saveExtraState: function (this: CreateWithBlock): {itemCount: number} {
return {
'itemCount': this.itemCount_,
};
@@ -176,7 +175,7 @@ const LISTS_CREATE_WITH = {
*
* @param state The state to apply to this block, ie the item count.
*/
loadExtraState: function(this: CreateWithBlock, state: AnyDuringMigration) {
loadExtraState: function (this: CreateWithBlock, state: AnyDuringMigration) {
this.itemCount_ = state['itemCount'];
this.updateShape_();
},
@@ -186,32 +185,37 @@ const LISTS_CREATE_WITH = {
* @param workspace Mutator's workspace.
* @return Root block in mutator.
*/
decompose: function(this: CreateWithBlock, workspace: Workspace):
ContainerBlock {
const containerBlock =
workspace.newBlock('lists_create_with_container') as ContainerBlock;
(containerBlock as BlockSvg).initSvg();
let connection = containerBlock.getInput('STACK')!.connection;
for (let i = 0; i < this.itemCount_; i++) {
const itemBlock =
workspace.newBlock('lists_create_with_item') as ItemBlock;
(itemBlock as BlockSvg).initSvg();
if (!itemBlock.previousConnection) {
throw new Error('itemBlock has no previousConnection');
}
connection!.connect(itemBlock.previousConnection);
connection = itemBlock.nextConnection;
}
return containerBlock;
},
decompose: function (
this: CreateWithBlock,
workspace: Workspace
): ContainerBlock {
const containerBlock = workspace.newBlock(
'lists_create_with_container'
) as ContainerBlock;
(containerBlock as BlockSvg).initSvg();
let connection = containerBlock.getInput('STACK')!.connection;
for (let i = 0; i < this.itemCount_; i++) {
const itemBlock = workspace.newBlock(
'lists_create_with_item'
) as ItemBlock;
(itemBlock as BlockSvg).initSvg();
if (!itemBlock.previousConnection) {
throw new Error('itemBlock has no previousConnection');
}
connection!.connect(itemBlock.previousConnection);
connection = itemBlock.nextConnection;
}
return containerBlock;
},
/**
* Reconfigure this block based on the mutator dialog's components.
*
* @param containerBlock Root block in mutator.
*/
compose: function(this: CreateWithBlock, containerBlock: Block) {
let itemBlock: ItemBlock|null =
containerBlock.getInputTargetBlock('STACK') as ItemBlock;
compose: function (this: CreateWithBlock, containerBlock: Block) {
let itemBlock: ItemBlock | null = containerBlock.getInputTargetBlock(
'STACK'
) as ItemBlock;
// Count number of inputs.
const connections: Connection[] = [];
while (itemBlock) {
@@ -241,9 +245,10 @@ const LISTS_CREATE_WITH = {
*
* @param containerBlock Root block in mutator.
*/
saveConnections: function(this: CreateWithBlock, containerBlock: Block) {
let itemBlock: ItemBlock|null =
containerBlock.getInputTargetBlock('STACK') as ItemBlock;
saveConnections: function (this: CreateWithBlock, containerBlock: Block) {
let itemBlock: ItemBlock | null = containerBlock.getInputTargetBlock(
'STACK'
) as ItemBlock;
let i = 0;
while (itemBlock) {
if (itemBlock.isInsertionMarker()) {
@@ -251,8 +256,8 @@ const LISTS_CREATE_WITH = {
continue;
}
const input = this.getInput('ADD' + i);
itemBlock.valueConnection_ =
input?.connection!.targetConnection as Connection;
itemBlock.valueConnection_ = input?.connection!
.targetConnection as Connection;
itemBlock = itemBlock.getNextBlock() as ItemBlock | null;
i++;
}
@@ -260,12 +265,13 @@ const LISTS_CREATE_WITH = {
/**
* Modify this block to have the correct number of inputs.
*/
updateShape_: function(this: CreateWithBlock) {
updateShape_: function (this: CreateWithBlock) {
if (this.itemCount_ && this.getInput('EMPTY')) {
this.removeInput('EMPTY');
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
this.appendDummyInput('EMPTY').appendField(
Msg['LISTS_CREATE_EMPTY_TITLE']);
Msg['LISTS_CREATE_EMPTY_TITLE']
);
}
// Add new inputs.
for (let i = 0; i < this.itemCount_; i++) {
@@ -285,7 +291,7 @@ const LISTS_CREATE_WITH = {
blocks['lists_create_with'] = LISTS_CREATE_WITH;
/** Type for a 'lists_create_with_container' block. */
type ContainerBlock = Block&ContainerMutator;
type ContainerBlock = Block & ContainerMutator;
interface ContainerMutator extends ContainerMutatorType {}
type ContainerMutatorType = typeof LISTS_CREATE_WITH_CONTAINER;
@@ -293,10 +299,11 @@ const LISTS_CREATE_WITH_CONTAINER = {
/**
* Mutator block for list container.
*/
init: function(this: ContainerBlock) {
init: function (this: ContainerBlock) {
this.setStyle('list_blocks');
this.appendDummyInput().appendField(
Msg['LISTS_CREATE_WITH_CONTAINER_TITLE_ADD']);
Msg['LISTS_CREATE_WITH_CONTAINER_TITLE_ADD']
);
this.appendStatementInput('STACK');
this.setTooltip(Msg['LISTS_CREATE_WITH_CONTAINER_TOOLTIP']);
this.contextMenu = false;
@@ -305,7 +312,7 @@ const LISTS_CREATE_WITH_CONTAINER = {
blocks['lists_create_with_container'] = LISTS_CREATE_WITH_CONTAINER;
/** Type for a 'lists_create_with_item' block. */
type ItemBlock = Block&ItemMutator;
type ItemBlock = Block & ItemMutator;
interface ItemMutator extends ItemMutatorType {
valueConnection_?: Connection;
}
@@ -315,7 +322,7 @@ const LISTS_CREATE_WITH_ITEM = {
/**
* Mutator block for adding items.
*/
init: function(this: ItemBlock) {
init: function (this: ItemBlock) {
this.setStyle('list_blocks');
this.appendDummyInput().appendField(Msg['LISTS_CREATE_WITH_ITEM_TITLE']);
this.setPreviousStatement(true);
@@ -327,7 +334,7 @@ const LISTS_CREATE_WITH_ITEM = {
blocks['lists_create_with_item'] = LISTS_CREATE_WITH_ITEM;
/** Type for a 'lists_indexOf' block. */
type IndexOfBlock = Block&IndexOfMutator;
type IndexOfBlock = Block & IndexOfMutator;
interface IndexOfMutator extends IndexOfMutatorType {}
type IndexOfMutatorType = typeof LISTS_INDEXOF;
@@ -335,7 +342,7 @@ const LISTS_INDEXOF = {
/**
* Block for finding an item in the list.
*/
init: function(this: IndexOfBlock) {
init: function (this: IndexOfBlock) {
const OPERATORS = [
[Msg['LISTS_INDEX_OF_FIRST'], 'FIRST'],
[Msg['LISTS_INDEX_OF_LAST'], 'LAST'],
@@ -343,8 +350,9 @@ const LISTS_INDEXOF = {
this.setHelpUrl(Msg['LISTS_INDEX_OF_HELPURL']);
this.setStyle('list_blocks');
this.setOutput(true, 'Number');
this.appendValueInput('VALUE').setCheck('Array').appendField(
Msg['LISTS_INDEX_OF_INPUT_IN_LIST']);
this.appendValueInput('VALUE')
.setCheck('Array')
.appendField(Msg['LISTS_INDEX_OF_INPUT_IN_LIST']);
const operatorsDropdown = fieldRegistry.fromJson({
type: 'field_dropdown',
options: OPERATORS,
@@ -352,18 +360,18 @@ const LISTS_INDEXOF = {
if (!operatorsDropdown) throw new Error('field_dropdown not found');
this.appendValueInput('FIND').appendField(operatorsDropdown, 'END');
this.setInputsInline(true);
// Assign 'this' to a variable for use in the tooltip closure below.
const thisBlock = this;
this.setTooltip(function() {
this.setTooltip(() => {
return Msg['LISTS_INDEX_OF_TOOLTIP'].replace(
'%1', thisBlock.workspace.options.oneBasedIndex ? '0' : '-1');
'%1',
this.workspace.options.oneBasedIndex ? '0' : '-1'
);
});
},
};
blocks['lists_indexOf'] = LISTS_INDEXOF;
/** Type for a 'lists_getIndex' block. */
type GetIndexBlock = Block&GetIndexMutator;
type GetIndexBlock = Block & GetIndexMutator;
interface GetIndexMutator extends GetIndexMutatorType {
WHERE_OPTIONS: Array<[string, string]>;
}
@@ -373,7 +381,7 @@ const LISTS_GETINDEX = {
/**
* Block for getting element at index.
*/
init: function(this: GetIndexBlock) {
init: function (this: GetIndexBlock) {
const MODE = [
[Msg['LISTS_GET_INDEX_GET'], 'GET'],
[Msg['LISTS_GET_INDEX_GET_REMOVE'], 'GET_REMOVE'],
@@ -393,18 +401,19 @@ const LISTS_GETINDEX = {
options: MODE,
}) as FieldDropdown;
modeMenu.setValidator(
/** @param value The input value. */
function(this: FieldDropdown, value: string) {
const isStatement = (value === 'REMOVE');
(this.getSourceBlock() as GetIndexBlock)
.updateStatement_(isStatement);
return undefined;
});
this.appendValueInput('VALUE').setCheck('Array').appendField(
Msg['LISTS_GET_INDEX_INPUT_IN_LIST']);
/** @param value The input value. */
function (this: FieldDropdown, value: string) {
const isStatement = value === 'REMOVE';
(this.getSourceBlock() as GetIndexBlock).updateStatement_(isStatement);
return undefined;
}
);
this.appendValueInput('VALUE')
.setCheck('Array')
.appendField(Msg['LISTS_GET_INDEX_INPUT_IN_LIST']);
this.appendDummyInput()
.appendField(modeMenu, 'MODE')
.appendField('', 'SPACE');
.appendField(modeMenu, 'MODE')
.appendField('', 'SPACE');
this.appendDummyInput('AT');
if (Msg['LISTS_GET_INDEX_TAIL']) {
this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_INDEX_TAIL']);
@@ -412,11 +421,9 @@ const LISTS_GETINDEX = {
this.setInputsInline(true);
this.setOutput(true);
this.updateAt_(true);
// Assign 'this' to a variable for use in the tooltip closure below.
const thisBlock = this;
this.setTooltip(function() {
const mode = thisBlock.getFieldValue('MODE');
const where = thisBlock.getFieldValue('WHERE');
this.setTooltip(() => {
const mode = this.getFieldValue('MODE');
const where = this.getFieldValue('WHERE');
let tooltip = '';
switch (mode + ' ' + where) {
case 'GET FROM_START':
@@ -460,12 +467,13 @@ const LISTS_GETINDEX = {
break;
}
if (where === 'FROM_START' || where === 'FROM_END') {
const msg = (where === 'FROM_START') ?
Msg['LISTS_INDEX_FROM_START_TOOLTIP'] :
Msg['LISTS_INDEX_FROM_END_TOOLTIP'];
tooltip += ' ' +
msg.replace(
'%1', thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
const msg =
where === 'FROM_START'
? Msg['LISTS_INDEX_FROM_START_TOOLTIP']
: Msg['LISTS_INDEX_FROM_END_TOOLTIP'];
tooltip +=
' ' +
msg.replace('%1', this.workspace.options.oneBasedIndex ? '#1' : '#0');
}
return tooltip;
});
@@ -476,7 +484,7 @@ const LISTS_GETINDEX = {
*
* @return XML storage element.
*/
mutationToDom: function(this: GetIndexBlock): Element {
mutationToDom: function (this: GetIndexBlock): Element {
const container = xmlUtils.createElement('mutation');
const isStatement = !this.outputConnection;
container.setAttribute('statement', String(isStatement));
@@ -489,12 +497,12 @@ const LISTS_GETINDEX = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: GetIndexBlock, xmlElement: Element) {
domToMutation: function (this: GetIndexBlock, xmlElement: Element) {
// Note: Until January 2013 this block did not have mutations,
// so 'statement' defaults to false and 'at' defaults to true.
const isStatement = (xmlElement.getAttribute('statement') === 'true');
const isStatement = xmlElement.getAttribute('statement') === 'true';
this.updateStatement_(isStatement);
const isAt = (xmlElement.getAttribute('at') !== 'false');
const isAt = xmlElement.getAttribute('at') !== 'false';
this.updateAt_(isAt);
},
/**
@@ -503,9 +511,9 @@ const LISTS_GETINDEX = {
*
* @return The state of this block, ie whether it's a statement.
*/
saveExtraState: function(this: GetIndexBlock): ({
isStatement: boolean
} | null) {
saveExtraState: function (this: GetIndexBlock): {
isStatement: boolean;
} | null {
if (!this.outputConnection) {
return {
isStatement: true,
@@ -520,7 +528,7 @@ const LISTS_GETINDEX = {
* @param state The state to apply to this block, ie whether it's a
* statement.
*/
loadExtraState: function(this: GetIndexBlock, state: AnyDuringMigration) {
loadExtraState: function (this: GetIndexBlock, state: AnyDuringMigration) {
if (state['isStatement']) {
this.updateStatement_(true);
} else if (typeof state === 'string') {
@@ -535,7 +543,7 @@ const LISTS_GETINDEX = {
* @param newStatement True if the block should be a statement.
* False if the block should be a value.
*/
updateStatement_: function(this: GetIndexBlock, newStatement: boolean) {
updateStatement_: function (this: GetIndexBlock, newStatement: boolean) {
const oldStatement = !this.outputConnection;
if (newStatement !== oldStatement) {
// TODO(#6920): The .unplug only has one parameter.
@@ -556,7 +564,7 @@ const LISTS_GETINDEX = {
*
* @param isAt True if the input should exist.
*/
updateAt_: function(this: GetIndexBlock, isAt: boolean) {
updateAt_: function (this: GetIndexBlock, isAt: boolean) {
// Destroy old 'AT' and 'ORDINAL' inputs.
this.removeInput('AT');
this.removeInput('ORDINAL', true);
@@ -565,7 +573,8 @@ const LISTS_GETINDEX = {
this.appendValueInput('AT').setCheck('Number');
if (Msg['ORDINAL_NUMBER_SUFFIX']) {
this.appendDummyInput('ORDINAL').appendField(
Msg['ORDINAL_NUMBER_SUFFIX']);
Msg['ORDINAL_NUMBER_SUFFIX']
);
}
} else {
this.appendDummyInput('AT');
@@ -575,24 +584,25 @@ const LISTS_GETINDEX = {
options: this.WHERE_OPTIONS,
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function(this: FieldDropdown, value: string) {
const newAt = (value === 'FROM_START') || (value === 'FROM_END');
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as GetIndexBlock;
block.updateAt_(newAt);
// This menu has been destroyed and replaced. Update the
// replacement.
block.setFieldValue(value, 'WHERE');
return null;
}
return undefined;
});
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function (this: FieldDropdown, value: string) {
const newAt = value === 'FROM_START' || value === 'FROM_END';
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as GetIndexBlock;
block.updateAt_(newAt);
// This menu has been destroyed and replaced. Update the
// replacement.
block.setFieldValue(value, 'WHERE');
return null;
}
return undefined;
}
);
this.getInput('AT')!.appendField(menu, 'WHERE');
if (Msg['LISTS_GET_INDEX_TAIL']) {
this.moveInputBefore('TAIL', null);
@@ -602,7 +612,7 @@ const LISTS_GETINDEX = {
blocks['lists_getIndex'] = LISTS_GETINDEX;
/** Type for a 'lists_setIndex' block. */
type SetIndexBlock = Block&SetIndexMutator;
type SetIndexBlock = Block & SetIndexMutator;
interface SetIndexMutator extends SetIndexMutatorType {
WHERE_OPTIONS: Array<[string, string]>;
}
@@ -612,7 +622,7 @@ const LISTS_SETINDEX = {
/**
* Block for setting the element at index.
*/
init: function(this: SetIndexBlock) {
init: function (this: SetIndexBlock) {
const MODE = [
[Msg['LISTS_SET_INDEX_SET'], 'SET'],
[Msg['LISTS_SET_INDEX_INSERT'], 'INSERT'],
@@ -626,15 +636,16 @@ const LISTS_SETINDEX = {
];
this.setHelpUrl(Msg['LISTS_SET_INDEX_HELPURL']);
this.setStyle('list_blocks');
this.appendValueInput('LIST').setCheck('Array').appendField(
Msg['LISTS_SET_INDEX_INPUT_IN_LIST']);
this.appendValueInput('LIST')
.setCheck('Array')
.appendField(Msg['LISTS_SET_INDEX_INPUT_IN_LIST']);
const operationDropdown = fieldRegistry.fromJson({
type: 'field_dropdown',
options: MODE,
}) as FieldDropdown;
this.appendDummyInput()
.appendField(operationDropdown, 'MODE')
.appendField('', 'SPACE');
.appendField(operationDropdown, 'MODE')
.appendField('', 'SPACE');
this.appendDummyInput('AT');
this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']);
this.setInputsInline(true);
@@ -642,11 +653,9 @@ const LISTS_SETINDEX = {
this.setNextStatement(true);
this.setTooltip(Msg['LISTS_SET_INDEX_TOOLTIP']);
this.updateAt_(true);
// Assign 'this' to a variable for use in the tooltip closure below.
const thisBlock = this;
this.setTooltip(function() {
const mode = thisBlock.getFieldValue('MODE');
const where = thisBlock.getFieldValue('WHERE');
this.setTooltip(() => {
const mode = this.getFieldValue('MODE');
const where = this.getFieldValue('WHERE');
let tooltip = '';
switch (mode + ' ' + where) {
case 'SET FROM_START':
@@ -677,9 +686,12 @@ const LISTS_SETINDEX = {
break;
}
if (where === 'FROM_START' || where === 'FROM_END') {
tooltip += ' ' +
Msg['LISTS_INDEX_FROM_START_TOOLTIP'].replace(
'%1', thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
tooltip +=
' ' +
Msg['LISTS_INDEX_FROM_START_TOOLTIP'].replace(
'%1',
this.workspace.options.oneBasedIndex ? '#1' : '#0'
);
}
return tooltip;
});
@@ -689,7 +701,7 @@ const LISTS_SETINDEX = {
*
* @return XML storage element.
*/
mutationToDom: function(this: SetIndexBlock): Element {
mutationToDom: function (this: SetIndexBlock): Element {
const container = xmlUtils.createElement('mutation');
const isAt = this.getInput('AT') instanceof ValueInput;
container.setAttribute('at', String(isAt));
@@ -700,10 +712,10 @@ const LISTS_SETINDEX = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: SetIndexBlock, xmlElement: Element) {
domToMutation: function (this: SetIndexBlock, xmlElement: Element) {
// Note: Until January 2013 this block did not have mutations,
// so 'at' defaults to true.
const isAt = (xmlElement.getAttribute('at') !== 'false');
const isAt = xmlElement.getAttribute('at') !== 'false';
this.updateAt_(isAt);
},
@@ -715,7 +727,7 @@ const LISTS_SETINDEX = {
*
* @return The state of this block.
*/
saveExtraState: function(this: SetIndexBlock): null {
saveExtraState: function (this: SetIndexBlock): null {
return null;
},
@@ -724,14 +736,14 @@ const LISTS_SETINDEX = {
* No extra state is needed or expected as it is already encoded in the
* dropdown values.
*/
loadExtraState: function(this: SetIndexBlock) {},
loadExtraState: function (this: SetIndexBlock) {},
/**
* Create or delete an input for the numeric index.
*
* @param isAt True if the input should exist.
*/
updateAt_: function(this: SetIndexBlock, isAt: boolean) {
updateAt_: function (this: SetIndexBlock, isAt: boolean) {
// Destroy old 'AT' and 'ORDINAL' input.
this.removeInput('AT');
this.removeInput('ORDINAL', true);
@@ -740,7 +752,8 @@ const LISTS_SETINDEX = {
this.appendValueInput('AT').setCheck('Number');
if (Msg['ORDINAL_NUMBER_SUFFIX']) {
this.appendDummyInput('ORDINAL').appendField(
Msg['ORDINAL_NUMBER_SUFFIX']);
Msg['ORDINAL_NUMBER_SUFFIX']
);
}
} else {
this.appendDummyInput('AT');
@@ -750,24 +763,25 @@ const LISTS_SETINDEX = {
options: this.WHERE_OPTIONS,
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function(this: FieldDropdown, value: string) {
const newAt = (value === 'FROM_START') || (value === 'FROM_END');
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as SetIndexBlock;
block.updateAt_(newAt);
// This menu has been destroyed and replaced. Update the
// replacement.
block.setFieldValue(value, 'WHERE');
return null;
}
return undefined;
});
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function (this: FieldDropdown, value: string) {
const newAt = value === 'FROM_START' || value === 'FROM_END';
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as SetIndexBlock;
block.updateAt_(newAt);
// This menu has been destroyed and replaced. Update the
// replacement.
block.setFieldValue(value, 'WHERE');
return null;
}
return undefined;
}
);
this.moveInputBefore('AT', 'TO');
if (this.getInput('ORDINAL')) {
this.moveInputBefore('ORDINAL', 'TO');
@@ -779,7 +793,7 @@ const LISTS_SETINDEX = {
blocks['lists_setIndex'] = LISTS_SETINDEX;
/** Type for a 'lists_getSublist' block. */
type GetSublistBlock = Block&GetSublistMutator;
type GetSublistBlock = Block & GetSublistMutator;
interface GetSublistMutator extends GetSublistMutatorType {
WHERE_OPTIONS_1: Array<[string, string]>;
WHERE_OPTIONS_2: Array<[string, string]>;
@@ -790,7 +804,7 @@ const LISTS_GETSUBLIST = {
/**
* Block for getting sublist.
*/
init: function(this: GetSublistBlock) {
init: function (this: GetSublistBlock) {
this['WHERE_OPTIONS_1'] = [
[Msg['LISTS_GET_SUBLIST_START_FROM_START'], 'FROM_START'],
[Msg['LISTS_GET_SUBLIST_START_FROM_END'], 'FROM_END'],
@@ -803,8 +817,9 @@ const LISTS_GETSUBLIST = {
];
this.setHelpUrl(Msg['LISTS_GET_SUBLIST_HELPURL']);
this.setStyle('list_blocks');
this.appendValueInput('LIST').setCheck('Array').appendField(
Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']);
this.appendValueInput('LIST')
.setCheck('Array')
.appendField(Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']);
this.appendDummyInput('AT1');
this.appendDummyInput('AT2');
if (Msg['LISTS_GET_SUBLIST_TAIL']) {
@@ -821,7 +836,7 @@ const LISTS_GETSUBLIST = {
*
* @return XML storage element.
*/
mutationToDom: function(this: GetSublistBlock): Element {
mutationToDom: function (this: GetSublistBlock): Element {
const container = xmlUtils.createElement('mutation');
const isAt1 = this.getInput('AT1') instanceof ValueInput;
container.setAttribute('at1', String(isAt1));
@@ -834,9 +849,9 @@ const LISTS_GETSUBLIST = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: GetSublistBlock, xmlElement: Element) {
const isAt1 = (xmlElement.getAttribute('at1') === 'true');
const isAt2 = (xmlElement.getAttribute('at2') === 'true');
domToMutation: function (this: GetSublistBlock, xmlElement: Element) {
const isAt1 = xmlElement.getAttribute('at1') === 'true';
const isAt2 = xmlElement.getAttribute('at2') === 'true';
this.updateAt_(1, isAt1);
this.updateAt_(2, isAt2);
},
@@ -849,7 +864,7 @@ const LISTS_GETSUBLIST = {
*
* @return The state of this block.
*/
saveExtraState: function(this: GetSublistBlock): null {
saveExtraState: function (this: GetSublistBlock): null {
return null;
},
@@ -858,7 +873,7 @@ const LISTS_GETSUBLIST = {
* No extra state is needed or expected as it is already encoded in the
* dropdown values.
*/
loadExtraState: function(this: GetSublistBlock) {},
loadExtraState: function (this: GetSublistBlock) {},
/**
* Create or delete an input for a numeric index.
@@ -867,7 +882,7 @@ const LISTS_GETSUBLIST = {
* @param n Specify first or second input (1 or 2).
* @param isAt True if the input should exist.
*/
updateAt_: function(this: GetSublistBlock, n: 1|2, isAt: boolean) {
updateAt_: function (this: GetSublistBlock, n: 1 | 2, isAt: boolean) {
// Create or delete an input for the numeric index.
// Destroy old 'AT' and 'ORDINAL' inputs.
this.removeInput('AT' + n);
@@ -876,37 +891,37 @@ const LISTS_GETSUBLIST = {
if (isAt) {
this.appendValueInput('AT' + n).setCheck('Number');
if (Msg['ORDINAL_NUMBER_SUFFIX']) {
this.appendDummyInput('ORDINAL' + n)
.appendField(Msg['ORDINAL_NUMBER_SUFFIX']);
this.appendDummyInput('ORDINAL' + n).appendField(
Msg['ORDINAL_NUMBER_SUFFIX']
);
}
} else {
this.appendDummyInput('AT' + n);
}
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
// TODO(#6920): Rewrite this so that clang-format doesn't make such an
// awful unreadable mess of it.
options: this
[('WHERE_OPTIONS_' + n) as ('WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2')],
options:
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function(this: FieldDropdown, value: string) {
const newAt = (value === 'FROM_START') || (value === 'FROM_END');
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as GetSublistBlock;
block.updateAt_(n, newAt);
// This menu has been destroyed and replaced.
// Update the replacement.
block.setFieldValue(value, 'WHERE' + n);
return null;
}
});
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function (this: FieldDropdown, value: string) {
const newAt = value === 'FROM_START' || value === 'FROM_END';
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as GetSublistBlock;
block.updateAt_(n, newAt);
// This menu has been destroyed and replaced.
// Update the replacement.
block.setFieldValue(value, 'WHERE' + n);
return null;
}
}
);
this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n);
if (n === 1) {
this.moveInputBefore('AT1', 'AT2');
@@ -921,13 +936,13 @@ const LISTS_GETSUBLIST = {
};
blocks['lists_getSublist'] = LISTS_GETSUBLIST;
type SortBlock = Block|typeof blocks['lists_sort'];
type SortBlock = Block | (typeof blocks)['lists_sort'];
blocks['lists_sort'] = {
/**
* Block for sorting a list.
*/
init: function(this: SortBlock) {
init: function (this: SortBlock) {
this.jsonInit({
'message0': '%{BKY_LISTS_SORT_TITLE}',
'args0': [
@@ -962,15 +977,13 @@ blocks['lists_sort'] = {
},
};
type SplitBlock = Block|typeof blocks['lists_split'];
type SplitBlock = Block | (typeof blocks)['lists_split'];
blocks['lists_split'] = {
/**
* Block for splitting text into a list, or joining a list into text.
*/
init: function(this: SplitBlock) {
// Assign 'this' to a variable for use in the closures below.
const thisBlock = this;
init: function (this: SplitBlock) {
const dropdown = fieldRegistry.fromJson({
type: 'field_dropdown',
options: [
@@ -979,19 +992,21 @@ blocks['lists_split'] = {
],
});
if (!dropdown) throw new Error('field_dropdown not found');
dropdown.setValidator(function(newMode) {
thisBlock.updateType_(newMode);
dropdown.setValidator((newMode) => {
this.updateType_(newMode);
});
this.setHelpUrl(Msg['LISTS_SPLIT_HELPURL']);
this.setStyle('list_blocks');
this.appendValueInput('INPUT').setCheck('String').appendField(
dropdown, 'MODE');
this.appendValueInput('DELIM').setCheck('String').appendField(
Msg['LISTS_SPLIT_WITH_DELIMITER']);
this.appendValueInput('INPUT')
.setCheck('String')
.appendField(dropdown, 'MODE');
this.appendValueInput('DELIM')
.setCheck('String')
.appendField(Msg['LISTS_SPLIT_WITH_DELIMITER']);
this.setInputsInline(true);
this.setOutput(true, 'Array');
this.setTooltip(function() {
const mode = thisBlock.getFieldValue('MODE');
this.setTooltip(() => {
const mode = this.getFieldValue('MODE');
if (mode === 'SPLIT') {
return Msg['LISTS_SPLIT_TOOLTIP_SPLIT'];
} else if (mode === 'JOIN') {
@@ -1005,7 +1020,7 @@ blocks['lists_split'] = {
*
* @param newMode Either 'SPLIT' or 'JOIN'.
*/
updateType_: function(this: SplitBlock, newMode: string) {
updateType_: function (this: SplitBlock, newMode: string) {
const mode = this.getFieldValue('MODE');
if (mode !== newMode) {
const inputConnection = this.getInput('INPUT')!.connection;
@@ -1034,7 +1049,7 @@ blocks['lists_split'] = {
*
* @return XML storage element.
*/
mutationToDom: function(this: SplitBlock): Element {
mutationToDom: function (this: SplitBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('mode', this.getFieldValue('MODE'));
return container;
@@ -1044,7 +1059,7 @@ blocks['lists_split'] = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: SplitBlock, xmlElement: Element) {
domToMutation: function (this: SplitBlock, xmlElement: Element) {
this.updateType_(xmlElement.getAttribute('mode'));
},
@@ -1056,7 +1071,7 @@ blocks['lists_split'] = {
*
* @return The state of this block.
*/
saveExtraState: function(this: SplitBlock): null {
saveExtraState: function (this: SplitBlock): null {
return null;
},
@@ -1065,7 +1080,7 @@ blocks['lists_split'] = {
* No extra state is needed or expected as it is already encoded in the
* dropdown values.
*/
loadExtraState: function(this: SplitBlock) {},
loadExtraState: function (this: SplitBlock) {},
};
// Register provided blocks.

View File

@@ -14,13 +14,19 @@ goog.declareModuleId('Blockly.libraryBlocks.loops');
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js';
import * as ContextMenu from '../core/contextmenu.js';
import type {ContextMenuOption, LegacyContextMenuOption} from '../core/contextmenu_registry.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from '../core/contextmenu_registry.js';
import * as Events from '../core/events/events.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import * as xmlUtils from '../core/utils/xml.js';
import {Msg} from '../core/msg.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_dropdown.js';
import '../core/field_label.js';
import '../core/field_number.js';
@@ -29,7 +35,6 @@ import '../core/warning.js';
import {FieldVariable} from '../core/field_variable.js';
import {WorkspaceSvg} from '../core/workspace_svg.js';
/**
* A dictionary of the block definitions provided by this module.
*/
@@ -38,16 +43,20 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'controls_repeat_ext',
'message0': '%{BKY_CONTROLS_REPEAT_TITLE}',
'args0': [{
'type': 'input_value',
'name': 'TIMES',
'check': 'Number',
}],
'args0': [
{
'type': 'input_value',
'name': 'TIMES',
'check': 'Number',
},
],
'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1',
'args1': [{
'type': 'input_statement',
'name': 'DO',
}],
'args1': [
{
'type': 'input_statement',
'name': 'DO',
},
],
'previousStatement': null,
'nextStatement': null,
'style': 'loop_blocks',
@@ -59,18 +68,22 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'controls_repeat',
'message0': '%{BKY_CONTROLS_REPEAT_TITLE}',
'args0': [{
'type': 'field_number',
'name': 'TIMES',
'value': 10,
'min': 0,
'precision': 1,
}],
'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',
}],
'args1': [
{
'type': 'input_statement',
'name': 'DO',
},
],
'previousStatement': null,
'nextStatement': null,
'style': 'loop_blocks',
@@ -97,10 +110,12 @@ export const blocks = createBlockDefinitionsFromJsonArray([
},
],
'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1',
'args1': [{
'type': 'input_statement',
'name': 'DO',
}],
'args1': [
{
'type': 'input_statement',
'name': 'DO',
},
],
'previousStatement': null,
'nextStatement': null,
'style': 'loop_blocks',
@@ -137,19 +152,18 @@ export const blocks = createBlockDefinitionsFromJsonArray([
},
],
'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1',
'args1': [{
'type': 'input_statement',
'name': 'DO',
}],
'args1': [
{
'type': 'input_statement',
'name': 'DO',
},
],
'inputsInline': true,
'previousStatement': null,
'nextStatement': null,
'style': 'loop_blocks',
'helpUrl': '%{BKY_CONTROLS_FOR_HELPURL}',
'extensions': [
'contextMenu_newGetVariableBlock',
'controls_for_tooltip',
],
'extensions': ['contextMenu_newGetVariableBlock', 'controls_for_tooltip'],
},
// Block for 'for each' loop.
{
@@ -168,10 +182,12 @@ export const blocks = createBlockDefinitionsFromJsonArray([
},
],
'message1': '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1',
'args1': [{
'type': 'input_statement',
'name': 'DO',
}],
'args1': [
{
'type': 'input_statement',
'name': 'DO',
},
],
'previousStatement': null,
'nextStatement': null,
'style': 'loop_blocks',
@@ -185,22 +201,21 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'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'],
],
}],
'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': true,
'extensions': [
'controls_flow_tooltip',
'controls_flow_in_loop_check',
],
'extensions': ['controls_flow_tooltip', 'controls_flow_in_loop_check'],
},
]);
@@ -215,8 +230,9 @@ const WHILE_UNTIL_TOOLTIPS = {
};
Extensions.register(
'controls_whileUntil_tooltip',
Extensions.buildTooltipForDropdown('MODE', WHILE_UNTIL_TOOLTIPS));
'controls_whileUntil_tooltip',
Extensions.buildTooltipForDropdown('MODE', WHILE_UNTIL_TOOLTIPS)
);
/**
* Tooltips for the 'controls_flow_statements' block, keyed by FLOW value.
@@ -229,14 +245,15 @@ const BREAK_CONTINUE_TOOLTIPS = {
};
Extensions.register(
'controls_flow_tooltip',
Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS));
'controls_flow_tooltip',
Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS)
);
/** Type of a block that has CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN */
type CustomContextMenuBlock = Block&CustomContextMenuMixin;
type CustomContextMenuBlock = Block & CustomContextMenuMixin;
interface CustomContextMenuMixin extends CustomContextMenuMixinType {}
type CustomContextMenuMixinType =
typeof CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN;
typeof CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN;
/**
* Mixin to add a context menu item to create a 'variables_get' block.
@@ -249,9 +266,10 @@ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
*
* @param options List of menu options to add to.
*/
customContextMenu: function(
this: CustomContextMenuBlock,
options: Array<ContextMenuOption|LegacyContextMenuOption>) {
customContextMenu: function (
this: CustomContextMenuBlock,
options: Array<ContextMenuOption | LegacyContextMenuOption>
) {
if (this.isInFlyout) {
return;
}
@@ -267,24 +285,26 @@ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
options.push({
enabled: true,
text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName),
callback: ContextMenu.callbackFactory(this, xmlBlock)
callback: ContextMenu.callbackFactory(this, xmlBlock),
});
}
},
};
Extensions.registerMixin(
'contextMenu_newGetVariableBlock',
CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN);
'contextMenu_newGetVariableBlock',
CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN
);
Extensions.register(
'controls_for_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_CONTROLS_FOR_TOOLTIP}', 'VAR'));
'controls_for_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_CONTROLS_FOR_TOOLTIP}', 'VAR')
);
Extensions.register(
'controls_forEach_tooltip',
Extensions.buildTooltipWithFieldText(
'%{BKY_CONTROLS_FOREACH_TOOLTIP}', 'VAR'));
'controls_forEach_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_CONTROLS_FOREACH_TOOLTIP}', 'VAR')
);
/**
* List of block types that are loops and thus do not need warnings.
@@ -310,7 +330,7 @@ export const loopTypes: Set<string> = new Set([
]);
/** Type of a block that has CONTROL_FLOW_IN_LOOP_CHECK_MIXIN */
type ControlFlowInLoopBlock = Block&ControlFlowInLoopMixin;
type ControlFlowInLoopBlock = Block & ControlFlowInLoopMixin;
interface ControlFlowInLoopMixin extends ControlFlowInLoopMixinType {}
type ControlFlowInLoopMixinType = typeof CONTROL_FLOW_IN_LOOP_CHECK_MIXIN;
@@ -324,23 +344,23 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
*
* @returns The nearest surrounding loop, or null if none.
*/
getSurroundLoop: function(this: ControlFlowInLoopBlock): Block |
null {
let block: Block|null = this;
do {
if (loopTypes.has(block.type)) {
return block;
}
block = block.getSurroundParent();
} while (block);
return null;
},
getSurroundLoop: function (this: ControlFlowInLoopBlock): Block | null {
// eslint-disable-next-line @typescript-eslint/no-this-alias
let block: Block | null = this;
do {
if (loopTypes.has(block.type)) {
return block;
}
block = block.getSurroundParent();
} while (block);
return null;
},
/**
* Called whenever anything on the workspace changes.
* Add warning if this flow block is not nested inside a loop.
*/
onchange: function(this: ControlFlowInLoopBlock, e: AbstractEvent) {
onchange: function (this: ControlFlowInLoopBlock, e: AbstractEvent) {
const ws = this.workspace as WorkspaceSvg;
// Don't change state if:
// * It's at the start of a drag.
@@ -350,7 +370,8 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
}
const enabled = !!this.getSurroundLoop();
this.setWarningText(
enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']);
enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']
);
if (!this.isInFlyout) {
const group = Events.getGroup();
// Makes it so the move and the disable event get undone together.
@@ -362,7 +383,9 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
};
Extensions.registerMixin(
'controls_flow_in_loop_check', CONTROL_FLOW_IN_LOOP_CHECK_MIXIN);
'controls_flow_in_loop_check',
CONTROL_FLOW_IN_LOOP_CHECK_MIXIN
);
// Register provided blocks.
defineBlocks(blocks);

View File

@@ -12,18 +12,18 @@ import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.libraryBlocks.math');
import * as Extensions from '../core/extensions.js';
import type {Field} from '../core/field.js';
import type {FieldDropdown} from '../core/field_dropdown.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Block} from '../core/block.js';
import type {BlockDefinition} from '../core/blocks.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_dropdown.js';
import '../core/field_label.js';
import '../core/field_number.js';
import '../core/field_variable.js';
/**
* A dictionary of the block definitions provided by this module.
*/
@@ -32,11 +32,13 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'math_number',
'message0': '%1',
'args0': [{
'type': 'field_number',
'name': 'NUM',
'value': 0,
}],
'args0': [
{
'type': 'field_number',
'name': 'NUM',
'value': 0,
},
],
'output': 'Number',
'helpUrl': '%{BKY_MATH_NUMBER_HELPURL}',
'style': 'math_blocks',
@@ -428,13 +430,13 @@ const TOOLTIPS_BY_OP = {
};
Extensions.register(
'math_op_tooltip',
Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP));
'math_op_tooltip',
Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP)
);
/** Type of a block that has IS_DIVISBLEBY_MUTATOR_MIXIN */
type DivisiblebyBlock = Block&DivisiblebyMixin;
type DivisiblebyBlock = Block & DivisiblebyMixin;
interface DivisiblebyMixin extends DivisiblebyMixinType {}
;
type DivisiblebyMixinType = typeof IS_DIVISIBLEBY_MUTATOR_MIXIN;
/**
@@ -452,9 +454,9 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
*
* @returns XML storage element.
*/
mutationToDom: function(this: DivisiblebyBlock): Element {
mutationToDom: function (this: DivisiblebyBlock): Element {
const container = xmlUtils.createElement('mutation');
const divisorInput = (this.getFieldValue('PROPERTY') === 'DIVISIBLE_BY');
const divisorInput = this.getFieldValue('PROPERTY') === 'DIVISIBLE_BY';
container.setAttribute('divisor_input', String(divisorInput));
return container;
},
@@ -464,8 +466,8 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: DivisiblebyBlock, xmlElement: Element) {
const divisorInput = (xmlElement.getAttribute('divisor_input') === 'true');
domToMutation: function (this: DivisiblebyBlock, xmlElement: Element) {
const divisorInput = xmlElement.getAttribute('divisor_input') === 'true';
this.updateShape_(divisorInput);
},
@@ -479,7 +481,7 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
*
* @param divisorInput True if this block has a divisor input.
*/
updateShape_: function(this: DivisiblebyBlock, divisorInput: boolean) {
updateShape_: function (this: DivisiblebyBlock, divisorInput: boolean) {
// Add or remove a Value Input.
const inputExists = this.getInput('DIVISOR');
if (divisorInput) {
@@ -497,29 +499,32 @@ const IS_DIVISIBLEBY_MUTATOR_MIXIN = {
* can update the block shape (add/remove divisor input) based on whether
* property is "divisible by".
*/
const IS_DIVISIBLE_MUTATOR_EXTENSION = function(this: DivisiblebyBlock) {
const IS_DIVISIBLE_MUTATOR_EXTENSION = function (this: DivisiblebyBlock) {
this.getField('PROPERTY')!.setValidator(
/** @param option The selected dropdown option. */
function(this: FieldDropdown, option: string) {
const divisorInput = (option === 'DIVISIBLE_BY');
(this.getSourceBlock() as DivisiblebyBlock).updateShape_(divisorInput);
return undefined; // FieldValidators can't be void. Use option as-is.
});
/** @param option The selected dropdown option. */
function (this: FieldDropdown, option: string) {
const divisorInput = option === 'DIVISIBLE_BY';
(this.getSourceBlock() as DivisiblebyBlock).updateShape_(divisorInput);
return undefined; // FieldValidators can't be void. Use option as-is.
}
);
};
Extensions.registerMutator(
'math_is_divisibleby_mutator', IS_DIVISIBLEBY_MUTATOR_MIXIN,
IS_DIVISIBLE_MUTATOR_EXTENSION);
'math_is_divisibleby_mutator',
IS_DIVISIBLEBY_MUTATOR_MIXIN,
IS_DIVISIBLE_MUTATOR_EXTENSION
);
// Update the tooltip of 'math_change' block to reference the variable.
Extensions.register(
'math_change_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR'));
'math_change_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_MATH_CHANGE_TOOLTIP}', 'VAR')
);
/** Type of a block that has LIST_MODES_MUTATOR_MIXIN */
type ListModesBlock = Block&ListModesMixin;
type ListModesBlock = Block & ListModesMixin;
interface ListModesMixin extends ListModesMixinType {}
;
type ListModesMixinType = typeof LIST_MODES_MUTATOR_MIXIN;
/**
@@ -532,7 +537,7 @@ const LIST_MODES_MUTATOR_MIXIN = {
*
* @param newOp Either 'MODE' or some op than returns a number.
*/
updateType_: function(this: ListModesBlock, newOp: string) {
updateType_: function (this: ListModesBlock, newOp: string) {
if (newOp === 'MODE') {
this.outputConnection!.setCheck('Array');
} else {
@@ -545,7 +550,7 @@ const LIST_MODES_MUTATOR_MIXIN = {
*
* @returns XML storage element.
*/
mutationToDom: function(this: ListModesBlock): Element {
mutationToDom: function (this: ListModesBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('op', this.getFieldValue('OP'));
return container;
@@ -556,7 +561,7 @@ const LIST_MODES_MUTATOR_MIXIN = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: ListModesBlock, xmlElement: Element) {
domToMutation: function (this: ListModesBlock, xmlElement: Element) {
const op = xmlElement.getAttribute('op');
if (op === null) throw new TypeError('xmlElement had no op attribute');
this.updateType_(op);
@@ -572,17 +577,20 @@ const LIST_MODES_MUTATOR_MIXIN = {
* Extension to 'math_on_list' blocks that allows support of
* modes operation (outputs a list of numbers).
*/
const LIST_MODES_MUTATOR_EXTENSION = function(this: ListModesBlock) {
this.getField(
'OP')!.setValidator(function(this: ListModesBlock, newOp: string) {
this.updateType_(newOp);
return undefined;
}.bind(this));
const LIST_MODES_MUTATOR_EXTENSION = function (this: ListModesBlock) {
this.getField('OP')!.setValidator(
function (this: ListModesBlock, newOp: string) {
this.updateType_(newOp);
return undefined;
}.bind(this)
);
};
Extensions.registerMutator(
'math_modes_of_list_mutator', LIST_MODES_MUTATOR_MIXIN,
LIST_MODES_MUTATOR_EXTENSION);
'math_modes_of_list_mutator',
LIST_MODES_MUTATOR_MIXIN,
LIST_MODES_MUTATOR_EXTENSION
);
// Register provided blocks.
defineBlocks(blocks);

View File

@@ -18,18 +18,19 @@ import {Align} from '../core/inputs/input.js';
import type {Block} from '../core/block.js';
import type {BlockSvg} from '../core/block_svg.js';
import {Connection} from '../core/connection.js';
import {ConnectionType} from '../core/connection_type.js';
import {FieldImage} from '../core/field_image.js';
import {FieldDropdown} from '../core/field_dropdown.js';
import {FieldTextInput} from '../core/field_textinput.js';
import {Msg} from '../core/msg.js';
import {Mutator} from '../core/mutator.js';
import type {Workspace} from '../core/workspace.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_multilineinput.js';
import '../core/field_variable.js';
import { ValueInput } from '../core/inputs/value_input.js';
import {ValueInput} from '../core/inputs/value_input.js';
/**
* A dictionary of the block definitions provided by this module.
@@ -39,19 +40,18 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'text',
'message0': '%1',
'args0': [{
'type': 'field_input',
'name': 'TEXT',
'text': '',
}],
'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',
],
'extensions': ['text_quotes', 'parent_tooltip_when_inline'],
},
{
'type': 'text_multiline',
@@ -60,15 +60,15 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'field_image',
'src':
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpP' +
'U2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0' +
'U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjm' +
'RIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z88' +
'73zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5luj' +
'nYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiD' +
'MYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYX' +
'fwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4z' +
'wA5X2Z9AYnQrEAAAAASUVORK5CYII=',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpP' +
'U2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0' +
'U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjm' +
'RIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z88' +
'73zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5luj' +
'nYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiD' +
'MYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYX' +
'fwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4z' +
'wA5X2Z9AYnQrEAAAAASUVORK5CYII=',
'width': 12,
'height': 17,
'alt': '\u00B6',
@@ -83,9 +83,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
'style': 'text_blocks',
'helpUrl': '%{BKY_TEXT_TEXT_HELPURL}',
'tooltip': '%{BKY_TEXT_TEXT_TOOLTIP}',
'extensions': [
'parent_tooltip_when_inline',
],
'extensions': ['parent_tooltip_when_inline'],
},
{
'type': 'text_join',
@@ -95,7 +93,6 @@ export const blocks = createBlockDefinitionsFromJsonArray([
'helpUrl': '%{BKY_TEXT_JOIN_HELPURL}',
'tooltip': '%{BKY_TEXT_JOIN_TOOLTIP}',
'mutator': 'text_join_mutator',
},
{
'type': 'text_create_join_container',
@@ -139,9 +136,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
'previousStatement': null,
'nextStatement': null,
'style': 'text_blocks',
'extensions': [
'text_append_tooltip',
],
'extensions': ['text_append_tooltip'],
},
{
'type': 'text_length',
@@ -186,14 +181,8 @@ export const blocks = createBlockDefinitionsFromJsonArray([
'type': 'field_dropdown',
'name': 'END',
'options': [
[
'%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}',
'FIRST',
],
[
'%{BKY_TEXT_INDEXOF_OPERATOR_LAST}',
'LAST',
],
['%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}', 'FIRST'],
['%{BKY_TEXT_INDEXOF_OPERATOR_LAST}', 'LAST'],
],
},
{
@@ -206,13 +195,11 @@ export const blocks = createBlockDefinitionsFromJsonArray([
'style': 'text_blocks',
'helpUrl': '%{BKY_TEXT_INDEXOF_HELPURL}',
'inputsInline': true,
'extensions': [
'text_indexOf_tooltip',
],
'extensions': ['text_indexOf_tooltip'],
},
{
'type': 'text_charAt',
'message0': '%{BKY_TEXT_CHARAT_TITLE}', // "in text %1 %2"
'message0': '%{BKY_TEXT_CHARAT_TITLE}', // "in text %1 %2"
'args0': [
{
'type': 'input_value',
@@ -240,7 +227,7 @@ export const blocks = createBlockDefinitionsFromJsonArray([
]);
/** Type of a 'text_get_substring' block. */
type GetSubstringBlock = Block&GetSubstringMixin;
type GetSubstringBlock = Block & GetSubstringMixin;
interface GetSubstringMixin extends GetSubstringType {
WHERE_OPTIONS_1: Array<[string, string]>;
WHERE_OPTIONS_2: Array<[string, string]>;
@@ -251,7 +238,7 @@ const GET_SUBSTRING_BLOCK = {
/**
* Block for getting substring.
*/
init: function(this: GetSubstringBlock) {
init: function (this: GetSubstringBlock) {
this['WHERE_OPTIONS_1'] = [
[Msg['TEXT_GET_SUBSTRING_START_FROM_START'], 'FROM_START'],
[Msg['TEXT_GET_SUBSTRING_START_FROM_END'], 'FROM_END'],
@@ -264,8 +251,9 @@ const GET_SUBSTRING_BLOCK = {
];
this.setHelpUrl(Msg['TEXT_GET_SUBSTRING_HELPURL']);
this.setStyle('text_blocks');
this.appendValueInput('STRING').setCheck('String').appendField(
Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']);
this.appendValueInput('STRING')
.setCheck('String')
.appendField(Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']);
this.appendDummyInput('AT1');
this.appendDummyInput('AT2');
if (Msg['TEXT_GET_SUBSTRING_TAIL']) {
@@ -283,7 +271,7 @@ const GET_SUBSTRING_BLOCK = {
*
* @returns XML storage element.
*/
mutationToDom: function(this: GetSubstringBlock): Element {
mutationToDom: function (this: GetSubstringBlock): Element {
const container = xmlUtils.createElement('mutation');
const isAt1 = this.getInput('AT1') instanceof ValueInput;
container.setAttribute('at1', `${isAt1}`);
@@ -297,9 +285,9 @@ const GET_SUBSTRING_BLOCK = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: GetSubstringBlock, xmlElement: Element) {
const isAt1 = (xmlElement.getAttribute('at1') === 'true');
const isAt2 = (xmlElement.getAttribute('at2') === 'true');
domToMutation: function (this: GetSubstringBlock, xmlElement: Element) {
const isAt1 = xmlElement.getAttribute('at1') === 'true';
const isAt2 = xmlElement.getAttribute('at2') === 'true';
this.updateAt_(1, isAt1);
this.updateAt_(2, isAt2);
},
@@ -316,7 +304,7 @@ const GET_SUBSTRING_BLOCK = {
* @param n Which input to modify (either 1 or 2).
* @param isAt True if the input includes a value connection, false otherwise.
*/
updateAt_: function(this: GetSubstringBlock, n: 1|2, isAt: boolean) {
updateAt_: function (this: GetSubstringBlock, n: 1 | 2, isAt: boolean) {
// Create or delete an input for the numeric index.
// Destroy old 'AT' and 'ORDINAL' inputs.
this.removeInput('AT' + n);
@@ -325,8 +313,9 @@ const GET_SUBSTRING_BLOCK = {
if (isAt) {
this.appendValueInput('AT' + n).setCheck('Number');
if (Msg['ORDINAL_NUMBER_SUFFIX']) {
this.appendDummyInput('ORDINAL' + n)
.appendField(Msg['ORDINAL_NUMBER_SUFFIX']);
this.appendDummyInput('ORDINAL' + n).appendField(
Msg['ORDINAL_NUMBER_SUFFIX']
);
}
} else {
this.appendDummyInput('AT' + n);
@@ -339,27 +328,28 @@ const GET_SUBSTRING_BLOCK = {
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options:
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function(this: FieldDropdown, value: any): null|undefined {
const newAt = (value === 'FROM_START') || (value === 'FROM_END');
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as GetSubstringBlock;
block.updateAt_(n, newAt);
// This menu has been destroyed and replaced.
// Update the replacement.
block.setFieldValue(value, 'WHERE' + n);
return null;
}
return undefined;
});
/**
* @param value The input value.
* @return Null if the field has been replaced; otherwise undefined.
*/
function (this: FieldDropdown, value: any): null | undefined {
const newAt = value === 'FROM_START' || value === 'FROM_END';
// The 'isAt' variable is available due to this function being a
// closure.
if (newAt !== isAt) {
const block = this.getSourceBlock() as GetSubstringBlock;
block.updateAt_(n, newAt);
// This menu has been destroyed and replaced.
// Update the replacement.
block.setFieldValue(value, 'WHERE' + n);
return null;
}
return undefined;
}
);
this.getInput('AT' + n)!.appendField(menu, 'WHERE' + n);
if (n === 1) {
@@ -377,7 +367,7 @@ blocks['text_changeCase'] = {
/**
* Block for changing capitalization.
*/
init: function(this: Block) {
init: function (this: Block) {
const OPERATORS = [
[Msg['TEXT_CHANGECASE_OPERATOR_UPPERCASE'], 'UPPERCASE'],
[Msg['TEXT_CHANGECASE_OPERATOR_LOWERCASE'], 'LOWERCASE'],
@@ -385,12 +375,15 @@ blocks['text_changeCase'] = {
];
this.setHelpUrl(Msg['TEXT_CHANGECASE_HELPURL']);
this.setStyle('text_blocks');
this.appendValueInput('TEXT').setCheck('String').appendField(
this.appendValueInput('TEXT')
.setCheck('String')
.appendField(
fieldRegistry.fromJson({
type: 'field_dropdown',
options: OPERATORS,
}) as FieldDropdown,
'CASE');
'CASE'
);
this.setOutput(true, 'String');
this.setTooltip(Msg['TEXT_CHANGECASE_TOOLTIP']);
},
@@ -400,7 +393,7 @@ blocks['text_trim'] = {
/**
* Block for trimming spaces.
*/
init: function(this: Block) {
init: function (this: Block) {
const OPERATORS = [
[Msg['TEXT_TRIM_OPERATOR_BOTH'], 'BOTH'],
[Msg['TEXT_TRIM_OPERATOR_LEFT'], 'LEFT'],
@@ -408,12 +401,15 @@ blocks['text_trim'] = {
];
this.setHelpUrl(Msg['TEXT_TRIM_HELPURL']);
this.setStyle('text_blocks');
this.appendValueInput('TEXT').setCheck('String').appendField(
this.appendValueInput('TEXT')
.setCheck('String')
.appendField(
fieldRegistry.fromJson({
type: 'field_dropdown',
options: OPERATORS,
}) as FieldDropdown,
'MODE');
'MODE'
);
this.setOutput(true, 'String');
this.setTooltip(Msg['TEXT_TRIM_TOOLTIP']);
},
@@ -423,7 +419,7 @@ blocks['text_print'] = {
/**
* Block for print statement.
*/
init: function(this: Block) {
init: function (this: Block) {
this.jsonInit({
'message0': Msg['TEXT_PRINT_TITLE'],
'args0': [
@@ -441,7 +437,7 @@ blocks['text_print'] = {
},
};
type PromptCommonBlock = Block&PromptCommonMixin;
type PromptCommonBlock = Block & PromptCommonMixin;
interface PromptCommonMixin extends PromptCommonType {}
type PromptCommonType = typeof PROMPT_COMMON;
@@ -455,7 +451,7 @@ const PROMPT_COMMON = {
*
* @param newOp The new output type. Should be either 'TEXT' or 'NUMBER'.
*/
updateType_: function(this: PromptCommonBlock, newOp: string) {
updateType_: function (this: PromptCommonBlock, newOp: string) {
this.outputConnection!.setCheck(newOp === 'NUMBER' ? 'Number' : 'String');
},
/**
@@ -464,7 +460,7 @@ const PROMPT_COMMON = {
*
* @returns XML storage element.
*/
mutationToDom: function(this: PromptCommonBlock): Element {
mutationToDom: function (this: PromptCommonBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('type', this.getFieldValue('TYPE'));
return container;
@@ -475,7 +471,7 @@ const PROMPT_COMMON = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: PromptCommonBlock, xmlElement: Element) {
domToMutation: function (this: PromptCommonBlock, xmlElement: Element) {
this.updateType_(xmlElement.getAttribute('type')!);
},
};
@@ -485,29 +481,27 @@ blocks['text_prompt_ext'] = {
/**
* Block for prompt function (external message).
*/
init: function(this: PromptCommonBlock) {
init: function (this: PromptCommonBlock) {
const TYPES = [
[Msg['TEXT_PROMPT_TYPE_TEXT'], 'TEXT'],
[Msg['TEXT_PROMPT_TYPE_NUMBER'], 'NUMBER'],
];
this.setHelpUrl(Msg['TEXT_PROMPT_HELPURL']);
this.setStyle('text_blocks');
// Assign 'this' to a variable for use in the closures below.
const thisBlock = this;
const dropdown = fieldRegistry.fromJson({
type: 'field_dropdown',
options: TYPES,
}) as FieldDropdown;
dropdown.setValidator(function(this: FieldDropdown, newOp: string) {
thisBlock.updateType_(newOp);
return undefined; // FieldValidators can't be void. Use option as-is.
dropdown.setValidator((newOp: string) => {
this.updateType_(newOp);
return undefined; // FieldValidators can't be void. Use option as-is.
});
this.appendValueInput('TEXT').appendField(dropdown, 'TYPE');
this.setOutput(true, 'String');
this.setTooltip(function() {
return (thisBlock.getFieldValue('TYPE') === 'TEXT') ?
Msg['TEXT_PROMPT_TOOLTIP_TEXT'] :
Msg['TEXT_PROMPT_TOOLTIP_NUMBER'];
this.setTooltip(() => {
return this.getFieldValue('TYPE') === 'TEXT'
? Msg['TEXT_PROMPT_TOOLTIP_TEXT']
: Msg['TEXT_PROMPT_TOOLTIP_NUMBER'];
});
},
@@ -517,7 +511,7 @@ blocks['text_prompt_ext'] = {
// XML hooks are kept for backwards compatibility.
};
type PromptBlock = Block&PromptCommonMixin&QuoteImageMixin;
type PromptBlock = Block & PromptCommonMixin & QuoteImageMixin;
const TEXT_PROMPT_BLOCK = {
...PROMPT_COMMON,
@@ -525,40 +519,39 @@ const TEXT_PROMPT_BLOCK = {
* Block for prompt function (internal message).
* The 'text_prompt_ext' block is preferred as it is more flexible.
*/
init: function(this: PromptBlock) {
init: function (this: PromptBlock) {
this.mixin(QUOTE_IMAGE_MIXIN);
const TYPES = [
[Msg['TEXT_PROMPT_TYPE_TEXT'], 'TEXT'],
[Msg['TEXT_PROMPT_TYPE_NUMBER'], 'NUMBER'],
];
// Assign 'this' to a variable for use in the closures below.
const thisBlock = this;
this.setHelpUrl(Msg['TEXT_PROMPT_HELPURL']);
this.setStyle('text_blocks');
const dropdown = fieldRegistry.fromJson({
type: 'field_dropdown',
options: TYPES,
}) as FieldDropdown;
dropdown.setValidator(function(this: FieldDropdown, newOp: string) {
thisBlock.updateType_(newOp);
return undefined; // FieldValidators can't be void. Use option as-is.
dropdown.setValidator((newOp: string) => {
this.updateType_(newOp);
return undefined; // FieldValidators can't be void. Use option as-is.
});
this.appendDummyInput()
.appendField(dropdown, 'TYPE')
.appendField(this.newQuote_(true))
.appendField(
fieldRegistry.fromJson({
type: 'field_input',
text: '',
}) as FieldTextInput,
'TEXT')
.appendField(this.newQuote_(false));
.appendField(dropdown, 'TYPE')
.appendField(this.newQuote_(true))
.appendField(
fieldRegistry.fromJson({
type: 'field_input',
text: '',
}) as FieldTextInput,
'TEXT'
)
.appendField(this.newQuote_(false));
this.setOutput(true, 'String');
this.setTooltip(function() {
return (thisBlock.getFieldValue('TYPE') === 'TEXT') ?
Msg['TEXT_PROMPT_TOOLTIP_TEXT'] :
Msg['TEXT_PROMPT_TOOLTIP_NUMBER'];
this.setTooltip(() => {
return this.getFieldValue('TYPE') === 'TEXT'
? Msg['TEXT_PROMPT_TOOLTIP_TEXT']
: Msg['TEXT_PROMPT_TOOLTIP_NUMBER'];
});
},
};
@@ -569,7 +562,7 @@ blocks['text_count'] = {
/**
* Block for counting how many times one string appears within another string.
*/
init: function(this: Block) {
init: function (this: Block) {
this.jsonInit({
'message0': Msg['TEXT_COUNT_MESSAGE0'],
'args0': [
@@ -597,7 +590,7 @@ blocks['text_replace'] = {
/**
* Block for replacing one string with another in the text.
*/
init: function(this: Block) {
init: function (this: Block) {
this.jsonInit({
'message0': Msg['TEXT_REPLACE_MESSAGE0'],
'args0': [
@@ -630,7 +623,7 @@ blocks['text_reverse'] = {
/**
* Block for reversing a string.
*/
init: function(this: Block) {
init: function (this: Block) {
this.jsonInit({
'message0': Msg['TEXT_REVERSE_MESSAGE0'],
'args0': [
@@ -650,7 +643,7 @@ blocks['text_reverse'] = {
};
/** Type of a block that has QUOTE_IMAGE_MIXIN */
type QuoteImageBlock = Block&QuoteImageMixin;
type QuoteImageBlock = Block & QuoteImageMixin;
interface QuoteImageMixin extends QuoteImageMixinType {}
type QuoteImageMixinType = typeof QUOTE_IMAGE_MIXIN;
@@ -660,21 +653,21 @@ const QUOTE_IMAGE_MIXIN = {
* quote).
*/
QUOTE_IMAGE_LEFT_DATAURI:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' +
'n0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY' +
'1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1' +
'HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMf' +
'z9AylsaRRgGzvZAAAAAElFTkSuQmCC',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' +
'n0lEQVQI1z3OMa5BURSF4f/cQhAKjUQhuQmFNwGJEUi0RKN5rU7FHKhpjEH3TEMtkdBSCY' +
'1EIv8r7nFX9e29V7EBAOvu7RPjwmWGH/VuF8CyN9/OAdvqIXYLvtRaNjx9mMTDyo+NjAN1' +
'HNcl9ZQ5oQMM3dgDUqDo1l8DzvwmtZN7mnD+PkmLa+4mhrxVA9fRowBWmVBhFy5gYEjKMf' +
'z9AylsaRRgGzvZAAAAAElFTkSuQmCC',
/**
* Image data URI of an LTR closing double quote (same as RTL opening double
* quote).
*/
QUOTE_IMAGE_RIGHT_DATAURI:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' +
'qUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhg' +
'gONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvB' +
'O3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5Aos' +
'lLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAKCAQAAAAqJXdxAAAA' +
'qUlEQVQI1z3KvUpCcRiA8ef9E4JNHhI0aFEacm1o0BsI0Slx8wa8gLauoDnoBhq7DcfWhg' +
'gONDmJJgqCPA7neJ7p934EOOKOnM8Q7PDElo/4x4lFb2DmuUjcUzS3URnGib9qaPNbuXvB' +
'O3sGPHJDRG6fGVdMSeWDP2q99FQdFrz26Gu5Tq7dFMzUvbXy8KXeAj57cOklgA+u1B5Aos' +
'lLtGIHQMaCVnwDnADZIFIrXsoXrgAAAABJRU5ErkJggg==',
/**
* Pixel width of QUOTE_IMAGE_LEFT_DATAURI and QUOTE_IMAGE_RIGHT_DATAURI.
*/
@@ -689,7 +682,7 @@ const QUOTE_IMAGE_MIXIN = {
*
* @param fieldName The name of the field to wrap with quotes.
*/
quoteField_: function(this: QuoteImageBlock, fieldName: string) {
quoteField_: function (this: QuoteImageBlock, fieldName: string) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
for (let j = 0, field; (field = input.fieldRow[j]); j++) {
if (fieldName === field.name) {
@@ -700,7 +693,8 @@ const QUOTE_IMAGE_MIXIN = {
}
}
console.warn(
'field named "' + fieldName + '" not found in ' + this.toDevString());
'field named "' + fieldName + '" not found in ' + this.toDevString()
);
},
/**
@@ -711,10 +705,11 @@ const QUOTE_IMAGE_MIXIN = {
* Otherwise, a closing quote is used (” in LTR).
* @returns The new field.
*/
newQuote_: function(this: QuoteImageBlock, open: boolean): FieldImage {
newQuote_: function (this: QuoteImageBlock, open: boolean): FieldImage {
const isLeft = this.RTL ? !open : open;
const dataUri =
isLeft ? this.QUOTE_IMAGE_LEFT_DATAURI : this.QUOTE_IMAGE_RIGHT_DATAURI;
const dataUri = isLeft
? this.QUOTE_IMAGE_LEFT_DATAURI
: this.QUOTE_IMAGE_RIGHT_DATAURI;
return fieldRegistry.fromJson({
type: 'field_image',
src: dataUri,
@@ -728,20 +723,20 @@ const QUOTE_IMAGE_MIXIN = {
/**
* Wraps TEXT field with images of double quote characters.
*/
const QUOTES_EXTENSION = function(this: QuoteImageBlock) {
const QUOTES_EXTENSION = function (this: QuoteImageBlock) {
this.mixin(QUOTE_IMAGE_MIXIN);
this.quoteField_('TEXT');
};
/** Type of a block that has TEXT_JOIN_MUTATOR_MIXIN */
type JoinMutatorBlock = Block&JoinMutatorMixin&QuoteImageMixin;
type JoinMutatorBlock = Block & JoinMutatorMixin & QuoteImageMixin;
interface JoinMutatorMixin extends JoinMutatorMixinType {}
type JoinMutatorMixinType = typeof JOIN_MUTATOR_MIXIN;
/** Type of a item block in the text_join_mutator bubble. */
type JoinItemBlock = BlockSvg&JoinItemMixin;
type JoinItemBlock = BlockSvg & JoinItemMixin;
interface JoinItemMixin {
valueConnection_: Connection|null
valueConnection_: Connection | null;
}
/**
@@ -755,7 +750,7 @@ const JOIN_MUTATOR_MIXIN = {
*
* @returns XML storage element.
*/
mutationToDom: function(this: JoinMutatorBlock): Element {
mutationToDom: function (this: JoinMutatorBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('items', `${this.itemCount_}`);
return container;
@@ -766,7 +761,7 @@ const JOIN_MUTATOR_MIXIN = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: JoinMutatorBlock, xmlElement: Element) {
domToMutation: function (this: JoinMutatorBlock, xmlElement: Element) {
this.itemCount_ = parseInt(xmlElement.getAttribute('items')!, 10);
this.updateShape_();
},
@@ -775,7 +770,7 @@ const JOIN_MUTATOR_MIXIN = {
*
* @returns The state of this block, ie the item count.
*/
saveExtraState: function(this: JoinMutatorBlock): {itemCount: number;} {
saveExtraState: function (this: JoinMutatorBlock): {itemCount: number} {
return {
'itemCount': this.itemCount_,
};
@@ -785,7 +780,7 @@ const JOIN_MUTATOR_MIXIN = {
*
* @param state The state to apply to this block, ie the item count.
*/
loadExtraState: function(this: JoinMutatorBlock, state: {[x: string]: any;}) {
loadExtraState: function (this: JoinMutatorBlock, state: {[x: string]: any}) {
this.itemCount_ = state['itemCount'];
this.updateShape_();
},
@@ -795,14 +790,16 @@ const JOIN_MUTATOR_MIXIN = {
* @param workspace Mutator's workspace.
* @returns Root block in mutator.
*/
decompose: function(this: JoinMutatorBlock, workspace: Workspace): Block {
const containerBlock =
workspace.newBlock('text_create_join_container') as BlockSvg;
decompose: function (this: JoinMutatorBlock, workspace: Workspace): Block {
const containerBlock = workspace.newBlock(
'text_create_join_container'
) as BlockSvg;
containerBlock.initSvg();
let connection = containerBlock.getInput('STACK')!.connection!;
for (let i = 0; i < this.itemCount_; i++) {
const itemBlock =
workspace.newBlock('text_create_join_item') as JoinItemBlock;
const itemBlock = workspace.newBlock(
'text_create_join_item'
) as JoinItemBlock;
itemBlock.initSvg();
connection.connect(itemBlock.previousConnection);
connection = itemBlock.nextConnection;
@@ -814,9 +811,10 @@ const JOIN_MUTATOR_MIXIN = {
*
* @param containerBlock Root block in mutator.
*/
compose: function(this: JoinMutatorBlock, containerBlock: Block) {
let itemBlock =
containerBlock.getInputTargetBlock('STACK') as JoinItemBlock;
compose: function (this: JoinMutatorBlock, containerBlock: Block) {
let itemBlock = containerBlock.getInputTargetBlock(
'STACK'
) as JoinItemBlock;
// Count number of inputs.
const connections = [];
while (itemBlock) {
@@ -846,7 +844,7 @@ const JOIN_MUTATOR_MIXIN = {
*
* @param containerBlock Root block in mutator.
*/
saveConnections: function(this: JoinMutatorBlock, containerBlock: Block) {
saveConnections: function (this: JoinMutatorBlock, containerBlock: Block) {
let itemBlock = containerBlock.getInputTargetBlock('STACK');
let i = 0;
while (itemBlock) {
@@ -856,7 +854,7 @@ const JOIN_MUTATOR_MIXIN = {
}
const input = this.getInput('ADD' + i);
(itemBlock as JoinItemBlock).valueConnection_ =
input && input.connection!.targetConnection;
input && input.connection!.targetConnection;
itemBlock = itemBlock.getNextBlock();
i++;
}
@@ -864,13 +862,13 @@ const JOIN_MUTATOR_MIXIN = {
/**
* Modify this block to have the correct number of inputs.
*/
updateShape_: function(this: JoinMutatorBlock) {
updateShape_: function (this: JoinMutatorBlock) {
if (this.itemCount_ && this.getInput('EMPTY')) {
this.removeInput('EMPTY');
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
this.appendDummyInput('EMPTY')
.appendField(this.newQuote_(true))
.appendField(this.newQuote_(false));
.appendField(this.newQuote_(true))
.appendField(this.newQuote_(false));
}
// Add new inputs.
for (let i = 0; i < this.itemCount_; i++) {
@@ -891,7 +889,7 @@ const JOIN_MUTATOR_MIXIN = {
/**
* Performs final setup of a text_join block.
*/
const JOIN_EXTENSION = function(this: JoinMutatorBlock) {
const JOIN_EXTENSION = function (this: JoinMutatorBlock) {
// Add the quote mixin for the itemCount_ = 0 case.
this.mixin(QUOTE_IMAGE_MIXIN);
// Initialize the mutator values.
@@ -903,22 +901,24 @@ const JOIN_EXTENSION = function(this: JoinMutatorBlock) {
// Update the tooltip of 'text_append' block to reference the variable.
Extensions.register(
'text_append_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_TEXT_APPEND_TOOLTIP}', 'VAR'));
'text_append_tooltip',
Extensions.buildTooltipWithFieldText('%{BKY_TEXT_APPEND_TOOLTIP}', 'VAR')
);
/**
* Update the tooltip of 'text_append' block to reference the variable.
*/
const INDEXOF_TOOLTIP_EXTENSION = function(this: Block) {
const INDEXOF_TOOLTIP_EXTENSION = function (this: Block) {
this.setTooltip(() => {
return Msg['TEXT_INDEXOF_TOOLTIP'].replace(
'%1', this.workspace.options.oneBasedIndex ? '0' : '-1');
'%1',
this.workspace.options.oneBasedIndex ? '0' : '-1'
);
});
};
/** Type of a block that has TEXT_CHARAT_MUTATOR_MIXIN */
type CharAtBlock = Block&CharAtMixin;
type CharAtBlock = Block & CharAtMixin;
interface CharAtMixin extends CharAtMixinType {}
type CharAtMixinType = typeof CHARAT_MUTATOR_MIXIN;
@@ -933,7 +933,7 @@ const CHARAT_MUTATOR_MIXIN = {
*
* @returns XML storage element.
*/
mutationToDom: function(this: CharAtBlock): Element {
mutationToDom: function (this: CharAtBlock): Element {
const container = xmlUtils.createElement('mutation');
container.setAttribute('at', `${this.isAt_}`);
return container;
@@ -944,10 +944,10 @@ const CHARAT_MUTATOR_MIXIN = {
*
* @param xmlElement XML storage element.
*/
domToMutation: function(this: CharAtBlock, xmlElement: Element) {
domToMutation: function (this: CharAtBlock, xmlElement: Element) {
// Note: Until January 2013 this block did not have mutations,
// so 'at' defaults to true.
const isAt = (xmlElement.getAttribute('at') !== 'false');
const isAt = xmlElement.getAttribute('at') !== 'false';
this.updateAt_(isAt);
},
@@ -961,7 +961,7 @@ const CHARAT_MUTATOR_MIXIN = {
*
* @param isAt True if the input should exist.
*/
updateAt_: function(this: CharAtBlock, isAt: boolean) {
updateAt_: function (this: CharAtBlock, isAt: boolean) {
// Destroy old 'AT' and 'ORDINAL' inputs.
this.removeInput('AT', true);
this.removeInput('ORDINAL', true);
@@ -970,7 +970,8 @@ const CHARAT_MUTATOR_MIXIN = {
this.appendValueInput('AT').setCheck('Number');
if (Msg['ORDINAL_NUMBER_SUFFIX']) {
this.appendDummyInput('ORDINAL').appendField(
Msg['ORDINAL_NUMBER_SUFFIX']);
Msg['ORDINAL_NUMBER_SUFFIX']
);
}
}
if (Msg['TEXT_CHARAT_TAIL']) {
@@ -985,30 +986,29 @@ const CHARAT_MUTATOR_MIXIN = {
/**
* Does the initial mutator update of text_charAt and adds the tooltip
*/
const CHARAT_EXTENSION = function(this: CharAtBlock) {
const CHARAT_EXTENSION = function (this: CharAtBlock) {
const dropdown = this.getField('WHERE') as FieldDropdown;
dropdown.setValidator(function(this: FieldDropdown, value: any) {
const newAt = (value === 'FROM_START') || (value === 'FROM_END');
dropdown.setValidator(function (this: FieldDropdown, value: any) {
const newAt = value === 'FROM_START' || value === 'FROM_END';
const block = this.getSourceBlock() as CharAtBlock;
if (newAt !== block.isAt_) {
block.updateAt_(newAt);
}
return undefined; // FieldValidators can't be void. Use option as-is.
return undefined; // FieldValidators can't be void. Use option as-is.
});
this.updateAt_(true);
// Assign 'this' to a variable for use in the tooltip closure below.
const thisBlock = this;
this.setTooltip(function() {
const where = thisBlock.getFieldValue('WHERE');
this.setTooltip(() => {
const where = this.getFieldValue('WHERE');
let tooltip = Msg['TEXT_CHARAT_TOOLTIP'];
if (where === 'FROM_START' || where === 'FROM_END') {
const msg = (where === 'FROM_START') ?
Msg['LISTS_INDEX_FROM_START_TOOLTIP'] :
Msg['LISTS_INDEX_FROM_END_TOOLTIP'];
const msg =
where === 'FROM_START'
? Msg['LISTS_INDEX_FROM_START_TOOLTIP']
: Msg['LISTS_INDEX_FROM_END_TOOLTIP'];
if (msg) {
tooltip += ' ' +
msg.replace(
'%1', thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
tooltip +=
' ' +
msg.replace('%1', this.workspace.options.oneBasedIndex ? '#1' : '#0');
}
}
return tooltip;
@@ -1020,10 +1020,16 @@ Extensions.register('text_indexOf_tooltip', INDEXOF_TOOLTIP_EXTENSION);
Extensions.register('text_quotes', QUOTES_EXTENSION);
Extensions.registerMutator(
'text_join_mutator', JOIN_MUTATOR_MIXIN, JOIN_EXTENSION);
'text_join_mutator',
JOIN_MUTATOR_MIXIN,
JOIN_EXTENSION
);
Extensions.registerMutator(
'text_charAt_mutator', CHARAT_MUTATOR_MIXIN, CHARAT_EXTENSION);
'text_charAt_mutator',
CHARAT_MUTATOR_MIXIN,
CHARAT_EXTENSION
);
// Register provided blocks.
defineBlocks(blocks);

View File

@@ -17,14 +17,19 @@ import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Block} from '../core/block.js';
import type {ContextMenuOption, LegacyContextMenuOption} from '../core/contextmenu_registry.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from '../core/contextmenu_registry.js';
import {FieldVariable} from '../core/field_variable.js';
import {Msg} from '../core/msg.js';
import type {WorkspaceSvg} from '../core/workspace_svg.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_label.js';
/**
* A dictionary of the block definitions provided by this module.
*/
@@ -71,10 +76,10 @@ export const blocks = createBlockDefinitionsFromJsonArray([
]);
/** Type of a block that has CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN */
type VariableBlock = Block&VariableMixin;
type VariableBlock = Block & VariableMixin;
interface VariableMixin extends VariableMixinType {}
type VariableMixinType =
typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN;
typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN;
/**
* Mixin to add context menu items to create getter/setter blocks for this
@@ -87,9 +92,10 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
*
* @param options List of menu options to add to.
*/
customContextMenu: function(
this: VariableBlock,
options: Array<ContextMenuOption|LegacyContextMenuOption>) {
customContextMenu: function (
this: VariableBlock,
options: Array<ContextMenuOption | LegacyContextMenuOption>
) {
if (!this.isInFlyout) {
let oppositeType;
let contextMenuMsg;
@@ -113,12 +119,14 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
options.push({
enabled: this.workspace.remainingCapacity() > 0,
text: contextMenuMsg.replace('%1', name),
callback: ContextMenu.callbackFactory(this, xmlBlock)
callback: ContextMenu.callbackFactory(this, xmlBlock),
});
// Getter blocks have the option to rename or delete that variable.
} else {
if (this.type === 'variables_get' ||
this.type === 'variables_get_reporter') {
if (
this.type === 'variables_get' ||
this.type === 'variables_get_reporter'
) {
const renameOption = {
text: Msg['RENAME_VARIABLE'],
enabled: true,
@@ -144,8 +152,10 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
* @param block The block with the variable to rename.
* @returns A function that renames the variable.
*/
const renameOptionCallbackFactory = function(block: VariableBlock): () => void {
return function() {
const renameOptionCallbackFactory = function (
block: VariableBlock
): () => void {
return function () {
const workspace = block.workspace;
const variableField = block.getField('VAR') as FieldVariable;
const variable = variableField.getVariable()!;
@@ -160,8 +170,10 @@ const renameOptionCallbackFactory = function(block: VariableBlock): () => void {
* @param block The block with the variable to delete.
* @returns A function that deletes the variable.
*/
const deleteOptionCallbackFactory = function(block: VariableBlock): () => void {
return function() {
const deleteOptionCallbackFactory = function (
block: VariableBlock
): () => void {
return function () {
const workspace = block.workspace;
const variableField = block.getField('VAR') as FieldVariable;
const variable = variableField.getVariable()!;
@@ -171,8 +183,9 @@ const deleteOptionCallbackFactory = function(block: VariableBlock): () => void {
};
Extensions.registerMixin(
'contextMenu_variableSetterGetter',
CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
'contextMenu_variableSetterGetter',
CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN
);
// Register provided blocks.
defineBlocks(blocks);

View File

@@ -18,14 +18,19 @@ import * as Variables from '../core/variables.js';
import * as xml from '../core/utils/xml.js';
import {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js';
import type {ContextMenuOption, LegacyContextMenuOption} from '../core/contextmenu_registry.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from '../core/contextmenu_registry.js';
import {FieldVariable} from '../core/field_variable.js';
import {Msg} from '../core/msg.js';
import type {WorkspaceSvg} from '../core/workspace_svg.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_label.js';
/**
* A dictionary of the block definitions provided by this module.
*/
@@ -34,11 +39,13 @@ export const blocks = createBlockDefinitionsFromJsonArray([
{
'type': 'variables_get_dynamic',
'message0': '%1',
'args0': [{
'type': 'field_variable',
'name': 'VAR',
'variable': '%{BKY_VARIABLES_DEFAULT_NAME}',
}],
'args0': [
{
'type': 'field_variable',
'name': 'VAR',
'variable': '%{BKY_VARIABLES_DEFAULT_NAME}',
},
],
'output': null,
'style': 'variable_dynamic_blocks',
'helpUrl': '%{BKY_VARIABLES_GET_HELPURL}',
@@ -70,10 +77,10 @@ export const blocks = createBlockDefinitionsFromJsonArray([
]);
/** Type of a block that has CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN */
type VariableBlock = Block&VariableMixin;
type VariableBlock = Block & VariableMixin;
interface VariableMixin extends VariableMixinType {}
type VariableMixinType =
typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN;
typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN;
/**
* Mixin to add context menu items to create getter/setter blocks for this
@@ -86,9 +93,10 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
*
* @param options List of menu options to add to.
*/
customContextMenu: function(
this: VariableBlock,
options: Array<ContextMenuOption|LegacyContextMenuOption>) {
customContextMenu: function (
this: VariableBlock,
options: Array<ContextMenuOption | LegacyContextMenuOption>
) {
// Getter blocks have the option to create a setter block, and vice versa.
if (!this.isInFlyout) {
let oppositeType;
@@ -116,11 +124,13 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
options.push({
enabled: this.workspace.remainingCapacity() > 0,
text: contextMenuMsg.replace('%1', name),
callback: ContextMenu.callbackFactory(this, xmlBlock)
callback: ContextMenu.callbackFactory(this, xmlBlock),
});
} else {
if (this.type === 'variables_get_dynamic' ||
this.type === 'variables_get_reporter_dynamic') {
if (
this.type === 'variables_get_dynamic' ||
this.type === 'variables_get_reporter_dynamic'
) {
const renameOption = {
text: Msg['RENAME_VARIABLE'],
enabled: true,
@@ -143,7 +153,7 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
*
* @param _e Change event.
*/
onchange: function(this: VariableBlock, _e: AbstractEvent) {
onchange: function (this: VariableBlock, _e: AbstractEvent) {
const id = this.getFieldValue('VAR');
const variableModel = Variables.getVariable(this.workspace, id)!;
if (this.type === 'variables_get_dynamic') {
@@ -161,8 +171,8 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
* @param block The block with the variable to rename.
* @returns A function that renames the variable.
*/
const renameOptionCallbackFactory = function(block: VariableBlock) {
return function() {
const renameOptionCallbackFactory = function (block: VariableBlock) {
return function () {
const workspace = block.workspace;
const variableField = block.getField('VAR') as FieldVariable;
const variable = variableField.getVariable()!;
@@ -177,8 +187,8 @@ const renameOptionCallbackFactory = function(block: VariableBlock) {
* @param block The block with the variable to delete.
* @returns A function that deletes the variable.
*/
const deleteOptionCallbackFactory = function(block: VariableBlock) {
return function() {
const deleteOptionCallbackFactory = function (block: VariableBlock) {
return function () {
const workspace = block.workspace;
const variableField = block.getField('VAR') as FieldVariable;
const variable = variableField.getVariable()!;
@@ -188,8 +198,9 @@ const deleteOptionCallbackFactory = function(block: VariableBlock) {
};
Extensions.registerMixin(
'contextMenu_variableDynamicSetterGetter',
CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN);
'contextMenu_variableDynamicSetterGetter',
CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN
);
// Register provided blocks.
defineBlocks(blocks);

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,6 @@ import type {BlockSvg} from './block_svg.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
/** A bounding box for a cloned block. */
interface CloneRect {
x: number;
@@ -21,11 +20,10 @@ interface CloneRect {
}
/** PID of disconnect UI animation. There can only be one at a time. */
let disconnectPid: ReturnType<typeof setTimeout>|null = null;
let disconnectPid: ReturnType<typeof setTimeout> | null = null;
/** The wobbling block. There can only be one at a time. */
let wobblingBlock: BlockSvg|null = null;
let wobblingBlock: BlockSvg | null = null;
/**
* Play some UI effects (sound, animation) when disposing of a block.
@@ -46,8 +44,12 @@ export function disposeUiEffect(block: BlockSvg) {
const clone: SVGGElement = svgGroup.cloneNode(true) as SVGGElement;
clone.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
workspace.getParentSvg().appendChild(clone);
const cloneRect =
{'x': xy.x, 'y': xy.y, 'width': block.width, 'height': block.height};
const cloneRect = {
'x': xy.x,
'y': xy.y,
'width': block.width,
'height': block.height,
};
disposeUiStep(clone, cloneRect, workspace.RTL, new Date(), workspace.scale);
}
/**
@@ -62,21 +64,25 @@ export function disposeUiEffect(block: BlockSvg) {
* @param workspaceScale Scale of workspace.
*/
function disposeUiStep(
clone: Element, rect: CloneRect, rtl: boolean, start: Date,
workspaceScale: number) {
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 =
rect.x + (rtl ? -1 : 1) * rect.width * workspaceScale / 2 * percent;
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 + ')');
'transform',
'translate(' + x + ',' + y + ')' + ' scale(' + scale + ')'
);
setTimeout(disposeUiStep, 10, clone, rect, rtl, start, workspaceScale);
}
}
@@ -92,7 +98,7 @@ export function connectionUiEffect(block: BlockSvg) {
const scale = workspace.scale;
workspace.getAudioManager().play('click');
if (scale < 1) {
return; // Too small to care about visual effects.
return; // Too small to care about visual effects.
}
// Determine the absolute coordinates of the inferior block.
const xy = workspace.getSvgXY(block.getSvgRoot());
@@ -105,15 +111,17 @@ export function connectionUiEffect(block: BlockSvg) {
xy.y += 3 * scale;
}
const ripple = dom.createSvgElement(
Svg.CIRCLE, {
'cx': xy.x,
'cy': xy.y,
'r': 0,
'fill': 'none',
'stroke': '#888',
'stroke-width': 10,
},
workspace.getParentSvg());
Svg.CIRCLE,
{
'cx': xy.x,
'cy': xy.y,
'r': 0,
'fill': 'none',
'stroke': '#888',
'stroke-width': 10,
},
workspace.getParentSvg()
);
// Start the animation.
connectionUiStep(ripple, new Date(), scale);
}
@@ -147,13 +155,13 @@ export function disconnectUiEffect(block: BlockSvg) {
disconnectUiStop();
block.workspace.getAudioManager().play('disconnect');
if (block.workspace.scale < 1) {
return; // Too small to care about visual effects.
return; // Too small to care about visual effects.
}
// Horizontal distance for bottom of block to wiggle.
const DISPLACEMENT = 10;
// Scale magnitude of skew to height of block.
const height = block.getHeightWidth().height;
let magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
let magnitude = (Math.atan(DISPLACEMENT / height) / Math.PI) * 180;
if (!block.RTL) {
magnitude *= -1;
}
@@ -170,8 +178,8 @@ export function disconnectUiEffect(block: BlockSvg) {
* @param start Date of animation's start.
*/
function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) {
const DURATION = 200; // Milliseconds.
const WIGGLES = 3; // Half oscillations.
const DURATION = 200; // Milliseconds.
const WIGGLES = 3; // Half oscillations.
const ms = new Date().getTime() - start.getTime();
const percent = ms / DURATION;
@@ -179,13 +187,15 @@ function disconnectUiStep(block: BlockSvg, magnitude: number, start: Date) {
let skew = '';
if (percent <= 1) {
const val = Math.round(
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude);
Math.sin(percent * Math.PI * WIGGLES) * (1 - percent) * magnitude
);
skew = `skewX(${val})`;
disconnectPid = setTimeout(disconnectUiStep, 10, block, magnitude, start);
}
block.getSvgRoot().setAttribute(
'transform', `${block.getTranslation()} ${skew}`);
block
.getSvgRoot()
.setAttribute('transform', `${block.getTranslation()} ${skew}`);
}
/**
@@ -199,7 +209,8 @@ export function disconnectUiStop() {
clearTimeout(disconnectPid);
disconnectPid = null;
}
wobblingBlock.getSvgRoot().setAttribute(
'transform', wobblingBlock.getTranslation());
wobblingBlock
.getSvgRoot()
.setAttribute('transform', wobblingBlock.getTranslation());
wobblingBlock = null;
}

View File

@@ -23,7 +23,6 @@ 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.
@@ -65,14 +64,16 @@ export class BlockDragSurfaceSvg {
/** @param container Containing element. */
constructor(private readonly container: Element) {
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);
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);
}
@@ -120,8 +121,9 @@ export class BlockDragSurfaceSvg {
this.childSurfaceXY.x = roundX;
this.childSurfaceXY.y = roundY;
this.dragGroup.setAttribute(
'transform',
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')');
'transform',
'translate(' + roundX + ',' + roundY + ') scale(' + scale + ')'
);
}
/**
@@ -200,7 +202,7 @@ export class BlockDragSurfaceSvg {
*
* @returns Drag surface block DOM element, or null if no blocks exist.
*/
getCurrentBlock(): Element|null {
getCurrentBlock(): Element | null {
return this.dragGroup.firstChild as Element;
}

View File

@@ -30,7 +30,6 @@ 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.
@@ -44,7 +43,7 @@ export class BlockDragger implements IBlockDragger {
protected workspace_: WorkspaceSvg;
/** Which drag area the mouse pointer is over, if any. */
private dragTarget_: IDragTarget|null = null;
private dragTarget_: IDragTarget | null = null;
/** Whether the block would be deleted if dropped immediately. */
protected wouldDeleteBlock_ = false;
@@ -59,8 +58,9 @@ export class BlockDragger implements IBlockDragger {
this.draggingBlock_ = block;
/** Object that keeps track of connections on dragged blocks. */
this.draggedConnectionManager_ =
new InsertionMarkerManager(this.draggingBlock_);
this.draggedConnectionManager_ = new InsertionMarkerManager(
this.draggingBlock_
);
this.workspace_ = workspace;
@@ -136,9 +136,11 @@ export class BlockDragger implements IBlockDragger {
*/
protected shouldDisconnect_(healStack: boolean): boolean {
return !!(
this.draggingBlock_.getParent() ||
healStack && this.draggingBlock_.nextConnection &&
this.draggingBlock_.nextConnection.targetBlock());
this.draggingBlock_.getParent() ||
(healStack &&
this.draggingBlock_.nextConnection &&
this.draggingBlock_.nextConnection.targetBlock())
);
}
/**
@@ -149,7 +151,9 @@ export class BlockDragger implements IBlockDragger {
* at mouse down, in pixel units.
*/
protected disconnectBlock_(
healStack: boolean, currentDragDeltaXY: Coordinate) {
healStack: boolean,
currentDragDeltaXY: Coordinate
) {
this.draggingBlock_.unplug(healStack);
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLoc = Coordinate.sum(this.startXY_, delta);
@@ -162,7 +166,10 @@ export class BlockDragger implements IBlockDragger {
/** 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));
this.draggingBlock_,
true,
this.draggingBlock_.getDescendants(false)
);
eventUtils.fire(event);
}
@@ -217,10 +224,11 @@ export class BlockDragger implements IBlockDragger {
blockAnimation.disconnectUiStop();
const preventMove = !!this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
const preventMove =
!!this.dragTarget_ &&
this.dragTarget_.shouldPreventMove(this.draggingBlock_);
let newLoc: Coordinate;
let delta: Coordinate|null = null;
let delta: Coordinate | null = null;
if (preventMove) {
newLoc = this.startXY_;
} else {
@@ -238,15 +246,17 @@ export class BlockDragger implements IBlockDragger {
if (!deleted) {
// These are expensive and don't need to be done if we're deleting.
this.draggingBlock_.setDragging(false);
if (delta) { // !preventMove
if (delta) {
// !preventMove
this.updateBlockAfterMove_();
} else {
// Blocks dragged directly from a flyout may need to be bumped into
// bounds.
bumpObjects.bumpIntoBounds(
this.draggingBlock_.workspace,
this.workspace_.getMetricsManager().getScrollMetrics(true),
this.draggingBlock_);
this.draggingBlock_.workspace,
this.workspace_.getMetricsManager().getScrollMetrics(true),
this.draggingBlock_
);
}
}
this.workspace_.setResizesEnabled(true);
@@ -262,8 +272,10 @@ export class BlockDragger implements IBlockDragger {
* @returns New location after drag. delta is in workspace units. newLocation
* is the new coordinate where the block should end up.
*/
protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate):
{delta: Coordinate, newLocation: Coordinate} {
protected getNewLocationAfterDrag_(currentDragDeltaXY: Coordinate): {
delta: Coordinate;
newLocation: Coordinate;
} {
const delta = this.pixelsToWorkspaceUnits_(currentDragDeltaXY);
const newLocation = Coordinate.sum(this.startXY_, delta);
return {
@@ -307,7 +319,10 @@ export class BlockDragger implements IBlockDragger {
/** 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));
this.draggingBlock_,
false,
this.draggingBlock_.getDescendants(false)
);
eventUtils.fire(event);
}
@@ -322,21 +337,25 @@ export class BlockDragger implements IBlockDragger {
const toolbox = this.workspace_.getToolbox();
if (toolbox) {
const style = this.draggingBlock_.isDeletable() ? 'blocklyToolboxDelete' :
'blocklyToolboxGrab';
const style = this.draggingBlock_.isDeletable()
? 'blocklyToolboxDelete'
: 'blocklyToolboxGrab';
// AnyDuringMigration because: Property 'removeStyle' does not exist on
// type 'IToolbox'.
if (isEnd &&
typeof (toolbox as AnyDuringMigration).removeStyle === 'function') {
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') {
!isEnd &&
typeof (toolbox as AnyDuringMigration).addStyle === 'function'
) {
// AnyDuringMigration because: Property 'addStyle' does not exist on
// type 'IToolbox'.
(toolbox as AnyDuringMigration).addStyle(style);
@@ -348,7 +367,8 @@ export class BlockDragger implements IBlockDragger {
protected fireMoveEvent_() {
if (this.draggingBlock_.isDeadOrDying()) return;
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
this.draggingBlock_) as BlockMove;
this.draggingBlock_
) as BlockMove;
event.setReason(['drag']);
event.oldCoordinate = this.startXY_;
event.recordNew();
@@ -374,8 +394,9 @@ export class BlockDragger implements IBlockDragger {
*/
protected pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
const result = new Coordinate(
pixelCoord.x / this.workspace_.scale,
pixelCoord.y / this.workspace_.scale);
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
@@ -408,8 +429,10 @@ export class BlockDragger implements IBlockDragger {
*/
getInsertionMarkers(): BlockSvg[] {
// No insertion markers with the old style of dragged connection managers.
if (this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers) {
if (
this.draggedConnectionManager_ &&
this.draggedConnectionManager_.getInsertionMarkers
) {
return this.draggedConnectionManager_.getInsertionMarkers();
}
return [];
@@ -433,15 +456,15 @@ export interface IconPositionData {
function initIconData(block: BlockSvg): IconPositionData[] {
// Build a list of icons that need to be moved and where they started.
const dragIconData = [];
const descendants = (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);

View File

@@ -25,7 +25,11 @@ import type {Connection} from './connection.js';
import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js';
import * as ContextMenu from './contextmenu.js';
import {ContextMenuOption, ContextMenuRegistry, LegacyContextMenuOption} from './contextmenu_registry.js';
import {
ContextMenuOption,
ContextMenuRegistry,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
import type {BlockMove} from './events/events_block_move.js';
import * as eventUtils from './events/utils.js';
import type {Field} from './field.js';
@@ -58,14 +62,14 @@ import type {Workspace} from './workspace.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import {queueRender} from './render_management.js';
/**
* Class for a block's SVG representation.
* Not normally called directly, workspace.newBlock() is preferred.
*/
export class BlockSvg extends Block implements IASTNodeLocationSvg,
IBoundedElement, ICopyable,
IDraggable {
export class BlockSvg
extends Block
implements IASTNodeLocationSvg, IBoundedElement, ICopyable, IDraggable
{
/**
* Constant for identifying rows that are to be rendered inline.
* Don't collide with Blockly.inputTypes.
@@ -81,15 +85,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
override decompose?: (p1: Workspace) => BlockSvg;
// override compose?: ((p1: BlockSvg) => void)|null;
saveConnections?: (p1: BlockSvg) => void;
customContextMenu?:
(p1: Array<ContextMenuOption|LegacyContextMenuOption>) => void;
customContextMenu?: (
p1: Array<ContextMenuOption | LegacyContextMenuOption>
) => void;
/**
* An property used internally to reference the block's rendering debugger.
*
* @internal
*/
renderingDebugger: BlockRenderingDebug|null = null;
renderingDebugger: BlockRenderingDebug | null = null;
/**
* Height of this block, not including any statement blocks above or below.
@@ -110,13 +115,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
private warningTextDb = new Map<string, ReturnType<typeof setTimeout>>();
/** Block's mutator icon (if any). */
mutator: Mutator|null = null;
mutator: Mutator | null = null;
/** Block's comment icon (if any). */
private commentIcon_: Comment|null = null;
private commentIcon_: Comment | null = null;
/** Block's warning icon (if any). */
warning: Warning|null = null;
warning: Warning | null = null;
private svgGroup_: SVGGElement;
style: BlockStyle;
@@ -160,7 +165,6 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
relativeCoords = new Coordinate(0, 0);
/**
* @param workspace The block's workspace.
* @param prototypeName Name of the language object containing type-specific
@@ -177,8 +181,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
/** The renderer's path object. */
this.pathObject =
workspace.getRenderer().makePathObject(this.svgGroup_, this.style);
this.pathObject = workspace
.getRenderer()
.makePathObject(this.svgGroup_, this.style);
/**
* Whether to move the block to the drag surface when it is dragged.
@@ -204,7 +209,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
if (!this.workspace.rendered) {
throw TypeError('Workspace is headless.');
}
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
input.init();
}
const icons = this.getIcons();
@@ -216,7 +221,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
const svg = this.getSvgRoot();
if (!this.workspace.options.readOnly && !this.eventsInit_ && svg) {
browserEvents.conditionalBind(
svg, 'pointerdown', this, this.onMouseDown_);
svg,
'pointerdown',
this,
this.onMouseDown_
);
}
this.eventsInit_ = true;
@@ -230,7 +239,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns #RRGGBB string.
*/
getColourSecondary(): string|undefined {
getColourSecondary(): string | undefined {
return this.style.colourSecondary;
}
@@ -239,7 +248,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns #RRGGBB string.
*/
getColourTertiary(): string|undefined {
getColourTertiary(): string | undefined {
return this.style.colourTertiary;
}
@@ -268,7 +277,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
}
const event = new (eventUtils.get(eventUtils.SELECTED))(
oldId, this.id, this.workspace.id);
oldId,
this.id,
this.workspace.id
);
eventUtils.fire(event);
common.setSelected(this);
this.addSelect();
@@ -283,7 +295,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
return;
}
const event = new (eventUtils.get(eventUtils.SELECTED))(
this.id, null, this.workspace.id);
this.id,
null,
this.workspace.id
);
event.workspaceId = this.workspace.id;
eventUtils.fire(event);
common.setSelected(null);
@@ -315,7 +330,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @param newParent New parent block.
* @internal
*/
override setParent(newParent: this|null) {
override setParent(newParent: this | null) {
const oldParent = this.parentBlock_;
if (newParent === oldParent) {
return;
@@ -359,9 +374,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
let x = 0;
let y = 0;
const dragSurfaceGroup = this.useDragSurface_ ?
this.workspace.getBlockDragSurface()!.getGroup() :
null;
const dragSurfaceGroup = this.useDragSurface_
? this.workspace.getBlockDragSurface()!.getGroup()
: null;
let element: SVGElement = this.getSvgRoot();
if (element) {
@@ -372,17 +387,22 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
y += xy.y;
// If this element is the current element on the drag surface, include
// the translation of the drag surface itself.
if (this.useDragSurface_ &&
this.workspace.getBlockDragSurface()!.getCurrentBlock() ===
element) {
const surfaceTranslation =
this.workspace.getBlockDragSurface()!.getSurfaceTranslation();
if (
this.useDragSurface_ &&
this.workspace.getBlockDragSurface()!.getCurrentBlock() === element
) {
const surfaceTranslation = this.workspace
.getBlockDragSurface()!
.getSurfaceTranslation();
x += surfaceTranslation.x;
y += surfaceTranslation.y;
}
element = element.parentNode as SVGElement;
} while (element && element !== this.workspace.getCanvas() &&
element !== dragSurfaceGroup);
} while (
element &&
element !== this.workspace.getCanvas() &&
element !== dragSurfaceGroup
);
}
return new Coordinate(x, y);
}
@@ -399,9 +419,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
throw Error('Block has parent');
}
const eventsEnabled = eventUtils.isEnabled();
let event: BlockMove|null = null;
let event: BlockMove | null = null;
if (eventsEnabled) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))!(this) as BlockMove;
event = new (eventUtils.get(eventUtils.BLOCK_MOVE)!)(this) as BlockMove;
reason && event.setReason(reason);
}
const xy = this.getRelativeToSurfaceXY();
@@ -487,8 +507,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
// Translate to current position, turning off 3d.
this.translate(newXY.x, newXY.y);
this.workspace.getBlockDragSurface()!.clearAndHide(
this.workspace.getCanvas());
this.workspace
.getBlockDragSurface()!
.clearAndHide(this.workspace.getCanvas());
}
/**
@@ -501,8 +522,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
moveDuringDrag(newLoc: Coordinate) {
if (this.useDragSurface_) {
this.workspace.getBlockDragSurface()!.translateSurface(
newLoc.x, newLoc.y);
this.workspace
.getBlockDragSurface()!
.translateSurface(newLoc.x, newLoc.y);
} else {
this.translate(newLoc.x, newLoc.y);
this.getSvgRoot().setAttribute('transform', this.getTranslation());
@@ -520,29 +542,31 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
/** Snap this block to the nearest grid point. */
snapToGrid() {
if (this.isDeadOrDying()) {
return; // Deleted block.
return; // Deleted block.
}
if (this.workspace.isDragging()) {
return; // Don't bump blocks during a drag.;
return; // Don't bump blocks during a drag.;
}
if (this.getParent()) {
return; // Only snap top-level blocks.
return; // Only snap top-level blocks.
}
if (this.isInFlyout) {
return; // Don't move blocks around in a flyout.
return; // Don't move blocks around in a flyout.
}
const grid = this.workspace.getGrid();
if (!grid || !grid.shouldSnap()) {
return; // Config says no snapping.
return; // Config says no snapping.
}
const spacing = grid.getSpacing();
const half = spacing / 2;
const xy = this.getRelativeToSurfaceXY();
const dx =
Math.round(Math.round((xy.x - half) / spacing) * spacing + half - xy.x);
const dy =
Math.round(Math.round((xy.y - half) / spacing) * spacing + half - xy.y);
const dx = Math.round(
Math.round((xy.x - half) / spacing) * spacing + half - xy.x
);
const dy = Math.round(
Math.round((xy.y - half) / spacing) * spacing + half - xy.y
);
if (dx || dy) {
this.moveBy(dx, dy, ['snap']);
}
@@ -576,7 +600,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
markDirty() {
this.pathObject.constants = this.workspace.getRenderer().getConstants();
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
input.markDirty();
}
}
@@ -603,7 +627,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
const collapsedInputName = constants.COLLAPSED_INPUT_NAME;
const collapsedFieldName = constants.COLLAPSED_FIELD_NAME;
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
if (input.name !== collapsedInputName) {
input.setVisible(!collapsed);
}
@@ -616,7 +640,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
const icons = this.getIcons();
for (let i = 0, icon; icon = icons[i]; i++) {
for (let i = 0, icon; (icon = icons[i]); i++) {
icon.setVisible(false);
}
@@ -626,8 +650,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
field.setValue(text);
return;
}
const input = this.getInput(collapsedInputName) ||
this.appendDummyInput(collapsedInputName);
const input =
this.getInput(collapsedInputName) ||
this.appendDummyInput(collapsedInputName);
input.appendField(new FieldLabel(text), collapsedFieldName);
}
@@ -679,7 +704,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
showHelp() {
const url =
typeof this.helpUrl === 'function' ? this.helpUrl() : this.helpUrl;
typeof this.helpUrl === 'function' ? this.helpUrl() : this.helpUrl;
if (url) {
window.open(url);
}
@@ -690,13 +715,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns Context menu options or null if no menu.
*/
protected generateContextMenu():
Array<ContextMenuOption|LegacyContextMenuOption>|null {
protected generateContextMenu(): Array<
ContextMenuOption | LegacyContextMenuOption
> | null {
if (this.workspace.options.readOnly || !this.contextMenu) {
return null;
}
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
ContextMenuRegistry.ScopeType.BLOCK, {block: this});
ContextMenuRegistry.ScopeType.BLOCK,
{block: this}
);
// Allow the block to add or modify menuOptions.
if (this.customContextMenu) {
@@ -815,12 +843,13 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
override setInsertionMarker(insertionMarker: boolean) {
if (this.isInsertionMarker_ === insertionMarker) {
return; // No change.
return; // No change.
}
this.isInsertionMarker_ = insertionMarker;
if (this.isInsertionMarker_) {
this.setColour(
this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR);
this.workspace.getRenderer().getConstants().INSERTION_MARKER_COLOUR
);
this.pathObject.updateInsertionMarker(true);
}
}
@@ -897,8 +926,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
// (https://github.com/google/blockly/issues/4832)
this.dispose(false, true);
} else {
this.dispose(/* heal */
true, true);
this.dispose(/* heal */ true, true);
}
eventUtils.setGroup(false);
}
@@ -909,14 +937,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns Copy metadata, or null if the block is an insertion marker.
* @internal
*/
toCopyData(): CopyData|null {
toCopyData(): CopyData | null {
if (this.isInsertionMarker_) {
return null;
}
return {
saveInfo:
blocks.save(this, {addCoordinates: true, addNextBlocks: false}) as
blocks.State,
saveInfo: blocks.save(this, {
addCoordinates: true,
addNextBlocks: false,
}) as blocks.State,
source: this.workspace,
typeCounts: common.getBlockTypeCounts(this, true),
};
@@ -935,8 +964,8 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
icons[i].applyColour();
}
for (let x = 0, input; input = this.inputList[x]; x++) {
for (let y = 0, field; field = input.fieldRow[y]; y++) {
for (let x = 0, input; (input = this.inputList[x]); x++) {
for (let y = 0, field; (field = input.fieldRow[y]); y++) {
field.applyColour();
}
}
@@ -969,7 +998,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns The comment icon attached to this block, or null.
*/
getCommentIcon(): Comment|null {
getCommentIcon(): Comment | null {
return this.commentIcon_;
}
@@ -978,7 +1007,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @param text The text, or null to delete.
*/
override setCommentText(text: string|null) {
override setCommentText(text: string | null) {
if (this.commentModel.text === text) {
return;
}
@@ -993,11 +1022,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
if (shouldHaveComment) {
this.commentIcon_ = new Comment(this);
this.comment = this.commentIcon_; // For backwards compatibility.
this.comment = this.commentIcon_; // For backwards compatibility.
} else {
this.commentIcon_!.dispose();
this.commentIcon_ = null;
this.comment = null; // For backwards compatibility.
this.comment = null; // For backwards compatibility.
}
if (this.rendered) {
// Icons must force an immediate render so that bubbles can be opened
@@ -1015,7 +1044,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @param opt_id An optional ID for the warning text to be able to maintain
* multiple warnings.
*/
override setWarningText(text: string|null, opt_id?: string) {
override setWarningText(text: string | null, opt_id?: string) {
const id = opt_id || '';
if (!id) {
// Kill all previous pending processes, this edit supersedes them all.
@@ -1031,12 +1060,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
if (this.workspace.isDragging()) {
// Don't change the warning text during a drag.
// Wait until the drag finishes.
this.warningTextDb.set(id, setTimeout(() => {
if (!this.isDeadOrDying()) {
this.warningTextDb.delete(id);
this.setWarningText(text, id);
}
}, 100));
this.warningTextDb.set(
id,
setTimeout(() => {
if (!this.isDeadOrDying()) {
this.warningTextDb.delete(id);
this.setWarningText(text, id);
}
}, 100)
);
return;
}
if (this.isInFlyout) {
@@ -1056,14 +1088,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
}
if (collapsedParent) {
collapsedParent.setWarningText(
Msg['COLLAPSED_WARNINGS_WARNING'], BlockSvg.COLLAPSED_WARNING_ID);
Msg['COLLAPSED_WARNINGS_WARNING'],
BlockSvg.COLLAPSED_WARNING_ID
);
}
if (!this.warning) {
this.warning = new Warning(this);
changedState = true;
}
this.warning!.setText((text), id);
this.warning!.setText(text, id);
} else {
// Dispose all warnings if no ID is given.
if (this.warning && !id) {
@@ -1093,7 +1127,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @param mutator A mutator dialog instance or null to remove.
*/
override setMutator(mutator: Mutator|null) {
override setMutator(mutator: Mutator | null) {
if (this.mutator && this.mutator !== mutator) {
this.mutator.dispose();
}
@@ -1186,11 +1220,12 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @param colour HSV hue value, or #RRGGBB string.
*/
override setColour(colour: number|string) {
override setColour(colour: number | string) {
super.setColour(colour);
const styleObj =
this.workspace.getRenderer().getConstants().getBlockStyleForColour(
this.colour_);
const styleObj = this.workspace
.getRenderer()
.getConstants()
.getBlockStyleForColour(this.colour_);
this.pathObject.setStyle(styleObj.style);
this.style = styleObj.style;
@@ -1206,9 +1241,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @throws {Error} if the block style does not exist.
*/
override setStyle(blockStyleName: string) {
const blockStyle =
this.workspace.getRenderer().getConstants().getBlockStyle(
blockStyleName);
const blockStyle = this.workspace
.getRenderer()
.getConstants()
.getBlockStyle(blockStyleName);
this.styleName_ = blockStyleName;
if (blockStyle) {
@@ -1234,7 +1270,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
bringToFront() {
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
let block: this|null = this;
let block: this | null = this;
do {
const root = block.getSvgRoot();
const parent = root.parentNode;
@@ -1255,7 +1291,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* if any type could be connected.
*/
override setPreviousStatement(
newBoolean: boolean, opt_check?: string|string[]|null) {
newBoolean: boolean,
opt_check?: string | string[] | null
) {
super.setPreviousStatement(newBoolean, opt_check);
if (this.rendered) {
@@ -1272,7 +1310,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* if any type could be connected.
*/
override setNextStatement(
newBoolean: boolean, opt_check?: string|string[]|null) {
newBoolean: boolean,
opt_check?: string | string[] | null
) {
super.setNextStatement(newBoolean, opt_check);
if (this.rendered) {
@@ -1288,7 +1328,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @param opt_check Returned type or list of returned types. Null or
* undefined if any type could be returned (e.g. variable get).
*/
override setOutput(newBoolean: boolean, opt_check?: string|string[]|null) {
override setOutput(
newBoolean: boolean,
opt_check?: string | string[] | null
) {
super.setOutput(newBoolean, opt_check);
if (this.rendered) {
@@ -1372,14 +1415,14 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
setConnectionTracking(track: boolean) {
if (this.previousConnection) {
(this.previousConnection).setTracking(track);
this.previousConnection.setTracking(track);
}
if (this.outputConnection) {
(this.outputConnection).setTracking(track);
this.outputConnection.setTracking(track);
}
if (this.nextConnection) {
(this.nextConnection).setTracking(track);
const child = (this.nextConnection).targetBlock();
this.nextConnection.setTracking(track);
const child = this.nextConnection.targetBlock();
if (child) {
child.setConnectionTracking(track);
}
@@ -1428,7 +1471,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
myConnections.push(this.nextConnection);
}
if (all || !this.collapsed_) {
for (let i = 0, input; input = this.inputList[i]; i++) {
for (let i = 0, input; (input = this.inputList[i]); i++) {
if (input.connection) {
myConnections.push(input.connection as RenderedConnection);
}
@@ -1448,8 +1491,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns The last next connection on the stack, or null.
* @internal
*/
override lastConnectionInStack(ignoreShadows: boolean): RenderedConnection
|null {
override lastConnectionInStack(
ignoreShadows: boolean
): RenderedConnection | null {
return super.lastConnectionInStack(ignoreShadows) as RenderedConnection;
}
@@ -1463,8 +1507,10 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns The matching connection on this block, or null.
* @internal
*/
override getMatchingConnection(otherBlock: Block, conn: Connection):
RenderedConnection|null {
override getMatchingConnection(
otherBlock: Block,
conn: Connection
): RenderedConnection | null {
return super.getMatchingConnection(otherBlock, conn) as RenderedConnection;
}
@@ -1483,7 +1529,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns The next statement block or null.
*/
override getNextBlock(): BlockSvg|null {
override getNextBlock(): BlockSvg | null {
return super.getNextBlock() as BlockSvg;
}
@@ -1492,7 +1538,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*
* @returns The previous statement block or null.
*/
override getPreviousBlock(): BlockSvg|null {
override getPreviousBlock(): BlockSvg | null {
return super.getPreviousBlock() as BlockSvg;
}
@@ -1520,8 +1566,11 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
private bumpNeighboursInternal() {
const root = this.getRootBlock();
if (this.isDeadOrDying() || this.workspace.isDragging() ||
root.isInFlyout) {
if (
this.isDeadOrDying() ||
this.workspace.isDragging() ||
root.isInFlyout
) {
return;
}
@@ -1581,12 +1630,15 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @internal
*/
positionNearConnection(
sourceConnection: RenderedConnection,
targetConnection: RenderedConnection) {
sourceConnection: RenderedConnection,
targetConnection: RenderedConnection
) {
// We only need to position the new block if it's before the existing one,
// otherwise its position is set by the previous block.
if (sourceConnection.type === ConnectionType.NEXT_STATEMENT ||
sourceConnection.type === ConnectionType.INPUT_VALUE) {
if (
sourceConnection.type === ConnectionType.NEXT_STATEMENT ||
sourceConnection.type === ConnectionType.INPUT_VALUE
) {
const dx = targetConnection.x - sourceConnection.x;
const dy = targetConnection.y - sourceConnection.y;
@@ -1598,7 +1650,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns The first statement connection or null.
* @internal
*/
override getFirstStatementConnection(): RenderedConnection|null {
override getFirstStatementConnection(): RenderedConnection | null {
return super.getFirstStatementConnection() as RenderedConnection | null;
}
@@ -1636,7 +1688,7 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
*/
render(opt_bubble?: boolean) {
if (this.renderIsInProgress_) {
return; // Don't allow recursive renders.
return; // Don't allow recursive renders.
}
this.renderIsInProgress_ = true;
try {
@@ -1784,15 +1836,16 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg,
* @returns Object with height and width properties in workspace units.
* @internal
*/
getHeightWidth(): {height: number, width: number} {
getHeightWidth(): {height: number; width: number} {
let height = this.height;
let width = this.width;
// Recursively add size of subsequent blocks.
const nextBlock = this.getNextBlock();
if (nextBlock) {
const nextHeightWidth = nextBlock.getHeightWidth();
const tabHeight =
this.workspace.getRenderer().getConstants().NOTCH_HEIGHT;
const tabHeight = this.workspace
.getRenderer()
.getConstants().NOTCH_HEIGHT;
height += nextHeightWidth.height - tabHeight;
width = Math.max(width, nextHeightWidth.width);
}

View File

@@ -48,19 +48,75 @@ 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, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
import {FieldAngle, FieldAngleConfig, FieldAngleFromJsonConfig, FieldAngleValidator} from './field_angle.js';
import {FieldCheckbox, FieldCheckboxConfig, FieldCheckboxFromJsonConfig, FieldCheckboxValidator} from './field_checkbox.js';
import {FieldColour, FieldColourConfig, FieldColourFromJsonConfig, FieldColourValidator} from './field_colour.js';
import {FieldDropdown, FieldDropdownConfig, FieldDropdownFromJsonConfig, FieldDropdownValidator, MenuGenerator, MenuGeneratorFunction, MenuOption} from './field_dropdown.js';
import {FieldImage, FieldImageConfig, FieldImageFromJsonConfig} from './field_image.js';
import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js';
import {
Field,
FieldConfig,
FieldValidator,
UnattachedFieldError,
} from './field.js';
import {
FieldAngle,
FieldAngleConfig,
FieldAngleFromJsonConfig,
FieldAngleValidator,
} from './field_angle.js';
import {
FieldCheckbox,
FieldCheckboxConfig,
FieldCheckboxFromJsonConfig,
FieldCheckboxValidator,
} from './field_checkbox.js';
import {
FieldColour,
FieldColourConfig,
FieldColourFromJsonConfig,
FieldColourValidator,
} from './field_colour.js';
import {
FieldDropdown,
FieldDropdownConfig,
FieldDropdownFromJsonConfig,
FieldDropdownValidator,
MenuGenerator,
MenuGeneratorFunction,
MenuOption,
} from './field_dropdown.js';
import {
FieldImage,
FieldImageConfig,
FieldImageFromJsonConfig,
} from './field_image.js';
import {
FieldLabel,
FieldLabelConfig,
FieldLabelFromJsonConfig,
} from './field_label.js';
import {FieldLabelSerializable} from './field_label_serializable.js';
import {FieldMultilineInput, FieldMultilineInputConfig, FieldMultilineInputFromJsonConfig, FieldMultilineInputValidator} from './field_multilineinput.js';
import {FieldNumber, FieldNumberConfig, FieldNumberFromJsonConfig, FieldNumberValidator} from './field_number.js';
import {
FieldMultilineInput,
FieldMultilineInputConfig,
FieldMultilineInputFromJsonConfig,
FieldMultilineInputValidator,
} from './field_multilineinput.js';
import {
FieldNumber,
FieldNumberConfig,
FieldNumberFromJsonConfig,
FieldNumberValidator,
} from './field_number.js';
import * as fieldRegistry from './field_registry.js';
import {FieldTextInput, FieldTextInputConfig, FieldTextInputFromJsonConfig, FieldTextInputValidator} from './field_textinput.js';
import {FieldVariable, FieldVariableConfig, FieldVariableFromJsonConfig, FieldVariableValidator} from './field_variable.js';
import {
FieldTextInput,
FieldTextInputConfig,
FieldTextInputFromJsonConfig,
FieldTextInputValidator,
} from './field_textinput.js';
import {
FieldVariable,
FieldVariableConfig,
FieldVariableFromJsonConfig,
FieldVariableValidator,
} from './field_variable.js';
import {Flyout} from './flyout_base.js';
import {FlyoutButton} from './flyout_button.js';
import {HorizontalFlyout} from './flyout_horizontal.js';
@@ -105,7 +161,10 @@ import {ISelectableToolboxItem} from './interfaces/i_selectable_toolbox_item.js'
import {IStyleable} from './interfaces/i_styleable.js';
import {IToolbox} from './interfaces/i_toolbox.js';
import {IToolboxItem} from './interfaces/i_toolbox_item.js';
import {IVariableBackedParameterModel, isVariableBackedParameterModel} from './interfaces/i_variable_backed_parameter_model.js';
import {
IVariableBackedParameterModel,
isVariableBackedParameterModel,
} from './interfaces/i_variable_backed_parameter_model.js';
import * as internalConstants from './internal_constants.js';
import {ASTNode} from './keyboard_nav/ast_node.js';
import {BasicCursor} from './keyboard_nav/basic_cursor.js';
@@ -163,11 +222,13 @@ 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 {
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
@@ -327,8 +388,11 @@ export const setParentContainer = common.setParentContainer;
*/
function resizeSvgContentsLocal(workspace: WorkspaceSvg) {
deprecation.warn(
'Blockly.resizeSvgContents', 'December 2021', 'December 2022',
'Blockly.WorkspaceSvg.resizeSvgContents');
'Blockly.resizeSvgContents',
'December 2021',
'December 2022',
'Blockly.WorkspaceSvg.resizeSvgContents'
);
realResizeSvgContents(workspace);
}
export const resizeSvgContents = resizeSvgContentsLocal;
@@ -342,8 +406,11 @@ export const resizeSvgContents = resizeSvgContentsLocal;
*/
export function copy(toCopy: ICopyable) {
deprecation.warn(
'Blockly.copy', 'December 2021', 'December 2022',
'Blockly.clipboard.copy');
'Blockly.copy',
'December 2021',
'December 2022',
'Blockly.clipboard.copy'
);
clipboard.copy(toCopy);
}
@@ -356,8 +423,11 @@ export function copy(toCopy: ICopyable) {
*/
export function paste(): boolean {
deprecation.warn(
'Blockly.paste', 'December 2021', 'December 2022',
'Blockly.clipboard.paste');
'Blockly.paste',
'December 2021',
'December 2022',
'Blockly.clipboard.paste'
);
return !!clipboard.paste();
}
@@ -370,8 +440,11 @@ export function paste(): boolean {
*/
export function duplicate(toDuplicate: ICopyable) {
deprecation.warn(
'Blockly.duplicate', 'December 2021', 'December 2022',
'Blockly.clipboard.duplicate');
'Blockly.duplicate',
'December 2021',
'December 2022',
'Blockly.clipboard.duplicate'
);
clipboard.duplicate(toDuplicate);
}
@@ -385,8 +458,11 @@ export function duplicate(toDuplicate: ICopyable) {
*/
export function isNumber(str: string): boolean {
deprecation.warn(
'Blockly.isNumber', 'December 2021', 'December 2022',
'Blockly.utils.string.isNumber');
'Blockly.isNumber',
'December 2021',
'December 2022',
'Blockly.utils.string.isNumber'
);
return utils.string.isNumber(str);
}
@@ -400,8 +476,11 @@ export function isNumber(str: string): boolean {
*/
export function hueToHex(hue: number): string {
deprecation.warn(
'Blockly.hueToHex', 'December 2021', 'December 2022',
'Blockly.utils.colour.hueToHex');
'Blockly.hueToHex',
'December 2021',
'December 2022',
'Blockly.utils.colour.hueToHex'
);
return colour.hueToHex(hue);
}
@@ -420,11 +499,17 @@ export function hueToHex(hue: number): string {
* @see Blockly.browserEvents.bind
*/
export function bindEvent_(
node: EventTarget, name: string, thisObject: Object|null,
func: Function): browserEvents.Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function
): browserEvents.Data {
deprecation.warn(
'Blockly.bindEvent_', 'December 2021', 'December 2022',
'Blockly.browserEvents.bind');
'Blockly.bindEvent_',
'December 2021',
'December 2022',
'Blockly.browserEvents.bind'
);
return browserEvents.bind(node, name, thisObject, func);
}
@@ -439,8 +524,11 @@ export function bindEvent_(
*/
export function unbindEvent_(bindData: browserEvents.Data): Function {
deprecation.warn(
'Blockly.unbindEvent_', 'December 2021', 'December 2022',
'Blockly.browserEvents.unbind');
'Blockly.unbindEvent_',
'December 2021',
'December 2022',
'Blockly.browserEvents.unbind'
);
return browserEvents.unbind(bindData);
}
@@ -463,14 +551,26 @@ export function unbindEvent_(bindData: browserEvents.Data): Function {
* @see browserEvents.conditionalBind
*/
export function bindEventWithChecks_(
node: EventTarget, name: string, thisObject: Object|null, func: Function,
opt_noCaptureIdentifier?: boolean,
_opt_noPreventDefault?: boolean): browserEvents.Data {
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');
'Blockly.bindEventWithChecks_',
'December 2021',
'December 2022',
'Blockly.browserEvents.conditionalBind'
);
return browserEvents.conditionalBind(
node, name, thisObject, func, opt_noCaptureIdentifier);
node,
name,
thisObject,
func,
opt_noCaptureIdentifier
);
}
// Aliases to allow external code to access these values for legacy reasons.
@@ -495,7 +595,7 @@ export const VARIABLE_CATEGORY_NAME: string = Variables.CATEGORY_NAME;
* variable blocks.
*/
export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
VariablesDynamic.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
@@ -503,58 +603,62 @@ export const VARIABLE_DYNAMIC_CATEGORY_NAME: string =
*/
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);
};
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.prototype.newBlock = function (
prototypeName: string,
opt_id?: string
): BlockSvg {
return new BlockSvg(this, prototypeName, opt_id);
};
WorkspaceSvg.newTrashcan = function(workspace: WorkspaceSvg): Trashcan {
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 = [];
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));
}
if (this.isDeletable() && this.isMovable()) {
menuOptions.push(ContextMenu.commentDuplicateOption(this));
menuOptions.push(ContextMenu.commentDeleteOption(this));
}
ContextMenu.show(e, menuOptions, this.RTL);
};
ContextMenu.show(e, menuOptions, this.RTL);
};
Mutator.prototype.newWorkspaceSvg =
function(options: Options): WorkspaceSvg {
return new WorkspaceSvg(options);
};
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 =
procedures[0].concat(procedures[1]);
for (let i = 0; i < flattenedProcedures.length; i++) {
this.getName(flattenedProcedures[i][0], Names.NameType.PROCEDURE);
}
};
Names.prototype.populateProcedures = function (
this: Names,
workspace: Workspace
) {
const procedures = Procedures.allProcedures(workspace);
// Flatten the return vs no-return procedure lists.
const flattenedProcedures = 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};
@@ -668,9 +772,9 @@ export {Flyout};
export {FlyoutButton};
export {FlyoutMetricsManager};
export {CodeGenerator};
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
export {Gesture};
export {Gesture as TouchGesture}; // Remove in v10.
export {Gesture as TouchGesture}; // Remove in v10.
export {Grid};
export {HorizontalFlyout};
export {IASTNodeLocation};

View File

@@ -11,7 +11,6 @@ import type {Theme, ITheme} from './theme.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import type {ToolboxDefinition} from './utils/toolbox.js';
/**
* Blockly options.
*/
@@ -32,14 +31,14 @@ export interface BlocklyOptions {
renderer?: string;
rendererOverrides?: {[rendererConstant: string]: any};
rtl?: boolean;
scrollbars?: ScrollbarOptions|boolean;
scrollbars?: ScrollbarOptions | boolean;
sounds?: boolean;
theme?: Theme|string|ITheme;
toolbox?: string|ToolboxDefinition|Element;
theme?: Theme | string | ITheme;
toolbox?: string | ToolboxDefinition | Element;
toolboxPosition?: string;
trashcan?: boolean;
maxTrashcanContents?: number;
plugins?: {[key: string]: (new(...p1: any[]) => any)|string};
plugins?: {[key: string]: (new (...p1: any[]) => any) | string};
zoom?: ZoomOptions;
parentWorkspace?: WorkspaceSvg;
}
@@ -53,7 +52,7 @@ export interface GridOptions {
export interface MoveOptions {
drag?: boolean;
scrollbars?: boolean|ScrollbarOptions;
scrollbars?: boolean | ScrollbarOptions;
wheel?: boolean;
}

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.blocks');
/**
* A block definition. For now this very loose, but it can potentially
* be refined e.g. by replacing this typedef with a class definition.

View File

@@ -11,7 +11,6 @@ import * as Touch from './touch.js';
import * as deprecation from './utils/deprecation.js';
import * as userAgent from './utils/useragent.js';
/**
* Blockly opaque event data used to unbind events when using
* `bind` and `conditionalBind`.
@@ -49,12 +48,19 @@ const PAGE_MODE_MULTIPLIER = 125;
* @returns Opaque data that can be passed to unbindEvent_.
*/
export function conditionalBind(
node: EventTarget, name: string, thisObject: Object|null, func: Function,
opt_noCaptureIdentifier?: boolean, opt_noPreventDefault?: boolean): Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function,
opt_noCaptureIdentifier?: boolean,
opt_noPreventDefault?: boolean
): Data {
if (opt_noPreventDefault !== undefined) {
deprecation.warn(
'The opt_noPreventDefault argument of conditionalBind', 'version 9',
'version 10');
'The opt_noPreventDefault argument of conditionalBind',
'version 9',
'version 10'
);
}
/**
*
@@ -99,8 +105,11 @@ export function conditionalBind(
* @returns Opaque data that can be passed to unbindEvent_.
*/
export function bind(
node: EventTarget, name: string, thisObject: Object|null,
func: Function): Data {
node: EventTarget,
name: string,
thisObject: Object | null,
func: Function
): Data {
/**
*
* @param e
@@ -154,17 +163,24 @@ export function unbind(bindData: Data): (e: Event) => void {
*/
export function isTargetInput(e: Event): boolean {
if (e.target instanceof HTMLElement) {
if (e.target.isContentEditable ||
e.target.getAttribute('data-is-text-input') === 'true') {
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';
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) {
@@ -200,7 +216,10 @@ export function isRightButton(e: MouseEvent): boolean {
* @returns Object with .x and .y properties.
*/
export function mouseToSvg(
e: MouseEvent, svg: SVGSVGElement, matrix: SVGMatrix|null): SVGPoint {
e: MouseEvent,
svg: SVGSVGElement,
matrix: SVGMatrix | null
): SVGPoint {
const svgPoint = svg.createSVGPoint();
svgPoint.x = e.clientX;
svgPoint.y = e.clientY;
@@ -217,17 +236,17 @@ export function mouseToSvg(
* @param e Mouse event.
* @returns Scroll delta object with .x and .y properties.
*/
export function getScrollDeltaPixels(e: WheelEvent): {x: number, y: number} {
export function getScrollDeltaPixels(e: WheelEvent): {x: number; y: number} {
switch (e.deltaMode) {
case 0x00: // Pixel mode.
case 0x00: // Pixel mode.
default:
return {x: e.deltaX, y: e.deltaY};
case 0x01: // Line mode.
case 0x01: // Line mode.
return {
x: e.deltaX * LINE_MODE_MULTIPLIER,
y: e.deltaY * LINE_MODE_MULTIPLIER,
};
case 0x02: // Page mode.
case 0x02: // Page mode.
return {
x: e.deltaX * PAGE_MODE_MULTIPLIER,
y: e.deltaY * PAGE_MODE_MULTIPLIER,

View File

@@ -27,7 +27,6 @@ import {Svg} from './utils/svg.js';
import * as userAgent from './utils/useragent.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Class for UI bubble.
*/
@@ -54,10 +53,10 @@ export class Bubble implements IBubble {
static ANCHOR_RADIUS = 8;
/** Mouse up event data. */
private static onMouseUpWrapper: browserEvents.Data|null = null;
private static onMouseUpWrapper: browserEvents.Data | null = null;
/** Mouse move event data. */
private static onMouseMoveWrapper: browserEvents.Data|null = null;
private static onMouseMoveWrapper: browserEvents.Data | null = null;
workspace_: WorkspaceSvg;
content_: SVGElement;
@@ -67,18 +66,18 @@ export class Bubble implements IBubble {
private readonly rendered: boolean;
/** The SVG group containing all parts of the bubble. */
private bubbleGroup: SVGGElement|null = null;
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;
private bubbleArrow: SVGPathElement | null = null;
/** The SVG rect for the main body of the bubble. */
private bubbleBack: SVGRectElement|null = null;
private bubbleBack: SVGRectElement | null = null;
/** The SVG group for the resize hash marks on some bubbles. */
private resizeGroup: SVGGElement|null = null;
private resizeGroup: SVGGElement | null = null;
/** Absolute coordinate of anchor point, in workspace coordinates. */
private anchorXY!: Coordinate;
@@ -106,16 +105,16 @@ export class Bubble implements IBubble {
private autoLayout = true;
/** Method to call on resize of bubble. */
private resizeCallback: (() => void)|null = null;
private resizeCallback: (() => void) | null = null;
/** Method to call on move of bubble. */
private moveCallback: (() => void)|null = null;
private moveCallback: (() => void) | null = null;
/** Mouse down on bubbleBack event data. */
private onMouseDownBubbleWrapper: browserEvents.Data|null = null;
private onMouseDownBubbleWrapper: browserEvents.Data | null = null;
/** Mouse down on resizeGroup event data. */
private onMouseDownResizeWrapper: browserEvents.Data|null = null;
private onMouseDownResizeWrapper: browserEvents.Data | null = null;
/**
* Describes whether this bubble has been disposed of (nodes and event
@@ -135,9 +134,13 @@ export class Bubble implements IBubble {
* @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) {
workspace: WorkspaceSvg,
content: SVGElement,
shape: SVGElement,
anchorXY: Coordinate,
bubbleWidth: number | null,
bubbleHeight: number | null
) {
this.rendered = false;
this.workspace_ = workspace;
this.content_ = content;
@@ -151,7 +154,8 @@ export class Bubble implements IBubble {
const canvas = workspace.getBubbleCanvas();
canvas.appendChild(
this.createDom(content, !!(bubbleWidth && bubbleHeight)));
this.createDom(content, !!(bubbleWidth && bubbleHeight))
);
this.setAnchorLocation(anchorXY);
if (!bubbleWidth || !bubbleHeight) {
@@ -192,8 +196,10 @@ export class Bubble implements IBubble {
*/
this.bubbleGroup = dom.createSvgElement(Svg.G, {});
let filter: {filter?: string} = {
'filter': 'url(#' +
this.workspace_.getRenderer().getConstants().embossFilterId + ')',
'filter':
'url(#' +
this.workspace_.getRenderer().getConstants().embossFilterId +
')',
};
if (userAgent.JavaFx) {
// Multiple reports that JavaFX can't handle filters.
@@ -203,53 +209,70 @@ export class Bubble implements IBubble {
const bubbleEmboss = dom.createSvgElement(Svg.G, filter, this.bubbleGroup);
this.bubbleArrow = dom.createSvgElement(Svg.PATH, {}, bubbleEmboss);
this.bubbleBack = dom.createSvgElement(
Svg.RECT, {
'class': 'blocklyDraggable',
'x': 0,
'y': 0,
'rx': Bubble.BORDER_WIDTH,
'ry': Bubble.BORDER_WIDTH,
},
bubbleEmboss);
Svg.RECT,
{
'class': 'blocklyDraggable',
'x': 0,
'y': 0,
'rx': Bubble.BORDER_WIDTH,
'ry': Bubble.BORDER_WIDTH,
},
bubbleEmboss
);
if (hasResize) {
this.resizeGroup = dom.createSvgElement(
Svg.G, {
'class': this.workspace_.RTL ? 'blocklyResizeSW' :
'blocklyResizeSE',
},
this.bubbleGroup);
Svg.G,
{
'class': this.workspace_.RTL ? 'blocklyResizeSW' : 'blocklyResizeSE',
},
this.bubbleGroup
);
const size = 2 * Bubble.BORDER_WIDTH;
dom.createSvgElement(
Svg.POLYGON, {'points': `0,${size} ${size},${size} ${size},0`},
this.resizeGroup);
Svg.POLYGON,
{'points': `0,${size} ${size},${size} ${size},0`},
this.resizeGroup
);
dom.createSvgElement(
Svg.LINE, {
'class': 'blocklyResizeLine',
'x1': size / 3,
'y1': size - 1,
'x2': size - 1,
'y2': size / 3,
},
this.resizeGroup);
Svg.LINE,
{
'class': 'blocklyResizeLine',
'x1': size / 3,
'y1': size - 1,
'x2': size - 1,
'y2': size / 3,
},
this.resizeGroup
);
dom.createSvgElement(
Svg.LINE, {
'class': 'blocklyResizeLine',
'x1': size * 2 / 3,
'y1': size - 1,
'x2': size - 1,
'y2': size * 2 / 3,
},
this.resizeGroup);
Svg.LINE,
{
'class': 'blocklyResizeLine',
'x1': (size * 2) / 3,
'y1': size - 1,
'x2': size - 1,
'y2': (size * 2) / 3,
},
this.resizeGroup
);
} else {
this.resizeGroup = null;
}
if (!this.workspace_.options.readOnly) {
this.onMouseDownBubbleWrapper = browserEvents.conditionalBind(
this.bubbleBack, 'pointerdown', this, this.bubbleMouseDown);
this.bubbleBack,
'pointerdown',
this,
this.bubbleMouseDown
);
if (this.resizeGroup) {
this.onMouseDownResizeWrapper = browserEvents.conditionalBind(
this.resizeGroup, 'pointerdown', this, this.resizeMouseDown);
this.resizeGroup,
'pointerdown',
this,
this.resizeMouseDown
);
}
}
this.bubbleGroup.appendChild(content);
@@ -329,14 +352,25 @@ export class Bubble implements IBubble {
}
// Left-click (or middle click)
this.workspace_.startDrag(
e,
new Coordinate(
this.workspace_.RTL ? -this.width : this.width, this.height));
e,
new Coordinate(
this.workspace_.RTL ? -this.width : this.width,
this.height
)
);
Bubble.onMouseUpWrapper = browserEvents.conditionalBind(
document, 'pointerup', this, Bubble.bubbleMouseUp);
document,
'pointerup',
this,
Bubble.bubbleMouseUp
);
Bubble.onMouseMoveWrapper = browserEvents.conditionalBind(
document, 'pointermove', this, this.resizeMouseMove);
document,
'pointermove',
this,
this.resizeMouseMove
);
this.workspace_.hideChaff();
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
@@ -406,8 +440,9 @@ export class Bubble implements IBubble {
/** 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 viewMetrics = this.workspace_
.getMetricsManager()
.getViewMetrics(true);
const optimalLeft = this.getOptimalRelativeLeft(viewMetrics);
const optimalTop = this.getOptimalRelativeTop(viewMetrics);
@@ -415,30 +450,35 @@ export class Bubble implements IBubble {
const topPosition = {
x: optimalLeft,
y: -this.height -
this.workspace_.getRenderer().getConstants().MIN_BLOCK_HEIGHT as
number,
y: (-this.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};
const bottomPosition = {x: optimalLeft, y: bbox.height};
const closerPosition =
bbox.width < bbox.height ? endPosition : bottomPosition;
bbox.width < bbox.height ? endPosition : bottomPosition;
const fartherPosition =
bbox.width < bbox.height ? bottomPosition : endPosition;
bbox.width < bbox.height ? bottomPosition : endPosition;
const topPositionOverlap = this.getOverlap(topPosition, viewMetrics);
const startPositionOverlap = this.getOverlap(startPosition, viewMetrics);
const closerPositionOverlap = this.getOverlap(closerPosition, viewMetrics);
const fartherPositionOverlap =
this.getOverlap(fartherPosition, viewMetrics);
const fartherPositionOverlap = this.getOverlap(
fartherPosition,
viewMetrics
);
// Set the position to whichever position shows the most of the bubble,
// with tiebreaks going in the order: top > start > close > far.
const mostOverlap = Math.max(
topPositionOverlap, startPositionOverlap, closerPositionOverlap,
fartherPositionOverlap);
topPositionOverlap,
startPositionOverlap,
closerPositionOverlap,
fartherPositionOverlap
);
if (topPositionOverlap === mostOverlap) {
this.relativeLeft = topPosition.x;
this.relativeTop = topPosition.y;
@@ -472,12 +512,14 @@ export class Bubble implements IBubble {
* @returns The percentage of the bubble that is visible.
*/
private getOverlap(
relativeMin: {x: number, y: number},
viewMetrics: ContainerRegion): number {
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.
@@ -499,13 +541,16 @@ export class Bubble implements IBubble {
y: viewMetrics.top + viewMetrics.height,
};
const overlapWidth = Math.min(bubbleMax.x, workspaceMax.x) -
Math.max(bubbleMin.x, workspaceMin.x);
const overlapHeight = Math.min(bubbleMax.y, workspaceMax.y) -
Math.max(bubbleMin.y, workspaceMin.y);
const overlapWidth =
Math.min(bubbleMax.x, workspaceMax.x) -
Math.max(bubbleMin.x, workspaceMin.x);
const overlapHeight =
Math.min(bubbleMax.y, workspaceMax.y) -
Math.max(bubbleMin.y, workspaceMin.y);
return Math.max(
0,
Math.min(1, overlapWidth * overlapHeight / (this.width * this.height)));
0,
Math.min(1, (overlapWidth * overlapHeight) / (this.width * this.height))
);
}
/**
@@ -532,9 +577,10 @@ export class Bubble implements IBubble {
const bubbleLeft = bubbleRight - this.width;
const workspaceRight = viewMetrics.left + viewMetrics.width;
const workspaceLeft = viewMetrics.left +
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
const workspaceLeft =
viewMetrics.left +
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
if (bubbleLeft < workspaceLeft) {
// Slide the bubble right until it is onscreen.
@@ -548,9 +594,11 @@ export class Bubble implements IBubble {
const bubbleRight = bubbleLeft + this.width;
const workspaceLeft = viewMetrics.left;
const workspaceRight = viewMetrics.left + viewMetrics.width -
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
const workspaceRight =
viewMetrics.left +
viewMetrics.width -
// Thickness in workspace units.
Scrollbar.scrollbarThickness / this.workspace_.scale;
if (bubbleLeft < workspaceLeft) {
// Slide the bubble right until it is onscreen.
@@ -585,9 +633,10 @@ export class Bubble implements IBubble {
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) {
@@ -622,7 +671,9 @@ export class Bubble implements IBubble {
*/
moveTo(x: number, y: number) {
this.bubbleGroup?.setAttribute(
'transform', 'translate(' + x + ',' + y + ')');
'transform',
'translate(' + x + ',' + y + ')'
);
}
/**
@@ -666,14 +717,22 @@ export class Bubble implements IBubble {
// Mirror the resize group.
const resizeSize = 2 * Bubble.BORDER_WIDTH;
this.resizeGroup.setAttribute(
'transform',
'translate(' + resizeSize + ',' + (height - doubleBorderWidth) +
') scale(-1 1)');
'transform',
'translate(' +
resizeSize +
',' +
(height - doubleBorderWidth) +
') scale(-1 1)'
);
} else {
this.resizeGroup.setAttribute(
'transform',
'translate(' + (width - doubleBorderWidth) + ',' +
(height - doubleBorderWidth) + ')');
'transform',
'translate(' +
(width - doubleBorderWidth) +
',' +
(height - doubleBorderWidth) +
')'
);
}
}
if (this.autoLayout) {
@@ -724,7 +783,7 @@ export class Bubble implements IBubble {
// Calculate the thickness of the base of the arrow.
const bubbleSize = this.getBubbleSize();
let thickness =
(bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS;
(bubbleSize.width + bubbleSize.height) / Bubble.ARROW_THICKNESS;
thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 4;
// Back the tip of the arrow off of the anchor.
@@ -743,16 +802,38 @@ export class Bubble implements IBubble {
if (swirlAngle > Math.PI * 2) {
swirlAngle -= Math.PI * 2;
}
const swirlRise = Math.sin(swirlAngle) * hypotenuse / Bubble.ARROW_BEND;
const swirlRun = Math.cos(swirlAngle) * hypotenuse / Bubble.ARROW_BEND;
const swirlRise = (Math.sin(swirlAngle) * hypotenuse) / Bubble.ARROW_BEND;
const swirlRun = (Math.cos(swirlAngle) * hypotenuse) / Bubble.ARROW_BEND;
steps.push('M' + baseX1 + ',' + baseY1);
steps.push(
'C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) + ' ' +
relAnchorX + ',' + relAnchorY + ' ' + relAnchorX + ',' + relAnchorY);
'C' +
(baseX1 + swirlRun) +
',' +
(baseY1 + swirlRise) +
' ' +
relAnchorX +
',' +
relAnchorY +
' ' +
relAnchorX +
',' +
relAnchorY
);
steps.push(
'C' + relAnchorX + ',' + relAnchorY + ' ' + (baseX2 + swirlRun) +
',' + (baseY2 + swirlRise) + ' ' + baseX2 + ',' + baseY2);
'C' +
relAnchorX +
',' +
relAnchorY +
' ' +
(baseX2 + swirlRun) +
',' +
(baseY2 + swirlRise) +
' ' +
baseX2 +
',' +
baseY2
);
}
steps.push('z');
this.bubbleArrow?.setAttribute('d', steps.join(' '));
@@ -813,10 +894,11 @@ export class Bubble implements IBubble {
*/
getRelativeToSurfaceXY(): Coordinate {
return new Coordinate(
this.workspace_.RTL ?
-this.relativeLeft + this.anchorXY.x - this.width :
this.anchorXY.x + this.relativeLeft,
this.anchorXY.y + this.relativeTop);
this.workspace_.RTL
? -this.relativeLeft + this.anchorXY.x - this.width
: this.anchorXY.x + this.relativeLeft,
this.anchorXY.y + this.relativeTop
);
}
/**
@@ -868,7 +950,10 @@ export class Bubble implements IBubble {
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
const tspanElement = dom.createSvgElement(
Svg.TSPAN, {'dy': '1em', 'x': Bubble.BORDER_WIDTH}, paragraph);
Svg.TSPAN,
{'dy': '1em', 'x': Bubble.BORDER_WIDTH},
paragraph
);
const textNode = document.createTextNode(lines[i]);
tspanElement.appendChild(textNode);
}
@@ -885,20 +970,29 @@ export class Bubble implements IBubble {
* @internal
*/
static createNonEditableBubble(
paragraphElement: SVGTextElement, block: BlockSvg,
iconXY: Coordinate): Bubble {
paragraphElement: SVGTextElement,
block: BlockSvg,
iconXY: Coordinate
): Bubble {
const bubble = new Bubble(
block.workspace!, paragraphElement, block.pathObject.svgPath, (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) {
// Right-align the paragraph.
// 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] as SVGTSpanElement;
i++) {
for (
let i = 0, textElement;
(textElement = paragraphElement.childNodes[i] as SVGTSpanElement);
i++
) {
textElement.setAttribute('text-anchor', 'end');
textElement.setAttribute('x', String(maxWidth + Bubble.BORDER_WIDTH));
}

View File

@@ -23,7 +23,6 @@ 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
@@ -31,12 +30,12 @@ import type {WorkspaceSvg} from './workspace_svg.js';
*/
export class BubbleDragger {
/** Which drag target the mouse pointer is over, if any. */
private dragTarget_: IDragTarget|null = null;
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;
private dragSurface_: BlockDragSurfaceSvg | null;
/**
* @param bubble The item on the bubble canvas to drag.
@@ -116,11 +115,13 @@ export class BubbleDragger {
* @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 {
private shouldDelete_(dragTarget: IDragTarget | null): boolean {
if (dragTarget) {
const componentManager = this.workspace.getComponentManager();
const isDeleteArea = componentManager.hasCapability(
dragTarget.id, ComponentManager.Capability.DELETE_AREA);
dragTarget.id,
ComponentManager.Capability.DELETE_AREA
);
if (isDeleteArea) {
return (dragTarget as IDeleteArea).wouldDelete(this.bubble, false);
}
@@ -149,7 +150,7 @@ export class BubbleDragger {
this.dragBubble(e, currentDragDeltaXY);
const preventMove =
this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble);
this.dragTarget_ && this.dragTarget_.shouldPreventMove(this.bubble);
let newLoc;
if (preventMove) {
newLoc = this.startXY_;
@@ -187,7 +188,8 @@ export class BubbleDragger {
private fireMoveEvent_() {
if (this.bubble instanceof WorkspaceCommentSvg) {
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
this.bubble) as CommentMove;
this.bubble
) as CommentMove;
event.setOldCoordinate(this.startXY_);
event.recordNew();
eventUtils.fire(event);
@@ -207,8 +209,9 @@ export class BubbleDragger {
*/
private pixelsToWorkspaceUnits_(pixelCoord: Coordinate): Coordinate {
const result = new Coordinate(
pixelCoord.x / this.workspace.scale,
pixelCoord.y / this.workspace.scale);
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

View File

@@ -21,7 +21,6 @@ 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.
*
@@ -34,8 +33,10 @@ import type {WorkspaceSvg} from './workspace_svg.js';
* @returns True if object was bumped.
*/
function bumpObjectIntoBounds(
workspace: WorkspaceSvg, bounds: ContainerRegion,
object: IBoundedElement): boolean {
workspace: WorkspaceSvg,
bounds: ContainerRegion,
object: IBoundedElement
): boolean {
// Compute new top/left position for object.
const objectMetrics = object.getBoundingRectangle();
const height = objectMetrics.bottom - objectMetrics.top;
@@ -46,8 +47,11 @@ function bumpObjectIntoBounds(
const bottomClamp = boundsBottom - height;
// If the object is taller than the workspace we want to
// top-align the block
const newYPosition =
mathUtils.clamp(topClamp, objectMetrics.top, bottomClamp);
const newYPosition = mathUtils.clamp(
topClamp,
objectMetrics.top,
bottomClamp
);
const deltaY = newYPosition - objectMetrics.top;
// Note: Even in RTL mode the "anchor" of the object is the
@@ -66,8 +70,11 @@ function bumpObjectIntoBounds(
// the right clamp to match.
rightClamp = Math.max(leftClamp, rightClamp);
}
const newXPosition =
mathUtils.clamp(leftClamp, objectMetrics.left, rightClamp);
const newXPosition = mathUtils.clamp(
leftClamp,
objectMetrics.left,
rightClamp
);
const deltaX = newXPosition - objectMetrics.left;
if (deltaX || deltaY) {
@@ -84,8 +91,9 @@ export const bumpIntoBounds = bumpObjectIntoBounds;
* @param workspace The workspace to handle.
* @returns The event handler.
*/
export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
(p1: Abstract) => void {
export function bumpIntoBoundsHandler(
workspace: WorkspaceSvg
): (p1: Abstract) => void {
return (e) => {
const metricsManager = workspace.getMetricsManager();
if (!metricsManager.hasFixedEdges() || workspace.isDragging()) {
@@ -96,8 +104,10 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
// Triggered by move/create event
const object =
extractObjectFromEvent(workspace, e as eventUtils.BumpEvent);
const object = extractObjectFromEvent(
workspace,
e as eventUtils.BumpEvent
);
if (!object) {
return;
}
@@ -106,18 +116,25 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
eventUtils.setGroup(e.group);
const wasBumped = bumpObjectIntoBounds(
workspace, scrollMetricsInWsCoords, (object as IBoundedElement));
workspace,
scrollMetricsInWsCoords,
object as IBoundedElement
);
if (wasBumped && !e.group) {
console.warn(
'Moved object in bounds but there was no' +
' event group. This may break undo.');
'Moved object in bounds but there was no' +
' event group. This may break undo.'
);
}
eventUtils.setGroup(existingGroup);
} else if (e.type === eventUtils.VIEWPORT_CHANGE) {
const viewportEvent = (e as ViewportChange);
if (viewportEvent.scale && viewportEvent.oldScale &&
viewportEvent.scale > viewportEvent.oldScale) {
const viewportEvent = e as ViewportChange;
if (
viewportEvent.scale &&
viewportEvent.oldScale &&
viewportEvent.scale > viewportEvent.oldScale
) {
bumpTopObjectsIntoBounds(workspace);
}
}
@@ -134,8 +151,9 @@ export function bumpIntoBoundsHandler(workspace: WorkspaceSvg):
* object.
*/
function extractObjectFromEvent(
workspace: WorkspaceSvg, e: eventUtils.BumpEvent): BlockSvg|null|
WorkspaceCommentSvg {
workspace: WorkspaceSvg,
e: eventUtils.BumpEvent
): BlockSvg | null | WorkspaceCommentSvg {
let object = null;
switch (e.type) {
case eventUtils.BLOCK_CREATE:
@@ -147,10 +165,9 @@ function extractObjectFromEvent(
break;
case eventUtils.COMMENT_CREATE:
case eventUtils.COMMENT_MOVE:
object =
workspace.getCommentById((e as CommentCreate | CommentMove).commentId!
) as WorkspaceCommentSvg |
null;
object = workspace.getCommentById(
(e as CommentCreate | CommentMove).commentId!
) as WorkspaceCommentSvg | null;
break;
}
return object;
@@ -169,7 +186,7 @@ export function bumpTopObjectsIntoBounds(workspace: WorkspaceSvg) {
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);
}
}

View File

@@ -9,9 +9,8 @@ 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;
let copyData: CopyData | null = null;
/**
* Copy a block or workspace comment onto the local clipboard.
@@ -36,7 +35,7 @@ function copyInternal(toCopy: ICopyable) {
* @returns The pasted thing if the paste was successful, null otherwise.
* @internal
*/
export function paste(): ICopyable|null {
export function paste(): ICopyable | null {
if (!copyData) {
return null;
}
@@ -46,8 +45,10 @@ export function paste(): ICopyable|null {
if (workspace.isFlyout) {
workspace = workspace.targetWorkspace!;
}
if (copyData.typeCounts &&
workspace.isCapacityAvailable(copyData.typeCounts)) {
if (
copyData.typeCounts &&
workspace.isCapacityAvailable(copyData.typeCounts)
) {
return workspace.paste(copyData.saveInfo);
}
return null;
@@ -61,18 +62,18 @@ export function paste(): ICopyable|null {
* duplication failed.
* @internal
*/
export function duplicate(toDuplicate: ICopyable): ICopyable|null {
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 {
function duplicateInternal(toDuplicate: ICopyable): ICopyable | null {
const oldCopyData = copyData;
copy(toDuplicate);
const pastedThing =
toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null;
toDuplicate.toCopyData()?.source?.paste(copyData!.saveInfo) ?? null;
copyData = oldCopyData;
return pastedThing;
}

View File

@@ -29,7 +29,6 @@ import * as dom from './utils/dom.js';
import type {Size} from './utils/size.js';
import {Svg} from './utils/svg.js';
/**
* Class for a comment.
*/
@@ -40,7 +39,7 @@ export class Comment extends Icon {
* 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.
*/
private cachedText: string|null = '';
private cachedText: string | null = '';
/**
* Array holding info needed to unbind events.
@@ -52,13 +51,13 @@ export class Comment extends Icon {
/**
* The SVG element that contains the text edit area, or null if not created.
*/
private foreignObject: SVGForeignObjectElement|null = null;
private foreignObject: SVGForeignObjectElement | null = null;
/** The editable text area, or null if not created. */
private textarea_: HTMLTextAreaElement|null = null;
private textarea_: HTMLTextAreaElement | null = null;
/** The top-level node of the comment text, or null if not created. */
private paragraphElement_: SVGTextElement|null = null;
private paragraphElement_: SVGTextElement | null = null;
/** @param block The block associated with this comment. */
constructor(block: BlockSvg) {
@@ -81,28 +80,35 @@ export class Comment extends Icon {
protected override drawIcon_(group: Element) {
// Circle.
dom.createSvgElement(
Svg.CIRCLE,
{'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'}, group);
Svg.CIRCLE,
{'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
group
);
// Can't use a real '?' text character since different browsers and
// operating systems render it differently. Body of question mark.
dom.createSvgElement(
Svg.PATH, {
'class': 'blocklyIconSymbol',
'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
'0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
'-1.201,0.998 -1.201,1.528 -1.204,2.19z',
},
group);
Svg.PATH,
{
'class': 'blocklyIconSymbol',
'd':
'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
'0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
'-1.201,0.998 -1.201,1.528 -1.204,2.19z',
},
group
);
// Dot of question mark.
dom.createSvgElement(
Svg.RECT, {
'class': 'blocklyIconSymbol',
'x': '6.8',
'y': '10.78',
'height': '2',
'width': '2',
},
group);
Svg.RECT,
{
'class': 'blocklyIconSymbol',
'x': '6.8',
'y': '10.78',
'height': '2',
'width': '2',
},
group
);
}
/**
@@ -123,16 +129,19 @@ export class Comment extends Icon {
* For non-editable mode see Warning.textToDom_.
*/
this.foreignObject = dom.createSvgElement(
Svg.FOREIGNOBJECT,
{'x': Bubble.BORDER_WIDTH, 'y': Bubble.BORDER_WIDTH});
this.foreignObject = dom.createSvgElement(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') as
HTMLTextAreaElement;
this.textarea_ = document.createElementNS(
dom.HTML_NS,
'textarea'
) as HTMLTextAreaElement;
const textarea = this.textarea_;
textarea.className = 'blocklyCommentTextarea';
textarea.setAttribute('dir', this.getBlock().RTL ? 'RTL' : 'LTR');
@@ -142,33 +151,62 @@ export class Comment extends Icon {
body.appendChild(textarea);
this.foreignObject!.appendChild(body);
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'focus', this, this.startEdit, true));
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'focus',
this,
this.startEdit,
true
)
);
// Don't zoom with mousewheel.
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'wheel', this, function(e: Event) {
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'wheel',
this,
function (e: Event) {
e.stopPropagation();
}));
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'change', this,
}
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'change',
this,
/**
* @param _e Unused event parameter.
*/
function(this: Comment, _e: Event) {
function (this: Comment, _e: Event) {
if (this.cachedText !== this.model.text) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.getBlock(), 'comment', null, this.cachedText,
this.model.text));
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.getBlock(),
'comment',
null,
this.cachedText,
this.model.text
)
);
}
}));
this.boundEvents.push(browserEvents.conditionalBind(
textarea, 'input', this,
}
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
textarea,
'input',
this,
/**
* @param _e Unused event parameter.
*/
function(this: Comment, _e: Event) {
function (this: Comment, _e: Event) {
this.model.text = textarea.value;
}));
}
)
);
setTimeout(textarea.focus.bind(textarea), 0);
@@ -225,8 +263,13 @@ export class Comment extends Icon {
if (visible === this.isVisible()) {
return;
}
eventUtils.fire(new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
this.getBlock(), visible, 'comment'));
eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
this.getBlock(),
visible,
'comment'
)
);
this.model.pinned = visible;
if (visible) {
this.createBubble();
@@ -248,9 +291,13 @@ export class Comment extends Icon {
private createEditableBubble() {
const block = this.getBlock();
this.bubble_ = new Bubble(
block.workspace, this.createEditor(), block.pathObject.svgPath,
(this.iconXY_ as Coordinate), this.model.size.width,
this.model.size.height);
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(block.id);
this.bubble_.registerResizeEvent(this.onBubbleResize.bind(this));
@@ -264,7 +311,10 @@ export class Comment extends Icon {
// TODO (#2917): It would be great if the comment could support line breaks.
this.paragraphElement_ = Bubble.textToDom(this.model.text ?? '');
this.bubble_ = Bubble.createNonEditableBubble(
this.paragraphElement_, this.getBlock(), this.iconXY_ as Coordinate);
this.paragraphElement_,
this.getBlock(),
this.iconXY_ as Coordinate
);
this.applyColour();
}

View File

@@ -15,18 +15,16 @@ 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 {
export function getWorkspaceById(id: string): Workspace | null {
return WorkspaceDB_[id] || null;
}
@@ -90,12 +88,12 @@ export function setMainWorkspace(workspace: Workspace) {
/**
* Currently selected copyable object.
*/
let selected: ICopyable|null = null;
let selected: ICopyable | null = null;
/**
* Returns the currently selected copyable object.
*/
export function getSelected(): ICopyable|null {
export function getSelected(): ICopyable | null {
return selected;
}
@@ -107,14 +105,14 @@ export function getSelected(): ICopyable|null {
* @param newSelection The newly selected block.
* @internal
*/
export function setSelected(newSelection: ICopyable|null) {
export function setSelected(newSelection: ICopyable | null) {
selected = newSelection;
}
/**
* Container element in which to render the WidgetDiv, DropDownDiv and Tooltip.
*/
let parentContainer: Element|null;
let parentContainer: Element | null;
/**
* Get the container element in which to render the WidgetDiv, DropDownDiv and
@@ -122,7 +120,7 @@ let parentContainer: Element|null;
*
* @returns The parent container.
*/
export function getParentContainer(): Element|null {
export function getParentContainer(): Element | null {
return parentContainer;
}
@@ -189,7 +187,9 @@ export const draggingConnections: Connection[] = [];
* @returns Map of types to type counts for descendants of the bock.
*/
export function getBlockTypeCounts(
block: Block, opt_stripFollowing?: boolean): {[key: string]: number} {
block: Block,
opt_stripFollowing?: boolean
): {[key: string]: number} {
const typeCountsMap = Object.create(null);
const descendants = block.getDescendants(true);
if (opt_stripFollowing) {
@@ -199,7 +199,7 @@ export function getBlockTypeCounts(
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 {
@@ -218,7 +218,7 @@ export function getBlockTypeCounts(
* of jsonDef.
*/
function jsonInitFactory(jsonDef: AnyDuringMigration): () => void {
return function(this: Block) {
return function (this: Block) {
this.jsonInit(jsonDef);
};
}
@@ -249,7 +249,8 @@ function defineBlocksWithJsonArrayInternal(jsonArray: AnyDuringMigration[]) {
* definitions created.
*/
export function createBlockDefinitionsFromJsonArray(
jsonArray: AnyDuringMigration[]): {[key: string]: BlockDefinition} {
jsonArray: AnyDuringMigration[]
): {[key: string]: BlockDefinition} {
const blocks: {[key: string]: BlockDefinition} = {};
for (let i = 0; i < jsonArray.length; i++) {
const elem = jsonArray[i];
@@ -260,8 +261,9 @@ export function createBlockDefinitionsFromJsonArray(
const type = elem['type'];
if (!type) {
console.warn(
`Block definition #${i} in JSON array is missing a type attribute. ` +
'Skipping.');
`Block definition #${i} in JSON array is missing a type attribute. ` +
'Skipping.'
);
continue;
}
blocks[type] = {init: jsonInitFactory(elem)};

View File

@@ -19,7 +19,6 @@ 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');
@@ -67,8 +66,12 @@ export class ComponentManager {
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.');
'Plugin "' +
id +
'" with capabilities "' +
this.componentData.get(id)?.capabilities +
'" already added.'
);
}
this.componentData.set(id, componentInfo);
const stringCapabilities = [];
@@ -107,15 +110,20 @@ export class ComponentManager {
* @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>) {
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');
'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 + '"');
'Plugin "' + id + 'already has capability "' + capability + '"'
);
return;
}
capability = `${capability}`.toLowerCase();
@@ -129,16 +137,24 @@ export class ComponentManager {
* @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>) {
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');
'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');
'Plugin "' +
id +
'doesn\'t have capability "' +
capability +
'" to remove'
);
return;
}
capability = `${capability}`.toLowerCase();
@@ -153,10 +169,12 @@ export class ComponentManager {
* @param capability The capability to check for.
* @returns Whether the component has the capability.
*/
hasCapability<T>(id: string, capability: string|Capability<T>): boolean {
hasCapability<T>(id: string, capability: string | Capability<T>): boolean {
capability = `${capability}`.toLowerCase();
return this.componentData.has(id) &&
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1;
return (
this.componentData.has(id) &&
this.componentData.get(id)!.capabilities.indexOf(capability) !== -1
);
}
/**
@@ -165,7 +183,7 @@ export class ComponentManager {
* @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 {
getComponent(id: string): IComponent | undefined {
return this.componentData.get(id)?.component;
}
@@ -177,7 +195,9 @@ export class ComponentManager {
* @returns The components that match the specified capability.
*/
getComponents<T extends IComponent>(
capability: string|Capability<T>, sorted: boolean): T[] {
capability: string | Capability<T>,
sorted: boolean
): T[] {
capability = `${capability}`.toLowerCase();
const componentIds = this.capabilityToComponentIds.get(capability);
if (!componentIds) {
@@ -189,10 +209,10 @@ export class ComponentManager {
componentIds.forEach((id) => {
componentDataList.push(this.componentData.get(id)!);
});
componentDataList.sort(function(a, b) {
componentDataList.sort(function (a, b) {
return a.weight - b.weight;
});
componentDataList.forEach(function(componentDatum) {
componentDataList.forEach(function (componentDatum) {
components.push(componentDatum.component as T);
});
} else {
@@ -208,7 +228,7 @@ export namespace ComponentManager {
/** An object storing component information. */
export interface ComponentDatum {
component: IComponent;
capabilities: Array<string|Capability<IComponent>>;
capabilities: Array<string | Capability<IComponent>>;
weight: number;
}
}

View File

@@ -7,7 +7,6 @@
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.

View File

@@ -22,7 +22,6 @@ 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.
*/
@@ -41,7 +40,7 @@ export class Connection implements IASTNodeLocationWithBlock {
protected sourceBlock_: Block;
/** Connection this connection connects to. Null if not connected. */
targetConnection: Connection|null = null;
targetConnection: Connection | null = null;
/**
* Has this connection been disposed of?
@@ -51,10 +50,10 @@ export class Connection implements IASTNodeLocationWithBlock {
disposed = false;
/** List of compatible value types. Null if all types are compatible. */
private check: string[]|null = null;
private check: string[] | null = null;
/** DOM representation of a shadow block, or null if none. */
private shadowDom: Element|null = null;
private shadowDom: Element | null = null;
/**
* Horizontal location of this connection.
@@ -70,7 +69,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*/
y = 0;
private shadowState: blocks.State|null = null;
private shadowState: blocks.State | null = null;
/**
* @param source The block establishing this connection.
@@ -113,8 +112,9 @@ export class Connection implements IASTNodeLocationWithBlock {
// Connect the new connection to the parent.
let event;
if (eventUtils.isEnabled()) {
event =
new (eventUtils.get(eventUtils.BLOCK_MOVE))(childBlock) as BlockMove;
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
childBlock
) as BlockMove;
event.setReason(['connect']);
}
connectReciprocally(this, childConnection);
@@ -126,11 +126,15 @@ export class Connection implements IASTNodeLocationWithBlock {
// Deal with the orphan if it exists.
if (orphan) {
const orphanConnection = this.type === INPUT ? orphan.outputConnection :
orphan.previousConnection;
const orphanConnection =
this.type === INPUT
? orphan.outputConnection
: orphan.previousConnection;
if (!orphanConnection) return;
const connection = Connection.getConnectionForOrphanedConnection(
childBlock, orphanConnection);
childBlock,
orphanConnection
);
if (connection) {
orphanConnection.connect(connection);
} else {
@@ -176,8 +180,10 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns True if connection faces down or right.
*/
isSuperior(): boolean {
return this.type === ConnectionType.INPUT_VALUE ||
this.type === ConnectionType.NEXT_STATEMENT;
return (
this.type === ConnectionType.INPUT_VALUE ||
this.type === ConnectionType.NEXT_STATEMENT
);
}
/**
@@ -259,7 +265,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*/
protected disconnectInternal(setParent = true) {
const {parentConnection, childConnection} =
this.getParentAndChildConnections();
this.getParentAndChildConnections();
if (!parentConnection || !childConnection) {
throw Error('Source connection not connected.');
}
@@ -272,7 +278,8 @@ export class Connection implements IASTNodeLocationWithBlock {
let event;
if (eventUtils.isEnabled()) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
childConnection.getSourceBlock()) as BlockMove;
childConnection.getSourceBlock()
) as BlockMove;
event.setReason(['disconnect']);
}
const otherConnection = this.targetConnection;
@@ -301,8 +308,10 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns The parent connection and child connection, given this connection
* and the connection it is connected to.
*/
protected getParentAndChildConnections():
{parentConnection?: Connection, childConnection?: Connection} {
protected getParentAndChildConnections(): {
parentConnection?: Connection;
childConnection?: Connection;
} {
if (!this.targetConnection) return {};
if (this.isSuperior()) {
return {
@@ -329,7 +338,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @returns The connected block or null if none is connected.
*/
targetBlock(): Block|null {
targetBlock(): Block | null {
if (this.isConnected()) {
return this.targetConnection?.getSourceBlock() ?? null;
}
@@ -341,10 +350,15 @@ export class Connection implements IASTNodeLocationWithBlock {
*/
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))) {
if (
this.isConnected() &&
(!this.targetConnection ||
!this.getConnectionChecker().canConnect(
this,
this.targetConnection,
false
))
) {
const child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child!.unplug();
}
@@ -357,7 +371,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* types are compatible.
* @returns The connection being modified (to allow chaining).
*/
setCheck(check: string|string[]|null): Connection {
setCheck(check: string | string[] | null): Connection {
if (check) {
if (!Array.isArray(check)) {
check = [check];
@@ -376,7 +390,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns List of compatible value types.
* Null if all types are compatible.
*/
getCheck(): string[]|null {
getCheck(): string[] | null {
return this.check;
}
@@ -385,7 +399,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param shadowDom DOM representation of a block or null.
*/
setShadowDom(shadowDom: Element|null) {
setShadowDom(shadowDom: Element | null) {
this.setShadowStateInternal({shadowDom});
}
@@ -398,10 +412,10 @@ export class Connection implements IASTNodeLocationWithBlock {
* just returned.
* @returns Shadow DOM representation of a block or null.
*/
getShadowDom(returnCurrent?: boolean): Element|null {
return returnCurrent && this.targetBlock()!.isShadow() ?
Xml.blockToDom((this.targetBlock() as Block)) as Element :
this.shadowDom;
getShadowDom(returnCurrent?: boolean): Element | null {
return returnCurrent && this.targetBlock()!.isShadow()
? (Xml.blockToDom(this.targetBlock() as Block) as Element)
: this.shadowDom;
}
/**
@@ -409,7 +423,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param shadowState An state represetation of the block or null.
*/
setShadowState(shadowState: blocks.State|null) {
setShadowState(shadowState: blocks.State | null) {
this.setShadowStateInternal({shadowState});
}
@@ -423,7 +437,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* returned.
* @returns Serialized object representation of the block, or null.
*/
getShadowState(returnCurrent?: boolean): blocks.State|null {
getShadowState(returnCurrent?: boolean): blocks.State | null {
if (returnCurrent && this.targetBlock() && this.targetBlock()!.isShadow()) {
return blocks.save(this.targetBlock() as Block);
}
@@ -454,7 +468,7 @@ export class Connection implements IASTNodeLocationWithBlock {
* exists.
* @internal
*/
getParentInput(): Input|null {
getParentInput(): Input | null {
let parentInput = null;
const inputs = this.sourceBlock_.inputList;
for (let i = 0; i < inputs.length; i++) {
@@ -486,7 +500,7 @@ export class Connection implements IASTNodeLocationWithBlock {
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;
@@ -508,8 +522,10 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @returns The state of both the shadowDom_ and shadowState_ properties.
*/
private stashShadowState():
{shadowDom: Element|null, shadowState: blocks.State|null} {
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.
@@ -524,9 +540,12 @@ export class Connection implements IASTNodeLocationWithBlock {
* @param param0 The state to reapply to the shadowDom_ and shadowState_
* properties.
*/
private applyShadowState({shadowDom, shadowState}: {
shadowDom: Element|null,
shadowState: blocks.State|null
private applyShadowState({
shadowDom,
shadowState,
}: {
shadowDom: Element | null;
shadowState: blocks.State | null;
}) {
this.shadowDom = shadowDom;
this.shadowState = shadowState;
@@ -537,9 +556,12 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param param0 The state to set the shadow of this connection to.
*/
private setShadowStateInternal({shadowDom = null, shadowState = null}: {
shadowDom?: Element|null,
shadowState?: blocks.State|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.
@@ -577,11 +599,11 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns The shadow block that was created, or null if both the
* shadowState_ and shadowDom_ are null.
*/
private createShadowBlock(attemptToConnect: boolean): Block|null {
private createShadowBlock(attemptToConnect: boolean): Block | null {
const parentBlock = this.getSourceBlock();
const shadowState = this.getShadowState();
const shadowDom = this.getShadowDom();
if (parentBlock.isDeadOrDying() || !shadowState && !shadowDom) {
if (parentBlock.isDeadOrDying() || (!shadowState && !shadowDom)) {
return null;
}
@@ -614,7 +636,8 @@ export class Connection implements IASTNodeLocationWithBlock {
}
} else {
throw new Error(
'Cannot connect a shadow block to a previous/output connection');
'Cannot connect a shadow block to a previous/output connection'
);
}
}
return blockShadow;
@@ -628,7 +651,7 @@ export class Connection implements IASTNodeLocationWithBlock {
*
* @param shadow The shadow to serialize, or null.
*/
private serializeShadow(shadow: Block|null) {
private serializeShadow(shadow: Block | null) {
if (!shadow) {
return;
}
@@ -646,10 +669,14 @@ export class Connection implements IASTNodeLocationWithBlock {
* @returns The suitable connection point on the chain of blocks, or null.
*/
static getConnectionForOrphanedConnection(
startBlock: Block, orphanConnection: Connection): Connection|null {
startBlock: Block,
orphanConnection: Connection
): Connection | null {
if (orphanConnection.type === ConnectionType.OUTPUT_VALUE) {
return getConnectionForOrphanedOutput(
startBlock, orphanConnection.getSourceBlock());
startBlock,
orphanConnection.getSourceBlock()
);
}
// Otherwise we're dealing with a stack.
const connection = startBlock.lastConnectionInStack(true);
@@ -684,17 +711,19 @@ function connectReciprocally(first: Connection, second: Connection) {
* @param orphanBlock The inferior block.
* @returns The suitable connection point on 'block', or null.
*/
function getSingleConnection(block: Block, orphanBlock: Block): Connection|
null {
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) {
return null; // More than one connection.
return null; // More than one connection.
}
foundConnection = connection;
}
@@ -714,10 +743,12 @@ function getSingleConnection(block: Block, orphanBlock: Block): Connection|
* @returns The suitable connection point on the chain of blocks, or null.
*/
function getConnectionForOrphanedOutput(
startBlock: Block, orphanBlock: Block): Connection|null {
let newBlock: Block|null = startBlock;
startBlock: Block,
orphanBlock: Block
): Connection | null {
let newBlock: Block | null = startBlock;
let connection;
while (connection = getSingleConnection(newBlock, orphanBlock)) {
while ((connection = getSingleConnection(newBlock, orphanBlock))) {
newBlock = connection.targetBlock();
if (!newBlock || newBlock.isShadow()) {
return connection;

View File

@@ -21,7 +21,6 @@ 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.
*/
@@ -38,10 +37,15 @@ export class ConnectionChecker implements IConnectionChecker {
* @returns Whether the connection is legal.
*/
canConnect(
a: Connection|null, b: Connection|null, isDragging: boolean,
opt_distance?: number): boolean {
return this.canConnectWithReason(a, b, isDragging, opt_distance) ===
Connection.CAN_CONNECT;
a: Connection | null,
b: Connection | null,
isDragging: boolean,
opt_distance?: number
): boolean {
return (
this.canConnectWithReason(a, b, isDragging, opt_distance) ===
Connection.CAN_CONNECT
);
}
/**
@@ -57,8 +61,11 @@ export class ConnectionChecker implements IConnectionChecker {
* otherwise.
*/
canConnectWithReason(
a: Connection|null, b: Connection|null, isDragging: boolean,
opt_distance?: number): number {
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;
@@ -71,10 +78,14 @@ export class ConnectionChecker implements IConnectionChecker {
return Connection.REASON_CHECKS_FAILED;
}
if (isDragging &&
!this.doDragChecks(
a as RenderedConnection, b as RenderedConnection,
opt_distance || 0)) {
if (
isDragging &&
!this.doDragChecks(
a as RenderedConnection,
b as RenderedConnection,
opt_distance || 0
)
) {
return Connection.REASON_DRAG_CHECKS_FAILED;
}
@@ -89,8 +100,11 @@ export class ConnectionChecker implements IConnectionChecker {
* @param b The second of the two connections being checked.
* @returns A developer-readable error string.
*/
getErrorMessage(errorCode: number, a: Connection|null, b: Connection|null):
string {
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.';
@@ -105,8 +119,12 @@ export class ConnectionChecker implements IConnectionChecker {
const connOne = a!;
const connTwo = b!;
let msg = 'Connection checks failed. ';
msg += connOne + ' expected ' + connOne.getCheck() + ', found ' +
connTwo.getCheck();
msg +=
connOne +
' expected ' +
connOne.getCheck() +
', found ' +
connTwo.getCheck();
return msg;
}
case Connection.REASON_SHADOW_PARENT:
@@ -128,7 +146,7 @@ export class ConnectionChecker implements IConnectionChecker {
* @param b The second of the connections to check.
* @returns An enum with the reason this connection is safe or unsafe.
*/
doSafetyChecks(a: Connection|null, b: Connection|null): number {
doSafetyChecks(a: Connection | null, b: Connection | null): number {
if (!a || !b) {
return Connection.REASON_TARGET_NULL;
}
@@ -150,22 +168,25 @@ export class ConnectionChecker implements IConnectionChecker {
if (superiorBlock === inferiorBlock) {
return Connection.REASON_SELF_CONNECTION;
} else if (
inferiorConnection.type !==
internalConstants.OPPOSITE_TYPE[superiorConnection.type]) {
inferiorConnection.type !==
internalConstants.OPPOSITE_TYPE[superiorConnection.type]
) {
return Connection.REASON_WRONG_TYPE;
} else if (superiorBlock.workspace !== inferiorBlock.workspace) {
return Connection.REASON_DIFFERENT_WORKSPACES;
} else if (superiorBlock.isShadow() && !inferiorBlock.isShadow()) {
return Connection.REASON_SHADOW_PARENT;
} else if (
inferiorConnection.type === ConnectionType.OUTPUT_VALUE &&
inferiorBlock.previousConnection &&
inferiorBlock.previousConnection.isConnected()) {
inferiorConnection.type === ConnectionType.OUTPUT_VALUE &&
inferiorBlock.previousConnection &&
inferiorBlock.previousConnection.isConnected()
) {
return Connection.REASON_PREVIOUS_AND_OUTPUT;
} else if (
inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT &&
inferiorBlock.outputConnection &&
inferiorBlock.outputConnection.isConnected()) {
inferiorConnection.type === ConnectionType.PREVIOUS_STATEMENT &&
inferiorBlock.outputConnection &&
inferiorBlock.outputConnection.isConnected()
) {
return Connection.REASON_PREVIOUS_AND_OUTPUT;
}
return Connection.CAN_CONNECT;
@@ -206,8 +227,11 @@ export class ConnectionChecker implements IConnectionChecker {
* @param distance The maximum allowable distance between connections.
* @returns True if the connection is allowed during a drag.
*/
doDragChecks(a: RenderedConnection, b: RenderedConnection, distance: number):
boolean {
doDragChecks(
a: RenderedConnection,
b: RenderedConnection,
distance: number
): boolean {
if (a.distanceFrom(b) > distance) {
return false;
}
@@ -223,8 +247,10 @@ export class ConnectionChecker implements IConnectionChecker {
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() ||
a.isConnected()) {
if (
(b.isConnected() && !b.targetBlock()!.isInsertionMarker()) ||
a.isConnected()
) {
return false;
}
break;
@@ -233,8 +259,11 @@ export class ConnectionChecker implements IConnectionChecker {
// 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;
@@ -244,15 +273,22 @@ export class ConnectionChecker implements IConnectionChecker {
// the stack. But covering up a shadow block or stack of shadow blocks
// is fine. Similarly, replacing a terminal statement with another
// terminal statement is allowed.
if (b.isConnected() && !a.getSourceBlock().nextConnection &&
!b.targetBlock()!.isShadow() && b.targetBlock()!.nextConnection) {
if (
b.isConnected() &&
!a.getSourceBlock().nextConnection &&
!b.targetBlock()!.isShadow() &&
b.targetBlock()!.nextConnection
) {
return false;
}
// Don't offer to splice into a stack where the connected block is
// immovable, unless the block is a shadow block.
if (b.targetBlock() && !b.targetBlock()!.isMovable() &&
!b.targetBlock()!.isShadow()) {
if (
b.targetBlock() &&
!b.targetBlock()!.isMovable() &&
!b.targetBlock()!.isShadow()
) {
return false;
}
break;
@@ -307,4 +343,7 @@ export class ConnectionChecker implements IConnectionChecker {
}
registry.register(
registry.Type.CONNECTION_CHECKER, registry.DEFAULT, ConnectionChecker);
registry.Type.CONNECTION_CHECKER,
registry.DEFAULT,
ConnectionChecker
);

View File

@@ -19,7 +19,6 @@ 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
@@ -58,8 +57,10 @@ export class ConnectionDB {
* @returns The index of the connection, or -1 if the connection was not
* found.
*/
private findIndexOfConnection(conn: RenderedConnection, yPos: number):
number {
private findIndexOfConnection(
conn: RenderedConnection,
yPos: number
): number {
if (!this.connections.length) {
return -1;
}
@@ -81,8 +82,10 @@ export class ConnectionDB {
}
pointer = bestGuess;
while (pointer < this.connections.length &&
this.connections[pointer].y === yPos) {
while (
pointer < this.connections.length &&
this.connections[pointer].y === yPos
) {
if (this.connections[pointer] === conn) {
return pointer;
}
@@ -140,8 +143,10 @@ export class ConnectionDB {
* @param maxRadius The maximum radius to another connection.
* @returns List of connections.
*/
getNeighbours(connection: RenderedConnection, maxRadius: number):
RenderedConnection[] {
getNeighbours(
connection: RenderedConnection,
maxRadius: number
): RenderedConnection[] {
const db = this.connections;
const currentX = connection.x;
const currentY = connection.y;
@@ -218,8 +223,10 @@ export class ConnectionDB {
* connection or null, and 'radius' which is the distance.
*/
searchForClosest(
conn: RenderedConnection, maxRadius: number,
dxy: Coordinate): {connection: RenderedConnection|null, radius: number} {
conn: RenderedConnection,
maxRadius: number,
dxy: Coordinate
): {connection: RenderedConnection | null; radius: number} {
if (!this.connections.length) {
// Don't bother.
return {connection: null, radius: maxRadius};
@@ -253,8 +260,10 @@ export class ConnectionDB {
}
let pointerMax = closestIndex;
while (pointerMax < this.connections.length &&
this.isInYRange(pointerMax, conn.y, maxRadius)) {
while (
pointerMax < this.connections.length &&
this.isInYRange(pointerMax, conn.y, maxRadius)
) {
temp = this.connections[pointerMax];
if (this.connectionChecker.canConnect(conn, temp, true, bestRadius)) {
bestConnection = temp;

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.ConnectionType');
/**
* Enum for the type of a connection or input.
*/
@@ -19,5 +18,5 @@ export enum ConnectionType {
// A down-facing block stack. E.g. 'if-do' or 'else'.
NEXT_STATEMENT,
// An up-facing block stack. E.g. 'break out of loop'.
PREVIOUS_STATEMENT
PREVIOUS_STATEMENT,
}

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.constants');
/**
* The language-neutral ID given to the collapsed input.
*/

View File

@@ -13,7 +13,10 @@ 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 type {
ContextMenuOption,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
import * as eventUtils from './events/utils.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
@@ -27,11 +30,10 @@ 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?
*/
let currentBlock: Block|null = null;
let currentBlock: Block | null = null;
const dummyOwner = {};
@@ -40,7 +42,7 @@ const dummyOwner = {};
*
* @returns The block the context menu is attached to.
*/
export function getCurrentBlock(): Block|null {
export function getCurrentBlock(): Block | null {
return currentBlock;
}
@@ -49,14 +51,14 @@ export function getCurrentBlock(): Block|null {
*
* @param block The block the context menu is attached to.
*/
export function setCurrentBlock(block: Block|null) {
export function setCurrentBlock(block: Block | null) {
currentBlock = block;
}
/**
* Menu object.
*/
let menu_: Menu|null = null;
let menu_: Menu | null = null;
/**
* Construct the menu based on the list of options and show the menu.
@@ -66,8 +68,10 @@ let menu_: Menu|null = null;
* @param rtl True if RTL, false if LTR.
*/
export function show(
e: Event, options: (ContextMenuOption|LegacyContextMenuOption)[],
rtl: boolean) {
e: Event,
options: (ContextMenuOption | LegacyContextMenuOption)[],
rtl: boolean
) {
WidgetDiv.show(dummyOwner, rtl, dispose);
if (!options.length) {
hide();
@@ -79,10 +83,10 @@ export function show(
position_(menu, e, rtl);
// 1ms delay is required for focusing on context menus because some other
// mouse event is still waiting in the queue and clears focus.
setTimeout(function() {
setTimeout(function () {
menu.focus();
}, 1);
currentBlock = null; // May be set by Blockly.Block.
currentBlock = null; // May be set by Blockly.Block.
}
/**
@@ -93,8 +97,9 @@ export function show(
* @returns The menu that will be shown on right click.
*/
function populate_(
options: (ContextMenuOption|LegacyContextMenuOption)[],
rtl: boolean): Menu {
options: (ContextMenuOption | LegacyContextMenuOption)[],
rtl: boolean
): Menu {
/* Here's what one option object looks like:
{text: 'Make It So',
enabled: true,
@@ -110,7 +115,7 @@ function populate_(
menu.addChild(menuItem);
menuItem.setEnabled(option.enabled);
if (option.enabled) {
const actionHandler = function() {
const actionHandler = function () {
hide();
requestAnimationFrame(() => {
setTimeout(() => {
@@ -143,10 +148,11 @@ function position_(menu: Menu, e: Event, rtl: boolean) {
// 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(
mouseEvent.clientY + viewportBBox.top,
mouseEvent.clientY + viewportBBox.top,
mouseEvent.clientX + viewportBBox.left,
mouseEvent.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();
@@ -179,7 +185,11 @@ function createWidget_(menu: Menu) {
dom.addClass(menuDom, 'blocklyContextMenu');
// Prevent system context menu when right-clicking a Blockly context menu.
browserEvents.conditionalBind(
(menuDom as EventTarget), 'contextmenu', null, haltPropagation);
menuDom as EventTarget,
'contextmenu',
null,
haltPropagation
);
// Focus only after the initial render to avoid issue #1329.
menu.focus();
}
@@ -256,12 +266,13 @@ export function callbackFactory(block: Block, xml: Element): () => void {
* containing text, enabled, and a callback.
* @internal
*/
export function commentDeleteOption(comment: WorkspaceCommentSvg):
LegacyContextMenuOption {
export function commentDeleteOption(
comment: WorkspaceCommentSvg
): LegacyContextMenuOption {
const deleteOption = {
text: Msg['REMOVE_COMMENT'],
enabled: true,
callback: function() {
callback: function () {
eventUtils.setGroup(true);
comment.dispose();
eventUtils.setGroup(false);
@@ -279,12 +290,13 @@ export function commentDeleteOption(comment: WorkspaceCommentSvg):
* containing text, enabled, and a callback.
* @internal
*/
export function commentDuplicateOption(comment: WorkspaceCommentSvg):
LegacyContextMenuOption {
export function commentDuplicateOption(
comment: WorkspaceCommentSvg
): LegacyContextMenuOption {
const duplicateOption = {
text: Msg['DUPLICATE_COMMENT'],
enabled: true,
callback: function() {
callback: function () {
clipboard.duplicate(comment);
},
};
@@ -303,15 +315,20 @@ export function commentDuplicateOption(comment: WorkspaceCommentSvg):
* @internal
*/
export function workspaceCommentOption(
ws: WorkspaceSvg, e: Event): ContextMenuOption {
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);
ws,
Msg['WORKSPACE_COMMENT_DEFAULT_TEXT'],
WorkspaceCommentSvg.DEFAULT_SIZE,
WorkspaceCommentSvg.DEFAULT_SIZE
);
const injectionDiv = ws.getInjectionDiv();
// Bounding rect coordinates are in client coordinates, meaning that they
@@ -322,8 +339,9 @@ export function workspaceCommentOption(
// The client coordinates offset by the injection div's upper left corner.
const mouseEvent = e as MouseEvent;
const clientOffsetPixels = new Coordinate(
mouseEvent.clientX - boundingRect.left,
mouseEvent.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.
@@ -331,8 +349,10 @@ export function workspaceCommentOption(
// The position of the new comment in pixels relative to the origin of the
// main workspace.
const finalOffset =
Coordinate.difference(clientOffsetPixels, mainOffsetPixels);
const finalOffset = Coordinate.difference(
clientOffsetPixels,
mainOffsetPixels
);
// The position of the new comment in main workspace coordinates.
finalOffset.scale(1 / ws.scale);
@@ -350,7 +370,7 @@ export function workspaceCommentOption(
enabled: true,
} as ContextMenuOption;
wsCommentOption.text = Msg['ADD_COMMENT'];
wsCommentOption.callback = function() {
wsCommentOption.callback = function () {
addWsComment();
};
return wsCommentOption;

View File

@@ -9,7 +9,11 @@ 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 {
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';
@@ -17,7 +21,6 @@ import {Msg} from './msg.js';
import {StatementInput} from './renderers/zelos/zelos.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Option to undo previous action.
*/
@@ -111,7 +114,7 @@ function toggleOption_(shouldCollapse: boolean, topBlocks: BlockSvg[]) {
}
Events.setGroup(true);
for (let i = 0; i < topBlocks.length; i++) {
let block: BlockSvg|null = topBlocks[i];
let block: BlockSvg | null = topBlocks[i];
while (block) {
timeoutCounter++;
setTimeout(timeoutFn.bind(null, block), ms);
@@ -133,7 +136,7 @@ export function registerCollapse() {
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];
let block: BlockSvg | null = topBlocks[i];
while (block) {
if (!block.isCollapsed()) {
return 'enabled';
@@ -167,7 +170,7 @@ export function registerExpand() {
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];
let block: BlockSvg | null = topBlocks[i];
while (block) {
if (block.isCollapsed()) {
return 'enabled';
@@ -280,13 +283,16 @@ export function registerDeleteAll() {
deleteNext_(deletableBlocks);
} else {
dialog.confirm(
Msg['DELETE_ALL_BLOCKS'].replace(
'%1', String(deletableBlocks.length)),
function(ok) {
if (ok) {
deleteNext_(deletableBlocks);
}
});
Msg['DELETE_ALL_BLOCKS'].replace(
'%1',
String(deletableBlocks.length)
),
function (ok) {
if (ok) {
deleteNext_(deletableBlocks);
}
}
);
}
},
scopeType: ContextMenuRegistry.ScopeType.WORKSPACE,
@@ -350,8 +356,12 @@ export function registerComment() {
},
preconditionFn(scope: Scope) {
const block = scope.block;
if (!block!.isInFlyout && block!.workspace.options.comments &&
!block!.isCollapsed() && block!.isEditable()) {
if (
!block!.isInFlyout &&
block!.workspace.options.comments &&
!block!.isCollapsed() &&
block!.isEditable()
) {
return 'enabled';
}
return 'hidden';
@@ -377,8 +387,9 @@ export function registerComment() {
export function registerInline() {
const inlineOption: RegistryItem = {
displayText(scope: Scope) {
return scope.block!.getInputsInline() ? Msg['EXTERNAL_INPUTS'] :
Msg['INLINE_INPUTS'];
return scope.block!.getInputsInline()
? Msg['EXTERNAL_INPUTS']
: Msg['INLINE_INPUTS'];
},
preconditionFn(scope: Scope) {
const block = scope.block;
@@ -386,8 +397,10 @@ export function registerInline() {
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] instanceof StatementInput) &&
!(block!.inputList[i] instanceof StatementInput)) {
if (
!(block!.inputList[i - 1] instanceof StatementInput) &&
!(block!.inputList[i] instanceof StatementInput)
) {
return 'enabled';
}
}
@@ -410,13 +423,17 @@ export function registerInline() {
export function registerCollapseExpandBlock() {
const collapseExpandOption: RegistryItem = {
displayText(scope: Scope) {
return scope.block!.isCollapsed() ? Msg['EXPAND_BLOCK'] :
Msg['COLLAPSE_BLOCK'];
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) {
if (
!block!.isInFlyout &&
block!.isMovable() &&
block!.workspace.options.collapse
) {
return 'enabled';
}
return 'hidden';
@@ -437,13 +454,17 @@ export function registerCollapseExpandBlock() {
export function registerDisable() {
const disableOption: RegistryItem = {
displayText(scope: Scope) {
return scope.block!.isEnabled() ? Msg['DISABLE_BLOCK'] :
Msg['ENABLE_BLOCK'];
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!.isInFlyout &&
block!.workspace.options.disable &&
block!.isEditable()
) {
if (block!.getInheritedDisabled()) {
return 'disabled';
}
@@ -481,9 +502,9 @@ export function registerDelete() {
// 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', `${descendantCount}`);
return descendantCount === 1
? Msg['DELETE_BLOCK']
: Msg['DELETE_X_BLOCKS'].replace('%1', `${descendantCount}`);
},
preconditionFn(scope: Scope) {
if (!scope.block!.isInFlyout && scope.block!.isDeletable()) {
@@ -513,8 +534,10 @@ export function registerHelp() {
},
preconditionFn(scope: Scope) {
const block = scope.block;
const url = typeof block!.helpUrl === 'function' ? block!.helpUrl() :
block!.helpUrl;
const url =
typeof block!.helpUrl === 'function'
? block!.helpUrl()
: block!.helpUrl;
if (url) {
return 'enabled';
}

View File

@@ -15,7 +15,6 @@ 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
@@ -66,7 +65,7 @@ export class ContextMenuRegistry {
* @param id The ID of the RegistryItem to get.
* @returns RegistryItem or null if not found
*/
getItem(id: string): RegistryItem|null {
getItem(id: string): RegistryItem | null {
return this.registry_.get(id) ?? null;
}
@@ -81,16 +80,19 @@ export class ContextMenuRegistry {
* block being clicked on)
* @returns the list of ContextMenuOptions
*/
getContextMenuOptions(scopeType: ScopeType, scope: Scope):
ContextMenuOption[] {
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 displayText =
typeof item.displayText === 'function'
? item.displayText(scope)
: item.displayText;
const menuOption: ContextMenuOption = {
text: displayText,
enabled: precondition === 'enabled',
@@ -102,7 +104,7 @@ export class ContextMenuRegistry {
}
}
}
menuOptions.sort(function(a, b) {
menuOptions.sort(function (a, b) {
return a.weight - b.weight;
});
return menuOptions;
@@ -135,7 +137,7 @@ export namespace ContextMenuRegistry {
export interface RegistryItem {
callback: (p1: Scope) => void;
scopeType: ScopeType;
displayText: ((p1: Scope) => string)|string;
displayText: ((p1: Scope) => string) | string;
preconditionFn: (p1: Scope) => string;
weight: number;
id: string;
@@ -175,4 +177,4 @@ export type Scope = ContextMenuRegistry.Scope;
export type RegistryItem = ContextMenuRegistry.RegistryItem;
export type ContextMenuOption = ContextMenuRegistry.ContextMenuOption;
export type LegacyContextMenuOption =
ContextMenuRegistry.LegacyContextMenuOption;
ContextMenuRegistry.LegacyContextMenuOption;

View File

@@ -7,7 +7,6 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.Css');
/** Has CSS already been injected? */
let injected = false;

View File

@@ -18,7 +18,6 @@ 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.
@@ -59,7 +58,7 @@ export class DeleteArea extends DragTarget implements IDeleteArea {
*/
wouldDelete(element: IDraggable, couldConnect: boolean): boolean {
if (element instanceof BlockSvg) {
const block = (element);
const block = element;
const couldDeleteBlock = !block.getParent() && block.isDeletable();
this.updateWouldDelete_(couldDeleteBlock && !couldConnect);
} else {

View File

@@ -7,22 +7,28 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.dialog');
let alertImplementation = function(message: string, opt_callback?: () => void) {
let alertImplementation = function (
message: string,
opt_callback?: () => void
) {
window.alert(message);
if (opt_callback) {
opt_callback();
}
};
let confirmImplementation = function(
message: string, callback: (result: boolean) => void) {
let confirmImplementation = function (
message: string,
callback: (result: boolean) => void
) {
callback(window.confirm(message));
};
let promptImplementation = function(
message: string, defaultValue: string,
callback: (result: string|null) => void) {
let promptImplementation = function (
message: string,
defaultValue: string,
callback: (result: string | null) => void
) {
callback(window.prompt(message, defaultValue));
};
@@ -65,7 +71,6 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) {
confirmImplementation(message, callback);
}
/**
* Sets the function to be run when Blockly.dialog.confirm() is called.
*
@@ -73,7 +78,8 @@ function confirmInternal(message: string, callback: (p1: boolean) => void) {
* @see Blockly.dialog.confirm
*/
export function setConfirm(
confirmFunction: (p1: string, p2: (p1: boolean) => void) => void) {
confirmFunction: (p1: string, p2: (p1: boolean) => void) => void
) {
confirmImplementation = confirmFunction;
}
@@ -88,8 +94,10 @@ export function setConfirm(
* @param callback The callback for handling user response.
*/
export function prompt(
message: string, defaultValue: string,
callback: (p1: string|null) => void) {
message: string,
defaultValue: string,
callback: (p1: string | null) => void
) {
promptImplementation(message, defaultValue, callback);
}
@@ -100,8 +108,12 @@ export function prompt(
* @see Blockly.dialog.prompt
*/
export function setPrompt(
promptFunction: (p1: string, p2: string, p3: (p1: string|null) => void) =>
void) {
promptFunction: (
p1: string,
p2: string,
p3: (p1: string | null) => void
) => void
) {
promptImplementation = promptFunction;
}

View File

@@ -17,7 +17,6 @@ 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.
@@ -76,7 +75,7 @@ export class DragTarget implements IDragTarget {
* @returns The component's bounding box. Null if drag target area should be
* ignored.
*/
getClientRect(): Rect|null {
getClientRect(): Rect | null {
return null;
}

View File

@@ -23,7 +23,6 @@ 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).
@@ -52,10 +51,10 @@ 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;
let animateOutTimer: ReturnType<typeof setTimeout> | null = null;
/** Callback for when the drop-down is hidden. */
let onHide: Function|null = null;
let onHide: Function | null = null;
/** A class name representing the current owner's workspace renderer. */
let renderedClassName = '';
@@ -76,13 +75,13 @@ let arrow: HTMLDivElement;
* Drop-downs will appear within the bounds of this element if possible.
* Set in setBoundsElement.
*/
let boundsElement: Element|null = null;
let boundsElement: Element | null = null;
/** The object currently using the drop-down. */
let owner: Field|null = null;
let owner: Field | null = null;
/** Whether the dropdown was positioned to a field or the source block. */
let positionToField: boolean|null = null;
let positionToField: boolean | null = null;
/**
* Dropdown bounds info object used to encapsulate sizing information about a
@@ -103,9 +102,9 @@ export interface PositionMetrics {
initialY: number;
finalX: number;
finalY: number;
arrowX: number|null;
arrowY: number|null;
arrowAtTop: boolean|null;
arrowX: number | null;
arrowY: number | null;
arrowAtTop: boolean | null;
arrowVisible: boolean;
}
@@ -116,7 +115,7 @@ export interface PositionMetrics {
*/
export function createDom() {
if (div) {
return; // Already created.
return; // Already created.
}
div = document.createElement('div');
div.className = 'blocklyDropDownDiv';
@@ -133,15 +132,15 @@ export function createDom() {
div.style.opacity = '0';
// Transition animation for transform: translate() and opacity.
div.style.transition = 'transform ' + ANIMATION_TIME + 's, ' +
'opacity ' + ANIMATION_TIME + 's';
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() {
div.addEventListener('focusin', function () {
dom.addClass(div, 'blocklyFocused');
});
div.addEventListener('focusout', function() {
div.addEventListener('focusout', function () {
dom.removeClass(div, 'blocklyFocused');
});
}
@@ -152,14 +151,14 @@ export function createDom() {
*
* @param boundsElem Element to bind drop-down to.
*/
export function setBoundsElement(boundsElem: Element|null) {
export function setBoundsElement(boundsElem: Element | null) {
boundsElement = boundsElem;
}
/**
* @returns The field that currently owns this, or null.
*/
export function getOwner(): Field|null {
export function getOwner(): Field | null {
return owner;
}
@@ -202,11 +201,17 @@ export function setColour(backgroundColour: string, borderColour: string) {
* @returns True if the menu rendered below block; false if above.
*/
export function showPositionedByBlock<T>(
field: Field<T>, block: BlockSvg, opt_onHide?: Function,
opt_secondaryYOffset?: number): boolean {
field: Field<T>,
block: BlockSvg,
opt_onHide?: Function,
opt_secondaryYOffset?: number
): boolean {
return showPositionedByRect(
getScaledBboxOfBlock(block), field as Field, opt_onHide,
opt_secondaryYOffset);
getScaledBboxOfBlock(block),
field as Field,
opt_onHide,
opt_secondaryYOffset
);
}
/**
@@ -221,12 +226,17 @@ export function showPositionedByBlock<T>(
* @returns True if the menu rendered below block; false if above.
*/
export function showPositionedByField<T>(
field: Field<T>, opt_onHide?: Function,
opt_secondaryYOffset?: number): boolean {
field: Field<T>,
opt_onHide?: Function,
opt_secondaryYOffset?: number
): boolean {
positionToField = true;
return showPositionedByRect(
getScaledBboxOfField(field as Field), field as Field, opt_onHide,
opt_secondaryYOffset);
getScaledBboxOfField(field as Field),
field as Field,
opt_onHide,
opt_secondaryYOffset
);
}
/**
* Get the scaled bounding box of a block.
@@ -267,8 +277,11 @@ function getScaledBboxOfField(field: Field): Rect {
* @returns True if the menu rendered below block; false if above.
*/
function showPositionedByRect(
bBox: Rect, field: Field, opt_onHide?: Function,
opt_secondaryYOffset?: number): boolean {
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;
@@ -286,8 +299,14 @@ function showPositionedByRect(
}
setBoundsElement(workspace.getParentSvg().parentNode as Element | null);
return show(
field, sourceBlock.RTL, primaryX, primaryY, secondaryX, secondaryY,
opt_onHide);
field,
sourceBlock.RTL,
primaryX,
primaryY,
secondaryX,
secondaryY,
opt_onHide
);
}
/**
@@ -310,8 +329,14 @@ function showPositionedByRect(
* @internal
*/
export function show<T>(
newOwner: Field<T>, rtl: boolean, primaryX: number, primaryY: number,
secondaryX: number, secondaryY: number, opt_onHide?: Function): boolean {
newOwner: Field<T>,
rtl: boolean,
primaryX: number,
primaryY: number,
secondaryX: number,
secondaryY: number,
opt_onHide?: Function
): boolean {
owner = newOwner as Field;
onHide = opt_onHide || null;
// Set direction.
@@ -345,7 +370,7 @@ const internal = {
* @returns An object containing size information about the bounding element
* (bounding box and width/height).
*/
getBoundsInfo: function(): BoundsInfo {
getBoundsInfo: function (): BoundsInfo {
const boundPosition = style.getPageOffset(boundsElement as Element);
const boundSize = style.getSize(boundsElement as Element);
@@ -370,9 +395,12 @@ const internal = {
* @returns Various final metrics, including rendered positions for drop-down
* and arrow.
*/
getPositionMetrics: function(
primaryX: number, primaryY: number, secondaryX: number,
secondaryY: number): PositionMetrics {
getPositionMetrics: function (
primaryX: number,
primaryY: number,
secondaryX: number,
secondaryY: number
): PositionMetrics {
const boundsInfo = internal.getBoundsInfo();
const divSize = style.getSize(div as Element);
@@ -383,7 +411,11 @@ const internal = {
// Can we fit in-bounds above the target?
if (secondaryY - divSize.height > boundsInfo.top) {
return getPositionAboveMetrics(
secondaryX, secondaryY, boundsInfo, divSize);
secondaryX,
secondaryY,
boundsInfo,
divSize
);
}
// Can we fit outside the workspace bounds (but inside the window)
// below?
@@ -394,7 +426,11 @@ const internal = {
// above?
if (secondaryY - divSize.height > document.documentElement.clientTop) {
return getPositionAboveMetrics(
secondaryX, secondaryY, boundsInfo, divSize);
secondaryX,
secondaryY,
boundsInfo,
divSize
);
}
// Last resort, render at top of page.
@@ -415,10 +451,17 @@ const internal = {
* and arrow.
*/
function getPositionBelowMetrics(
primaryX: number, primaryY: number, boundsInfo: BoundsInfo,
divSize: Size): PositionMetrics {
const xCoords =
getPositionX(primaryX, boundsInfo.left, boundsInfo.right, divSize.width);
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;
@@ -426,7 +469,7 @@ function getPositionBelowMetrics(
return {
initialX: xCoords.divX,
initialY: primaryY,
finalX: xCoords.divX, // X position remains constant during animation.
finalX: xCoords.divX, // X position remains constant during animation.
finalY,
arrowX: xCoords.arrowX,
arrowY,
@@ -448,19 +491,26 @@ function getPositionBelowMetrics(
* and arrow.
*/
function getPositionAboveMetrics(
secondaryX: number, secondaryY: number, boundsInfo: BoundsInfo,
divSize: Size): PositionMetrics {
secondaryX: number,
secondaryY: number,
boundsInfo: BoundsInfo,
divSize: Size
): PositionMetrics {
const xCoords = getPositionX(
secondaryX, boundsInfo.left, boundsInfo.right, divSize.width);
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.
const initialY = secondaryY - divSize.height; // No padding on Y.
return {
initialX: xCoords.divX,
initialY,
finalX: xCoords.divX, // X position remains constant during animation.
finalX: xCoords.divX, // X position remains constant during animation.
finalY,
arrowX: xCoords.arrowX,
arrowY,
@@ -481,16 +531,23 @@ function getPositionAboveMetrics(
* and arrow.
*/
function getPositionTopOfPageMetrics(
sourceX: number, boundsInfo: BoundsInfo, divSize: Size): PositionMetrics {
const xCoords =
getPositionX(sourceX, boundsInfo.left, boundsInfo.right, divSize.width);
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.
finalX: xCoords.divX, // X position remains constant during animation.
finalY: 0, // Y position remains constant during animation.
arrowAtTop: null,
arrowX: null,
arrowY: null,
@@ -511,8 +568,11 @@ function getPositionTopOfPageMetrics(
* @internal
*/
export function getPositionX(
sourceX: number, boundsLeft: number, boundsRight: number,
divWidth: number): {divX: number, arrowX: number} {
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;
@@ -527,7 +587,10 @@ export function getPositionX(
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);
horizPadding,
relativeArrowX,
divWidth - horizPadding - ARROW_SIZE
);
return {arrowX: relativeArrowX, divX};
}
@@ -550,7 +613,9 @@ export function isVisible(): boolean {
* @returns True if hidden.
*/
export function hideIfOwner<T>(
divOwner: Field<T>, opt_withoutAnimation?: boolean): boolean {
divOwner: Field<T>,
opt_withoutAnimation?: boolean
): boolean {
if (owner === divOwner) {
if (opt_withoutAnimation) {
hideWithoutAnimation();
@@ -569,7 +634,7 @@ export function hide() {
div.style.transform = 'translate(0, 0)';
div.style.opacity = '0';
// Finish animation - reset all values to default.
animateOutTimer = setTimeout(function() {
animateOutTimer = setTimeout(function () {
hideWithoutAnimation();
}, ANIMATION_TIME * 1000);
if (onHide) {
@@ -625,20 +690,33 @@ export function hideWithoutAnimation() {
* @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);
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.style.transform =
'translate(' +
metrics.arrowX +
'px,' +
metrics.arrowY +
'px) rotate(45deg)';
arrow.setAttribute(
'class',
metrics.arrowAtTop ? 'blocklyDropDownArrow blocklyArrowTop' :
'blocklyDropDownArrow blocklyArrowBottom');
'class',
metrics.arrowAtTop
? 'blocklyDropDownArrow blocklyArrowTop'
: 'blocklyDropDownArrow blocklyArrowBottom'
);
} else {
arrow.style.display = 'none';
}
@@ -679,8 +757,9 @@ export function repositionForWindowResize() {
// it.
if (owner) {
const block = owner.getSourceBlock() as BlockSvg;
const bBox = positionToField ? getScaledBboxOfField(owner) :
getScaledBboxOfBlock(block);
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;

View File

@@ -7,7 +7,6 @@
import * as goog from '../../closure/goog/goog.js';
goog.declareModuleId('Blockly.Events');
import {Abstract, AbstractEventJson} from './events_abstract.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {BlockChange, BlockChangeJson} from './events_block_change.js';
@@ -25,7 +24,10 @@ 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 {
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';
@@ -37,7 +39,6 @@ import {ViewportChange, ViewportChangeJson} from './events_viewport.js';
import * as eventUtils from './utils.js';
import {FinishedLoading, FinishedLoadingJson} from './workspace_events.js';
// Events.
export {Abstract};
export {AbstractEventJson};

View File

@@ -19,7 +19,6 @@ import type {Workspace} from '../workspace.js';
import * as eventUtils from './utils.js';
/**
* Abstract class for an event.
*/
@@ -74,8 +73,11 @@ export abstract class Abstract {
*/
fromJson(json: AbstractEventJson) {
deprecation.warn(
'Blockly.Events.Abstract.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Abstract.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
this.isBlank = false;
this.group = json['group'] || '';
}
@@ -89,8 +91,11 @@ export abstract class Abstract {
* supertypes of parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: AbstractEventJson, workspace: Workspace, event: any):
Abstract {
static fromJson(
json: AbstractEventJson,
workspace: Workspace,
event: any
): Abstract {
event.isBlank = false;
event.group = json['group'] || '';
event.workspaceId = workspace.id;
@@ -130,8 +135,9 @@ export abstract class Abstract {
}
if (!workspace) {
throw Error(
'Workspace is null. Event must have been generated from real' +
' Blockly events.');
'Workspace is null. Event must have been generated from real' +
' Blockly events.'
);
}
return workspace;
}

View File

@@ -16,8 +16,10 @@ import type {Block} from '../block.js';
import * as deprecation from '../utils/deprecation.js';
import type {Workspace} from '../workspace.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
/**
* Abstract class for any event related to blocks.
@@ -51,8 +53,9 @@ export class BlockBase extends AbstractEvent {
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');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['blockId'] = this.blockId;
return json;
@@ -65,8 +68,11 @@ export class BlockBase extends AbstractEvent {
*/
override fromJson(json: BlockBaseJson) {
deprecation.warn(
'Blockly.Events.BlockBase.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockBase.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.blockId = json['blockId'];
}
@@ -80,10 +86,16 @@ export class BlockBase extends AbstractEvent {
* static methods in superclasses.
* @internal
*/
static fromJson(json: BlockBaseJson, workspace: Workspace, event?: any):
BlockBase {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockBase()) as BlockBase;
static fromJson(
json: BlockBaseJson,
workspace: Workspace,
event?: any
): BlockBase {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockBase()
) as BlockBase;
newEvent.blockId = json['blockId'];
return newEvent;
}

View File

@@ -23,7 +23,6 @@ import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
/**
* Notifies listeners when some element of a block has changed (e.g.
* field values, comments, etc).
@@ -53,12 +52,16 @@ export class BlockChange extends BlockBase {
* @param opt_newValue New value of element.
*/
constructor(
opt_block?: Block, opt_element?: string, opt_name?: string|null,
opt_oldValue?: unknown, opt_newValue?: unknown) {
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.
return; // Blank event to be populated by fromJson.
}
this.element = opt_element;
this.name = opt_name || undefined;
@@ -75,8 +78,9 @@ export class BlockChange extends BlockBase {
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');
'The changed element is undefined. Either pass an ' +
'element to the constructor, or call fromJson'
);
}
json['element'] = this.element;
json['name'] = this.name;
@@ -92,8 +96,11 @@ export class BlockChange extends BlockBase {
*/
override fromJson(json: BlockChangeJson) {
deprecation.warn(
'Blockly.Events.BlockChange.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockChange.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.element = json['element'];
this.name = json['name'];
@@ -110,11 +117,16 @@ export class BlockChange extends BlockBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BlockChangeJson, workspace: Workspace, event?: any):
BlockChange {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockChange()) as
BlockChange;
static fromJson(
json: BlockChangeJson,
workspace: Workspace,
event?: any
): BlockChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockChange()
) as BlockChange;
newEvent.element = json['element'];
newEvent.name = json['name'];
newEvent.oldValue = json['oldValue'];
@@ -140,14 +152,16 @@ export class BlockChange extends BlockBase {
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');
'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');
'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;
@@ -162,12 +176,12 @@ export class BlockChange extends BlockBase {
if (field) {
field.setValue(value);
} else {
console.warn('Can\'t set non-existent field: ' + this.name);
console.warn("Can't set non-existent field: " + this.name);
}
break;
}
case 'comment':
block.setCommentText(value as string || null);
block.setCommentText((value as string) || null);
break;
case 'collapsed':
block.setCollapsed(!!value);
@@ -181,13 +195,15 @@ export class BlockChange extends BlockBase {
case 'mutation': {
const oldState = BlockChange.getExtraBlockState_(block as BlockSvg);
if (block.loadExtraState) {
block.loadExtraState(JSON.parse(value as string || '{}'));
block.loadExtraState(JSON.parse((value as string) || '{}'));
} else if (block.domToMutation) {
block.domToMutation(
utilsXml.textToDom(value as string || '<mutation/>'));
utilsXml.textToDom((value as string) || '<mutation/>')
);
}
eventUtils.fire(
new BlockChange(block, 'mutation', null, oldState, value));
new BlockChange(block, 'mutation', null, oldState, value)
);
break;
}
default:

View File

@@ -23,7 +23,6 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners when a block (or connected stack of blocks) is
* created.
@@ -32,7 +31,7 @@ export class BlockCreate extends BlockBase {
override type = eventUtils.BLOCK_CREATE;
/** The XML representation of the created block(s). */
xml?: Element|DocumentFragment;
xml?: Element | DocumentFragment;
/** The JSON respresentation of the created block(s). */
json?: blocks.State;
@@ -45,7 +44,7 @@ export class BlockCreate extends BlockBase {
super(opt_block);
if (!opt_block) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
if (opt_block.isShadow()) {
@@ -68,18 +67,21 @@ export class BlockCreate extends BlockBase {
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');
'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');
'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');
'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;
@@ -97,8 +99,11 @@ export class BlockCreate extends BlockBase {
*/
override fromJson(json: BlockCreateJson) {
deprecation.warn(
'Blockly.Events.BlockCreate.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockCreate.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.xml = utilsXml.textToDom(json['xml']);
this.ids = json['ids'];
@@ -117,11 +122,16 @@ export class BlockCreate extends BlockBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BlockCreateJson, workspace: Workspace, event?: any):
BlockCreate {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockCreate()) as
BlockCreate;
static fromJson(
json: BlockCreateJson,
workspace: Workspace,
event?: any
): BlockCreate {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockCreate()
) as BlockCreate;
newEvent.xml = utilsXml.textToDom(json['xml']);
newEvent.ids = json['ids'];
newEvent.json = json['json'] as blocks.State;
@@ -140,13 +150,15 @@ export class BlockCreate extends BlockBase {
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');
'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');
'The block IDs are undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
blocks.append(this.json, workspace);
@@ -158,7 +170,7 @@ export class BlockCreate extends BlockBase {
block.dispose(false);
} else if (id === this.blockId) {
// Only complain about root-level block.
console.warn('Can\'t uncreate non-existent block: ' + id);
console.warn("Can't uncreate non-existent block: " + id);
}
}
}

View File

@@ -23,14 +23,13 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners when a block (or connected stack of blocks) is
* deleted.
*/
export class BlockDelete extends BlockBase {
/** The XML representation of the deleted block(s). */
oldXml?: Element|DocumentFragment;
oldXml?: Element | DocumentFragment;
/** The JSON respresentation of the deleted block(s). */
oldJson?: blocks.State;
@@ -48,7 +47,7 @@ export class BlockDelete extends BlockBase {
super(opt_block);
if (!opt_block) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
if (opt_block.getParent()) {
@@ -62,8 +61,9 @@ export class BlockDelete extends BlockBase {
this.oldXml = Xml.blockToDomWithXY(opt_block);
this.ids = eventUtils.getDescendantIds(opt_block);
this.wasShadow = opt_block.isShadow();
this.oldJson =
blocks.save(opt_block, {addCoordinates: true}) as blocks.State;
this.oldJson = blocks.save(opt_block, {
addCoordinates: true,
}) as blocks.State;
}
/**
@@ -75,23 +75,27 @@ export class BlockDelete extends BlockBase {
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');
'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');
'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');
'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');
'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;
@@ -110,13 +114,16 @@ export class BlockDelete extends BlockBase {
*/
override fromJson(json: BlockDeleteJson) {
deprecation.warn(
'Blockly.Events.BlockDelete.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockDelete.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldXml = utilsXml.textToDom(json['oldXml']);
this.ids = json['ids'];
this.wasShadow =
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
json['wasShadow'] || this.oldXml.tagName.toLowerCase() === 'shadow';
this.oldJson = json['oldJson'];
if (json['recordUndo'] !== undefined) {
this.recordUndo = json['recordUndo'];
@@ -132,15 +139,20 @@ export class BlockDelete extends BlockBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BlockDeleteJson, workspace: Workspace, event?: any):
BlockDelete {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockDelete()) as
BlockDelete;
static fromJson(
json: BlockDeleteJson,
workspace: Workspace,
event?: any
): BlockDelete {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockDelete()
) as BlockDelete;
newEvent.oldXml = utilsXml.textToDom(json['oldXml']);
newEvent.ids = json['ids'];
newEvent.wasShadow =
json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow';
json['wasShadow'] || newEvent.oldXml.tagName.toLowerCase() === 'shadow';
newEvent.oldJson = json['oldJson'];
if (json['recordUndo'] !== undefined) {
newEvent.recordUndo = json['recordUndo'];
@@ -157,13 +169,15 @@ export class BlockDelete extends BlockBase {
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');
'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');
'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++) {
@@ -173,7 +187,7 @@ export class BlockDelete extends BlockBase {
block.dispose(false);
} else if (id === this.blockId) {
// Only complain about root-level block.
console.warn('Can\'t delete non-existent block: ' + id);
console.warn("Can't delete non-existent block: " + id);
}
}
} else {

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners when a block is being manually dragged/dropped.
*/
@@ -66,13 +65,15 @@ export class BlockDrag extends UiBase {
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');
'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');
'The block ID is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['isStart'] = this.isStart;
json['blockId'] = this.blockId;
@@ -89,8 +90,11 @@ export class BlockDrag extends UiBase {
*/
override fromJson(json: BlockDragJson) {
deprecation.warn(
'Blockly.Events.BlockDrag.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockDrag.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isStart = json['isStart'];
this.blockId = json['blockId'];
@@ -106,10 +110,16 @@ export class BlockDrag extends UiBase {
* static methods in superclasses..
* @internal
*/
static fromJson(json: BlockDragJson, workspace: Workspace, event?: any):
BlockDrag {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockDrag()) as BlockDrag;
static fromJson(
json: BlockDragJson,
workspace: Workspace,
event?: any
): BlockDrag {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockDrag()
) as BlockDrag;
newEvent.isStart = json['isStart'];
newEvent.blockId = json['blockId'];
newEvent.blocks = json['blocks'];

View File

@@ -22,7 +22,6 @@ import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
interface BlockLocation {
parentId?: string;
inputName?: string;
@@ -109,14 +108,16 @@ export class BlockMove extends BlockBase {
json['oldParentId'] = this.oldParentId;
json['oldInputName'] = this.oldInputName;
if (this.oldCoordinate) {
json['oldCoordinate'] = `${Math.round(this.oldCoordinate.x)}, ` +
`${Math.round(this.oldCoordinate.y)}`;
json['oldCoordinate'] =
`${Math.round(this.oldCoordinate.x)}, ` +
`${Math.round(this.oldCoordinate.y)}`;
}
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.reason) {
json['reason'] = this.reason;
@@ -134,8 +135,11 @@ export class BlockMove extends BlockBase {
*/
override fromJson(json: BlockMoveJson) {
deprecation.warn(
'Blockly.Events.BlockMove.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BlockMove.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldParentId = json['oldParentId'];
this.oldInputName = json['oldInputName'];
@@ -166,10 +170,16 @@ export class BlockMove extends BlockBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: BlockMoveJson, workspace: Workspace, event?: any):
BlockMove {
const newEvent =
super.fromJson(json, workspace, event ?? new BlockMove()) as BlockMove;
static fromJson(
json: BlockMoveJson,
workspace: Workspace,
event?: any
): BlockMove {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BlockMove()
) as BlockMove;
newEvent.oldParentId = json['oldParentId'];
newEvent.oldInputName = json['oldInputName'];
if (json['oldCoordinate']) {
@@ -218,14 +228,15 @@ export class BlockMove extends BlockBase {
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');
'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 block associated with the block move event ' +
'could not be found');
'The block associated with the block move event ' + 'could not be found'
);
}
const location = {} as BlockLocation;
const parent = block.getParent();
@@ -247,9 +258,11 @@ export class BlockMove extends BlockBase {
* @returns False if something changed.
*/
override isNull(): boolean {
return this.oldParentId === this.newParentId &&
this.oldInputName === this.newInputName &&
Coordinate.equals(this.oldCoordinate, this.newCoordinate);
return (
this.oldParentId === this.newParentId &&
this.oldInputName === this.newInputName &&
Coordinate.equals(this.oldCoordinate, this.newCoordinate)
);
}
/**
@@ -261,22 +274,23 @@ export class BlockMove extends BlockBase {
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');
'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);
console.warn("Can't move non-existent block: " + this.blockId);
return;
}
const parentId = forward ? this.newParentId : this.oldParentId;
const inputName = forward ? this.newInputName : this.oldInputName;
const coordinate = forward ? this.newCoordinate : this.oldCoordinate;
let parentBlock: Block|null;
let parentBlock: Block | null;
if (parentId) {
parentBlock = workspace.getBlockById(parentId);
if (!parentBlock) {
console.warn('Can\'t connect to non-existent block: ' + parentId);
console.warn("Can't connect to non-existent block: " + parentId);
return;
}
}
@@ -288,8 +302,10 @@ export class BlockMove extends BlockBase {
block.moveBy(coordinate.x - xy.x, coordinate.y - xy.y, this.reason);
} else {
let blockConnection = block.outputConnection;
if (!blockConnection ||
block.previousConnection && block.previousConnection.isConnected()) {
if (
!blockConnection ||
(block.previousConnection && block.previousConnection.isConnected())
) {
blockConnection = block.previousConnection;
}
let parentConnection;
@@ -305,7 +321,7 @@ export class BlockMove extends BlockBase {
if (parentConnection && blockConnection) {
blockConnection.connect(parentConnection);
} else {
console.warn('Can\'t connect to non-existent input: ' + inputName);
console.warn("Can't connect to non-existent input: " + inputName);
}
}
}

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Class for a bubble open event.
*/
@@ -44,7 +43,10 @@ export class BubbleOpen extends UiBase {
* 'warning'. Undefined for a blank event.
*/
constructor(
opt_block?: BlockSvg, opt_isOpen?: boolean, opt_bubbleType?: BubbleType) {
opt_block?: BlockSvg,
opt_isOpen?: boolean,
opt_bubbleType?: BubbleType
) {
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
super(workspaceId);
if (!opt_block) return;
@@ -63,13 +65,15 @@ export class BubbleOpen extends UiBase {
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');
'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');
'The type of bubble is undefined. Either pass the ' +
'value to the constructor, or call fromJson'
);
}
json['isOpen'] = this.isOpen;
json['bubbleType'] = this.bubbleType;
@@ -84,8 +88,11 @@ export class BubbleOpen extends UiBase {
*/
override fromJson(json: BubbleOpenJson) {
deprecation.warn(
'Blockly.Events.BubbleOpen.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.BubbleOpen.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isOpen = json['isOpen'];
this.bubbleType = json['bubbleType'];
@@ -101,11 +108,16 @@ export class BubbleOpen extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: BubbleOpenJson, workspace: Workspace, event?: any):
BubbleOpen {
const newEvent =
super.fromJson(json, workspace, event ?? new BubbleOpen()) as
BubbleOpen;
static fromJson(
json: BubbleOpenJson,
workspace: Workspace,
event?: any
): BubbleOpen {
const newEvent = super.fromJson(
json,
workspace,
event ?? new BubbleOpen()
) as BubbleOpen;
newEvent.isOpen = json['isOpen'];
newEvent.bubbleType = json['bubbleType'];
newEvent.blockId = json['blockId'];

View File

@@ -21,7 +21,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js';
/**
* Notifies listeners that ome blockly element was clicked.
*/
@@ -46,8 +45,10 @@ export class Click extends UiBase {
* Undefined for a blank event.
*/
constructor(
opt_block?: Block|null, opt_workspaceId?: string|null,
opt_targetType?: ClickTarget) {
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;
@@ -67,8 +68,9 @@ export class Click extends UiBase {
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');
'The click target type is undefined. Either pass a block to ' +
'the constructor, or call fromJson'
);
}
json['targetType'] = this.targetType;
json['blockId'] = this.blockId;
@@ -82,8 +84,11 @@ export class Click extends UiBase {
*/
override fromJson(json: ClickJson) {
deprecation.warn(
'Blockly.Events.Click.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Click.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.targetType = json['targetType'];
this.blockId = json['blockId'];
@@ -99,8 +104,11 @@ export class Click extends UiBase {
* @internal
*/
static fromJson(json: ClickJson, workspace: Workspace, event?: any): Click {
const newEvent =
super.fromJson(json, workspace, event ?? new Click()) as Click;
const newEvent = super.fromJson(
json,
workspace,
event ?? new Click()
) as Click;
newEvent.targetType = json['targetType'];
newEvent.blockId = json['blockId'];
return newEvent;

View File

@@ -17,13 +17,15 @@ 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 {
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';
import type {Workspace} from '../workspace.js';
/**
* Abstract class for a comment event.
*/
@@ -59,8 +61,9 @@ export class CommentBase extends AbstractEvent {
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');
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
json['commentId'] = this.commentId;
return json;
@@ -73,8 +76,11 @@ export class CommentBase extends AbstractEvent {
*/
override fromJson(json: CommentBaseJson) {
deprecation.warn(
'Blockly.Events.CommentBase.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentBase.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.commentId = json['commentId'];
}
@@ -88,11 +94,16 @@ export class CommentBase extends AbstractEvent {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentBaseJson, workspace: Workspace, event?: any):
CommentBase {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentBase()) as
CommentBase;
static fromJson(
json: CommentBaseJson,
workspace: Workspace,
event?: any
): CommentBase {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentBase()
) as CommentBase;
newEvent.commentId = json['commentId'];
return newEvent;
}
@@ -104,7 +115,9 @@ export class CommentBase extends AbstractEvent {
* @param create if True then Create, if False then Delete
*/
static CommentCreateDeleteHelper(
event: CommentCreate|CommentDelete, create: boolean) {
event: CommentCreate | CommentDelete,
create: boolean
) {
const workspace = event.getEventWorkspace_();
if (create) {
const xmlElement = utilsXml.createElement('xml');
@@ -116,16 +129,16 @@ export class CommentBase extends AbstractEvent {
} else {
if (!event.commentId) {
throw new Error(
'The comment ID is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'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);
console.warn("Can't uncreate non-existent comment: " + event.commentId);
}
}
}

View File

@@ -20,7 +20,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that the contents of a workspace comment has changed.
*/
@@ -41,18 +40,20 @@ export class CommentChange extends CommentBase {
* @param opt_newContents New contents of the comment.
*/
constructor(
opt_comment?: WorkspaceComment, opt_oldContents?: string,
opt_newContents?: string) {
opt_comment?: WorkspaceComment,
opt_oldContents?: string,
opt_newContents?: string
) {
super(opt_comment);
if (!opt_comment) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.oldContents_ =
typeof opt_oldContents === 'undefined' ? '' : opt_oldContents;
typeof opt_oldContents === 'undefined' ? '' : opt_oldContents;
this.newContents_ =
typeof opt_newContents === 'undefined' ? '' : opt_newContents;
typeof opt_newContents === 'undefined' ? '' : opt_newContents;
}
/**
@@ -64,13 +65,15 @@ export class CommentChange extends CommentBase {
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');
'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');
'The new contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
json['oldContents'] = this.oldContents_;
json['newContents'] = this.newContents_;
@@ -84,8 +87,11 @@ export class CommentChange extends CommentBase {
*/
override fromJson(json: CommentChangeJson) {
deprecation.warn(
'Blockly.Events.CommentChange.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentChange.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldContents_ = json['oldContents'];
this.newContents_ = json['newContents'];
@@ -100,11 +106,16 @@ export class CommentChange extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentChangeJson, workspace: Workspace, event?: any):
CommentChange {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentChange()) as
CommentChange;
static fromJson(
json: CommentChangeJson,
workspace: Workspace,
event?: any
): CommentChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentChange()
) as CommentChange;
newEvent.oldContents_ = json['oldContents'];
newEvent.newContents_ = json['newContents'];
return newEvent;
@@ -128,24 +139,27 @@ export class CommentChange extends CommentBase {
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');
'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);
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');
'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');
'The old contents is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
comment.setContent(contents);
}
@@ -157,4 +171,7 @@ export interface CommentChangeJson extends CommentBaseJson {
}
registry.register(
registry.Type.EVENT, eventUtils.COMMENT_CHANGE, CommentChange);
registry.Type.EVENT,
eventUtils.COMMENT_CHANGE,
CommentChange
);

View File

@@ -22,7 +22,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a workspace comment was created.
*/
@@ -30,7 +29,7 @@ export class CommentCreate extends CommentBase {
override type = eventUtils.COMMENT_CREATE;
/** The XML representation of the created workspace comment. */
xml?: Element|DocumentFragment;
xml?: Element | DocumentFragment;
/**
* @param opt_comment The created comment.
@@ -56,8 +55,9 @@ export class CommentCreate extends CommentBase {
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');
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
json['xml'] = Xml.domToText(this.xml);
return json;
@@ -70,8 +70,11 @@ export class CommentCreate extends CommentBase {
*/
override fromJson(json: CommentCreateJson) {
deprecation.warn(
'Blockly.Events.CommentCreate.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentCreate.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.xml = utilsXml.textToDom(json['xml']);
}
@@ -85,11 +88,16 @@ export class CommentCreate extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentCreateJson, workspace: Workspace, event?: any):
CommentCreate {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentCreate()) as
CommentCreate;
static fromJson(
json: CommentCreateJson,
workspace: Workspace,
event?: any
): CommentCreate {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentCreate()
) as CommentCreate;
newEvent.xml = utilsXml.textToDom(json['xml']);
return newEvent;
}
@@ -109,4 +117,7 @@ export interface CommentCreateJson extends CommentBaseJson {
}
registry.register(
registry.Type.EVENT, eventUtils.COMMENT_CREATE, CommentCreate);
registry.Type.EVENT,
eventUtils.COMMENT_CREATE,
CommentCreate
);

View File

@@ -21,7 +21,6 @@ import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a workspace comment has been deleted.
*/
@@ -39,7 +38,7 @@ export class CommentDelete extends CommentBase {
super(opt_comment);
if (!opt_comment) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.xml = opt_comment.toXmlWithXY();
@@ -63,8 +62,9 @@ export class CommentDelete extends CommentBase {
const json = super.toJson() as CommentDeleteJson;
if (!this.xml) {
throw new Error(
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The comment XML is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
json['xml'] = Xml.domToText(this.xml);
return json;
@@ -79,11 +79,16 @@ export class CommentDelete extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentDeleteJson, workspace: Workspace, event?: any):
CommentDelete {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentDelete()) as
CommentDelete;
static fromJson(
json: CommentDeleteJson,
workspace: Workspace,
event?: any
): CommentDelete {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentDelete()
) as CommentDelete;
newEvent.xml = utilsXml.textToDom(json['xml']);
return newEvent;
}
@@ -94,4 +99,7 @@ export interface CommentDeleteJson extends CommentBaseJson {
}
registry.register(
registry.Type.EVENT, eventUtils.COMMENT_DELETE, CommentDelete);
registry.Type.EVENT,
eventUtils.COMMENT_DELETE,
CommentDelete
);

View File

@@ -21,7 +21,6 @@ import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a workspace comment has moved.
*/
@@ -46,7 +45,7 @@ export class CommentMove extends CommentBase {
super(opt_comment);
if (!opt_comment) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.comment_ = opt_comment;
@@ -60,13 +59,15 @@ export class CommentMove extends CommentBase {
recordNew() {
if (this.newCoordinate_) {
throw Error(
'Tried to record the new position of a comment on the ' +
'same event twice.');
'Tried to record the new position of a comment on the ' +
'same event twice.'
);
}
if (!this.comment_) {
throw new Error(
'The comment is undefined. Pass a comment to ' +
'the constructor if you want to use the record functionality');
'The comment is undefined. Pass a comment to ' +
'the constructor if you want to use the record functionality'
);
}
this.newCoordinate_ = this.comment_.getRelativeToSurfaceXY();
}
@@ -91,18 +92,23 @@ export class CommentMove extends CommentBase {
const json = super.toJson() as CommentMoveJson;
if (!this.oldCoordinate_) {
throw new Error(
'The old comment position is undefined. Either pass a comment to ' +
'the constructor, or call fromJson');
'The old comment position is undefined. Either pass a comment to ' +
'the constructor, or call fromJson'
);
}
if (!this.newCoordinate_) {
throw new Error(
'The new comment position is undefined. Either call recordNew, or ' +
'call fromJson');
'The new comment position is undefined. Either call recordNew, or ' +
'call fromJson'
);
}
json['oldCoordinate'] = `${Math.round(this.oldCoordinate_.x)}, ` +
`${Math.round(this.oldCoordinate_.y)}`;
json['newCoordinate'] = Math.round(this.newCoordinate_.x) + ',' +
Math.round(this.newCoordinate_.y);
json['oldCoordinate'] =
`${Math.round(this.oldCoordinate_.x)}, ` +
`${Math.round(this.oldCoordinate_.y)}`;
json['newCoordinate'] =
Math.round(this.newCoordinate_.x) +
',' +
Math.round(this.newCoordinate_.y);
return json;
}
@@ -113,8 +119,11 @@ export class CommentMove extends CommentBase {
*/
override fromJson(json: CommentMoveJson) {
deprecation.warn(
'Blockly.Events.CommentMove.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.CommentMove.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
let xy = json['oldCoordinate'].split(',');
this.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
@@ -131,11 +140,16 @@ export class CommentMove extends CommentBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: CommentMoveJson, workspace: Workspace, event?: any):
CommentMove {
const newEvent =
super.fromJson(json, workspace, event ?? new CommentMove()) as
CommentMove;
static fromJson(
json: CommentMoveJson,
workspace: Workspace,
event?: any
): CommentMove {
const newEvent = super.fromJson(
json,
workspace,
event ?? new CommentMove()
) as CommentMove;
let xy = json['oldCoordinate'].split(',');
newEvent.oldCoordinate_ = new Coordinate(Number(xy[0]), Number(xy[1]));
xy = json['newCoordinate'].split(',');
@@ -161,21 +175,23 @@ export class CommentMove extends CommentBase {
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');
'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 move non-existent comment: ' + this.commentId);
console.warn("Can't move non-existent comment: " + this.commentId);
return;
}
const target = forward ? this.newCoordinate_ : this.oldCoordinate_;
if (!target) {
throw new Error(
'Either oldCoordinate_ or newCoordinate_ is undefined. ' +
'Either oldCoordinate_ or newCoordinate_ is undefined. ' +
'Either pass a comment to the constructor and call recordNew, ' +
'or call fromJson');
'or call fromJson'
);
}
// TODO: Check if the comment is being dragged, and give up if so.
const current = comment.getRelativeToSurfaceXY();

View File

@@ -22,7 +22,6 @@ import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
/**
* Notifies listeners that a marker (used for keyboard navigation) has
* moved.
@@ -57,8 +56,11 @@ export class MarkerMove extends UiBase {
* Undefined for a blank event.
*/
constructor(
opt_block?: Block|null, isCursor?: boolean, opt_oldNode?: ASTNode|null,
opt_newNode?: ASTNode) {
opt_block?: Block | null,
isCursor?: boolean,
opt_oldNode?: ASTNode | null,
opt_newNode?: ASTNode
) {
let workspaceId = opt_block ? opt_block.workspace.id : undefined;
if (opt_newNode && opt_newNode.getType() === ASTNode.types.WORKSPACE) {
workspaceId = (opt_newNode.getLocation() as Workspace).id;
@@ -80,13 +82,15 @@ export class MarkerMove extends UiBase {
const json = super.toJson() as MarkerMoveJson;
if (this.isCursor === undefined) {
throw new Error(
'Whether this is a cursor event or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson');
'Whether this is a cursor event or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson'
);
}
if (!this.newNode) {
throw new Error(
'The new node is undefined. Either pass a node to ' +
'the constructor, or call fromJson');
'The new node is undefined. Either pass a node to ' +
'the constructor, or call fromJson'
);
}
json['isCursor'] = this.isCursor;
json['blockId'] = this.blockId;
@@ -102,8 +106,11 @@ export class MarkerMove extends UiBase {
*/
override fromJson(json: MarkerMoveJson) {
deprecation.warn(
'Blockly.Events.MarkerMove.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.MarkerMove.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isCursor = json['isCursor'];
this.blockId = json['blockId'];
@@ -120,11 +127,16 @@ export class MarkerMove extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: MarkerMoveJson, workspace: Workspace, event?: any):
MarkerMove {
const newEvent =
super.fromJson(json, workspace, event ?? new MarkerMove()) as
MarkerMove;
static fromJson(
json: MarkerMoveJson,
workspace: Workspace,
event?: any
): MarkerMove {
const newEvent = super.fromJson(
json,
workspace,
event ?? new MarkerMove()
) as MarkerMove;
newEvent.isCursor = json['isCursor'];
newEvent.blockId = json['blockId'];
newEvent.oldNode = json['oldNode'];

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Class for a selected event.
* Notifies listeners that a new element has been selected.
@@ -46,8 +45,10 @@ export class Selected extends UiBase {
* Null if no element previously selected. Undefined for a blank event.
*/
constructor(
opt_oldElementId?: string|null, opt_newElementId?: string|null,
opt_workspaceId?: string) {
opt_oldElementId?: string | null,
opt_newElementId?: string | null,
opt_workspaceId?: string
) {
super(opt_workspaceId);
this.oldElementId = opt_oldElementId ?? undefined;
@@ -73,8 +74,11 @@ export class Selected extends UiBase {
*/
override fromJson(json: SelectedJson) {
deprecation.warn(
'Blockly.Events.Selected.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Selected.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldElementId = json['oldElementId'];
this.newElementId = json['newElementId'];
@@ -89,10 +93,16 @@ export class Selected extends UiBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: SelectedJson, workspace: Workspace, event?: any):
Selected {
const newEvent =
super.fromJson(json, workspace, event ?? new Selected()) as Selected;
static fromJson(
json: SelectedJson,
workspace: Workspace,
event?: any
): Selected {
const newEvent = super.fromJson(
json,
workspace,
event ?? new Selected()
) as Selected;
newEvent.oldElementId = json['oldElementId'];
newEvent.newElementId = json['newElementId'];
return newEvent;

View File

@@ -19,7 +19,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that the workspace theme has changed.
*/
@@ -48,8 +47,9 @@ export class ThemeChange extends UiBase {
const json = super.toJson() as ThemeChangeJson;
if (!this.themeName) {
throw new Error(
'The theme name is undefined. Either pass a theme name to ' +
'the constructor, or call fromJson');
'The theme name is undefined. Either pass a theme name to ' +
'the constructor, or call fromJson'
);
}
json['themeName'] = this.themeName;
return json;
@@ -62,8 +62,11 @@ export class ThemeChange extends UiBase {
*/
override fromJson(json: ThemeChangeJson) {
deprecation.warn(
'Blockly.Events.ThemeChange.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.ThemeChange.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.themeName = json['themeName'];
}
@@ -77,11 +80,16 @@ export class ThemeChange extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: ThemeChangeJson, workspace: Workspace, event?: any):
ThemeChange {
const newEvent =
super.fromJson(json, workspace, event ?? new ThemeChange()) as
ThemeChange;
static fromJson(
json: ThemeChangeJson,
workspace: Workspace,
event?: any
): ThemeChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new ThemeChange()
) as ThemeChange;
newEvent.themeName = json['themeName'];
return newEvent;
}

View File

@@ -19,7 +19,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a toolbox item has been selected.
*/
@@ -41,8 +40,10 @@ export class ToolboxItemSelect extends UiBase {
* Undefined for a blank event.
*/
constructor(
opt_oldItem?: string|null, opt_newItem?: string|null,
opt_workspaceId?: string) {
opt_oldItem?: string | null,
opt_newItem?: string | null,
opt_workspaceId?: string
) {
super(opt_workspaceId);
this.oldItem = opt_oldItem ?? undefined;
this.newItem = opt_newItem ?? undefined;
@@ -67,8 +68,11 @@ export class ToolboxItemSelect extends UiBase {
*/
override fromJson(json: ToolboxItemSelectJson) {
deprecation.warn(
'Blockly.Events.ToolboxItemSelect.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.ToolboxItemSelect.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldItem = json['oldItem'];
this.newItem = json['newItem'];
@@ -84,11 +88,15 @@ export class ToolboxItemSelect extends UiBase {
* @internal
*/
static fromJson(
json: ToolboxItemSelectJson, workspace: Workspace,
event?: any): ToolboxItemSelect {
const newEvent =
super.fromJson(json, workspace, event ?? new ToolboxItemSelect()) as
ToolboxItemSelect;
json: ToolboxItemSelectJson,
workspace: Workspace,
event?: any
): ToolboxItemSelect {
const newEvent = super.fromJson(
json,
workspace,
event ?? new ToolboxItemSelect()
) as ToolboxItemSelect;
newEvent.oldItem = json['oldItem'];
newEvent.newItem = json['newItem'];
return newEvent;
@@ -101,4 +109,7 @@ export interface ToolboxItemSelectJson extends AbstractEventJson {
}
registry.register(
registry.Type.EVENT, eventUtils.TOOLBOX_ITEM_SELECT, ToolboxItemSelect);
registry.Type.EVENT,
eventUtils.TOOLBOX_ITEM_SELECT,
ToolboxItemSelect
);

View File

@@ -20,7 +20,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners when the trashcan is opening or closing.
*/
@@ -52,8 +51,9 @@ export class TrashcanOpen extends UiBase {
const json = super.toJson() as TrashcanOpenJson;
if (this.isOpen === undefined) {
throw new Error(
'Whether this is already open or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson');
'Whether this is already open or not is undefined. Either pass ' +
'a value to the constructor, or call fromJson'
);
}
json['isOpen'] = this.isOpen;
return json;
@@ -66,8 +66,11 @@ export class TrashcanOpen extends UiBase {
*/
override fromJson(json: TrashcanOpenJson) {
deprecation.warn(
'Blockly.Events.TrashcanOpen.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.TrashcanOpen.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.isOpen = json['isOpen'];
}
@@ -81,11 +84,16 @@ export class TrashcanOpen extends UiBase {
* parameters to static methods in superclasses.
* @internal
*/
static fromJson(json: TrashcanOpenJson, workspace: Workspace, event?: any):
TrashcanOpen {
const newEvent =
super.fromJson(json, workspace, event ?? new TrashcanOpen()) as
TrashcanOpen;
static fromJson(
json: TrashcanOpenJson,
workspace: Workspace,
event?: any
): TrashcanOpen {
const newEvent = super.fromJson(
json,
workspace,
event ?? new TrashcanOpen()
) as TrashcanOpen;
newEvent.isOpen = json['isOpen'];
return newEvent;
}

View File

@@ -18,7 +18,6 @@ import * as registry from '../registry.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
/**
* Class for a UI event.
*
@@ -39,8 +38,11 @@ export class Ui extends UiBase {
* @param opt_newValue New value of element.
*/
constructor(
opt_block?: Block|null, opt_element?: string,
opt_oldValue?: AnyDuringMigration, opt_newValue?: AnyDuringMigration) {
opt_block?: Block | null,
opt_element?: string,
opt_oldValue?: AnyDuringMigration,
opt_newValue?: AnyDuringMigration
) {
const workspaceId = opt_block ? opt_block.workspace.id : undefined;
super(workspaceId);

View File

@@ -15,7 +15,6 @@ goog.declareModuleId('Blockly.Events.UiBase');
import {Abstract as AbstractEvent} from './events_abstract.js';
/**
* Base class for a UI event.
* UI events are events that don't need to be sent over the wire for multi-user

View File

@@ -15,10 +15,12 @@ goog.declareModuleId('Blockly.Events.VarBase');
import * as deprecation from '../utils/deprecation.js';
import type {VariableModel} from '../variable_model.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import type {Workspace} from '../workspace.js';
/**
* Abstract class for a variable event.
*/
@@ -49,8 +51,9 @@ export class VarBase extends AbstractEvent {
const json = super.toJson() as VarBaseJson;
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
json['varId'] = this.varId;
return json;
@@ -63,8 +66,11 @@ export class VarBase extends AbstractEvent {
*/
override fromJson(json: VarBaseJson) {
deprecation.warn(
'Blockly.Events.VarBase.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.VarBase.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.varId = json['varId'];
}
@@ -78,10 +84,16 @@ export class VarBase extends AbstractEvent {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarBaseJson, workspace: Workspace, event?: any):
VarBase {
const newEvent =
super.fromJson(json, workspace, event ?? new VarBase()) as VarBase;
static fromJson(
json: VarBaseJson,
workspace: Workspace,
event?: any
): VarBase {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarBase()
) as VarBase;
newEvent.varId = json['varId'];
return newEvent;
}

View File

@@ -20,7 +20,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a variable model has been created.
*/
@@ -40,7 +39,7 @@ export class VarCreate extends VarBase {
super(opt_variable);
if (!opt_variable) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.varType = opt_variable.type;
this.varName = opt_variable.name;
@@ -55,13 +54,15 @@ export class VarCreate extends VarBase {
const json = super.toJson() as VarCreateJson;
if (this.varType === undefined) {
throw new Error(
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
json['varType'] = this.varType;
json['varName'] = this.varName;
@@ -75,8 +76,11 @@ export class VarCreate extends VarBase {
*/
override fromJson(json: VarCreateJson) {
deprecation.warn(
'Blockly.Events.VarCreate.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.VarCreate.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.varType = json['varType'];
this.varName = json['varName'];
@@ -91,10 +95,16 @@ export class VarCreate extends VarBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarCreateJson, workspace: Workspace, event?: any):
VarCreate {
const newEvent =
super.fromJson(json, workspace, event ?? new VarCreate()) as VarCreate;
static fromJson(
json: VarCreateJson,
workspace: Workspace,
event?: any
): VarCreate {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarCreate()
) as VarCreate;
newEvent.varType = json['varType'];
newEvent.varName = json['varName'];
return newEvent;
@@ -109,13 +119,15 @@ export class VarCreate extends VarBase {
const workspace = this.getEventWorkspace_();
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
workspace.createVariable(this.varName, this.varType, this.varId);

View File

@@ -15,7 +15,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a variable model has been deleted.
*
@@ -35,7 +34,7 @@ export class VarDelete extends VarBase {
super(opt_variable);
if (!opt_variable) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.varType = opt_variable.type;
this.varName = opt_variable.name;
@@ -50,13 +49,15 @@ export class VarDelete extends VarBase {
const json = super.toJson() as VarDeleteJson;
if (this.varType === undefined) {
throw new Error(
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var type is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
json['varType'] = this.varType;
json['varName'] = this.varName;
@@ -70,8 +71,11 @@ export class VarDelete extends VarBase {
*/
override fromJson(json: VarDeleteJson) {
deprecation.warn(
'Blockly.Events.VarDelete.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.VarDelete.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.varType = json['varType'];
this.varName = json['varName'];
@@ -86,10 +90,16 @@ export class VarDelete extends VarBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarDeleteJson, workspace: Workspace, event?: any):
VarDelete {
const newEvent =
super.fromJson(json, workspace, event ?? new VarDelete()) as VarDelete;
static fromJson(
json: VarDeleteJson,
workspace: Workspace,
event?: any
): VarDelete {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarDelete()
) as VarDelete;
newEvent.varType = json['varType'];
newEvent.varName = json['varName'];
return newEvent;
@@ -104,13 +114,15 @@ export class VarDelete extends VarBase {
const workspace = this.getEventWorkspace_();
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.varName) {
throw new Error(
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
workspace.deleteVariableById(this.varId);

View File

@@ -15,7 +15,6 @@ import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that a variable model was renamed.
*
@@ -38,7 +37,7 @@ export class VarRename extends VarBase {
super(opt_variable);
if (!opt_variable) {
return; // Blank event to be populated by fromJson.
return; // Blank event to be populated by fromJson.
}
this.oldName = opt_variable.name;
this.newName = typeof newName === 'undefined' ? '' : newName;
@@ -53,13 +52,15 @@ export class VarRename extends VarBase {
const json = super.toJson() as VarRenameJson;
if (!this.oldName) {
throw new Error(
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.newName) {
throw new Error(
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
json['oldName'] = this.oldName;
json['newName'] = this.newName;
@@ -73,8 +74,11 @@ export class VarRename extends VarBase {
*/
override fromJson(json: VarRenameJson) {
deprecation.warn(
'Blockly.Events.VarRename.prototype.fromJson', 'version 9',
'version 10', 'Blockly.Events.fromJson');
'Blockly.Events.VarRename.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.oldName = json['oldName'];
this.newName = json['newName'];
@@ -89,10 +93,16 @@ export class VarRename extends VarBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: VarRenameJson, workspace: Workspace, event?: any):
VarRename {
const newEvent =
super.fromJson(json, workspace, event ?? new VarRename()) as VarRename;
static fromJson(
json: VarRenameJson,
workspace: Workspace,
event?: any
): VarRename {
const newEvent = super.fromJson(
json,
workspace,
event ?? new VarRename()
) as VarRename;
newEvent.oldName = json['oldName'];
newEvent.newName = json['newName'];
return newEvent;
@@ -107,18 +117,21 @@ export class VarRename extends VarBase {
const workspace = this.getEventWorkspace_();
if (!this.varId) {
throw new Error(
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The var ID is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.oldName) {
throw new Error(
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson');
'The old var name is undefined. Either pass a variable to ' +
'the constructor, or call fromJson'
);
}
if (!this.newName) {
throw new Error(
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The new var name is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (forward) {
workspace.renameVariableById(this.varId, this.newName);

View File

@@ -19,7 +19,6 @@ import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
/**
* Notifies listeners that the workspace surface's position or scale has
* changed.
@@ -59,8 +58,12 @@ export class ViewportChange extends UiBase {
* event.
*/
constructor(
opt_top?: number, opt_left?: number, opt_scale?: number,
opt_workspaceId?: string, opt_oldScale?: number) {
opt_top?: number,
opt_left?: number,
opt_scale?: number,
opt_workspaceId?: string,
opt_oldScale?: number
) {
super(opt_workspaceId);
this.viewTop = opt_top;
@@ -78,23 +81,27 @@ export class ViewportChange extends UiBase {
const json = super.toJson() as ViewportChangeJson;
if (this.viewTop === undefined) {
throw new Error(
'The view top is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The view top is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (this.viewLeft === undefined) {
throw new Error(
'The view left is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The view left is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (this.scale === undefined) {
throw new Error(
'The scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
if (this.oldScale === undefined) {
throw new Error(
'The old scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson');
'The old scale is undefined. Either pass a value to ' +
'the constructor, or call fromJson'
);
}
json['viewTop'] = this.viewTop;
json['viewLeft'] = this.viewLeft;
@@ -110,8 +117,11 @@ export class ViewportChange extends UiBase {
*/
override fromJson(json: ViewportChangeJson) {
deprecation.warn(
'Blockly.Events.Viewport.prototype.fromJson', 'version 9', 'version 10',
'Blockly.Events.fromJson');
'Blockly.Events.Viewport.prototype.fromJson',
'version 9',
'version 10',
'Blockly.Events.fromJson'
);
super.fromJson(json);
this.viewTop = json['viewTop'];
this.viewLeft = json['viewLeft'];
@@ -128,11 +138,16 @@ export class ViewportChange extends UiBase {
* static methods in superclasses.
* @internal
*/
static fromJson(json: ViewportChangeJson, workspace: Workspace, event?: any):
ViewportChange {
const newEvent =
super.fromJson(json, workspace, event ?? new ViewportChange()) as
ViewportChange;
static fromJson(
json: ViewportChangeJson,
workspace: Workspace,
event?: any
): ViewportChange {
const newEvent = super.fromJson(
json,
workspace,
event ?? new ViewportChange()
) as ViewportChange;
newEvent.viewTop = json['viewTop'];
newEvent.viewLeft = json['viewLeft'];
newEvent.scale = json['scale'];
@@ -149,4 +164,7 @@ export interface ViewportChangeJson extends AbstractEventJson {
}
registry.register(
registry.Type.EVENT, eventUtils.VIEWPORT_CHANGE, ViewportChange);
registry.Type.EVENT,
eventUtils.VIEWPORT_CHANGE,
ViewportChange
);

View File

@@ -22,7 +22,6 @@ import type {CommentCreate} from './events_comment_create.js';
import type {CommentMove} from './events_comment_move.js';
import type {ViewportChange} from './events_viewport.js';
/** Group ID for new events. Grouped events are indivisible. */
let group = '';
@@ -187,7 +186,7 @@ export const FINISHED_LOADING = 'finished_loading';
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
*/
export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove;
export type BumpEvent = BlockCreate | BlockMove | CommentCreate | CommentMove;
/**
* List of events that cause objects to be bumped back into the visible
@@ -196,8 +195,12 @@ export type BumpEvent = BlockCreate|BlockMove|CommentCreate|CommentMove;
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
*/
export const BUMP_EVENTS: string[] =
[BLOCK_CREATE, BLOCK_MOVE, COMMENT_CREATE, COMMENT_MOVE];
export const BUMP_EVENTS: string[] = [
BLOCK_CREATE,
BLOCK_MOVE,
COMMENT_CREATE,
COMMENT_MOVE,
];
/** List of events queued for firing. */
const FIRE_QUEUE: Abstract[] = [];
@@ -235,12 +238,11 @@ function fireInternal(event: Abstract) {
FIRE_QUEUE.push(event);
}
/** Fire all queued events. */
function fireNow() {
const queue = filter(FIRE_QUEUE, true);
FIRE_QUEUE.length = 0;
for (let i = 0, event; event = queue[i]; i++) {
for (let i = 0, event; (event = queue[i]); i++) {
if (!event.workspaceId) {
continue;
}
@@ -268,7 +270,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
const mergedQueue = [];
const hash = Object.create(null);
// Merge duplicates.
for (let i = 0, event; event = queue[i]; i++) {
for (let i = 0, event; (event = queue[i]); i++) {
if (!event.isNull()) {
// Treat all UI events as the same type in hash table.
const eventType = event.isUiEvent ? UI : event.type;
@@ -293,8 +295,9 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
if (moveEvent.reason) {
if (lastEvent.reason) {
// Concatenate reasons without duplicates.
const reasonSet =
new Set(moveEvent.reason.concat(lastEvent.reason));
const reasonSet = new Set(
moveEvent.reason.concat(lastEvent.reason)
);
lastEvent.reason = Array.from(reasonSet);
} else {
lastEvent.reason = moveEvent.reason;
@@ -302,9 +305,10 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
}
lastEntry.index = i;
} else if (
event.type === CHANGE &&
(event as BlockChange).element === lastEvent.element &&
(event as BlockChange).name === lastEvent.name) {
event.type === CHANGE &&
(event as BlockChange).element === lastEvent.element &&
(event as BlockChange).name === lastEvent.name
) {
const changeEvent = event as BlockChange;
// Merge change events.
lastEvent.newValue = changeEvent.newValue;
@@ -326,7 +330,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
}
}
// Filter out any events that have become null due to merging.
queue = mergedQueue.filter(function(e) {
queue = mergedQueue.filter(function (e) {
return !e.isNull();
});
if (!forward) {
@@ -335,11 +339,13 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
}
// Move mutation events to the top of the queue.
// Intentionally skip first event.
for (let i = 1, event; event = queue[i]; i++) {
for (let i = 1, event; (event = queue[i]); i++) {
// AnyDuringMigration because: Property 'element' does not exist on type
// 'Abstract'.
if (event.type === CHANGE &&
(event as AnyDuringMigration).element === 'mutation') {
if (
event.type === CHANGE &&
(event as AnyDuringMigration).element === 'mutation'
) {
queue.unshift(queue.splice(i, 1)[0]);
}
}
@@ -351,7 +357,7 @@ export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
* in the undo stack. Called by Workspace.clearUndo.
*/
export function clearPendingUndo() {
for (let i = 0, event; event = FIRE_QUEUE[i]; i++) {
for (let i = 0, event; (event = FIRE_QUEUE[i]); i++) {
event.recordUndo = false;
}
}
@@ -395,14 +401,14 @@ export function getGroup(): string {
* @param state True to start new group, false to end group.
* String to set group explicitly.
*/
export function setGroup(state: boolean|string) {
export function setGroup(state: boolean | string) {
TEST_ONLY.setGroupInternal(state);
}
/**
* Private version of setGroup for stubbing in tests.
*/
function setGroupInternal(state: boolean|string) {
function setGroupInternal(state: boolean | string) {
if (typeof state === 'boolean') {
group = state ? idGenerator.genUid() : '';
} else {
@@ -420,7 +426,7 @@ function setGroupInternal(state: boolean|string) {
export function getDescendantIds(block: Block): string[] {
const ids = [];
const descendants = block.getDescendants(false);
for (let i = 0, descendant; descendant = descendants[i]; i++) {
for (let i = 0, descendant; (descendant = descendants[i]); i++) {
ids[i] = descendant.id;
}
return ids;
@@ -435,7 +441,9 @@ export function getDescendantIds(block: Block): string[] {
* @throws {Error} if an event type is not found in the registry.
*/
export function fromJson(
json: AnyDuringMigration, workspace: Workspace): Abstract {
json: AnyDuringMigration,
workspace: Workspace
): Abstract {
const eventClass = get(json['type']);
if (!eventClass) throw Error('Unknown event type.');
@@ -457,11 +465,14 @@ export function fromJson(
* Returns false if no static fromJson method exists on the contructor, or if
* the static fromJson method is inheritted.
*/
function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract):
boolean {
function eventClassHasStaticFromJson(
eventClass: new (...p: any[]) => Abstract
): boolean {
const untypedEventClass = eventClass as any;
return Object.getOwnPropertyDescriptors(untypedEventClass).fromJson &&
typeof untypedEventClass.fromJson === 'function';
return (
Object.getOwnPropertyDescriptors(untypedEventClass).fromJson &&
typeof untypedEventClass.fromJson === 'function'
);
}
/**
@@ -470,8 +481,9 @@ function eventClassHasStaticFromJson(eventClass: new (...p: any[]) => Abstract):
* @param eventType The type of the event to get.
* @returns The event class with the given type.
*/
export function get(eventType: string):
(new (...p1: AnyDuringMigration[]) => Abstract) {
export function get(
eventType: string
): new (...p1: AnyDuringMigration[]) => Abstract {
const event = registry.getClass(registry.Type.EVENT, eventType);
if (!event) {
throw new Error(`Event type ${eventType} not found in registry.`);
@@ -493,8 +505,9 @@ export function disableOrphans(event: Abstract) {
if (!blockEvent.workspaceId) {
return;
}
const eventWorkspace =
common.getWorkspaceById(blockEvent.workspaceId) as WorkspaceSvg;
const eventWorkspace = common.getWorkspaceById(
blockEvent.workspaceId
) as WorkspaceSvg;
if (!blockEvent.blockId) {
throw new Error('Encountered a blockEvent without a proper blockId');
}
@@ -507,12 +520,13 @@ export function disableOrphans(event: Abstract) {
const parent = block.getParent();
if (parent && parent.isEnabled()) {
const children = block.getDescendants(false);
for (let i = 0, child; child = children[i]; i++) {
for (let i = 0, child; (child = children[i]); i++) {
child.setEnabled(true);
}
} else if (
(block.outputConnection || block.previousConnection) &&
!eventWorkspace.isDragging()) {
(block.outputConnection || block.previousConnection) &&
!eventWorkspace.isDragging()
) {
do {
block.setEnabled(false);
block = block.getNextBlock();

View File

@@ -14,10 +14,12 @@ goog.declareModuleId('Blockly.Events.FinishedLoading');
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {Abstract as AbstractEvent, AbstractEventJson} from './events_abstract.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import * as eventUtils from './utils.js';
/**
* Notifies listeners when the workspace has finished deserializing from
* JSON/XML.
@@ -49,8 +51,9 @@ export class FinishedLoading extends AbstractEvent {
const json = super.toJson() as FinishedLoadingJson;
if (!this.workspaceId) {
throw new Error(
'The workspace ID is undefined. Either pass a workspace to ' +
'the constructor, or call fromJson');
'The workspace ID is undefined. Either pass a workspace to ' +
'the constructor, or call fromJson'
);
}
json['workspaceId'] = this.workspaceId;
return json;
@@ -72,4 +75,7 @@ export interface FinishedLoadingJson extends AbstractEventJson {
}
registry.register(
registry.Type.EVENT, eventUtils.FINISHED_LOADING, FinishedLoading);
registry.Type.EVENT,
eventUtils.FINISHED_LOADING,
FinishedLoading
);

View File

@@ -13,7 +13,6 @@ import {FieldDropdown} from './field_dropdown.js';
import {Mutator} from './mutator.js';
import * as parsing from './utils/parsing.js';
/** The set of all registered extensions, keyed by extension name/id. */
const allExtensions = Object.create(null);
export const TEST_ONLY = {allExtensions};
@@ -54,7 +53,7 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) {
if (!mixinObj || typeof mixinObj !== 'object') {
throw Error('Error: Mixin "' + name + '" must be a object');
}
register(name, function(this: Block) {
register(name, function (this: Block) {
this.mixin(mixinObj);
});
}
@@ -73,8 +72,11 @@ export function registerMixin(name: string, mixinObj: AnyDuringMigration) {
* @throws {Error} if the mutation is invalid or can't be applied to the block.
*/
export function registerMutator(
name: string, mixinObj: AnyDuringMigration,
opt_helperFn?: () => AnyDuringMigration, opt_blockList?: string[]) {
name: string,
mixinObj: AnyDuringMigration,
opt_helperFn?: () => AnyDuringMigration,
opt_blockList?: string[]
) {
const errorPrefix = 'Error when registering mutator "' + name + '": ';
checkHasMutatorProperties(errorPrefix, mixinObj);
@@ -85,7 +87,7 @@ export function registerMutator(
}
// Sanity checks passed.
register(name, function(this: Block) {
register(name, function (this: Block) {
if (hasMutatorDialog) {
this.setMutator(new Mutator(opt_blockList || [], this as BlockSvg));
}
@@ -108,7 +110,8 @@ export function unregister(name: string) {
delete allExtensions[name];
} else {
console.warn(
'No extension mapping for name "' + name + '" found to unregister');
'No extension mapping for name "' + name + '" found to unregister'
);
}
}
@@ -151,11 +154,15 @@ export function apply(name: string, block: Block, isMutator: boolean) {
const errorPrefix = 'Error after applying mutator "' + name + '": ';
checkHasMutatorProperties(errorPrefix, block);
} else {
if (!mutatorPropertiesMatch(
mutatorProperties as AnyDuringMigration[], block)) {
if (
!mutatorPropertiesMatch(mutatorProperties as AnyDuringMigration[], block)
) {
throw Error(
'Error when applying extension "' + name + '": ' +
'mutation properties changed when applying a non-mutator extension.');
'Error when applying extension "' +
name +
'": ' +
'mutation properties changed when applying a non-mutator extension.'
);
}
}
}
@@ -173,9 +180,12 @@ function checkNoMutatorProperties(mutationName: string, block: Block) {
const properties = getMutatorProperties(block);
if (properties.length) {
throw Error(
'Error: tried to apply mutation "' + mutationName +
'Error: tried to apply mutation "' +
mutationName +
'" to a block that already has mutator functions.' +
' Block id: ' + block.id);
' Block id: ' +
block.id
);
}
}
@@ -191,10 +201,14 @@ function checkNoMutatorProperties(mutationName: string, block: Block) {
* actually a function.
*/
function checkXmlHooks(
object: AnyDuringMigration, errorPrefix: string): boolean {
object: AnyDuringMigration,
errorPrefix: string
): boolean {
return checkHasFunctionPair(
object.mutationToDom, object.domToMutation,
errorPrefix + ' mutationToDom/domToMutation');
object.mutationToDom,
object.domToMutation,
errorPrefix + ' mutationToDom/domToMutation'
);
}
/**
* Checks if the given object has both the 'saveExtraState' and 'loadExtraState'
@@ -208,10 +222,14 @@ function checkXmlHooks(
* actually a function.
*/
function checkJsonHooks(
object: AnyDuringMigration, errorPrefix: string): boolean {
object: AnyDuringMigration,
errorPrefix: string
): boolean {
return checkHasFunctionPair(
object.saveExtraState, object.loadExtraState,
errorPrefix + ' saveExtraState/loadExtraState');
object.saveExtraState,
object.loadExtraState,
errorPrefix + ' saveExtraState/loadExtraState'
);
}
/**
@@ -225,9 +243,14 @@ function checkJsonHooks(
* actually a function.
*/
function checkMutatorDialog(
object: AnyDuringMigration, errorPrefix: string): boolean {
object: AnyDuringMigration,
errorPrefix: string
): boolean {
return checkHasFunctionPair(
object.compose, object.decompose, errorPrefix + ' compose/decompose');
object.compose,
object.decompose,
errorPrefix + ' compose/decompose'
);
}
/**
@@ -243,8 +266,10 @@ function checkMutatorDialog(
* actually a function.
*/
function checkHasFunctionPair(
func1: AnyDuringMigration, func2: AnyDuringMigration,
errorPrefix: string): boolean {
func1: AnyDuringMigration,
func2: AnyDuringMigration,
errorPrefix: string
): boolean {
if (func1 && func2) {
if (typeof func1 !== 'function' || typeof func2 !== 'function') {
throw Error(errorPrefix + ' must be a function');
@@ -263,13 +288,16 @@ function checkHasFunctionPair(
* @param object The object to inspect.
*/
function checkHasMutatorProperties(
errorPrefix: string, object: AnyDuringMigration) {
errorPrefix: string,
object: AnyDuringMigration
) {
const hasXmlHooks = checkXmlHooks(object, errorPrefix);
const hasJsonHooks = checkJsonHooks(object, errorPrefix);
if (!hasXmlHooks && !hasJsonHooks) {
throw Error(
errorPrefix +
'Mutations must contain either XML hooks, or JSON hooks, or both');
errorPrefix +
'Mutations must contain either XML hooks, or JSON hooks, or both'
);
}
// A block with a mutator isn't required to have a mutation dialog, but
// it should still have both or neither of compose and decompose.
@@ -318,7 +346,9 @@ function getMutatorProperties(block: Block): AnyDuringMigration[] {
* @returns True if the property lists match.
*/
function mutatorPropertiesMatch(
oldProperties: AnyDuringMigration[], block: Block): boolean {
oldProperties: AnyDuringMigration[],
block: Block
): boolean {
const newProperties = getMutatorProperties(block);
if (newProperties.length !== oldProperties.length) {
return false;
@@ -343,10 +373,10 @@ export function runAfterPageLoad(fn: () => void) {
throw Error('runAfterPageLoad() requires browser document.');
}
if (document.readyState === 'complete') {
fn(); // Page has already loaded. Call immediately.
fn(); // Page has already loaded. Call immediately.
} else {
// Poll readyState.
const readyStateCheckInterval = setInterval(function() {
const readyStateCheckInterval = setInterval(function () {
if (document.readyState === 'complete') {
clearInterval(readyStateCheckInterval);
fn();
@@ -375,7 +405,9 @@ export function runAfterPageLoad(fn: () => void) {
* @returns The extension function.
*/
export function buildTooltipForDropdown(
dropdownName: string, lookupTable: {[key: string]: string}): Function {
dropdownName: string,
lookupTable: {[key: string]: string}
): Function {
// List of block types already validated, to minimize duplicate warnings.
const blockTypesChecked: AnyDuringMigration[] = [];
@@ -383,8 +415,9 @@ export function buildTooltipForDropdown(
// Wait for load, in case Blockly.Msg is not yet populated.
// runAfterPageLoad() does not run in a Node.js environment due to lack
// of document object, in which case skip the validation.
if (typeof document === 'object') { // Relies on document.readyState
runAfterPageLoad(function() {
if (typeof document === 'object') {
// Relies on document.readyState
runAfterPageLoad(function () {
for (const key in lookupTable) {
// Will print warnings if reference is missing.
parsing.checkMessageReferences(lookupTable[key]);
@@ -399,24 +432,29 @@ export function buildTooltipForDropdown(
blockTypesChecked.push(this.type);
}
this.setTooltip(function(this: Block) {
const value = String(this.getFieldValue(dropdownName));
let tooltip = lookupTable[value];
if (tooltip === null) {
if (blockTypesChecked.indexOf(this.type) === -1) {
// Warn for missing values on generated tooltips.
let warning = 'No tooltip mapping for value ' + value + ' of field ' +
this.setTooltip(
function (this: Block) {
const value = String(this.getFieldValue(dropdownName));
let tooltip = lookupTable[value];
if (tooltip === null) {
if (blockTypesChecked.indexOf(this.type) === -1) {
// Warn for missing values on generated tooltips.
let warning =
'No tooltip mapping for value ' +
value +
' of field ' +
dropdownName;
if (this.type !== null) {
warning += ' of block type ' + this.type;
if (this.type !== null) {
warning += ' of block type ' + this.type;
}
console.warn(warning + '.');
}
console.warn(warning + '.');
} else {
tooltip = parsing.replaceMessageReferences(tooltip);
}
} else {
tooltip = parsing.replaceMessageReferences(tooltip);
}
return tooltip;
}.bind(this));
return tooltip;
}.bind(this)
);
}
return extensionFn;
}
@@ -430,17 +468,25 @@ export function buildTooltipForDropdown(
* @param lookupTable The string lookup table
*/
function checkDropdownOptionsInTable(
block: Block, dropdownName: string, lookupTable: {[key: string]: string}) {
block: Block,
dropdownName: string,
lookupTable: {[key: string]: string}
) {
// Validate all dropdown options have values.
const dropdown = block.getField(dropdownName);
if (dropdown instanceof FieldDropdown && !dropdown.isOptionListDynamic()) {
const options = dropdown.getOptions();
for (let i = 0; i < options.length; i++) {
const optionKey = options[i][1]; // label, then value
const optionKey = options[i][1]; // label, then value
if (lookupTable[optionKey] === null) {
console.warn(
'No tooltip mapping for value ' + optionKey + ' of field ' +
dropdownName + ' of block type ' + block.type);
'No tooltip mapping for value ' +
optionKey +
' of field ' +
dropdownName +
' of block type ' +
block.type
);
}
}
}
@@ -457,13 +503,16 @@ function checkDropdownOptionsInTable(
* @returns The extension function.
*/
export function buildTooltipWithFieldText(
msgTemplate: string, fieldName: string): Function {
msgTemplate: string,
fieldName: string
): Function {
// Check the tooltip string messages for invalid references.
// Wait for load, in case Blockly.Msg is not yet populated.
// runAfterPageLoad() does not run in a Node.js environment due to lack
// of document object, in which case skip the validation.
if (typeof document === 'object') { // Relies on document.readyState
runAfterPageLoad(function() {
if (typeof document === 'object') {
// Relies on document.readyState
runAfterPageLoad(function () {
// Will print warnings if reference is missing.
parsing.checkMessageReferences(msgTemplate);
});
@@ -471,11 +520,14 @@ export function buildTooltipWithFieldText(
/** The actual extension. */
function extensionFn(this: Block) {
this.setTooltip(function(this: Block) {
const field = this.getField(fieldName);
return parsing.replaceMessageReferences(msgTemplate)
this.setTooltip(
function (this: Block) {
const field = this.getField(fieldName);
return parsing
.replaceMessageReferences(msgTemplate)
.replace('%1', field ? field.getText() : '');
}.bind(this));
}.bind(this)
);
}
return extensionFn;
}
@@ -488,10 +540,14 @@ export function buildTooltipWithFieldText(
*/
function extensionParentTooltip(this: Block) {
const tooltipWhenNotConnected = this.tooltip;
this.setTooltip(function(this: Block) {
const parent = this.getParent();
return parent && parent.getInputsInline() && parent.tooltip ||
tooltipWhenNotConnected;
}.bind(this));
this.setTooltip(
function (this: Block) {
const parent = this.getParent();
return (
(parent && parent.getInputsInline() && parent.tooltip) ||
tooltipWhenNotConnected
);
}.bind(this)
);
}
register('parent_tooltip_when_inline', extensionParentTooltip);

View File

@@ -59,17 +59,21 @@ import {ISerializable} from './interfaces/i_serializable.js';
*
* - `undefined` to set `newValue` as is.
*/
export type FieldValidator<T = any> = (newValue: T) => T|null|undefined;
export type FieldValidator<T = any> = (newValue: T) => T | null | undefined;
/**
* Abstract class for an editable field.
*
* @typeParam T - The value stored on the field.
*/
export abstract class Field<T = any> implements IASTNodeLocationSvg,
IASTNodeLocationWithBlock,
IKeyboardAccessible,
IRegistrable, ISerializable {
export abstract class Field<T = any>
implements
IASTNodeLocationSvg,
IASTNodeLocationWithBlock,
IKeyboardAccessible,
IRegistrable,
ISerializable
{
/**
* To overwrite the default value which is set in **Field**, directly update
* the prototype.
@@ -79,7 +83,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* FieldImage.prototype.DEFAULT_VALUE = null;
* ```
*/
DEFAULT_VALUE: T|null = null;
DEFAULT_VALUE: T | null = null;
/** Non-breaking space. */
static readonly NBSP = '\u00A0';
@@ -96,47 +100,47 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* Static labels are usually unnamed.
*/
name?: string = undefined;
protected value_: T|null;
protected value_: T | null;
/** Validation function called when user edits an editable field. */
protected validator_: FieldValidator<T>|null = null;
protected validator_: FieldValidator<T> | null = null;
/**
* Used to cache the field's tooltip value if setTooltip is called when the
* field is not yet initialized. Is *not* guaranteed to be accurate.
*/
private tooltip_: Tooltip.TipInfo|null = null;
private tooltip_: Tooltip.TipInfo | null = null;
protected size_: Size;
/**
* Holds the cursors svg element when the cursor is attached to the field.
* This is null if there is no cursor on the field.
*/
private cursorSvg_: SVGElement|null = null;
private cursorSvg_: SVGElement | null = null;
/**
* Holds the markers svg element when the marker is attached to the field.
* This is null if there is no marker on the field.
*/
private markerSvg_: SVGElement|null = null;
private markerSvg_: SVGElement | null = null;
/** The rendered field's SVG group element. */
protected fieldGroup_: SVGGElement|null = null;
protected fieldGroup_: SVGGElement | null = null;
/** The rendered field's SVG border element. */
protected borderRect_: SVGRectElement|null = null;
protected borderRect_: SVGRectElement | null = null;
/** The rendered field's SVG text element. */
protected textElement_: SVGTextElement|null = null;
protected textElement_: SVGTextElement | null = null;
/** The rendered field's text content element. */
protected textContent_: Text|null = null;
protected textContent_: Text | null = null;
/** Mouse down event listener data. */
private mouseDownWrapper_: browserEvents.Data|null = null;
private mouseDownWrapper_: browserEvents.Data | null = null;
/** Constants associated with the source block's renderer. */
protected constants_: ConstantProvider|null = null;
protected constants_: ConstantProvider | null = null;
/**
* Has this field been disposed of?
@@ -149,7 +153,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
maxDisplayLength = 50;
/** Block this field is attached to. Starts as null, then set in init. */
protected sourceBlock_: Block|null = null;
protected sourceBlock_: Block | null = null;
/** Does this block need to be re-rendered? */
protected isDirty_ = true;
@@ -163,21 +167,21 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
protected enabled_ = true;
/** The element the click handler is bound to. */
protected clickTarget_: Element|null = null;
protected clickTarget_: Element | null = null;
/**
* The prefix field.
*
* @internal
*/
prefixField: string|null = null;
prefixField: string | null = null;
/**
* The suffix field.
*
* @internal
*/
suffixField: string|null = null;
suffixField: string | null = null;
/**
* Editable fields usually show some sort of UI indicating they are
@@ -208,15 +212,18 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* this parameter supports.
*/
constructor(
value: T|typeof Field.SKIP_SETUP, validator?: FieldValidator<T>|null,
config?: FieldConfig) {
value: T | typeof Field.SKIP_SETUP,
validator?: FieldValidator<T> | null,
config?: FieldConfig
) {
/**
* A generic value possessed by the field.
* Should generally be non-null, only null when the field is created.
*/
this.value_ = 'DEFAULT_VALUE' in new.target.prototype ?
new.target.prototype.DEFAULT_VALUE :
this.DEFAULT_VALUE;
this.value_ =
'DEFAULT_VALUE' in new.target.prototype
? new.target.prototype.DEFAULT_VALUE
: this.DEFAULT_VALUE;
/** The size of the area rendered by the field. */
this.size_ = new Size(0, 0);
@@ -263,13 +270,16 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns The renderer constant provider.
*/
getConstants(): ConstantProvider|null {
if (!this.constants_ && this.sourceBlock_ &&
!this.sourceBlock_.isDeadOrDying() &&
this.sourceBlock_.workspace.rendered) {
getConstants(): ConstantProvider | null {
if (
!this.constants_ &&
this.sourceBlock_ &&
!this.sourceBlock_.isDeadOrDying() &&
this.sourceBlock_.workspace.rendered
) {
this.constants_ = (this.sourceBlock_.workspace as WorkspaceSvg)
.getRenderer()
.getConstants();
.getRenderer()
.getConstants();
}
return this.constants_;
}
@@ -280,7 +290,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns The block containing this field.
* @throws An error if the source block is not defined.
*/
getSourceBlock(): Block|null {
getSourceBlock(): Block | null {
return this.sourceBlock_;
}
@@ -334,16 +344,18 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*/
protected createBorderRect_() {
this.borderRect_ = dom.createSvgElement(
Svg.RECT, {
'rx': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'ry': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'x': 0,
'y': 0,
'height': this.size_.height,
'width': this.size_.width,
'class': 'blocklyFieldRect',
},
this.fieldGroup_);
Svg.RECT,
{
'rx': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'ry': this.getConstants()!.FIELD_BORDER_RECT_RADIUS,
'x': 0,
'y': 0,
'height': this.size_.height,
'width': this.size_.width,
'class': 'blocklyFieldRect',
},
this.fieldGroup_
);
}
/**
@@ -353,10 +365,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*/
protected createTextElement_() {
this.textElement_ = dom.createSvgElement(
Svg.TEXT, {
'class': 'blocklyText',
},
this.fieldGroup_);
Svg.TEXT,
{
'class': 'blocklyText',
},
this.fieldGroup_
);
if (this.getConstants()!.FIELD_TEXT_BASELINE_CENTER) {
this.textElement_.setAttribute('dominant-baseline', 'central');
}
@@ -373,7 +387,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
if (!clickTarget) throw new Error('A click target has not been set.');
Tooltip.bindMouseEvents(clickTarget);
this.mouseDownWrapper_ = browserEvents.conditionalBind(
clickTarget, 'pointerdown', this, this.onMouseDown_);
clickTarget,
'pointerdown',
this,
this.onMouseDown_
);
}
/**
@@ -443,14 +461,18 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* Used to see if `this` has overridden any relevant hooks.
* @returns The stringified version of the XML state, or null.
*/
protected saveLegacyState(callingClass: FieldProto): string|null {
if (callingClass.prototype.saveState === this.saveState &&
callingClass.prototype.toXml !== this.toXml) {
protected saveLegacyState(callingClass: FieldProto): string | null {
if (
callingClass.prototype.saveState === this.saveState &&
callingClass.prototype.toXml !== this.toXml
) {
const elem = utilsXml.createElement('field');
elem.setAttribute('name', this.name || '');
const text = utilsXml.domToText(this.toXml(elem));
return text.replace(
' xmlns="https://developers.google.com/blockly/xml"', '');
' xmlns="https://developers.google.com/blockly/xml"',
''
);
}
// Either they called this on purpose from their saveState, or they have
// no implementations of either hook. Just do our thing.
@@ -466,10 +488,14 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @param state The state to apply to the field.
* @returns Whether the state was applied or not.
*/
loadLegacyState(callingClass: FieldProto, state: AnyDuringMigration):
boolean {
if (callingClass.prototype.loadState === this.loadState &&
callingClass.prototype.fromXml !== this.fromXml) {
loadLegacyState(
callingClass: FieldProto,
state: AnyDuringMigration
): boolean {
if (
callingClass.prototype.loadState === this.loadState &&
callingClass.prototype.fromXml !== this.fromXml
) {
this.fromXml(utilsXml.textToDom(state as string));
return true;
}
@@ -539,9 +565,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns Whether this field is clickable.
*/
isClickable(): boolean {
return this.enabled_ && !!this.sourceBlock_ &&
this.sourceBlock_.isEditable() &&
this.showEditor_ !== Field.prototype.showEditor_;
return (
this.enabled_ &&
!!this.sourceBlock_ &&
this.sourceBlock_.isEditable() &&
this.showEditor_ !== Field.prototype.showEditor_
);
}
/**
@@ -553,8 +582,12 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* editable block.
*/
isCurrentlyEditable(): boolean {
return this.enabled_ && this.EDITABLE && !!this.sourceBlock_ &&
this.sourceBlock_.isEditable();
return (
this.enabled_ &&
this.EDITABLE &&
!!this.sourceBlock_ &&
this.sourceBlock_.isEditable()
);
}
/**
@@ -570,9 +603,10 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
isSerializable = true;
} else if (this.EDITABLE) {
console.warn(
'Detected an editable field that was not serializable.' +
'Detected an editable field that was not serializable.' +
' Please define SERIALIZABLE property as true on all editable custom' +
' fields. Proceeding with serialization.');
' fields. Proceeding with serialization.'
);
isSerializable = true;
}
}
@@ -630,7 +664,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Validation function, or null.
*/
getValidator(): FieldValidator<T>|null {
getValidator(): FieldValidator<T> | null {
return this.validator_;
}
@@ -640,7 +674,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns The group element.
*/
getSvgRoot(): SVGGElement|null {
getSvgRoot(): SVGGElement | null {
return this.fieldGroup_;
}
@@ -764,17 +798,23 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*/
protected updateSize_(margin?: number) {
const constants = this.getConstants();
const xOffset = margin !== undefined ? margin :
this.borderRect_ ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING :
0;
const xOffset =
margin !== undefined
? margin
: this.borderRect_
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
let totalWidth = xOffset * 2;
let totalHeight = constants!.FIELD_TEXT_HEIGHT;
let contentWidth = 0;
if (this.textElement_) {
contentWidth = dom.getFastTextWidth(
this.textElement_, constants!.FIELD_TEXT_FONTSIZE,
constants!.FIELD_TEXT_FONTWEIGHT, constants!.FIELD_TEXT_FONTFAMILY);
this.textElement_,
constants!.FIELD_TEXT_FONTSIZE,
constants!.FIELD_TEXT_FONTWEIGHT,
constants!.FIELD_TEXT_FONTFAMILY
);
totalWidth += contentWidth;
}
if (this.borderRect_) {
@@ -803,18 +843,23 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
const halfHeight = this.size_.height / 2;
this.textElement_.setAttribute(
'x',
String(
this.getSourceBlock()?.RTL ?
this.size_.width - contentWidth - xOffset :
xOffset));
'x',
String(
this.getSourceBlock()?.RTL
? this.size_.width - contentWidth - xOffset
: xOffset
)
);
this.textElement_.setAttribute(
'y',
String(
constants!.FIELD_TEXT_BASELINE_CENTER ?
halfHeight :
halfHeight - constants!.FIELD_TEXT_HEIGHT / 2 +
constants!.FIELD_TEXT_BASELINE));
'y',
String(
constants!.FIELD_TEXT_BASELINE_CENTER
? halfHeight
: halfHeight -
constants!.FIELD_TEXT_HEIGHT / 2 +
constants!.FIELD_TEXT_BASELINE
)
);
}
/** Position a field's border rect after a size change. */
@@ -825,9 +870,13 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.borderRect_.setAttribute('width', String(this.size_.width));
this.borderRect_.setAttribute('height', String(this.size_.height));
this.borderRect_.setAttribute(
'rx', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
'rx',
String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS)
);
this.borderRect_.setAttribute(
'ry', String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS));
'ry',
String(this.getConstants()!.FIELD_BORDER_RECT_RADIUS)
);
}
/**
@@ -852,8 +901,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
// Don't issue a warning if the field is actually zero width.
if (this.size_.width !== 0) {
console.warn(
'Deprecated use of setting size_.width to 0 to rerender a' +
' field. Set field.isDirty_ to true instead.');
'Deprecated use of setting size_.width to 0 to rerender a' +
' field. Set field.isDirty_ to true instead.'
);
}
}
return this.size_;
@@ -953,7 +1003,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Current text or null.
*/
protected getText_(): string|null {
protected getText_(): string | null {
return null;
}
@@ -1031,8 +1081,15 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
this.doValueUpdate_(localValue);
if (source && eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
source, 'field', this.name || null, oldValue, localValue));
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
source,
'field',
this.name || null,
oldValue,
localValue
)
);
}
if (this.isDirty_) {
this.forceRerender();
@@ -1048,7 +1105,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* @returns New value, or an Error object.
*/
private processValidation_(
newValue: AnyDuringMigration, validatedValue: T|null|undefined): T|Error {
newValue: AnyDuringMigration,
validatedValue: T | null | undefined
): T | Error {
if (validatedValue === null) {
this.doValueInvalid_(newValue);
if (this.isDirty_) {
@@ -1056,7 +1115,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
}
return Error();
}
return validatedValue === undefined ? newValue as T : validatedValue;
return validatedValue === undefined ? (newValue as T) : validatedValue;
}
/**
@@ -1064,7 +1123,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Current value.
*/
getValue(): T|null {
getValue(): T | null {
return this.value_;
}
@@ -1088,10 +1147,11 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* - `undefined` to set `newValue` as is.
*/
protected doClassValidation_(newValue: T): T|null|undefined;
protected doClassValidation_(newValue?: AnyDuringMigration): T|null;
protected doClassValidation_(newValue?: T|AnyDuringMigration): T|null
|undefined {
protected doClassValidation_(newValue: T): T | null | undefined;
protected doClassValidation_(newValue?: AnyDuringMigration): T | null;
protected doClassValidation_(
newValue?: T | AnyDuringMigration
): T | null | undefined {
if (newValue === null || newValue === undefined) {
return null;
}
@@ -1143,8 +1203,9 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
* display the tooltip of the parent block. To not display a tooltip pass
* the empty string.
*/
setTooltip(newTip: Tooltip.TipInfo|null) {
if (!newTip && newTip !== '') { // If null or undefined.
setTooltip(newTip: Tooltip.TipInfo | null) {
if (!newTip && newTip !== '') {
// If null or undefined.
newTip = this.sourceBlock_;
}
const clickTarget = this.getClickTarget_();
@@ -1177,7 +1238,7 @@ export abstract class Field<T = any> implements IASTNodeLocationSvg,
*
* @returns Element to bind click handler to.
*/
protected getClickTarget_(): Element|null {
protected getClickTarget_(): Element | null {
return this.clickTarget_ || this.getSvgRoot();
}
@@ -1351,7 +1412,8 @@ export class UnattachedFieldError extends Error {
/** @internal */
constructor() {
super(
'The field has not yet been attached to its input. ' +
'Call appendField to attach it.');
'The field has not yet been attached to its input. ' +
'Call appendField to attach it.'
);
}
}

View File

@@ -18,7 +18,11 @@ import * as Css from './css.js';
import * as dropDownDiv from './dropdowndiv.js';
import {Field, UnattachedFieldError} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import {
FieldInput,
FieldInputConfig,
FieldInputValidator,
} from './field_input.js';
import * as dom from './utils/dom.js';
import * as math from './utils/math.js';
import {Svg} from './utils/svg.js';
@@ -92,13 +96,13 @@ export class FieldAngle extends FieldInput<number> {
private boundEvents: browserEvents.Data[] = [];
/** Dynamic red line pointing at the value's angle. */
private line: SVGLineElement|null = null;
private line: SVGLineElement | null = null;
/** Dynamic pink area extending from 0 to the value's angle. */
private gauge: SVGPathElement|null = null;
private gauge: SVGPathElement | null = null;
/** The degree symbol for this field. */
protected symbol_: SVGTSpanElement|null = null;
protected symbol_: SVGTSpanElement | null = null;
/**
* @param value The initial value of the field. Should cast to a number.
@@ -114,8 +118,10 @@ export class FieldAngle extends FieldInput<number> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|number|typeof Field.SKIP_SETUP,
validator?: FieldAngleValidator, config?: FieldAngleConfig) {
value?: string | number | typeof Field.SKIP_SETUP,
validator?: FieldAngleValidator,
config?: FieldAngleConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -192,8 +198,9 @@ export class FieldAngle extends FieldInput<number> {
if (this.sourceBlock_ instanceof BlockSvg) {
dropDownDiv.setColour(
this.sourceBlock_.style.colourPrimary,
this.sourceBlock_.style.colourTertiary);
this.sourceBlock_.style.colourPrimary,
this.sourceBlock_.style.colourTertiary
);
}
dropDownDiv.showPositionedByField(this, this.dropdownDispose.bind(this));
@@ -217,50 +224,80 @@ export class FieldAngle extends FieldInput<number> {
'style': 'touch-action: none',
});
const circle = dom.createSvgElement(
Svg.CIRCLE, {
'cx': FieldAngle.HALF,
'cy': FieldAngle.HALF,
'r': FieldAngle.RADIUS,
'class': 'blocklyAngleCircle',
},
svg);
this.gauge =
dom.createSvgElement(Svg.PATH, {'class': 'blocklyAngleGauge'}, svg);
Svg.CIRCLE,
{
'cx': FieldAngle.HALF,
'cy': FieldAngle.HALF,
'r': FieldAngle.RADIUS,
'class': 'blocklyAngleCircle',
},
svg
);
this.gauge = dom.createSvgElement(
Svg.PATH,
{'class': 'blocklyAngleGauge'},
svg
);
this.line = dom.createSvgElement(
Svg.LINE, {
'x1': FieldAngle.HALF,
'y1': FieldAngle.HALF,
'class': 'blocklyAngleLine',
},
svg);
Svg.LINE,
{
'x1': FieldAngle.HALF,
'y1': FieldAngle.HALF,
'class': 'blocklyAngleLine',
},
svg
);
// Draw markers around the edge.
for (let angle = 0; angle < 360; angle += 15) {
dom.createSvgElement(
Svg.LINE, {
'x1': FieldAngle.HALF + FieldAngle.RADIUS,
'y1': FieldAngle.HALF,
'x2': FieldAngle.HALF + FieldAngle.RADIUS -
(angle % 45 === 0 ? 10 : 5),
'y2': FieldAngle.HALF,
'class': 'blocklyAngleMarks',
'transform': 'rotate(' + angle + ',' + FieldAngle.HALF + ',' +
FieldAngle.HALF + ')',
},
svg);
Svg.LINE,
{
'x1': FieldAngle.HALF + FieldAngle.RADIUS,
'y1': FieldAngle.HALF,
'x2':
FieldAngle.HALF + FieldAngle.RADIUS - (angle % 45 === 0 ? 10 : 5),
'y2': FieldAngle.HALF,
'class': 'blocklyAngleMarks',
'transform':
'rotate(' +
angle +
',' +
FieldAngle.HALF +
',' +
FieldAngle.HALF +
')',
},
svg
);
}
// The angle picker is different from other fields in that it updates on
// mousemove even if it's not in the middle of a drag. In future we may
// change this behaviour.
this.boundEvents.push(
browserEvents.conditionalBind(svg, 'click', this, this.hide));
browserEvents.conditionalBind(svg, 'click', this, this.hide)
);
// On touch devices, the picker's value is only updated with a drag. Add
// a click handler on the drag surface to update the value if the surface
// is clicked.
this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointerdown', this, this.onMouseMove_, true));
this.boundEvents.push(browserEvents.conditionalBind(
circle, 'pointermove', this, this.onMouseMove_, true));
this.boundEvents.push(
browserEvents.conditionalBind(
circle,
'pointerdown',
this,
this.onMouseMove_,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
circle,
'pointermove',
this,
this.onMouseMove_,
true
)
);
return svg;
}
@@ -353,14 +390,31 @@ export class FieldAngle extends FieldInput<number> {
x2 += Math.cos(angleRadians) * FieldAngle.RADIUS;
y2 -= Math.sin(angleRadians) * FieldAngle.RADIUS;
// Don't ask how the flag calculations work. They just do.
let largeFlag =
Math.abs(Math.floor((angleRadians - angle1) / Math.PI) % 2);
let largeFlag = Math.abs(
Math.floor((angleRadians - angle1) / Math.PI) % 2
);
if (clockwiseFlag) {
largeFlag = 1 - largeFlag;
}
path.push(
' l ', x1, ',', y1, ' A ', FieldAngle.RADIUS, ',', FieldAngle.RADIUS,
' 0 ', largeFlag, ' ', clockwiseFlag, ' ', x2, ',', y2, ' z');
' l ',
x1,
',',
y1,
' A ',
FieldAngle.RADIUS,
',',
FieldAngle.RADIUS,
' 0 ',
largeFlag,
' ',
clockwiseFlag,
' ',
x2,
',',
y2,
' z'
);
}
this.gauge.setAttribute('d', path.join(''));
this.line.setAttribute('x2', `${x2}`);
@@ -412,7 +466,7 @@ export class FieldAngle extends FieldInput<number> {
* @param newValue The input value.
* @returns A valid angle, or null if invalid.
*/
protected override doClassValidation_(newValue?: any): number|null {
protected override doClassValidation_(newValue?: any): number | null {
const value = Number(newValue);
if (isNaN(value) || !isFinite(value)) {
return null;
@@ -456,7 +510,6 @@ fieldRegistry.register('field_angle', FieldAngle);
FieldAngle.prototype.DEFAULT_VALUE = 0;
/**
* CSS for angle field.
*/

View File

@@ -19,8 +19,8 @@ import * as dom from './utils/dom.js';
import {Field, FieldConfig, FieldValidator} from './field.js';
import * as fieldRegistry from './field_registry.js';
type BoolString = 'TRUE'|'FALSE';
type CheckboxBool = BoolString|boolean;
type BoolString = 'TRUE' | 'FALSE';
type CheckboxBool = BoolString | boolean;
/**
* Class for a checkbox field.
@@ -45,7 +45,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* NOTE: The default value is set in `Field`, so maintain that value instead
* of overwriting it here or in the constructor.
*/
override value_: boolean|null = this.value_;
override value_: boolean | null = this.value_;
/**
* @param value The initial value of the field. Should either be 'TRUE',
@@ -62,8 +62,10 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* for a list of properties this parameter supports.
*/
constructor(
value?: CheckboxBool|typeof Field.SKIP_SETUP,
validator?: FieldCheckboxValidator, config?: FieldCheckboxConfig) {
value?: CheckboxBool | typeof Field.SKIP_SETUP,
validator?: FieldCheckboxValidator,
config?: FieldCheckboxConfig
) {
super(Field.SKIP_SETUP);
/**
@@ -136,7 +138,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param character The character to use for the check mark, or null to use
* the default.
*/
setCheckCharacter(character: string|null) {
setCheckCharacter(character: string | null) {
this.checkChar = character || FieldCheckbox.CHECK_CHAR;
this.forceRerender();
}
@@ -152,8 +154,9 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param newValue The input value.
* @returns A valid value ('TRUE' or 'FALSE), or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration):
BoolString|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): BoolString | null {
if (newValue === true || newValue === 'TRUE') {
return 'TRUE';
}
@@ -191,7 +194,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
*
* @returns The boolean value of this field.
*/
getValueBoolean(): boolean|null {
getValueBoolean(): boolean | null {
return this.value_;
}
@@ -213,7 +216,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param value The value to convert.
* @returns The converted value.
*/
private convertValueToBool_(value: CheckboxBool|null): boolean {
private convertValueToBool_(value: CheckboxBool | null): boolean {
if (typeof value === 'string') return value === 'TRUE';
return !!value;
}

View File

@@ -36,6 +36,7 @@ export class FieldColour extends Field<string> {
* Copied from goog.ui.ColorPicker.SIMPLE_GRID_COLORS
* All colour pickers use this unless overridden with setColours.
*/
// prettier-ignore
static COLOURS: string[] = [
// grays
'#ffffff', '#cccccc', '#c0c0c0', '#999999',
@@ -74,10 +75,10 @@ export class FieldColour extends Field<string> {
static COLUMNS = 7;
/** The field's colour picker element. */
private picker: HTMLElement|null = null;
private picker: HTMLElement | null = null;
/** Index of the currently highlighted element. */
private highlightedIndex: number|null = null;
private highlightedIndex: number | null = null;
/**
* Array holding info needed to unbind events.
@@ -103,13 +104,13 @@ export class FieldColour extends Field<string> {
protected override isDirty_ = false;
/** Array of colours used by this field. If null, use the global list. */
private colours: string[]|null = null;
private colours: string[] | null = null;
/**
* Array of colour tooltips used by this field. If null, use the global
* list.
*/
private titles: string[]|null = null;
private titles: string[] | null = null;
/**
* Number of colour columns used by this field. If 0, use the global
@@ -132,8 +133,10 @@ export class FieldColour extends Field<string> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP, validator?: FieldColourValidator,
config?: FieldColourConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldColourValidator,
config?: FieldColourConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -165,8 +168,9 @@ export class FieldColour extends Field<string> {
*/
override initView() {
this.size_ = new Size(
this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH,
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT);
this.getConstants()!.FIELD_COLOUR_DEFAULT_WIDTH,
this.getConstants()!.FIELD_COLOUR_DEFAULT_HEIGHT
);
if (!this.getConstants()!.FIELD_COLOUR_FULL_BLOCK) {
this.createBorderRect_();
this.getBorderRect().style['fillOpacity'] = '1';
@@ -185,7 +189,9 @@ export class FieldColour extends Field<string> {
}
} else if (this.sourceBlock_ instanceof BlockSvg) {
this.sourceBlock_.pathObject.svgPath.setAttribute(
'fill', this.getValue() as string);
'fill',
this.getValue() as string
);
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
}
@@ -196,7 +202,7 @@ export class FieldColour extends Field<string> {
* @param newValue The input value.
* @returns A valid colour, or null if invalid.
*/
protected override doClassValidation_(newValue?: any): string|null {
protected override doClassValidation_(newValue?: any): string | null {
if (typeof newValue !== 'string') {
return null;
}
@@ -214,8 +220,10 @@ export class FieldColour extends Field<string> {
if (this.borderRect_) {
this.borderRect_.style.fill = newValue;
} else if (
this.sourceBlock_ && this.sourceBlock_.rendered &&
this.sourceBlock_ instanceof BlockSvg) {
this.sourceBlock_ &&
this.sourceBlock_.rendered &&
this.sourceBlock_ instanceof BlockSvg
) {
this.sourceBlock_.pathObject.svgPath.setAttribute('fill', newValue);
this.sourceBlock_.pathObject.svgPath.setAttribute('stroke', '#fff');
}
@@ -297,7 +305,7 @@ export class FieldColour extends Field<string> {
*/
private onKeyDown(e: KeyboardEvent) {
let handled = true;
let highlighted: HTMLElement|null;
let highlighted: HTMLElement | null;
switch (e.key) {
case 'ArrowUp':
this.moveHighlightBy(0, -1);
@@ -423,7 +431,7 @@ export class FieldColour extends Field<string> {
*
* @returns Highlighted item (null if none).
*/
private getHighlighted(): HTMLElement|null {
private getHighlighted(): HTMLElement | null {
if (!this.highlightedIndex) {
return null;
}
@@ -476,7 +484,10 @@ export class FieldColour extends Field<string> {
aria.setRole(table, aria.Role.GRID);
aria.setState(table, aria.State.EXPANDED, true);
aria.setState(
table, aria.State.ROWCOUNT, Math.floor(colours.length / columns));
table,
aria.State.ROWCOUNT,
Math.floor(colours.length / columns)
);
aria.setState(table, aria.State.COLCOUNT, columns);
let row: Element;
for (let i = 0; i < colours.length; i++) {
@@ -485,7 +496,7 @@ export class FieldColour extends Field<string> {
aria.setRole(row, aria.Role.ROW);
table.appendChild(row);
}
const cell = (document.createElement('td'));
const cell = document.createElement('td');
row!.appendChild(cell);
// This becomes the value, if clicked.
cell.setAttribute('data-colour', colours[i]);
@@ -503,16 +514,51 @@ export class FieldColour extends Field<string> {
}
// Configure event handler on the table to listen for any event in a cell.
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerdown', this, this.onClick, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointermove', this, this.onMouseMove, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerenter', this, this.onMouseEnter, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'pointerleave', this, this.onMouseLeave, true));
this.boundEvents.push(browserEvents.conditionalBind(
table, 'keydown', this, this.onKeyDown, false));
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointerdown',
this,
this.onClick,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointermove',
this,
this.onMouseMove,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointerenter',
this,
this.onMouseEnter,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'pointerleave',
this,
this.onMouseLeave,
true
)
);
this.boundEvents.push(
browserEvents.conditionalBind(
table,
'keydown',
this,
this.onKeyDown,
false
)
);
this.picker = table;
}
@@ -547,7 +593,6 @@ FieldColour.prototype.DEFAULT_VALUE = FieldColour.COLOURS[0];
fieldRegistry.register('field_colour', FieldColour);
/**
* CSS for colour picker.
*/

View File

@@ -16,7 +16,12 @@ goog.declareModuleId('Blockly.FieldDropdown');
import type {BlockSvg} from './block_svg.js';
import * as dropDownDiv from './dropdowndiv.js';
import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
import {
Field,
FieldConfig,
FieldValidator,
UnattachedFieldError,
} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
@@ -44,21 +49,21 @@ export class FieldDropdown extends Field<string> {
static ARROW_CHAR = '▾';
/** A reference to the currently selected menu item. */
private selectedMenuItem: MenuItem|null = null;
private selectedMenuItem: MenuItem | null = null;
/** The dropdown menu. */
protected menu_: Menu|null = null;
protected menu_: Menu | null = null;
/**
* SVG image element if currently selected option is an image, or null.
*/
private imageElement: SVGImageElement|null = null;
private imageElement: SVGImageElement | null = null;
/** Tspan based arrow element. */
private arrow: SVGTSpanElement|null = null;
private arrow: SVGTSpanElement | null = null;
/** SVG based arrow element. */
private svgArrow: SVGElement|null = null;
private svgArrow: SVGElement | null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -72,24 +77,24 @@ export class FieldDropdown extends Field<string> {
protected menuGenerator_?: MenuGenerator;
/** A cache of the most recently generated options. */
private generatedOptions: MenuOption[]|null = null;
private generatedOptions: MenuOption[] | null = null;
/**
* The prefix field label, of common words set after options are trimmed.
*
* @internal
*/
override prefixField: string|null = null;
override prefixField: string | null = null;
/**
* The suffix field label, of common words set after options are trimmed.
*
* @internal
*/
override suffixField: string|null = null;
override suffixField: string | null = null;
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
private selectedOption!: MenuOption;
override clickTarget_: SVGElement|null = null;
override clickTarget_: SVGElement | null = null;
/**
* @param menuGenerator A non-empty array of options for a dropdown list, or a
@@ -108,15 +113,15 @@ export class FieldDropdown extends Field<string> {
* @throws {TypeError} If `menuGenerator` options are incorrectly structured.
*/
constructor(
menuGenerator: MenuGenerator,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig,
menuGenerator: MenuGenerator,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig
);
constructor(menuGenerator: typeof Field.SKIP_SETUP);
constructor(
menuGenerator: MenuGenerator|typeof Field.SKIP_SETUP,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig,
menuGenerator: MenuGenerator | typeof Field.SKIP_SETUP,
validator?: FieldDropdownValidator,
config?: FieldDropdownConfig
) {
super(Field.SKIP_SETUP);
@@ -210,17 +215,23 @@ export class FieldDropdown extends Field<string> {
* @returns True if the dropdown field should add a border rect.
*/
protected shouldAddBorderRect_(): boolean {
return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
!this.getSourceBlock()?.isShadow();
return (
!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
(this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
!this.getSourceBlock()?.isShadow())
);
}
/** Create a tspan based arrow. */
protected createTextArrow_() {
this.arrow = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
this.arrow!.appendChild(document.createTextNode(
this.getSourceBlock()?.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
' ' + FieldDropdown.ARROW_CHAR));
this.arrow!.appendChild(
document.createTextNode(
this.getSourceBlock()?.RTL
? FieldDropdown.ARROW_CHAR + ' '
: ' ' + FieldDropdown.ARROW_CHAR
)
);
if (this.getSourceBlock()?.RTL) {
this.getTextElement().insertBefore(this.arrow, this.textContent_);
} else {
@@ -231,14 +242,18 @@ export class FieldDropdown extends Field<string> {
/** Create an SVG based arrow. */
protected createSVGArrow_() {
this.svgArrow = dom.createSvgElement(
Svg.IMAGE, {
'height': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
'width': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
},
this.fieldGroup_);
Svg.IMAGE,
{
'height': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
'width': this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE + 'px',
},
this.fieldGroup_
);
this.svgArrow!.setAttributeNS(
dom.XLINK_NS, 'xlink:href',
this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI);
dom.XLINK_NS,
'xlink:href',
this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_DATAURI
);
}
/**
@@ -266,11 +281,12 @@ export class FieldDropdown extends Field<string> {
dom.addClass(menuElement, 'blocklyDropdownMenu');
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
const primaryColour =
block.isShadow() ? block.getParent()!.getColour() : block.getColour();
const borderColour = block.isShadow() ?
(block.getParent() as BlockSvg).style.colourTertiary :
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
const primaryColour = block.isShadow()
? block.getParent()!.getColour()
: block.getColour();
const borderColour = block.isShadow()
? (block.getParent() as BlockSvg).style.colourTertiary
: (this.sourceBlock_ as BlockSvg).style.colourTertiary;
dropDownDiv.setColour(primaryColour, borderColour);
}
@@ -284,8 +300,10 @@ export class FieldDropdown extends Field<string> {
if (this.selectedMenuItem) {
this.menu_!.setHighlighted(this.selectedMenuItem);
style.scrollIntoContainerView(
this.selectedMenuItem.getElement()!, dropDownDiv.getContentDiv(),
true);
this.selectedMenuItem.getElement()!,
dropDownDiv.getContentDiv(),
true
);
}
this.applyColour();
@@ -397,16 +415,21 @@ export class FieldDropdown extends Field<string> {
* @param newValue The input value.
* @returns A valid language-neutral option, or null if invalid.
*/
protected override doClassValidation_(newValue?: string): string|null {
protected override doClassValidation_(newValue?: string): string | null {
const options = this.getOptions(true);
const isValueValid = options.some((option) => option[1] === newValue);
if (!isValueValid) {
if (this.sourceBlock_) {
console.warn(
'Cannot set the dropdown\'s value to an unavailable option.' +
' Block type: ' + this.sourceBlock_.type +
', Field name: ' + this.name + ', Value: ' + newValue);
"Cannot set the dropdown's value to an unavailable option." +
' Block type: ' +
this.sourceBlock_.type +
', Field name: ' +
this.name +
', Value: ' +
newValue
);
}
return null;
}
@@ -422,7 +445,7 @@ export class FieldDropdown extends Field<string> {
protected override doValueUpdate_(newValue: string) {
super.doValueUpdate_(newValue);
const options = this.getOptions(true);
for (let i = 0, option; option = options[i]; i++) {
for (let i = 0, option; (option = options[i]); i++) {
if (option[1] === this.value_) {
this.selectedOption = option;
}
@@ -481,7 +504,10 @@ export class FieldDropdown extends Field<string> {
}
this.imageElement!.style.display = '';
this.imageElement!.setAttributeNS(
dom.XLINK_NS, 'xlink:href', imageJson.src);
dom.XLINK_NS,
'xlink:href',
imageJson.src
);
this.imageElement!.setAttribute('height', String(imageJson.height));
this.imageElement!.setAttribute('width', String(imageJson.width));
@@ -491,21 +517,25 @@ export class FieldDropdown extends Field<string> {
// Height and width include the border rect.
const hasBorder = !!this.borderRect_;
const height = Math.max(
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + IMAGE_Y_PADDING);
const xPadding =
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + IMAGE_Y_PADDING
);
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
let arrowWidth = 0;
if (this.svgArrow) {
arrowWidth = this.positionSVGArrow(
imageWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
imageWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2
);
} else {
arrowWidth = dom.getFastTextWidth(
this.arrow as SVGTSpanElement,
this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
this.arrow as SVGTSpanElement,
this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY
);
}
this.size_.width = imageWidth + arrowWidth + xPadding * 2;
this.size_.height = height;
@@ -535,19 +565,24 @@ export class FieldDropdown extends Field<string> {
// Height and width include the border rect.
const hasBorder = !!this.borderRect_;
const height = Math.max(
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
this.getConstants()!.FIELD_TEXT_HEIGHT);
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
this.getConstants()!.FIELD_TEXT_HEIGHT
);
const textWidth = dom.getFastTextWidth(
this.getTextElement(), this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY);
const xPadding =
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
this.getTextElement(),
this.getConstants()!.FIELD_TEXT_FONTSIZE,
this.getConstants()!.FIELD_TEXT_FONTWEIGHT,
this.getConstants()!.FIELD_TEXT_FONTFAMILY
);
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
let arrowWidth = 0;
if (this.svgArrow) {
arrowWidth = this.positionSVGArrow(
textWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2);
textWidth + xPadding,
height / 2 - this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE / 2
);
}
this.size_.width = textWidth + arrowWidth + xPadding * 2;
this.size_.height = height;
@@ -571,13 +606,16 @@ export class FieldDropdown extends Field<string> {
throw new UnattachedFieldError();
}
const hasBorder = !!this.borderRect_;
const xPadding =
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
: 0;
const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE;
const arrowX = block.RTL ? xPadding : x + textPadding;
this.svgArrow.setAttribute(
'transform', 'translate(' + arrowX + ',' + y + ')');
'transform',
'translate(' + arrowX + ',' + y + ')'
);
return svgArrowSize + textPadding;
}
@@ -588,7 +626,7 @@ export class FieldDropdown extends Field<string> {
*
* @returns Selected option text.
*/
protected override getText_(): string|null {
protected override getText_(): string | null {
if (!this.selectedOption) {
return null;
}
@@ -610,9 +648,10 @@ export class FieldDropdown extends Field<string> {
static fromJson(options: FieldDropdownFromJsonConfig): FieldDropdown {
if (!options.options) {
throw new Error(
'options are required for the dropdown field. The ' +
'options are required for the dropdown field. The ' +
'options property must be assigned an array of ' +
'[humanReadableValue, languageNeutralValue] tuples.');
'[humanReadableValue, languageNeutralValue] tuples.'
);
}
// `this` might be a subclass of FieldDropdown if that class doesn't
// override the static fromJson method.
@@ -647,7 +686,7 @@ export type MenuGeneratorFunction = (this: FieldDropdown) => MenuOption[];
* Either an array of menu options or a function that generates an array of
* menu options for FieldDropdown or its descendants.
*/
export type MenuGenerator = MenuOption[]|MenuGeneratorFunction;
export type MenuGenerator = MenuOption[] | MenuGeneratorFunction;
/**
* Config options for the dropdown field.
@@ -691,8 +730,11 @@ const IMAGE_Y_PADDING: number = IMAGE_Y_OFFSET * 2;
* Factor out common words in statically defined options.
* Create prefix and/or suffix labels.
*/
function trimOptions(options: MenuOption[]):
{options: MenuOption[]; prefix?: string; suffix?: string;} {
function trimOptions(options: MenuOption[]): {
options: MenuOption[];
prefix?: string;
suffix?: string;
} {
let hasImages = false;
const trimmedOptions = options.map(([label, value]): MenuOption => {
if (typeof label === 'string') {
@@ -702,9 +744,10 @@ function trimOptions(options: MenuOption[]):
hasImages = true;
// Copy the image properties so they're not influenced by the original.
// NOTE: No need to deep copy since image properties are only 1 level deep.
const imageLabel = label.alt !== null ?
{...label, alt: parsing.replaceMessageReferences(label.alt)} :
{...label};
const imageLabel =
label.alt !== null
? {...label, alt: parsing.replaceMessageReferences(label.alt)}
: {...label};
return [imageLabel, value];
});
@@ -717,16 +760,20 @@ function trimOptions(options: MenuOption[]):
const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest);
const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest);
if ((!prefixLength && !suffixLength) ||
(shortest <= prefixLength + suffixLength)) {
if (
(!prefixLength && !suffixLength) ||
shortest <= prefixLength + suffixLength
) {
// One or more strings will entirely vanish if we proceed. Abort.
return {options: stringOptions};
}
const prefix =
prefixLength ? stringLabels[0].substring(0, prefixLength - 1) : undefined;
const suffix =
suffixLength ? stringLabels[0].substr(1 - suffixLength) : undefined;
const prefix = prefixLength
? stringLabels[0].substring(0, prefixLength - 1)
: undefined;
const suffix = suffixLength
? stringLabels[0].substr(1 - suffixLength)
: undefined;
return {
options: applyTrim(stringOptions, prefixLength, suffixLength),
prefix,
@@ -745,12 +792,13 @@ function trimOptions(options: MenuOption[]):
* @returns A new array with all of the option text trimmed.
*/
function applyTrim(
options: [string, string][], prefixLength: number,
suffixLength: number): MenuOption[] {
return options.map(
([text, value]) =>
[text.substring(prefixLength, text.length - suffixLength),
value,
options: [string, string][],
prefixLength: number,
suffixLength: number
): MenuOption[] {
return options.map(([text, value]) => [
text.substring(prefixLength, text.length - suffixLength),
value,
]);
}
@@ -773,23 +821,38 @@ function validateOptions(options: MenuOption[]) {
if (!Array.isArray(tuple)) {
foundError = true;
console.error(
'Invalid option[' + i + ']: Each FieldDropdown option must be an ' +
'array. Found: ',
tuple);
'Invalid option[' +
i +
']: Each FieldDropdown option must be an ' +
'array. Found: ',
tuple
);
} else if (typeof tuple[1] !== 'string') {
foundError = true;
console.error(
'Invalid option[' + i + ']: Each FieldDropdown option id must be ' +
'a string. Found ' + tuple[1] + ' in: ',
tuple);
'Invalid option[' +
i +
']: Each FieldDropdown option id must be ' +
'a string. Found ' +
tuple[1] +
' in: ',
tuple
);
} else if (
tuple[0] && typeof tuple[0] !== 'string' &&
typeof tuple[0].src !== 'string') {
tuple[0] &&
typeof tuple[0] !== 'string' &&
typeof tuple[0].src !== 'string'
) {
foundError = true;
console.error(
'Invalid option[' + i + ']: Each FieldDropdown option must have a ' +
'string label or image description. Found' + tuple[0] + ' in: ',
tuple);
'Invalid option[' +
i +
']: Each FieldDropdown option must have a ' +
'string label or image description. Found' +
tuple[0] +
' in: ',
tuple
);
}
}
if (foundError) {

View File

@@ -32,10 +32,10 @@ export class FieldImage extends Field<string> {
private readonly imageHeight: number;
/** The function to be called when this field is clicked. */
private clickHandler: ((p1: FieldImage) => void)|null = null;
private clickHandler: ((p1: FieldImage) => void) | null = null;
/** The rendered field's image element. */
private imageElement: SVGImageElement|null = null;
private imageElement: SVGImageElement | null = null;
/**
* Editable fields usually show some sort of UI indicating they are
@@ -73,22 +73,27 @@ export class FieldImage extends Field<string> {
* for a list of properties this parameter supports.
*/
constructor(
src: string|typeof Field.SKIP_SETUP, width: string|number,
height: string|number, alt?: string, onClick?: (p1: FieldImage) => void,
flipRtl?: boolean, config?: FieldImageConfig) {
src: string | typeof Field.SKIP_SETUP,
width: string | number,
height: string | number,
alt?: string,
onClick?: (p1: FieldImage) => void,
flipRtl?: boolean,
config?: FieldImageConfig
) {
super(Field.SKIP_SETUP);
const imageHeight = Number(parsing.replaceMessageReferences(height));
const imageWidth = Number(parsing.replaceMessageReferences(width));
if (isNaN(imageHeight) || isNaN(imageWidth)) {
throw Error(
'Height and width values of an image field must cast to' +
' numbers.');
'Height and width values of an image field must cast to' + ' numbers.'
);
}
if (imageHeight <= 0 || imageWidth <= 0) {
throw Error(
'Height and width values of an image field must be greater' +
' than 0.');
'Height and width values of an image field must be greater' + ' than 0.'
);
}
/** The size of the area rendered by the field. */
@@ -134,14 +139,19 @@ export class FieldImage extends Field<string> {
*/
override initView() {
this.imageElement = dom.createSvgElement(
Svg.IMAGE, {
'height': this.imageHeight + 'px',
'width': this.size_.width + 'px',
'alt': this.altText,
},
this.fieldGroup_);
Svg.IMAGE,
{
'height': this.imageHeight + 'px',
'width': this.size_.width + 'px',
'alt': this.altText,
},
this.fieldGroup_
);
this.imageElement.setAttributeNS(
dom.XLINK_NS, 'xlink:href', this.value_ as string);
dom.XLINK_NS,
'xlink:href',
this.value_ as string
);
if (this.clickHandler) {
this.imageElement.style.cursor = 'pointer';
@@ -157,7 +167,7 @@ export class FieldImage extends Field<string> {
* @param newValue The input value.
* @returns A string, or null if invalid.
*/
protected override doClassValidation_(newValue?: any): string|null {
protected override doClassValidation_(newValue?: any): string | null {
if (typeof newValue !== 'string') {
return null;
}
@@ -191,7 +201,7 @@ export class FieldImage extends Field<string> {
*
* @param alt New alt text.
*/
setAlt(alt: string|null) {
setAlt(alt: string | null) {
if (alt === this.altText) {
return;
}
@@ -217,7 +227,7 @@ export class FieldImage extends Field<string> {
* @param func The function that is called when the image is clicked, or null
* to remove.
*/
setOnClickHandler(func: ((p1: FieldImage) => void)|null) {
setOnClickHandler(func: ((p1: FieldImage) => void) | null) {
this.clickHandler = func;
}
@@ -228,7 +238,7 @@ export class FieldImage extends Field<string> {
*
* @returns The image alt text.
*/
protected override getText_(): string|null {
protected override getText_(): string | null {
return this.altText;
}
@@ -245,14 +255,21 @@ export class FieldImage extends Field<string> {
static fromJson(options: FieldImageFromJsonConfig): FieldImage {
if (!options.src || !options.width || !options.height) {
throw new Error(
'src, width, and height values for an image field are' +
'required. The width and height must be non-zero.');
'src, width, and height values for an image field are' +
'required. The width and height must be non-zero.'
);
}
// `this` might be a subclass of FieldImage if that class doesn't override
// the static fromJson method.
return new this(
options.src, options.width, options.height, undefined, undefined,
undefined, options);
options.src,
options.width,
options.height,
undefined,
undefined,
undefined,
options
);
}
}

View File

@@ -22,7 +22,12 @@ import * as dialog from './dialog.js';
import * as dom from './utils/dom.js';
import * as dropDownDiv from './dropdowndiv.js';
import * as eventUtils from './events/utils.js';
import {Field, FieldConfig, FieldValidator, UnattachedFieldError} from './field.js';
import {
Field,
FieldConfig,
FieldValidator,
UnattachedFieldError,
} from './field.js';
import {Msg} from './msg.js';
import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js';
@@ -36,7 +41,7 @@ import * as renderManagement from './render_management.js';
*
* @internal
*/
type InputTypes = string|number;
type InputTypes = string | number;
/**
* Abstract class for an editable input field.
@@ -44,7 +49,9 @@ type InputTypes = string|number;
* @typeParam T - The value stored on the field.
* @internal
*/
export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
export abstract class FieldInput<T extends InputTypes> extends Field<
string | T
> {
/**
* Pixel size of input border radius.
* Should match blocklyText's border-radius in CSS.
@@ -55,7 +62,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
protected spellcheck_ = true;
/** The HTML input element. */
protected htmlInput_: HTMLInputElement|null = null;
protected htmlInput_: HTMLInputElement | null = null;
/** True if the field's value is currently being edited via the UI. */
protected isBeingEdited_ = false;
@@ -66,19 +73,19 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
protected isTextValid_ = false;
/** Key down event data. */
private onKeyDownWrapper_: browserEvents.Data|null = null;
private onKeyDownWrapper_: browserEvents.Data | null = null;
/** Key input event data. */
private onKeyInputWrapper_: browserEvents.Data|null = null;
private onKeyInputWrapper_: browserEvents.Data | null = null;
/**
* Whether the field should consider the whole parent block to be its click
* target.
*/
fullBlockClickTarget_: boolean|null = false;
fullBlockClickTarget_: boolean | null = false;
/** The workspace that this field belongs to. */
protected workspace_: WorkspaceSvg|null = null;
protected workspace_: WorkspaceSvg | null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -104,8 +111,10 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP,
validator?: FieldInputValidator<T>|null, config?: FieldInputConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldInputValidator<T> | null,
config?: FieldInputConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -137,7 +146,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
let nFields = 0;
let nConnections = 0;
// Count the number of fields, excluding text fields
for (let i = 0, input; input = block.inputList[i]; i++) {
for (let i = 0, input; (input = block.inputList[i]); i++) {
for (let j = 0; input.fieldRow[j]; j++) {
nFields++;
}
@@ -148,7 +157,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// The special case is when this is the only non-label field on the block
// and it has an output but no inputs.
this.fullBlockClickTarget_ =
nFields <= 1 && block.outputConnection && !nConnections;
nFields <= 1 && block.outputConnection && !nConnections;
} else {
this.fullBlockClickTarget_ = false;
}
@@ -178,9 +187,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// Revert value when the text becomes invalid.
this.value_ = this.htmlInput_!.getAttribute('data-untyped-default-value');
if (this.sourceBlock_ && eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.sourceBlock_, 'field', this.name || null, oldValue,
this.value_));
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
this.sourceBlock_,
'field',
this.name || null,
oldValue,
this.value_
)
);
}
}
}
@@ -193,7 +208,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
* @param newValue The value to be saved. The default validator guarantees
* that this is a string.
*/
protected override doValueUpdate_(newValue: string|T) {
protected override doValueUpdate_(newValue: string | T) {
this.isDirty_ = true;
this.isTextValid_ = true;
this.value_ = newValue;
@@ -211,7 +226,9 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
this.borderRect_.setAttribute('stroke', source.style.colourTertiary);
} else {
source.pathObject.svgPath.setAttribute(
'fill', this.getConstants()!.FIELD_BORDER_RECT_COLOUR);
'fill',
this.getConstants()!.FIELD_BORDER_RECT_COLOUR
);
}
}
@@ -250,7 +267,9 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// AnyDuringMigration because: Argument of type 'boolean' is not
// assignable to parameter of type 'string'.
this.htmlInput_.setAttribute(
'spellcheck', this.spellcheck_ as AnyDuringMigration);
'spellcheck',
this.spellcheck_ as AnyDuringMigration
);
}
}
@@ -267,8 +286,11 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
*/
protected override showEditor_(_e?: Event, quietInput = false) {
this.workspace_ = (this.sourceBlock_ as BlockSvg).workspace;
if (!quietInput && this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)) {
if (
!quietInput &&
this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)
) {
this.showPromptEditor_();
} else {
this.showInlineEditor_(quietInput);
@@ -282,12 +304,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
*/
private showPromptEditor_() {
dialog.prompt(
Msg['CHANGE_VALUE_TITLE'], this.getText(), (text: string|null) => {
// Text is null if user pressed cancel button.
if (text !== null) {
this.setValue(this.getValueFromEditorText_(text));
}
});
Msg['CHANGE_VALUE_TITLE'],
this.getText(),
(text: string | null) => {
// Text is null if user pressed cancel button.
if (text !== null) {
this.setValue(this.getValueFromEditorText_(text));
}
}
);
}
/**
@@ -329,12 +354,14 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
if (!clickTarget) throw new Error('A click target has not been set.');
dom.addClass(clickTarget, 'editing');
const htmlInput = (document.createElement('input'));
const htmlInput = document.createElement('input');
htmlInput.className = 'blocklyHtmlInput';
// AnyDuringMigration because: Argument of type 'boolean' is not assignable
// to parameter of type 'string'.
htmlInput.setAttribute(
'spellcheck', this.spellcheck_ as AnyDuringMigration);
'spellcheck',
this.spellcheck_ as AnyDuringMigration
);
const scale = this.workspace_!.getScale();
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
div!.style.fontSize = fontSize;
@@ -347,15 +374,15 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
// Override border radius.
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
// Pull stroke colour from the existing shadow block
const strokeColour = block.getParent() ?
(block.getParent() as BlockSvg).style.colourTertiary :
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
const strokeColour = block.getParent()
? (block.getParent() as BlockSvg).style.colourTertiary
: (this.sourceBlock_ as BlockSvg).style.colourTertiary;
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
div!.style.borderRadius = borderRadius;
div!.style.transition = 'box-shadow 0.25s ease 0s';
if (this.getConstants()!.FIELD_TEXTINPUT_BOX_SHADOW) {
div!.style.boxShadow =
'rgba(255, 255, 255, 0.3) 0 0 0 ' + (4 * scale) + 'px';
'rgba(255, 255, 255, 0.3) 0 0 0 ' + 4 * scale + 'px';
}
}
htmlInput.style.borderRadius = borderRadius;
@@ -417,10 +444,18 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
protected bindInputEvents_(htmlInput: HTMLElement) {
// Trap Enter without IME and Esc to hide.
this.onKeyDownWrapper_ = browserEvents.conditionalBind(
htmlInput, 'keydown', this, this.onHtmlInputKeyDown_);
htmlInput,
'keydown',
this,
this.onHtmlInputKeyDown_
);
// Resize after every input change.
this.onKeyInputWrapper_ = browserEvents.conditionalBind(
htmlInput, 'input', this, this.onHtmlInputChange_);
htmlInput,
'input',
this,
this.onHtmlInputChange_
);
}
/** Unbind handlers for user input and workspace size changes. */
@@ -446,7 +481,8 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
dropDownDiv.hideWithoutAnimation();
} else if (e.key === 'Escape') {
this.setValue(
this.htmlInput_!.getAttribute('data-untyped-default-value'));
this.htmlInput_!.getAttribute('data-untyped-default-value')
);
WidgetDiv.hide();
dropDownDiv.hideWithoutAnimation();
} else if (e.key === 'Tab') {
@@ -525,8 +561,10 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
if (!(block instanceof BlockSvg)) return false;
bumpObjects.bumpIntoBounds(
this.workspace_!,
this.workspace_!.getMetricsManager().getViewMetrics(true), block);
this.workspace_!,
this.workspace_!.getMetricsManager().getViewMetrics(true),
block
);
this.resizeEditor_();
@@ -550,7 +588,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<string|T> {
*
* @returns The HTML value if we're editing, otherwise null.
*/
protected override getText_(): string|null {
protected override getText_(): string | null {
if (this.isBeingEdited_ && this.htmlInput_) {
// We are currently editing, return the HTML input value instead.
return this.htmlInput_.value;
@@ -611,5 +649,6 @@ export interface FieldInputConfig extends FieldConfig {
* - `undefined` to set `newValue` as is.
* @internal
*/
export type FieldInputValidator<T extends InputTypes> =
FieldValidator<string|T>;
export type FieldInputValidator<T extends InputTypes> = FieldValidator<
string | T
>;

View File

@@ -23,7 +23,7 @@ import * as parsing from './utils/parsing.js';
*/
export class FieldLabel extends Field<string> {
/** The HTML class name to use for this field. */
private class: string|null = null;
private class: string | null = null;
/**
* Editable fields usually show some sort of UI indicating they are
@@ -47,8 +47,10 @@ export class FieldLabel extends Field<string> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP, textClass?: string,
config?: FieldLabelConfig) {
value?: string | typeof Field.SKIP_SETUP,
textClass?: string,
config?: FieldLabelConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -83,8 +85,9 @@ export class FieldLabel extends Field<string> {
* @param newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): string | null {
if (newValue === null || newValue === undefined) {
return null;
}
@@ -96,7 +99,7 @@ export class FieldLabel extends Field<string> {
*
* @param cssClass The new CSS class name, or null to remove.
*/
setClass(cssClass: string|null) {
setClass(cssClass: string | null) {
if (this.textElement_) {
if (this.class) {
dom.removeClass(this.textElement_, this.class);
@@ -139,7 +142,6 @@ export interface FieldLabelConfig extends FieldConfig {
}
// clang-format on
/**
* fromJson config options for the label field.
*/

View File

@@ -14,7 +14,11 @@
import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.FieldLabelSerializable');
import {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig} from './field_label.js';
import {
FieldLabel,
FieldLabelConfig,
FieldLabelFromJsonConfig,
} from './field_label.js';
import * as fieldRegistry from './field_registry.js';
import * as parsing from './utils/parsing.js';
@@ -57,8 +61,9 @@ export class FieldLabelSerializable extends FieldLabel {
* @nocollapse
* @internal
*/
static override fromJson(options: FieldLabelFromJsonConfig):
FieldLabelSerializable {
static override fromJson(
options: FieldLabelFromJsonConfig
): FieldLabelSerializable {
const text = parsing.replaceMessageReferences(options.text);
// `this` might be a subclass of FieldLabelSerializable if that class
// doesn't override the static fromJson method.

View File

@@ -15,7 +15,11 @@ goog.declareModuleId('Blockly.FieldMultilineInput');
import * as Css from './css.js';
import {Field, UnattachedFieldError} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {FieldTextInput, FieldTextInputConfig, FieldTextInputValidator} from './field_textinput.js';
import {
FieldTextInput,
FieldTextInputConfig,
FieldTextInputValidator,
} from './field_textinput.js';
import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js';
@@ -31,7 +35,7 @@ export class FieldMultilineInput extends FieldTextInput {
* The SVG group element that will contain a text element for each text row
* when initialized.
*/
textGroup: SVGGElement|null = null;
textGroup: SVGGElement | null = null;
/**
* Defines the maximum number of lines of field.
@@ -58,9 +62,10 @@ export class FieldMultilineInput extends FieldTextInput {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP,
validator?: FieldMultilineInputValidator,
config?: FieldMultilineInputConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldMultilineInputValidator,
config?: FieldMultilineInputConfig
) {
super(Field.SKIP_SETUP);
if (value === Field.SKIP_SETUP) return;
@@ -96,8 +101,10 @@ export class FieldMultilineInput extends FieldTextInput {
// needed so the plain-text representation of the XML produced by
// `Blockly.Xml.domToText` will appear on a single line (this is a
// limitation of the plain-text format).
fieldElement.textContent =
(this.getValue() as string).replace(/\n/g, '&#10;');
fieldElement.textContent = (this.getValue() as string).replace(
/\n/g,
'&#10;'
);
return fieldElement;
}
@@ -151,10 +158,12 @@ export class FieldMultilineInput extends FieldTextInput {
override initView() {
this.createBorderRect_();
this.textGroup = dom.createSvgElement(
Svg.G, {
'class': 'blocklyEditableText',
},
this.fieldGroup_);
Svg.G,
{
'class': 'blocklyEditableText',
},
this.fieldGroup_
);
}
/**
@@ -175,8 +184,9 @@ export class FieldMultilineInput extends FieldTextInput {
}
const lines = textLines.split('\n');
textLines = '';
const displayLinesNumber =
this.isOverflowedY_ ? this.maxLines_ : lines.length;
const displayLinesNumber = this.isOverflowedY_
? this.maxLines_
: lines.length;
for (let i = 0; i < displayLinesNumber; i++) {
let text = lines[i];
if (text.length > this.maxDisplayLength) {
@@ -226,7 +236,7 @@ export class FieldMultilineInput extends FieldTextInput {
// Remove all text group children.
let currentChild;
const textGroup = this.textGroup;
while (currentChild = textGroup!.firstChild) {
while ((currentChild = textGroup!.firstChild)) {
textGroup!.removeChild(currentChild);
}
@@ -234,16 +244,19 @@ export class FieldMultilineInput extends FieldTextInput {
const lines = this.getDisplayText_().split('\n');
let y = 0;
for (let i = 0; i < lines.length; i++) {
const lineHeight = this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
const lineHeight =
this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
const span = dom.createSvgElement(
Svg.TEXT, {
'class': 'blocklyText blocklyMultilineText',
'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING,
'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING,
'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
},
textGroup);
Svg.TEXT,
{
'class': 'blocklyText blocklyMultilineText',
'x': this.getConstants()!.FIELD_BORDER_RECT_X_PADDING,
'y': y + this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING,
'dy': this.getConstants()!.FIELD_TEXT_BASELINE,
},
textGroup
);
span.appendChild(document.createTextNode(lines[i]));
y += lineHeight;
}
@@ -289,13 +302,18 @@ export class FieldMultilineInput extends FieldTextInput {
let totalHeight = 0;
for (let i = 0; i < nodes.length; i++) {
const tspan = nodes[i] as SVGTextElement;
const textWidth =
dom.getFastTextWidth(tspan, fontSize, fontWeight, fontFamily);
const textWidth = dom.getFastTextWidth(
tspan,
fontSize,
fontWeight,
fontFamily
);
if (textWidth > totalWidth) {
totalWidth = textWidth;
}
totalHeight += this.getConstants()!.FIELD_TEXT_HEIGHT +
(i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0);
totalHeight +=
this.getConstants()!.FIELD_TEXT_HEIGHT +
(i > 0 ? this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING : 0);
}
if (this.isBeingEdited_) {
// The default width is based on the longest line in the display text,
@@ -304,24 +322,31 @@ export class FieldMultilineInput extends FieldTextInput {
// Otherwise we would get wrong editor width when there are more
// lines than this.maxLines_.
const actualEditorLines = String(this.value_).split('\n');
const dummyTextElement = dom.createSvgElement(
Svg.TEXT, {'class': 'blocklyText blocklyMultilineText'});
const dummyTextElement = dom.createSvgElement(Svg.TEXT, {
'class': 'blocklyText blocklyMultilineText',
});
for (let i = 0; i < actualEditorLines.length; i++) {
if (actualEditorLines[i].length > this.maxDisplayLength) {
actualEditorLines[i] =
actualEditorLines[i].substring(0, this.maxDisplayLength);
actualEditorLines[i] = actualEditorLines[i].substring(
0,
this.maxDisplayLength
);
}
dummyTextElement.textContent = actualEditorLines[i];
const lineWidth = dom.getFastTextWidth(
dummyTextElement, fontSize, fontWeight, fontFamily);
dummyTextElement,
fontSize,
fontWeight,
fontFamily
);
if (lineWidth > totalWidth) {
totalWidth = lineWidth;
}
}
const scrollbarWidth =
this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth;
this.htmlInput_!.offsetWidth - this.htmlInput_!.clientWidth;
totalWidth += scrollbarWidth;
}
if (this.borderRect_) {
@@ -360,7 +385,7 @@ export class FieldMultilineInput extends FieldTextInput {
const div = WidgetDiv.getDiv();
const scale = this.workspace_!.getScale();
const htmlInput = (document.createElement('textarea'));
const htmlInput = document.createElement('textarea');
htmlInput.className = 'blocklyHtmlInput blocklyHtmlTextAreaInput';
htmlInput.setAttribute('spellcheck', String(this.spellcheck_));
const fontSize = this.getConstants()!.FIELD_TEXT_FONTSIZE * scale + 'pt';
@@ -370,11 +395,12 @@ export class FieldMultilineInput extends FieldTextInput {
htmlInput.style.borderRadius = borderRadius;
const paddingX = this.getConstants()!.FIELD_BORDER_RECT_X_PADDING * scale;
const paddingY =
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale / 2;
htmlInput.style.padding = paddingY + 'px ' + paddingX + 'px ' + paddingY +
'px ' + paddingX + 'px';
const lineHeight = this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
(this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING * scale) / 2;
htmlInput.style.padding =
paddingY + 'px ' + paddingX + 'px ' + paddingY + 'px ' + paddingX + 'px';
const lineHeight =
this.getConstants()!.FIELD_TEXT_HEIGHT +
this.getConstants()!.FIELD_BORDER_RECT_Y_PADDING;
htmlInput.style.lineHeight = lineHeight * scale + 'px';
div!.appendChild(htmlInput);
@@ -401,8 +427,11 @@ export class FieldMultilineInput extends FieldTextInput {
* scrolling functionality is enabled.
*/
setMaxLines(maxLines: number) {
if (typeof maxLines === 'number' && maxLines > 0 &&
maxLines !== this.maxLines_) {
if (
typeof maxLines === 'number' &&
maxLines > 0 &&
maxLines !== this.maxLines_
) {
this.maxLines_ = maxLines;
this.forceRerender();
}
@@ -438,8 +467,9 @@ export class FieldMultilineInput extends FieldTextInput {
* @nocollapse
* @internal
*/
static override fromJson(options: FieldMultilineInputFromJsonConfig):
FieldMultilineInput {
static override fromJson(
options: FieldMultilineInputFromJsonConfig
): FieldMultilineInput {
const text = parsing.replaceMessageReferences(options.text);
// `this` might be a subclass of FieldMultilineInput if that class doesn't
// override the static fromJson method.
@@ -449,7 +479,6 @@ export class FieldMultilineInput extends FieldTextInput {
fieldRegistry.register('field_multilinetext', FieldMultilineInput);
/**
* CSS for multiline field.
*/
@@ -477,8 +506,8 @@ export interface FieldMultilineInputConfig extends FieldTextInputConfig {
/**
* fromJson config options for the multiline input field.
*/
export interface FieldMultilineInputFromJsonConfig extends
FieldMultilineInputConfig {
export interface FieldMultilineInputFromJsonConfig
extends FieldMultilineInputConfig {
text?: string;
}

View File

@@ -14,7 +14,11 @@ goog.declareModuleId('Blockly.FieldNumber');
import {Field} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import {
FieldInput,
FieldInputConfig,
FieldInputValidator,
} from './field_input.js';
import * as aria from './utils/aria.js';
/**
@@ -34,7 +38,7 @@ export class FieldNumber extends FieldInput<number> {
* The number of decimal places to allow, or null to allow any number of
* decimal digits.
*/
private decimalPlaces: number|null = null;
private decimalPlaces: number | null = null;
/** Don't spellcheck numbers. Our validator does a better job. */
protected override spellcheck_ = false;
@@ -59,9 +63,13 @@ export class FieldNumber extends FieldInput<number> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|number|typeof Field.SKIP_SETUP, min?: string|number|null,
max?: string|number|null, precision?: string|number|null,
validator?: FieldNumberValidator|null, config?: FieldNumberConfig) {
value?: string | number | typeof Field.SKIP_SETUP,
min?: string | number | null,
max?: string | number | null,
precision?: string | number | null,
validator?: FieldNumberValidator | null,
config?: FieldNumberConfig
) {
// Pass SENTINEL so that we can define properties before value validation.
super(Field.SKIP_SETUP);
@@ -103,8 +111,10 @@ export class FieldNumber extends FieldInput<number> {
* @param precision Precision for value.
*/
setConstraints(
min: number|string|undefined|null, max: number|string|undefined|null,
precision: number|string|undefined|null) {
min: number | string | undefined | null,
max: number | string | undefined | null,
precision: number | string | undefined | null
) {
this.setMinInternal(min);
this.setMaxInternal(max);
this.setPrecisionInternal(precision);
@@ -117,7 +127,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param min Minimum value.
*/
setMin(min: number|string|undefined|null) {
setMin(min: number | string | undefined | null) {
this.setMinInternal(min);
this.setValue(this.getValue());
}
@@ -128,7 +138,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param min Minimum value.
*/
private setMinInternal(min: number|string|undefined|null) {
private setMinInternal(min: number | string | undefined | null) {
if (min == null) {
this.min_ = -Infinity;
} else {
@@ -155,7 +165,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param max Maximum value.
*/
setMax(max: number|string|undefined|null) {
setMax(max: number | string | undefined | null) {
this.setMaxInternal(max);
this.setValue(this.getValue());
}
@@ -166,7 +176,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param max Maximum value.
*/
private setMaxInternal(max: number|string|undefined|null) {
private setMaxInternal(max: number | string | undefined | null) {
if (max == null) {
this.max_ = Infinity;
} else {
@@ -193,7 +203,7 @@ export class FieldNumber extends FieldInput<number> {
*
* @param precision The number to which the field's value is rounded.
*/
setPrecision(precision: number|string|undefined|null) {
setPrecision(precision: number | string | undefined | null) {
this.setPrecisionInternal(precision);
this.setValue(this.getValue());
}
@@ -204,14 +214,15 @@ export class FieldNumber extends FieldInput<number> {
*
* @param precision The number to which the field's value is rounded.
*/
private setPrecisionInternal(precision: number|string|undefined|null) {
private setPrecisionInternal(precision: number | string | undefined | null) {
this.precision_ = Number(precision) || 0;
let precisionString = String(this.precision_);
if (precisionString.indexOf('e') !== -1) {
// String() is fast. But it turns .0000001 into '1e-7'.
// Use the much slower toLocaleString to access all the digits.
precisionString =
this.precision_.toLocaleString('en-US', {maximumFractionDigits: 20});
precisionString = this.precision_.toLocaleString('en-US', {
maximumFractionDigits: 20,
});
}
const decimalIndex = precisionString.indexOf('.');
if (decimalIndex === -1) {
@@ -241,8 +252,9 @@ export class FieldNumber extends FieldInput<number> {
* @param newValue The input value.
* @returns A valid number, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): number
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): number | null {
if (newValue === null) {
return null;
}
@@ -251,7 +263,7 @@ export class FieldNumber extends FieldInput<number> {
newValue = `${newValue}`;
// TODO: Handle cases like 'ten', '1.203,14', etc.
// 'O' is sometimes mistaken for '0' by inexperienced users.
newValue = newValue.replace(/O/ig, '0');
newValue = newValue.replace(/O/gi, '0');
// Strip out thousands separators.
newValue = newValue.replace(/,/g, '');
// Ignore case of 'Infinity'.
@@ -309,7 +321,13 @@ export class FieldNumber extends FieldInput<number> {
// `this` might be a subclass of FieldNumber if that class doesn't override
// the static fromJson method.
return new this(
options.value, undefined, undefined, undefined, undefined, options);
options.value,
undefined,
undefined,
undefined,
undefined,
options
);
}
}

View File

@@ -50,7 +50,7 @@ export function unregister(type: string) {
* given type name
* @internal
*/
export function fromJson<T>(options: RegistryOptions): Field<T>|null {
export function fromJson<T>(options: RegistryOptions): Field<T> | null {
return TEST_ONLY.fromJsonInternal(options);
}
@@ -59,14 +59,16 @@ export function fromJson<T>(options: RegistryOptions): Field<T>|null {
*
* @param options
*/
function fromJsonInternal<T>(options: RegistryOptions): Field<T>|null {
function fromJsonInternal<T>(options: RegistryOptions): Field<T> | null {
const fieldObject = registry.getObject(registry.Type.FIELD, options.type);
if (!fieldObject) {
console.warn(
'Blockly could not create a field of type ' + options['type'] +
'Blockly could not create a field of type ' +
options['type'] +
'. The field is probably not being registered. This could be because' +
' the file is not loaded, the field does not register itself (Issue' +
' #1584), or the registration is not being reached.');
' #1584), or the registration is not being reached.'
);
return null;
} else if (typeof (fieldObject as any).fromJson !== 'function') {
throw new TypeError('returned Field was not a IRegistrableField');

View File

@@ -16,7 +16,11 @@ goog.declareModuleId('Blockly.FieldTextInput');
import './events/events_block_change.js';
import {Field} from './field.js';
import {FieldInput, FieldInputConfig, FieldInputValidator} from './field_input.js';
import {
FieldInput,
FieldInputConfig,
FieldInputValidator,
} from './field_input.js';
import * as fieldRegistry from './field_registry.js';
import * as parsing from './utils/parsing.js';
@@ -39,8 +43,10 @@ export class FieldTextInput extends FieldInput<string> {
* for a list of properties this parameter supports.
*/
constructor(
value?: string|typeof Field.SKIP_SETUP,
validator?: FieldTextInputValidator|null, config?: FieldTextInputConfig) {
value?: string | typeof Field.SKIP_SETUP,
validator?: FieldTextInputValidator | null,
config?: FieldTextInputConfig
) {
super(value, validator, config);
}
@@ -50,8 +56,9 @@ export class FieldTextInput extends FieldInput<string> {
* @param newValue The input value.
* @returns A valid string, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): string | null {
if (newValue === undefined) {
return null;
}

View File

@@ -17,7 +17,12 @@ import './events/events_block_change.js';
import type {Block} from './block.js';
import {Field, FieldConfig, UnattachedFieldError} from './field.js';
import {FieldDropdown, FieldDropdownValidator, MenuGenerator, MenuOption} from './field_dropdown.js';
import {
FieldDropdown,
FieldDropdownValidator,
MenuGenerator,
MenuOption,
} from './field_dropdown.js';
import * as fieldRegistry from './field_registry.js';
import * as internalConstants from './internal_constants.js';
import type {Menu} from './menu.js';
@@ -33,7 +38,7 @@ import * as Xml from './xml.js';
* Class for a variable's dropdown field.
*/
export class FieldVariable extends FieldDropdown {
protected override menuGenerator_: MenuGenerator|undefined;
protected override menuGenerator_: MenuGenerator | undefined;
defaultVariableName: string;
/** The type of the default variable for this field. */
@@ -43,11 +48,11 @@ export class FieldVariable extends FieldDropdown {
* All of the types of variables that will be available in this field's
* dropdown.
*/
variableTypes: string[]|null = [];
variableTypes: string[] | null = [];
protected override size_: Size;
/** The variable model associated with this field. */
private variable: VariableModel|null = null;
private variable: VariableModel | null = null;
/**
* Serializable fields are saved by the serializer, non-serializable fields
@@ -75,9 +80,12 @@ export class FieldVariable extends FieldDropdown {
* for a list of properties this parameter supports.
*/
constructor(
varName: string|null|typeof Field.SKIP_SETUP,
validator?: FieldVariableValidator, variableTypes?: string[],
defaultType?: string, config?: FieldVariableConfig) {
varName: string | null | typeof Field.SKIP_SETUP,
validator?: FieldVariableValidator,
variableTypes?: string[],
defaultType?: string,
config?: FieldVariableConfig
) {
super(Field.SKIP_SETUP);
/**
@@ -131,10 +139,14 @@ export class FieldVariable extends FieldDropdown {
throw new UnattachedFieldError();
}
if (this.variable) {
return; // Initialization already happened.
return; // Initialization already happened.
}
const variable = Variables.getOrCreateVariablePackage(
block.workspace, null, this.defaultVariableName, this.defaultType);
block.workspace,
null,
this.defaultVariableName,
this.defaultType
);
// Don't call setValue because we don't want to cause a rerender.
this.doValueUpdate_(variable.getId());
}
@@ -144,9 +156,11 @@ export class FieldVariable extends FieldDropdown {
if (!block) {
throw new UnattachedFieldError();
}
return super.shouldAddBorderRect_() &&
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
block.type !== 'variables_get');
return (
super.shouldAddBorderRect_() &&
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
block.type !== 'variables_get')
);
}
/**
@@ -164,21 +178,32 @@ export class FieldVariable extends FieldDropdown {
const variableName = fieldElement.textContent;
// 'variabletype' should be lowercase, but until July 2019 it was sometimes
// recorded as 'variableType'. Thus we need to check for both.
const variableType = fieldElement.getAttribute('variabletype') ||
fieldElement.getAttribute('variableType') || '';
const variableType =
fieldElement.getAttribute('variabletype') ||
fieldElement.getAttribute('variableType') ||
'';
// AnyDuringMigration because: Argument of type 'string | null' is not
// assignable to parameter of type 'string | undefined'.
const variable = Variables.getOrCreateVariablePackage(
block.workspace, id, variableName as AnyDuringMigration, variableType);
block.workspace,
id,
variableName as AnyDuringMigration,
variableType
);
// This should never happen :)
if (variableType !== null && variableType !== variable.type) {
throw Error(
'Serialized variable type with id \'' + variable.getId() +
'\' had type ' + variable.type + ', and ' +
"Serialized variable type with id '" +
variable.getId() +
"' had type " +
variable.type +
', and ' +
'does not match variable field that references it: ' +
Xml.domToText(fieldElement) + '.');
Xml.domToText(fieldElement) +
'.'
);
}
this.setValue(variable.getId());
@@ -243,8 +268,11 @@ export class FieldVariable extends FieldDropdown {
}
// This is necessary so that blocks in the flyout can have custom var names.
const variable = Variables.getOrCreateVariablePackage(
block.workspace, state['id'] || null, state['name'],
state['type'] || '');
block.workspace,
state['id'] || null,
state['name'],
state['type'] || ''
);
this.setValue(variable.getId());
}
@@ -265,7 +293,7 @@ export class FieldVariable extends FieldDropdown {
*
* @returns Current variable's ID.
*/
override getValue(): string|null {
override getValue(): string | null {
return this.variable ? this.variable.getId() : null;
}
@@ -287,7 +315,7 @@ export class FieldVariable extends FieldDropdown {
* @returns The selected variable, or null if none was selected.
* @internal
*/
getVariable(): VariableModel|null {
getVariable(): VariableModel | null {
return this.variable;
}
@@ -299,7 +327,7 @@ export class FieldVariable extends FieldDropdown {
*
* @returns Validation function, or null.
*/
override getValidator(): FieldVariableValidator|null {
override getValidator(): FieldVariableValidator | null {
// Validators shouldn't operate on the initial setValue call.
// Normally this is achieved by calling setValidator after setValue, but
// this is not a possibility with variable fields.
@@ -315,8 +343,9 @@ export class FieldVariable extends FieldDropdown {
* @param newValue The ID of the new variable to set.
* @returns The validated ID, or null if invalid.
*/
protected override doClassValidation_(newValue?: AnyDuringMigration): string
|null {
protected override doClassValidation_(
newValue?: AnyDuringMigration
): string | null {
if (newValue === null) {
return null;
}
@@ -328,15 +357,14 @@ export class FieldVariable extends FieldDropdown {
const variable = Variables.getVariable(block.workspace, newId);
if (!variable) {
console.warn(
'Variable id doesn\'t point to a real variable! ' +
'ID was ' + newId);
"Variable id doesn't point to a real variable! " + 'ID was ' + newId
);
return null;
}
// Type Checks.
const type = variable.type;
if (!this.typeIsAllowed(type)) {
console.warn(
'Variable type doesn\'t match this field! Type was ' + type);
console.warn("Variable type doesn't match this field! Type was " + type);
return null;
}
return newId;
@@ -368,7 +396,7 @@ export class FieldVariable extends FieldDropdown {
private typeIsAllowed(type: string): boolean {
const typeList = this.getVariableTypes();
if (!typeList) {
return true; // If it's null, all types are valid.
return true; // If it's null, all types are valid.
}
for (let i = 0; i < typeList.length; i++) {
if (type === typeList[i]) {
@@ -397,7 +425,8 @@ export class FieldVariable extends FieldDropdown {
// Throw an error if variableTypes is an empty list.
const name = this.getText();
throw Error(
'\'variableTypes\' of field variable ' + name + ' was an empty list');
"'variableTypes' of field variable " + name + ' was an empty list'
);
}
return variableTypes;
}
@@ -412,7 +441,7 @@ export class FieldVariable extends FieldDropdown {
* @param defaultType The type of the variable to create if this field's
* value is not explicitly set. Defaults to ''.
*/
private setTypes(variableTypes: string[]|null = null, defaultType = '') {
private setTypes(variableTypes: string[] | null = null, defaultType = '') {
// If you expected that the default type would be the same as the only entry
// in the variable types array, tell the Blockly team by commenting on
// #1499.
@@ -428,13 +457,17 @@ export class FieldVariable extends FieldDropdown {
}
if (!isInArray) {
throw Error(
'Invalid default type \'' + defaultType + '\' in ' +
'the definition of a FieldVariable');
"Invalid default type '" +
defaultType +
"' in " +
'the definition of a FieldVariable'
);
}
} else if (variableTypes !== null) {
throw Error(
'\'variableTypes\' was not an array in the definition of ' +
'a FieldVariable');
"'variableTypes' was not an array in the definition of " +
'a FieldVariable'
);
}
// Only update the field once all checks pass.
this.defaultType = defaultType;
@@ -467,7 +500,9 @@ export class FieldVariable extends FieldDropdown {
if (id === internalConstants.RENAME_VARIABLE_ID) {
// Rename variable.
Variables.renameVariable(
this.sourceBlock_.workspace, this.variable as VariableModel);
this.sourceBlock_.workspace,
this.variable as VariableModel
);
return;
} else if (id === internalConstants.DELETE_VARIABLE_ID) {
// Delete variable.
@@ -500,8 +535,9 @@ export class FieldVariable extends FieldDropdown {
* @nocollapse
* @internal
*/
static override fromJson(options: FieldVariableFromJsonConfig):
FieldVariable {
static override fromJson(
options: FieldVariableFromJsonConfig
): FieldVariable {
const varName = parsing.replaceMessageReferences(options.variable);
// `this` might be a subclass of FieldVariable if that class doesn't
// override the static fromJson method.
@@ -517,8 +553,9 @@ export class FieldVariable extends FieldDropdown {
static dropdownCreate(this: FieldVariable): MenuOption[] {
if (!this.variable) {
throw Error(
'Tried to call dropdownCreate on a variable field with no' +
' variable selected.');
'Tried to call dropdownCreate on a variable field with no' +
' variable selected.'
);
}
const name = this.getText();
let variableModelList: VariableModel[] = [];
@@ -529,7 +566,7 @@ export class FieldVariable extends FieldDropdown {
for (let i = 0; i < variableTypes.length; i++) {
const variableType = variableTypes[i];
const variables =
this.sourceBlock_.workspace.getVariablesOfType(variableType);
this.sourceBlock_.workspace.getVariablesOfType(variableType);
variableModelList = variableModelList.concat(variables);
}
}
@@ -540,8 +577,10 @@ export class FieldVariable extends FieldDropdown {
// Set the UUID as the internal representation of the variable.
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
}
options.push(
[Msg['RENAME_VARIABLE'], internalConstants.RENAME_VARIABLE_ID]);
options.push([
Msg['RENAME_VARIABLE'],
internalConstants.RENAME_VARIABLE_ID,
]);
if (Msg['DELETE_VARIABLE']) {
options.push([
Msg['DELETE_VARIABLE'].replace('%1', name),

View File

@@ -36,7 +36,6 @@ import {WorkspaceSvg} from './workspace_svg.js';
import * as utilsXml from './utils/xml.js';
import * as Xml from './xml.js';
enum FlyoutItemType {
BLOCK = 'block',
BUTTON = 'button',
@@ -69,7 +68,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* between 0 and 1 specifying the degree of scrolling and a
* similar x property.
*/
protected abstract setMetrics_(xyRatio: {x?: number, y?: number}): void;
protected abstract setMetrics_(xyRatio: {x?: number; y?: number}): void;
/**
* Lay out the blocks in the flyout.
@@ -137,14 +136,14 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* Function that will be registered as a change listener on the workspace
* to reflow when blocks in the flyout workspace change.
*/
private reflowWrapper: Function|null = null;
private reflowWrapper: Function | null = null;
/**
* Function that disables blocks in the flyout based on max block counts
* allowed in the target workspace. Registered as a change listener on the
* target workspace.
*/
private filterWrapper: Function|null = null;
private filterWrapper: Function | null = null;
/**
* List of background mats that lurk behind each block to catch clicks
@@ -247,12 +246,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* The path around the background of the flyout, which will be filled with a
* background colour.
*/
protected svgBackground_: SVGPathElement|null = null;
protected svgBackground_: SVGPathElement | null = null;
/**
* The root SVG group for the button or label.
*/
protected svgGroup_: SVGGElement|null = null;
protected svgGroup_: SVGGElement | null = null;
/**
* @param workspaceOptions Dictionary of options for the
* workspace.
@@ -263,7 +262,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.workspace_ = new WorkspaceSvg(workspaceOptions);
this.workspace_.setMetricsManager(
new FlyoutMetricsManager(this.workspace_, this));
new FlyoutMetricsManager(this.workspace_, this)
);
this.workspace_.internalIsFlyout = true;
// Keep the workspace visibility consistent with the flyout's visibility.
@@ -326,7 +326,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* put the flyout in. This should be <svg> or <g>.
* @returns The flyout's SVG group.
*/
createDom(tagName: string|Svg<SVGSVGElement>|Svg<SVGGElement>): SVGElement {
createDom(
tagName: string | Svg<SVGSVGElement> | Svg<SVGGElement>
): SVGElement {
/*
<svg | g>
<path class="blocklyFlyoutBackground"/>
@@ -335,15 +337,22 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
// Setting style to display:none to start. The toolbox and flyout
// hide/show code will set up proper visibility and size later.
this.svgGroup_ = dom.createSvgElement(
tagName, {'class': 'blocklyFlyout', 'style': 'display: none'});
this.svgGroup_ = dom.createSvgElement(tagName, {
'class': 'blocklyFlyout',
'style': 'display: none',
});
this.svgBackground_ = dom.createSvgElement(
Svg.PATH, {'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
Svg.PATH,
{'class': 'blocklyFlyoutBackground'},
this.svgGroup_
);
this.svgGroup_.appendChild(this.workspace_.createDom());
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutBackgroundColour', 'fill');
this.workspace_.getThemeManager().subscribe(
this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
this.workspace_
.getThemeManager()
.subscribe(this.svgBackground_, 'flyoutBackgroundColour', 'fill');
this.workspace_
.getThemeManager()
.subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
return this.svgGroup_;
}
@@ -358,26 +367,42 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.workspace_.targetWorkspace = targetWorkspace;
this.workspace_.scrollbar = new ScrollbarPair(
this.workspace_, this.horizontalLayout, !this.horizontalLayout,
'blocklyFlyoutScrollbar', this.SCROLLBAR_MARGIN);
this.workspace_,
this.horizontalLayout,
!this.horizontalLayout,
'blocklyFlyoutScrollbar',
this.SCROLLBAR_MARGIN
);
this.hide();
this.boundEvents.push(browserEvents.conditionalBind(
(this.svgGroup_ as SVGGElement), 'wheel', this, this.wheel_));
this.boundEvents.push(
browserEvents.conditionalBind(
this.svgGroup_ as SVGGElement,
'wheel',
this,
this.wheel_
)
);
if (!this.autoClose) {
this.filterWrapper = this.filterForCapacity.bind(this);
this.targetWorkspace.addChangeListener(this.filterWrapper);
}
// Dragging the flyout up and down.
this.boundEvents.push(browserEvents.conditionalBind(
(this.svgBackground_ as SVGPathElement), 'pointerdown', this,
this.onMouseDown));
this.boundEvents.push(
browserEvents.conditionalBind(
this.svgBackground_ as SVGPathElement,
'pointerdown',
this,
this.onMouseDown
)
);
// A flyout connected to a workspace doesn't have its own current gesture.
this.workspace_.getGesture =
this.targetWorkspace.getGesture.bind(this.targetWorkspace);
this.workspace_.getGesture = this.targetWorkspace.getGesture.bind(
this.targetWorkspace
);
// Get variables from the main workspace rather than the target workspace.
this.workspace_.setVariableMap(this.targetWorkspace.getVariableMap());
@@ -546,11 +571,15 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// reposition in resize, we need to call setPosition. See issue #4692.
if (scrollbar.hScroll) {
scrollbar.hScroll.setPosition(
scrollbar.hScroll.position.x, scrollbar.hScroll.position.y);
scrollbar.hScroll.position.x,
scrollbar.hScroll.position.y
);
}
if (scrollbar.vScroll) {
scrollbar.vScroll.setPosition(
scrollbar.vScroll.position.x, scrollbar.vScroll.position.y);
scrollbar.vScroll.position.x,
scrollbar.vScroll.position.y
);
}
}
}
@@ -583,7 +612,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* in the flyout. This is either an array of Nodes, a NodeList, a
* toolbox definition, or a string with the name of the dynamic category.
*/
show(flyoutDef: toolbox.FlyoutDefinition|string) {
show(flyoutDef: toolbox.FlyoutDefinition | string) {
this.workspace_.setResizesEnabled(false);
this.hide();
this.clearOldBlocks();
@@ -626,40 +655,42 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* of objects to show in the flyout.
* @returns The list of contents and gaps needed to lay out the flyout.
*/
private createFlyoutInfo(parsedContent: toolbox.FlyoutItemInfoArray):
{contents: FlyoutItem[], gaps: number[]} {
private createFlyoutInfo(parsedContent: toolbox.FlyoutItemInfoArray): {
contents: FlyoutItem[];
gaps: number[];
} {
const contents: FlyoutItem[] = [];
const gaps: number[] = [];
this.permanentlyDisabled.length = 0;
const defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y;
for (const info of parsedContent) {
if ('custom' in info) {
const customInfo = (info as toolbox.DynamicCategoryInfo);
const customInfo = info as toolbox.DynamicCategoryInfo;
const categoryName = customInfo['custom'];
const flyoutDef = this.getDynamicCategoryContents(categoryName);
const parsedDynamicContent =
toolbox.convertFlyoutDefToJsonArray(flyoutDef);
toolbox.convertFlyoutDefToJsonArray(flyoutDef);
const {contents: dynamicContents, gaps: dynamicGaps} =
this.createFlyoutInfo(parsedDynamicContent);
this.createFlyoutInfo(parsedDynamicContent);
contents.push(...dynamicContents);
gaps.push(...dynamicGaps);
}
switch (info['kind'].toUpperCase()) {
case 'BLOCK': {
const blockInfo = (info as toolbox.BlockInfo);
const blockInfo = info as toolbox.BlockInfo;
const block = this.createFlyoutBlock(blockInfo);
contents.push({type: FlyoutItemType.BLOCK, block: block});
this.addBlockGap(blockInfo, gaps, defaultGap);
break;
}
case 'SEP': {
const sepInfo = (info as toolbox.SeparatorInfo);
const sepInfo = info as toolbox.SeparatorInfo;
this.addSeparatorGap(sepInfo, gaps, defaultGap);
break;
}
case 'LABEL': {
const labelInfo = (info as toolbox.LabelInfo);
const labelInfo = info as toolbox.LabelInfo;
// A label is a button with different styling.
const label = this.createButton(labelInfo, /** isLabel */ true);
contents.push({type: FlyoutItemType.BUTTON, button: label});
@@ -667,7 +698,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
break;
}
case 'BUTTON': {
const buttonInfo = (info as toolbox.ButtonInfo);
const buttonInfo = info as toolbox.ButtonInfo;
const button = this.createButton(buttonInfo, /** isLabel */ false);
contents.push({type: FlyoutItemType.BUTTON, button: button});
gaps.push(defaultGap);
@@ -686,17 +717,18 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @returns The definition of the
* flyout in one of its many forms.
*/
private getDynamicCategoryContents(categoryName: string):
toolbox.FlyoutDefinition {
private getDynamicCategoryContents(
categoryName: string
): toolbox.FlyoutDefinition {
// Look up the correct category generation function and call that to get a
// valid XML list.
const fnToApply =
this.workspace_.targetWorkspace!.getToolboxCategoryCallback(
categoryName);
this.workspace_.targetWorkspace!.getToolboxCategoryCallback(categoryName);
if (typeof fnToApply !== 'function') {
throw TypeError(
'Couldn\'t find a callback function when opening' +
' a toolbox category.');
"Couldn't find a callback function when opening" +
' a toolbox category.'
);
}
return fnToApply(this.workspace_.targetWorkspace!);
}
@@ -709,11 +741,16 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @returns The object used to display the button in the
* flyout.
*/
private createButton(btnInfo: toolbox.ButtonOrLabelInfo, isLabel: boolean):
FlyoutButton {
private createButton(
btnInfo: toolbox.ButtonOrLabelInfo,
isLabel: boolean
): FlyoutButton {
const curButton = new FlyoutButton(
this.workspace_, (this.targetWorkspace as WorkspaceSvg), btnInfo,
isLabel);
this.workspace_,
this.targetWorkspace as WorkspaceSvg,
btnInfo,
isLabel
);
return curButton;
}
@@ -727,9 +764,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
private createFlyoutBlock(blockInfo: toolbox.BlockInfo): BlockSvg {
let block;
if (blockInfo['blockxml']) {
const xml = (typeof blockInfo['blockxml'] === 'string' ?
utilsXml.textToDom(blockInfo['blockxml']) :
blockInfo['blockxml']) as Element;
const xml = (
typeof blockInfo['blockxml'] === 'string'
? utilsXml.textToDom(blockInfo['blockxml'])
: blockInfo['blockxml']
) as Element;
block = this.getRecycledBlock(xml.getAttribute('type')!);
if (!block) {
block = Xml.domToBlock(xml, this.workspace_);
@@ -738,10 +777,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
block = this.getRecycledBlock(blockInfo['type']!);
if (!block) {
if (blockInfo['enabled'] === undefined) {
blockInfo['enabled'] = blockInfo['disabled'] !== 'true' &&
blockInfo['disabled'] !== true;
blockInfo['enabled'] =
blockInfo['disabled'] !== 'true' && blockInfo['disabled'] !== true;
}
block = blocks.append((blockInfo as blocks.State), this.workspace_);
block = blocks.append(blockInfo as blocks.State, this.workspace_);
}
}
@@ -750,7 +789,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// Do not enable these blocks as a result of capacity filtering.
this.permanentlyDisabled.push(block);
}
return (block as BlockSvg);
return block as BlockSvg;
}
/**
@@ -761,7 +800,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @returns The recycled block, or undefined if
* one could not be recycled.
*/
private getRecycledBlock(blockType: string): BlockSvg|undefined {
private getRecycledBlock(blockType: string): BlockSvg | undefined {
let index = -1;
for (let i = 0; i < this.recycledBlocks.length; i++) {
if (this.recycledBlocks[i].type === blockType) {
@@ -781,14 +820,19 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* next.
*/
private addBlockGap(
blockInfo: toolbox.BlockInfo, gaps: number[], defaultGap: number) {
blockInfo: toolbox.BlockInfo,
gaps: number[],
defaultGap: number
) {
let gap;
if (blockInfo['gap']) {
gap = parseInt(String(blockInfo['gap']));
} else if (blockInfo['blockxml']) {
const xml = (typeof blockInfo['blockxml'] === 'string' ?
utilsXml.textToDom(blockInfo['blockxml']) :
blockInfo['blockxml']) as Element;
const xml = (
typeof blockInfo['blockxml'] === 'string'
? utilsXml.textToDom(blockInfo['blockxml'])
: blockInfo['blockxml']
) as Element;
gap = parseInt(xml.getAttribute('gap')!);
}
gaps.push(!gap || isNaN(gap) ? defaultGap : gap);
@@ -804,7 +848,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* element.
*/
private addSeparatorGap(
sepInfo: toolbox.SeparatorInfo, gaps: number[], defaultGap: number) {
sepInfo: toolbox.SeparatorInfo,
gaps: number[],
defaultGap: number
) {
// Change the gap between two toolbox elements.
// <sep gap="36"></sep>
// The default gap is 24, can be set larger or smaller.
@@ -824,7 +871,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
private clearOldBlocks() {
// Delete any blocks from a previous showing.
const oldBlocks = this.workspace_.getTopBlocks(false);
for (let i = 0, block; block = oldBlocks[i]; i++) {
for (let i = 0, block; (block = oldBlocks[i]); i++) {
if (this.blockIsRecyclable_(block)) {
this.recycleBlock(block);
} else {
@@ -841,7 +888,7 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
}
this.mats.length = 0;
// Delete any buttons from a previous showing.
for (let i = 0, button; button = this.buttons_[i]; i++) {
for (let i = 0, button; (button = this.buttons_[i]); i++) {
button.dispose();
}
this.buttons_.length = 0;
@@ -893,19 +940,38 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* as a mat for that block.
*/
protected addBlockListeners_(
root: SVGElement, block: BlockSvg, rect: SVGElement) {
this.listeners.push(browserEvents.conditionalBind(
root, 'pointerdown', null, this.blockMouseDown(block)));
this.listeners.push(browserEvents.conditionalBind(
rect, 'pointerdown', null, this.blockMouseDown(block)));
root: SVGElement,
block: BlockSvg,
rect: SVGElement
) {
this.listeners.push(
browserEvents.bind(root, 'pointerenter', block, block.addSelect));
browserEvents.conditionalBind(
root,
'pointerdown',
null,
this.blockMouseDown(block)
)
);
this.listeners.push(
browserEvents.bind(root, 'pointerleave', block, block.removeSelect));
browserEvents.conditionalBind(
rect,
'pointerdown',
null,
this.blockMouseDown(block)
)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerenter', block, block.addSelect));
browserEvents.bind(root, 'pointerenter', block, block.addSelect)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerleave', block, block.removeSelect));
browserEvents.bind(root, 'pointerleave', block, block.removeSelect)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerenter', block, block.addSelect)
);
this.listeners.push(
browserEvents.bind(rect, 'pointerleave', block, block.removeSelect)
);
}
/**
@@ -972,7 +1038,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
this.targetWorkspace.hideChaff();
const newVariables = Variables.getAddedVariables(
this.targetWorkspace, variablesBeforeCreation);
this.targetWorkspace,
variablesBeforeCreation
);
if (eventUtils.isEnabled()) {
eventUtils.setGroup(true);
@@ -980,7 +1048,8 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
for (let i = 0; i < newVariables.length; i++) {
const thisVariable = newVariables[i];
eventUtils.fire(
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable));
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable)
);
}
// Block events come after var events, in case they refer to newly created
@@ -1009,8 +1078,14 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
button.show();
// Clicking on a flyout button or label is a lot like clicking on the
// flyout background.
this.listeners.push(browserEvents.conditionalBind(
buttonSvg, 'pointerdown', this, this.onMouseDown));
this.listeners.push(
browserEvents.conditionalBind(
buttonSvg,
'pointerdown',
this,
this.onMouseDown
)
);
this.buttons_.push(button);
}
@@ -1029,8 +1104,12 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* the block.
*/
protected createRect_(
block: BlockSvg, x: number, y: number,
blockHW: {height: number, width: number}, index: number): SVGElement {
block: BlockSvg,
x: number,
y: number,
blockHW: {height: number; width: number},
index: number
): SVGElement {
// Create an invisible rectangle under the block to act as a button. Just
// using the block as a button is poor, since blocks have holes in them.
const rect = dom.createSvgElement(Svg.RECT, {
@@ -1065,7 +1144,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
const blockXY = block.getRelativeToSurfaceXY();
rect.setAttribute('y', String(blockXY.y));
rect.setAttribute(
'x', String(this.RTL ? blockXY.x - blockHW.width : blockXY.x));
'x',
String(this.RTL ? blockXY.x - blockHW.width : blockXY.x)
);
}
/**
@@ -1076,10 +1157,11 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
private filterForCapacity() {
const blocks = this.workspace_.getTopBlocks(false);
for (let i = 0, block; block = blocks[i]; i++) {
for (let i = 0, block; (block = blocks[i]); i++) {
if (this.permanentlyDisabled.indexOf(block) === -1) {
const enable = this.targetWorkspace.isCapacityAvailable(
common.getBlockTypeCounts(block));
common.getBlockTypeCounts(block)
);
while (block) {
block.setEnabled(enable);
block = block.getNextBlock();
@@ -1107,8 +1189,9 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
* @internal
*/
isScrollable(): boolean {
return this.workspace_.scrollbar ? this.workspace_.scrollbar.isVisible() :
false;
return this.workspace_.scrollbar
? this.workspace_.scrollbar.isVisible()
: false;
}
/**
@@ -1125,10 +1208,10 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
}
// Clone the block.
const json = (blocks.save(oldBlock) as blocks.State);
const json = blocks.save(oldBlock) as blocks.State;
// Normallly this resizes leading to weird jumps. Save it for terminateDrag.
targetWorkspace.setResizesEnabled(false);
const block = (blocks.append(json, targetWorkspace) as BlockSvg);
const block = blocks.append(json, targetWorkspace) as BlockSvg;
this.positionNewBlock(oldBlock, block);
@@ -1160,13 +1243,17 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
// The position of the old block in pixels relative to the upper left corner
// of the injection div.
const oldBlockOffsetPixels =
Coordinate.sum(flyoutOffsetPixels, oldBlockPos);
const oldBlockOffsetPixels = Coordinate.sum(
flyoutOffsetPixels,
oldBlockPos
);
// The position of the old block in pixels relative to the origin of the
// main workspace.
const finalOffset =
Coordinate.difference(oldBlockOffsetPixels, mainOffsetPixels);
const finalOffset = Coordinate.difference(
oldBlockOffsetPixels,
mainOffsetPixels
);
// The position of the old block in main workspace coordinates.
finalOffset.scale(1 / targetWorkspace.scale);
@@ -1180,6 +1267,6 @@ export abstract class Flyout extends DeleteArea implements IFlyout {
*/
export interface FlyoutItem {
type: FlyoutItemType;
button?: FlyoutButton|undefined;
block?: BlockSvg|undefined;
button?: FlyoutButton | undefined;
block?: BlockSvg | undefined;
}

Some files were not shown because too many files have changed in this diff Show More