release: Merge branch 'rc/v11.2.0' into rc/v12.0.0

This commit is contained in:
Aaron Dodson
2024-12-04 12:06:12 -08:00
329 changed files with 7487 additions and 7805 deletions

View File

@@ -1,28 +0,0 @@
# 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

View File

@@ -1,187 +0,0 @@
const rules = {
'no-unused-vars': [
'error',
{
'args': 'after-used',
// Ignore vars starting with an underscore.
'varsIgnorePattern': '^_',
// Ignore arguments starting with an underscore.
'argsIgnorePattern': '^_',
},
],
// Blockly uses for exporting symbols. no-self-assign added in eslint 5.
'no-self-assign': ['off'],
// Blockly uses single quotes except for JSON blobs, which must use double
// quotes.
'quotes': ['off'],
// Blockly uses 'use strict' in files.
'strict': ['off'],
// Closure style allows redeclarations.
'no-redeclare': ['off'],
'valid-jsdoc': ['error'],
'no-console': ['off'],
'spaced-comment': [
'error',
'always',
{
'block': {
'balanced': true,
},
'exceptions': ['*'],
},
],
// Blockly uses prefixes for optional arguments and test-only functions.
'camelcase': [
'error',
{
'properties': 'never',
'allow': ['^opt_', '^_opt_', '^testOnly_'],
},
],
// Blockly uses capital letters for some non-constructor namespaces.
// Keep them for legacy reasons.
'new-cap': ['off'],
// Blockly uses objects as maps, but uses Object.create(null) to
// instantiate them.
'guard-for-in': ['off'],
'prefer-spread': ['off'],
};
/**
* Build shared settings for TS linting and add in the config differences.
* @return {Object} The override TS linting for given files and a given
* tsconfig.
*/
function buildTSOverride({files, tsconfig}) {
return {
'files': files,
'plugins': ['@typescript-eslint/eslint-plugin', 'jsdoc'],
'settings': {
'jsdoc': {
'mode': 'typescript',
},
},
'parser': '@typescript-eslint/parser',
'parserOptions': {
'project': tsconfig,
'tsconfigRootDir': '.',
'ecmaVersion': 2020,
'sourceType': 'module',
},
'extends': [
'plugin:@typescript-eslint/recommended',
'plugin:jsdoc/recommended',
'prettier', // Extend again so that these rules are applied last
],
'rules': {
// TS rules
// Blockly uses namespaces to do declaration merging in some cases.
'@typescript-eslint/no-namespace': ['off'],
// Use the updated TypeScript-specific rule.
'no-invalid-this': ['off'],
'@typescript-eslint/no-invalid-this': ['error'],
// Needs decision. 601 problems.
'@typescript-eslint/no-non-null-assertion': ['off'],
// Use TS-specific rule.
'no-unused-vars': ['off'],
'@typescript-eslint/no-unused-vars': [
'error',
{
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_',
},
],
// Temporarily disable. 23 problems.
'@typescript-eslint/no-explicit-any': ['off'],
// Temporarily disable. 128 problems.
'require-jsdoc': ['off'],
// Temporarily disable. 55 problems.
'@typescript-eslint/ban-types': ['off'],
// Temporarily disable. 33 problems.
'@typescript-eslint/no-empty-function': ['off'],
// Temporarily disable. 3 problems.
'@typescript-eslint/no-empty-interface': ['off'],
// TsDoc rules (using JsDoc plugin)
// Disable built-in jsdoc verifier.
'valid-jsdoc': ['off'],
// Don't require types in params and returns docs.
'jsdoc/require-param-type': ['off'],
'jsdoc/require-returns-type': ['off'],
// params and returns docs are optional.
'jsdoc/require-param-description': ['off'],
'jsdoc/require-returns': ['off'],
// Disable for now (breaks on `this` which is not really a param).
'jsdoc/require-param': ['off'],
// Don't auto-add missing jsdoc. Only required on exported items.
'jsdoc/require-jsdoc': [
'warn',
{
'enableFixer': false,
'publicOnly': true,
},
],
'jsdoc/check-tag-names': [
'error',
{
'definedTags': [
'sealed',
'typeParam',
'remarks',
'define',
'nocollapse',
'suppress',
],
},
],
// Re-enable after Closure is removed. There shouldn't even be
// types in the TsDoc.
// These are "types" because of Closure's @suppress {warningName}
'jsdoc/no-undefined-types': ['off'],
'jsdoc/valid-types': ['off'],
// Disabled due to not handling `this`. If re-enabled,
// checkDestructured option
// should be left as false.
'jsdoc/check-param-names': ['off', {'checkDestructured': false}],
// Allow any text in the license tag. Other checks are not relevant.
'jsdoc/check-values': ['off'],
// Ensure there is a blank line between the body and any @tags,
// as required by the tsdoc spec (see #6353).
'jsdoc/tag-lines': ['error', 'any', {'startLines': 1}],
},
};
}
// NOTE: When this output is put directly in `module.exports`, the formatter
// does not align with the linter.
const eslintJSON = {
'rules': rules,
'env': {
'es2020': true,
'browser': true,
},
'globals': {
'goog': true,
'exports': true,
},
'extends': ['eslint:recommended', 'google', 'prettier'],
// TypeScript-specific config. Uses above rules plus these.
'overrides': [
buildTSOverride({
files: ['./**/*.ts', './**/*.tsx'],
tsconfig: './tsconfig.json',
}),
buildTSOverride({
files: ['./tests/typescript/**/*.ts', './tests/typescript/**/*.tsx'],
tsconfig: './tests/typescript/tsconfig.json',
}),
{
'files': ['./.eslintrc.js'],
'env': {
'node': true,
},
},
],
};
module.exports = eslintJSON;

View File

@@ -42,7 +42,7 @@ jobs:
path: _deploy/ path: _deploy/
- name: Deploy to App Engine - name: Deploy to App Engine
uses: google-github-actions/deploy-appengine@v2.1.2 uses: google-github-actions/deploy-appengine@v2.1.4
# For parameters see: # For parameters see:
# https://github.com/google-github-actions/deploy-appengine#inputs # https://github.com/google-github-actions/deploy-appengine#inputs
with: with:

View File

@@ -18,8 +18,7 @@ jobs:
# TODO (#2114): re-enable osx build. # TODO (#2114): re-enable osx build.
# os: [ubuntu-latest, macos-latest] # os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest] os: [ubuntu-latest]
# TODO(#8392): unpin v22 once npm issue fixed. node-version: [18.x, 20.x, 22.x]
node-version: [18.x, 20.x, 22.4.1]
# See supported Node.js release schedule at # See supported Node.js release schedule at
# https://nodejs.org/en/about/releases/ # https://nodejs.org/en/about/releases/

View File

@@ -10,4 +10,6 @@ module.exports = {
bracketSpacing: false, bracketSpacing: false,
// Put HTML tag closing brackets on same line as last attribute. // Put HTML tag closing brackets on same line as last attribute.
bracketSameLine: true, bracketSameLine: true,
// Organise imports using a plugin.
'plugins': ['prettier-plugin-organize-imports'],
}; };

View File

@@ -6,6 +6,6 @@ var msg = 'Compiled Blockly files should be loaded from https://unpkg.com/blockl
console.log(msg); console.log(msg);
try { try {
alert(msg); alert(msg);
} catch (_e) { } catch {
// Can't alert? Probably node.js. // Can't alert? Probably node.js.
} }

View File

@@ -6,6 +6,7 @@
// Former goog.module ID: Blockly.libraryBlocks // Former goog.module ID: Blockly.libraryBlocks
import type {BlockDefinition} from '../core/blocks.js';
import * as lists from './lists.js'; import * as lists from './lists.js';
import * as logic from './logic.js'; import * as logic from './logic.js';
import * as loops from './loops.js'; import * as loops from './loops.js';
@@ -14,7 +15,6 @@ import * as procedures from './procedures.js';
import * as texts from './text.js'; import * as texts from './text.js';
import * as variables from './variables.js'; import * as variables from './variables.js';
import * as variablesDynamic from './variables_dynamic.js'; import * as variablesDynamic from './variables_dynamic.js';
import type {BlockDefinition} from '../core/blocks.js';
export { export {
lists, lists,

View File

@@ -6,22 +6,22 @@
// Former goog.module ID: Blockly.libraryBlocks.lists // Former goog.module ID: Blockly.libraryBlocks.lists
import * as fieldRegistry from '../core/field_registry.js';
import * as xmlUtils from '../core/utils/xml.js';
import {Align} from '../core/inputs/align.js';
import type {Block} from '../core/block.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 {BlockSvg} from '../core/block_svg.js';
import type {FieldDropdown} from '../core/field_dropdown.js';
import {Msg} from '../core/msg.js';
import {MutatorIcon} from '../core/icons/mutator_icon.js';
import type {Workspace} from '../core/workspace.js';
import { import {
createBlockDefinitionsFromJsonArray, createBlockDefinitionsFromJsonArray,
defineBlocks, defineBlocks,
} from '../core/common.js'; } from '../core/common.js';
import type {Connection} from '../core/connection.js';
import '../core/field_dropdown.js'; import '../core/field_dropdown.js';
import type {FieldDropdown} from '../core/field_dropdown.js';
import * as fieldRegistry from '../core/field_registry.js';
import {MutatorIcon} from '../core/icons/mutator_icon.js';
import {Align} from '../core/inputs/align.js';
import {ValueInput} from '../core/inputs/value_input.js'; import {ValueInput} from '../core/inputs/value_input.js';
import {Msg} from '../core/msg.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Workspace} from '../core/workspace.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.
@@ -412,6 +412,24 @@ const LISTS_GETINDEX = {
this.appendDummyInput() this.appendDummyInput()
.appendField(modeMenu, 'MODE') .appendField(modeMenu, 'MODE')
.appendField('', 'SPACE'); .appendField('', 'SPACE');
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options: this.WHERE_OPTIONS,
}) as FieldDropdown;
menu.setValidator(
/** @param value The input value. */
function (this: FieldDropdown, value: string) {
const oldValue: string | null = this.getValue();
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
const newAt = value === 'FROM_START' || value === 'FROM_END';
if (newAt !== oldAt) {
const block = this.getSourceBlock() as GetIndexBlock;
block.updateAt_(newAt);
}
return undefined;
},
);
this.appendDummyInput().appendField(menu, 'WHERE');
this.appendDummyInput('AT'); this.appendDummyInput('AT');
if (Msg['LISTS_GET_INDEX_TAIL']) { if (Msg['LISTS_GET_INDEX_TAIL']) {
this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_INDEX_TAIL']); this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_INDEX_TAIL']);
@@ -577,31 +595,6 @@ const LISTS_GETINDEX = {
} else { } else {
this.appendDummyInput('AT'); this.appendDummyInput('AT');
} }
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options: this.WHERE_OPTIONS,
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @returns 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']) { if (Msg['LISTS_GET_INDEX_TAIL']) {
this.moveInputBefore('TAIL', null); this.moveInputBefore('TAIL', null);
} }
@@ -644,6 +637,24 @@ const LISTS_SETINDEX = {
this.appendDummyInput() this.appendDummyInput()
.appendField(operationDropdown, 'MODE') .appendField(operationDropdown, 'MODE')
.appendField('', 'SPACE'); .appendField('', 'SPACE');
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options: this.WHERE_OPTIONS,
}) as FieldDropdown;
menu.setValidator(
/** @param value The input value. */
function (this: FieldDropdown, value: string) {
const oldValue: string | null = this.getValue();
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
const newAt = value === 'FROM_START' || value === 'FROM_END';
if (newAt !== oldAt) {
const block = this.getSourceBlock() as SetIndexBlock;
block.updateAt_(newAt);
}
return undefined;
},
);
this.appendDummyInput().appendField(menu, 'WHERE');
this.appendDummyInput('AT'); this.appendDummyInput('AT');
this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']); this.appendValueInput('TO').appendField(Msg['LISTS_SET_INDEX_INPUT_TO']);
this.setInputsInline(true); this.setInputsInline(true);
@@ -756,36 +767,10 @@ const LISTS_SETINDEX = {
} else { } else {
this.appendDummyInput('AT'); this.appendDummyInput('AT');
} }
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options: this.WHERE_OPTIONS,
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @returns 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'); this.moveInputBefore('AT', 'TO');
if (this.getInput('ORDINAL')) { if (this.getInput('ORDINAL')) {
this.moveInputBefore('ORDINAL', 'TO'); this.moveInputBefore('ORDINAL', 'TO');
} }
this.getInput('AT')!.appendField(menu, 'WHERE');
}, },
}; };
blocks['lists_setIndex'] = LISTS_SETINDEX; blocks['lists_setIndex'] = LISTS_SETINDEX;
@@ -818,7 +803,30 @@ const LISTS_GETSUBLIST = {
this.appendValueInput('LIST') this.appendValueInput('LIST')
.setCheck('Array') .setCheck('Array')
.appendField(Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']); .appendField(Msg['LISTS_GET_SUBLIST_INPUT_IN_LIST']);
const createMenu = (n: 1 | 2): FieldDropdown => {
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options:
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
}) as FieldDropdown;
menu.setValidator(
/** @param value The input value. */
function (this: FieldDropdown, value: string) {
const oldValue: string | null = this.getValue();
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
const newAt = value === 'FROM_START' || value === 'FROM_END';
if (newAt !== oldAt) {
const block = this.getSourceBlock() as GetSublistBlock;
block.updateAt_(n, newAt);
}
return undefined;
},
);
return menu;
};
this.appendDummyInput('WHERE1_INPUT').appendField(createMenu(1), 'WHERE1');
this.appendDummyInput('AT1'); this.appendDummyInput('AT1');
this.appendDummyInput('WHERE2_INPUT').appendField(createMenu(2), 'WHERE2');
this.appendDummyInput('AT2'); this.appendDummyInput('AT2');
if (Msg['LISTS_GET_SUBLIST_TAIL']) { if (Msg['LISTS_GET_SUBLIST_TAIL']) {
this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_SUBLIST_TAIL']); this.appendDummyInput('TAIL').appendField(Msg['LISTS_GET_SUBLIST_TAIL']);
@@ -896,35 +904,10 @@ const LISTS_GETSUBLIST = {
} else { } else {
this.appendDummyInput('AT' + n); this.appendDummyInput('AT' + n);
} }
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options:
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @returns 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) { if (n === 1) {
this.moveInputBefore('AT1', 'AT2'); this.moveInputBefore('AT1', 'WHERE2_INPUT');
if (this.getInput('ORDINAL1')) { if (this.getInput('ORDINAL1')) {
this.moveInputBefore('ORDINAL1', 'AT2'); this.moveInputBefore('ORDINAL1', 'WHERE2_INPUT');
} }
} }
if (Msg['LISTS_GET_SUBLIST_TAIL']) { if (Msg['LISTS_GET_SUBLIST_TAIL']) {

View File

@@ -6,22 +6,22 @@
// Former goog.module ID: Blockly.libraryBlocks.logic // Former goog.module ID: Blockly.libraryBlocks.logic
import * as Events from '../core/events/events.js';
import * as Extensions from '../core/extensions.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js'; import type {Block} from '../core/block.js';
import type {BlockSvg} from '../core/block_svg.js'; import type {BlockSvg} from '../core/block_svg.js';
import type {Connection} from '../core/connection.js';
import {Msg} from '../core/msg.js';
import type {Workspace} from '../core/workspace.js';
import { import {
createBlockDefinitionsFromJsonArray, createBlockDefinitionsFromJsonArray,
defineBlocks, defineBlocks,
} from '../core/common.js'; } from '../core/common.js';
import type {Connection} from '../core/connection.js';
import * as Events from '../core/events/events.js';
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import * as Extensions from '../core/extensions.js';
import '../core/field_dropdown.js'; import '../core/field_dropdown.js';
import '../core/field_label.js'; import '../core/field_label.js';
import '../core/icons/mutator_icon.js'; import '../core/icons/mutator_icon.js';
import {Msg} from '../core/msg.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Workspace} from '../core/workspace.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.

View File

@@ -6,27 +6,27 @@
// Former goog.module ID: Blockly.libraryBlocks.loops // Former goog.module ID: Blockly.libraryBlocks.loops
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js'; import type {Block} from '../core/block.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import * as ContextMenu from '../core/contextmenu.js'; import * as ContextMenu from '../core/contextmenu.js';
import type { import type {
ContextMenuOption, ContextMenuOption,
LegacyContextMenuOption, LegacyContextMenuOption,
} from '../core/contextmenu_registry.js'; } from '../core/contextmenu_registry.js';
import * as Events from '../core/events/events.js'; import * as Events from '../core/events/events.js';
import * as Extensions from '../core/extensions.js'; import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import {Msg} from '../core/msg.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import * as eventUtils from '../core/events/utils.js'; import * as eventUtils from '../core/events/utils.js';
import * as Extensions from '../core/extensions.js';
import '../core/field_dropdown.js'; import '../core/field_dropdown.js';
import '../core/field_label.js'; import '../core/field_label.js';
import '../core/field_number.js'; import '../core/field_number.js';
import '../core/field_variable.js'; import '../core/field_variable.js';
import '../core/icons/warning_icon.js';
import {FieldVariable} from '../core/field_variable.js'; import {FieldVariable} from '../core/field_variable.js';
import '../core/icons/warning_icon.js';
import {Msg} from '../core/msg.js';
import {WorkspaceSvg} from '../core/workspace_svg.js'; import {WorkspaceSvg} from '../core/workspace_svg.js';
/** /**

View File

@@ -6,18 +6,18 @@
// Former goog.module ID: Blockly.libraryBlocks.math // Former goog.module ID: Blockly.libraryBlocks.math
import * as Extensions from '../core/extensions.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 {Block} from '../core/block.js';
import { import {
createBlockDefinitionsFromJsonArray, createBlockDefinitionsFromJsonArray,
defineBlocks, defineBlocks,
} from '../core/common.js'; } from '../core/common.js';
import * as Extensions from '../core/extensions.js';
import '../core/field_dropdown.js'; import '../core/field_dropdown.js';
import type {FieldDropdown} from '../core/field_dropdown.js';
import '../core/field_label.js'; import '../core/field_label.js';
import '../core/field_number.js'; import '../core/field_number.js';
import '../core/field_variable.js'; import '../core/field_variable.js';
import * as xmlUtils from '../core/utils/xml.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.

View File

@@ -6,43 +6,43 @@
// Former goog.module ID: Blockly.libraryBlocks.procedures // Former goog.module ID: Blockly.libraryBlocks.procedures
import * as ContextMenu from '../core/contextmenu.js';
import * as Events from '../core/events/events.js';
import * as Procedures from '../core/procedures.js';
import * as Variables from '../core/variables.js';
import * as Xml from '../core/xml.js';
import * as fieldRegistry from '../core/field_registry.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import {Align} from '../core/inputs/align.js';
import type {Block} from '../core/block.js'; import type {Block} from '../core/block.js';
import type {BlockSvg} from '../core/block_svg.js'; import type {BlockSvg} from '../core/block_svg.js';
import type {BlockCreate} from '../core/events/events_block_create.js';
import type {BlockChange} from '../core/events/events_block_change.js';
import type {BlockDefinition} from '../core/blocks.js'; import type {BlockDefinition} from '../core/blocks.js';
import * as common from '../core/common.js';
import {defineBlocks} from '../core/common.js';
import {config} from '../core/config.js';
import type {Connection} from '../core/connection.js'; import type {Connection} from '../core/connection.js';
import * as ContextMenu from '../core/contextmenu.js';
import type { import type {
ContextMenuOption, ContextMenuOption,
LegacyContextMenuOption, LegacyContextMenuOption,
} from '../core/contextmenu_registry.js'; } from '../core/contextmenu_registry.js';
import * as Events from '../core/events/events.js';
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {BlockChange} from '../core/events/events_block_change.js';
import type {BlockCreate} from '../core/events/events_block_create.js';
import * as eventUtils from '../core/events/utils.js'; import * as eventUtils from '../core/events/utils.js';
import {FieldCheckbox} from '../core/field_checkbox.js'; import {FieldCheckbox} from '../core/field_checkbox.js';
import {FieldLabel} from '../core/field_label.js'; import {FieldLabel} from '../core/field_label.js';
import * as fieldRegistry from '../core/field_registry.js';
import {FieldTextInput} from '../core/field_textinput.js'; import {FieldTextInput} from '../core/field_textinput.js';
import {Msg} from '../core/msg.js'; import '../core/icons/comment_icon.js';
import {MutatorIcon as Mutator} from '../core/icons/mutator_icon.js'; import {MutatorIcon as Mutator} from '../core/icons/mutator_icon.js';
import {Names} from '../core/names.js'; import '../core/icons/warning_icon.js';
import {Align} from '../core/inputs/align.js';
import type { import type {
IVariableModel, IVariableModel,
IVariableState, IVariableState,
} from '../core/interfaces/i_variable_model.js'; } from '../core/interfaces/i_variable_model.js';
import {Msg} from '../core/msg.js';
import {Names} from '../core/names.js';
import * as Procedures from '../core/procedures.js';
import * as xmlUtils from '../core/utils/xml.js';
import * as Variables from '../core/variables.js';
import type {Workspace} from '../core/workspace.js'; import type {Workspace} from '../core/workspace.js';
import type {WorkspaceSvg} from '../core/workspace_svg.js'; import type {WorkspaceSvg} from '../core/workspace_svg.js';
import {config} from '../core/config.js'; import * as Xml from '../core/xml.js';
import {defineBlocks} from '../core/common.js';
import '../core/icons/comment_icon.js';
import '../core/icons/warning_icon.js';
import * as common from '../core/common.js';
/** A dictionary of the block definitions provided by this module. */ /** A dictionary of the block definitions provided by this module. */
export const blocks: {[key: string]: BlockDefinition} = {}; export const blocks: {[key: string]: BlockDefinition} = {};
@@ -1108,6 +1108,14 @@ const PROCEDURE_CALL_COMMON = {
xml.appendChild(block); xml.appendChild(block);
Xml.domToWorkspace(xml, this.workspace); Xml.domToWorkspace(xml, this.workspace);
Events.setGroup(false); Events.setGroup(false);
} else if (!def.isEnabled()) {
this.setDisabledReason(
true,
DISABLED_PROCEDURE_DEFINITION_DISABLED_REASON,
);
this.setWarningText(
Msg['PROCEDURES_CALL_DISABLED_DEF_WARNING'].replace('%1', name),
);
} }
} else if (event.type === Events.BLOCK_DELETE) { } else if (event.type === Events.BLOCK_DELETE) {
// Look for the case where a procedure definition has been deleted, // Look for the case where a procedure definition has been deleted,
@@ -1215,7 +1223,7 @@ blocks['procedures_callreturn'] = {
this.appendDummyInput('TOPROW').appendField('', 'NAME'); this.appendDummyInput('TOPROW').appendField('', 'NAME');
this.setOutput(true); this.setOutput(true);
this.setStyle('procedure_blocks'); this.setStyle('procedure_blocks');
// Tooltip is set in domToMutation. // Tooltip is set in renameProcedure.
this.setHelpUrl(Msg['PROCEDURES_CALLRETURN_HELPURL']); this.setHelpUrl(Msg['PROCEDURES_CALLRETURN_HELPURL']);
this.arguments_ = []; this.arguments_ = [];
this.argumentVarModels_ = []; this.argumentVarModels_ = [];

View File

@@ -6,25 +6,25 @@
// Former goog.module ID: Blockly.libraryBlocks.texts // Former goog.module ID: Blockly.libraryBlocks.texts
import * as Extensions from '../core/extensions.js';
import * as fieldRegistry from '../core/field_registry.js';
import * as xmlUtils from '../core/utils/xml.js';
import {Align} from '../core/inputs/align.js';
import type {Block} from '../core/block.js'; import type {Block} from '../core/block.js';
import type {BlockSvg} from '../core/block_svg.js'; import type {BlockSvg} from '../core/block_svg.js';
import {Connection} from '../core/connection.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 {MutatorIcon} from '../core/icons/mutator_icon.js';
import type {Workspace} from '../core/workspace.js';
import { import {
createBlockDefinitionsFromJsonArray, createBlockDefinitionsFromJsonArray,
defineBlocks, defineBlocks,
} from '../core/common.js'; } from '../core/common.js';
import {Connection} from '../core/connection.js';
import * as Extensions from '../core/extensions.js';
import {FieldDropdown} from '../core/field_dropdown.js';
import {FieldImage} from '../core/field_image.js';
import * as fieldRegistry from '../core/field_registry.js';
import {FieldTextInput} from '../core/field_textinput.js';
import '../core/field_variable.js'; import '../core/field_variable.js';
import {MutatorIcon} from '../core/icons/mutator_icon.js';
import {Align} from '../core/inputs/align.js';
import {ValueInput} from '../core/inputs/value_input.js'; import {ValueInput} from '../core/inputs/value_input.js';
import {Msg} from '../core/msg.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Workspace} from '../core/workspace.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.
@@ -216,7 +216,30 @@ const GET_SUBSTRING_BLOCK = {
this.appendValueInput('STRING') this.appendValueInput('STRING')
.setCheck('String') .setCheck('String')
.appendField(Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']); .appendField(Msg['TEXT_GET_SUBSTRING_INPUT_IN_TEXT']);
const createMenu = (n: 1 | 2): FieldDropdown => {
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options:
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
}) as FieldDropdown;
menu.setValidator(
/** @param value The input value. */
function (this: FieldDropdown, value: any): null | undefined {
const oldValue: string | null = this.getValue();
const oldAt = oldValue === 'FROM_START' || oldValue === 'FROM_END';
const newAt = value === 'FROM_START' || value === 'FROM_END';
if (newAt !== oldAt) {
const block = this.getSourceBlock() as GetSubstringBlock;
block.updateAt_(n, newAt);
}
return undefined;
},
);
return menu;
};
this.appendDummyInput('WHERE1_INPUT').appendField(createMenu(1), 'WHERE1');
this.appendDummyInput('AT1'); this.appendDummyInput('AT1');
this.appendDummyInput('WHERE2_INPUT').appendField(createMenu(2), 'WHERE2');
this.appendDummyInput('AT2'); this.appendDummyInput('AT2');
if (Msg['TEXT_GET_SUBSTRING_TAIL']) { if (Msg['TEXT_GET_SUBSTRING_TAIL']) {
this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']); this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']);
@@ -288,37 +311,10 @@ const GET_SUBSTRING_BLOCK = {
this.removeInput('TAIL', true); this.removeInput('TAIL', true);
this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']); this.appendDummyInput('TAIL').appendField(Msg['TEXT_GET_SUBSTRING_TAIL']);
} }
const menu = fieldRegistry.fromJson({
type: 'field_dropdown',
options:
this[('WHERE_OPTIONS_' + n) as 'WHERE_OPTIONS_1' | 'WHERE_OPTIONS_2'],
}) as FieldDropdown;
menu.setValidator(
/**
* @param value The input value.
* @returns 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) { if (n === 1) {
this.moveInputBefore('AT1', 'AT2'); this.moveInputBefore('AT1', 'WHERE2_INPUT');
if (this.getInput('ORDINAL1')) { if (this.getInput('ORDINAL1')) {
this.moveInputBefore('ORDINAL1', 'AT2'); this.moveInputBefore('ORDINAL1', 'WHERE2_INPUT');
} }
} }
}, },
@@ -438,6 +434,11 @@ const PROMPT_COMMON = {
domToMutation: function (this: PromptCommonBlock, xmlElement: Element) { domToMutation: function (this: PromptCommonBlock, xmlElement: Element) {
this.updateType_(xmlElement.getAttribute('type')!); this.updateType_(xmlElement.getAttribute('type')!);
}, },
// These blocks do not need JSO serialization hooks (saveExtraState
// and loadExtraState) because the state of this object is already
// encoded in the dropdown values.
// XML hooks are kept for backwards compatibility.
}; };
blocks['text_prompt_ext'] = { blocks['text_prompt_ext'] = {
@@ -468,16 +469,11 @@ blocks['text_prompt_ext'] = {
: Msg['TEXT_PROMPT_TOOLTIP_NUMBER']; : Msg['TEXT_PROMPT_TOOLTIP_NUMBER'];
}); });
}, },
// This block does not need JSO serialization hooks (saveExtraState and
// loadExtraState) because the state of this object is already encoded in the
// dropdown values.
// XML hooks are kept for backwards compatibility.
}; };
type PromptBlock = Block & PromptCommonMixin & QuoteImageMixin; type PromptBlock = Block & PromptCommonMixin & QuoteImageMixin;
const TEXT_PROMPT_BLOCK = { blocks['text_prompt'] = {
...PROMPT_COMMON, ...PROMPT_COMMON,
/** /**
* Block for prompt function (internal message). * Block for prompt function (internal message).
@@ -520,8 +516,6 @@ const TEXT_PROMPT_BLOCK = {
}, },
}; };
blocks['text_prompt'] = TEXT_PROMPT_BLOCK;
blocks['text_count'] = { blocks['text_count'] = {
/** /**
* Block for counting how many times one string appears within another string. * Block for counting how many times one string appears within another string.
@@ -666,7 +660,7 @@ const QUOTE_IMAGE_MIXIN = {
* closing double quote. The selected quote will be adapted for RTL blocks. * closing double quote. The selected quote will be adapted for RTL blocks.
* *
* @param open If the image should be open quote (“ in LTR). * @param open If the image should be open quote (“ in LTR).
* Otherwise, a closing quote is used (” in LTR). * Otherwise, a closing quote is used (” in LTR).
* @returns The new field. * @returns The new field.
*/ */
newQuote_: function (this: QuoteImageBlock, open: boolean): FieldImage { newQuote_: function (this: QuoteImageBlock, open: boolean): FieldImage {

View File

@@ -6,22 +6,22 @@
// Former goog.module ID: Blockly.libraryBlocks.variables // Former goog.module ID: Blockly.libraryBlocks.variables
import * as ContextMenu from '../core/contextmenu.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import type {Block} from '../core/block.js'; import type {Block} from '../core/block.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 { import {
createBlockDefinitionsFromJsonArray, createBlockDefinitionsFromJsonArray,
defineBlocks, defineBlocks,
} from '../core/common.js'; } from '../core/common.js';
import * as ContextMenu from '../core/contextmenu.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from '../core/contextmenu_registry.js';
import * as Extensions from '../core/extensions.js';
import '../core/field_label.js'; import '../core/field_label.js';
import {FieldVariable} from '../core/field_variable.js';
import {Msg} from '../core/msg.js';
import * as Variables from '../core/variables.js';
import type {WorkspaceSvg} from '../core/workspace_svg.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.

View File

@@ -6,23 +6,23 @@
// Former goog.module ID: Blockly.libraryBlocks.variablesDynamic // Former goog.module ID: Blockly.libraryBlocks.variablesDynamic
import * as ContextMenu from '../core/contextmenu.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js'; import type {Block} from '../core/block.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 { import {
createBlockDefinitionsFromJsonArray, createBlockDefinitionsFromJsonArray,
defineBlocks, defineBlocks,
} from '../core/common.js'; } from '../core/common.js';
import * as ContextMenu from '../core/contextmenu.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from '../core/contextmenu_registry.js';
import {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import * as Extensions from '../core/extensions.js';
import '../core/field_label.js'; import '../core/field_label.js';
import {FieldVariable} from '../core/field_variable.js';
import {Msg} from '../core/msg.js';
import * as Variables from '../core/variables.js';
import type {WorkspaceSvg} from '../core/workspace_svg.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.

View File

@@ -23,38 +23,39 @@ import * as common from './common.js';
import {Connection} from './connection.js'; import {Connection} from './connection.js';
import {ConnectionType} from './connection_type.js'; import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js'; import * as constants from './constants.js';
import {DuplicateIconType} from './icons/exceptions.js';
import type {Abstract} from './events/events_abstract.js'; import type {Abstract} from './events/events_abstract.js';
import type {BlockChange} from './events/events_block_change.js'; import type {BlockChange} from './events/events_block_change.js';
import type {BlockMove} from './events/events_block_move.js'; import type {BlockMove} from './events/events_block_move.js';
import * as deprecation from './utils/deprecation.js'; import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import * as Extensions from './extensions.js'; import * as Extensions from './extensions.js';
import type {Field} from './field.js'; import type {Field} from './field.js';
import * as fieldRegistry from './field_registry.js'; import * as fieldRegistry from './field_registry.js';
import {Input} from './inputs/input.js'; import {DuplicateIconType} from './icons/exceptions.js';
import {Align} from './inputs/align.js'; import {IconType} from './icons/icon_types.js';
import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
import {type IIcon} from './interfaces/i_icon.js';
import {isCommentIcon} from './interfaces/i_comment_icon.js';
import type {MutatorIcon} from './icons/mutator_icon.js'; import type {MutatorIcon} from './icons/mutator_icon.js';
import {Align} from './inputs/align.js';
import {DummyInput} from './inputs/dummy_input.js';
import {EndRowInput} from './inputs/end_row_input.js';
import {Input} from './inputs/input.js';
import {StatementInput} from './inputs/statement_input.js';
import {ValueInput} from './inputs/value_input.js';
import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
import {isCommentIcon} from './interfaces/i_comment_icon.js';
import {type IIcon} from './interfaces/i_icon.js';
import * as registry from './registry.js';
import * as Tooltip from './tooltip.js'; import * as Tooltip from './tooltip.js';
import * as arrayUtils from './utils/array.js'; import * as arrayUtils from './utils/array.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import * as deprecation from './utils/deprecation.js';
import * as idGenerator from './utils/idgenerator.js'; import * as idGenerator from './utils/idgenerator.js';
import * as parsing from './utils/parsing.js'; import * as parsing from './utils/parsing.js';
import * as registry from './registry.js';
import {Size} from './utils/size.js'; import {Size} from './utils/size.js';
import type { import type {
IVariableModel, IVariableModel,
IVariableState, IVariableState,
} from './interfaces/i_variable_model.js'; } from './interfaces/i_variable_model.js';
import type {Workspace} from './workspace.js'; import type {Workspace} from './workspace.js';
import {DummyInput} from './inputs/dummy_input.js';
import {EndRowInput} from './inputs/end_row_input.js';
import {ValueInput} from './inputs/value_input.js';
import {StatementInput} from './inputs/statement_input.js';
import {IconType} from './icons/icon_types.js';
/** /**
* Class for one block. * Class for one block.
@@ -91,7 +92,7 @@ export class Block implements IASTNodeLocation {
* Colour of the block as HSV hue value (0-360) * Colour of the block as HSV hue value (0-360)
* This may be null if the block colour was not set via a hue number. * This may be null if the block colour was not set via a hue number.
*/ */
private hue_: number | null = null; private hue: number | null = null;
/** Colour of the block in '#RRGGBB' format. */ /** Colour of the block in '#RRGGBB' format. */
protected colour_ = '#000000'; protected colour_ = '#000000';
@@ -146,24 +147,31 @@ export class Block implements IASTNodeLocation {
suppressPrefixSuffix: boolean | null = false; suppressPrefixSuffix: boolean | null = false;
/** /**
* An optional property for declaring developer variables. Return a list of * An optional method for declaring developer variables, to be used
* variable names for use by generators. Developer variables are never * by generators. Developer variables are never shown to the user,
* shown to the user, but are declared as global variables in the generated * but are declared as global variables in the generated code.
* code. *
* @returns a list of developer variable names.
*/ */
getDeveloperVariables?: () => string[]; getDeveloperVariables?: () => string[];
/** /**
* An optional function that reconfigures the block based on the contents of * An optional method that reconfigures the block based on the
* the mutator dialog. * contents of the mutator dialog.
*
* @param rootBlock The root block in the mutator flyout.
*/ */
compose?: (p1: Block) => void; compose?: (rootBlock: Block) => void;
/** /**
* An optional function that populates the mutator's dialog with * An optional function that populates the mutator flyout with
* this block's components. * blocks representing this block's configuration.
*
* @param workspace The mutator flyout's workspace.
* @returns The root block created in the flyout's workspace.
*/ */
decompose?: (p1: Workspace) => Block; decompose?: (workspace: Workspace) => Block;
id: string; id: string;
outputConnection: Connection | null = null; outputConnection: Connection | null = null;
nextConnection: Connection | null = null; nextConnection: Connection | null = null;
@@ -179,13 +187,13 @@ export class Block implements IASTNodeLocation {
protected childBlocks_: this[] = []; protected childBlocks_: this[] = [];
private deletable_ = true; private deletable = true;
private movable_ = true; private movable = true;
private editable_ = true; private editable = true;
private isShadow_ = false; private shadow = false;
protected collapsed_ = false; protected collapsed_ = false;
protected outputShape_: number | null = null; protected outputShape_: number | null = null;
@@ -202,7 +210,7 @@ export class Block implements IASTNodeLocation {
*/ */
initialized = false; initialized = false;
private readonly xy_: Coordinate; private readonly xy: Coordinate;
isInFlyout: boolean; isInFlyout: boolean;
isInMutator: boolean; isInMutator: boolean;
RTL: boolean; RTL: boolean;
@@ -219,10 +227,10 @@ export class Block implements IASTNodeLocation {
/** /**
* String for block help, or function that returns a URL. Null for no help. * String for block help, or function that returns a URL. Null for no help.
*/ */
helpUrl: string | Function | null = null; helpUrl: string | (() => string) | null = null;
/** A bound callback function to use when the parent workspace changes. */ /** A bound callback function to use when the parent workspace changes. */
private onchangeWrapper_: ((p1: Abstract) => void) | null = null; private onchangeWrapper: ((p1: Abstract) => void) | null = null;
/** /**
* A count of statement inputs on the block. * A count of statement inputs on the block.
@@ -255,7 +263,7 @@ export class Block implements IASTNodeLocation {
* The block's position in workspace units. (0, 0) is at the workspace's * The block's position in workspace units. (0, 0) is at the workspace's
* origin; scale does not change this value. * origin; scale does not change this value.
*/ */
this.xy_ = new Coordinate(0, 0); this.xy = new Coordinate(0, 0);
this.isInFlyout = workspace.isFlyout; this.isInFlyout = workspace.isFlyout;
this.isInMutator = workspace.isMutator; this.isInMutator = workspace.isMutator;
@@ -300,7 +308,7 @@ export class Block implements IASTNodeLocation {
// Fire a create event. // Fire a create event.
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(this)); eventUtils.fire(new (eventUtils.get(EventType.BLOCK_CREATE))(this));
} }
} finally { } finally {
eventUtils.setGroup(existingGroup); eventUtils.setGroup(existingGroup);
@@ -328,14 +336,14 @@ export class Block implements IASTNodeLocation {
// Dispose of this change listener before unplugging. // Dispose of this change listener before unplugging.
// Technically not necessary due to the event firing delay. // Technically not necessary due to the event firing delay.
// But future-proofing. // But future-proofing.
if (this.onchangeWrapper_) { if (this.onchangeWrapper) {
this.workspace.removeChangeListener(this.onchangeWrapper_); this.workspace.removeChangeListener(this.onchangeWrapper);
} }
this.unplug(healStack); this.unplug(healStack);
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
// Constructing the delete event is costly. Only perform if necessary. // Constructing the delete event is costly. Only perform if necessary.
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_DELETE))(this)); eventUtils.fire(new (eventUtils.get(EventType.BLOCK_DELETE))(this));
} }
this.workspace.removeTopBlock(this); this.workspace.removeTopBlock(this);
this.disposeInternal(); this.disposeInternal();
@@ -347,8 +355,8 @@ export class Block implements IASTNodeLocation {
*/ */
protected disposeInternal() { protected disposeInternal() {
this.disposing = true; this.disposing = true;
if (this.onchangeWrapper_) { if (this.onchangeWrapper) {
this.workspace.removeChangeListener(this.onchangeWrapper_); this.workspace.removeChangeListener(this.onchangeWrapper);
} }
this.workspace.removeTypedBlock(this); this.workspace.removeTypedBlock(this);
@@ -398,10 +406,10 @@ export class Block implements IASTNodeLocation {
*/ */
unplug(opt_healStack?: boolean) { unplug(opt_healStack?: boolean) {
if (this.outputConnection) { if (this.outputConnection) {
this.unplugFromRow_(opt_healStack); this.unplugFromRow(opt_healStack);
} }
if (this.previousConnection) { if (this.previousConnection) {
this.unplugFromStack_(opt_healStack); this.unplugFromStack(opt_healStack);
} }
} }
@@ -412,7 +420,7 @@ export class Block implements IASTNodeLocation {
* @param opt_healStack Disconnect right-side block and connect to left-side * @param opt_healStack Disconnect right-side block and connect to left-side
* block. Defaults to false. * block. Defaults to false.
*/ */
private unplugFromRow_(opt_healStack?: boolean) { private unplugFromRow(opt_healStack?: boolean) {
let parentConnection = null; let parentConnection = null;
if (this.outputConnection?.isConnected()) { if (this.outputConnection?.isConnected()) {
parentConnection = this.outputConnection.targetConnection; parentConnection = this.outputConnection.targetConnection;
@@ -425,7 +433,7 @@ export class Block implements IASTNodeLocation {
return; return;
} }
const thisConnection = this.getOnlyValueConnection_(); const thisConnection = this.getOnlyValueConnection();
if ( if (
!thisConnection || !thisConnection ||
!thisConnection.isConnected() || !thisConnection.isConnected() ||
@@ -462,7 +470,7 @@ export class Block implements IASTNodeLocation {
* *
* @returns The connection on the value input, or null. * @returns The connection on the value input, or null.
*/ */
private getOnlyValueConnection_(): Connection | null { private getOnlyValueConnection(): Connection | null {
let connection = null; let connection = null;
for (let i = 0; i < this.inputList.length; i++) { for (let i = 0; i < this.inputList.length; i++) {
const thisConnection = this.inputList[i].connection; const thisConnection = this.inputList[i].connection;
@@ -487,7 +495,7 @@ export class Block implements IASTNodeLocation {
* @param opt_healStack Disconnect child statement and reconnect stack. * @param opt_healStack Disconnect child statement and reconnect stack.
* Defaults to false. * Defaults to false.
*/ */
private unplugFromStack_(opt_healStack?: boolean) { private unplugFromStack(opt_healStack?: boolean) {
let previousTarget = null; let previousTarget = null;
if (this.previousConnection?.isConnected()) { if (this.previousConnection?.isConnected()) {
// Remember the connection that any next statements need to connect to. // Remember the connection that any next statements need to connect to.
@@ -719,7 +727,7 @@ export class Block implements IASTNodeLocation {
} }
// Check that block is connected to new parent if new parent is not null and // Check that block is connected to new parent if new parent is not null and
// that block is not connected to superior one if new parent is null. // that block is not connected to superior one if new parent is null.
const targetBlock = const targetBlock =
(this.previousConnection && this.previousConnection.targetBlock()) || (this.previousConnection && this.previousConnection.targetBlock()) ||
(this.outputConnection && this.outputConnection.targetBlock()); (this.outputConnection && this.outputConnection.targetBlock());
@@ -737,14 +745,13 @@ export class Block implements IASTNodeLocation {
} }
// This block hasn't actually moved on-screen, so there's no need to // This block hasn't actually moved on-screen, so there's no need to
// update // update its connection locations.
// its connection locations.
if (this.parentBlock_) { if (this.parentBlock_) {
// Remove this block from the old parent's child list. // Remove this block from the old parent's child list.
arrayUtils.removeElem(this.parentBlock_.childBlocks_, this); arrayUtils.removeElem(this.parentBlock_.childBlocks_, this);
} else { } else {
// New parent must be non-null so remove this block from the workspace's // New parent must be non-null so remove this block from the
// list of top-most blocks. // workspace's list of top-most blocks.
this.workspace.removeTopBlock(this); this.workspace.removeTopBlock(this);
} }
@@ -785,8 +792,8 @@ export class Block implements IASTNodeLocation {
*/ */
isDeletable(): boolean { isDeletable(): boolean {
return ( return (
this.deletable_ && this.deletable &&
!this.isShadow_ && !this.shadow &&
!this.isDeadOrDying() && !this.isDeadOrDying() &&
!this.workspace.options.readOnly !this.workspace.options.readOnly
); );
@@ -798,7 +805,7 @@ export class Block implements IASTNodeLocation {
* @returns True if the block's deletable property is true, false otherwise. * @returns True if the block's deletable property is true, false otherwise.
*/ */
isOwnDeletable(): boolean { isOwnDeletable(): boolean {
return this.deletable_; return this.deletable;
} }
/** /**
@@ -807,7 +814,7 @@ export class Block implements IASTNodeLocation {
* @param deletable True if deletable. * @param deletable True if deletable.
*/ */
setDeletable(deletable: boolean) { setDeletable(deletable: boolean) {
this.deletable_ = deletable; this.deletable = deletable;
} }
/** /**
@@ -818,8 +825,8 @@ export class Block implements IASTNodeLocation {
*/ */
isMovable(): boolean { isMovable(): boolean {
return ( return (
this.movable_ && this.movable &&
!this.isShadow_ && !this.shadow &&
!this.isDeadOrDying() && !this.isDeadOrDying() &&
!this.workspace.options.readOnly !this.workspace.options.readOnly
); );
@@ -832,7 +839,7 @@ export class Block implements IASTNodeLocation {
* @internal * @internal
*/ */
isOwnMovable(): boolean { isOwnMovable(): boolean {
return this.movable_; return this.movable;
} }
/** /**
@@ -841,7 +848,7 @@ export class Block implements IASTNodeLocation {
* @param movable True if movable. * @param movable True if movable.
*/ */
setMovable(movable: boolean) { setMovable(movable: boolean) {
this.movable_ = movable; this.movable = movable;
} }
/** /**
@@ -867,7 +874,7 @@ export class Block implements IASTNodeLocation {
* @returns True if a shadow. * @returns True if a shadow.
*/ */
isShadow(): boolean { isShadow(): boolean {
return this.isShadow_; return this.shadow;
} }
/** /**
@@ -879,7 +886,7 @@ export class Block implements IASTNodeLocation {
* @internal * @internal
*/ */
setShadow(shadow: boolean) { setShadow(shadow: boolean) {
this.isShadow_ = shadow; this.shadow = shadow;
} }
/** /**
@@ -910,9 +917,7 @@ export class Block implements IASTNodeLocation {
*/ */
isEditable(): boolean { isEditable(): boolean {
return ( return (
this.editable_ && this.editable && !this.isDeadOrDying() && !this.workspace.options.readOnly
!this.isDeadOrDying() &&
!this.workspace.options.readOnly
); );
} }
@@ -922,7 +927,7 @@ export class Block implements IASTNodeLocation {
* @returns True if the block's editable property is true, false otherwise. * @returns True if the block's editable property is true, false otherwise.
*/ */
isOwnEditable(): boolean { isOwnEditable(): boolean {
return this.editable_; return this.editable;
} }
/** /**
@@ -931,7 +936,7 @@ export class Block implements IASTNodeLocation {
* @param editable True if editable. * @param editable True if editable.
*/ */
setEditable(editable: boolean) { setEditable(editable: boolean) {
this.editable_ = editable; this.editable = editable;
for (let i = 0, input; (input = this.inputList[i]); i++) { for (let i = 0, input; (input = this.inputList[i]); i++) {
for (let j = 0, field; (field = input.fieldRow[j]); j++) { for (let j = 0, field; (field = input.fieldRow[j]); j++) {
field.updateEditable(); field.updateEditable();
@@ -994,7 +999,7 @@ export class Block implements IASTNodeLocation {
* @param url URL string for block help, or function that returns a URL. Null * @param url URL string for block help, or function that returns a URL. Null
* for no help. * for no help.
*/ */
setHelpUrl(url: string | Function) { setHelpUrl(url: string | (() => string)) {
this.helpUrl = url; this.helpUrl = url;
} }
@@ -1042,7 +1047,7 @@ export class Block implements IASTNodeLocation {
* @returns Hue value (0-360). * @returns Hue value (0-360).
*/ */
getHue(): number | null { getHue(): number | null {
return this.hue_; return this.hue;
} }
/** /**
@@ -1053,7 +1058,7 @@ export class Block implements IASTNodeLocation {
*/ */
setColour(colour: number | string) { setColour(colour: number | string) {
const parsed = parsing.parseBlockColour(colour); const parsed = parsing.parseBlockColour(colour);
this.hue_ = parsed.hue; this.hue = parsed.hue;
this.colour_ = parsed.hex; this.colour_ = parsed.hex;
} }
@@ -1079,12 +1084,12 @@ export class Block implements IASTNodeLocation {
if (onchangeFn && typeof onchangeFn !== 'function') { if (onchangeFn && typeof onchangeFn !== 'function') {
throw Error('onchange must be a function.'); throw Error('onchange must be a function.');
} }
if (this.onchangeWrapper_) { if (this.onchangeWrapper) {
this.workspace.removeChangeListener(this.onchangeWrapper_); this.workspace.removeChangeListener(this.onchangeWrapper);
} }
this.onchange = onchangeFn; this.onchange = onchangeFn;
this.onchangeWrapper_ = onchangeFn.bind(this); this.onchangeWrapper = onchangeFn.bind(this);
this.workspace.addChangeListener(this.onchangeWrapper_); this.workspace.addChangeListener(this.onchangeWrapper);
} }
/** /**
@@ -1326,7 +1331,7 @@ export class Block implements IASTNodeLocation {
setInputsInline(newBoolean: boolean) { setInputsInline(newBoolean: boolean) {
if (this.inputsInline !== newBoolean) { if (this.inputsInline !== newBoolean) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this, this,
'inline', 'inline',
null, null,
@@ -1463,13 +1468,32 @@ export class Block implements IASTNodeLocation {
* update whether the block is currently disabled for this reason. * update whether the block is currently disabled for this reason.
*/ */
setDisabledReason(disabled: boolean, reason: string): void { setDisabledReason(disabled: boolean, reason: string): void {
// Workspaces that were serialized before the reason for being disabled
// could be specified may have blocks that are disabled without a known
// reason. On being loaded, these blocks will default to having the manually
// disabled reason. However, if the user isn't allowed to manually disable
// or enable blocks, then this manually disabled reason cannot be removed.
// For backward compatibility with these legacy workspaces, when removing
// any disabled reason and the workspace does not allow manually disabling
// but the block is manually disabled, then remove the manually disabled
// reason in addition to the more specific reason. For example, when an
// orphaned block is no longer orphaned, the block should be enabled again.
if (
!disabled &&
!this.workspace.options.disable &&
this.hasDisabledReason(constants.MANUALLY_DISABLED) &&
reason != constants.MANUALLY_DISABLED
) {
this.setDisabledReason(false, constants.MANUALLY_DISABLED);
}
if (this.disabledReasons.has(reason) !== disabled) { if (this.disabledReasons.has(reason) !== disabled) {
if (disabled) { if (disabled) {
this.disabledReasons.add(reason); this.disabledReasons.add(reason);
} else { } else {
this.disabledReasons.delete(reason); this.disabledReasons.delete(reason);
} }
const blockChangeEvent = new (eventUtils.get(eventUtils.BLOCK_CHANGE))( const blockChangeEvent = new (eventUtils.get(EventType.BLOCK_CHANGE))(
this, this,
'disabled', 'disabled',
/* name= */ null, /* name= */ null,
@@ -1537,7 +1561,7 @@ export class Block implements IASTNodeLocation {
setCollapsed(collapsed: boolean) { setCollapsed(collapsed: boolean) {
if (this.collapsed_ !== collapsed) { if (this.collapsed_ !== collapsed) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this, this,
'collapsed', 'collapsed',
null, null,
@@ -1751,15 +1775,15 @@ export class Block implements IASTNodeLocation {
if (json['style'] && json['colour']) { if (json['style'] && json['colour']) {
throw Error(warningPrefix + 'Must not have both a colour and a style.'); throw Error(warningPrefix + 'Must not have both a colour and a style.');
} else if (json['style']) { } else if (json['style']) {
this.jsonInitStyle_(json, warningPrefix); this.jsonInitStyle(json, warningPrefix);
} else { } else {
this.jsonInitColour_(json, warningPrefix); this.jsonInitColour(json, warningPrefix);
} }
// Interpolate the message blocks. // Interpolate the message blocks.
let i = 0; let i = 0;
while (json['message' + i] !== undefined) { while (json['message' + i] !== undefined) {
this.interpolate_( this.interpolate(
json['message' + i], json['message' + i],
json['args' + i] || [], json['args' + i] || [],
// Backwards compatibility: lastDummyAlign aliases implicitAlign. // Backwards compatibility: lastDummyAlign aliases implicitAlign.
@@ -1834,7 +1858,7 @@ export class Block implements IASTNodeLocation {
* @param json Structured data describing the block. * @param json Structured data describing the block.
* @param warningPrefix Warning prefix string identifying block. * @param warningPrefix Warning prefix string identifying block.
*/ */
private jsonInitColour_(json: AnyDuringMigration, warningPrefix: string) { private jsonInitColour(json: AnyDuringMigration, warningPrefix: string) {
if ('colour' in json) { if ('colour' in json) {
if (json['colour'] === undefined) { if (json['colour'] === undefined) {
console.warn(warningPrefix + 'Undefined colour value.'); console.warn(warningPrefix + 'Undefined colour value.');
@@ -1842,7 +1866,7 @@ export class Block implements IASTNodeLocation {
const rawValue = json['colour']; const rawValue = json['colour'];
try { try {
this.setColour(rawValue); this.setColour(rawValue);
} catch (e) { } catch {
console.warn(warningPrefix + 'Illegal colour value: ', rawValue); console.warn(warningPrefix + 'Illegal colour value: ', rawValue);
} }
} }
@@ -1855,11 +1879,11 @@ export class Block implements IASTNodeLocation {
* @param json Structured data describing the block. * @param json Structured data describing the block.
* @param warningPrefix Warning prefix string identifying block. * @param warningPrefix Warning prefix string identifying block.
*/ */
private jsonInitStyle_(json: AnyDuringMigration, warningPrefix: string) { private jsonInitStyle(json: AnyDuringMigration, warningPrefix: string) {
const blockStyleName = json['style']; const blockStyleName = json['style'];
try { try {
this.setStyle(blockStyleName); this.setStyle(blockStyleName);
} catch (styleError) { } catch {
console.warn(warningPrefix + 'Style does not exist: ', blockStyleName); console.warn(warningPrefix + 'Style does not exist: ', blockStyleName);
} }
} }
@@ -1907,21 +1931,21 @@ export class Block implements IASTNodeLocation {
* of newline tokens, how should it be aligned? * of newline tokens, how should it be aligned?
* @param warningPrefix Warning prefix string identifying block. * @param warningPrefix Warning prefix string identifying block.
*/ */
private interpolate_( private interpolate(
message: string, message: string,
args: AnyDuringMigration[], args: AnyDuringMigration[],
implicitAlign: string | undefined, implicitAlign: string | undefined,
warningPrefix: string, warningPrefix: string,
) { ) {
const tokens = parsing.tokenizeInterpolation(message); const tokens = parsing.tokenizeInterpolation(message);
this.validateTokens_(tokens, args.length); this.validateTokens(tokens, args.length);
const elements = this.interpolateArguments_(tokens, args, implicitAlign); const elements = this.interpolateArguments(tokens, args, implicitAlign);
// An array of [field, fieldName] tuples. // An array of [field, fieldName] tuples.
const fieldStack = []; const fieldStack = [];
for (let i = 0, element; (element = elements[i]); i++) { for (let i = 0, element; (element = elements[i]); i++) {
if (this.isInputKeyword_(element['type'])) { if (this.isInputKeyword(element['type'])) {
const input = this.inputFromJson_(element, warningPrefix); const input = this.inputFromJson(element, warningPrefix);
// Should never be null, but just in case. // Should never be null, but just in case.
if (input) { if (input) {
for (let j = 0, tuple; (tuple = fieldStack[j]); j++) { for (let j = 0, tuple; (tuple = fieldStack[j]); j++) {
@@ -1932,7 +1956,7 @@ export class Block implements IASTNodeLocation {
} else { } else {
// All other types, including ones starting with 'input_' get routed // All other types, including ones starting with 'input_' get routed
// here. // here.
const field = this.fieldFromJson_(element); const field = this.fieldFromJson(element);
if (field) { if (field) {
fieldStack.push([field, element['name']]); fieldStack.push([field, element['name']]);
} }
@@ -1948,7 +1972,7 @@ export class Block implements IASTNodeLocation {
* @param tokens An array of tokens to validate * @param tokens An array of tokens to validate
* @param argsCount The number of args that need to be referred to. * @param argsCount The number of args that need to be referred to.
*/ */
private validateTokens_(tokens: Array<string | number>, argsCount: number) { private validateTokens(tokens: Array<string | number>, argsCount: number) {
const visitedArgsHash = []; const visitedArgsHash = [];
let visitedArgsCount = 0; let visitedArgsCount = 0;
for (let i = 0; i < tokens.length; i++) { for (let i = 0; i < tokens.length; i++) {
@@ -2003,7 +2027,7 @@ export class Block implements IASTNodeLocation {
* or dummy inputs, if necessary. * or dummy inputs, if necessary.
* @returns The JSON definitions of field and inputs to add to the block. * @returns The JSON definitions of field and inputs to add to the block.
*/ */
private interpolateArguments_( private interpolateArguments(
tokens: Array<string | number>, tokens: Array<string | number>,
args: Array<AnyDuringMigration | string>, args: Array<AnyDuringMigration | string>,
implicitAlign: string | undefined, implicitAlign: string | undefined,
@@ -2026,7 +2050,7 @@ export class Block implements IASTNodeLocation {
} else { } else {
// AnyDuringMigration because: Type '{ text: string; type: string; } // AnyDuringMigration because: Type '{ text: string; type: string; }
// | null' is not assignable to type 'string | number'. // | null' is not assignable to type 'string | number'.
element = this.stringToFieldJson_(element) as AnyDuringMigration; element = this.stringToFieldJson(element) as AnyDuringMigration;
if (!element) { if (!element) {
continue; continue;
} }
@@ -2038,9 +2062,7 @@ export class Block implements IASTNodeLocation {
const length = elements.length; const length = elements.length;
if ( if (
length && length &&
!this.isInputKeyword_( !this.isInputKeyword((elements as AnyDuringMigration)[length - 1]['type'])
(elements as AnyDuringMigration)[length - 1]['type'],
)
) { ) {
const dummyInput = {'type': 'input_dummy'}; const dummyInput = {'type': 'input_dummy'};
if (implicitAlign) { if (implicitAlign) {
@@ -2060,7 +2082,7 @@ export class Block implements IASTNodeLocation {
* @param element The element to try to turn into a field. * @param element The element to try to turn into a field.
* @returns The field defined by the JSON, or null if one couldn't be created. * @returns The field defined by the JSON, or null if one couldn't be created.
*/ */
private fieldFromJson_(element: { private fieldFromJson(element: {
alt?: string; alt?: string;
type: string; type: string;
text?: string; text?: string;
@@ -2068,10 +2090,10 @@ export class Block implements IASTNodeLocation {
const field = fieldRegistry.fromJson(element); const field = fieldRegistry.fromJson(element);
if (!field && element['alt']) { if (!field && element['alt']) {
if (typeof element['alt'] === 'string') { if (typeof element['alt'] === 'string') {
const json = this.stringToFieldJson_(element['alt']); const json = this.stringToFieldJson(element['alt']);
return json ? this.fieldFromJson_(json) : null; return json ? this.fieldFromJson(json) : null;
} }
return this.fieldFromJson_(element['alt']); return this.fieldFromJson(element['alt']);
} }
return field; return field;
} }
@@ -2086,7 +2108,7 @@ export class Block implements IASTNodeLocation {
* @returns The input that has been created, or null if one could not be * @returns The input that has been created, or null if one could not be
* created for some reason (should never happen). * created for some reason (should never happen).
*/ */
private inputFromJson_( private inputFromJson(
element: AnyDuringMigration, element: AnyDuringMigration,
warningPrefix: string, warningPrefix: string,
): Input | null { ): Input | null {
@@ -2144,7 +2166,7 @@ export class Block implements IASTNodeLocation {
* @returns True if the given string matches one of the input keywords, false * @returns True if the given string matches one of the input keywords, false
* otherwise. * otherwise.
*/ */
private isInputKeyword_(str: string): boolean { private isInputKeyword(str: string): boolean {
return ( return (
str === 'input_value' || str === 'input_value' ||
str === 'input_statement' || str === 'input_statement' ||
@@ -2161,7 +2183,7 @@ export class Block implements IASTNodeLocation {
* @param str String to turn into the JSON definition of a label field. * @param str String to turn into the JSON definition of a label field.
* @returns The JSON definition or null. * @returns The JSON definition or null.
*/ */
private stringToFieldJson_(str: string): {text: string; type: string} | null { private stringToFieldJson(str: string): {text: string; type: string} | null {
str = str.trim(); str = str.trim();
if (str) { if (str) {
return { return {
@@ -2336,7 +2358,7 @@ export class Block implements IASTNodeLocation {
} }
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this, this,
'comment', 'comment',
null, null,
@@ -2422,7 +2444,7 @@ export class Block implements IASTNodeLocation {
* @returns Object with .x and .y properties. * @returns Object with .x and .y properties.
*/ */
getRelativeToSurfaceXY(): Coordinate { getRelativeToSurfaceXY(): Coordinate {
return this.xy_; return this.xy;
} }
/** /**
@@ -2436,11 +2458,9 @@ export class Block implements IASTNodeLocation {
if (this.parentBlock_) { if (this.parentBlock_) {
throw Error('Block has parent'); throw Error('Block has parent');
} }
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( const event = new (eventUtils.get(EventType.BLOCK_MOVE))(this) as BlockMove;
this, if (reason) event.setReason(reason);
) as BlockMove; this.xy.translate(dx, dy);
reason && event.setReason(reason);
this.xy_.translate(dx, dy);
event.recordNew(); event.recordNew();
eventUtils.fire(event); eventUtils.fire(event);
} }

View File

@@ -16,7 +16,9 @@ import './events/events_selected.js';
import {Block} from './block.js'; import {Block} from './block.js';
import * as blockAnimations from './block_animations.js'; import * as blockAnimations from './block_animations.js';
import {IDeletable} from './blockly.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import {BlockCopyData, BlockPaster} from './clipboard/block_paster.js';
import * as common from './common.js'; import * as common from './common.js';
import {config} from './config.js'; import {config} from './config.js';
import type {Connection} from './connection.js'; import type {Connection} from './connection.js';
@@ -28,11 +30,15 @@ import {
ContextMenuRegistry, ContextMenuRegistry,
LegacyContextMenuOption, LegacyContextMenuOption,
} from './contextmenu_registry.js'; } from './contextmenu_registry.js';
import {BlockDragStrategy} from './dragging/block_drag_strategy.js';
import type {BlockMove} from './events/events_block_move.js'; import type {BlockMove} from './events/events_block_move.js';
import * as deprecation from './utils/deprecation.js'; import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import type {Field} from './field.js'; import type {Field} from './field.js';
import {FieldLabel} from './field_label.js'; import {FieldLabel} from './field_label.js';
import {IconType} from './icons/icon_types.js';
import {MutatorIcon} from './icons/mutator_icon.js';
import {WarningIcon} from './icons/warning_icon.js';
import type {Input} from './inputs/input.js'; import type {Input} from './inputs/input.js';
import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js';
@@ -44,26 +50,21 @@ import {ASTNode} from './keyboard_nav/ast_node.js';
import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js'; import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js';
import {MarkerManager} from './marker_manager.js'; import {MarkerManager} from './marker_manager.js';
import {Msg} from './msg.js'; import {Msg} from './msg.js';
import {MutatorIcon} from './icons/mutator_icon.js'; import * as renderManagement from './render_management.js';
import {RenderedConnection} from './rendered_connection.js'; import {RenderedConnection} from './rendered_connection.js';
import type {IPathObject} from './renderers/common/i_path_object.js'; import type {IPathObject} from './renderers/common/i_path_object.js';
import * as blocks from './serialization/blocks.js'; import * as blocks from './serialization/blocks.js';
import type {BlockStyle} from './theme.js'; import type {BlockStyle} from './theme.js';
import * as Tooltip from './tooltip.js'; import * as Tooltip from './tooltip.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import * as deprecation from './utils/deprecation.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import {Rect} from './utils/rect.js'; import {Rect} from './utils/rect.js';
import {Svg} from './utils/svg.js'; import {Svg} from './utils/svg.js';
import * as svgMath from './utils/svg_math.js'; import * as svgMath from './utils/svg_math.js';
import {WarningIcon} from './icons/warning_icon.js'; import {FlyoutItemInfo} from './utils/toolbox.js';
import type {Workspace} from './workspace.js'; import type {Workspace} from './workspace.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import * as renderManagement from './render_management.js';
import {IconType} from './icons/icon_types.js';
import {BlockCopyData, BlockPaster} from './clipboard/block_paster.js';
import {BlockDragStrategy} from './dragging/block_drag_strategy.js';
import {IDeletable} from './blockly.js';
import {FlyoutItemInfo} from './utils/toolbox.js';
/** /**
* Class for a block's SVG representation. * Class for a block's SVG representation.
@@ -92,7 +93,25 @@ export class BlockSvg
static readonly COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_'; static readonly COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';
override decompose?: (p1: Workspace) => BlockSvg; override decompose?: (p1: Workspace) => BlockSvg;
// override compose?: ((p1: BlockSvg) => void)|null; // override compose?: ((p1: BlockSvg) => void)|null;
saveConnections?: (p1: BlockSvg) => void;
/**
* An optional method which saves a record of blocks connected to
* this block so they can be later restored after this block is
* recoomposed (reconfigured). Typically records the connected
* blocks on properties on blocks in the mutator flyout, so that
* rearranging those component blocks will automatically rearrange
* the corresponding connected blocks on this block after this block
* is recomposed.
*
* To keep the saved connection information up-to-date, MutatorIcon
* arranges for an event listener to call this method any time the
* mutator flyout is open and a change occurs on this block's
* workspace.
*
* @param rootBlock The root block in the mutator flyout.
*/
saveConnections?: (rootBlock: BlockSvg) => void;
customContextMenu?: ( customContextMenu?: (
p1: Array<ContextMenuOption | LegacyContextMenuOption>, p1: Array<ContextMenuOption | LegacyContextMenuOption>,
) => void; ) => void;
@@ -126,7 +145,7 @@ export class BlockSvg
/** Block's mutator icon (if any). */ /** Block's mutator icon (if any). */
mutator: MutatorIcon | null = null; mutator: MutatorIcon | null = null;
private svgGroup_: SVGGElement; private svgGroup: SVGGElement;
style: BlockStyle; style: BlockStyle;
/** @internal */ /** @internal */
pathObject: IPathObject; pathObject: IPathObject;
@@ -136,15 +155,6 @@ export class BlockSvg
private visuallyDisabled = false; private visuallyDisabled = false;
/**
* Is this block currently rendering? Used to stop recursive render calls
* from actually triggering a re-render.
*/
private renderIsInProgress_ = false;
/** Whether mousedown events have been bound yet. */
private eventsInit_ = false;
override workspace: WorkspaceSvg; override workspace: WorkspaceSvg;
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix. // TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
override outputConnection!: RenderedConnection; override outputConnection!: RenderedConnection;
@@ -182,10 +192,10 @@ export class BlockSvg
throw TypeError('Cannot create a rendered block in a headless workspace'); throw TypeError('Cannot create a rendered block in a headless workspace');
} }
this.workspace = workspace; this.workspace = workspace;
this.svgGroup_ = dom.createSvgElement(Svg.G, {}); this.svgGroup = dom.createSvgElement(Svg.G, {});
if (prototypeName) { if (prototypeName) {
dom.addClass(this.svgGroup_, prototypeName); dom.addClass(this.svgGroup, prototypeName);
} }
/** A block style object. */ /** A block style object. */
this.style = workspace.getRenderer().getConstants().getBlockStyle(null); this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
@@ -193,14 +203,14 @@ export class BlockSvg
/** The renderer's path object. */ /** The renderer's path object. */
this.pathObject = workspace this.pathObject = workspace
.getRenderer() .getRenderer()
.makePathObject(this.svgGroup_, this.style); .makePathObject(this.svgGroup, this.style);
const svgPath = this.pathObject.svgPath; const svgPath = this.pathObject.svgPath;
(svgPath as any).tooltip = this; (svgPath as any).tooltip = this;
Tooltip.bindMouseEvents(svgPath); Tooltip.bindMouseEvents(svgPath);
// Expose this block's ID on its top-level SVG group. // Expose this block's ID on its top-level SVG group.
this.svgGroup_.setAttribute('data-id', this.id); this.svgGroup.setAttribute('data-id', this.id);
this.doInit_(); this.doInit_();
} }
@@ -222,12 +232,7 @@ export class BlockSvg
this.pathObject.updateMovable(this.isMovable() || this.isInFlyout); this.pathObject.updateMovable(this.isMovable() || this.isInFlyout);
const svg = this.getSvgRoot(); const svg = this.getSvgRoot();
if (!this.workspace.options.readOnly && svg) { if (!this.workspace.options.readOnly && svg) {
browserEvents.conditionalBind( browserEvents.conditionalBind(svg, 'pointerdown', this, this.onMouseDown);
svg,
'pointerdown',
this,
this.onMouseDown_,
);
} }
if (!svg.parentNode) { if (!svg.parentNode) {
@@ -263,7 +268,7 @@ export class BlockSvg
this.addSelect(); this.addSelect();
} }
/** Unselects this block. Unhighlights the blockv visually. */ /** Unselects this block. Unhighlights the block visually. */
unselect() { unselect() {
if (this.isShadow()) { if (this.isShadow()) {
this.getParent()?.unselect(); this.getParent()?.unselect();
@@ -362,8 +367,8 @@ export class BlockSvg
const eventsEnabled = eventUtils.isEnabled(); const eventsEnabled = eventUtils.isEnabled();
let event: BlockMove | null = null; let event: BlockMove | null = null;
if (eventsEnabled) { if (eventsEnabled) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE)!)(this) as BlockMove; event = new (eventUtils.get(EventType.BLOCK_MOVE)!)(this) as BlockMove;
reason && event.setReason(reason); if (reason) event.setReason(reason);
} }
const delta = new Coordinate(dx, dy); const delta = new Coordinate(dx, dy);
@@ -502,14 +507,14 @@ export class BlockSvg
return; return;
} }
super.setCollapsed(collapsed); super.setCollapsed(collapsed);
this.updateCollapsed_(); this.updateCollapsed();
} }
/** /**
* Makes sure that when the block is collapsed, it is rendered correctly * Makes sure that when the block is collapsed, it is rendered correctly
* for that state. * for that state.
*/ */
private updateCollapsed_() { private updateCollapsed() {
const collapsed = this.isCollapsed(); const collapsed = this.isCollapsed();
const collapsedInputName = constants.COLLAPSED_INPUT_NAME; const collapsedInputName = constants.COLLAPSED_INPUT_NAME;
const collapsedFieldName = constants.COLLAPSED_FIELD_NAME; const collapsedFieldName = constants.COLLAPSED_FIELD_NAME;
@@ -527,11 +532,11 @@ export class BlockSvg
if (!collapsed) { if (!collapsed) {
this.updateDisabled(); this.updateDisabled();
this.removeInput(collapsedInputName); this.removeInput(collapsedInputName);
dom.removeClass(this.svgGroup_, 'blocklyCollapsed'); dom.removeClass(this.svgGroup, 'blocklyCollapsed');
return; return;
} }
dom.addClass(this.svgGroup_, 'blocklyCollapsed'); dom.addClass(this.svgGroup, 'blocklyCollapsed');
const text = this.toString(internalConstants.COLLAPSE_CHARS); const text = this.toString(internalConstants.COLLAPSE_CHARS);
const field = this.getField(collapsedFieldName); const field = this.getField(collapsedFieldName);
@@ -579,7 +584,7 @@ export class BlockSvg
* *
* @param e Pointer down event. * @param e Pointer down event.
*/ */
private onMouseDown_(e: PointerEvent) { private onMouseDown(e: PointerEvent) {
const gesture = this.workspace.getGesture(e); const gesture = this.workspace.getGesture(e);
if (gesture) { if (gesture) {
gesture.handleBlockStart(e, this); gesture.handleBlockStart(e, this);
@@ -683,7 +688,7 @@ export class BlockSvg
* @param className * @param className
*/ */
addClass(className: string) { addClass(className: string) {
dom.addClass(this.svgGroup_, className); dom.addClass(this.svgGroup, className);
} }
/** /**
@@ -692,7 +697,7 @@ export class BlockSvg
* @param className * @param className
*/ */
removeClass(className: string) { removeClass(className: string) {
dom.removeClass(this.svgGroup_, className); dom.removeClass(this.svgGroup, className);
} }
/** /**
@@ -737,9 +742,9 @@ export class BlockSvg
super.setEditable(editable); super.setEditable(editable);
if (editable) { if (editable) {
dom.removeClass(this.svgGroup_, 'blocklyNotEditable'); dom.removeClass(this.svgGroup, 'blocklyNotEditable');
} else { } else {
dom.addClass(this.svgGroup_, 'blocklyNotEditable'); dom.addClass(this.svgGroup, 'blocklyNotEditable');
} }
const icons = this.getIcons(); const icons = this.getIcons();
@@ -787,7 +792,7 @@ export class BlockSvg
* @returns The root SVG node (probably a group). * @returns The root SVG node (probably a group).
*/ */
getSvgRoot(): SVGGElement { getSvgRoot(): SVGGElement {
return this.svgGroup_; return this.svgGroup;
} }
/** /**
@@ -809,8 +814,27 @@ export class BlockSvg
blockAnimations.disposeUiEffect(this); blockAnimations.disposeUiEffect(this);
} }
// Selecting a shadow block highlights an ancestor block, but that highlight
// should be removed if the shadow block will be deleted. So, before
// deleting blocks and severing the connections between them, check whether
// doing so would delete a selected block and make sure that any associated
// parent is updated.
const selection = common.getSelected();
if (selection instanceof Block) {
let selectionAncestor: Block | null = selection;
while (selectionAncestor !== null) {
if (selectionAncestor === this) {
// The block to be deleted contains the selected block, so remove any
// selection highlight associated with the selected block before
// deleting them.
selection.unselect();
}
selectionAncestor = selectionAncestor.getParent();
}
}
super.dispose(!!healStack); super.dispose(!!healStack);
dom.removeNode(this.svgGroup_); dom.removeNode(this.svgGroup);
} }
/** /**
@@ -1090,9 +1114,9 @@ export class BlockSvg
super.setDeletable(deletable); super.setDeletable(deletable);
if (deletable) { if (deletable) {
dom.removeClass(this.svgGroup_, 'blocklyNotDeletable'); dom.removeClass(this.svgGroup, 'blocklyNotDeletable');
} else { } else {
dom.addClass(this.svgGroup_, 'blocklyNotDeletable'); dom.addClass(this.svgGroup, 'blocklyNotDeletable');
} }
} }
@@ -1180,7 +1204,7 @@ export class BlockSvg
.getBlockStyle(blockStyleName); .getBlockStyle(blockStyleName);
if (this.styleName_) { if (this.styleName_) {
dom.removeClass(this.svgGroup_, this.styleName_); dom.removeClass(this.svgGroup, this.styleName_);
} }
if (blockStyle) { if (blockStyle) {
@@ -1192,7 +1216,7 @@ export class BlockSvg
this.applyColour(); this.applyColour();
dom.addClass(this.svgGroup_, blockStyleName); dom.addClass(this.svgGroup, blockStyleName);
this.styleName_ = blockStyleName; this.styleName_ = blockStyleName;
} else { } else {
throw Error('Invalid style name: ' + blockStyleName); throw Error('Invalid style name: ' + blockStyleName);
@@ -1204,10 +1228,11 @@ export class BlockSvg
* <g> tags do not respect z-index so SVG renders them in the * <g> tags do not respect z-index so SVG renders them in the
* order that they are in the DOM. By placing this block first within the * order that they are in the DOM. By placing this block first within the
* block group's <g>, it will render on top of any other blocks. * block group's <g>, it will render on top of any other blocks.
* Use sparingly, this method is expensive because it reorders the DOM
* nodes.
* *
* @param blockOnly: True to only move this block to the front without * @param blockOnly True to only move this block to the front without
* adjusting its parents. * adjusting its parents.
* @internal
*/ */
bringToFront(blockOnly = false) { bringToFront(blockOnly = false) {
/* eslint-disable-next-line @typescript-eslint/no-this-alias */ /* eslint-disable-next-line @typescript-eslint/no-this-alias */
@@ -1484,9 +1509,9 @@ export class BlockSvg
if (conn.isConnected() && neighbour.isConnected()) continue; if (conn.isConnected() && neighbour.isConnected()) continue;
if (conn.isSuperior()) { if (conn.isSuperior()) {
neighbour.bumpAwayFrom(conn); neighbour.bumpAwayFrom(conn, /* initiatedByThis = */ false);
} else { } else {
conn.bumpAwayFrom(neighbour); conn.bumpAwayFrom(neighbour, /* initiatedByThis = */ true);
} }
} }
} }
@@ -1577,7 +1602,7 @@ export class BlockSvg
dom.startTextWidthCache(); dom.startTextWidthCache();
if (this.isCollapsed()) { if (this.isCollapsed()) {
this.updateCollapsed_(); this.updateCollapsed();
} }
if (!this.isEnabled()) { if (!this.isEnabled()) {

View File

@@ -17,13 +17,17 @@ import './events/events_var_create.js';
import {Block} from './block.js'; import {Block} from './block.js';
import * as blockAnimations from './block_animations.js'; import * as blockAnimations from './block_animations.js';
import {BlockFlyoutInflater} from './block_flyout_inflater.js';
import {BlockSvg} from './block_svg.js'; import {BlockSvg} from './block_svg.js';
import {BlocklyOptions} from './blockly_options.js'; import {BlocklyOptions} from './blockly_options.js';
import {Blocks} from './blocks.js'; import {Blocks} from './blocks.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import * as bubbles from './bubbles.js'; import * as bubbles from './bubbles.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
import * as bumpObjects from './bump_objects.js'; import * as bumpObjects from './bump_objects.js';
import {ButtonFlyoutInflater} from './button_flyout_inflater.js';
import * as clipboard from './clipboard.js'; import * as clipboard from './clipboard.js';
import * as comments from './comments.js';
import * as common from './common.js'; import * as common from './common.js';
import {ComponentManager} from './component_manager.js'; import {ComponentManager} from './component_manager.js';
import {config} from './config.js'; import {config} from './config.js';
@@ -31,15 +35,15 @@ import {Connection} from './connection.js';
import {ConnectionChecker} from './connection_checker.js'; import {ConnectionChecker} from './connection_checker.js';
import {ConnectionDB} from './connection_db.js'; import {ConnectionDB} from './connection_db.js';
import {ConnectionType} from './connection_type.js'; import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js';
import * as ContextMenu from './contextmenu.js'; import * as ContextMenu from './contextmenu.js';
import * as ContextMenuItems from './contextmenu_items.js'; import * as ContextMenuItems from './contextmenu_items.js';
import {ContextMenuRegistry} from './contextmenu_registry.js'; import {ContextMenuRegistry} from './contextmenu_registry.js';
import * as comments from './comments.js';
import * as Css from './css.js'; import * as Css from './css.js';
import {DeleteArea} from './delete_area.js'; import {DeleteArea} from './delete_area.js';
import * as dialog from './dialog.js'; import * as dialog from './dialog.js';
import * as dragging from './dragging.js';
import {DragTarget} from './drag_target.js'; import {DragTarget} from './drag_target.js';
import * as dragging from './dragging.js';
import * as dropDownDiv from './dropdowndiv.js'; import * as dropDownDiv from './dropdowndiv.js';
import * as Events from './events/events.js'; import * as Events from './events/events.js';
import * as Extensions from './extensions.js'; import * as Extensions from './extensions.js';
@@ -97,22 +101,21 @@ import {
} from './field_variable.js'; } from './field_variable.js';
import {Flyout, FlyoutItem} from './flyout_base.js'; import {Flyout, FlyoutItem} from './flyout_base.js';
import {FlyoutButton} from './flyout_button.js'; import {FlyoutButton} from './flyout_button.js';
import {FlyoutSeparator} from './flyout_separator.js';
import {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
import {BlockFlyoutInflater} from './block_flyout_inflater.js';
import {ButtonFlyoutInflater} from './button_flyout_inflater.js';
import {LabelFlyoutInflater} from './label_flyout_inflater.js';
import {SeparatorFlyoutInflater} from './separator_flyout_inflater.js';
import {HorizontalFlyout} from './flyout_horizontal.js'; import {HorizontalFlyout} from './flyout_horizontal.js';
import {FlyoutMetricsManager} from './flyout_metrics_manager.js'; import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
import {FlyoutSeparator} from './flyout_separator.js';
import {VerticalFlyout} from './flyout_vertical.js'; import {VerticalFlyout} from './flyout_vertical.js';
import {CodeGenerator} from './generator.js'; import {CodeGenerator} from './generator.js';
import {Gesture} from './gesture.js'; import {Gesture} from './gesture.js';
import {Grid} from './grid.js'; import {Grid} from './grid.js';
import * as icons from './icons.js'; import * as icons from './icons.js';
import {inject} from './inject.js'; import {inject} from './inject.js';
import {Input} from './inputs/input.js';
import * as inputs from './inputs.js'; import * as inputs from './inputs.js';
import {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
import {LabelFlyoutInflater} from './label_flyout_inflater.js';
import {SeparatorFlyoutInflater} from './separator_flyout_inflater.js';
import {Input} from './inputs/input.js';
import {InsertionMarkerPreviewer} from './insertion_marker_previewer.js'; import {InsertionMarkerPreviewer} from './insertion_marker_previewer.js';
import {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import {IASTNodeLocation} from './interfaces/i_ast_node_location.js';
import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
@@ -125,16 +128,16 @@ import {IComponent} from './interfaces/i_component.js';
import {IConnectionChecker} from './interfaces/i_connection_checker.js'; import {IConnectionChecker} from './interfaces/i_connection_checker.js';
import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js'; import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js';
import {IContextMenu} from './interfaces/i_contextmenu.js'; import {IContextMenu} from './interfaces/i_contextmenu.js';
import {ICopyable, isCopyable, ICopyData} from './interfaces/i_copyable.js'; import {ICopyData, ICopyable, isCopyable} from './interfaces/i_copyable.js';
import {IDeletable, isDeletable} from './interfaces/i_deletable.js'; import {IDeletable, isDeletable} from './interfaces/i_deletable.js';
import {IDeleteArea} from './interfaces/i_delete_area.js'; import {IDeleteArea} from './interfaces/i_delete_area.js';
import {IDragTarget} from './interfaces/i_drag_target.js'; import {IDragTarget} from './interfaces/i_drag_target.js';
import {IDragger} from './interfaces/i_dragger.js';
import { import {
IDragStrategy,
IDraggable, IDraggable,
isDraggable, isDraggable,
IDragStrategy,
} from './interfaces/i_draggable.js'; } from './interfaces/i_draggable.js';
import {IDragger} from './interfaces/i_dragger.js';
import {IFlyout} from './interfaces/i_flyout.js'; import {IFlyout} from './interfaces/i_flyout.js';
import {IHasBubble, hasBubble} from './interfaces/i_has_bubble.js'; import {IHasBubble, hasBubble} from './interfaces/i_has_bubble.js';
import {IIcon, isIcon} from './interfaces/i_icon.js'; import {IIcon, isIcon} from './interfaces/i_icon.js';
@@ -155,35 +158,33 @@ import {ISerializable, isSerializable} from './interfaces/i_serializable.js';
import {IStyleable} from './interfaces/i_styleable.js'; import {IStyleable} from './interfaces/i_styleable.js';
import {IToolbox} from './interfaces/i_toolbox.js'; import {IToolbox} from './interfaces/i_toolbox.js';
import {IToolboxItem} from './interfaces/i_toolbox_item.js'; import {IToolboxItem} from './interfaces/i_toolbox_item.js';
import {IVariableMap} from './interfaces/i_variable_map.js';
import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js';
import { import {
IVariableBackedParameterModel, IVariableBackedParameterModel,
isVariableBackedParameterModel, isVariableBackedParameterModel,
} from './interfaces/i_variable_backed_parameter_model.js'; } from './interfaces/i_variable_backed_parameter_model.js';
import {IVariableMap} from './interfaces/i_variable_map.js';
import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js';
import * as internalConstants from './internal_constants.js'; import * as internalConstants from './internal_constants.js';
import {ASTNode} from './keyboard_nav/ast_node.js'; import {ASTNode} from './keyboard_nav/ast_node.js';
import {BasicCursor} from './keyboard_nav/basic_cursor.js'; import {BasicCursor} from './keyboard_nav/basic_cursor.js';
import {Cursor} from './keyboard_nav/cursor.js'; import {Cursor} from './keyboard_nav/cursor.js';
import {Marker} from './keyboard_nav/marker.js'; import {Marker} from './keyboard_nav/marker.js';
import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js'; import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js';
import {MarkerManager} from './marker_manager.js';
import type {LayerManager} from './layer_manager.js'; import type {LayerManager} from './layer_manager.js';
import * as layers from './layers.js'; import * as layers from './layers.js';
import {MarkerManager} from './marker_manager.js';
import {Menu} from './menu.js'; import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js'; import {MenuItem} from './menuitem.js';
import {MetricsManager} from './metrics_manager.js'; import {MetricsManager} from './metrics_manager.js';
import {Msg, setLocale} from './msg.js'; import {Msg, setLocale} from './msg.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
import {Names} from './names.js'; import {Names} from './names.js';
import {Options} from './options.js'; import {Options} from './options.js';
import * as uiPosition from './positionable_helpers.js'; import * as uiPosition from './positionable_helpers.js';
import * as Procedures from './procedures.js'; import * as Procedures from './procedures.js';
import * as registry from './registry.js'; import * as registry from './registry.js';
import {RenderedConnection} from './rendered_connection.js';
import * as renderManagement from './render_management.js'; import * as renderManagement from './render_management.js';
import {RenderedConnection} from './rendered_connection.js';
import * as blockRendering from './renderers/common/block_rendering.js'; import * as blockRendering from './renderers/common/block_rendering.js';
import * as constants from './constants.js';
import * as geras from './renderers/geras/geras.js'; import * as geras from './renderers/geras/geras.js';
import * as thrasos from './renderers/thrasos/thrasos.js'; import * as thrasos from './renderers/thrasos/thrasos.js';
import * as zelos from './renderers/zelos/zelos.js'; import * as zelos from './renderers/zelos/zelos.js';
@@ -426,181 +427,205 @@ Names.prototype.populateProcedures = function (
// clang-format on // clang-format on
// Re-export submodules that no longer declareLegacyNamespace. // Re-export submodules that no longer declareLegacyNamespace.
export {browserEvents};
export {ContextMenu};
export {ContextMenuItems};
export {Css};
export {Events};
export {Extensions};
export {Procedures};
export {Procedures as procedures};
export {ShortcutItems};
export {Themes};
export {Tooltip};
export {Touch};
export {Variables};
export {VariablesDynamic};
export {WidgetDiv};
export {Xml};
export {blockAnimations};
export {blockRendering};
export {bumpObjects};
export {clipboard};
export {common};
export {constants};
export {dialog};
export {fieldRegistry};
export {geras};
export {registry};
export {thrasos};
export {uiPosition};
export {utils};
export {zelos};
export {ASTNode};
export {BasicCursor};
export {Block};
export {BlocklyOptions};
export {BlockSvg};
export {Blocks};
export {bubbles};
export {CollapsibleToolboxCategory};
export {ComponentManager};
export {Connection};
export {ConnectionType};
export {ConnectionChecker};
export {ConnectionDB};
export {ContextMenuRegistry};
export {comments};
export {Cursor};
export {DeleteArea};
export {dragging};
export {DragTarget};
export const DropDownDiv = dropDownDiv;
export {Field, FieldConfig, FieldValidator, UnattachedFieldError};
export { export {
ASTNode,
BasicCursor,
Block,
BlockSvg,
BlocklyOptions,
Blocks,
CollapsibleToolboxCategory,
ComponentManager,
Connection,
ConnectionChecker,
ConnectionDB,
ConnectionType,
ContextMenu,
ContextMenuItems,
ContextMenuRegistry,
Css,
Cursor,
DeleteArea,
DragTarget,
Events,
Extensions,
Procedures,
ShortcutItems,
Themes,
Tooltip,
Touch,
Variables,
VariablesDynamic,
WidgetDiv,
Xml,
blockAnimations,
blockRendering,
browserEvents,
bubbles,
bumpObjects,
clipboard,
comments,
common,
constants,
dialog,
dragging,
fieldRegistry,
geras,
Procedures as procedures,
registry,
thrasos,
uiPosition,
utils,
zelos,
};
export const DropDownDiv = dropDownDiv;
export {
BlockFlyoutInflater,
ButtonFlyoutInflater,
CodeGenerator,
CodeGenerator,
Field,
FieldCheckbox, FieldCheckbox,
FieldCheckboxConfig, FieldCheckboxConfig,
FieldCheckboxFromJsonConfig, FieldCheckboxFromJsonConfig,
FieldCheckboxValidator, FieldCheckboxValidator,
}; FieldConfig,
export {
FieldDropdown, FieldDropdown,
FieldDropdownConfig, FieldDropdownConfig,
FieldDropdownFromJsonConfig, FieldDropdownFromJsonConfig,
FieldDropdownValidator, FieldDropdownValidator,
ImageProperties, FieldImage,
MenuGenerator, FieldImage,
MenuGeneratorFunction, FieldImageConfig,
MenuOption, FieldImageConfig,
}; FieldImageFromJsonConfig,
export {FieldImage, FieldImageConfig, FieldImageFromJsonConfig}; FieldImageFromJsonConfig,
export {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig}; FieldLabel,
export {FieldLabelSerializable}; FieldLabel,
export { FieldLabelConfig,
FieldLabelConfig,
FieldLabelFromJsonConfig,
FieldLabelFromJsonConfig,
FieldLabelSerializable,
FieldLabelSerializable,
FieldNumber, FieldNumber,
FieldNumberConfig, FieldNumberConfig,
FieldNumberFromJsonConfig, FieldNumberFromJsonConfig,
FieldNumberValidator, FieldNumberValidator,
};
export {
FieldTextInput, FieldTextInput,
FieldTextInputConfig, FieldTextInputConfig,
FieldTextInputFromJsonConfig, FieldTextInputFromJsonConfig,
FieldTextInputValidator, FieldTextInputValidator,
}; FieldValidator,
export {
FieldVariable, FieldVariable,
FieldVariableConfig, FieldVariableConfig,
FieldVariableFromJsonConfig, FieldVariableFromJsonConfig,
FieldVariableValidator, FieldVariableValidator,
Flyout,
FlyoutButton,
FlyoutItem,
FlyoutMetricsManager,
FlyoutSeparator,
CodeGenerator as Generator,
Gesture,
Grid,
HorizontalFlyout,
IASTNodeLocation,
IASTNodeLocationSvg,
IASTNodeLocationWithBlock,
IAutoHideable,
IBoundedElement,
IBubble,
ICollapsibleToolboxItem,
IComponent,
IConnectionChecker,
IConnectionPreviewer,
IContextMenu,
ICopyData,
ICopyable,
IDeletable,
IDeleteArea,
IDragStrategy,
IDragTarget,
IDraggable,
IDragger,
IFlyout,
IFlyoutInflater,
IHasBubble,
IIcon,
IKeyboardAccessible,
IMetricsManager,
IMovable,
IObservable,
IPaster,
IPositionable,
IRegistrable,
IRenderedElement,
ISelectable,
ISelectableToolboxItem,
ISerializable,
IStyleable,
IToolbox,
IToolboxItem,
IVariableBackedParameterModel,
IVariableMap,
IVariableModel,
IVariableState,
ImageProperties,
Input,
InsertionMarkerPreviewer,
LabelFlyoutInflater,
LayerManager,
Marker,
MarkerManager,
Menu,
MenuGenerator,
MenuGeneratorFunction,
MenuItem,
MenuOption,
MetricsManager,
Msg,
Names,
Options,
RenderedConnection,
Scrollbar,
ScrollbarPair,
SeparatorFlyoutInflater,
ShortcutRegistry,
TabNavigateCursor,
Theme,
ThemeManager,
Toolbox,
ToolboxCategory,
ToolboxItem,
ToolboxSeparator,
Trashcan,
UnattachedFieldError,
VariableMap,
VariableModel,
VerticalFlyout,
Workspace,
WorkspaceAudio,
WorkspaceDragger,
WorkspaceSvg,
ZoomControls,
config,
hasBubble,
icons,
inject,
inputs,
isCopyable,
isDeletable,
isDraggable,
isIcon,
isObservable,
isPaster,
isRenderedElement,
isSelectable,
isSerializable,
isVariableBackedParameterModel,
layers,
renderManagement,
serialization,
setLocale,
}; };
export {Flyout, FlyoutItem};
export {FlyoutButton};
export {FlyoutMetricsManager};
export {FlyoutSeparator};
export {IFlyoutInflater};
export {BlockFlyoutInflater};
export {ButtonFlyoutInflater};
export {LabelFlyoutInflater};
export {SeparatorFlyoutInflater};
export {CodeGenerator};
export {CodeGenerator as Generator}; // Deprecated name, October 2022.
export {Gesture};
export {Grid};
export {HorizontalFlyout};
export {IASTNodeLocation};
export {IASTNodeLocationSvg};
export {IASTNodeLocationWithBlock};
export {IAutoHideable};
export {IBoundedElement};
export {IBubble};
export {ICollapsibleToolboxItem};
export {IComponent};
export {IConnectionChecker};
export {IConnectionPreviewer};
export {IContextMenu};
export {icons};
export {ICopyable, isCopyable, ICopyData};
export {IDeletable, isDeletable};
export {IDeleteArea};
export {IDragTarget};
export {IDragger};
export {IDraggable, isDraggable, IDragStrategy};
export {IFlyout};
export {IHasBubble, hasBubble};
export {IIcon, isIcon};
export {IKeyboardAccessible};
export {IMetricsManager};
export {IMovable};
export {Input};
export {inputs};
export {InsertionMarkerPreviewer};
export {IObservable, isObservable};
export {IPaster, isPaster};
export {IPositionable};
export {IRegistrable};
export {IRenderedElement, isRenderedElement};
export {ISelectable, isSelectable};
export {ISelectableToolboxItem};
export {ISerializable, isSerializable};
export {IStyleable};
export {IToolbox};
export {IToolboxItem};
export {IVariableMap};
export {IVariableModel};
export {IVariableState};
export {IVariableBackedParameterModel, isVariableBackedParameterModel};
export {Marker};
export {MarkerManager};
export {LayerManager};
export {Menu};
export {MenuItem};
export {MetricsManager};
export {Msg, setLocale};
export {Names};
export {Options};
export {RenderedConnection};
export {renderManagement};
export {Scrollbar};
export {ScrollbarPair};
export {ShortcutRegistry};
export {TabNavigateCursor};
export {Theme};
export {ThemeManager};
export {Toolbox};
export {ToolboxCategory};
export {ToolboxItem};
export {ToolboxSeparator};
export {Trashcan};
export {VariableMap};
export {VariableModel};
export {VerticalFlyout};
export {Workspace};
export {WorkspaceAudio};
export {WorkspaceDragger};
export {WorkspaceSvg};
export {ZoomControls};
export {config};
export {inject};
export {serialization};
export {layers};

View File

@@ -6,9 +6,9 @@
// Former goog.module ID: Blockly.BlocklyOptions // Former goog.module ID: Blockly.BlocklyOptions
import type {Theme, ITheme} from './theme.js'; import type {ITheme, Theme} from './theme.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import type {ToolboxDefinition} from './utils/toolbox.js'; import type {ToolboxDefinition} from './utils/toolbox.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/** /**
* Blockly options. * Blockly options.

View File

@@ -6,6 +6,10 @@
// Former goog.module ID: Blockly.browserEvents // Former goog.module ID: Blockly.browserEvents
// Theoretically we could figure out a way to type the event params correctly,
// but it's not high priority.
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import * as Touch from './touch.js'; import * as Touch from './touch.js';
import * as userAgent from './utils/useragent.js'; import * as userAgent from './utils/useragent.js';
@@ -47,7 +51,7 @@ const PAGE_MODE_MULTIPLIER = 125;
export function conditionalBind( export function conditionalBind(
node: EventTarget, node: EventTarget,
name: string, name: string,
thisObject: Object | null, thisObject: object | null,
func: Function, func: Function,
opt_noCaptureIdentifier?: boolean, opt_noCaptureIdentifier?: boolean,
): Data { ): Data {
@@ -96,7 +100,7 @@ export function conditionalBind(
export function bind( export function bind(
node: EventTarget, node: EventTarget,
name: string, name: string,
thisObject: Object | null, thisObject: object | null,
func: Function, func: Function,
): Data { ): Data {
/** /**

View File

@@ -5,8 +5,8 @@
*/ */
import {Bubble} from './bubbles/bubble.js'; import {Bubble} from './bubbles/bubble.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
import {TextBubble} from './bubbles/text_bubble.js'; import {TextBubble} from './bubbles/text_bubble.js';
import {TextInputBubble} from './bubbles/textinput_bubble.js'; import {TextInputBubble} from './bubbles/textinput_bubble.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
export {Bubble, TextBubble, TextInputBubble, MiniWorkspaceBubble}; export {Bubble, MiniWorkspaceBubble, TextBubble, TextInputBubble};

View File

@@ -4,21 +4,21 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {ISelectable} from '../blockly.js';
import * as browserEvents from '../browser_events.js'; import * as browserEvents from '../browser_events.js';
import * as common from '../common.js';
import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js'; import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js';
import {IBubble} from '../interfaces/i_bubble.js'; import {IBubble} from '../interfaces/i_bubble.js';
import {ContainerRegion} from '../metrics_manager.js'; import {ContainerRegion} from '../metrics_manager.js';
import {Scrollbar} from '../scrollbar.js'; import {Scrollbar} from '../scrollbar.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import * as idGenerator from '../utils/idgenerator.js';
import * as math from '../utils/math.js'; import * as math from '../utils/math.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import {WorkspaceSvg} from '../workspace_svg.js'; import {WorkspaceSvg} from '../workspace_svg.js';
import * as common from '../common.js';
import {ISelectable} from '../blockly.js';
import * as idGenerator from '../utils/idgenerator.js';
/** /**
* The abstract pop-up bubble class. This creates a UI that looks like a speech * The abstract pop-up bubble class. This creates a UI that looks like a speech
@@ -208,9 +208,10 @@ export abstract class Bubble implements IBubble, ISelectable {
this.background.setAttribute('fill', colour); this.background.setAttribute('fill', colour);
} }
/** Passes the pointer event off to the gesture system. */ /** Brings the bubble to the front and passes the pointer event off to the gesture system. */
private onMouseDown(e: PointerEvent) { private onMouseDown(e: PointerEvent) {
this.workspace.getGesture(e)?.handleBubbleStart(e, this); this.workspace.getGesture(e)?.handleBubbleStart(e, this);
this.bringToFront();
common.setSelected(this); common.setSelected(this);
} }

View File

@@ -4,16 +4,16 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {Abstract as AbstractEvent} from '../events/events_abstract.js';
import type {BlocklyOptions} from '../blockly_options.js'; import type {BlocklyOptions} from '../blockly_options.js';
import {Bubble} from './bubble.js'; import {Abstract as AbstractEvent} from '../events/events_abstract.js';
import {Options} from '../options.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import {Options} from '../options.js';
import {Svg} from '../utils/svg.js';
import type {Rect} from '../utils/rect.js'; import type {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js';
import type {WorkspaceSvg} from '../workspace_svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js';
import {Bubble} from './bubble.js';
/** /**
* A bubble that contains a mini-workspace which can hold arbitrary blocks. * A bubble that contains a mini-workspace which can hold arbitrary blocks.

View File

@@ -4,13 +4,13 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {Bubble} from './bubble.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import {WorkspaceSvg} from '../workspace_svg.js'; import {WorkspaceSvg} from '../workspace_svg.js';
import {Bubble} from './bubble.js';
/** /**
* A bubble that displays non-editable text. Used by the warning icon. * A bubble that displays non-editable text. Used by the warning icon.

View File

@@ -4,16 +4,17 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {Bubble} from './bubble.js';
import {Coordinate} from '../utils/coordinate.js';
import * as Css from '../css.js'; import * as Css from '../css.js';
import * as touch from '../touch.js';
import {browserEvents} from '../utils.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import * as drag from '../utils/drag.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import * as touch from '../touch.js';
import {WorkspaceSvg} from '../workspace_svg.js'; import {WorkspaceSvg} from '../workspace_svg.js';
import {browserEvents} from '../utils.js'; import {Bubble} from './bubble.js';
/** /**
* A bubble that displays editable text. It can also be resized by the user. * A bubble that displays editable text. It can also be resized by the user.
@@ -65,6 +66,8 @@ export class TextInputBubble extends Bubble {
20 + Bubble.DOUBLE_BORDER, 20 + Bubble.DOUBLE_BORDER,
); );
private editable = true;
/** /**
* @param workspace The workspace this bubble belongs to. * @param workspace The workspace this bubble belongs to.
* @param anchor The anchor location of the thing this bubble is attached to. * @param anchor The anchor location of the thing this bubble is attached to.
@@ -98,6 +101,21 @@ export class TextInputBubble extends Bubble {
this.onTextChange(); this.onTextChange();
} }
/** Sets whether or not the text in the bubble is editable. */
setEditable(editable: boolean) {
this.editable = editable;
if (this.editable) {
this.textArea.removeAttribute('readonly');
} else {
this.textArea.setAttribute('readonly', '');
}
}
/** Returns whether or not the text in the bubble is editable. */
isEditable(): boolean {
return this.editable;
}
/** Adds a change listener to be notified when this bubble's text changes. */ /** Adds a change listener to be notified when this bubble's text changes. */
addTextChangeListener(listener: () => void) { addTextChangeListener(listener: () => void) {
this.textChangeListeners.push(listener); this.textChangeListeners.push(listener);
@@ -247,7 +265,8 @@ export class TextInputBubble extends Bubble {
return; return;
} }
this.workspace.startDrag( drag.start(
this.workspace,
e, e,
new Coordinate( new Coordinate(
this.workspace.RTL ? -this.getSize().width : this.getSize().width, this.workspace.RTL ? -this.getSize().width : this.getSize().width,
@@ -287,7 +306,7 @@ export class TextInputBubble extends Bubble {
/** Handles pointer move events on the resize target. */ /** Handles pointer move events on the resize target. */
private onResizePointerMove(e: PointerEvent) { private onResizePointerMove(e: PointerEvent) {
const delta = this.workspace.moveDrag(e); const delta = drag.move(this.workspace, e);
this.setSize( this.setSize(
new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y), new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y),
false, false,

View File

@@ -13,7 +13,8 @@ import type {BlockMove} from './events/events_block_move.js';
import type {CommentCreate} from './events/events_comment_create.js'; import type {CommentCreate} from './events/events_comment_create.js';
import type {CommentMove} from './events/events_comment_move.js'; import type {CommentMove} from './events/events_comment_move.js';
import type {CommentResize} from './events/events_comment_resize.js'; import type {CommentResize} from './events/events_comment_resize.js';
import type {ViewportChange} from './events/events_viewport.js'; import {isViewportChange} from './events/predicates.js';
import {BUMP_EVENTS, EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js';
import type {ContainerRegion} from './metrics_manager.js'; import type {ContainerRegion} from './metrics_manager.js';
@@ -99,7 +100,7 @@ export function bumpIntoBoundsHandler(
return; return;
} }
if (eventUtils.BUMP_EVENTS.includes(e.type ?? '')) { if (BUMP_EVENTS.includes(e.type ?? '')) {
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true); const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
// Triggered by move/create event // Triggered by move/create event
@@ -127,13 +128,8 @@ export function bumpIntoBoundsHandler(
); );
} }
eventUtils.setGroup(existingGroup); eventUtils.setGroup(existingGroup);
} else if (e.type === eventUtils.VIEWPORT_CHANGE) { } else if (isViewportChange(e)) {
const viewportEvent = e as ViewportChange; if (e.scale && e.oldScale && e.scale > e.oldScale) {
if (
viewportEvent.scale &&
viewportEvent.oldScale &&
viewportEvent.scale > viewportEvent.oldScale
) {
bumpTopObjectsIntoBounds(workspace); bumpTopObjectsIntoBounds(workspace);
} }
} }
@@ -155,16 +151,16 @@ function extractObjectFromEvent(
): IBoundedElement | null { ): IBoundedElement | null {
let object = null; let object = null;
switch (e.type) { switch (e.type) {
case eventUtils.BLOCK_CREATE: case EventType.BLOCK_CREATE:
case eventUtils.BLOCK_MOVE: case EventType.BLOCK_MOVE:
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!); object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!);
if (object) { if (object) {
object = object.getRootBlock(); object = object.getRootBlock();
} }
break; break;
case eventUtils.COMMENT_CREATE: case EventType.COMMENT_CREATE:
case eventUtils.COMMENT_MOVE: case EventType.COMMENT_MOVE:
case eventUtils.COMMENT_RESIZE: case EventType.COMMENT_RESIZE:
object = workspace.getCommentById( object = workspace.getCommentById(
(e as CommentCreate | CommentMove | CommentResize).commentId!, (e as CommentCreate | CommentMove | CommentResize).commentId!,
) as RenderedWorkspaceComment; ) as RenderedWorkspaceComment;

View File

@@ -6,12 +6,12 @@
// Former goog.module ID: Blockly.clipboard // Former goog.module ID: Blockly.clipboard
import type {ICopyData, ICopyable} from './interfaces/i_copyable.js'; import {BlockCopyData, BlockPaster} from './clipboard/block_paster.js';
import {BlockPaster, BlockCopyData} from './clipboard/block_paster.js';
import * as globalRegistry from './registry.js';
import {WorkspaceSvg} from './workspace_svg.js';
import * as registry from './clipboard/registry.js'; import * as registry from './clipboard/registry.js';
import type {ICopyData, ICopyable} from './interfaces/i_copyable.js';
import * as globalRegistry from './registry.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import {WorkspaceSvg} from './workspace_svg.js';
/** Metadata about the object that is currently on the clipboard. */ /** Metadata about the object that is currently on the clipboard. */
let stashedCopyData: ICopyData | null = null; let stashedCopyData: ICopyData | null = null;
@@ -110,4 +110,4 @@ export const TEST_ONLY = {
copyInternal, copyInternal,
}; };
export {BlockPaster, BlockCopyData, registry}; export {BlockCopyData, BlockPaster, registry};

View File

@@ -5,15 +5,16 @@
*/ */
import {BlockSvg} from '../block_svg.js'; import {BlockSvg} from '../block_svg.js';
import * as registry from './registry.js'; import * as common from '../common.js';
import {config} from '../config.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {ICopyData} from '../interfaces/i_copyable.js'; import {ICopyData} from '../interfaces/i_copyable.js';
import {IPaster} from '../interfaces/i_paster.js'; import {IPaster} from '../interfaces/i_paster.js';
import {State, append} from '../serialization/blocks.js'; import {State, append} from '../serialization/blocks.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import {WorkspaceSvg} from '../workspace_svg.js'; import {WorkspaceSvg} from '../workspace_svg.js';
import * as eventUtils from '../events/utils.js'; import * as registry from './registry.js';
import {config} from '../config.js';
import * as common from '../common.js';
export class BlockPaster implements IPaster<BlockCopyData, BlockSvg> { export class BlockPaster implements IPaster<BlockCopyData, BlockSvg> {
static TYPE = 'block'; static TYPE = 'block';
@@ -52,7 +53,7 @@ export class BlockPaster implements IPaster<BlockCopyData, BlockSvg> {
if (!block) return block; if (!block) return block;
if (eventUtils.isEnabled() && !block.isShadow()) { if (eventUtils.isEnabled() && !block.isShadow()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(block)); eventUtils.fire(new (eventUtils.get(EventType.BLOCK_CREATE))(block));
} }
common.setSelected(block); common.setSelected(block);
return block; return block;

View File

@@ -4,15 +4,16 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {IPaster} from '../interfaces/i_paster.js'; import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js';
import * as common from '../common.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {ICopyData} from '../interfaces/i_copyable.js'; import {ICopyData} from '../interfaces/i_copyable.js';
import {IPaster} from '../interfaces/i_paster.js';
import * as commentSerialiation from '../serialization/workspace_comments.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import {WorkspaceSvg} from '../workspace_svg.js'; import {WorkspaceSvg} from '../workspace_svg.js';
import * as registry from './registry.js'; import * as registry from './registry.js';
import * as commentSerialiation from '../serialization/workspace_comments.js';
import * as eventUtils from '../events/utils.js';
import * as common from '../common.js';
import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js';
export class WorkspaceCommentPaster export class WorkspaceCommentPaster
implements IPaster<WorkspaceCommentCopyData, RenderedWorkspaceComment> implements IPaster<WorkspaceCommentCopyData, RenderedWorkspaceComment>
@@ -46,7 +47,7 @@ export class WorkspaceCommentPaster
if (!comment) return null; if (!comment) return null;
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_CREATE))(comment)); eventUtils.fire(new (eventUtils.get(EventType.COMMENT_CREATE))(comment));
} }
common.setSelected(comment); common.setSelected(comment);
return comment; return comment;

View File

@@ -5,5 +5,5 @@
*/ */
export {CommentView} from './comments/comment_view.js'; export {CommentView} from './comments/comment_view.js';
export {WorkspaceComment} from './comments/workspace_comment.js';
export {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js'; export {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js';
export {WorkspaceComment} from './comments/workspace_comment.js';

View File

@@ -4,16 +4,17 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import * as dom from '../utils/dom.js';
import {Svg} from '../utils/svg.js';
import * as layers from '../layers.js';
import * as css from '../css.js';
import {Coordinate} from '../utils/coordinate.js';
import {Size} from '../utils/size.js';
import * as browserEvents from '../browser_events.js'; import * as browserEvents from '../browser_events.js';
import * as css from '../css.js';
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
import * as layers from '../layers.js';
import * as touch from '../touch.js'; import * as touch from '../touch.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import * as drag from '../utils/drag.js';
import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js';
import {WorkspaceSvg} from '../workspace_svg.js';
export class CommentView implements IRenderedElement { export class CommentView implements IRenderedElement {
/** The root group element of the comment view. */ /** The root group element of the comment view. */
@@ -528,8 +529,8 @@ export class CommentView implements IRenderedElement {
this.preResizeSize = this.getSize(); this.preResizeSize = this.getSize();
// TODO(#7926): Move this into a utils file. drag.start(
this.workspace.startDrag( this.workspace,
e, e,
new Coordinate( new Coordinate(
this.workspace.RTL ? -this.getSize().width : this.getSize().width, this.workspace.RTL ? -this.getSize().width : this.getSize().width,
@@ -573,8 +574,7 @@ export class CommentView implements IRenderedElement {
/** Resizes the comment in response to a drag on the resize handle. */ /** Resizes the comment in response to a drag on the resize handle. */
private onResizePointerMove(e: PointerEvent) { private onResizePointerMove(e: PointerEvent) {
// TODO(#7926): Move this into a utils file. const size = drag.move(this.workspace, e);
const size = this.workspace.moveDrag(e);
this.setSizeWithoutFiringEvents( this.setSizeWithoutFiringEvents(
new Size(this.workspace.RTL ? -size.x : size.x, size.y), new Size(this.workspace.RTL ? -size.x : size.x, size.y),
); );
@@ -627,6 +627,7 @@ export class CommentView implements IRenderedElement {
* event on the foldout icon. * event on the foldout icon.
*/ */
private onFoldoutDown(e: PointerEvent) { private onFoldoutDown(e: PointerEvent) {
touch.clearTouchIdentifier();
this.bringToFront(); this.bringToFront();
if (browserEvents.isRightButton(e)) { if (browserEvents.isRightButton(e)) {
e.stopPropagation(); e.stopPropagation();
@@ -747,6 +748,7 @@ export class CommentView implements IRenderedElement {
* delete icon. * delete icon.
*/ */
private onDeleteDown(e: PointerEvent) { private onDeleteDown(e: PointerEvent) {
touch.clearTouchIdentifier();
if (browserEvents.isRightButton(e)) { if (browserEvents.isRightButton(e)) {
e.stopPropagation(); e.stopPropagation();
return; return;
@@ -836,7 +838,6 @@ css.register(`
} }
.blocklyCommentTopbarBackground { .blocklyCommentTopbarBackground {
cursor: grab;
fill: var(--commentBorderColour); fill: var(--commentBorderColour);
height: 24px; height: 24px;
} }

View File

@@ -4,30 +4,31 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {WorkspaceComment} from './workspace_comment.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import {CommentView} from './comment_view.js';
import {Coordinate} from '../utils/coordinate.js';
import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js';
import {IBoundedElement} from '../interfaces/i_bounded_element.js';
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
import * as dom from '../utils/dom.js';
import {IDraggable} from '../interfaces/i_draggable.js';
import {CommentDragStrategy} from '../dragging/comment_drag_strategy.js';
import * as browserEvents from '../browser_events.js'; import * as browserEvents from '../browser_events.js';
import * as common from '../common.js';
import {ISelectable} from '../interfaces/i_selectable.js';
import {IDeletable} from '../interfaces/i_deletable.js';
import {ICopyable} from '../interfaces/i_copyable.js';
import * as commentSerialization from '../serialization/workspace_comments.js';
import { import {
WorkspaceCommentPaster,
WorkspaceCommentCopyData, WorkspaceCommentCopyData,
WorkspaceCommentPaster,
} from '../clipboard/workspace_comment_paster.js'; } from '../clipboard/workspace_comment_paster.js';
import {IContextMenu} from '../interfaces/i_contextmenu.js'; import * as common from '../common.js';
import * as contextMenu from '../contextmenu.js'; import * as contextMenu from '../contextmenu.js';
import {ContextMenuRegistry} from '../contextmenu_registry.js'; import {ContextMenuRegistry} from '../contextmenu_registry.js';
import {CommentDragStrategy} from '../dragging/comment_drag_strategy.js';
import {IBoundedElement} from '../interfaces/i_bounded_element.js';
import {IContextMenu} from '../interfaces/i_contextmenu.js';
import {ICopyable} from '../interfaces/i_copyable.js';
import {IDeletable} from '../interfaces/i_deletable.js';
import {IDraggable} from '../interfaces/i_draggable.js';
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
import {ISelectable} from '../interfaces/i_selectable.js';
import * as layers from '../layers.js';
import * as commentSerialization from '../serialization/workspace_comments.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import {CommentView} from './comment_view.js';
import {WorkspaceComment} from './workspace_comment.js';
export class RenderedWorkspaceComment export class RenderedWorkspaceComment
extends WorkspaceComment extends WorkspaceComment
@@ -213,6 +214,7 @@ export class RenderedWorkspaceComment
const gesture = this.workspace.getGesture(e); const gesture = this.workspace.getGesture(e);
if (gesture) { if (gesture) {
gesture.handleCommentStart(e, this); gesture.handleCommentStart(e, this);
this.workspace.getLayerManager()?.append(this, layers.BLOCK);
common.setSelected(this); common.setSelected(this);
} }
} }

View File

@@ -4,13 +4,14 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {Workspace} from '../workspace.js';
import {Size} from '../utils/size.js';
import {Coordinate} from '../utils/coordinate.js';
import * as idGenerator from '../utils/idgenerator.js';
import * as eventUtils from '../events/utils.js';
import {CommentMove} from '../events/events_comment_move.js'; import {CommentMove} from '../events/events_comment_move.js';
import {CommentResize} from '../events/events_comment_resize.js'; import {CommentResize} from '../events/events_comment_resize.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {Coordinate} from '../utils/coordinate.js';
import * as idGenerator from '../utils/idgenerator.js';
import {Size} from '../utils/size.js';
import {Workspace} from '../workspace.js';
import {CommentView} from './comment_view.js'; import {CommentView} from './comment_view.js';
export class WorkspaceComment { export class WorkspaceComment {
@@ -65,13 +66,13 @@ export class WorkspaceComment {
private fireCreateEvent() { private fireCreateEvent() {
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_CREATE))(this)); eventUtils.fire(new (eventUtils.get(EventType.COMMENT_CREATE))(this));
} }
} }
private fireDeleteEvent() { private fireDeleteEvent() {
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_DELETE))(this)); eventUtils.fire(new (eventUtils.get(EventType.COMMENT_DELETE))(this));
} }
} }
@@ -79,7 +80,7 @@ export class WorkspaceComment {
private fireChangeEvent(oldText: string, newText: string) { private fireChangeEvent(oldText: string, newText: string) {
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.COMMENT_CHANGE))(this, oldText, newText), new (eventUtils.get(EventType.COMMENT_CHANGE))(this, oldText, newText),
); );
} }
} }
@@ -88,7 +89,7 @@ export class WorkspaceComment {
private fireCollapseEvent(newCollapsed: boolean) { private fireCollapseEvent(newCollapsed: boolean) {
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.COMMENT_COLLAPSE))(this, newCollapsed), new (eventUtils.get(EventType.COMMENT_COLLAPSE))(this, newCollapsed),
); );
} }
} }
@@ -107,7 +108,7 @@ export class WorkspaceComment {
/** Sets the comment's size in workspace units. */ /** Sets the comment's size in workspace units. */
setSize(size: Size) { setSize(size: Size) {
const event = new (eventUtils.get(eventUtils.COMMENT_RESIZE))( const event = new (eventUtils.get(EventType.COMMENT_RESIZE))(
this, this,
) as CommentResize; ) as CommentResize;
@@ -185,7 +186,11 @@ export class WorkspaceComment {
* workspace is read-only. * workspace is read-only.
*/ */
isDeletable(): boolean { isDeletable(): boolean {
return this.isOwnDeletable() && !this.workspace.options.readOnly; return (
this.isOwnDeletable() &&
!this.isDeadOrDying() &&
!this.workspace.options.readOnly
);
} }
/** /**
@@ -198,7 +203,7 @@ export class WorkspaceComment {
/** Moves the comment to the given location in workspace coordinates. */ /** Moves the comment to the given location in workspace coordinates. */
moveTo(location: Coordinate, reason?: string[] | undefined) { moveTo(location: Coordinate, reason?: string[] | undefined) {
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( const event = new (eventUtils.get(EventType.COMMENT_MOVE))(
this, this,
) as CommentMove; ) as CommentMove;
if (reason) event.setReason(reason); if (reason) event.setReason(reason);

View File

@@ -6,14 +6,14 @@
// Former goog.module ID: Blockly.common // Former goog.module ID: Blockly.common
/* eslint-disable-next-line no-unused-vars */
import type {Block} from './block.js'; import type {Block} from './block.js';
import {ISelectable} from './blockly.js'; import {ISelectable} from './blockly.js';
import {BlockDefinition, Blocks} from './blocks.js'; import {BlockDefinition, Blocks} from './blocks.js';
import type {Connection} from './connection.js'; import type {Connection} from './connection.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js';
import type {Workspace} from './workspace.js'; import type {Workspace} from './workspace.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import * as eventUtils from './events/utils.js';
/** Database of all workspaces. */ /** Database of all workspaces. */
const WorkspaceDB_ = Object.create(null); const WorkspaceDB_ = Object.create(null);
@@ -108,7 +108,7 @@ export function getSelected(): ISelectable | null {
export function setSelected(newSelection: ISelectable | null) { export function setSelected(newSelection: ISelectable | null) {
if (selected === newSelection) return; if (selected === newSelection) return;
const event = new (eventUtils.get(eventUtils.SELECTED))( const event = new (eventUtils.get(EventType.SELECTED))(
selected?.id ?? null, selected?.id ?? null,
newSelection?.id ?? null, newSelection?.id ?? null,
newSelection?.workspace.id ?? selected?.workspace.id ?? '', newSelection?.workspace.id ?? selected?.workspace.id ?? '',

View File

@@ -23,10 +23,10 @@ class Capability<_T> {
static DRAG_TARGET = new Capability<IDragTarget>('drag_target'); static DRAG_TARGET = new Capability<IDragTarget>('drag_target');
static DELETE_AREA = new Capability<IDeleteArea>('delete_area'); static DELETE_AREA = new Capability<IDeleteArea>('delete_area');
static AUTOHIDEABLE = new Capability<IAutoHideable>('autohideable'); static AUTOHIDEABLE = new Capability<IAutoHideable>('autohideable');
private readonly name_: string; private readonly name: string;
/** @param name The name of the component capability. */ /** @param name The name of the component capability. */
constructor(name: string) { constructor(name: string) {
this.name_ = name; this.name = name;
} }
/** /**
@@ -35,7 +35,7 @@ class Capability<_T> {
* @returns The name. * @returns The name.
*/ */
toString(): string { toString(): string {
return this.name_; return this.name;
} }
} }
@@ -224,6 +224,16 @@ export class ComponentManager {
} }
export namespace ComponentManager { export namespace ComponentManager {
export enum ComponentWeight {
// The toolbox weight is lower (higher precedence) than the flyout, so that
// if both are under the pointer, the toolbox takes precedence even though
// the flyout's drag target area is large enough to include the toolbox.
TOOLBOX_WEIGHT = 0,
FLYOUT_WEIGHT = 1,
TRASHCAN_WEIGHT = 2,
ZOOM_CONTROLS_WEIGHT = 3,
}
/** An object storing component information. */ /** An object storing component information. */
export interface ComponentDatum { export interface ComponentDatum {
component: IComponent; component: IComponent;
@@ -232,4 +242,6 @@ export namespace ComponentManager {
} }
} }
export type ComponentWeight = ComponentManager.ComponentWeight;
export const ComponentWeight = ComponentManager.ComponentWeight;
export type ComponentDatum = ComponentManager.ComponentDatum; export type ComponentDatum = ComponentManager.ComponentDatum;

View File

@@ -14,6 +14,7 @@
import type {Block} from './block.js'; import type {Block} from './block.js';
import {ConnectionType} from './connection_type.js'; import {ConnectionType} from './connection_type.js';
import type {BlockMove} from './events/events_block_move.js'; import type {BlockMove} from './events/events_block_move.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import type {Input} from './inputs/input.js'; import type {Input} from './inputs/input.js';
import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js';
@@ -114,7 +115,7 @@ export class Connection implements IASTNodeLocationWithBlock {
// Connect the new connection to the parent. // Connect the new connection to the parent.
let event; let event;
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( event = new (eventUtils.get(EventType.BLOCK_MOVE))(
childBlock, childBlock,
) as BlockMove; ) as BlockMove;
event.setReason(['connect']); event.setReason(['connect']);
@@ -213,11 +214,11 @@ export class Connection implements IASTNodeLocationWithBlock {
* Called when an attempted connection fails. NOP by default (i.e. for * Called when an attempted connection fails. NOP by default (i.e. for
* headless workspaces). * headless workspaces).
* *
* @param _otherConnection Connection that this connection failed to connect * @param _superiorConnection Connection that this connection failed to connect
* to. * to. The provided connection should be the superior connection.
* @internal * @internal
*/ */
onFailedConnect(_otherConnection: Connection) {} onFailedConnect(_superiorConnection: Connection) {}
// NOP // NOP
/** /**
@@ -281,7 +282,7 @@ export class Connection implements IASTNodeLocationWithBlock {
let event; let event;
if (eventUtils.isEnabled()) { if (eventUtils.isEnabled()) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( event = new (eventUtils.get(EventType.BLOCK_MOVE))(
childConnection.getSourceBlock(), childConnection.getSourceBlock(),
) as BlockMove; ) as BlockMove;
event.setReason(['disconnect']); event.setReason(['disconnect']);

View File

@@ -9,23 +9,24 @@
import type {Block} from './block.js'; import type {Block} from './block.js';
import type {BlockSvg} from './block_svg.js'; import type {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import * as common from './common.js';
import {config} from './config.js'; import {config} from './config.js';
import * as dom from './utils/dom.js';
import type { import type {
ContextMenuOption, ContextMenuOption,
LegacyContextMenuOption, LegacyContextMenuOption,
} from './contextmenu_registry.js'; } from './contextmenu_registry.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import {Menu} from './menu.js'; import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js'; import {MenuItem} from './menuitem.js';
import * as aria from './utils/aria.js';
import {Rect} from './utils/rect.js';
import * as serializationBlocks from './serialization/blocks.js'; import * as serializationBlocks from './serialization/blocks.js';
import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js';
import {Rect} from './utils/rect.js';
import * as svgMath from './utils/svg_math.js'; import * as svgMath from './utils/svg_math.js';
import * as WidgetDiv from './widgetdiv.js'; import * as WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import * as Xml from './xml.js'; import * as Xml from './xml.js';
import * as common from './common.js';
/** /**
* Which block is the context menu attached to? * Which block is the context menu attached to?
@@ -260,7 +261,7 @@ export function callbackFactory(
eventUtils.enable(); eventUtils.enable();
} }
if (eventUtils.isEnabled() && !newBlock.isShadow()) { if (eventUtils.isEnabled() && !newBlock.isShadow()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock)); eventUtils.fire(new (eventUtils.get(EventType.BLOCK_CREATE))(newBlock));
} }
common.setSelected(newBlock); common.setSelected(newBlock);
return newBlock; return newBlock;

View File

@@ -9,12 +9,13 @@
import type {BlockSvg} from './block_svg.js'; import type {BlockSvg} from './block_svg.js';
import * as clipboard from './clipboard.js'; import * as clipboard from './clipboard.js';
import {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js'; import {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js';
import * as common from './common.js';
import {MANUALLY_DISABLED} from './constants.js';
import { import {
ContextMenuRegistry, ContextMenuRegistry,
RegistryItem, RegistryItem,
Scope, Scope,
} from './contextmenu_registry.js'; } from './contextmenu_registry.js';
import {MANUALLY_DISABLED} from './constants.js';
import * as dialog from './dialog.js'; import * as dialog from './dialog.js';
import * as Events from './events/events.js'; import * as Events from './events/events.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
@@ -23,7 +24,6 @@ import {Msg} from './msg.js';
import {StatementInput} from './renderers/zelos/zelos.js'; import {StatementInput} from './renderers/zelos/zelos.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import * as common from './common.js';
/** /**
* Option to undo previous action. * Option to undo previous action.

View File

@@ -23,7 +23,7 @@ import type {WorkspaceSvg} from './workspace_svg.js';
export class ContextMenuRegistry { export class ContextMenuRegistry {
static registry: ContextMenuRegistry; static registry: ContextMenuRegistry;
/** Registry of all registered RegistryItems, keyed by ID. */ /** Registry of all registered RegistryItems, keyed by ID. */
private registry_ = new Map<string, RegistryItem>(); private registeredItems = new Map<string, RegistryItem>();
/** Resets the existing singleton instance of ContextMenuRegistry. */ /** Resets the existing singleton instance of ContextMenuRegistry. */
constructor() { constructor() {
@@ -32,7 +32,7 @@ export class ContextMenuRegistry {
/** Clear and recreate the registry. */ /** Clear and recreate the registry. */
reset() { reset() {
this.registry_.clear(); this.registeredItems.clear();
} }
/** /**
@@ -42,10 +42,10 @@ export class ContextMenuRegistry {
* @throws {Error} if an item with the given ID already exists. * @throws {Error} if an item with the given ID already exists.
*/ */
register(item: RegistryItem) { register(item: RegistryItem) {
if (this.registry_.has(item.id)) { if (this.registeredItems.has(item.id)) {
throw Error('Menu item with ID "' + item.id + '" is already registered.'); throw Error('Menu item with ID "' + item.id + '" is already registered.');
} }
this.registry_.set(item.id, item); this.registeredItems.set(item.id, item);
} }
/** /**
@@ -55,10 +55,10 @@ export class ContextMenuRegistry {
* @throws {Error} if an item with the given ID does not exist. * @throws {Error} if an item with the given ID does not exist.
*/ */
unregister(id: string) { unregister(id: string) {
if (!this.registry_.has(id)) { if (!this.registeredItems.has(id)) {
throw new Error('Menu item with ID "' + id + '" not found.'); throw new Error('Menu item with ID "' + id + '" not found.');
} }
this.registry_.delete(id); this.registeredItems.delete(id);
} }
/** /**
@@ -66,7 +66,7 @@ export class ContextMenuRegistry {
* @returns RegistryItem or null if not found * @returns RegistryItem or null if not found
*/ */
getItem(id: string): RegistryItem | null { getItem(id: string): RegistryItem | null {
return this.registry_.get(id) ?? null; return this.registeredItems.get(id) ?? null;
} }
/** /**
@@ -85,7 +85,7 @@ export class ContextMenuRegistry {
scope: Scope, scope: Scope,
): ContextMenuOption[] { ): ContextMenuOption[] {
const menuOptions: ContextMenuOption[] = []; const menuOptions: ContextMenuOption[] = [];
for (const item of this.registry_.values()) { for (const item of this.registeredItems.values()) {
if (scopeType === item.scopeType) { if (scopeType === item.scopeType) {
const precondition = item.preconditionFn(scope); const precondition = item.preconditionFn(scope);
if (precondition !== 'hidden') { if (precondition !== 'hidden') {

View File

@@ -78,6 +78,8 @@ let content = `
position: relative; position: relative;
overflow: hidden; /* So blocks in drag surface disappear at edges */ overflow: hidden; /* So blocks in drag surface disappear at edges */
touch-action: none; touch-action: none;
user-select: none;
-webkit-user-select: none;
} }
.blocklyBlockCanvas.blocklyCanvasTransitioning, .blocklyBlockCanvas.blocklyCanvasTransitioning,
@@ -214,10 +216,6 @@ let content = `
stroke: none; stroke: none;
} }
.blocklyMultilineText {
font-family: monospace;
}
.blocklyNonEditableField>text { .blocklyNonEditableField>text {
pointer-events: none; pointer-events: none;
} }

View File

@@ -14,9 +14,9 @@
import {BlockSvg} from './block_svg.js'; import {BlockSvg} from './block_svg.js';
import {DragTarget} from './drag_target.js'; import {DragTarget} from './drag_target.js';
import {isDeletable} from './interfaces/i_deletable.js';
import type {IDeleteArea} from './interfaces/i_delete_area.js'; import type {IDeleteArea} from './interfaces/i_delete_area.js';
import type {IDraggable} from './interfaces/i_draggable.js'; import type {IDraggable} from './interfaces/i_draggable.js';
import {isDeletable} from './interfaces/i_deletable.js';
/** /**
* Abstract class for a component that can delete a block or bubble that is * Abstract class for a component that can delete a block or bubble that is

View File

@@ -4,9 +4,9 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {Dragger} from './dragging/dragger.js';
import {BlockDragStrategy} from './dragging/block_drag_strategy.js'; import {BlockDragStrategy} from './dragging/block_drag_strategy.js';
import {BubbleDragStrategy} from './dragging/bubble_drag_strategy.js'; import {BubbleDragStrategy} from './dragging/bubble_drag_strategy.js';
import {CommentDragStrategy} from './dragging/comment_drag_strategy.js'; import {CommentDragStrategy} from './dragging/comment_drag_strategy.js';
import {Dragger} from './dragging/dragger.js';
export {Dragger, BlockDragStrategy, BubbleDragStrategy, CommentDragStrategy}; export {BlockDragStrategy, BubbleDragStrategy, CommentDragStrategy, Dragger};

View File

@@ -4,24 +4,25 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {WorkspaceSvg} from '../workspace_svg.js';
import {IDragStrategy} from '../interfaces/i_draggable.js';
import {Coordinate} from '../utils.js';
import * as eventUtils from '../events/utils.js';
import {BlockSvg} from '../block_svg.js';
import {RenderedConnection} from '../rendered_connection.js';
import * as dom from '../utils/dom.js';
import * as blockAnimation from '../block_animations.js';
import {ConnectionType} from '../connection_type.js';
import * as bumpObjects from '../bump_objects.js';
import * as registry from '../registry.js';
import {IConnectionPreviewer} from '../interfaces/i_connection_previewer.js';
import {Connection} from '../connection.js';
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import * as blockAnimation from '../block_animations.js';
import {BlockSvg} from '../block_svg.js';
import * as bumpObjects from '../bump_objects.js';
import {config} from '../config.js'; import {config} from '../config.js';
import {Connection} from '../connection.js';
import {ConnectionType} from '../connection_type.js';
import type {BlockMove} from '../events/events_block_move.js'; import type {BlockMove} from '../events/events_block_move.js';
import {finishQueuedRenders} from '../render_management.js'; import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {IConnectionPreviewer} from '../interfaces/i_connection_previewer.js';
import {IDragStrategy} from '../interfaces/i_draggable.js';
import * as layers from '../layers.js'; import * as layers from '../layers.js';
import * as registry from '../registry.js';
import {finishQueuedRenders} from '../render_management.js';
import {RenderedConnection} from '../rendered_connection.js';
import {Coordinate} from '../utils.js';
import * as dom from '../utils/dom.js';
import {WorkspaceSvg} from '../workspace_svg.js';
/** Represents a nearby valid connection. */ /** Represents a nearby valid connection. */
interface ConnectionCandidate { interface ConnectionCandidate {
@@ -61,6 +62,9 @@ export class BlockDragStrategy implements IDragStrategy {
*/ */
private dragOffset = new Coordinate(0, 0); private dragOffset = new Coordinate(0, 0);
/** Was there already an event group in progress when the drag started? */
private inGroup: boolean = false;
constructor(private block: BlockSvg) { constructor(private block: BlockSvg) {
this.workspace = block.workspace; this.workspace = block.workspace;
} }
@@ -92,7 +96,8 @@ export class BlockDragStrategy implements IDragStrategy {
} }
this.dragging = true; this.dragging = true;
if (!eventUtils.getGroup()) { this.inGroup = !!eventUtils.getGroup();
if (!this.inGroup) {
eventUtils.setGroup(true); eventUtils.setGroup(true);
} }
this.fireDragStartEvent(); this.fireDragStartEvent();
@@ -173,7 +178,7 @@ export class BlockDragStrategy implements IDragStrategy {
/** Fire a UI event at the start of a block drag. */ /** Fire a UI event at the start of a block drag. */
private fireDragStartEvent() { private fireDragStartEvent() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( const event = new (eventUtils.get(EventType.BLOCK_DRAG))(
this.block, this.block,
true, true,
this.block.getDescendants(false), this.block.getDescendants(false),
@@ -183,7 +188,7 @@ export class BlockDragStrategy implements IDragStrategy {
/** Fire a UI event at the end of a block drag. */ /** Fire a UI event at the end of a block drag. */
private fireDragEndEvent() { private fireDragEndEvent() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))( const event = new (eventUtils.get(EventType.BLOCK_DRAG))(
this.block, this.block,
false, false,
this.block.getDescendants(false), this.block.getDescendants(false),
@@ -194,7 +199,7 @@ export class BlockDragStrategy implements IDragStrategy {
/** Fire a move event at the end of a block drag. */ /** Fire a move event at the end of a block drag. */
private fireMoveEvent() { private fireMoveEvent() {
if (this.block.isDeadOrDying()) return; if (this.block.isDeadOrDying()) return;
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))( const event = new (eventUtils.get(EventType.BLOCK_MOVE))(
this.block, this.block,
) as BlockMove; ) as BlockMove;
event.setReason(['drag']); event.setReason(['drag']);
@@ -379,17 +384,24 @@ export class BlockDragStrategy implements IDragStrategy {
if (this.connectionCandidate) { if (this.connectionCandidate) {
// Applying connections also rerenders the relevant blocks. // Applying connections also rerenders the relevant blocks.
this.applyConnections(this.connectionCandidate); this.applyConnections(this.connectionCandidate);
this.disposeStep();
} else { } else {
this.block.queueRender(); this.block.queueRender().then(() => this.disposeStep());
} }
if (!this.inGroup) {
eventUtils.setGroup(false);
}
}
/** Disposes of any state at the end of the drag. */
private disposeStep() {
this.block.snapToGrid(); this.block.snapToGrid();
// Must dispose after connections are applied to not break the dynamic // Must dispose after connections are applied to not break the dynamic
// connections plugin. See #7859 // connections plugin. See #7859
this.connectionPreviewer!.dispose(); this.connectionPreviewer!.dispose();
this.workspace.setResizesEnabled(true); this.workspace.setResizesEnabled(true);
eventUtils.setGroup(false);
} }
/** Connects the given candidate connections. */ /** Connects the given candidate connections. */

View File

@@ -4,15 +4,18 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {IDragStrategy} from '../interfaces/i_draggable.js';
import {Coordinate} from '../utils.js';
import * as eventUtils from '../events/utils.js';
import {IBubble, WorkspaceSvg} from '../blockly.js'; import {IBubble, WorkspaceSvg} from '../blockly.js';
import * as eventUtils from '../events/utils.js';
import {IDragStrategy} from '../interfaces/i_draggable.js';
import * as layers from '../layers.js'; import * as layers from '../layers.js';
import {Coordinate} from '../utils.js';
export class BubbleDragStrategy implements IDragStrategy { export class BubbleDragStrategy implements IDragStrategy {
private startLoc: Coordinate | null = null; private startLoc: Coordinate | null = null;
/** Was there already an event group in progress when the drag started? */
private inGroup: boolean = false;
constructor( constructor(
private bubble: IBubble, private bubble: IBubble,
private workspace: WorkspaceSvg, private workspace: WorkspaceSvg,
@@ -23,13 +26,16 @@ export class BubbleDragStrategy implements IDragStrategy {
} }
startDrag(): void { startDrag(): void {
if (!eventUtils.getGroup()) { this.inGroup = !!eventUtils.getGroup();
if (!this.inGroup) {
eventUtils.setGroup(true); eventUtils.setGroup(true);
} }
this.startLoc = this.bubble.getRelativeToSurfaceXY(); this.startLoc = this.bubble.getRelativeToSurfaceXY();
this.workspace.setResizesEnabled(false); this.workspace.setResizesEnabled(false);
this.workspace.getLayerManager()?.moveToDragLayer(this.bubble); this.workspace.getLayerManager()?.moveToDragLayer(this.bubble);
this.bubble.setDragging && this.bubble.setDragging(true); if (this.bubble.setDragging) {
this.bubble.setDragging(true);
}
} }
drag(newLoc: Coordinate): void { drag(newLoc: Coordinate): void {
@@ -38,7 +44,9 @@ export class BubbleDragStrategy implements IDragStrategy {
endDrag(): void { endDrag(): void {
this.workspace.setResizesEnabled(true); this.workspace.setResizesEnabled(true);
eventUtils.setGroup(false); if (!this.inGroup) {
eventUtils.setGroup(false);
}
this.workspace this.workspace
.getLayerManager() .getLayerManager()

View File

@@ -4,29 +4,38 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {IDragStrategy} from '../interfaces/i_draggable.js';
import {Coordinate} from '../utils.js';
import * as eventUtils from '../events/utils.js';
import * as layers from '../layers.js';
import {RenderedWorkspaceComment} from '../comments.js'; import {RenderedWorkspaceComment} from '../comments.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import {CommentMove} from '../events/events_comment_move.js'; import {CommentMove} from '../events/events_comment_move.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {IDragStrategy} from '../interfaces/i_draggable.js';
import * as layers from '../layers.js';
import {Coordinate} from '../utils.js';
import {WorkspaceSvg} from '../workspace_svg.js';
export class CommentDragStrategy implements IDragStrategy { export class CommentDragStrategy implements IDragStrategy {
private startLoc: Coordinate | null = null; private startLoc: Coordinate | null = null;
private workspace: WorkspaceSvg; private workspace: WorkspaceSvg;
/** Was there already an event group in progress when the drag started? */
private inGroup: boolean = false;
constructor(private comment: RenderedWorkspaceComment) { constructor(private comment: RenderedWorkspaceComment) {
this.workspace = comment.workspace; this.workspace = comment.workspace;
} }
isMovable(): boolean { isMovable(): boolean {
return this.comment.isOwnMovable() && !this.workspace.options.readOnly; return (
this.comment.isOwnMovable() &&
!this.comment.isDeadOrDying() &&
!this.workspace.options.readOnly
);
} }
startDrag(): void { startDrag(): void {
if (!eventUtils.getGroup()) { this.inGroup = !!eventUtils.getGroup();
if (!this.inGroup) {
eventUtils.setGroup(true); eventUtils.setGroup(true);
} }
this.fireDragStartEvent(); this.fireDragStartEvent();
@@ -52,12 +61,14 @@ export class CommentDragStrategy implements IDragStrategy {
this.comment.snapToGrid(); this.comment.snapToGrid();
this.workspace.setResizesEnabled(true); this.workspace.setResizesEnabled(true);
eventUtils.setGroup(false); if (!this.inGroup) {
eventUtils.setGroup(false);
}
} }
/** Fire a UI event at the start of a comment drag. */ /** Fire a UI event at the start of a comment drag. */
private fireDragStartEvent() { private fireDragStartEvent() {
const event = new (eventUtils.get(eventUtils.COMMENT_DRAG))( const event = new (eventUtils.get(EventType.COMMENT_DRAG))(
this.comment, this.comment,
true, true,
); );
@@ -66,7 +77,7 @@ export class CommentDragStrategy implements IDragStrategy {
/** Fire a UI event at the end of a comment drag. */ /** Fire a UI event at the end of a comment drag. */
private fireDragEndEvent() { private fireDragEndEvent() {
const event = new (eventUtils.get(eventUtils.COMMENT_DRAG))( const event = new (eventUtils.get(EventType.COMMENT_DRAG))(
this.comment, this.comment,
false, false,
); );
@@ -76,7 +87,7 @@ export class CommentDragStrategy implements IDragStrategy {
/** Fire a move event at the end of a comment drag. */ /** Fire a move event at the end of a comment drag. */
private fireMoveEvent() { private fireMoveEvent() {
if (this.comment.isDeadOrDying()) return; if (this.comment.isDeadOrDying()) return;
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))( const event = new (eventUtils.get(EventType.COMMENT_MOVE))(
this.comment, this.comment,
) as CommentMove; ) as CommentMove;
event.setReason(['drag']); event.setReason(['drag']);

View File

@@ -4,18 +4,18 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {IDragTarget} from '../interfaces/i_drag_target.js';
import {IDeletable, isDeletable} from '../interfaces/i_deletable.js';
import {IDragger} from '../interfaces/i_dragger.js';
import {IDraggable} from '../interfaces/i_draggable.js';
import {Coordinate} from '../utils/coordinate.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import {ComponentManager} from '../component_manager.js';
import {IDeleteArea} from '../interfaces/i_delete_area.js';
import * as registry from '../registry.js';
import * as eventUtils from '../events/utils.js';
import * as blockAnimations from '../block_animations.js'; import * as blockAnimations from '../block_animations.js';
import {BlockSvg} from '../block_svg.js'; import {BlockSvg} from '../block_svg.js';
import {ComponentManager} from '../component_manager.js';
import * as eventUtils from '../events/utils.js';
import {IDeletable, isDeletable} from '../interfaces/i_deletable.js';
import {IDeleteArea} from '../interfaces/i_delete_area.js';
import {IDragTarget} from '../interfaces/i_drag_target.js';
import {IDraggable} from '../interfaces/i_draggable.js';
import {IDragger} from '../interfaces/i_dragger.js';
import * as registry from '../registry.js';
import {Coordinate} from '../utils/coordinate.js';
import {WorkspaceSvg} from '../workspace_svg.js';
export class Dragger implements IDragger { export class Dragger implements IDragger {
protected startLoc: Coordinate; protected startLoc: Coordinate;

View File

@@ -14,8 +14,8 @@
import type {BlockSvg} from './block_svg.js'; import type {BlockSvg} from './block_svg.js';
import * as common from './common.js'; import * as common from './common.js';
import * as dom from './utils/dom.js';
import type {Field} from './field.js'; import type {Field} from './field.js';
import * as dom from './utils/dom.js';
import * as math from './utils/math.js'; import * as math from './utils/math.js';
import {Rect} from './utils/rect.js'; import {Rect} from './utils/rect.js';
import type {Size} from './utils/size.js'; import type {Size} from './utils/size.js';
@@ -53,7 +53,7 @@ export const ANIMATION_TIME = 0.25;
let animateOutTimer: ReturnType<typeof setTimeout> | null = null; let animateOutTimer: ReturnType<typeof setTimeout> | null = null;
/** Callback for when the drop-down is hidden. */ /** Callback for when the drop-down is hidden. */
let onHide: Function | null = null; let onHide: (() => void) | null = null;
/** A class name representing the current owner's workspace renderer. */ /** A class name representing the current owner's workspace renderer. */
let renderedClassName = ''; let renderedClassName = '';
@@ -196,7 +196,7 @@ export function setColour(backgroundColour: string, borderColour: string) {
export function showPositionedByBlock<T>( export function showPositionedByBlock<T>(
field: Field<T>, field: Field<T>,
block: BlockSvg, block: BlockSvg,
opt_onHide?: Function, opt_onHide?: () => void,
opt_secondaryYOffset?: number, opt_secondaryYOffset?: number,
): boolean { ): boolean {
return showPositionedByRect( return showPositionedByRect(
@@ -220,7 +220,7 @@ export function showPositionedByBlock<T>(
*/ */
export function showPositionedByField<T>( export function showPositionedByField<T>(
field: Field<T>, field: Field<T>,
opt_onHide?: Function, opt_onHide?: () => void,
opt_secondaryYOffset?: number, opt_secondaryYOffset?: number,
): boolean { ): boolean {
positionToField = true; positionToField = true;
@@ -272,7 +272,7 @@ function getScaledBboxOfField(field: Field): Rect {
function showPositionedByRect( function showPositionedByRect(
bBox: Rect, bBox: Rect,
field: Field, field: Field,
opt_onHide?: Function, opt_onHide?: () => void,
opt_secondaryYOffset?: number, opt_secondaryYOffset?: number,
): boolean { ): boolean {
// If we can fit it, render below the block. // If we can fit it, render below the block.
@@ -328,7 +328,7 @@ export function show<T>(
primaryY: number, primaryY: number,
secondaryX: number, secondaryX: number,
secondaryY: number, secondaryY: number,
opt_onHide?: Function, opt_onHide?: () => void,
): boolean { ): boolean {
owner = newOwner as Field; owner = newOwner as Field;
onHide = opt_onHide || null; onHide = opt_onHide || null;

View File

@@ -6,158 +6,105 @@
// Former goog.module ID: Blockly.Events // Former goog.module ID: Blockly.Events
import {Abstract, AbstractEventJson} from './events_abstract.js'; import {EventType} from './type.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {BlockChange, BlockChangeJson} from './events_block_change.js'; // Events.
import {BlockCreate, BlockCreateJson} from './events_block_create.js'; export {Abstract, AbstractEventJson} from './events_abstract.js';
import {BlockDelete, BlockDeleteJson} from './events_block_delete.js'; export {BlockBase, BlockBaseJson} from './events_block_base.js';
import {BlockDrag, BlockDragJson} from './events_block_drag.js'; export {BlockChange, BlockChangeJson} from './events_block_change.js';
import { export {BlockCreate, BlockCreateJson} from './events_block_create.js';
export {BlockDelete, BlockDeleteJson} from './events_block_delete.js';
export {BlockDrag, BlockDragJson} from './events_block_drag.js';
export {
BlockFieldIntermediateChange, BlockFieldIntermediateChange,
BlockFieldIntermediateChangeJson, BlockFieldIntermediateChangeJson,
} from './events_block_field_intermediate_change.js'; } from './events_block_field_intermediate_change.js';
import {BlockMove, BlockMoveJson} from './events_block_move.js'; export {BlockMove, BlockMoveJson} from './events_block_move.js';
import {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js'; export {BubbleOpen, BubbleOpenJson, BubbleType} from './events_bubble_open.js';
import {Click, ClickJson, ClickTarget} from './events_click.js'; export {Click, ClickJson, ClickTarget} from './events_click.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js'; export {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {CommentChange, CommentChangeJson} from './events_comment_change.js'; export {CommentChange, CommentChangeJson} from './events_comment_change.js';
import {CommentCreate, CommentCreateJson} from './events_comment_create.js'; export {
import {CommentDelete} from './events_comment_delete.js';
import {CommentMove, CommentMoveJson} from './events_comment_move.js';
import {CommentResize, CommentResizeJson} from './events_comment_resize.js';
import {CommentDrag, CommentDragJson} from './events_comment_drag.js';
import {
CommentCollapse, CommentCollapse,
CommentCollapseJson, CommentCollapseJson,
} from './events_comment_collapse.js'; } from './events_comment_collapse.js';
import {MarkerMove, MarkerMoveJson} from './events_marker_move.js'; export {CommentCreate, CommentCreateJson} from './events_comment_create.js';
import {Selected, SelectedJson} from './events_selected.js'; export {CommentDelete} from './events_comment_delete.js';
import {ThemeChange, ThemeChangeJson} from './events_theme_change.js'; export {CommentDrag, CommentDragJson} from './events_comment_drag.js';
import { export {CommentMove, CommentMoveJson} from './events_comment_move.js';
export {CommentResize, CommentResizeJson} from './events_comment_resize.js';
export {MarkerMove, MarkerMoveJson} from './events_marker_move.js';
export {Selected, SelectedJson} from './events_selected.js';
export {ThemeChange, ThemeChangeJson} from './events_theme_change.js';
export {
ToolboxItemSelect, ToolboxItemSelect,
ToolboxItemSelectJson, ToolboxItemSelectJson,
} from './events_toolbox_item_select.js'; } from './events_toolbox_item_select.js';
import {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js';
import {UiBase} from './events_ui_base.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import {VarCreate, VarCreateJson} from './events_var_create.js';
import {VarDelete, VarDeleteJson} from './events_var_delete.js';
import {VarRename, VarRenameJson} from './events_var_rename.js';
import {VarTypeChange, VarTypeChangeJson} from './events_var_type_change.js';
import {ViewportChange, ViewportChangeJson} from './events_viewport.js';
import * as eventUtils from './utils.js';
import {FinishedLoading} from './workspace_events.js';
// Events. // Events.
export {Abstract}; export {TrashcanOpen, TrashcanOpenJson} from './events_trashcan_open.js';
export {AbstractEventJson}; export {UiBase} from './events_ui_base.js';
export {BubbleOpen}; export {VarBase, VarBaseJson} from './events_var_base.js';
export {BubbleOpenJson}; export {VarCreate, VarCreateJson} from './events_var_create.js';
export {BubbleType}; export {VarDelete, VarDeleteJson} from './events_var_delete.js';
export {BlockBase}; export {VarRename, VarRenameJson} from './events_var_rename.js';
export {BlockBaseJson}; export {ViewportChange, ViewportChangeJson} from './events_viewport.js';
export {BlockChange}; export {FinishedLoading} from './workspace_events.js';
export {BlockChangeJson};
export {BlockCreate}; export type {BumpEvent} from './utils.js';
export {BlockCreateJson};
export {BlockDelete};
export {BlockDeleteJson};
export {BlockDrag};
export {BlockDragJson};
export {BlockFieldIntermediateChange};
export {BlockFieldIntermediateChangeJson};
export {BlockMove};
export {BlockMoveJson};
export {Click};
export {ClickJson};
export {ClickTarget};
export {CommentBase};
export {CommentBaseJson};
export {CommentChange};
export {CommentChangeJson};
export {CommentCreate};
export {CommentCreateJson};
export {CommentDelete};
export {CommentMove};
export {CommentMoveJson};
export {CommentResize};
export {CommentResizeJson};
export {CommentDrag};
export {CommentDragJson};
export {CommentCollapse};
export {CommentCollapseJson};
export {FinishedLoading};
export {MarkerMove};
export {MarkerMoveJson};
export {Selected};
export {SelectedJson};
export {ThemeChange};
export {ThemeChangeJson};
export {ToolboxItemSelect};
export {ToolboxItemSelectJson};
export {TrashcanOpen};
export {TrashcanOpenJson};
export {UiBase};
export {VarBase};
export {VarBaseJson};
export {VarCreate};
export {VarCreateJson};
export {VarDelete};
export {VarDeleteJson};
export {VarRename};
export {VarRenameJson};
export {VarTypeChange};
export {VarTypeChangeJson};
export {ViewportChange};
export {ViewportChangeJson};
// Event types. // Event types.
export const BLOCK_CHANGE = eventUtils.BLOCK_CHANGE; export const BLOCK_CHANGE = EventType.BLOCK_CHANGE;
export const BLOCK_CREATE = eventUtils.BLOCK_CREATE; export const BLOCK_CREATE = EventType.BLOCK_CREATE;
export const BLOCK_DELETE = eventUtils.BLOCK_DELETE; export const BLOCK_DELETE = EventType.BLOCK_DELETE;
export const BLOCK_DRAG = eventUtils.BLOCK_DRAG; export const BLOCK_DRAG = EventType.BLOCK_DRAG;
export const BLOCK_MOVE = eventUtils.BLOCK_MOVE; export const BLOCK_MOVE = EventType.BLOCK_MOVE;
export const BLOCK_FIELD_INTERMEDIATE_CHANGE = export const BLOCK_FIELD_INTERMEDIATE_CHANGE =
eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE; EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE;
export const BUBBLE_OPEN = eventUtils.BUBBLE_OPEN; export const BUBBLE_OPEN = EventType.BUBBLE_OPEN;
export type BumpEvent = eventUtils.BumpEvent; /** @deprecated Use BLOCK_CHANGE instead */
export const BUMP_EVENTS = eventUtils.BUMP_EVENTS; export const CHANGE = EventType.BLOCK_CHANGE;
export const CHANGE = eventUtils.CHANGE; export const CLICK = EventType.CLICK;
export const CLICK = eventUtils.CLICK; export const COMMENT_CHANGE = EventType.COMMENT_CHANGE;
export const COMMENT_CHANGE = eventUtils.COMMENT_CHANGE; export const COMMENT_CREATE = EventType.COMMENT_CREATE;
export const COMMENT_CREATE = eventUtils.COMMENT_CREATE; export const COMMENT_DELETE = EventType.COMMENT_DELETE;
export const COMMENT_DELETE = eventUtils.COMMENT_DELETE; export const COMMENT_MOVE = EventType.COMMENT_MOVE;
export const COMMENT_MOVE = eventUtils.COMMENT_MOVE; export const COMMENT_RESIZE = EventType.COMMENT_RESIZE;
export const COMMENT_RESIZE = eventUtils.COMMENT_RESIZE; export const COMMENT_DRAG = EventType.COMMENT_DRAG;
export const COMMENT_DRAG = eventUtils.COMMENT_DRAG; /** @deprecated Use BLOCK_CREATE instead */
export const CREATE = eventUtils.CREATE; export const CREATE = EventType.BLOCK_CREATE;
export const DELETE = eventUtils.DELETE; /** @deprecated Use BLOCK_DELETE instead */
export const FINISHED_LOADING = eventUtils.FINISHED_LOADING; export const DELETE = EventType.BLOCK_DELETE;
export const MARKER_MOVE = eventUtils.MARKER_MOVE; export const FINISHED_LOADING = EventType.FINISHED_LOADING;
export const MOVE = eventUtils.MOVE; export const MARKER_MOVE = EventType.MARKER_MOVE;
export const SELECTED = eventUtils.SELECTED; /** @deprecated Use BLOCK_MOVE instead */
export const THEME_CHANGE = eventUtils.THEME_CHANGE; export const MOVE = EventType.BLOCK_MOVE;
export const TOOLBOX_ITEM_SELECT = eventUtils.TOOLBOX_ITEM_SELECT; export const SELECTED = EventType.SELECTED;
export const TRASHCAN_OPEN = eventUtils.TRASHCAN_OPEN; export const THEME_CHANGE = EventType.THEME_CHANGE;
export const UI = eventUtils.UI; export const TOOLBOX_ITEM_SELECT = EventType.TOOLBOX_ITEM_SELECT;
export const VAR_CREATE = eventUtils.VAR_CREATE; export const TRASHCAN_OPEN = EventType.TRASHCAN_OPEN;
export const VAR_DELETE = eventUtils.VAR_DELETE; export const UI = EventType.UI;
export const VAR_RENAME = eventUtils.VAR_RENAME; export const VAR_CREATE = EventType.VAR_CREATE;
export const VAR_TYPE_CHAGE = eventUtils.VAR_TYPE_CHANGE; export const VAR_DELETE = EventType.VAR_DELETE;
export const VIEWPORT_CHANGE = eventUtils.VIEWPORT_CHANGE; export const VAR_RENAME = EventType.VAR_RENAME;
export const VIEWPORT_CHANGE = EventType.VIEWPORT_CHANGE;
export {BUMP_EVENTS} from './type.js';
// Event utils. // Event utils.
export const clearPendingUndo = eventUtils.clearPendingUndo; export {
export const disable = eventUtils.disable; clearPendingUndo,
export const enable = eventUtils.enable; disable,
export const filter = eventUtils.filter; disableOrphans,
export const fire = eventUtils.fire; enable,
export const fromJson = eventUtils.fromJson; filter,
export const getDescendantIds = eventUtils.getDescendantIds; fire,
export const get = eventUtils.get; fromJson,
export const getGroup = eventUtils.getGroup; get,
export const getRecordUndo = eventUtils.getRecordUndo; getDescendantIds,
export const isEnabled = eventUtils.isEnabled; getGroup,
export const setGroup = eventUtils.setGroup; getRecordUndo,
export const setRecordUndo = eventUtils.setRecordUndo; isEnabled,
export const disableOrphans = eventUtils.disableOrphans; setGroup,
setRecordUndo,
} from './utils.js';

View File

@@ -14,8 +14,7 @@
import * as common from '../common.js'; import * as common from '../common.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {getGroup, getRecordUndo} from './utils.js';
import * as eventUtils from './utils.js';
/** /**
* Abstract class for an event. * Abstract class for an event.
@@ -48,8 +47,8 @@ export abstract class Abstract {
type = ''; type = '';
constructor() { constructor() {
this.group = eventUtils.getGroup(); this.group = getGroup();
this.recordUndo = eventUtils.getRecordUndo(); this.recordUndo = getRecordUndo();
} }
/** /**

View File

@@ -13,7 +13,6 @@
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import { import {
Abstract as AbstractEvent, Abstract as AbstractEvent,
AbstractEventJson, AbstractEventJson,

View File

@@ -13,15 +13,15 @@
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import type {BlockSvg} from '../block_svg.js'; import type {BlockSvg} from '../block_svg.js';
import {MANUALLY_DISABLED} from '../constants.js';
import {IconType} from '../icons/icon_types.js'; import {IconType} from '../icons/icon_types.js';
import {hasBubble} from '../interfaces/i_has_bubble.js'; import {hasBubble} from '../interfaces/i_has_bubble.js';
import {MANUALLY_DISABLED} from '../constants.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import * as utilsXml from '../utils/xml.js'; import * as utilsXml from '../utils/xml.js';
import {Workspace} from '../workspace.js'; import {Workspace} from '../workspace.js';
import * as Xml from '../xml.js'; import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js'; import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {EventType} from './type.js';
import * as eventUtils from './utils.js'; import * as eventUtils from './utils.js';
/** /**
@@ -29,7 +29,7 @@ import * as eventUtils from './utils.js';
* field values, comments, etc). * field values, comments, etc).
*/ */
export class BlockChange extends BlockBase { export class BlockChange extends BlockBase {
override type = eventUtils.BLOCK_CHANGE; override type = EventType.BLOCK_CHANGE;
/** /**
* The element that changed; one of 'field', 'comment', 'collapsed', * The element that changed; one of 'field', 'comment', 'collapsed',
* 'disabled', 'inline', or 'mutation' * 'disabled', 'inline', or 'mutation'
@@ -256,4 +256,4 @@ export interface BlockChangeJson extends BlockBaseJson {
disabledReason?: string; disabledReason?: string;
} }
registry.register(registry.Type.EVENT, eventUtils.CHANGE, BlockChange); registry.register(registry.Type.EVENT, EventType.BLOCK_CHANGE, BlockChange);

View File

@@ -15,18 +15,18 @@ import type {Block} from '../block.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import * as blocks from '../serialization/blocks.js'; import * as blocks from '../serialization/blocks.js';
import * as utilsXml from '../utils/xml.js'; import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js'; import {Workspace} from '../workspace.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {EventType} from './type.js';
import * as eventUtils from './utils.js';
/** /**
* Notifies listeners when a block (or connected stack of blocks) is * Notifies listeners when a block (or connected stack of blocks) is
* created. * created.
*/ */
export class BlockCreate extends BlockBase { export class BlockCreate extends BlockBase {
override type = eventUtils.BLOCK_CREATE; override type = EventType.BLOCK_CREATE;
/** The XML representation of the created block(s). */ /** The XML representation of the created block(s). */
xml?: Element | DocumentFragment; xml?: Element | DocumentFragment;
@@ -182,4 +182,4 @@ export interface BlockCreateJson extends BlockBaseJson {
recordUndo?: boolean; recordUndo?: boolean;
} }
registry.register(registry.Type.EVENT, eventUtils.CREATE, BlockCreate); registry.register(registry.Type.EVENT, EventType.BLOCK_CREATE, BlockCreate);

View File

@@ -15,11 +15,11 @@ import type {Block} from '../block.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import * as blocks from '../serialization/blocks.js'; import * as blocks from '../serialization/blocks.js';
import * as utilsXml from '../utils/xml.js'; import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js'; import {Workspace} from '../workspace.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {EventType} from './type.js';
import * as eventUtils from './utils.js';
/** /**
* Notifies listeners when a block (or connected stack of blocks) is * Notifies listeners when a block (or connected stack of blocks) is
@@ -38,7 +38,7 @@ export class BlockDelete extends BlockBase {
/** True if the deleted block was a shadow block, false otherwise. */ /** True if the deleted block was a shadow block, false otherwise. */
wasShadow?: boolean; wasShadow?: boolean;
override type = eventUtils.BLOCK_DELETE; override type = EventType.BLOCK_DELETE;
/** @param opt_block The deleted block. Undefined for a blank event. */ /** @param opt_block The deleted block. Undefined for a blank event. */
constructor(opt_block?: Block) { constructor(opt_block?: Block) {
@@ -179,4 +179,4 @@ export interface BlockDeleteJson extends BlockBaseJson {
recordUndo?: boolean; recordUndo?: boolean;
} }
registry.register(registry.Type.EVENT, eventUtils.DELETE, BlockDelete); registry.register(registry.Type.EVENT, EventType.BLOCK_DELETE, BlockDelete);

View File

@@ -13,10 +13,10 @@
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js'; import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js'; import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
import {Workspace} from '../workspace.js';
/** /**
* Notifies listeners when a block is being manually dragged/dropped. * Notifies listeners when a block is being manually dragged/dropped.
@@ -34,7 +34,7 @@ export class BlockDrag extends UiBase {
*/ */
blocks?: Block[]; blocks?: Block[];
override type = eventUtils.BLOCK_DRAG; override type = EventType.BLOCK_DRAG;
/** /**
* @param opt_block The top block in the stack that is being dragged. * @param opt_block The top block in the stack that is being dragged.
@@ -113,4 +113,4 @@ export interface BlockDragJson extends AbstractEventJson {
blocks?: Block[]; blocks?: Block[];
} }
registry.register(registry.Type.EVENT, eventUtils.BLOCK_DRAG, BlockDrag); registry.register(registry.Type.EVENT, EventType.BLOCK_DRAG, BlockDrag);

View File

@@ -15,9 +15,8 @@
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {Workspace} from '../workspace.js'; import {Workspace} from '../workspace.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js'; import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
/** /**
* Notifies listeners when the value of a block's field has changed but the * Notifies listeners when the value of a block's field has changed but the
@@ -25,7 +24,7 @@ import * as eventUtils from './utils.js';
* event. * event.
*/ */
export class BlockFieldIntermediateChange extends BlockBase { export class BlockFieldIntermediateChange extends BlockBase {
override type = eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE; override type = EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE;
// Intermediate events do not undo or redo. They may be fired frequently while // Intermediate events do not undo or redo. They may be fired frequently while
// the field editor widget is open. A separate BLOCK_CHANGE event is fired // the field editor widget is open. A separate BLOCK_CHANGE event is fired
@@ -162,6 +161,6 @@ export interface BlockFieldIntermediateChangeJson extends BlockBaseJson {
registry.register( registry.register(
registry.Type.EVENT, registry.Type.EVENT,
eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE, EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE,
BlockFieldIntermediateChange, BlockFieldIntermediateChange,
); );

View File

@@ -15,10 +15,9 @@ import type {Block} from '../block.js';
import {ConnectionType} from '../connection_type.js'; import {ConnectionType} from '../connection_type.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {EventType} from './type.js';
interface BlockLocation { interface BlockLocation {
parentId?: string; parentId?: string;
@@ -31,7 +30,7 @@ interface BlockLocation {
* connection to another, or from one location on the workspace to another. * connection to another, or from one location on the workspace to another.
*/ */
export class BlockMove extends BlockBase { export class BlockMove extends BlockBase {
override type = eventUtils.BLOCK_MOVE; override type = EventType.BLOCK_MOVE;
/** The ID of the old parent block. Undefined if it was a top-level block. */ /** The ID of the old parent block. Undefined if it was a top-level block. */
oldParentId?: string; oldParentId?: string;
@@ -90,7 +89,7 @@ export class BlockMove extends BlockBase {
this.recordUndo = false; this.recordUndo = false;
} }
const location = this.currentLocation_(); const location = this.currentLocation();
this.oldParentId = location.parentId; this.oldParentId = location.parentId;
this.oldInputName = location.inputName; this.oldInputName = location.inputName;
this.oldCoordinate = location.coordinate; this.oldCoordinate = location.coordinate;
@@ -168,7 +167,7 @@ export class BlockMove extends BlockBase {
/** Record the block's new location. Called after the move. */ /** Record the block's new location. Called after the move. */
recordNew() { recordNew() {
const location = this.currentLocation_(); const location = this.currentLocation();
this.newParentId = location.parentId; this.newParentId = location.parentId;
this.newInputName = location.inputName; this.newInputName = location.inputName;
this.newCoordinate = location.coordinate; this.newCoordinate = location.coordinate;
@@ -189,7 +188,7 @@ export class BlockMove extends BlockBase {
* *
* @returns Collection of location info. * @returns Collection of location info.
*/ */
private currentLocation_(): BlockLocation { private currentLocation(): BlockLocation {
const workspace = this.getEventWorkspace_(); const workspace = this.getEventWorkspace_();
if (!this.blockId) { if (!this.blockId) {
throw new Error( throw new Error(
@@ -304,4 +303,4 @@ export interface BlockMoveJson extends BlockBaseJson {
recordUndo?: boolean; recordUndo?: boolean;
} }
registry.register(registry.Type.EVENT, eventUtils.MOVE, BlockMove); registry.register(registry.Type.EVENT, EventType.BLOCK_MOVE, BlockMove);

View File

@@ -9,14 +9,15 @@
* *
* @class * @class
*/ */
// Former goog.module ID: Blockly.Events.BubbleOpen // Former goog.module ID: Blockly.Events.BubbleOpen
import type {AbstractEventJson} from './events_abstract.js';
import type {BlockSvg} from '../block_svg.js'; import type {BlockSvg} from '../block_svg.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import type {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import {EventType} from './type.js';
/** /**
* Class for a bubble open event. * Class for a bubble open event.
@@ -31,7 +32,7 @@ export class BubbleOpen extends UiBase {
/** The type of bubble; one of 'mutator', 'comment', or 'warning'. */ /** The type of bubble; one of 'mutator', 'comment', or 'warning'. */
bubbleType?: BubbleType; bubbleType?: BubbleType;
override type = eventUtils.BUBBLE_OPEN; override type = EventType.BUBBLE_OPEN;
/** /**
* @param opt_block The associated block. Undefined for a blank event. * @param opt_block The associated block. Undefined for a blank event.
@@ -117,4 +118,4 @@ export interface BubbleOpenJson extends AbstractEventJson {
blockId: string; blockId: string;
} }
registry.register(registry.Type.EVENT, eventUtils.BUBBLE_OPEN, BubbleOpen); registry.register(registry.Type.EVENT, EventType.BUBBLE_OPEN, BubbleOpen);

View File

@@ -9,15 +9,15 @@
* *
* @class * @class
*/ */
// Former goog.module ID: Blockly.Events.Click // Former goog.module ID: Blockly.Events.Click
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import {Workspace} from '../workspace.js'; import {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that some blockly element was clicked. * Notifies listeners that some blockly element was clicked.
@@ -31,7 +31,7 @@ export class Click extends UiBase {
* or 'zoom_controls'. * or 'zoom_controls'.
*/ */
targetType?: ClickTarget; targetType?: ClickTarget;
override type = eventUtils.CLICK; override type = EventType.CLICK;
/** /**
* @param opt_block The affected block. Null for click events that do not have * @param opt_block The affected block. Null for click events that do not have
@@ -107,4 +107,4 @@ export interface ClickJson extends AbstractEventJson {
blockId?: string; blockId?: string;
} }
registry.register(registry.Type.EVENT, eventUtils.CLICK, Click); registry.register(registry.Type.EVENT, EventType.CLICK, Click);

View File

@@ -13,14 +13,14 @@
import type {WorkspaceComment} from '../comments/workspace_comment.js'; import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as comments from '../serialization/workspace_comments.js'; import * as comments from '../serialization/workspace_comments.js';
import type {Workspace} from '../workspace.js';
import { import {
Abstract as AbstractEvent, Abstract as AbstractEvent,
AbstractEventJson, AbstractEventJson,
} from './events_abstract.js'; } from './events_abstract.js';
import type {CommentCreate} from './events_comment_create.js'; import type {CommentCreate} from './events_comment_create.js';
import type {CommentDelete} from './events_comment_delete.js'; import type {CommentDelete} from './events_comment_delete.js';
import * as eventUtils from './utils.js'; import {getGroup, getRecordUndo} from './utils.js';
import type {Workspace} from '../workspace.js';
/** /**
* Abstract class for a comment event. * Abstract class for a comment event.
@@ -44,8 +44,8 @@ export class CommentBase extends AbstractEvent {
this.commentId = opt_comment.id; this.commentId = opt_comment.id;
this.workspaceId = opt_comment.workspace.id; this.workspaceId = opt_comment.workspace.id;
this.group = eventUtils.getGroup(); this.group = getGroup();
this.recordUndo = eventUtils.getRecordUndo(); this.recordUndo = getRecordUndo();
} }
/** /**

View File

@@ -11,18 +11,17 @@
*/ */
// Former goog.module ID: Blockly.Events.CommentChange // Former goog.module ID: Blockly.Events.CommentChange
import * as registry from '../registry.js';
import type {WorkspaceComment} from '../comments/workspace_comment.js'; import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that the contents of a workspace comment has changed. * Notifies listeners that the contents of a workspace comment has changed.
*/ */
export class CommentChange extends CommentBase { export class CommentChange extends CommentBase {
override type = eventUtils.COMMENT_CHANGE; override type = EventType.COMMENT_CHANGE;
// TODO(#6774): We should remove underscores. // TODO(#6774): We should remove underscores.
/** The previous contents of the comment. */ /** The previous contents of the comment. */
@@ -154,8 +153,4 @@ export interface CommentChangeJson extends CommentBaseJson {
newContents: string; newContents: string;
} }
registry.register( registry.register(registry.Type.EVENT, EventType.COMMENT_CHANGE, CommentChange);
registry.Type.EVENT,
eventUtils.COMMENT_CHANGE,
CommentChange,
);

View File

@@ -4,14 +4,14 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import * as registry from '../registry.js';
import {WorkspaceComment} from '../comments/workspace_comment.js'; import {WorkspaceComment} from '../comments/workspace_comment.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import * as registry from '../registry.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
export class CommentCollapse extends CommentBase { export class CommentCollapse extends CommentBase {
override type = eventUtils.COMMENT_COLLAPSE; override type = EventType.COMMENT_COLLAPSE;
constructor( constructor(
comment?: WorkspaceComment, comment?: WorkspaceComment,
@@ -98,6 +98,6 @@ export interface CommentCollapseJson extends CommentBaseJson {
registry.register( registry.register(
registry.Type.EVENT, registry.Type.EVENT,
eventUtils.COMMENT_COLLAPSE, EventType.COMMENT_COLLAPSE,
CommentCollapse, CommentCollapse,
); );

View File

@@ -11,20 +11,20 @@
*/ */
// Former goog.module ID: Blockly.Events.CommentCreate // Former goog.module ID: Blockly.Events.CommentCreate
import * as registry from '../registry.js';
import type {WorkspaceComment} from '../comments/workspace_comment.js'; import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js';
import * as comments from '../serialization/workspace_comments.js'; import * as comments from '../serialization/workspace_comments.js';
import * as utilsXml from '../utils/xml.js'; import * as utilsXml from '../utils/xml.js';
import type {Workspace} from '../workspace.js';
import * as Xml from '../xml.js'; import * as Xml from '../xml.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js'; import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
import type {Workspace} from '../workspace.js';
/** /**
* Notifies listeners that a workspace comment was created. * Notifies listeners that a workspace comment was created.
*/ */
export class CommentCreate extends CommentBase { export class CommentCreate extends CommentBase {
override type = eventUtils.COMMENT_CREATE; override type = EventType.COMMENT_CREATE;
/** The XML representation of the created workspace comment. */ /** The XML representation of the created workspace comment. */
xml?: Element | DocumentFragment; xml?: Element | DocumentFragment;
@@ -111,8 +111,4 @@ export interface CommentCreateJson extends CommentBaseJson {
json: object; json: object;
} }
registry.register( registry.register(registry.Type.EVENT, EventType.COMMENT_CREATE, CommentCreate);
registry.Type.EVENT,
eventUtils.COMMENT_CREATE,
CommentCreate,
);

View File

@@ -11,20 +11,20 @@
*/ */
// Former goog.module ID: Blockly.Events.CommentDelete // Former goog.module ID: Blockly.Events.CommentDelete
import * as registry from '../registry.js';
import type {WorkspaceComment} from '../comments/workspace_comment.js'; import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js';
import * as comments from '../serialization/workspace_comments.js'; import * as comments from '../serialization/workspace_comments.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import * as utilsXml from '../utils/xml.js'; import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import * as Xml from '../xml.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that a workspace comment has been deleted. * Notifies listeners that a workspace comment has been deleted.
*/ */
export class CommentDelete extends CommentBase { export class CommentDelete extends CommentBase {
override type = eventUtils.COMMENT_DELETE; override type = EventType.COMMENT_DELETE;
/** The XML representation of the deleted workspace comment. */ /** The XML representation of the deleted workspace comment. */
xml?: Element; xml?: Element;
@@ -110,8 +110,4 @@ export interface CommentDeleteJson extends CommentBaseJson {
json: object; json: object;
} }
registry.register( registry.register(registry.Type.EVENT, EventType.COMMENT_DELETE, CommentDelete);
registry.Type.EVENT,
eventUtils.COMMENT_DELETE,
CommentDelete,
);

View File

@@ -10,10 +10,10 @@
import type {WorkspaceComment} from '../comments/workspace_comment.js'; import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js'; import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js'; import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
import {Workspace} from '../workspace.js';
/** /**
* Notifies listeners when a comment is being manually dragged/dropped. * Notifies listeners when a comment is being manually dragged/dropped.
@@ -25,7 +25,7 @@ export class CommentDrag extends UiBase {
/** True if this is the start of a drag, false if this is the end of one. */ /** True if this is the start of a drag, false if this is the end of one. */
isStart?: boolean; isStart?: boolean;
override type = eventUtils.COMMENT_DRAG; override type = EventType.COMMENT_DRAG;
/** /**
* @param opt_comment The comment that is being dragged. * @param opt_comment The comment that is being dragged.
@@ -96,4 +96,4 @@ export interface CommentDragJson extends AbstractEventJson {
commentId: string; commentId: string;
} }
registry.register(registry.Type.EVENT, eventUtils.COMMENT_DRAG, CommentDrag); registry.register(registry.Type.EVENT, EventType.COMMENT_DRAG, CommentDrag);

View File

@@ -11,19 +11,18 @@
*/ */
// Former goog.module ID: Blockly.Events.CommentMove // Former goog.module ID: Blockly.Events.CommentMove
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that a workspace comment has moved. * Notifies listeners that a workspace comment has moved.
*/ */
export class CommentMove extends CommentBase { export class CommentMove extends CommentBase {
override type = eventUtils.COMMENT_MOVE; override type = EventType.COMMENT_MOVE;
/** The comment that is being moved. */ /** The comment that is being moved. */
comment_?: WorkspaceComment; comment_?: WorkspaceComment;
@@ -204,4 +203,4 @@ export interface CommentMoveJson extends CommentBaseJson {
newCoordinate: string; newCoordinate: string;
} }
registry.register(registry.Type.EVENT, eventUtils.COMMENT_MOVE, CommentMove); registry.register(registry.Type.EVENT, EventType.COMMENT_MOVE, CommentMove);

View File

@@ -8,19 +8,18 @@
* Class for comment resize event. * Class for comment resize event.
*/ */
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that a workspace comment has resized. * Notifies listeners that a workspace comment has resized.
*/ */
export class CommentResize extends CommentBase { export class CommentResize extends CommentBase {
override type = eventUtils.COMMENT_RESIZE; override type = EventType.COMMENT_RESIZE;
/** The size of the comment before the resize. */ /** The size of the comment before the resize. */
oldSize?: Size; oldSize?: Size;
@@ -167,8 +166,4 @@ export interface CommentResizeJson extends CommentBaseJson {
newHeight: number; newHeight: number;
} }
registry.register( registry.register(registry.Type.EVENT, EventType.COMMENT_RESIZE, CommentResize);
registry.Type.EVENT,
eventUtils.COMMENT_RESIZE,
CommentResize,
);

View File

@@ -16,9 +16,8 @@ import {ASTNode} from '../keyboard_nav/ast_node.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js'; import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js'; import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
/** /**
* Notifies listeners that a marker (used for keyboard navigation) has * Notifies listeners that a marker (used for keyboard navigation) has
@@ -41,7 +40,7 @@ export class MarkerMove extends UiBase {
*/ */
isCursor?: boolean; isCursor?: boolean;
override type = eventUtils.MARKER_MOVE; override type = EventType.MARKER_MOVE;
/** /**
* @param opt_block The affected block. Null if current node is of type * @param opt_block The affected block. Null if current node is of type
@@ -131,4 +130,4 @@ export interface MarkerMoveJson extends AbstractEventJson {
newNode: ASTNode; newNode: ASTNode;
} }
registry.register(registry.Type.EVENT, eventUtils.MARKER_MOVE, MarkerMove); registry.register(registry.Type.EVENT, EventType.MARKER_MOVE, MarkerMove);

View File

@@ -12,11 +12,10 @@
// Former goog.module ID: Blockly.Events.Selected // Former goog.module ID: Blockly.Events.Selected
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import {EventType} from './type.js';
/** /**
* Class for a selected event. * Class for a selected event.
@@ -32,7 +31,7 @@ export class Selected extends UiBase {
*/ */
newElementId?: string; newElementId?: string;
override type = eventUtils.SELECTED; override type = EventType.SELECTED;
/** /**
* @param opt_oldElementId The ID of the previously selected element. Null if * @param opt_oldElementId The ID of the previously selected element. Null if
@@ -95,4 +94,4 @@ export interface SelectedJson extends AbstractEventJson {
newElementId?: string; newElementId?: string;
} }
registry.register(registry.Type.EVENT, eventUtils.SELECTED, Selected); registry.register(registry.Type.EVENT, EventType.SELECTED, Selected);

View File

@@ -12,10 +12,10 @@
// Former goog.module ID: Blockly.Events.ThemeChange // Former goog.module ID: Blockly.Events.ThemeChange
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js'; import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js'; import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
import type {Workspace} from '../workspace.js';
/** /**
* Notifies listeners that the workspace theme has changed. * Notifies listeners that the workspace theme has changed.
@@ -24,7 +24,7 @@ export class ThemeChange extends UiBase {
/** The name of the new theme that has been set. */ /** The name of the new theme that has been set. */
themeName?: string; themeName?: string;
override type = eventUtils.THEME_CHANGE; override type = EventType.THEME_CHANGE;
/** /**
* @param opt_themeName The theme name. Undefined for a blank event. * @param opt_themeName The theme name. Undefined for a blank event.
@@ -81,4 +81,4 @@ export interface ThemeChangeJson extends AbstractEventJson {
themeName: string; themeName: string;
} }
registry.register(registry.Type.EVENT, eventUtils.THEME_CHANGE, ThemeChange); registry.register(registry.Type.EVENT, EventType.THEME_CHANGE, ThemeChange);

View File

@@ -12,10 +12,10 @@
// Former goog.module ID: Blockly.Events.ToolboxItemSelect // Former goog.module ID: Blockly.Events.ToolboxItemSelect
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js'; import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js'; import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
import type {Workspace} from '../workspace.js';
/** /**
* Notifies listeners that a toolbox item has been selected. * Notifies listeners that a toolbox item has been selected.
@@ -27,7 +27,7 @@ export class ToolboxItemSelect extends UiBase {
/** The newly selected toolbox item. */ /** The newly selected toolbox item. */
newItem?: string; newItem?: string;
override type = eventUtils.TOOLBOX_ITEM_SELECT; override type = EventType.TOOLBOX_ITEM_SELECT;
/** /**
* @param opt_oldItem The previously selected toolbox item. * @param opt_oldItem The previously selected toolbox item.
@@ -91,6 +91,6 @@ export interface ToolboxItemSelectJson extends AbstractEventJson {
registry.register( registry.register(
registry.Type.EVENT, registry.Type.EVENT,
eventUtils.TOOLBOX_ITEM_SELECT, EventType.TOOLBOX_ITEM_SELECT,
ToolboxItemSelect, ToolboxItemSelect,
); );

View File

@@ -12,11 +12,10 @@
// Former goog.module ID: Blockly.Events.TrashcanOpen // Former goog.module ID: Blockly.Events.TrashcanOpen
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners when the trashcan is opening or closing. * Notifies listeners when the trashcan is opening or closing.
@@ -27,7 +26,7 @@ export class TrashcanOpen extends UiBase {
* False if it is currently closing (previously open). * False if it is currently closing (previously open).
*/ */
isOpen?: boolean; isOpen?: boolean;
override type = eventUtils.TRASHCAN_OPEN; override type = EventType.TRASHCAN_OPEN;
/** /**
* @param opt_isOpen Whether the trashcan flyout is opening (false if * @param opt_isOpen Whether the trashcan flyout is opening (false if
@@ -85,4 +84,4 @@ export interface TrashcanOpenJson extends AbstractEventJson {
isOpen: boolean; isOpen: boolean;
} }
registry.register(registry.Type.EVENT, eventUtils.TRASHCAN_OPEN, TrashcanOpen); registry.register(registry.Type.EVENT, EventType.TRASHCAN_OPEN, TrashcanOpen);

View File

@@ -15,12 +15,11 @@ import type {
IVariableModel, IVariableModel,
IVariableState, IVariableState,
} from '../interfaces/i_variable_model.js'; } from '../interfaces/i_variable_model.js';
import type {Workspace} from '../workspace.js';
import { import {
Abstract as AbstractEvent, Abstract as AbstractEvent,
AbstractEventJson, AbstractEventJson,
} from './events_abstract.js'; } from './events_abstract.js';
import type {Workspace} from '../workspace.js';
/** /**
* Abstract class for a variable event. * Abstract class for a variable event.

View File

@@ -11,21 +11,21 @@
*/ */
// Former goog.module ID: Blockly.Events.VarCreate // Former goog.module ID: Blockly.Events.VarCreate
import * as registry from '../registry.js';
import type { import type {
IVariableModel, IVariableModel,
IVariableState, IVariableState,
} from '../interfaces/i_variable_model.js'; } from '../interfaces/i_variable_model.js';
import * as registry from '../registry.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that a variable model has been created. * Notifies listeners that a variable model has been created.
*/ */
export class VarCreate extends VarBase { export class VarCreate extends VarBase {
override type = eventUtils.VAR_CREATE; override type = EventType.VAR_CREATE;
/** The type of the variable that was created. */ /** The type of the variable that was created. */
varType?: string; varType?: string;
@@ -126,4 +126,4 @@ export interface VarCreateJson extends VarBaseJson {
varName: string; varName: string;
} }
registry.register(registry.Type.EVENT, eventUtils.VAR_CREATE, VarCreate); registry.register(registry.Type.EVENT, EventType.VAR_CREATE, VarCreate);

View File

@@ -6,15 +6,15 @@
// Former goog.module ID: Blockly.Events.VarDelete // Former goog.module ID: Blockly.Events.VarDelete
import * as registry from '../registry.js';
import type { import type {
IVariableModel, IVariableModel,
IVariableState, IVariableState,
} from '../interfaces/i_variable_model.js'; } from '../interfaces/i_variable_model.js';
import * as registry from '../registry.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that a variable model has been deleted. * Notifies listeners that a variable model has been deleted.
@@ -22,7 +22,7 @@ import type {Workspace} from '../workspace.js';
* @class * @class
*/ */
export class VarDelete extends VarBase { export class VarDelete extends VarBase {
override type = eventUtils.VAR_DELETE; override type = EventType.VAR_DELETE;
/** The type of the variable that was deleted. */ /** The type of the variable that was deleted. */
varType?: string; varType?: string;
/** The name of the variable that was deleted. */ /** The name of the variable that was deleted. */
@@ -121,4 +121,4 @@ export interface VarDeleteJson extends VarBaseJson {
varName: string; varName: string;
} }
registry.register(registry.Type.EVENT, eventUtils.VAR_DELETE, VarDelete); registry.register(registry.Type.EVENT, EventType.VAR_DELETE, VarDelete);

View File

@@ -6,15 +6,15 @@
// Former goog.module ID: Blockly.Events.VarRename // Former goog.module ID: Blockly.Events.VarRename
import * as registry from '../registry.js';
import type { import type {
IVariableModel, IVariableModel,
IVariableState, IVariableState,
} from '../interfaces/i_variable_model.js'; } from '../interfaces/i_variable_model.js';
import * as registry from '../registry.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {VarBase, VarBaseJson} from './events_var_base.js';
import {EventType} from './type.js';
/** /**
* Notifies listeners that a variable model was renamed. * Notifies listeners that a variable model was renamed.
@@ -22,7 +22,7 @@ import type {Workspace} from '../workspace.js';
* @class * @class
*/ */
export class VarRename extends VarBase { export class VarRename extends VarBase {
override type = eventUtils.VAR_RENAME; override type = EventType.VAR_RENAME;
/** The previous name of the variable. */ /** The previous name of the variable. */
oldName?: string; oldName?: string;
@@ -130,4 +130,4 @@ export interface VarRenameJson extends VarBaseJson {
newName: string; newName: string;
} }
registry.register(registry.Type.EVENT, eventUtils.VAR_RENAME, VarRename); registry.register(registry.Type.EVENT, EventType.VAR_RENAME, VarRename);

View File

@@ -12,10 +12,10 @@
// Former goog.module ID: Blockly.Events.ViewportChange // Former goog.module ID: Blockly.Events.ViewportChange
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.js'; import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js'; import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
import type {Workspace} from '../workspace.js';
/** /**
* Notifies listeners that the workspace surface's position or scale has * Notifies listeners that the workspace surface's position or scale has
@@ -42,7 +42,7 @@ export class ViewportChange extends UiBase {
/** The previous scale of the workspace. */ /** The previous scale of the workspace. */
oldScale?: number; oldScale?: number;
override type = eventUtils.VIEWPORT_CHANGE; override type = EventType.VIEWPORT_CHANGE;
/** /**
* @param opt_top Top-edge of the visible portion of the workspace, relative * @param opt_top Top-edge of the visible portion of the workspace, relative
@@ -144,6 +144,6 @@ export interface ViewportChangeJson extends AbstractEventJson {
registry.register( registry.register(
registry.Type.EVENT, registry.Type.EVENT,
eventUtils.VIEWPORT_CHANGE, EventType.VIEWPORT_CHANGE,
ViewportChange, ViewportChange,
); );

172
core/events/predicates.ts Normal file
View File

@@ -0,0 +1,172 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file Predicates for testing Abstract event subclasses based on
* their .type properties. These are useful because there are places
* where it is not possible to use instanceof <ClassConstructor> tests
* for type narrowing due to load ordering issues that would be caused
* by the need to import (rather than just import type) the class
* constructors in question.
*/
import type {Abstract} from './events_abstract.js';
import type {BlockChange} from './events_block_change.js';
import type {BlockCreate} from './events_block_create.js';
import type {BlockDelete} from './events_block_delete.js';
import type {BlockDrag} from './events_block_drag.js';
import type {BlockFieldIntermediateChange} from './events_block_field_intermediate_change.js';
import type {BlockMove} from './events_block_move.js';
import type {BubbleOpen} from './events_bubble_open.js';
import type {Click} from './events_click.js';
import type {CommentChange} from './events_comment_change.js';
import type {CommentCollapse} from './events_comment_collapse.js';
import type {CommentCreate} from './events_comment_create.js';
import type {CommentDelete} from './events_comment_delete.js';
import type {CommentDrag} from './events_comment_drag.js';
import type {CommentMove} from './events_comment_move.js';
import type {CommentResize} from './events_comment_resize.js';
import type {MarkerMove} from './events_marker_move.js';
import type {Selected} from './events_selected.js';
import type {ThemeChange} from './events_theme_change.js';
import type {ToolboxItemSelect} from './events_toolbox_item_select.js';
import type {TrashcanOpen} from './events_trashcan_open.js';
import type {VarCreate} from './events_var_create.js';
import type {VarDelete} from './events_var_delete.js';
import type {VarRename} from './events_var_rename.js';
import type {ViewportChange} from './events_viewport.js';
import type {FinishedLoading} from './workspace_events.js';
import {EventType} from './type.js';
/** @returns true iff event.type is EventType.BLOCK_CREATE */
export function isBlockCreate(event: Abstract): event is BlockCreate {
return event.type === EventType.BLOCK_CREATE;
}
/** @returns true iff event.type is EventType.BLOCK_DELETE */
export function isBlockDelete(event: Abstract): event is BlockDelete {
return event.type === EventType.BLOCK_DELETE;
}
/** @returns true iff event.type is EventType.BLOCK_CHANGE */
export function isBlockChange(event: Abstract): event is BlockChange {
return event.type === EventType.BLOCK_CHANGE;
}
/** @returns true iff event.type is EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE */
export function isBlockFieldIntermediateChange(
event: Abstract,
): event is BlockFieldIntermediateChange {
return event.type === EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE;
}
/** @returns true iff event.type is EventType.BLOCK_MOVE */
export function isBlockMove(event: Abstract): event is BlockMove {
return event.type === EventType.BLOCK_MOVE;
}
/** @returns true iff event.type is EventType.VAR_CREATE */
export function isVarCreate(event: Abstract): event is VarCreate {
return event.type === EventType.VAR_CREATE;
}
/** @returns true iff event.type is EventType.VAR_DELETE */
export function isVarDelete(event: Abstract): event is VarDelete {
return event.type === EventType.VAR_DELETE;
}
/** @returns true iff event.type is EventType.VAR_RENAME */
export function isVarRename(event: Abstract): event is VarRename {
return event.type === EventType.VAR_RENAME;
}
/** @returns true iff event.type is EventType.BLOCK_DRAG */
export function isBlockDrag(event: Abstract): event is BlockDrag {
return event.type === EventType.BLOCK_DRAG;
}
/** @returns true iff event.type is EventType.SELECTED */
export function isSelected(event: Abstract): event is Selected {
return event.type === EventType.SELECTED;
}
/** @returns true iff event.type is EventType.CLICK */
export function isClick(event: Abstract): event is Click {
return event.type === EventType.CLICK;
}
/** @returns true iff event.type is EventType.MARKER_MOVE */
export function isMarkerMove(event: Abstract): event is MarkerMove {
return event.type === EventType.MARKER_MOVE;
}
/** @returns true iff event.type is EventType.BUBBLE_OPEN */
export function isBubbleOpen(event: Abstract): event is BubbleOpen {
return event.type === EventType.BUBBLE_OPEN;
}
/** @returns true iff event.type is EventType.TRASHCAN_OPEN */
export function isTrashcanOpen(event: Abstract): event is TrashcanOpen {
return event.type === EventType.TRASHCAN_OPEN;
}
/** @returns true iff event.type is EventType.TOOLBOX_ITEM_SELECT */
export function isToolboxItemSelect(
event: Abstract,
): event is ToolboxItemSelect {
return event.type === EventType.TOOLBOX_ITEM_SELECT;
}
/** @returns true iff event.type is EventType.THEME_CHANGE */
export function isThemeChange(event: Abstract): event is ThemeChange {
return event.type === EventType.THEME_CHANGE;
}
/** @returns true iff event.type is EventType.VIEWPORT_CHANGE */
export function isViewportChange(event: Abstract): event is ViewportChange {
return event.type === EventType.VIEWPORT_CHANGE;
}
/** @returns true iff event.type is EventType.COMMENT_CREATE */
export function isCommentCreate(event: Abstract): event is CommentCreate {
return event.type === EventType.COMMENT_CREATE;
}
/** @returns true iff event.type is EventType.COMMENT_DELETE */
export function isCommentDelete(event: Abstract): event is CommentDelete {
return event.type === EventType.COMMENT_DELETE;
}
/** @returns true iff event.type is EventType.COMMENT_CHANGE */
export function isCommentChange(event: Abstract): event is CommentChange {
return event.type === EventType.COMMENT_CHANGE;
}
/** @returns true iff event.type is EventType.COMMENT_MOVE */
export function isCommentMove(event: Abstract): event is CommentMove {
return event.type === EventType.COMMENT_MOVE;
}
/** @returns true iff event.type is EventType.COMMENT_RESIZE */
export function isCommentResize(event: Abstract): event is CommentResize {
return event.type === EventType.COMMENT_RESIZE;
}
/** @returns true iff event.type is EventType.COMMENT_DRAG */
export function isCommentDrag(event: Abstract): event is CommentDrag {
return event.type === EventType.COMMENT_DRAG;
}
/** @returns true iff event.type is EventType.COMMENT_COLLAPSE */
export function isCommentCollapse(event: Abstract): event is CommentCollapse {
return event.type === EventType.COMMENT_COLLAPSE;
}
/** @returns true iff event.type is EventType.FINISHED_LOADING */
export function isFinishedLoading(event: Abstract): event is FinishedLoading {
return event.type === EventType.FINISHED_LOADING;
}

85
core/events/type.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Enum of values for the .type property for event classes (concrete subclasses
* of Abstract).
*/
export enum EventType {
/** Type of event that creates a block. */
BLOCK_CREATE = 'create',
/** Type of event that deletes a block. */
BLOCK_DELETE = 'delete',
/** Type of event that changes a block. */
BLOCK_CHANGE = 'change',
/**
* Type of event representing an in-progress change to a field of a
* block, which is expected to be followed by a block change event.
*/
BLOCK_FIELD_INTERMEDIATE_CHANGE = 'block_field_intermediate_change',
/** Type of event that moves a block. */
BLOCK_MOVE = 'move',
/** Type of event that creates a variable. */
VAR_CREATE = 'var_create',
/** Type of event that deletes a variable. */
VAR_DELETE = 'var_delete',
/** Type of event that renames a variable. */
VAR_RENAME = 'var_rename',
/**
* Type of generic event that records a UI change.
*
* @deprecated Was only ever intended for internal use.
*/
UI = 'ui',
/** Type of event that drags a block. */
BLOCK_DRAG = 'drag',
/** Type of event that records a change in selected element. */
SELECTED = 'selected',
/** Type of event that records a click. */
CLICK = 'click',
/** Type of event that records a marker move. */
MARKER_MOVE = 'marker_move',
/** Type of event that records a bubble open. */
BUBBLE_OPEN = 'bubble_open',
/** Type of event that records a trashcan open. */
TRASHCAN_OPEN = 'trashcan_open',
/** Type of event that records a toolbox item select. */
TOOLBOX_ITEM_SELECT = 'toolbox_item_select',
/** Type of event that records a theme change. */
THEME_CHANGE = 'theme_change',
/** Type of event that records a viewport change. */
VIEWPORT_CHANGE = 'viewport_change',
/** Type of event that creates a comment. */
COMMENT_CREATE = 'comment_create',
/** Type of event that deletes a comment. */
COMMENT_DELETE = 'comment_delete',
/** Type of event that changes a comment. */
COMMENT_CHANGE = 'comment_change',
/** Type of event that moves a comment. */
COMMENT_MOVE = 'comment_move',
/** Type of event that resizes a comment. */
COMMENT_RESIZE = 'comment_resize',
/** Type of event that drags a comment. */
COMMENT_DRAG = 'comment_drag',
/** Type of event that collapses a comment. */
COMMENT_COLLAPSE = 'comment_collapse',
/** Type of event that records a workspace load. */
FINISHED_LOADING = 'finished_loading',
}
/**
* List of events that cause objects to be bumped back into the visible
* portion of the workspace.
*
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
*/
export const BUMP_EVENTS: string[] = [
EventType.BLOCK_CREATE,
EventType.BLOCK_MOVE,
EventType.COMMENT_CREATE,
EventType.COMMENT_MOVE,
];

View File

@@ -9,18 +9,24 @@
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import * as common from '../common.js'; import * as common from '../common.js';
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import * as deprecation from '../utils/deprecation.js';
import * as idGenerator from '../utils/idgenerator.js'; import * as idGenerator from '../utils/idgenerator.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import type {WorkspaceSvg} from '../workspace_svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js';
import type {Abstract} from './events_abstract.js'; import type {Abstract} from './events_abstract.js';
import type {BlockChange} from './events_block_change.js';
import type {BlockCreate} from './events_block_create.js'; import type {BlockCreate} from './events_block_create.js';
import type {BlockMove} from './events_block_move.js'; import type {BlockMove} from './events_block_move.js';
import type {CommentCreate} from './events_comment_create.js'; import type {CommentCreate} from './events_comment_create.js';
import type {CommentMove} from './events_comment_move.js'; import type {CommentMove} from './events_comment_move.js';
import type {CommentResize} from './events_comment_resize.js'; import type {CommentResize} from './events_comment_resize.js';
import type {ViewportChange} from './events_viewport.js'; import {
isBlockChange,
isBlockCreate,
isBlockMove,
isBubbleOpen,
isClick,
isViewportChange,
} from './predicates.js';
/** Group ID for new events. Grouped events are indivisible. */ /** Group ID for new events. Grouped events are indivisible. */
let group = ''; let group = '';
@@ -49,157 +55,6 @@ export function getRecordUndo(): boolean {
/** Allow change events to be created and fired. */ /** Allow change events to be created and fired. */
let disabled = 0; let disabled = 0;
/**
* Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
*/
export const CREATE = 'create';
/**
* Name of event that creates a block.
*/
export const BLOCK_CREATE = CREATE;
/**
* Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
*/
export const DELETE = 'delete';
/**
* Name of event that deletes a block.
*/
export const BLOCK_DELETE = DELETE;
/**
* Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
*/
export const CHANGE = 'change';
/**
* Name of event that changes a block.
*/
export const BLOCK_CHANGE = CHANGE;
/**
* Name of event representing an in-progress change to a field of a block, which
* is expected to be followed by a block change event.
*/
export const BLOCK_FIELD_INTERMEDIATE_CHANGE =
'block_field_intermediate_change';
/**
* Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
*/
export const MOVE = 'move';
/**
* Name of event that moves a block.
*/
export const BLOCK_MOVE = MOVE;
/**
* Name of event that creates a variable.
*/
export const VAR_CREATE = 'var_create';
/**
* Name of event that deletes a variable.
*/
export const VAR_DELETE = 'var_delete';
/**
* Name of event that renames a variable.
*/
export const VAR_RENAME = 'var_rename';
/**
* Name of event that changes a variable's type.
*/
export const VAR_TYPE_CHANGE = 'var_type_change';
/**
* Name of generic event that records a UI change.
*/
export const UI = 'ui';
/**
* Name of event that drags a block.
*/
export const BLOCK_DRAG = 'drag';
/**
* Name of event that records a change in selected element.
*/
export const SELECTED = 'selected';
/**
* Name of event that records a click.
*/
export const CLICK = 'click';
/**
* Name of event that records a marker move.
*/
export const MARKER_MOVE = 'marker_move';
/**
* Name of event that records a bubble open.
*/
export const BUBBLE_OPEN = 'bubble_open';
/**
* Name of event that records a trashcan open.
*/
export const TRASHCAN_OPEN = 'trashcan_open';
/**
* Name of event that records a toolbox item select.
*/
export const TOOLBOX_ITEM_SELECT = 'toolbox_item_select';
/**
* Name of event that records a theme change.
*/
export const THEME_CHANGE = 'theme_change';
/**
* Name of event that records a viewport change.
*/
export const VIEWPORT_CHANGE = 'viewport_change';
/**
* Name of event that creates a comment.
*/
export const COMMENT_CREATE = 'comment_create';
/**
* Name of event that deletes a comment.
*/
export const COMMENT_DELETE = 'comment_delete';
/**
* Name of event that changes a comment.
*/
export const COMMENT_CHANGE = 'comment_change';
/**
* Name of event that moves a comment.
*/
export const COMMENT_MOVE = 'comment_move';
/** Name of event that resizes a comment. */
export const COMMENT_RESIZE = 'comment_resize';
/** Name of event that drags a comment. */
export const COMMENT_DRAG = 'comment_drag';
/** Type of event that collapses a comment. */
export const COMMENT_COLLAPSE = 'comment_collapse';
/**
* Name of event that records a workspace load.
*/
export const FINISHED_LOADING = 'finished_loading';
/** /**
* The language-neutral ID for when the reason why a block is disabled is * The language-neutral ID for when the reason why a block is disabled is
* because the block is not descended from a root block. * because the block is not descended from a root block.
@@ -220,27 +75,24 @@ export type BumpEvent =
| CommentMove | CommentMove
| CommentResize; | CommentResize;
/**
* List of events that cause objects to be bumped back into the visible
* portion of the workspace.
*
* Not to be confused with bumping so that disconnected connections do not
* appear connected.
*/
export const BUMP_EVENTS: string[] = [
BLOCK_CREATE,
BLOCK_MOVE,
COMMENT_CREATE,
COMMENT_MOVE,
];
/** List of events queued for firing. */ /** List of events queued for firing. */
const FIRE_QUEUE: Abstract[] = []; const FIRE_QUEUE: Abstract[] = [];
/** /**
* Create a custom event and fire it. * Enqueue an event to be dispatched to change listeners.
* *
* @param event Custom data for event. * Notes:
*
* - Events are enqueued until a timeout, generally after rendering is
* complete or at the end of the current microtask, if not running
* in a browser.
* - Queued events are subject to destructive modification by being
* combined with later-enqueued events, but only until they are
* fired.
* - Events are dispatched via the fireChangeListener method on the
* affected workspace.
*
* @param event Any Blockly event.
*/ */
export function fire(event: Abstract) { export function fire(event: Abstract) {
TEST_ONLY.fireInternal(event); TEST_ONLY.fireInternal(event);
@@ -261,166 +113,187 @@ function fireInternal(event: Abstract) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
setTimeout(fireNow, 0); setTimeout(fireNow, 0);
}); });
} catch (e) { } catch {
// Otherwise we just want to delay so events can be coallesced. // Otherwise we just want to delay so events can be coallesced.
// requestAnimationFrame will error triggering this. // requestAnimationFrame will error triggering this.
setTimeout(fireNow, 0); setTimeout(fireNow, 0);
} }
} }
FIRE_QUEUE.push(event); enqueueEvent(event);
} }
/** Fire all queued events. */ /** Dispatch all queued events. */
function fireNow() { function fireNow() {
const queue = filter(FIRE_QUEUE, true); const queue = filter(FIRE_QUEUE, true);
FIRE_QUEUE.length = 0; FIRE_QUEUE.length = 0;
for (let i = 0, event; (event = queue[i]); i++) { for (const event of queue) {
if (!event.workspaceId) { if (!event.workspaceId) continue;
continue; common.getWorkspaceById(event.workspaceId)?.fireChangeListener(event);
}
const eventWorkspace = common.getWorkspaceById(event.workspaceId);
if (eventWorkspace) {
eventWorkspace.fireChangeListener(event);
}
}
// Post-filter the undo stack to squash and remove any events that result in
// a null event
// 1. Determine which workspaces will need to have their undo stacks validated
const workspaceIds = new Set(queue.map((e) => e.workspaceId));
for (const workspaceId of workspaceIds) {
// Only process valid workspaces
if (!workspaceId) {
continue;
}
const eventWorkspace = common.getWorkspaceById(workspaceId);
if (!eventWorkspace) {
continue;
}
// Find the last contiguous group of events on the stack
const undoStack = eventWorkspace.getUndoStack();
let i;
let group: string | undefined = undefined;
for (i = undoStack.length; i > 0; i--) {
const event = undoStack[i - 1];
if (event.group === '') {
break;
} else if (group === undefined) {
group = event.group;
} else if (event.group !== group) {
break;
}
}
if (!group || i == undoStack.length - 1) {
// Need a group of two or more events on the stack. Nothing to do here.
continue;
}
// Extract the event group, filter, and add back to the undo stack
let events = undoStack.splice(i, undoStack.length - i);
events = filter(events, true);
undoStack.push(...events);
} }
} }
/** /**
* Filter the queued events and merge duplicates. * Enqueue an event on FIRE_QUEUE.
* *
* @param queueIn Array of events. * Normally this is equivalent to FIRE_QUEUE.push(event), but if the
* enqueued event is a BlockChange event and the most recent event(s)
* on the queue are BlockMove events that (re)connect other blocks to
* the changed block (and belong to the same event group) then the
* enqueued event will be enqueued before those events rather than
* after.
*
* This is a workaround for a problem caused by the fact that
* MutatorIcon.prototype.recomposeSourceBlock can only fire a
* BlockChange event after the mutating block's compose method
* returns, meaning that if the compose method reconnects child blocks
* the corresponding BlockMove events are emitted _before_ the
* BlockChange event, causing issues with undo, mirroring, etc.; see
* https://github.com/google/blockly/issues/8225#issuecomment-2195751783
* (and following) for details.
*/
function enqueueEvent(event: Abstract) {
if (isBlockChange(event) && event.element === 'mutation') {
let i;
for (i = FIRE_QUEUE.length; i > 0; i--) {
const otherEvent = FIRE_QUEUE[i - 1];
if (
otherEvent.group !== event.group ||
otherEvent.workspaceId !== event.workspaceId ||
!isBlockMove(otherEvent) ||
otherEvent.newParentId !== event.blockId
) {
break;
}
}
FIRE_QUEUE.splice(i, 0, event);
return;
}
FIRE_QUEUE.push(event);
}
/**
* Filter the queued events by merging duplicates, removing null
* events and reording BlockChange events.
*
* History of this function:
*
* This function was originally added in commit cf257ea5 with the
* intention of dramatically reduing the total number of dispatched
* events. Initialy it affected only BlockMove events but others were
* added over time.
*
* Code was added to reorder BlockChange events added in commit
* 5578458, for uncertain reasons but most probably as part of an
* only-partially-successful attemp to fix problems with event
* ordering during block mutations. This code should probably have
* been added to the top of the function, before merging and
* null-removal, but was added at the bottom for now-forgotten
* reasons. See these bug investigations for a fuller discussion of
* the underlying issue and some of the failures that arose because of
* this incomplete/incorrect fix:
*
* https://github.com/google/blockly/issues/8225#issuecomment-2195751783
* https://github.com/google/blockly/issues/2037#issuecomment-2209696351
*
* Later, in PR #1205 the original O(n^2) implementation was replaced
* by a linear-time implementation, though additonal fixes were made
* subsequently.
*
* In August 2024 a number of significant simplifications were made:
*
* This function was previously called from Workspace.prototype.undo,
* but the mutation of events by this function was the cause of issue
* #7026 (note that events would combine differently in reverse order
* vs. forward order). The originally-chosen fix for this was the
* addition (in PR #7069) of code to fireNow to post-filter the
* .undoStack_ and .redoStack_ of any workspace that had just been
* involved in dispatching events; this apparently resolved the issue
* but added considerable additional complexity and made it difficult
* to reason about how events are processed for undo/redo, so both the
* call from undo and the post-processing code was removed, and
* forward=true was made the default while calling the function with
* forward=false was deprecated.
*
* At the same time, the buggy code to reorder BlockChange events was
* replaced by a less-buggy version of the same functionality in a new
* function, enqueueEvent, called from fireInternal, thus assuring
* that events will be in the correct order at the time filter is
* called.
*
* Additionally, the event merging code was modified so that only
* immediately adjacent events would be merged. This simplified the
* implementation while ensuring that the merging of events cannot
* cause them to be reordered.
*
* @param queue Array of events.
* @param forward True if forward (redo), false if backward (undo). * @param forward True if forward (redo), false if backward (undo).
* This parameter is deprecated: true is now the default and
* calling filter with it set to false will in future not be
* supported.
* @returns Array of filtered events. * @returns Array of filtered events.
*/ */
export function filter(queueIn: Abstract[], forward: boolean): Abstract[] { export function filter(queue: Abstract[], forward = true): Abstract[] {
let queue = queueIn.slice();
// Shallow copy of queue.
if (!forward) { if (!forward) {
// Undo is merged in reverse order. deprecation.warn('filter(queue, /*forward=*/false)', 'v11.2', 'v12');
queue.reverse(); // Undo was merged in reverse order.
queue = queue.slice().reverse(); // Copy before reversing in place.
} }
const mergedQueue = []; const mergedQueue: Abstract[] = [];
const hash = Object.create(null);
// Merge duplicates. // Merge duplicates.
for (let i = 0, event; (event = queue[i]); i++) { for (const event of queue) {
if (!event.isNull()) { const lastEvent = mergedQueue[mergedQueue.length - 1];
// Treat all UI events as the same type in hash table. if (event.isNull()) continue;
const eventType = event.isUiEvent ? UI : event.type; if (
// TODO(#5927): Check whether `blockId` exists before accessing it. !lastEvent ||
const blockId = (event as AnyDuringMigration).blockId; lastEvent.workspaceId !== event.workspaceId ||
const key = [eventType, blockId, event.workspaceId].join(' '); lastEvent.group !== event.group
) {
const lastEntry = hash[key]; mergedQueue.push(event);
const lastEvent = lastEntry ? lastEntry.event : null; continue;
if (!lastEntry) { }
// Each item in the hash table has the event and the index of that event if (
// in the input array. This lets us make sure we only merge adjacent isBlockMove(event) &&
// move events. isBlockMove(lastEvent) &&
hash[key] = {event, index: i}; event.blockId === lastEvent.blockId
mergedQueue.push(event); ) {
} else if (event.type === MOVE && lastEntry.index === i - 1) { // Merge move events.
const moveEvent = event as BlockMove; lastEvent.newParentId = event.newParentId;
// Merge move events. lastEvent.newInputName = event.newInputName;
lastEvent.newParentId = moveEvent.newParentId; lastEvent.newCoordinate = event.newCoordinate;
lastEvent.newInputName = moveEvent.newInputName; // Concatenate reasons without duplicates.
lastEvent.newCoordinate = moveEvent.newCoordinate; if (lastEvent.reason || event.reason) {
if (moveEvent.reason) { lastEvent.reason = Array.from(
if (lastEvent.reason) { new Set((lastEvent.reason ?? []).concat(event.reason ?? [])),
// Concatenate reasons without duplicates. );
const reasonSet = new Set(
moveEvent.reason.concat(lastEvent.reason),
);
lastEvent.reason = Array.from(reasonSet);
} else {
lastEvent.reason = moveEvent.reason;
}
}
lastEntry.index = i;
} else if (
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;
} else if (event.type === VIEWPORT_CHANGE) {
const viewportEvent = event as ViewportChange;
// Merge viewport change events.
lastEvent.viewTop = viewportEvent.viewTop;
lastEvent.viewLeft = viewportEvent.viewLeft;
lastEvent.scale = viewportEvent.scale;
lastEvent.oldScale = viewportEvent.oldScale;
} else if (event.type === CLICK && lastEvent.type === BUBBLE_OPEN) {
// Drop click events caused by opening/closing bubbles.
} else {
// Collision: newer events should merge into this event to maintain
// order.
hash[key] = {event, index: i};
mergedQueue.push(event);
} }
} else if (
isBlockChange(event) &&
isBlockChange(lastEvent) &&
event.blockId === lastEvent.blockId &&
event.element === lastEvent.element &&
event.name === lastEvent.name
) {
// Merge change events.
lastEvent.newValue = event.newValue;
} else if (isViewportChange(event) && isViewportChange(lastEvent)) {
// Merge viewport change events.
lastEvent.viewTop = event.viewTop;
lastEvent.viewLeft = event.viewLeft;
lastEvent.scale = event.scale;
lastEvent.oldScale = event.oldScale;
} else if (isClick(event) && isBubbleOpen(lastEvent)) {
// Drop click events caused by opening/closing bubbles.
} else {
mergedQueue.push(event);
} }
} }
// Filter out any events that have become null due to merging. // Filter out any events that have become null due to merging.
queue = mergedQueue.filter(function (e) { queue = mergedQueue.filter((e) => !e.isNull());
return !e.isNull();
});
if (!forward) { if (!forward) {
// Restore undo order. // Restore undo order.
queue.reverse(); queue.reverse();
} }
// Move mutation events to the top of the queue.
// Intentionally skip first event.
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'
) {
queue.unshift(queue.splice(i, 1)[0]);
}
}
return queue; return queue;
} }
@@ -545,7 +418,7 @@ export function get(
* @param event Custom data for event. * @param event Custom data for event.
*/ */
export function disableOrphans(event: Abstract) { export function disableOrphans(event: Abstract) {
if (event.type === MOVE || event.type === CREATE) { if (isBlockMove(event) || isBlockCreate(event)) {
const blockEvent = event as BlockMove | BlockCreate; const blockEvent = event as BlockMove | BlockCreate;
if (!blockEvent.workspaceId) { if (!blockEvent.workspaceId) {
return; return;
@@ -589,6 +462,7 @@ export function disableOrphans(event: Abstract) {
export const TEST_ONLY = { export const TEST_ONLY = {
FIRE_QUEUE, FIRE_QUEUE,
enqueueEvent,
fireNow, fireNow,
fireInternal, fireInternal,
setGroupInternal, setGroupInternal,

View File

@@ -14,7 +14,7 @@
import * as registry from '../registry.js'; import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js'; import type {Workspace} from '../workspace.js';
import {Abstract as AbstractEvent} from './events_abstract.js'; import {Abstract as AbstractEvent} from './events_abstract.js';
import * as eventUtils from './utils.js'; import {EventType} from './type.js';
/** /**
* Notifies listeners when the workspace has finished deserializing from * Notifies listeners when the workspace has finished deserializing from
@@ -23,7 +23,7 @@ import * as eventUtils from './utils.js';
export class FinishedLoading extends AbstractEvent { export class FinishedLoading extends AbstractEvent {
override isBlank = true; override isBlank = true;
override recordUndo = false; override recordUndo = false;
override type = eventUtils.FINISHED_LOADING; override type = EventType.FINISHED_LOADING;
/** /**
* @param opt_workspace The workspace that has finished loading. Undefined * @param opt_workspace The workspace that has finished loading. Undefined
@@ -41,6 +41,6 @@ export class FinishedLoading extends AbstractEvent {
registry.register( registry.register(
registry.Type.EVENT, registry.Type.EVENT,
eventUtils.FINISHED_LOADING, EventType.FINISHED_LOADING,
FinishedLoading, FinishedLoading,
); );

View File

@@ -27,7 +27,10 @@ export const TEST_ONLY = {allExtensions};
* @throws {Error} if the extension name is empty, the extension is already * @throws {Error} if the extension name is empty, the extension is already
* registered, or extensionFn is not a function. * registered, or extensionFn is not a function.
*/ */
export function register(name: string, initFn: Function) { export function register<T extends Block>(
name: string,
initFn: (this: T) => void,
) {
if (typeof name !== 'string' || name.trim() === '') { if (typeof name !== 'string' || name.trim() === '') {
throw Error('Error: Invalid extension name "' + name + '"'); throw Error('Error: Invalid extension name "' + name + '"');
} }

View File

@@ -20,12 +20,14 @@ import type {Block} from './block.js';
import type {BlockSvg} from './block_svg.js'; import type {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import * as dropDownDiv from './dropdowndiv.js'; import * as dropDownDiv from './dropdowndiv.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import type {Input} from './inputs/input.js'; import type {Input} from './inputs/input.js';
import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js';
import type {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js'; import type {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js';
import type {IRegistrable} from './interfaces/i_registrable.js'; import type {IRegistrable} from './interfaces/i_registrable.js';
import {ISerializable} from './interfaces/i_serializable.js';
import {MarkerManager} from './marker_manager.js'; import {MarkerManager} from './marker_manager.js';
import type {ConstantProvider} from './renderers/common/constants.js'; import type {ConstantProvider} from './renderers/common/constants.js';
import type {KeyboardShortcut} from './shortcut_registry.js'; import type {KeyboardShortcut} from './shortcut_registry.js';
@@ -41,7 +43,6 @@ import * as userAgent from './utils/useragent.js';
import * as utilsXml from './utils/xml.js'; import * as utilsXml from './utils/xml.js';
import * as WidgetDiv from './widgetdiv.js'; import * as WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import {ISerializable} from './interfaces/i_serializable.js';
/** /**
* A function that is called to validate changes to the field's value before * A function that is called to validate changes to the field's value before
@@ -106,20 +107,20 @@ export abstract class Field<T = any>
* Used to cache the field's tooltip value if setTooltip is called when the * 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. * 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; protected size_: Size;
/** /**
* Holds the cursors svg element when the cursor is attached to the field. * Holds the cursors svg element when the cursor is attached to the field.
* This is null if there is no cursor on 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. * Holds the markers svg element when the marker is attached to the field.
* This is null if there is no marker on 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. */ /** The rendered field's SVG group element. */
protected fieldGroup_: SVGGElement | null = null; protected fieldGroup_: SVGGElement | null = null;
@@ -134,7 +135,7 @@ export abstract class Field<T = any>
protected textContent_: Text | null = null; protected textContent_: Text | null = null;
/** Mouse down event listener data. */ /** 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. */ /** Constants associated with the source block's renderer. */
protected constants_: ConstantProvider | null = null; protected constants_: ConstantProvider | null = null;
@@ -308,7 +309,7 @@ export abstract class Field<T = any>
sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_); sourceBlockSvg.getSvgRoot().appendChild(this.fieldGroup_);
this.initView(); this.initView();
this.updateEditable(); this.updateEditable();
this.setTooltip(this.tooltip_); this.setTooltip(this.tooltip);
this.bindEvents_(); this.bindEvents_();
this.initModel(); this.initModel();
this.applyColour(); this.applyColour();
@@ -392,7 +393,7 @@ export abstract class Field<T = any>
const clickTarget = this.getClickTarget_(); const clickTarget = this.getClickTarget_();
if (!clickTarget) throw new Error('A click target has not been set.'); if (!clickTarget) throw new Error('A click target has not been set.');
Tooltip.bindMouseEvents(clickTarget); Tooltip.bindMouseEvents(clickTarget);
this.mouseDownWrapper_ = browserEvents.conditionalBind( this.mouseDownWrapper = browserEvents.conditionalBind(
clickTarget, clickTarget,
'pointerdown', 'pointerdown',
this, this,
@@ -1065,62 +1066,73 @@ export abstract class Field<T = any>
setValue(newValue: AnyDuringMigration, fireChangeEvent = true) { setValue(newValue: AnyDuringMigration, fireChangeEvent = true) {
const doLogging = false; const doLogging = false;
if (newValue === null) { if (newValue === null) {
doLogging && console.log('null, return'); if (doLogging) console.log('null, return');
// Not a valid value to check. // Not a valid value to check.
return; return;
} }
const classValidation = this.doClassValidation_(newValue); // Field validators are allowed to make changes to the workspace, which
const classValue = this.processValidation_( // should get grouped with the field value change event.
newValue, const existingGroup = eventUtils.getGroup();
classValidation, if (!existingGroup) {
fireChangeEvent, eventUtils.setGroup(true);
);
if (classValue instanceof Error) {
doLogging && console.log('invalid class validation, return');
return;
} }
const localValidation = this.getValidator()?.call(this, classValue); try {
const localValue = this.processValidation_( const classValidation = this.doClassValidation_(newValue);
classValue, const classValue = this.processValidation(
localValidation, newValue,
fireChangeEvent, classValidation,
); fireChangeEvent,
if (localValue instanceof Error) {
doLogging && console.log('invalid local validation, return');
return;
}
const source = this.sourceBlock_;
if (source && source.disposed) {
doLogging && console.log('source disposed, return');
return;
}
const oldValue = this.getValue();
if (oldValue === localValue) {
doLogging && console.log('same, doValueUpdate_, return');
this.doValueUpdate_(localValue);
return;
}
this.doValueUpdate_(localValue);
if (fireChangeEvent && source && eventUtils.isEnabled()) {
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
source,
'field',
this.name || null,
oldValue,
localValue,
),
); );
if (classValue instanceof Error) {
if (doLogging) console.log('invalid class validation, return');
return;
}
const localValidation = this.getValidator()?.call(this, classValue);
const localValue = this.processValidation(
classValue,
localValidation,
fireChangeEvent,
);
if (localValue instanceof Error) {
if (doLogging) console.log('invalid local validation, return');
return;
}
const source = this.sourceBlock_;
if (source && source.disposed) {
if (doLogging) console.log('source disposed, return');
return;
}
const oldValue = this.getValue();
if (oldValue === localValue) {
if (doLogging) console.log('same, doValueUpdate_, return');
this.doValueUpdate_(localValue);
return;
}
this.doValueUpdate_(localValue);
if (fireChangeEvent && source && eventUtils.isEnabled()) {
eventUtils.fire(
new (eventUtils.get(EventType.BLOCK_CHANGE))(
source,
'field',
this.name || null,
oldValue,
localValue,
),
);
}
if (this.isDirty_) {
this.forceRerender();
}
if (doLogging) console.log(this.value_);
} finally {
eventUtils.setGroup(existingGroup);
} }
if (this.isDirty_) {
this.forceRerender();
}
doLogging && console.log(this.value_);
} }
/** /**
@@ -1131,7 +1143,7 @@ export abstract class Field<T = any>
* @param fireChangeEvent Whether to fire a change event if the value changes. * @param fireChangeEvent Whether to fire a change event if the value changes.
* @returns New value, or an Error object. * @returns New value, or an Error object.
*/ */
private processValidation_( private processValidation(
newValue: AnyDuringMigration, newValue: AnyDuringMigration,
validatedValue: T | null | undefined, validatedValue: T | null | undefined,
fireChangeEvent: boolean, fireChangeEvent: boolean,
@@ -1245,7 +1257,7 @@ export abstract class Field<T = any>
(clickTarget as AnyDuringMigration).tooltip = newTip; (clickTarget as AnyDuringMigration).tooltip = newTip;
} else { } else {
// Field has not been initialized yet. // Field has not been initialized yet.
this.tooltip_ = newTip; this.tooltip = newTip;
} }
} }
@@ -1259,8 +1271,8 @@ export abstract class Field<T = any>
if (clickTarget) { if (clickTarget) {
return Tooltip.getTooltipOfObject(clickTarget); return Tooltip.getTooltipOfObject(clickTarget);
} }
// Field has not been initialized yet. Return stashed this.tooltip_ value. // Field has not been initialized yet. Return stashed this.tooltip value.
return Tooltip.getTooltipOfObject({tooltip: this.tooltip_}); return Tooltip.getTooltipOfObject({tooltip: this.tooltip});
} }
/** /**
@@ -1366,7 +1378,7 @@ export abstract class Field<T = any>
*/ */
setCursorSvg(cursorSvg: SVGElement) { setCursorSvg(cursorSvg: SVGElement) {
if (!cursorSvg) { if (!cursorSvg) {
this.cursorSvg_ = null; this.cursorSvg = null;
return; return;
} }
@@ -1374,7 +1386,7 @@ export abstract class Field<T = any>
throw new Error(`The field group is ${this.fieldGroup_}.`); throw new Error(`The field group is ${this.fieldGroup_}.`);
} }
this.fieldGroup_.appendChild(cursorSvg); this.fieldGroup_.appendChild(cursorSvg);
this.cursorSvg_ = cursorSvg; this.cursorSvg = cursorSvg;
} }
/** /**
@@ -1385,7 +1397,7 @@ export abstract class Field<T = any>
*/ */
setMarkerSvg(markerSvg: SVGElement) { setMarkerSvg(markerSvg: SVGElement) {
if (!markerSvg) { if (!markerSvg) {
this.markerSvg_ = null; this.markerSvg = null;
return; return;
} }
@@ -1393,7 +1405,7 @@ export abstract class Field<T = any>
throw new Error(`The field group is ${this.fieldGroup_}.`); throw new Error(`The field group is ${this.fieldGroup_}.`);
} }
this.fieldGroup_.appendChild(markerSvg); this.fieldGroup_.appendChild(markerSvg);
this.markerSvg_ = markerSvg; this.markerSvg = markerSvg;
} }
/** /**
@@ -1407,10 +1419,10 @@ export abstract class Field<T = any>
throw new UnattachedFieldError(); throw new UnattachedFieldError();
} }
const workspace = block.workspace as WorkspaceSvg; const workspace = block.workspace as WorkspaceSvg;
if (workspace.keyboardAccessibilityMode && this.cursorSvg_) { if (workspace.keyboardAccessibilityMode && this.cursorSvg) {
workspace.getCursor()!.draw(); workspace.getCursor()!.draw();
} }
if (workspace.keyboardAccessibilityMode && this.markerSvg_) { if (workspace.keyboardAccessibilityMode && this.markerSvg) {
// TODO(#4592): Update all markers on the field. // TODO(#4592): Update all markers on the field.
workspace.getMarker(MarkerManager.LOCAL_MARKER)!.draw(); workspace.getMarker(MarkerManager.LOCAL_MARKER)!.draw();
} }

View File

@@ -14,9 +14,9 @@
// Unused import preserved for side-effects. Remove if unneeded. // Unused import preserved for side-effects. Remove if unneeded.
import './events/events_block_change.js'; import './events/events_block_change.js';
import * as dom from './utils/dom.js';
import {Field, FieldConfig, FieldValidator} from './field.js'; import {Field, FieldConfig, FieldValidator} from './field.js';
import * as fieldRegistry from './field_registry.js'; import * as fieldRegistry from './field_registry.js';
import * as dom from './utils/dom.js';
type BoolString = 'TRUE' | 'FALSE'; type BoolString = 'TRUE' | 'FALSE';
type CheckboxBool = BoolString | boolean; type CheckboxBool = BoolString | boolean;
@@ -165,7 +165,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* that this is a either 'TRUE' or 'FALSE'. * that this is a either 'TRUE' or 'FALSE'.
*/ */
protected override doValueUpdate_(newValue: BoolString) { protected override doValueUpdate_(newValue: BoolString) {
this.value_ = this.convertValueToBool_(newValue); this.value_ = this.convertValueToBool(newValue);
// Update visual. // Update visual.
if (this.textElement_) { if (this.textElement_) {
this.textElement_.style.display = this.value_ ? 'block' : 'none'; this.textElement_.style.display = this.value_ ? 'block' : 'none';
@@ -196,7 +196,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @returns Text representing the value of this field ('true' or 'false'). * @returns Text representing the value of this field ('true' or 'false').
*/ */
override getText(): string { override getText(): string {
return String(this.convertValueToBool_(this.value_)); return String(this.convertValueToBool(this.value_));
} }
/** /**
@@ -208,7 +208,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* @param value The value to convert. * @param value The value to convert.
* @returns The converted value. * @returns The converted value.
*/ */
private convertValueToBool_(value: CheckboxBool | null): boolean { private convertValueToBool(value: CheckboxBool | null): boolean {
if (typeof value === 'string') return value === 'TRUE'; if (typeof value === 'string') return value === 'TRUE';
return !!value; return !!value;
} }

View File

@@ -24,12 +24,12 @@ import {
import * as fieldRegistry from './field_registry.js'; import * as fieldRegistry from './field_registry.js';
import {Menu} from './menu.js'; import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js'; import {MenuItem} from './menuitem.js';
import * as style from './utils/style.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js'; import * as parsing from './utils/parsing.js';
import * as utilsString from './utils/string.js'; import * as utilsString from './utils/string.js';
import * as style from './utils/style.js';
import {Svg} from './utils/svg.js'; import {Svg} from './utils/svg.js';
/** /**
@@ -92,6 +92,15 @@ export class FieldDropdown extends Field<string> {
private selectedOption!: MenuOption; private selectedOption!: MenuOption;
override clickTarget_: SVGElement | null = null; override clickTarget_: SVGElement | null = null;
/**
* The y offset from the top of the field to the top of the image, if an image
* is selected.
*/
protected static IMAGE_Y_OFFSET = 5;
/** The total vertical padding above and below an image. */
protected static IMAGE_Y_PADDING = FieldDropdown.IMAGE_Y_OFFSET * 2;
/** /**
* @param menuGenerator A non-empty array of options for a dropdown list, or a * @param menuGenerator A non-empty array of options for a dropdown list, or a
* function which generates these options. Also accepts Field.SKIP_SETUP * function which generates these options. Also accepts Field.SKIP_SETUP
@@ -125,8 +134,8 @@ export class FieldDropdown extends Field<string> {
if (menuGenerator === Field.SKIP_SETUP) return; if (menuGenerator === Field.SKIP_SETUP) return;
if (Array.isArray(menuGenerator)) { if (Array.isArray(menuGenerator)) {
validateOptions(menuGenerator); this.validateOptions(menuGenerator);
const trimmed = trimOptions(menuGenerator); const trimmed = this.trimOptions(menuGenerator);
this.menuGenerator_ = trimmed.options; this.menuGenerator_ = trimmed.options;
this.prefixField = trimmed.prefix || null; this.prefixField = trimmed.prefix || null;
this.suffixField = trimmed.suffix || null; this.suffixField = trimmed.suffix || null;
@@ -403,7 +412,7 @@ export class FieldDropdown extends Field<string> {
if (useCache && this.generatedOptions) return this.generatedOptions; if (useCache && this.generatedOptions) return this.generatedOptions;
this.generatedOptions = this.menuGenerator_(); this.generatedOptions = this.menuGenerator_();
validateOptions(this.generatedOptions); this.validateOptions(this.generatedOptions);
return this.generatedOptions; return this.generatedOptions;
} }
@@ -522,7 +531,7 @@ export class FieldDropdown extends Field<string> {
const hasBorder = !!this.borderRect_; const hasBorder = !!this.borderRect_;
const height = Math.max( const height = Math.max(
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0, hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + IMAGE_Y_PADDING, imageHeight + FieldDropdown.IMAGE_Y_PADDING,
); );
const xPadding = hasBorder const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
@@ -653,6 +662,127 @@ export class FieldDropdown extends Field<string> {
// override the static fromJson method. // override the static fromJson method.
return new this(options.options, undefined, options); return new this(options.options, undefined, options);
} }
/**
* Factor out common words in statically defined options.
* Create prefix and/or suffix labels.
*/
protected trimOptions(options: MenuOption[]): {
options: MenuOption[];
prefix?: string;
suffix?: string;
} {
let hasImages = false;
const trimmedOptions = options.map(([label, value]): MenuOption => {
if (typeof label === 'string') {
return [parsing.replaceMessageReferences(label), value];
}
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};
return [imageLabel, value];
});
if (hasImages || options.length < 2) return {options: trimmedOptions};
const stringOptions = trimmedOptions as [string, string][];
const stringLabels = stringOptions.map(([label]) => label);
const shortest = utilsString.shortestStringLength(stringLabels);
const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest);
const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest);
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;
return {
options: this.applyTrim(stringOptions, prefixLength, suffixLength),
prefix,
suffix,
};
}
/**
* Use the calculated prefix and suffix lengths to trim all of the options in
* the given array.
*
* @param options Array of option tuples:
* (human-readable text or image, language-neutral name).
* @param prefixLength The length of the common prefix.
* @param suffixLength The length of the common suffix
* @returns A new array with all of the option text trimmed.
*/
private applyTrim(
options: [string, string][],
prefixLength: number,
suffixLength: number,
): MenuOption[] {
return options.map(([text, value]) => [
text.substring(prefixLength, text.length - suffixLength),
value,
]);
}
/**
* Validates the data structure to be processed as an options list.
*
* @param options The proposed dropdown options.
* @throws {TypeError} If proposed options are incorrectly structured.
*/
protected validateOptions(options: MenuOption[]) {
if (!Array.isArray(options)) {
throw TypeError('FieldDropdown options must be an array.');
}
if (!options.length) {
throw TypeError('FieldDropdown options must not be an empty array.');
}
let foundError = false;
for (let i = 0; i < options.length; i++) {
const tuple = options[i];
if (!Array.isArray(tuple)) {
foundError = true;
console.error(
`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}`,
);
} else if (
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}`,
);
}
}
if (foundError) {
throw TypeError('Found invalid FieldDropdown options.');
}
}
} }
/** /**
@@ -713,147 +843,4 @@ export interface FieldDropdownFromJsonConfig extends FieldDropdownConfig {
*/ */
export type FieldDropdownValidator = FieldValidator<string>; export type FieldDropdownValidator = FieldValidator<string>;
/**
* The y offset from the top of the field to the top of the image, if an image
* is selected.
*/
const IMAGE_Y_OFFSET = 5;
/** The total vertical padding above and below an image. */
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;
} {
let hasImages = false;
const trimmedOptions = options.map(([label, value]): MenuOption => {
if (typeof label === 'string') {
return [parsing.replaceMessageReferences(label), value];
}
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};
return [imageLabel, value];
});
if (hasImages || options.length < 2) return {options: trimmedOptions};
const stringOptions = trimmedOptions as [string, string][];
const stringLabels = stringOptions.map(([label]) => label);
const shortest = utilsString.shortestStringLength(stringLabels);
const prefixLength = utilsString.commonWordPrefix(stringLabels, shortest);
const suffixLength = utilsString.commonWordSuffix(stringLabels, shortest);
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;
return {
options: applyTrim(stringOptions, prefixLength, suffixLength),
prefix,
suffix,
};
}
/**
* Use the calculated prefix and suffix lengths to trim all of the options in
* the given array.
*
* @param options Array of option tuples:
* (human-readable text or image, language-neutral name).
* @param prefixLength The length of the common prefix.
* @param suffixLength The length of the common suffix
* @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,
]);
}
/**
* Validates the data structure to be processed as an options list.
*
* @param options The proposed dropdown options.
* @throws {TypeError} If proposed options are incorrectly structured.
*/
function validateOptions(options: MenuOption[]) {
if (!Array.isArray(options)) {
throw TypeError('FieldDropdown options must be an array.');
}
if (!options.length) {
throw TypeError('FieldDropdown options must not be an empty array.');
}
let foundError = false;
for (let i = 0; i < options.length; i++) {
const tuple = options[i];
if (!Array.isArray(tuple)) {
foundError = true;
console.error(
'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,
);
} else if (
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,
);
}
}
if (foundError) {
throw TypeError('Found invalid FieldDropdown options.');
}
}
fieldRegistry.register('field_dropdown', FieldDropdown); fieldRegistry.register('field_dropdown', FieldDropdown);

View File

@@ -15,11 +15,11 @@
import './events/events_block_change.js'; import './events/events_block_change.js';
import {BlockSvg} from './block_svg.js'; import {BlockSvg} from './block_svg.js';
import * as bumpObjects from './bump_objects.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import * as bumpObjects from './bump_objects.js';
import * as dialog from './dialog.js'; import * as dialog from './dialog.js';
import * as dom from './utils/dom.js';
import * as dropDownDiv from './dropdowndiv.js'; import * as dropDownDiv from './dropdowndiv.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import { import {
Field, Field,
@@ -28,12 +28,13 @@ import {
UnattachedFieldError, UnattachedFieldError,
} from './field.js'; } from './field.js';
import {Msg} from './msg.js'; import {Msg} from './msg.js';
import * as renderManagement from './render_management.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js';
import {Size} from './utils/size.js';
import * as userAgent from './utils/useragent.js'; import * as userAgent from './utils/useragent.js';
import * as WidgetDiv from './widgetdiv.js'; import * as WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import {Size} from './utils/size.js';
/** /**
* Supported types for FieldInput subclasses. * Supported types for FieldInput subclasses.
@@ -79,10 +80,10 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
protected valueWhenEditorWasOpened_: string | T | null = null; protected valueWhenEditorWasOpened_: string | T | null = null;
/** Key down event data. */ /** Key down event data. */
private onKeyDownWrapper_: browserEvents.Data | null = null; private onKeyDownWrapper: browserEvents.Data | null = null;
/** Key input event data. */ /** 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 * Whether the field should consider the whole parent block to be its click
@@ -188,7 +189,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
fireChangeEvent fireChangeEvent
) { ) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock_, this.sourceBlock_,
'field', 'field',
this.name || null, this.name || null,
@@ -338,9 +339,9 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
this.workspace_.options.modalInputs && this.workspace_.options.modalInputs &&
(userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD) (userAgent.MOBILE || userAgent.ANDROID || userAgent.IPAD)
) { ) {
this.showPromptEditor_(); this.showPromptEditor();
} else { } else {
this.showInlineEditor_(quietInput); this.showInlineEditor(quietInput);
} }
} }
@@ -349,7 +350,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
* Mobile browsers may have issues with in-line textareas (focus and * Mobile browsers may have issues with in-line textareas (focus and
* keyboards). * keyboards).
*/ */
private showPromptEditor_() { private showPromptEditor() {
dialog.prompt( dialog.prompt(
Msg['CHANGE_VALUE_TITLE'], Msg['CHANGE_VALUE_TITLE'],
this.getText(), this.getText(),
@@ -368,7 +369,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
* *
* @param quietInput True if editor should be created without focus. * @param quietInput True if editor should be created without focus.
*/ */
private showInlineEditor_(quietInput: boolean) { private showInlineEditor(quietInput: boolean) {
const block = this.getSourceBlock(); const block = this.getSourceBlock();
if (!block) { if (!block) {
throw new UnattachedFieldError(); throw new UnattachedFieldError();
@@ -476,7 +477,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
// multiple times while the editor was open, but this will fire an event // multiple times while the editor was open, but this will fire an event
// containing the value when the editor was opened as well as the new one. // containing the value when the editor was opened as well as the new one.
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock_, this.sourceBlock_,
'field', 'field',
this.name || null, this.name || null,
@@ -518,30 +519,30 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
*/ */
protected bindInputEvents_(htmlInput: HTMLElement) { protected bindInputEvents_(htmlInput: HTMLElement) {
// Trap Enter without IME and Esc to hide. // Trap Enter without IME and Esc to hide.
this.onKeyDownWrapper_ = browserEvents.conditionalBind( this.onKeyDownWrapper = browserEvents.conditionalBind(
htmlInput, htmlInput,
'keydown', 'keydown',
this, this,
this.onHtmlInputKeyDown_, this.onHtmlInputKeyDown_,
); );
// Resize after every input change. // Resize after every input change.
this.onKeyInputWrapper_ = browserEvents.conditionalBind( this.onKeyInputWrapper = browserEvents.conditionalBind(
htmlInput, htmlInput,
'input', 'input',
this, this,
this.onHtmlInputChange_, this.onHtmlInputChange,
); );
} }
/** Unbind handlers for user input and workspace size changes. */ /** Unbind handlers for user input and workspace size changes. */
protected unbindInputEvents_() { protected unbindInputEvents_() {
if (this.onKeyDownWrapper_) { if (this.onKeyDownWrapper) {
browserEvents.unbind(this.onKeyDownWrapper_); browserEvents.unbind(this.onKeyDownWrapper);
this.onKeyDownWrapper_ = null; this.onKeyDownWrapper = null;
} }
if (this.onKeyInputWrapper_) { if (this.onKeyInputWrapper) {
browserEvents.unbind(this.onKeyInputWrapper_); browserEvents.unbind(this.onKeyInputWrapper);
this.onKeyInputWrapper_ = null; this.onKeyInputWrapper = null;
} }
} }
@@ -574,7 +575,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
* *
* @param _e Keyboard event. * @param _e Keyboard event.
*/ */
private onHtmlInputChange_(_e: Event) { private onHtmlInputChange(_e: Event) {
// Intermediate value changes from user input are not confirmed until the // Intermediate value changes from user input are not confirmed until the
// user closes the editor, and may be numerous. Inhibit reporting these as // user closes the editor, and may be numerous. Inhibit reporting these as
// normal block change events, and instead report them as special // normal block change events, and instead report them as special
@@ -593,7 +594,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
// Fire a special event indicating that the value changed but the change // Fire a special event indicating that the value changed but the change
// isn't complete yet and normal field change listeners can wait. // isn't complete yet and normal field change listeners can wait.
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE))( new (eventUtils.get(EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE))(
this.sourceBlock_, this.sourceBlock_,
this.name || null, this.name || null,
oldValue, oldValue,
@@ -630,22 +631,22 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
/** Resize the editor to fit the text. */ /** Resize the editor to fit the text. */
protected resizeEditor_() { protected resizeEditor_() {
const block = this.getSourceBlock(); renderManagement.finishQueuedRenders().then(() => {
if (!block) { const block = this.getSourceBlock();
throw new UnattachedFieldError(); if (!block) throw new UnattachedFieldError();
} const div = WidgetDiv.getDiv();
const div = WidgetDiv.getDiv(); const bBox = this.getScaledBBox();
const bBox = this.getScaledBBox(); div!.style.width = bBox.right - bBox.left + 'px';
div!.style.width = bBox.right - bBox.left + 'px'; div!.style.height = bBox.bottom - bBox.top + 'px';
div!.style.height = bBox.bottom - bBox.top + 'px';
// In RTL mode block fields and LTR input fields the left edge moves, // In RTL mode block fields and LTR input fields the left edge moves,
// whereas the right edge is fixed. Reposition the editor. // whereas the right edge is fixed. Reposition the editor.
const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left; const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left;
const xy = new Coordinate(x, bBox.top); const y = bBox.top;
div!.style.left = xy.x + 'px'; div!.style.left = `${x}px`;
div!.style.top = xy.y + 'px'; div!.style.top = `${y}px`;
});
} }
/** /**
@@ -657,7 +658,7 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
* div. * div.
*/ */
override repositionForWindowResize(): boolean { override repositionForWindowResize(): boolean {
const block = this.getSourceBlock(); const block = this.getSourceBlock()?.getRootBlock();
// This shouldn't be possible. We should never have a WidgetDiv if not using // This shouldn't be possible. We should never have a WidgetDiv if not using
// rendered blocks. // rendered blocks.
if (!(block instanceof BlockSvg)) return false; if (!(block instanceof BlockSvg)) return false;

View File

@@ -12,9 +12,9 @@
*/ */
// Former goog.module ID: Blockly.FieldLabel // Former goog.module ID: Blockly.FieldLabel
import * as dom from './utils/dom.js';
import {Field, FieldConfig} from './field.js'; import {Field, FieldConfig} from './field.js';
import * as fieldRegistry from './field_registry.js'; import * as fieldRegistry from './field_registry.js';
import * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js'; import * as parsing from './utils/parsing.js';
/** /**

View File

@@ -12,12 +12,12 @@
// Former goog.module ID: Blockly.FieldNumber // Former goog.module ID: Blockly.FieldNumber
import {Field} from './field.js'; import {Field} from './field.js';
import * as fieldRegistry from './field_registry.js';
import { import {
FieldInput, FieldInput,
FieldInputConfig, FieldInputConfig,
FieldInputValidator, FieldInputValidator,
} from './field_input.js'; } from './field_input.js';
import * as fieldRegistry from './field_registry.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';

View File

@@ -11,17 +11,22 @@
*/ */
// Former goog.module ID: Blockly.Flyout // Former goog.module ID: Blockly.Flyout
import type {Abstract as AbstractEvent} from './events/events_abstract.js';
import {BlockSvg} from './block_svg.js'; import {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import {ComponentManager} from './component_manager.js'; import {ComponentManager} from './component_manager.js';
import {DeleteArea} from './delete_area.js'; import {DeleteArea} from './delete_area.js';
import type {Abstract as AbstractEvent} from './events/events_abstract.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import {FlyoutMetricsManager} from './flyout_metrics_manager.js'; import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js';
import {IAutoHideable} from './interfaces/i_autohideable.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
import type {IFlyout} from './interfaces/i_flyout.js'; import type {IFlyout} from './interfaces/i_flyout.js';
import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js'; import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
import type {Options} from './options.js'; import type {Options} from './options.js';
import * as registry from './registry.js';
import * as renderManagement from './render_management.js';
import {ScrollbarPair} from './scrollbar_pair.js'; import {ScrollbarPair} from './scrollbar_pair.js';
import * as blocks from './serialization/blocks.js'; import * as blocks from './serialization/blocks.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
@@ -31,10 +36,6 @@ import {Svg} from './utils/svg.js';
import * as toolbox from './utils/toolbox.js'; import * as toolbox from './utils/toolbox.js';
import * as Variables from './variables.js'; import * as Variables from './variables.js';
import {WorkspaceSvg} from './workspace_svg.js'; import {WorkspaceSvg} from './workspace_svg.js';
import * as registry from './registry.js';
import * as renderManagement from './render_management.js';
import {IAutoHideable} from './interfaces/i_autohideable.js';
import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js';
/** /**
* Class for a flyout. * Class for a flyout.
@@ -152,7 +153,7 @@ export abstract class Flyout
/** /**
* Whether the flyout is visible. * Whether the flyout is visible.
*/ */
private isVisible_ = false; private visible = false;
/** /**
* Whether the workspace containing this flyout is visible. * Whether the workspace containing this flyout is visible.
@@ -237,7 +238,7 @@ export abstract class Flyout
this.workspace_.internalIsFlyout = true; this.workspace_.internalIsFlyout = true;
// Keep the workspace visibility consistent with the flyout's visibility. // Keep the workspace visibility consistent with the flyout's visibility.
this.workspace_.setVisible(this.isVisible_); this.workspace_.setVisible(this.visible);
/** /**
* The unique id for this component that is used to register with the * The unique id for this component that is used to register with the
@@ -369,7 +370,7 @@ export abstract class Flyout
targetWorkspace.getComponentManager().addComponent({ targetWorkspace.getComponentManager().addComponent({
component: this, component: this,
weight: 1, weight: ComponentManager.ComponentWeight.FLYOUT_WEIGHT,
capabilities: [ capabilities: [
ComponentManager.Capability.AUTOHIDEABLE, ComponentManager.Capability.AUTOHIDEABLE,
ComponentManager.Capability.DELETE_AREA, ComponentManager.Capability.DELETE_AREA,
@@ -470,7 +471,7 @@ export abstract class Flyout
* @returns True if visible. * @returns True if visible.
*/ */
isVisible(): boolean { isVisible(): boolean {
return this.isVisible_; return this.visible;
} }
/** /**
@@ -483,7 +484,7 @@ export abstract class Flyout
setVisible(visible: boolean) { setVisible(visible: boolean) {
const visibilityChanged = visible !== this.isVisible(); const visibilityChanged = visible !== this.isVisible();
this.isVisible_ = visible; this.visible = visible;
if (visibilityChanged) { if (visibilityChanged) {
if (!this.autoClose) { if (!this.autoClose) {
// Auto-close flyouts are ignored as drag targets, so only non // Auto-close flyouts are ignored as drag targets, so only non
@@ -818,13 +819,13 @@ export abstract class Flyout
for (let i = 0; i < newVariables.length; i++) { for (let i = 0; i < newVariables.length; i++) {
const thisVariable = newVariables[i]; const thisVariable = newVariables[i];
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.VAR_CREATE))(thisVariable), new (eventUtils.get(EventType.VAR_CREATE))(thisVariable),
); );
} }
// Block events come after var events, in case they refer to newly created // Block events come after var events, in case they refer to newly created
// variables. // variables.
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock)); eventUtils.fire(new (eventUtils.get(EventType.BLOCK_CREATE))(newBlock));
} }
if (this.autoClose) { if (this.autoClose) {
this.hide(); this.hide();

View File

@@ -11,19 +11,19 @@
*/ */
// Former goog.module ID: Blockly.FlyoutButton // Former goog.module ID: Blockly.FlyoutButton
import type {IASTNodeLocationSvg} from './blockly.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import * as Css from './css.js'; import * as Css from './css.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
import type {IRenderedElement} from './interfaces/i_rendered_element.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js'; import * as parsing from './utils/parsing.js';
import {Rect} from './utils/rect.js';
import * as style from './utils/style.js'; import * as style from './utils/style.js';
import {Svg} from './utils/svg.js'; import {Svg} from './utils/svg.js';
import type * as toolbox from './utils/toolbox.js'; import type * as toolbox from './utils/toolbox.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
import type {IRenderedElement} from './interfaces/i_rendered_element.js';
import {Rect} from './utils/rect.js';
/** /**
* Class for a button or label in the flyout. * Class for a button or label in the flyout.

View File

@@ -38,13 +38,13 @@ export class FlyoutMetricsManager extends MetricsManager {
* *
* @returns The bounding box of the blocks on the workspace. * @returns The bounding box of the blocks on the workspace.
*/ */
private getBoundingBox_(): private getBoundingBox():
| SVGRect | SVGRect
| {height: number; y: number; width: number; x: number} { | {height: number; y: number; width: number; x: number} {
let blockBoundingBox; let blockBoundingBox;
try { try {
blockBoundingBox = this.workspace_.getCanvas().getBBox(); blockBoundingBox = this.workspace_.getCanvas().getBBox();
} catch (e) { } catch {
// Firefox has trouble with hidden elements (Bug 528969). // Firefox has trouble with hidden elements (Bug 528969).
// 2021 Update: It looks like this was fixed around Firefox 77 released in // 2021 Update: It looks like this was fixed around Firefox 77 released in
// 2020. // 2020.
@@ -55,7 +55,7 @@ export class FlyoutMetricsManager extends MetricsManager {
override getContentMetrics(opt_getWorkspaceCoordinates?: boolean) { override getContentMetrics(opt_getWorkspaceCoordinates?: boolean) {
// The bounding box is in workspace coordinates. // The bounding box is in workspace coordinates.
const blockBoundingBox = this.getBoundingBox_(); const blockBoundingBox = this.getBoundingBox();
const scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale; const scale = opt_getWorkspaceCoordinates ? 1 : this.workspace_.scale;
return { return {

View File

@@ -24,7 +24,7 @@ import type {Workspace} from './workspace.js';
* @deprecated * @deprecated
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/generating-code} * @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/generating-code}
* @param block The Block instance to generate code for. * @param block The Block instance to generate code for.
* @param genearator The CodeGenerator calling the function. * @param generator The CodeGenerator calling the function.
* @returns A string containing the generated code (for statement blocks), * @returns A string containing the generated code (for statement blocks),
* or a [code, precedence] tuple (for value/expression blocks), or * or a [code, precedence] tuple (for value/expression blocks), or
* null if no code should be emitted for block. * null if no code should be emitted for block.

View File

@@ -18,23 +18,24 @@ import './events/events_click.js';
import * as blockAnimations from './block_animations.js'; import * as blockAnimations from './block_animations.js';
import type {BlockSvg} from './block_svg.js'; import type {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js'; import * as browserEvents from './browser_events.js';
import {RenderedWorkspaceComment} from './comments.js';
import * as common from './common.js'; import * as common from './common.js';
import {config} from './config.js'; import {config} from './config.js';
import * as dropDownDiv from './dropdowndiv.js'; import * as dropDownDiv from './dropdowndiv.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js'; import * as eventUtils from './events/utils.js';
import type {Field} from './field.js'; import type {Field} from './field.js';
import type {IBubble} from './interfaces/i_bubble.js'; import type {IBubble} from './interfaces/i_bubble.js';
import {IDraggable, isDraggable} from './interfaces/i_draggable.js';
import {IDragger} from './interfaces/i_dragger.js';
import type {IFlyout} from './interfaces/i_flyout.js'; import type {IFlyout} from './interfaces/i_flyout.js';
import type {IIcon} from './interfaces/i_icon.js';
import * as registry from './registry.js';
import * as Tooltip from './tooltip.js'; import * as Tooltip from './tooltip.js';
import * as Touch from './touch.js'; import * as Touch from './touch.js';
import {Coordinate} from './utils/coordinate.js'; import {Coordinate} from './utils/coordinate.js';
import {WorkspaceDragger} from './workspace_dragger.js'; import {WorkspaceDragger} from './workspace_dragger.js';
import type {WorkspaceSvg} from './workspace_svg.js'; import type {WorkspaceSvg} from './workspace_svg.js';
import type {IIcon} from './interfaces/i_icon.js';
import {IDragger} from './interfaces/i_dragger.js';
import * as registry from './registry.js';
import {IDraggable, isDraggable} from './interfaces/i_draggable.js';
import {RenderedWorkspaceComment} from './comments.js';
/** /**
* Note: In this file "start" refers to pointerdown * Note: In this file "start" refers to pointerdown
@@ -145,7 +146,7 @@ export class Gesture {
private mostRecentEvent: PointerEvent; private mostRecentEvent: PointerEvent;
/** Boolean for whether or not this gesture is a multi-touch gesture. */ /** Boolean for whether or not this gesture is a multi-touch gesture. */
private isMultiTouch_ = false; private multiTouch = false;
/** A map of cached points used for tracking multi-touch gestures. */ /** A map of cached points used for tracking multi-touch gestures. */
private cachedPoints = new Map<string, Coordinate | null>(); private cachedPoints = new Map<string, Coordinate | null>();
@@ -585,7 +586,7 @@ export class Gesture {
const point0 = this.cachedPoints.get(pointers[0])!; const point0 = this.cachedPoints.get(pointers[0])!;
const point1 = this.cachedPoints.get(pointers[1])!; const point1 = this.cachedPoints.get(pointers[1])!;
this.startDistance = Coordinate.distance(point0, point1); this.startDistance = Coordinate.distance(point0, point1);
this.isMultiTouch_ = true; this.multiTouch = true;
e.preventDefault(); e.preventDefault();
} }
} }
@@ -599,13 +600,20 @@ export class Gesture {
*/ */
handleTouchMove(e: PointerEvent) { handleTouchMove(e: PointerEvent) {
const pointerId = Touch.getTouchIdentifierFromEvent(e); const pointerId = Touch.getTouchIdentifierFromEvent(e);
// Update the cache
this.cachedPoints.set(pointerId, this.getTouchPoint(e)); this.cachedPoints.set(pointerId, this.getTouchPoint(e));
if (this.isPinchZoomEnabled && this.cachedPoints.size === 2) { if (this.isPinchZoomEnabled && this.cachedPoints.size === 2) {
this.handlePinch(e); this.handlePinch(e);
} else { } else {
this.handleMove(e); // Handle the move directly instead of calling handleMove
this.updateFromEvent(e);
if (this.workspaceDragger) {
this.workspaceDragger.drag(this.currentDragDeltaXY);
} else if (this.dragger) {
this.dragger.onDrag(this.mostRecentEvent, this.currentDragDeltaXY);
}
e.preventDefault();
e.stopPropagation();
} }
} }
@@ -683,7 +691,7 @@ export class Gesture {
* @internal * @internal
*/ */
isMultiTouch(): boolean { isMultiTouch(): boolean {
return this.isMultiTouch_; return this.multiTouch;
} }
/** /**
@@ -769,7 +777,7 @@ export class Gesture {
*/ */
private fireWorkspaceClick(ws: WorkspaceSvg) { private fireWorkspaceClick(ws: WorkspaceSvg) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.CLICK))(null, ws.id, 'workspace'), new (eventUtils.get(EventType.CLICK))(null, ws.id, 'workspace'),
); );
} }
@@ -902,7 +910,7 @@ export class Gesture {
); );
} }
// Clicks events are on the start block, even if it was a shadow. // Clicks events are on the start block, even if it was a shadow.
const event = new (eventUtils.get(eventUtils.CLICK))( const event = new (eventUtils.get(EventType.CLICK))(
this.startBlock, this.startBlock,
this.startWorkspace_.id, this.startWorkspace_.id,
'block', 'block',

View File

@@ -12,10 +12,10 @@
*/ */
// Former goog.module ID: Blockly.Grid // Former goog.module ID: Blockly.Grid
import * as dom from './utils/dom.js';
import {Coordinate} from './utils/coordinate.js';
import {Svg} from './utils/svg.js';
import {GridOptions} from './options.js'; import {GridOptions} from './options.js';
import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
/** /**
* Class for a workspace's grid. * Class for a workspace's grid.

View File

@@ -4,21 +4,21 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import {Icon} from './icons/icon.js';
import {CommentIcon, CommentState} from './icons/comment_icon.js'; import {CommentIcon, CommentState} from './icons/comment_icon.js';
import {MutatorIcon} from './icons/mutator_icon.js';
import {WarningIcon} from './icons/warning_icon.js';
import {IconType} from './icons/icon_types.js';
import * as exceptions from './icons/exceptions.js'; import * as exceptions from './icons/exceptions.js';
import {Icon} from './icons/icon.js';
import {IconType} from './icons/icon_types.js';
import {MutatorIcon} from './icons/mutator_icon.js';
import * as registry from './icons/registry.js'; import * as registry from './icons/registry.js';
import {WarningIcon} from './icons/warning_icon.js';
export { export {
Icon,
CommentIcon, CommentIcon,
CommentState, CommentState,
MutatorIcon,
WarningIcon,
IconType,
exceptions, exceptions,
Icon,
IconType,
MutatorIcon,
registry, registry,
WarningIcon,
}; };

View File

@@ -8,21 +8,21 @@
import type {Block} from '../block.js'; import type {Block} from '../block.js';
import type {BlockSvg} from '../block_svg.js'; import type {BlockSvg} from '../block_svg.js';
import {IconType} from './icon_types.js'; import {TextInputBubble} from '../bubbles/textinput_bubble.js';
import {Coordinate} from '../utils.js'; import {EventType} from '../events/type.js';
import * as dom from '../utils/dom.js';
import * as eventUtils from '../events/utils.js'; import * as eventUtils from '../events/utils.js';
import {Icon} from './icon.js';
import type {IHasBubble} from '../interfaces/i_has_bubble.js'; import type {IHasBubble} from '../interfaces/i_has_bubble.js';
import type {ISerializable} from '../interfaces/i_serializable.js'; import type {ISerializable} from '../interfaces/i_serializable.js';
import * as renderManagement from '../render_management.js';
import {Coordinate} from '../utils.js';
import * as dom from '../utils/dom.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import * as registry from './registry.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import {TextBubble} from '../bubbles/text_bubble.js';
import {TextInputBubble} from '../bubbles/textinput_bubble.js';
import type {WorkspaceSvg} from '../workspace_svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js';
import * as renderManagement from '../render_management.js'; import {Icon} from './icon.js';
import {IconType} from './icon_types.js';
import * as registry from './registry.js';
/** The size of the comment icon in workspace-scale units. */ /** The size of the comment icon in workspace-scale units. */
const SIZE = 17; const SIZE = 17;
@@ -46,12 +46,9 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
*/ */
static readonly WEIGHT = 3; static readonly WEIGHT = 3;
/** The bubble used to show editable text to the user. */ /** The bubble used to show comment text to the user. */
private textInputBubble: TextInputBubble | null = null; private textInputBubble: TextInputBubble | null = null;
/** The bubble used to show non-editable text to the user. */
private textBubble: TextBubble | null = null;
/** The text of this comment. */ /** The text of this comment. */
private text = ''; private text = '';
@@ -120,7 +117,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
override dispose() { override dispose() {
super.dispose(); super.dispose();
this.textInputBubble?.dispose(); this.textInputBubble?.dispose();
this.textBubble?.dispose();
} }
override getWeight(): number { override getWeight(): number {
@@ -135,7 +131,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
super.applyColour(); super.applyColour();
const colour = (this.sourceBlock as BlockSvg).style.colourPrimary; const colour = (this.sourceBlock as BlockSvg).style.colourPrimary;
this.textInputBubble?.setColour(colour); this.textInputBubble?.setColour(colour);
this.textBubble?.setColour(colour);
} }
/** /**
@@ -161,14 +156,13 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
} }
const anchorLocation = this.getAnchorLocation(); const anchorLocation = this.getAnchorLocation();
this.textInputBubble?.setAnchorLocation(anchorLocation); this.textInputBubble?.setAnchorLocation(anchorLocation);
this.textBubble?.setAnchorLocation(anchorLocation);
} }
/** Sets the text of this comment. Updates any bubbles if they are visible. */ /** Sets the text of this comment. Updates any bubbles if they are visible. */
setText(text: string) { setText(text: string) {
const oldText = this.text; const oldText = this.text;
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock, this.sourceBlock,
'comment', 'comment',
null, null,
@@ -178,7 +172,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
); );
this.text = text; this.text = text;
this.textInputBubble?.setText(this.text); this.textInputBubble?.setText(this.text);
this.textBubble?.setText(this.text);
} }
/** Returns the text of this comment. */ /** Returns the text of this comment. */
@@ -282,7 +275,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
if (this.text === newText) return; if (this.text === newText) return;
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock, this.sourceBlock,
'comment', 'comment',
null, null,
@@ -338,7 +331,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
} }
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))( new (eventUtils.get(EventType.BUBBLE_OPEN))(
this.sourceBlock, this.sourceBlock,
visible, visible,
'comment', 'comment',
@@ -351,6 +344,18 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
* to update the state of this icon in response to changes in the bubble. * to update the state of this icon in response to changes in the bubble.
*/ */
private showEditableBubble() { private showEditableBubble() {
this.createBubble();
this.textInputBubble?.addTextChangeListener(() => this.onTextChange());
this.textInputBubble?.addSizeChangeListener(() => this.onSizeChange());
}
/** Shows the non editable text bubble for this comment. */
private showNonEditableBubble() {
this.createBubble();
this.textInputBubble?.setEditable(false);
}
protected createBubble() {
this.textInputBubble = new TextInputBubble( this.textInputBubble = new TextInputBubble(
this.sourceBlock.workspace as WorkspaceSvg, this.sourceBlock.workspace as WorkspaceSvg,
this.getAnchorLocation(), this.getAnchorLocation(),
@@ -368,25 +373,10 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
); );
} }
/** Shows the non editable text bubble for this comment. */
private showNonEditableBubble() {
this.textBubble = new TextBubble(
this.getText(),
this.sourceBlock.workspace as WorkspaceSvg,
this.getAnchorLocation(),
this.getBubbleOwnerRect(),
);
if (this.bubbleLocation) {
this.textBubble.moveDuringDrag(this.bubbleLocation);
}
}
/** Hides any open bubbles owned by this comment. */ /** Hides any open bubbles owned by this comment. */
private hideBubble() { private hideBubble() {
this.textInputBubble?.dispose(); this.textInputBubble?.dispose();
this.textInputBubble = null; this.textInputBubble = null;
this.textBubble?.dispose();
this.textBubble = null;
} }
/** /**
@@ -406,8 +396,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
* I.E. the block that owns this icon. * I.E. the block that owns this icon.
*/ */
private getBubbleOwnerRect(): Rect { private getBubbleOwnerRect(): Rect {
const bbox = (this.sourceBlock as BlockSvg).getSvgRoot().getBBox(); return (this.sourceBlock as BlockSvg).getBoundingRectangleWithoutChildren();
return new Rect(bbox.y, bbox.y + bbox.height, bbox.x, bbox.x + bbox.width);
} }
} }

View File

@@ -9,12 +9,12 @@ import type {BlockSvg} from '../block_svg.js';
import * as browserEvents from '../browser_events.js'; import * as browserEvents from '../browser_events.js';
import {hasBubble} from '../interfaces/i_has_bubble.js'; import {hasBubble} from '../interfaces/i_has_bubble.js';
import type {IIcon} from '../interfaces/i_icon.js'; import type {IIcon} from '../interfaces/i_icon.js';
import * as tooltip from '../tooltip.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import type {IconType} from './icon_types.js'; import type {IconType} from './icon_types.js';
import * as tooltip from '../tooltip.js';
/** /**
* The abstract icon class. Icons are visual elements that live in the top-start * The abstract icon class. Icons are visual elements that live in the top-start

View File

@@ -6,22 +6,24 @@
// Former goog.module ID: Blockly.Mutator // Former goog.module ID: Blockly.Mutator
import type {BlockSvg} from '../block_svg.js';
import type {BlocklyOptions} from '../blockly_options.js';
import {MiniWorkspaceBubble} from '../bubbles/mini_workspace_bubble.js';
import type {Abstract} from '../events/events_abstract.js'; import type {Abstract} from '../events/events_abstract.js';
import {BlockChange} from '../events/events_block_change.js'; import {BlockChange} from '../events/events_block_change.js';
import type {BlocklyOptions} from '../blockly_options.js'; import {isBlockChange, isBlockCreate} from '../events/predicates.js';
import type {BlockSvg} from '../block_svg.js'; import {EventType} from '../events/type.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import * as eventUtils from '../events/utils.js'; import * as eventUtils from '../events/utils.js';
import type {IHasBubble} from '../interfaces/i_has_bubble.js'; import type {IHasBubble} from '../interfaces/i_has_bubble.js';
import {Icon} from './icon.js'; import * as renderManagement from '../render_management.js';
import {MiniWorkspaceBubble} from '../bubbles/mini_workspace_bubble.js'; import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js'; import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import type {WorkspaceSvg} from '../workspace_svg.js'; import type {WorkspaceSvg} from '../workspace_svg.js';
import {Icon} from './icon.js';
import {IconType} from './icon_types.js'; import {IconType} from './icon_types.js';
import * as renderManagement from '../render_management.js';
/** The size of the mutator icon in workspace-scale units. */ /** The size of the mutator icon in workspace-scale units. */
const SIZE = 17; const SIZE = 17;
@@ -193,7 +195,7 @@ export class MutatorIcon extends Icon implements IHasBubble {
} }
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))( new (eventUtils.get(EventType.BUBBLE_OPEN))(
this.sourceBlock, this.sourceBlock,
visible, visible,
'mutator', 'mutator',
@@ -307,9 +309,8 @@ export class MutatorIcon extends Icon implements IHasBubble {
static isIgnorableMutatorEvent(e: Abstract) { static isIgnorableMutatorEvent(e: Abstract) {
return ( return (
e.isUiEvent || e.isUiEvent ||
e.type === eventUtils.CREATE || isBlockCreate(e) ||
(e.type === eventUtils.CHANGE && (isBlockChange(e) && e.element === 'disabled')
(e as BlockChange).element === 'disabled')
); );
} }
@@ -331,7 +332,7 @@ export class MutatorIcon extends Icon implements IHasBubble {
if (oldExtraState !== newExtraState) { if (oldExtraState !== newExtraState) {
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))( new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock, this.sourceBlock,
'mutation', 'mutation',
null, null,

View File

@@ -7,17 +7,18 @@
// Former goog.module ID: Blockly.Warning // Former goog.module ID: Blockly.Warning
import type {BlockSvg} from '../block_svg.js'; import type {BlockSvg} from '../block_svg.js';
import {TextBubble} from '../bubbles/text_bubble.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import type {IHasBubble} from '../interfaces/i_has_bubble.js';
import * as renderManagement from '../render_management.js';
import {Size} from '../utils.js';
import {Coordinate} from '../utils/coordinate.js'; import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js'; import * as dom from '../utils/dom.js';
import * as eventUtils from '../events/utils.js';
import {Icon} from './icon.js';
import type {IHasBubble} from '../interfaces/i_has_bubble.js';
import {Rect} from '../utils/rect.js'; import {Rect} from '../utils/rect.js';
import {Size} from '../utils.js';
import {Svg} from '../utils/svg.js'; import {Svg} from '../utils/svg.js';
import {TextBubble} from '../bubbles/text_bubble.js'; import {Icon} from './icon.js';
import {IconType} from './icon_types.js'; import {IconType} from './icon_types.js';
import * as renderManagement from '../render_management.js';
/** The size of the warning icon in workspace-scale units. */ /** The size of the warning icon in workspace-scale units. */
const SIZE = 17; const SIZE = 17;
@@ -188,7 +189,7 @@ export class WarningIcon extends Icon implements IHasBubble {
} }
eventUtils.fire( eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))( new (eventUtils.get(EventType.BUBBLE_OPEN))(
this.sourceBlock, this.sourceBlock,
visible, visible,
'warning', 'warning',

View File

@@ -22,7 +22,6 @@ import * as Touch from './touch.js';
import * as aria from './utils/aria.js'; import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js'; import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js'; import {Svg} from './utils/svg.js';
import * as userAgent from './utils/useragent.js';
import * as WidgetDiv from './widgetdiv.js'; import * as WidgetDiv from './widgetdiv.js';
import {WorkspaceSvg} from './workspace_svg.js'; import {WorkspaceSvg} from './workspace_svg.js';
@@ -342,18 +341,6 @@ function bindDocumentEvents() {
// should run regardless of what other touch event handlers have run. // should run regardless of what other touch event handlers have run.
browserEvents.bind(document, 'touchend', null, Touch.longStop); browserEvents.bind(document, 'touchend', null, Touch.longStop);
browserEvents.bind(document, 'touchcancel', null, Touch.longStop); browserEvents.bind(document, 'touchcancel', null, Touch.longStop);
// Some iPad versions don't fire resize after portrait to landscape change.
if (userAgent.IPAD) {
browserEvents.conditionalBind(
window,
'orientationchange',
document,
function () {
// TODO (#397): Fix for multiple Blockly workspaces.
common.svgResize(common.getMainWorkspace() as WorkspaceSvg);
},
);
}
} }
documentEventsBound = true; documentEventsBound = true;
} }

View File

@@ -5,19 +5,19 @@
*/ */
import {Align} from './inputs/align.js'; import {Align} from './inputs/align.js';
import {Input} from './inputs/input.js';
import {DummyInput} from './inputs/dummy_input.js'; import {DummyInput} from './inputs/dummy_input.js';
import {EndRowInput} from './inputs/end_row_input.js'; import {EndRowInput} from './inputs/end_row_input.js';
import {Input} from './inputs/input.js';
import {inputTypes} from './inputs/input_types.js';
import {StatementInput} from './inputs/statement_input.js'; import {StatementInput} from './inputs/statement_input.js';
import {ValueInput} from './inputs/value_input.js'; import {ValueInput} from './inputs/value_input.js';
import {inputTypes} from './inputs/input_types.js';
export { export {
Align, Align,
Input,
DummyInput, DummyInput,
EndRowInput, EndRowInput,
Input,
inputTypes,
StatementInput, StatementInput,
ValueInput, ValueInput,
inputTypes,
}; };

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