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

@@ -23,38 +23,39 @@ import * as common from './common.js';
import {Connection} from './connection.js';
import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js';
import {DuplicateIconType} from './icons/exceptions.js';
import type {Abstract} from './events/events_abstract.js';
import type {BlockChange} from './events/events_block_change.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 Extensions from './extensions.js';
import type {Field} from './field.js';
import * as fieldRegistry from './field_registry.js';
import {Input} from './inputs/input.js';
import {Align} from './inputs/align.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 {DuplicateIconType} from './icons/exceptions.js';
import {IconType} from './icons/icon_types.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 arrayUtils from './utils/array.js';
import {Coordinate} from './utils/coordinate.js';
import * as deprecation from './utils/deprecation.js';
import * as idGenerator from './utils/idgenerator.js';
import * as parsing from './utils/parsing.js';
import * as registry from './registry.js';
import {Size} from './utils/size.js';
import type {
IVariableModel,
IVariableModel,
IVariableState,
} from './interfaces/i_variable_model.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.
@@ -91,7 +92,7 @@ export class Block implements IASTNodeLocation {
* 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.
*/
private hue_: number | null = null;
private hue: number | null = null;
/** Colour of the block in '#RRGGBB' format. */
protected colour_ = '#000000';
@@ -146,24 +147,31 @@ export class Block implements IASTNodeLocation {
suppressPrefixSuffix: boolean | null = false;
/**
* An optional property for declaring developer variables. Return a list of
* variable names for use by generators. Developer variables are never
* shown to the user, but are declared as global variables in the generated
* code.
* An optional method for declaring developer variables, to be used
* by generators. Developer variables are never shown to the user,
* but are declared as global variables in the generated code.
*
* @returns a list of developer variable names.
*/
getDeveloperVariables?: () => string[];
/**
* An optional function that reconfigures the block based on the contents of
* the mutator dialog.
* An optional method that reconfigures the block based on the
* 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
* this block's components.
* An optional function that populates the mutator flyout with
* 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;
outputConnection: Connection | null = null;
nextConnection: Connection | null = null;
@@ -179,13 +187,13 @@ export class Block implements IASTNodeLocation {
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 outputShape_: number | null = null;
@@ -202,7 +210,7 @@ export class Block implements IASTNodeLocation {
*/
initialized = false;
private readonly xy_: Coordinate;
private readonly xy: Coordinate;
isInFlyout: boolean;
isInMutator: 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.
*/
helpUrl: string | Function | null = null;
helpUrl: string | (() => string) | null = null;
/** 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.
@@ -255,7 +263,7 @@ export class Block implements IASTNodeLocation {
* The block's position in workspace units. (0, 0) is at the workspace's
* origin; scale does not change this value.
*/
this.xy_ = new Coordinate(0, 0);
this.xy = new Coordinate(0, 0);
this.isInFlyout = workspace.isFlyout;
this.isInMutator = workspace.isMutator;
@@ -300,7 +308,7 @@ export class Block implements IASTNodeLocation {
// Fire a create event.
if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(this));
eventUtils.fire(new (eventUtils.get(EventType.BLOCK_CREATE))(this));
}
} finally {
eventUtils.setGroup(existingGroup);
@@ -328,14 +336,14 @@ export class Block implements IASTNodeLocation {
// Dispose of this change listener before unplugging.
// Technically not necessary due to the event firing delay.
// But future-proofing.
if (this.onchangeWrapper_) {
this.workspace.removeChangeListener(this.onchangeWrapper_);
if (this.onchangeWrapper) {
this.workspace.removeChangeListener(this.onchangeWrapper);
}
this.unplug(healStack);
if (eventUtils.isEnabled()) {
// Constructing the delete event is costly. Only perform if necessary.
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_DELETE))(this));
eventUtils.fire(new (eventUtils.get(EventType.BLOCK_DELETE))(this));
}
this.workspace.removeTopBlock(this);
this.disposeInternal();
@@ -347,8 +355,8 @@ export class Block implements IASTNodeLocation {
*/
protected disposeInternal() {
this.disposing = true;
if (this.onchangeWrapper_) {
this.workspace.removeChangeListener(this.onchangeWrapper_);
if (this.onchangeWrapper) {
this.workspace.removeChangeListener(this.onchangeWrapper);
}
this.workspace.removeTypedBlock(this);
@@ -398,10 +406,10 @@ export class Block implements IASTNodeLocation {
*/
unplug(opt_healStack?: boolean) {
if (this.outputConnection) {
this.unplugFromRow_(opt_healStack);
this.unplugFromRow(opt_healStack);
}
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
* block. Defaults to false.
*/
private unplugFromRow_(opt_healStack?: boolean) {
private unplugFromRow(opt_healStack?: boolean) {
let parentConnection = null;
if (this.outputConnection?.isConnected()) {
parentConnection = this.outputConnection.targetConnection;
@@ -425,7 +433,7 @@ export class Block implements IASTNodeLocation {
return;
}
const thisConnection = this.getOnlyValueConnection_();
const thisConnection = this.getOnlyValueConnection();
if (
!thisConnection ||
!thisConnection.isConnected() ||
@@ -462,7 +470,7 @@ export class Block implements IASTNodeLocation {
*
* @returns The connection on the value input, or null.
*/
private getOnlyValueConnection_(): Connection | null {
private getOnlyValueConnection(): Connection | null {
let connection = null;
for (let i = 0; i < this.inputList.length; i++) {
const thisConnection = this.inputList[i].connection;
@@ -487,7 +495,7 @@ export class Block implements IASTNodeLocation {
* @param opt_healStack Disconnect child statement and reconnect stack.
* Defaults to false.
*/
private unplugFromStack_(opt_healStack?: boolean) {
private unplugFromStack(opt_healStack?: boolean) {
let previousTarget = null;
if (this.previousConnection?.isConnected()) {
// 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
// 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 =
(this.previousConnection && this.previousConnection.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
// update
// its connection locations.
// update its connection locations.
if (this.parentBlock_) {
// Remove this block from the old parent's child list.
arrayUtils.removeElem(this.parentBlock_.childBlocks_, this);
} else {
// New parent must be non-null so remove this block from the workspace's
// list of top-most blocks.
// New parent must be non-null so remove this block from the
// workspace's list of top-most blocks.
this.workspace.removeTopBlock(this);
}
@@ -785,8 +792,8 @@ export class Block implements IASTNodeLocation {
*/
isDeletable(): boolean {
return (
this.deletable_ &&
!this.isShadow_ &&
this.deletable &&
!this.shadow &&
!this.isDeadOrDying() &&
!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.
*/
isOwnDeletable(): boolean {
return this.deletable_;
return this.deletable;
}
/**
@@ -807,7 +814,7 @@ export class Block implements IASTNodeLocation {
* @param deletable True if deletable.
*/
setDeletable(deletable: boolean) {
this.deletable_ = deletable;
this.deletable = deletable;
}
/**
@@ -818,8 +825,8 @@ export class Block implements IASTNodeLocation {
*/
isMovable(): boolean {
return (
this.movable_ &&
!this.isShadow_ &&
this.movable &&
!this.shadow &&
!this.isDeadOrDying() &&
!this.workspace.options.readOnly
);
@@ -832,7 +839,7 @@ export class Block implements IASTNodeLocation {
* @internal
*/
isOwnMovable(): boolean {
return this.movable_;
return this.movable;
}
/**
@@ -841,7 +848,7 @@ export class Block implements IASTNodeLocation {
* @param movable True if movable.
*/
setMovable(movable: boolean) {
this.movable_ = movable;
this.movable = movable;
}
/**
@@ -867,7 +874,7 @@ export class Block implements IASTNodeLocation {
* @returns True if a shadow.
*/
isShadow(): boolean {
return this.isShadow_;
return this.shadow;
}
/**
@@ -879,7 +886,7 @@ export class Block implements IASTNodeLocation {
* @internal
*/
setShadow(shadow: boolean) {
this.isShadow_ = shadow;
this.shadow = shadow;
}
/**
@@ -910,9 +917,7 @@ export class Block implements IASTNodeLocation {
*/
isEditable(): boolean {
return (
this.editable_ &&
!this.isDeadOrDying() &&
!this.workspace.options.readOnly
this.editable && !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.
*/
isOwnEditable(): boolean {
return this.editable_;
return this.editable;
}
/**
@@ -931,7 +936,7 @@ export class Block implements IASTNodeLocation {
* @param editable True if editable.
*/
setEditable(editable: boolean) {
this.editable_ = editable;
this.editable = editable;
for (let i = 0, input; (input = this.inputList[i]); i++) {
for (let j = 0, field; (field = input.fieldRow[j]); j++) {
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
* for no help.
*/
setHelpUrl(url: string | Function) {
setHelpUrl(url: string | (() => string)) {
this.helpUrl = url;
}
@@ -1042,7 +1047,7 @@ export class Block implements IASTNodeLocation {
* @returns Hue value (0-360).
*/
getHue(): number | null {
return this.hue_;
return this.hue;
}
/**
@@ -1053,7 +1058,7 @@ export class Block implements IASTNodeLocation {
*/
setColour(colour: number | string) {
const parsed = parsing.parseBlockColour(colour);
this.hue_ = parsed.hue;
this.hue = parsed.hue;
this.colour_ = parsed.hex;
}
@@ -1079,12 +1084,12 @@ export class Block implements IASTNodeLocation {
if (onchangeFn && typeof onchangeFn !== 'function') {
throw Error('onchange must be a function.');
}
if (this.onchangeWrapper_) {
this.workspace.removeChangeListener(this.onchangeWrapper_);
if (this.onchangeWrapper) {
this.workspace.removeChangeListener(this.onchangeWrapper);
}
this.onchange = onchangeFn;
this.onchangeWrapper_ = onchangeFn.bind(this);
this.workspace.addChangeListener(this.onchangeWrapper_);
this.onchangeWrapper = onchangeFn.bind(this);
this.workspace.addChangeListener(this.onchangeWrapper);
}
/**
@@ -1326,7 +1331,7 @@ export class Block implements IASTNodeLocation {
setInputsInline(newBoolean: boolean) {
if (this.inputsInline !== newBoolean) {
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
new (eventUtils.get(EventType.BLOCK_CHANGE))(
this,
'inline',
null,
@@ -1463,13 +1468,32 @@ export class Block implements IASTNodeLocation {
* update whether the block is currently disabled for this reason.
*/
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 (disabled) {
this.disabledReasons.add(reason);
} else {
this.disabledReasons.delete(reason);
}
const blockChangeEvent = new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
const blockChangeEvent = new (eventUtils.get(EventType.BLOCK_CHANGE))(
this,
'disabled',
/* name= */ null,
@@ -1537,7 +1561,7 @@ export class Block implements IASTNodeLocation {
setCollapsed(collapsed: boolean) {
if (this.collapsed_ !== collapsed) {
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
new (eventUtils.get(EventType.BLOCK_CHANGE))(
this,
'collapsed',
null,
@@ -1751,15 +1775,15 @@ export class Block implements IASTNodeLocation {
if (json['style'] && json['colour']) {
throw Error(warningPrefix + 'Must not have both a colour and a style.');
} else if (json['style']) {
this.jsonInitStyle_(json, warningPrefix);
this.jsonInitStyle(json, warningPrefix);
} else {
this.jsonInitColour_(json, warningPrefix);
this.jsonInitColour(json, warningPrefix);
}
// Interpolate the message blocks.
let i = 0;
while (json['message' + i] !== undefined) {
this.interpolate_(
this.interpolate(
json['message' + i],
json['args' + i] || [],
// Backwards compatibility: lastDummyAlign aliases implicitAlign.
@@ -1834,7 +1858,7 @@ export class Block implements IASTNodeLocation {
* @param json Structured data describing the 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 (json['colour'] === undefined) {
console.warn(warningPrefix + 'Undefined colour value.');
@@ -1842,7 +1866,7 @@ export class Block implements IASTNodeLocation {
const rawValue = json['colour'];
try {
this.setColour(rawValue);
} catch (e) {
} catch {
console.warn(warningPrefix + 'Illegal colour value: ', rawValue);
}
}
@@ -1855,11 +1879,11 @@ export class Block implements IASTNodeLocation {
* @param json Structured data describing the block.
* @param warningPrefix Warning prefix string identifying block.
*/
private jsonInitStyle_(json: AnyDuringMigration, warningPrefix: string) {
private jsonInitStyle(json: AnyDuringMigration, warningPrefix: string) {
const blockStyleName = json['style'];
try {
this.setStyle(blockStyleName);
} catch (styleError) {
} catch {
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?
* @param warningPrefix Warning prefix string identifying block.
*/
private interpolate_(
private interpolate(
message: string,
args: AnyDuringMigration[],
implicitAlign: string | undefined,
warningPrefix: string,
) {
const tokens = parsing.tokenizeInterpolation(message);
this.validateTokens_(tokens, args.length);
const elements = this.interpolateArguments_(tokens, args, implicitAlign);
this.validateTokens(tokens, args.length);
const elements = this.interpolateArguments(tokens, args, implicitAlign);
// An array of [field, fieldName] tuples.
const fieldStack = [];
for (let i = 0, element; (element = elements[i]); i++) {
if (this.isInputKeyword_(element['type'])) {
const input = this.inputFromJson_(element, warningPrefix);
if (this.isInputKeyword(element['type'])) {
const input = this.inputFromJson(element, warningPrefix);
// Should never be null, but just in case.
if (input) {
for (let j = 0, tuple; (tuple = fieldStack[j]); j++) {
@@ -1932,7 +1956,7 @@ export class Block implements IASTNodeLocation {
} else {
// All other types, including ones starting with 'input_' get routed
// here.
const field = this.fieldFromJson_(element);
const field = this.fieldFromJson(element);
if (field) {
fieldStack.push([field, element['name']]);
}
@@ -1948,7 +1972,7 @@ export class Block implements IASTNodeLocation {
* @param tokens An array of tokens to validate
* @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 = [];
let visitedArgsCount = 0;
for (let i = 0; i < tokens.length; i++) {
@@ -2003,7 +2027,7 @@ export class Block implements IASTNodeLocation {
* or dummy inputs, if necessary.
* @returns The JSON definitions of field and inputs to add to the block.
*/
private interpolateArguments_(
private interpolateArguments(
tokens: Array<string | number>,
args: Array<AnyDuringMigration | string>,
implicitAlign: string | undefined,
@@ -2026,7 +2050,7 @@ export class Block implements IASTNodeLocation {
} else {
// AnyDuringMigration because: Type '{ text: string; type: string; }
// | null' is not assignable to type 'string | number'.
element = this.stringToFieldJson_(element) as AnyDuringMigration;
element = this.stringToFieldJson(element) as AnyDuringMigration;
if (!element) {
continue;
}
@@ -2038,9 +2062,7 @@ export class Block implements IASTNodeLocation {
const length = elements.length;
if (
length &&
!this.isInputKeyword_(
(elements as AnyDuringMigration)[length - 1]['type'],
)
!this.isInputKeyword((elements as AnyDuringMigration)[length - 1]['type'])
) {
const dummyInput = {'type': 'input_dummy'};
if (implicitAlign) {
@@ -2060,7 +2082,7 @@ export class Block implements IASTNodeLocation {
* @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.
*/
private fieldFromJson_(element: {
private fieldFromJson(element: {
alt?: string;
type: string;
text?: string;
@@ -2068,10 +2090,10 @@ export class Block implements IASTNodeLocation {
const field = fieldRegistry.fromJson(element);
if (!field && element['alt']) {
if (typeof element['alt'] === 'string') {
const json = this.stringToFieldJson_(element['alt']);
return json ? this.fieldFromJson_(json) : null;
const json = this.stringToFieldJson(element['alt']);
return json ? this.fieldFromJson(json) : null;
}
return this.fieldFromJson_(element['alt']);
return this.fieldFromJson(element['alt']);
}
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
* created for some reason (should never happen).
*/
private inputFromJson_(
private inputFromJson(
element: AnyDuringMigration,
warningPrefix: string,
): Input | null {
@@ -2144,7 +2166,7 @@ export class Block implements IASTNodeLocation {
* @returns True if the given string matches one of the input keywords, false
* otherwise.
*/
private isInputKeyword_(str: string): boolean {
private isInputKeyword(str: string): boolean {
return (
str === 'input_value' ||
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.
* @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();
if (str) {
return {
@@ -2336,7 +2358,7 @@ export class Block implements IASTNodeLocation {
}
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
new (eventUtils.get(EventType.BLOCK_CHANGE))(
this,
'comment',
null,
@@ -2422,7 +2444,7 @@ export class Block implements IASTNodeLocation {
* @returns Object with .x and .y properties.
*/
getRelativeToSurfaceXY(): Coordinate {
return this.xy_;
return this.xy;
}
/**
@@ -2436,11 +2458,9 @@ export class Block implements IASTNodeLocation {
if (this.parentBlock_) {
throw Error('Block has parent');
}
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
this,
) as BlockMove;
reason && event.setReason(reason);
this.xy_.translate(dx, dy);
const event = new (eventUtils.get(EventType.BLOCK_MOVE))(this) as BlockMove;
if (reason) event.setReason(reason);
this.xy.translate(dx, dy);
event.recordNew();
eventUtils.fire(event);
}

View File

@@ -16,7 +16,9 @@ import './events/events_selected.js';
import {Block} from './block.js';
import * as blockAnimations from './block_animations.js';
import {IDeletable} from './blockly.js';
import * as browserEvents from './browser_events.js';
import {BlockCopyData, BlockPaster} from './clipboard/block_paster.js';
import * as common from './common.js';
import {config} from './config.js';
import type {Connection} from './connection.js';
@@ -28,11 +30,15 @@ import {
ContextMenuRegistry,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
import {BlockDragStrategy} from './dragging/block_drag_strategy.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 type {Field} from './field.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 {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.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 {MarkerManager} from './marker_manager.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 type {IPathObject} from './renderers/common/i_path_object.js';
import * as blocks from './serialization/blocks.js';
import type {BlockStyle} from './theme.js';
import * as Tooltip from './tooltip.js';
import {Coordinate} from './utils/coordinate.js';
import * as deprecation from './utils/deprecation.js';
import * as dom from './utils/dom.js';
import {Rect} from './utils/rect.js';
import {Svg} from './utils/svg.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 {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.
@@ -92,7 +93,25 @@ export class BlockSvg
static readonly COLLAPSED_WARNING_ID = 'TEMP_COLLAPSED_WARNING_';
override decompose?: (p1: Workspace) => BlockSvg;
// 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?: (
p1: Array<ContextMenuOption | LegacyContextMenuOption>,
) => void;
@@ -126,7 +145,7 @@ export class BlockSvg
/** Block's mutator icon (if any). */
mutator: MutatorIcon | null = null;
private svgGroup_: SVGGElement;
private svgGroup: SVGGElement;
style: BlockStyle;
/** @internal */
pathObject: IPathObject;
@@ -136,15 +155,6 @@ export class BlockSvg
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;
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
override outputConnection!: RenderedConnection;
@@ -182,10 +192,10 @@ export class BlockSvg
throw TypeError('Cannot create a rendered block in a headless workspace');
}
this.workspace = workspace;
this.svgGroup_ = dom.createSvgElement(Svg.G, {});
this.svgGroup = dom.createSvgElement(Svg.G, {});
if (prototypeName) {
dom.addClass(this.svgGroup_, prototypeName);
dom.addClass(this.svgGroup, prototypeName);
}
/** A block style object. */
this.style = workspace.getRenderer().getConstants().getBlockStyle(null);
@@ -193,14 +203,14 @@ export class BlockSvg
/** The renderer's path object. */
this.pathObject = workspace
.getRenderer()
.makePathObject(this.svgGroup_, this.style);
.makePathObject(this.svgGroup, this.style);
const svgPath = this.pathObject.svgPath;
(svgPath as any).tooltip = this;
Tooltip.bindMouseEvents(svgPath);
// 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_();
}
@@ -222,12 +232,7 @@ export class BlockSvg
this.pathObject.updateMovable(this.isMovable() || this.isInFlyout);
const svg = this.getSvgRoot();
if (!this.workspace.options.readOnly && svg) {
browserEvents.conditionalBind(
svg,
'pointerdown',
this,
this.onMouseDown_,
);
browserEvents.conditionalBind(svg, 'pointerdown', this, this.onMouseDown);
}
if (!svg.parentNode) {
@@ -263,7 +268,7 @@ export class BlockSvg
this.addSelect();
}
/** Unselects this block. Unhighlights the blockv visually. */
/** Unselects this block. Unhighlights the block visually. */
unselect() {
if (this.isShadow()) {
this.getParent()?.unselect();
@@ -362,8 +367,8 @@ export class BlockSvg
const eventsEnabled = eventUtils.isEnabled();
let event: BlockMove | null = null;
if (eventsEnabled) {
event = new (eventUtils.get(eventUtils.BLOCK_MOVE)!)(this) as BlockMove;
reason && event.setReason(reason);
event = new (eventUtils.get(EventType.BLOCK_MOVE)!)(this) as BlockMove;
if (reason) event.setReason(reason);
}
const delta = new Coordinate(dx, dy);
@@ -502,14 +507,14 @@ export class BlockSvg
return;
}
super.setCollapsed(collapsed);
this.updateCollapsed_();
this.updateCollapsed();
}
/**
* Makes sure that when the block is collapsed, it is rendered correctly
* for that state.
*/
private updateCollapsed_() {
private updateCollapsed() {
const collapsed = this.isCollapsed();
const collapsedInputName = constants.COLLAPSED_INPUT_NAME;
const collapsedFieldName = constants.COLLAPSED_FIELD_NAME;
@@ -527,11 +532,11 @@ export class BlockSvg
if (!collapsed) {
this.updateDisabled();
this.removeInput(collapsedInputName);
dom.removeClass(this.svgGroup_, 'blocklyCollapsed');
dom.removeClass(this.svgGroup, 'blocklyCollapsed');
return;
}
dom.addClass(this.svgGroup_, 'blocklyCollapsed');
dom.addClass(this.svgGroup, 'blocklyCollapsed');
const text = this.toString(internalConstants.COLLAPSE_CHARS);
const field = this.getField(collapsedFieldName);
@@ -579,7 +584,7 @@ export class BlockSvg
*
* @param e Pointer down event.
*/
private onMouseDown_(e: PointerEvent) {
private onMouseDown(e: PointerEvent) {
const gesture = this.workspace.getGesture(e);
if (gesture) {
gesture.handleBlockStart(e, this);
@@ -683,7 +688,7 @@ export class BlockSvg
* @param className
*/
addClass(className: string) {
dom.addClass(this.svgGroup_, className);
dom.addClass(this.svgGroup, className);
}
/**
@@ -692,7 +697,7 @@ export class BlockSvg
* @param className
*/
removeClass(className: string) {
dom.removeClass(this.svgGroup_, className);
dom.removeClass(this.svgGroup, className);
}
/**
@@ -737,9 +742,9 @@ export class BlockSvg
super.setEditable(editable);
if (editable) {
dom.removeClass(this.svgGroup_, 'blocklyNotEditable');
dom.removeClass(this.svgGroup, 'blocklyNotEditable');
} else {
dom.addClass(this.svgGroup_, 'blocklyNotEditable');
dom.addClass(this.svgGroup, 'blocklyNotEditable');
}
const icons = this.getIcons();
@@ -787,7 +792,7 @@ export class BlockSvg
* @returns The root SVG node (probably a group).
*/
getSvgRoot(): SVGGElement {
return this.svgGroup_;
return this.svgGroup;
}
/**
@@ -809,8 +814,27 @@ export class BlockSvg
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);
dom.removeNode(this.svgGroup_);
dom.removeNode(this.svgGroup);
}
/**
@@ -1090,9 +1114,9 @@ export class BlockSvg
super.setDeletable(deletable);
if (deletable) {
dom.removeClass(this.svgGroup_, 'blocklyNotDeletable');
dom.removeClass(this.svgGroup, 'blocklyNotDeletable');
} else {
dom.addClass(this.svgGroup_, 'blocklyNotDeletable');
dom.addClass(this.svgGroup, 'blocklyNotDeletable');
}
}
@@ -1180,7 +1204,7 @@ export class BlockSvg
.getBlockStyle(blockStyleName);
if (this.styleName_) {
dom.removeClass(this.svgGroup_, this.styleName_);
dom.removeClass(this.svgGroup, this.styleName_);
}
if (blockStyle) {
@@ -1192,7 +1216,7 @@ export class BlockSvg
this.applyColour();
dom.addClass(this.svgGroup_, blockStyleName);
dom.addClass(this.svgGroup, blockStyleName);
this.styleName_ = blockStyleName;
} else {
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
* 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.
* 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.
* @internal
*/
bringToFront(blockOnly = false) {
/* eslint-disable-next-line @typescript-eslint/no-this-alias */
@@ -1484,9 +1509,9 @@ export class BlockSvg
if (conn.isConnected() && neighbour.isConnected()) continue;
if (conn.isSuperior()) {
neighbour.bumpAwayFrom(conn);
neighbour.bumpAwayFrom(conn, /* initiatedByThis = */ false);
} else {
conn.bumpAwayFrom(neighbour);
conn.bumpAwayFrom(neighbour, /* initiatedByThis = */ true);
}
}
}
@@ -1577,7 +1602,7 @@ export class BlockSvg
dom.startTextWidthCache();
if (this.isCollapsed()) {
this.updateCollapsed_();
this.updateCollapsed();
}
if (!this.isEnabled()) {

View File

@@ -17,13 +17,17 @@ import './events/events_var_create.js';
import {Block} from './block.js';
import * as blockAnimations from './block_animations.js';
import {BlockFlyoutInflater} from './block_flyout_inflater.js';
import {BlockSvg} from './block_svg.js';
import {BlocklyOptions} from './blockly_options.js';
import {Blocks} from './blocks.js';
import * as browserEvents from './browser_events.js';
import * as bubbles from './bubbles.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
import * as bumpObjects from './bump_objects.js';
import {ButtonFlyoutInflater} from './button_flyout_inflater.js';
import * as clipboard from './clipboard.js';
import * as comments from './comments.js';
import * as common from './common.js';
import {ComponentManager} from './component_manager.js';
import {config} from './config.js';
@@ -31,15 +35,15 @@ import {Connection} from './connection.js';
import {ConnectionChecker} from './connection_checker.js';
import {ConnectionDB} from './connection_db.js';
import {ConnectionType} from './connection_type.js';
import * as constants from './constants.js';
import * as ContextMenu from './contextmenu.js';
import * as ContextMenuItems from './contextmenu_items.js';
import {ContextMenuRegistry} from './contextmenu_registry.js';
import * as comments from './comments.js';
import * as Css from './css.js';
import {DeleteArea} from './delete_area.js';
import * as dialog from './dialog.js';
import * as dragging from './dragging.js';
import {DragTarget} from './drag_target.js';
import * as dragging from './dragging.js';
import * as dropDownDiv from './dropdowndiv.js';
import * as Events from './events/events.js';
import * as Extensions from './extensions.js';
@@ -97,22 +101,21 @@ import {
} from './field_variable.js';
import {Flyout, FlyoutItem} from './flyout_base.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 {FlyoutMetricsManager} from './flyout_metrics_manager.js';
import {FlyoutSeparator} from './flyout_separator.js';
import {VerticalFlyout} from './flyout_vertical.js';
import {CodeGenerator} from './generator.js';
import {Gesture} from './gesture.js';
import {Grid} from './grid.js';
import * as icons from './icons.js';
import {inject} from './inject.js';
import {Input} from './inputs/input.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 {IASTNodeLocation} from './interfaces/i_ast_node_location.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 {IConnectionPreviewer} from './interfaces/i_connection_previewer.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 {IDeleteArea} from './interfaces/i_delete_area.js';
import {IDragTarget} from './interfaces/i_drag_target.js';
import {IDragger} from './interfaces/i_dragger.js';
import {
IDragStrategy,
IDraggable,
isDraggable,
IDragStrategy,
} from './interfaces/i_draggable.js';
import {IDragger} from './interfaces/i_dragger.js';
import {IFlyout} from './interfaces/i_flyout.js';
import {IHasBubble, hasBubble} from './interfaces/i_has_bubble.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 {IToolbox} from './interfaces/i_toolbox.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 {
IVariableBackedParameterModel,
isVariableBackedParameterModel,
} 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 {ASTNode} from './keyboard_nav/ast_node.js';
import {BasicCursor} from './keyboard_nav/basic_cursor.js';
import {Cursor} from './keyboard_nav/cursor.js';
import {Marker} from './keyboard_nav/marker.js';
import {TabNavigateCursor} from './keyboard_nav/tab_navigate_cursor.js';
import {MarkerManager} from './marker_manager.js';
import type {LayerManager} from './layer_manager.js';
import * as layers from './layers.js';
import {MarkerManager} from './marker_manager.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
import {MetricsManager} from './metrics_manager.js';
import {Msg, setLocale} from './msg.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
import {Names} from './names.js';
import {Options} from './options.js';
import * as uiPosition from './positionable_helpers.js';
import * as Procedures from './procedures.js';
import * as registry from './registry.js';
import {RenderedConnection} from './rendered_connection.js';
import * as renderManagement from './render_management.js';
import {RenderedConnection} from './rendered_connection.js';
import * as blockRendering from './renderers/common/block_rendering.js';
import * as constants from './constants.js';
import * as geras from './renderers/geras/geras.js';
import * as thrasos from './renderers/thrasos/thrasos.js';
import * as zelos from './renderers/zelos/zelos.js';
@@ -426,181 +427,205 @@ Names.prototype.populateProcedures = function (
// clang-format on
// Re-export submodules that no longer declareLegacyNamespace.
export {browserEvents};
export {ContextMenu};
export {ContextMenuItems};
export {Css};
export {Events};
export {Extensions};
export {Procedures};
export {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 {
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,
FieldCheckboxConfig,
FieldCheckboxFromJsonConfig,
FieldCheckboxValidator,
};
export {
FieldConfig,
FieldDropdown,
FieldDropdownConfig,
FieldDropdownFromJsonConfig,
FieldDropdownValidator,
ImageProperties,
MenuGenerator,
MenuGeneratorFunction,
MenuOption,
};
export {FieldImage, FieldImageConfig, FieldImageFromJsonConfig};
export {FieldLabel, FieldLabelConfig, FieldLabelFromJsonConfig};
export {FieldLabelSerializable};
export {
FieldImage,
FieldImage,
FieldImageConfig,
FieldImageConfig,
FieldImageFromJsonConfig,
FieldImageFromJsonConfig,
FieldLabel,
FieldLabel,
FieldLabelConfig,
FieldLabelConfig,
FieldLabelFromJsonConfig,
FieldLabelFromJsonConfig,
FieldLabelSerializable,
FieldLabelSerializable,
FieldNumber,
FieldNumberConfig,
FieldNumberFromJsonConfig,
FieldNumberValidator,
};
export {
FieldTextInput,
FieldTextInputConfig,
FieldTextInputFromJsonConfig,
FieldTextInputValidator,
};
export {
FieldValidator,
FieldVariable,
FieldVariableConfig,
FieldVariableFromJsonConfig,
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
import type {Theme, ITheme} from './theme.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import type {ITheme, Theme} from './theme.js';
import type {ToolboxDefinition} from './utils/toolbox.js';
import type {WorkspaceSvg} from './workspace_svg.js';
/**
* Blockly options.

View File

@@ -6,6 +6,10 @@
// 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 userAgent from './utils/useragent.js';
@@ -47,7 +51,7 @@ const PAGE_MODE_MULTIPLIER = 125;
export function conditionalBind(
node: EventTarget,
name: string,
thisObject: Object | null,
thisObject: object | null,
func: Function,
opt_noCaptureIdentifier?: boolean,
): Data {
@@ -96,7 +100,7 @@ export function conditionalBind(
export function bind(
node: EventTarget,
name: string,
thisObject: Object | null,
thisObject: object | null,
func: Function,
): Data {
/**

View File

@@ -5,8 +5,8 @@
*/
import {Bubble} from './bubbles/bubble.js';
import {MiniWorkspaceBubble} from './bubbles/mini_workspace_bubble.js';
import {TextBubble} from './bubbles/text_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
*/
import {ISelectable} from '../blockly.js';
import * as browserEvents from '../browser_events.js';
import * as common from '../common.js';
import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js';
import {IBubble} from '../interfaces/i_bubble.js';
import {ContainerRegion} from '../metrics_manager.js';
import {Scrollbar} from '../scrollbar.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import * as idGenerator from '../utils/idgenerator.js';
import * as math from '../utils/math.js';
import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js';
import {Svg} from '../utils/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
@@ -208,9 +208,10 @@ export abstract class Bubble implements IBubble, ISelectable {
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) {
this.workspace.getGesture(e)?.handleBubbleStart(e, this);
this.bringToFront();
common.setSelected(this);
}

View File

@@ -4,16 +4,16 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Abstract as AbstractEvent} from '../events/events_abstract.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 * 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 {Size} from '../utils/size.js';
import {Svg} from '../utils/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.

View File

@@ -4,13 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Bubble} from './bubble.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 {Svg} from '../utils/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.

View File

@@ -4,16 +4,17 @@
* 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 touch from '../touch.js';
import {browserEvents} from '../utils.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import * as drag from '../utils/drag.js';
import {Rect} from '../utils/rect.js';
import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.js';
import * as touch from '../touch.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.
@@ -65,6 +66,8 @@ export class TextInputBubble extends Bubble {
20 + Bubble.DOUBLE_BORDER,
);
private editable = true;
/**
* @param workspace The workspace this bubble belongs 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();
}
/** 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. */
addTextChangeListener(listener: () => void) {
this.textChangeListeners.push(listener);
@@ -247,7 +265,8 @@ export class TextInputBubble extends Bubble {
return;
}
this.workspace.startDrag(
drag.start(
this.workspace,
e,
new Coordinate(
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. */
private onResizePointerMove(e: PointerEvent) {
const delta = this.workspace.moveDrag(e);
const delta = drag.move(this.workspace, e);
this.setSize(
new Size(this.workspace.RTL ? -delta.x : delta.x, delta.y),
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 {CommentMove} from './events/events_comment_move.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 type {IBoundedElement} from './interfaces/i_bounded_element.js';
import type {ContainerRegion} from './metrics_manager.js';
@@ -99,7 +100,7 @@ export function bumpIntoBoundsHandler(
return;
}
if (eventUtils.BUMP_EVENTS.includes(e.type ?? '')) {
if (BUMP_EVENTS.includes(e.type ?? '')) {
const scrollMetricsInWsCoords = metricsManager.getScrollMetrics(true);
// Triggered by move/create event
@@ -127,13 +128,8 @@ export function bumpIntoBoundsHandler(
);
}
eventUtils.setGroup(existingGroup);
} else if (e.type === eventUtils.VIEWPORT_CHANGE) {
const viewportEvent = e as ViewportChange;
if (
viewportEvent.scale &&
viewportEvent.oldScale &&
viewportEvent.scale > viewportEvent.oldScale
) {
} else if (isViewportChange(e)) {
if (e.scale && e.oldScale && e.scale > e.oldScale) {
bumpTopObjectsIntoBounds(workspace);
}
}
@@ -155,16 +151,16 @@ function extractObjectFromEvent(
): IBoundedElement | null {
let object = null;
switch (e.type) {
case eventUtils.BLOCK_CREATE:
case eventUtils.BLOCK_MOVE:
case EventType.BLOCK_CREATE:
case EventType.BLOCK_MOVE:
object = workspace.getBlockById((e as BlockCreate | BlockMove).blockId!);
if (object) {
object = object.getRootBlock();
}
break;
case eventUtils.COMMENT_CREATE:
case eventUtils.COMMENT_MOVE:
case eventUtils.COMMENT_RESIZE:
case EventType.COMMENT_CREATE:
case EventType.COMMENT_MOVE:
case EventType.COMMENT_RESIZE:
object = workspace.getCommentById(
(e as CommentCreate | CommentMove | CommentResize).commentId!,
) as RenderedWorkspaceComment;

View File

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

View File

@@ -5,15 +5,16 @@
*/
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 {IPaster} from '../interfaces/i_paster.js';
import {State, append} from '../serialization/blocks.js';
import {Coordinate} from '../utils/coordinate.js';
import {WorkspaceSvg} from '../workspace_svg.js';
import * as eventUtils from '../events/utils.js';
import {config} from '../config.js';
import * as common from '../common.js';
import * as registry from './registry.js';
export class BlockPaster implements IPaster<BlockCopyData, BlockSvg> {
static TYPE = 'block';
@@ -52,7 +53,7 @@ export class BlockPaster implements IPaster<BlockCopyData, BlockSvg> {
if (!block) return block;
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);
return block;

View File

@@ -4,15 +4,16 @@
* 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 {IPaster} from '../interfaces/i_paster.js';
import * as commentSerialiation from '../serialization/workspace_comments.js';
import {Coordinate} from '../utils/coordinate.js';
import {WorkspaceSvg} from '../workspace_svg.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
implements IPaster<WorkspaceCommentCopyData, RenderedWorkspaceComment>
@@ -46,7 +47,7 @@ export class WorkspaceCommentPaster
if (!comment) return null;
if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_CREATE))(comment));
eventUtils.fire(new (eventUtils.get(EventType.COMMENT_CREATE))(comment));
}
common.setSelected(comment);
return comment;

View File

@@ -5,5 +5,5 @@
*/
export {CommentView} from './comments/comment_view.js';
export {WorkspaceComment} from './comments/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
*/
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 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 {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 {
/** The root group element of the comment view. */
@@ -528,8 +529,8 @@ export class CommentView implements IRenderedElement {
this.preResizeSize = this.getSize();
// TODO(#7926): Move this into a utils file.
this.workspace.startDrag(
drag.start(
this.workspace,
e,
new Coordinate(
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. */
private onResizePointerMove(e: PointerEvent) {
// TODO(#7926): Move this into a utils file.
const size = this.workspace.moveDrag(e);
const size = drag.move(this.workspace, e);
this.setSizeWithoutFiringEvents(
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.
*/
private onFoldoutDown(e: PointerEvent) {
touch.clearTouchIdentifier();
this.bringToFront();
if (browserEvents.isRightButton(e)) {
e.stopPropagation();
@@ -747,6 +748,7 @@ export class CommentView implements IRenderedElement {
* delete icon.
*/
private onDeleteDown(e: PointerEvent) {
touch.clearTouchIdentifier();
if (browserEvents.isRightButton(e)) {
e.stopPropagation();
return;
@@ -836,7 +838,6 @@ css.register(`
}
.blocklyCommentTopbarBackground {
cursor: grab;
fill: var(--commentBorderColour);
height: 24px;
}

View File

@@ -4,30 +4,31 @@
* 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 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 {
WorkspaceCommentPaster,
WorkspaceCommentCopyData,
WorkspaceCommentPaster,
} 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 {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
extends WorkspaceComment
@@ -213,6 +214,7 @@ export class RenderedWorkspaceComment
const gesture = this.workspace.getGesture(e);
if (gesture) {
gesture.handleCommentStart(e, this);
this.workspace.getLayerManager()?.append(this, layers.BLOCK);
common.setSelected(this);
}
}

View File

@@ -4,13 +4,14 @@
* 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 {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';
export class WorkspaceComment {
@@ -65,13 +66,13 @@ export class WorkspaceComment {
private fireCreateEvent() {
if (eventUtils.isEnabled()) {
eventUtils.fire(new (eventUtils.get(eventUtils.COMMENT_CREATE))(this));
eventUtils.fire(new (eventUtils.get(EventType.COMMENT_CREATE))(this));
}
}
private fireDeleteEvent() {
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) {
if (eventUtils.isEnabled()) {
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) {
if (eventUtils.isEnabled()) {
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. */
setSize(size: Size) {
const event = new (eventUtils.get(eventUtils.COMMENT_RESIZE))(
const event = new (eventUtils.get(EventType.COMMENT_RESIZE))(
this,
) as CommentResize;
@@ -185,7 +186,11 @@ export class WorkspaceComment {
* workspace is read-only.
*/
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. */
moveTo(location: Coordinate, reason?: string[] | undefined) {
const event = new (eventUtils.get(eventUtils.COMMENT_MOVE))(
const event = new (eventUtils.get(EventType.COMMENT_MOVE))(
this,
) as CommentMove;
if (reason) event.setReason(reason);

View File

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

View File

@@ -23,10 +23,10 @@ class Capability<_T> {
static DRAG_TARGET = new Capability<IDragTarget>('drag_target');
static DELETE_AREA = new Capability<IDeleteArea>('delete_area');
static AUTOHIDEABLE = new Capability<IAutoHideable>('autohideable');
private readonly name_: string;
private readonly name: string;
/** @param name The name of the component capability. */
constructor(name: string) {
this.name_ = name;
this.name = name;
}
/**
@@ -35,7 +35,7 @@ class Capability<_T> {
* @returns The name.
*/
toString(): string {
return this.name_;
return this.name;
}
}
@@ -224,6 +224,16 @@ export class 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. */
export interface ComponentDatum {
component: IComponent;
@@ -232,4 +242,6 @@ export namespace ComponentManager {
}
}
export type ComponentWeight = ComponentManager.ComponentWeight;
export const ComponentWeight = ComponentManager.ComponentWeight;
export type ComponentDatum = ComponentManager.ComponentDatum;

View File

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

View File

@@ -9,23 +9,24 @@
import type {Block} from './block.js';
import type {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js';
import * as common from './common.js';
import {config} from './config.js';
import * as dom from './utils/dom.js';
import type {
ContextMenuOption,
LegacyContextMenuOption,
} from './contextmenu_registry.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js';
import {Menu} from './menu.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 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 WidgetDiv from './widgetdiv.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import * as Xml from './xml.js';
import * as common from './common.js';
/**
* Which block is the context menu attached to?
@@ -260,7 +261,7 @@ export function callbackFactory(
eventUtils.enable();
}
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);
return newBlock;

View File

@@ -9,12 +9,13 @@
import type {BlockSvg} from './block_svg.js';
import * as clipboard from './clipboard.js';
import {RenderedWorkspaceComment} from './comments/rendered_workspace_comment.js';
import * as common from './common.js';
import {MANUALLY_DISABLED} from './constants.js';
import {
ContextMenuRegistry,
RegistryItem,
Scope,
} from './contextmenu_registry.js';
import {MANUALLY_DISABLED} from './constants.js';
import * as dialog from './dialog.js';
import * as Events from './events/events.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 {Coordinate} from './utils/coordinate.js';
import type {WorkspaceSvg} from './workspace_svg.js';
import * as common from './common.js';
/**
* Option to undo previous action.

View File

@@ -23,7 +23,7 @@ import type {WorkspaceSvg} from './workspace_svg.js';
export class ContextMenuRegistry {
static registry: ContextMenuRegistry;
/** 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. */
constructor() {
@@ -32,7 +32,7 @@ export class ContextMenuRegistry {
/** Clear and recreate the registry. */
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.
*/
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.');
}
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.
*/
unregister(id: string) {
if (!this.registry_.has(id)) {
if (!this.registeredItems.has(id)) {
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
*/
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,
): ContextMenuOption[] {
const menuOptions: ContextMenuOption[] = [];
for (const item of this.registry_.values()) {
for (const item of this.registeredItems.values()) {
if (scopeType === item.scopeType) {
const precondition = item.preconditionFn(scope);
if (precondition !== 'hidden') {

View File

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

View File

@@ -14,9 +14,9 @@
import {BlockSvg} from './block_svg.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 {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

View File

@@ -4,9 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Dragger} from './dragging/dragger.js';
import {BlockDragStrategy} from './dragging/block_drag_strategy.js';
import {BubbleDragStrategy} from './dragging/bubble_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
*/
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 * 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 {Connection} from '../connection.js';
import {ConnectionType} from '../connection_type.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 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. */
interface ConnectionCandidate {
@@ -61,6 +62,9 @@ export class BlockDragStrategy implements IDragStrategy {
*/
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) {
this.workspace = block.workspace;
}
@@ -92,7 +96,8 @@ export class BlockDragStrategy implements IDragStrategy {
}
this.dragging = true;
if (!eventUtils.getGroup()) {
this.inGroup = !!eventUtils.getGroup();
if (!this.inGroup) {
eventUtils.setGroup(true);
}
this.fireDragStartEvent();
@@ -173,7 +178,7 @@ export class BlockDragStrategy implements IDragStrategy {
/** Fire a UI event at the start of a block drag. */
private fireDragStartEvent() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
const event = new (eventUtils.get(EventType.BLOCK_DRAG))(
this.block,
true,
this.block.getDescendants(false),
@@ -183,7 +188,7 @@ export class BlockDragStrategy implements IDragStrategy {
/** Fire a UI event at the end of a block drag. */
private fireDragEndEvent() {
const event = new (eventUtils.get(eventUtils.BLOCK_DRAG))(
const event = new (eventUtils.get(EventType.BLOCK_DRAG))(
this.block,
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. */
private fireMoveEvent() {
if (this.block.isDeadOrDying()) return;
const event = new (eventUtils.get(eventUtils.BLOCK_MOVE))(
const event = new (eventUtils.get(EventType.BLOCK_MOVE))(
this.block,
) as BlockMove;
event.setReason(['drag']);
@@ -379,17 +384,24 @@ export class BlockDragStrategy implements IDragStrategy {
if (this.connectionCandidate) {
// Applying connections also rerenders the relevant blocks.
this.applyConnections(this.connectionCandidate);
this.disposeStep();
} 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();
// Must dispose after connections are applied to not break the dynamic
// connections plugin. See #7859
this.connectionPreviewer!.dispose();
this.workspace.setResizesEnabled(true);
eventUtils.setGroup(false);
}
/** Connects the given candidate connections. */

View File

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

View File

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

View File

@@ -4,18 +4,18 @@
* 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 {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 {
protected startLoc: Coordinate;

View File

@@ -14,8 +14,8 @@
import type {BlockSvg} from './block_svg.js';
import * as common from './common.js';
import * as dom from './utils/dom.js';
import type {Field} from './field.js';
import * as dom from './utils/dom.js';
import * as math from './utils/math.js';
import {Rect} from './utils/rect.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;
/** 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. */
let renderedClassName = '';
@@ -196,7 +196,7 @@ export function setColour(backgroundColour: string, borderColour: string) {
export function showPositionedByBlock<T>(
field: Field<T>,
block: BlockSvg,
opt_onHide?: Function,
opt_onHide?: () => void,
opt_secondaryYOffset?: number,
): boolean {
return showPositionedByRect(
@@ -220,7 +220,7 @@ export function showPositionedByBlock<T>(
*/
export function showPositionedByField<T>(
field: Field<T>,
opt_onHide?: Function,
opt_onHide?: () => void,
opt_secondaryYOffset?: number,
): boolean {
positionToField = true;
@@ -272,7 +272,7 @@ function getScaledBboxOfField(field: Field): Rect {
function showPositionedByRect(
bBox: Rect,
field: Field,
opt_onHide?: Function,
opt_onHide?: () => void,
opt_secondaryYOffset?: number,
): boolean {
// If we can fit it, render below the block.
@@ -328,7 +328,7 @@ export function show<T>(
primaryY: number,
secondaryX: number,
secondaryY: number,
opt_onHide?: Function,
opt_onHide?: () => void,
): boolean {
owner = newOwner as Field;
onHide = opt_onHide || null;

View File

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

View File

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

View File

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

View File

@@ -13,15 +13,15 @@
import type {Block} from '../block.js';
import type {BlockSvg} from '../block_svg.js';
import {MANUALLY_DISABLED} from '../constants.js';
import {IconType} from '../icons/icon_types.js';
import {hasBubble} from '../interfaces/i_has_bubble.js';
import {MANUALLY_DISABLED} from '../constants.js';
import * as registry from '../registry.js';
import * as utilsXml from '../utils/xml.js';
import {Workspace} from '../workspace.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {EventType} from './type.js';
import * as eventUtils from './utils.js';
/**
@@ -29,7 +29,7 @@ import * as eventUtils from './utils.js';
* field values, comments, etc).
*/
export class BlockChange extends BlockBase {
override type = eventUtils.BLOCK_CHANGE;
override type = EventType.BLOCK_CHANGE;
/**
* The element that changed; one of 'field', 'comment', 'collapsed',
* 'disabled', 'inline', or 'mutation'
@@ -256,4 +256,4 @@ export interface BlockChangeJson extends BlockBaseJson {
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 blocks from '../serialization/blocks.js';
import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.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
* created.
*/
export class BlockCreate extends BlockBase {
override type = eventUtils.BLOCK_CREATE;
override type = EventType.BLOCK_CREATE;
/** The XML representation of the created block(s). */
xml?: Element | DocumentFragment;
@@ -182,4 +182,4 @@ export interface BlockCreateJson extends BlockBaseJson {
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 blocks from '../serialization/blocks.js';
import * as utilsXml from '../utils/xml.js';
import * as Xml from '../xml.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.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
@@ -38,7 +38,7 @@ export class BlockDelete extends BlockBase {
/** True if the deleted block was a shadow block, false otherwise. */
wasShadow?: boolean;
override type = eventUtils.BLOCK_DELETE;
override type = EventType.BLOCK_DELETE;
/** @param opt_block The deleted block. Undefined for a blank event. */
constructor(opt_block?: Block) {
@@ -179,4 +179,4 @@ export interface BlockDeleteJson extends BlockBaseJson {
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 * as registry from '../registry.js';
import {Workspace} from '../workspace.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 {EventType} from './type.js';
/**
* Notifies listeners when a block is being manually dragged/dropped.
@@ -34,7 +34,7 @@ export class BlockDrag extends UiBase {
*/
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.
@@ -113,4 +113,4 @@ export interface BlockDragJson extends AbstractEventJson {
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 * as registry from '../registry.js';
import {Workspace} from '../workspace.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
@@ -25,7 +24,7 @@ import * as eventUtils from './utils.js';
* event.
*/
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
// 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.Type.EVENT,
eventUtils.BLOCK_FIELD_INTERMEDIATE_CHANGE,
EventType.BLOCK_FIELD_INTERMEDIATE_CHANGE,
BlockFieldIntermediateChange,
);

View File

@@ -15,10 +15,9 @@ import type {Block} from '../block.js';
import {ConnectionType} from '../connection_type.js';
import * as registry from '../registry.js';
import {Coordinate} from '../utils/coordinate.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
import {BlockBase, BlockBaseJson} from './events_block_base.js';
import {EventType} from './type.js';
interface BlockLocation {
parentId?: string;
@@ -31,7 +30,7 @@ interface BlockLocation {
* connection to another, or from one location on the workspace to another.
*/
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. */
oldParentId?: string;
@@ -90,7 +89,7 @@ export class BlockMove extends BlockBase {
this.recordUndo = false;
}
const location = this.currentLocation_();
const location = this.currentLocation();
this.oldParentId = location.parentId;
this.oldInputName = location.inputName;
this.oldCoordinate = location.coordinate;
@@ -168,7 +167,7 @@ export class BlockMove extends BlockBase {
/** Record the block's new location. Called after the move. */
recordNew() {
const location = this.currentLocation_();
const location = this.currentLocation();
this.newParentId = location.parentId;
this.newInputName = location.inputName;
this.newCoordinate = location.coordinate;
@@ -189,7 +188,7 @@ export class BlockMove extends BlockBase {
*
* @returns Collection of location info.
*/
private currentLocation_(): BlockLocation {
private currentLocation(): BlockLocation {
const workspace = this.getEventWorkspace_();
if (!this.blockId) {
throw new Error(
@@ -304,4 +303,4 @@ export interface BlockMoveJson extends BlockBaseJson {
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
*/
// Former goog.module ID: Blockly.Events.BubbleOpen
import type {AbstractEventJson} from './events_abstract.js';
import type {BlockSvg} from '../block_svg.js';
import * as registry from '../registry.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
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.
@@ -31,7 +32,7 @@ export class BubbleOpen extends UiBase {
/** The type of bubble; one of 'mutator', 'comment', or 'warning'. */
bubbleType?: BubbleType;
override type = eventUtils.BUBBLE_OPEN;
override type = EventType.BUBBLE_OPEN;
/**
* @param opt_block The associated block. Undefined for a blank event.
@@ -117,4 +118,4 @@ export interface BubbleOpenJson extends AbstractEventJson {
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
*/
// Former goog.module ID: Blockly.Events.Click
import type {Block} from '../block.js';
import * as registry from '../registry.js';
import {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import * as eventUtils from './utils.js';
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.
@@ -31,7 +31,7 @@ export class Click extends UiBase {
* or 'zoom_controls'.
*/
targetType?: ClickTarget;
override type = eventUtils.CLICK;
override type = EventType.CLICK;
/**
* @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;
}
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 * as comments from '../serialization/workspace_comments.js';
import type {Workspace} from '../workspace.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import type {CommentCreate} from './events_comment_create.js';
import type {CommentDelete} from './events_comment_delete.js';
import * as eventUtils from './utils.js';
import type {Workspace} from '../workspace.js';
import {getGroup, getRecordUndo} from './utils.js';
/**
* Abstract class for a comment event.
@@ -44,8 +44,8 @@ export class CommentBase extends AbstractEvent {
this.commentId = opt_comment.id;
this.workspaceId = opt_comment.workspace.id;
this.group = eventUtils.getGroup();
this.recordUndo = eventUtils.getRecordUndo();
this.group = getGroup();
this.recordUndo = getRecordUndo();
}
/**

View File

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

View File

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

View File

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

View File

@@ -11,20 +11,20 @@
*/
// Former goog.module ID: Blockly.Events.CommentDelete
import * as registry from '../registry.js';
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.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 Xml from '../xml.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.
*/
export class CommentDelete extends CommentBase {
override type = eventUtils.COMMENT_DELETE;
override type = EventType.COMMENT_DELETE;
/** The XML representation of the deleted workspace comment. */
xml?: Element;
@@ -110,8 +110,4 @@ export interface CommentDeleteJson extends CommentBaseJson {
json: object;
}
registry.register(
registry.Type.EVENT,
eventUtils.COMMENT_DELETE,
CommentDelete,
);
registry.register(registry.Type.EVENT, EventType.COMMENT_DELETE, CommentDelete);

View File

@@ -10,10 +10,10 @@
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.js';
import {Workspace} from '../workspace.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 {EventType} from './type.js';
/**
* 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. */
isStart?: boolean;
override type = eventUtils.COMMENT_DRAG;
override type = EventType.COMMENT_DRAG;
/**
* @param opt_comment The comment that is being dragged.
@@ -96,4 +96,4 @@ export interface CommentDragJson extends AbstractEventJson {
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
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.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 {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
/**
* Notifies listeners that a workspace comment has moved.
*/
export class CommentMove extends CommentBase {
override type = eventUtils.COMMENT_MOVE;
override type = EventType.COMMENT_MOVE;
/** The comment that is being moved. */
comment_?: WorkspaceComment;
@@ -204,4 +203,4 @@ export interface CommentMoveJson extends CommentBaseJson {
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.
*/
import type {WorkspaceComment} from '../comments/workspace_comment.js';
import * as registry from '../registry.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 {CommentBase, CommentBaseJson} from './events_comment_base.js';
import {EventType} from './type.js';
/**
* Notifies listeners that a workspace comment has resized.
*/
export class CommentResize extends CommentBase {
override type = eventUtils.COMMENT_RESIZE;
override type = EventType.COMMENT_RESIZE;
/** The size of the comment before the resize. */
oldSize?: Size;
@@ -167,8 +166,4 @@ export interface CommentResizeJson extends CommentBaseJson {
newHeight: number;
}
registry.register(
registry.Type.EVENT,
eventUtils.COMMENT_RESIZE,
CommentResize,
);
registry.register(registry.Type.EVENT, EventType.COMMENT_RESIZE, CommentResize);

View File

@@ -16,9 +16,8 @@ import {ASTNode} from '../keyboard_nav/ast_node.js';
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.js';
import {AbstractEventJson} from './events_abstract.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
@@ -41,7 +40,7 @@ export class MarkerMove extends UiBase {
*/
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
@@ -131,4 +130,4 @@ export interface MarkerMoveJson extends AbstractEventJson {
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
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 {AbstractEventJson} from './events_abstract.js';
import {UiBase} from './events_ui_base.js';
import {EventType} from './type.js';
/**
* Class for a selected event.
@@ -32,7 +31,7 @@ export class Selected extends UiBase {
*/
newElementId?: string;
override type = eventUtils.SELECTED;
override type = EventType.SELECTED;
/**
* @param opt_oldElementId The ID of the previously selected element. Null if
@@ -95,4 +94,4 @@ export interface SelectedJson extends AbstractEventJson {
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
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.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 {EventType} from './type.js';
/**
* 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. */
themeName?: string;
override type = eventUtils.THEME_CHANGE;
override type = EventType.THEME_CHANGE;
/**
* @param opt_themeName The theme name. Undefined for a blank event.
@@ -81,4 +81,4 @@ export interface ThemeChangeJson extends AbstractEventJson {
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
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.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 {EventType} from './type.js';
/**
* Notifies listeners that a toolbox item has been selected.
@@ -27,7 +27,7 @@ export class ToolboxItemSelect extends UiBase {
/** The newly selected toolbox item. */
newItem?: string;
override type = eventUtils.TOOLBOX_ITEM_SELECT;
override type = EventType.TOOLBOX_ITEM_SELECT;
/**
* @param opt_oldItem The previously selected toolbox item.
@@ -91,6 +91,6 @@ export interface ToolboxItemSelectJson extends AbstractEventJson {
registry.register(
registry.Type.EVENT,
eventUtils.TOOLBOX_ITEM_SELECT,
EventType.TOOLBOX_ITEM_SELECT,
ToolboxItemSelect,
);

View File

@@ -12,11 +12,10 @@
// Former goog.module ID: Blockly.Events.TrashcanOpen
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 {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.
@@ -27,7 +26,7 @@ export class TrashcanOpen extends UiBase {
* False if it is currently closing (previously open).
*/
isOpen?: boolean;
override type = eventUtils.TRASHCAN_OPEN;
override type = EventType.TRASHCAN_OPEN;
/**
* @param opt_isOpen Whether the trashcan flyout is opening (false if
@@ -85,4 +84,4 @@ export interface TrashcanOpenJson extends AbstractEventJson {
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,
IVariableState,
} from '../interfaces/i_variable_model.js';
import type {Workspace} from '../workspace.js';
import {
Abstract as AbstractEvent,
AbstractEventJson,
} from './events_abstract.js';
import type {Workspace} from '../workspace.js';
/**
* Abstract class for a variable event.

View File

@@ -11,21 +11,21 @@
*/
// Former goog.module ID: Blockly.Events.VarCreate
import * as registry from '../registry.js';
import type {
IVariableModel,
IVariableState,
} 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 {VarBase, VarBaseJson} from './events_var_base.js';
import {EventType} from './type.js';
/**
* Notifies listeners that a variable model has been created.
*/
export class VarCreate extends VarBase {
override type = eventUtils.VAR_CREATE;
override type = EventType.VAR_CREATE;
/** The type of the variable that was created. */
varType?: string;
@@ -126,4 +126,4 @@ export interface VarCreateJson extends VarBaseJson {
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
import * as registry from '../registry.js';
import type {
IVariableModel,
IVariableState,
} 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 {VarBase, VarBaseJson} from './events_var_base.js';
import {EventType} from './type.js';
/**
* Notifies listeners that a variable model has been deleted.
@@ -22,7 +22,7 @@ import type {Workspace} from '../workspace.js';
* @class
*/
export class VarDelete extends VarBase {
override type = eventUtils.VAR_DELETE;
override type = EventType.VAR_DELETE;
/** The type of the variable that was deleted. */
varType?: string;
/** The name of the variable that was deleted. */
@@ -121,4 +121,4 @@ export interface VarDeleteJson extends VarBaseJson {
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
import * as registry from '../registry.js';
import type {
IVariableModel,
IVariableState,
} 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 {VarBase, VarBaseJson} from './events_var_base.js';
import {EventType} from './type.js';
/**
* Notifies listeners that a variable model was renamed.
@@ -22,7 +22,7 @@ import type {Workspace} from '../workspace.js';
* @class
*/
export class VarRename extends VarBase {
override type = eventUtils.VAR_RENAME;
override type = EventType.VAR_RENAME;
/** The previous name of the variable. */
oldName?: string;
@@ -130,4 +130,4 @@ export interface VarRenameJson extends VarBaseJson {
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
import * as registry from '../registry.js';
import type {Workspace} from '../workspace.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 {EventType} from './type.js';
/**
* 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. */
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
@@ -144,6 +144,6 @@ export interface ViewportChangeJson extends AbstractEventJson {
registry.register(
registry.Type.EVENT,
eventUtils.VIEWPORT_CHANGE,
EventType.VIEWPORT_CHANGE,
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 * as common from '../common.js';
import * as registry from '../registry.js';
import * as deprecation from '../utils/deprecation.js';
import * as idGenerator from '../utils/idgenerator.js';
import type {Workspace} from '../workspace.js';
import type {WorkspaceSvg} from '../workspace_svg.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 {BlockMove} from './events_block_move.js';
import type {CommentCreate} from './events_comment_create.js';
import type {CommentMove} from './events_comment_move.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. */
let group = '';
@@ -49,157 +55,6 @@ export function getRecordUndo(): boolean {
/** Allow change events to be created and fired. */
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
* because the block is not descended from a root block.
@@ -220,27 +75,24 @@ export type BumpEvent =
| CommentMove
| 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. */
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) {
TEST_ONLY.fireInternal(event);
@@ -261,166 +113,187 @@ function fireInternal(event: Abstract) {
requestAnimationFrame(() => {
setTimeout(fireNow, 0);
});
} catch (e) {
} catch {
// Otherwise we just want to delay so events can be coallesced.
// requestAnimationFrame will error triggering this.
setTimeout(fireNow, 0);
}
}
FIRE_QUEUE.push(event);
enqueueEvent(event);
}
/** Fire all queued events. */
/** Dispatch all queued events. */
function fireNow() {
const queue = filter(FIRE_QUEUE, true);
FIRE_QUEUE.length = 0;
for (let i = 0, event; (event = queue[i]); i++) {
if (!event.workspaceId) {
continue;
}
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);
for (const event of queue) {
if (!event.workspaceId) continue;
common.getWorkspaceById(event.workspaceId)?.fireChangeListener(event);
}
}
/**
* 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).
* 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.
*/
export function filter(queueIn: Abstract[], forward: boolean): Abstract[] {
let queue = queueIn.slice();
// Shallow copy of queue.
export function filter(queue: Abstract[], forward = true): Abstract[] {
if (!forward) {
// Undo is merged in reverse order.
queue.reverse();
deprecation.warn('filter(queue, /*forward=*/false)', 'v11.2', 'v12');
// Undo was merged in reverse order.
queue = queue.slice().reverse(); // Copy before reversing in place.
}
const mergedQueue = [];
const hash = Object.create(null);
const mergedQueue: Abstract[] = [];
// Merge duplicates.
for (let i = 0, event; (event = queue[i]); i++) {
if (!event.isNull()) {
// Treat all UI events as the same type in hash table.
const eventType = event.isUiEvent ? UI : event.type;
// TODO(#5927): Check whether `blockId` exists before accessing it.
const blockId = (event as AnyDuringMigration).blockId;
const key = [eventType, blockId, event.workspaceId].join(' ');
const lastEntry = hash[key];
const lastEvent = lastEntry ? lastEntry.event : null;
if (!lastEntry) {
// Each item in the hash table has the event and the index of that event
// in the input array. This lets us make sure we only merge adjacent
// move events.
hash[key] = {event, index: i};
mergedQueue.push(event);
} else if (event.type === MOVE && lastEntry.index === i - 1) {
const moveEvent = event as BlockMove;
// Merge move events.
lastEvent.newParentId = moveEvent.newParentId;
lastEvent.newInputName = moveEvent.newInputName;
lastEvent.newCoordinate = moveEvent.newCoordinate;
if (moveEvent.reason) {
if (lastEvent.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);
for (const event of queue) {
const lastEvent = mergedQueue[mergedQueue.length - 1];
if (event.isNull()) continue;
if (
!lastEvent ||
lastEvent.workspaceId !== event.workspaceId ||
lastEvent.group !== event.group
) {
mergedQueue.push(event);
continue;
}
if (
isBlockMove(event) &&
isBlockMove(lastEvent) &&
event.blockId === lastEvent.blockId
) {
// Merge move events.
lastEvent.newParentId = event.newParentId;
lastEvent.newInputName = event.newInputName;
lastEvent.newCoordinate = event.newCoordinate;
// Concatenate reasons without duplicates.
if (lastEvent.reason || event.reason) {
lastEvent.reason = Array.from(
new Set((lastEvent.reason ?? []).concat(event.reason ?? [])),
);
}
} 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.
queue = mergedQueue.filter(function (e) {
return !e.isNull();
});
queue = mergedQueue.filter((e) => !e.isNull());
if (!forward) {
// Restore undo order.
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;
}
@@ -545,7 +418,7 @@ export function get(
* @param event Custom data for event.
*/
export function disableOrphans(event: Abstract) {
if (event.type === MOVE || event.type === CREATE) {
if (isBlockMove(event) || isBlockCreate(event)) {
const blockEvent = event as BlockMove | BlockCreate;
if (!blockEvent.workspaceId) {
return;
@@ -589,6 +462,7 @@ export function disableOrphans(event: Abstract) {
export const TEST_ONLY = {
FIRE_QUEUE,
enqueueEvent,
fireNow,
fireInternal,
setGroupInternal,

View File

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

View File

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

View File

@@ -14,9 +14,9 @@
// Unused import preserved for side-effects. Remove if unneeded.
import './events/events_block_change.js';
import * as dom from './utils/dom.js';
import {Field, FieldConfig, FieldValidator} from './field.js';
import * as fieldRegistry from './field_registry.js';
import * as dom from './utils/dom.js';
type BoolString = 'TRUE' | 'FALSE';
type CheckboxBool = BoolString | boolean;
@@ -165,7 +165,7 @@ export class FieldCheckbox extends Field<CheckboxBool> {
* that this is a either 'TRUE' or 'FALSE'.
*/
protected override doValueUpdate_(newValue: BoolString) {
this.value_ = this.convertValueToBool_(newValue);
this.value_ = this.convertValueToBool(newValue);
// Update visual.
if (this.textElement_) {
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').
*/
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.
* @returns The converted value.
*/
private convertValueToBool_(value: CheckboxBool | null): boolean {
private convertValueToBool(value: CheckboxBool | null): boolean {
if (typeof value === 'string') return value === 'TRUE';
return !!value;
}

View File

@@ -24,12 +24,12 @@ import {
import * as fieldRegistry from './field_registry.js';
import {Menu} from './menu.js';
import {MenuItem} from './menuitem.js';
import * as style from './utils/style.js';
import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js';
import * as utilsString from './utils/string.js';
import * as style from './utils/style.js';
import {Svg} from './utils/svg.js';
/**
@@ -92,6 +92,15 @@ export class FieldDropdown extends Field<string> {
private selectedOption!: MenuOption;
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
* 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 (Array.isArray(menuGenerator)) {
validateOptions(menuGenerator);
const trimmed = trimOptions(menuGenerator);
this.validateOptions(menuGenerator);
const trimmed = this.trimOptions(menuGenerator);
this.menuGenerator_ = trimmed.options;
this.prefixField = trimmed.prefix || null;
this.suffixField = trimmed.suffix || null;
@@ -403,7 +412,7 @@ export class FieldDropdown extends Field<string> {
if (useCache && this.generatedOptions) return this.generatedOptions;
this.generatedOptions = this.menuGenerator_();
validateOptions(this.generatedOptions);
this.validateOptions(this.generatedOptions);
return this.generatedOptions;
}
@@ -522,7 +531,7 @@ export class FieldDropdown extends Field<string> {
const hasBorder = !!this.borderRect_;
const height = Math.max(
hasBorder ? this.getConstants()!.FIELD_DROPDOWN_BORDER_RECT_HEIGHT : 0,
imageHeight + IMAGE_Y_PADDING,
imageHeight + FieldDropdown.IMAGE_Y_PADDING,
);
const xPadding = hasBorder
? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING
@@ -653,6 +662,127 @@ export class FieldDropdown extends Field<string> {
// override the static fromJson method.
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>;
/**
* 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);

View File

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

View File

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

View File

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

View File

@@ -11,17 +11,22 @@
*/
// Former goog.module ID: Blockly.Flyout
import type {Abstract as AbstractEvent} from './events/events_abstract.js';
import {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js';
import {ComponentManager} from './component_manager.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 {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 {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
import type {IBoundedElement} from './interfaces/i_bounded_element.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 * as blocks from './serialization/blocks.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 Variables from './variables.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.
@@ -152,7 +153,7 @@ export abstract class Flyout
/**
* Whether the flyout is visible.
*/
private isVisible_ = false;
private visible = false;
/**
* Whether the workspace containing this flyout is visible.
@@ -237,7 +238,7 @@ export abstract class Flyout
this.workspace_.internalIsFlyout = true;
// 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
@@ -369,7 +370,7 @@ export abstract class Flyout
targetWorkspace.getComponentManager().addComponent({
component: this,
weight: 1,
weight: ComponentManager.ComponentWeight.FLYOUT_WEIGHT,
capabilities: [
ComponentManager.Capability.AUTOHIDEABLE,
ComponentManager.Capability.DELETE_AREA,
@@ -470,7 +471,7 @@ export abstract class Flyout
* @returns True if visible.
*/
isVisible(): boolean {
return this.isVisible_;
return this.visible;
}
/**
@@ -483,7 +484,7 @@ export abstract class Flyout
setVisible(visible: boolean) {
const visibilityChanged = visible !== this.isVisible();
this.isVisible_ = visible;
this.visible = visible;
if (visibilityChanged) {
if (!this.autoClose) {
// 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++) {
const thisVariable = newVariables[i];
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
// variables.
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock));
eventUtils.fire(new (eventUtils.get(EventType.BLOCK_CREATE))(newBlock));
}
if (this.autoClose) {
this.hide();

View File

@@ -11,19 +11,19 @@
*/
// Former goog.module ID: Blockly.FlyoutButton
import type {IASTNodeLocationSvg} from './blockly.js';
import * as browserEvents from './browser_events.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 * as dom from './utils/dom.js';
import * as parsing from './utils/parsing.js';
import {Rect} from './utils/rect.js';
import * as style from './utils/style.js';
import {Svg} from './utils/svg.js';
import type * as toolbox from './utils/toolbox.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.

View File

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

View File

@@ -24,7 +24,7 @@ import type {Workspace} from './workspace.js';
* @deprecated
* @see {@link https://developers.google.com/blockly/guides/create-custom-blocks/generating-code}
* @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),
* or a [code, precedence] tuple (for value/expression blocks), or
* 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 type {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js';
import {RenderedWorkspaceComment} from './comments.js';
import * as common from './common.js';
import {config} from './config.js';
import * as dropDownDiv from './dropdowndiv.js';
import {EventType} from './events/type.js';
import * as eventUtils from './events/utils.js';
import type {Field} from './field.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 {IIcon} from './interfaces/i_icon.js';
import * as registry from './registry.js';
import * as Tooltip from './tooltip.js';
import * as Touch from './touch.js';
import {Coordinate} from './utils/coordinate.js';
import {WorkspaceDragger} from './workspace_dragger.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
@@ -145,7 +146,7 @@ export class Gesture {
private mostRecentEvent: PointerEvent;
/** 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. */
private cachedPoints = new Map<string, Coordinate | null>();
@@ -585,7 +586,7 @@ export class Gesture {
const point0 = this.cachedPoints.get(pointers[0])!;
const point1 = this.cachedPoints.get(pointers[1])!;
this.startDistance = Coordinate.distance(point0, point1);
this.isMultiTouch_ = true;
this.multiTouch = true;
e.preventDefault();
}
}
@@ -599,13 +600,20 @@ export class Gesture {
*/
handleTouchMove(e: PointerEvent) {
const pointerId = Touch.getTouchIdentifierFromEvent(e);
// Update the cache
this.cachedPoints.set(pointerId, this.getTouchPoint(e));
if (this.isPinchZoomEnabled && this.cachedPoints.size === 2) {
this.handlePinch(e);
} 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
*/
isMultiTouch(): boolean {
return this.isMultiTouch_;
return this.multiTouch;
}
/**
@@ -769,7 +777,7 @@ export class Gesture {
*/
private fireWorkspaceClick(ws: WorkspaceSvg) {
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.
const event = new (eventUtils.get(eventUtils.CLICK))(
const event = new (eventUtils.get(EventType.CLICK))(
this.startBlock,
this.startWorkspace_.id,
'block',

View File

@@ -12,10 +12,10 @@
*/
// 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 {Coordinate} from './utils/coordinate.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
/**
* Class for a workspace's grid.

View File

@@ -4,21 +4,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Icon} from './icons/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 {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 {WarningIcon} from './icons/warning_icon.js';
export {
Icon,
CommentIcon,
CommentState,
MutatorIcon,
WarningIcon,
IconType,
exceptions,
Icon,
IconType,
MutatorIcon,
registry,
WarningIcon,
};

View File

@@ -8,21 +8,21 @@
import type {Block} from '../block.js';
import type {BlockSvg} from '../block_svg.js';
import {IconType} from './icon_types.js';
import {Coordinate} from '../utils.js';
import * as dom from '../utils/dom.js';
import {TextInputBubble} from '../bubbles/textinput_bubble.js';
import {EventType} from '../events/type.js';
import * as eventUtils from '../events/utils.js';
import {Icon} from './icon.js';
import type {IHasBubble} from '../interfaces/i_has_bubble.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 * as registry from './registry.js';
import {Size} from '../utils/size.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 * 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. */
const SIZE = 17;
@@ -46,12 +46,9 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
*/
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;
/** The bubble used to show non-editable text to the user. */
private textBubble: TextBubble | null = null;
/** The text of this comment. */
private text = '';
@@ -120,7 +117,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
override dispose() {
super.dispose();
this.textInputBubble?.dispose();
this.textBubble?.dispose();
}
override getWeight(): number {
@@ -135,7 +131,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
super.applyColour();
const colour = (this.sourceBlock as BlockSvg).style.colourPrimary;
this.textInputBubble?.setColour(colour);
this.textBubble?.setColour(colour);
}
/**
@@ -161,14 +156,13 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
}
const anchorLocation = this.getAnchorLocation();
this.textInputBubble?.setAnchorLocation(anchorLocation);
this.textBubble?.setAnchorLocation(anchorLocation);
}
/** Sets the text of this comment. Updates any bubbles if they are visible. */
setText(text: string) {
const oldText = this.text;
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock,
'comment',
null,
@@ -178,7 +172,6 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
);
this.text = text;
this.textInputBubble?.setText(this.text);
this.textBubble?.setText(this.text);
}
/** Returns the text of this comment. */
@@ -282,7 +275,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
if (this.text === newText) return;
eventUtils.fire(
new (eventUtils.get(eventUtils.BLOCK_CHANGE))(
new (eventUtils.get(EventType.BLOCK_CHANGE))(
this.sourceBlock,
'comment',
null,
@@ -338,7 +331,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
}
eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
new (eventUtils.get(EventType.BUBBLE_OPEN))(
this.sourceBlock,
visible,
'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.
*/
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.sourceBlock.workspace as WorkspaceSvg,
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. */
private hideBubble() {
this.textInputBubble?.dispose();
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.
*/
private getBubbleOwnerRect(): Rect {
const bbox = (this.sourceBlock as BlockSvg).getSvgRoot().getBBox();
return new Rect(bbox.y, bbox.y + bbox.height, bbox.x, bbox.x + bbox.width);
return (this.sourceBlock as BlockSvg).getBoundingRectangleWithoutChildren();
}
}

View File

@@ -9,12 +9,12 @@ import type {BlockSvg} from '../block_svg.js';
import * as browserEvents from '../browser_events.js';
import {hasBubble} from '../interfaces/i_has_bubble.js';
import type {IIcon} from '../interfaces/i_icon.js';
import * as tooltip from '../tooltip.js';
import {Coordinate} from '../utils/coordinate.js';
import * as dom from '../utils/dom.js';
import {Size} from '../utils/size.js';
import {Svg} from '../utils/svg.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

View File

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

View File

@@ -7,17 +7,18 @@
// Former goog.module ID: Blockly.Warning
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 * 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 {Size} from '../utils.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 * as renderManagement from '../render_management.js';
/** The size of the warning icon in workspace-scale units. */
const SIZE = 17;
@@ -188,7 +189,7 @@ export class WarningIcon extends Icon implements IHasBubble {
}
eventUtils.fire(
new (eventUtils.get(eventUtils.BUBBLE_OPEN))(
new (eventUtils.get(EventType.BUBBLE_OPEN))(
this.sourceBlock,
visible,
'warning',

View File

@@ -22,7 +22,6 @@ import * as Touch from './touch.js';
import * as aria from './utils/aria.js';
import * as dom from './utils/dom.js';
import {Svg} from './utils/svg.js';
import * as userAgent from './utils/useragent.js';
import * as WidgetDiv from './widgetdiv.js';
import {WorkspaceSvg} from './workspace_svg.js';
@@ -342,18 +341,6 @@ function bindDocumentEvents() {
// should run regardless of what other touch event handlers have run.
browserEvents.bind(document, 'touchend', 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;
}

View File

@@ -5,19 +5,19 @@
*/
import {Align} from './inputs/align.js';
import {Input} from './inputs/input.js';
import {DummyInput} from './inputs/dummy_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 {ValueInput} from './inputs/value_input.js';
import {inputTypes} from './inputs/input_types.js';
export {
Align,
Input,
DummyInput,
EndRowInput,
Input,
inputTypes,
StatementInput,
ValueInput,
inputTypes,
};

View File

@@ -21,8 +21,8 @@ import type {ConnectionType} from '../connection_type.js';
import type {Field} from '../field.js';
import * as fieldRegistry from '../field_registry.js';
import type {RenderedConnection} from '../rendered_connection.js';
import {inputTypes} from './input_types.js';
import {Align} from './align.js';
import {inputTypes} from './input_types.js';
/** Class for an input with optional fields. */
export class Input {

View File

@@ -5,15 +5,15 @@
*/
import {BlockSvg} from './block_svg.js';
import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js';
import {RenderedConnection} from './rendered_connection.js';
import {WorkspaceSvg} from './workspace_svg.js';
import * as blocks from './serialization/blocks.js';
import * as eventUtils from './events/utils.js';
import * as renderManagement from './render_management.js';
import * as registry from './registry.js';
import {Renderer as ZelosRenderer} from './renderers/zelos/renderer.js';
import {ConnectionType} from './connection_type.js';
import * as eventUtils from './events/utils.js';
import {IConnectionPreviewer} from './interfaces/i_connection_previewer.js';
import * as registry from './registry.js';
import * as renderManagement from './render_management.js';
import {RenderedConnection} from './rendered_connection.js';
import {Renderer as ZelosRenderer} from './renderers/zelos/renderer.js';
import * as blocks from './serialization/blocks.js';
import {WorkspaceSvg} from './workspace_svg.js';
export class InsertionMarkerPreviewer implements IConnectionPreviewer {
private readonly workspace: WorkspaceSvg;

View File

@@ -6,8 +6,8 @@
// Former goog.module ID: Blockly.IASTNodeLocationWithBlock
import type {IASTNodeLocation} from './i_ast_node_location.js';
import type {Block} from '../block.js';
import type {IASTNodeLocation} from './i_ast_node_location.js';
/**
* An AST node location that has an associated block.

View File

@@ -4,9 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type {Rect} from '../utils/rect.js';
// Former goog.module ID: Blockly.IBoundedElement
import type {Rect} from '../utils/rect.js';
/**
* A bounded element interface.
*/

View File

@@ -4,9 +4,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type {Coordinate} from '../utils/coordinate.js';
// Former goog.module ID: Blockly.IBubble
import type {Coordinate} from '../utils/coordinate.js';
import type {IContextMenu} from './i_contextmenu.js';
import type {IDraggable} from './i_draggable.js';

View File

@@ -4,12 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {IconType} from '../icons/icon_types.js';
import {CommentState} from '../icons/comment_icon.js';
import {IIcon, isIcon} from './i_icon.js';
import {IconType} from '../icons/icon_types.js';
import {Size} from '../utils/size.js';
import {Coordinate} from '../utils/coordinate.js';
import {IHasBubble, hasBubble} from './i_has_bubble.js';
import {IIcon, isIcon} from './i_icon.js';
import {ISerializable, isSerializable} from './i_serializable.js';
export interface ICommentIcon extends IIcon, IHasBubble, ISerializable {
@@ -31,7 +31,7 @@ export interface ICommentIcon extends IIcon, IHasBubble, ISerializable {
}
/** Checks whether the given object is an ICommentIcon. */
export function isCommentIcon(obj: Object): obj is ICommentIcon {
export function isCommentIcon(obj: object): obj is ICommentIcon {
return (
isIcon(obj) &&
hasBubble(obj) &&

View File

@@ -4,9 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
// Former goog.module ID: Blockly.IConnectionChecker
import type {Connection} from '../connection.js';
import type {RenderedConnection} from '../rendered_connection.js';
// Former goog.module ID: Blockly.IConnectionChecker
/**
* Class for connection type checking logic.

View File

@@ -4,13 +4,11 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {Rect} from '../utils/rect.js';
import {IDraggable} from './i_draggable.js';
// Former goog.module ID: Blockly.IDragTarget
import {Rect} from '../utils/rect.js';
import type {IComponent} from './i_component.js';
import {IDraggable} from './i_draggable.js';
/**
* Interface for a component with custom behaviour when a block or bubble is

View File

@@ -6,13 +6,13 @@
// Former goog.module ID: Blockly.IFlyout
import type {WorkspaceSvg} from '../workspace_svg.js';
import type {BlockSvg} from '../block_svg.js';
import type {Coordinate} from '../utils/coordinate.js';
import type {FlyoutDefinition} from '../utils/toolbox.js';
import type {Svg} from '../utils/svg.js';
import type {IRegistrable} from './i_registrable.js';
import {FlyoutItem} from '../flyout_base.js';
import type {Coordinate} from '../utils/coordinate.js';
import type {Svg} from '../utils/svg.js';
import type {FlyoutDefinition} from '../utils/toolbox.js';
import type {WorkspaceSvg} from '../workspace_svg.js';
import type {IRegistrable} from './i_registrable.js';
/**
* Interface for a flyout.

View File

@@ -4,9 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
import {KeyboardShortcut} from '../shortcut_registry.js';
// Former goog.module ID: Blockly.IKeyboardAccessible
import {KeyboardShortcut} from '../shortcut_registry.js';
/**
* An interface for an object that handles keyboard shortcuts.
*/

View File

@@ -28,7 +28,7 @@ export interface LegacyProcedureDefBlock {
/** @internal */
export function isLegacyProcedureDefBlock(
block: Object,
block: object,
): block is LegacyProcedureDefBlock {
return (block as any).getProcedureDef !== undefined;
}
@@ -41,7 +41,7 @@ export interface LegacyProcedureCallBlock {
/** @internal */
export function isLegacyProcedureCallBlock(
block: Object,
block: object,
): block is LegacyProcedureCallBlock {
return (
(block as any).getProcedureCall !== undefined &&

View File

@@ -4,15 +4,16 @@
* SPDX-License-Identifier: Apache-2.0
*/
// Former goog.module ID: Blockly.IMetricsManager
import type {
AbsoluteMetrics,
ContainerRegion,
ToolboxMetrics,
AbsoluteMetrics,
UiMetrics,
} from '../metrics_manager.js';
import type {Size} from '../utils/size.js';
import type {Metrics} from '../utils/metrics.js';
// Former goog.module ID: Blockly.IMetricsManager
import type {Size} from '../utils/size.js';
/**
* Interface for a metrics manager.

View File

@@ -4,10 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
import type {Rect} from '../utils/rect.js';
import type {UiMetrics} from '../metrics_manager.js';
// Former goog.module ID: Blockly.IPositionable
import type {UiMetrics} from '../metrics_manager.js';
import type {Rect} from '../utils/rect.js';
import type {IComponent} from './i_component.js';
/**

View File

@@ -4,9 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
// Former goog.module ID: Blockly.procedures.IProcedureBlock
import type {Block} from '../block.js';
import {IProcedureModel} from './i_procedure_model.js';
// Former goog.module ID: Blockly.procedures.IProcedureBlock
/** The interface for a block which models a procedure. */
export interface IProcedureBlock {

View File

@@ -24,7 +24,7 @@ export interface ISelectable {
}
/** Checks whether the given object is an ISelectable. */
export function isSelectable(obj: Object): obj is ISelectable {
export function isSelectable(obj: object): obj is ISelectable {
return (
typeof (obj as any).id === 'string' &&
(obj as any).workspace !== undefined &&

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